mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-10-30 06:35:13 +08:00
5251 lines
168 KiB
CSS
5251 lines
168 KiB
CSS
/*!
|
||
* LuCI Liquid Glass Theme — cascade.css
|
||
*
|
||
* A macOS-style liquid glass theme for LuCI >= 23 (23.05 / master).
|
||
* Light / dark / auto modes are driven by <html data-darkmode="true|false">
|
||
* (set by main.js or the inline script in header.ut).
|
||
*
|
||
* Copyright 2026 然后七年 <69092025+zzsj0928@users.noreply.github.com>
|
||
* Licensed to the public under the Apache License 2.0.
|
||
*/
|
||
|
||
/* ==========================================================================
|
||
1. Design tokens
|
||
========================================================================== */
|
||
|
||
:root {
|
||
/* -- LuCI compatible palette (light) -- */
|
||
--background-color-high: #f4f6fa;
|
||
--background-color-medium: #e9edf4;
|
||
--background-color-low: #dfe5ef;
|
||
|
||
--text-color-highest: #10151d;
|
||
--text-color-high: #1d2633;
|
||
--text-color-medium: #4c5868;
|
||
--text-color-low: #8a95a6;
|
||
|
||
--border-color-high: rgba(23, 34, 52, 0.20);
|
||
--border-color-medium: rgba(23, 34, 52, 0.13);
|
||
--border-color-low: rgba(23, 34, 52, 0.07);
|
||
|
||
--primary-color-high: #2f7fe0;
|
||
--primary-color-medium: #1f6fc8;
|
||
--primary-color-low: #0f56a8;
|
||
--on-primary-color: #ffffff;
|
||
|
||
--error-color-high-rgb: 224, 58, 58;
|
||
--error-color-high: rgb(var(--error-color-high-rgb));
|
||
--error-color-medium: #d23c3c;
|
||
--error-color-low: #b52a2a;
|
||
--on-error-color: #ffffff;
|
||
|
||
--success-color-high-rgb: 16, 166, 96;
|
||
--success-color-high: rgb(var(--success-color-high-rgb));
|
||
--success-color-medium: #0d9a58;
|
||
--success-color-low: #0a8048;
|
||
--on-success-color: #ffffff;
|
||
|
||
--warn-color-high: #e0a013;
|
||
--warn-color-medium: #d99414;
|
||
--warn-color-low: #c98716;
|
||
--on-warn-color: #3a2c00;
|
||
|
||
--disabled-opacity: .65;
|
||
|
||
color-scheme: light;
|
||
|
||
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||
"Helvetica Neue", Arial, ui-sans-serif, system-ui, sans-serif;
|
||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas,
|
||
"Liberation Mono", monospace;
|
||
|
||
/* -- liquid glass extras (light) -- */
|
||
--glass-blur: 26px;
|
||
--glass-sat: 1.6;
|
||
--glass-radius: 18px;
|
||
--glass-radius-lg: 24px;
|
||
--glass-opacity: 1;
|
||
--glass-bg: rgba(255, 255, 255, calc(0.42 * var(--glass-opacity)));
|
||
--glass-bg-strong: rgba(255, 255, 255, calc(0.66 * var(--glass-opacity)));
|
||
/* 亮色 hover 背景用浅主题色 tint(纯白 hover 会发白、看不清字) */
|
||
--glass-bg-hover: color-mix(in srgb, color-mix(in srgb, var(--primary-color-high) 30%, #ffffff) calc(100% * var(--glass-opacity)), transparent);
|
||
/* picker 选项高亮:沿用 glass-bg-hover 逻辑(亮底对比度已足够) */
|
||
--picker-option-hover: var(--glass-bg-hover);
|
||
--picker-option-checked: var(--glass-bg-hover);
|
||
/* 表格隔行:不透明浅色(半透明玻璃隔行会让图标文字显得半透,
|
||
误以为接口未连接) */
|
||
--table-stripe: #f4f6f9;
|
||
--tab-active-text-color: var(--text-color-highest);
|
||
--glass-border: rgba(255, 255, 255, calc(0.8 * var(--glass-opacity)));
|
||
--glass-border-dark: rgba(23, 34, 52, calc(0.09 * var(--glass-opacity)));
|
||
--glass-inset: rgba(255, 255, 255, calc(0.95 * var(--glass-opacity)));
|
||
--glass-shadow: 0 22px 54px rgba(31, 45, 70, 0.16), 0 2px 8px rgba(31, 45, 70, 0.06);
|
||
--glass-shadow-soft: 0 8px 24px rgba(31, 45, 70, 0.10);
|
||
|
||
--menu-bg: rgba(255, 255, 255, calc(0.36 * var(--glass-opacity)));
|
||
--field-bg: rgba(255, 255, 255, calc(0.56 * var(--glass-opacity)));
|
||
--track-bg: rgba(23, 34, 52, calc(0.10 * var(--glass-opacity)));
|
||
|
||
--accent-grad: linear-gradient(135deg, #5aa8f7 0%, #2f7fe0 48%, #1a63c4 100%);
|
||
--accent-glow: rgba(47, 127, 224, 0.38);
|
||
--lock-card-bg: rgba(255, 255, 255, calc(0.5 * var(--glass-opacity)));
|
||
--indicator-bg: var(--accent-glass-soft);
|
||
--indicator-border: color-mix(in srgb, var(--primary-color-high) 48%, transparent);
|
||
--indicator-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color-high) 30%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||
--accent-glass: linear-gradient(135deg, rgba(90, 168, 247, 0.32), rgba(255, 255, 255, 0.62));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(90, 168, 247, 0.26), rgba(255, 255, 255, 0.72));
|
||
/* 移动端 chrome 条(菜单栏/面包屑/底部 foot/菜单抽屉)去掉
|
||
backdrop-filter 后的等价不透明色:分别是 --glass-bg-strong、
|
||
--menu-bg 叠 --background-color-medium 的合成值(与 meta
|
||
theme-color 一致,main.js syncThemeColor 同公式动态跟随)。
|
||
安卓 Chrome 的地址栏收缩动画与 sticky/fixed 元素的
|
||
backdrop-filter 重采样冲突(iOS 不受影响),移动端禁模糊改实底 */
|
||
--glass-chrome-solid: #f8f9fb;
|
||
--glass-menu-solid: #f1f3f8;
|
||
--body-grad:
|
||
radial-gradient(1100px 750px at 85% -10%, rgba(96, 165, 250, 0.38), transparent 60%),
|
||
radial-gradient(950px 700px at -10% 30%, rgba(167, 139, 250, 0.30), transparent 55%),
|
||
radial-gradient(900px 620px at 55% 118%, rgba(45, 212, 191, 0.24), transparent 55%),
|
||
linear-gradient(160deg, #eef2f9 0%, #e7edf6 50%, #e2e9f5 100%);
|
||
}
|
||
|
||
:root[data-darkmode="true"] {
|
||
/* -- LuCI compatible palette (dark) -- */
|
||
--background-color-high: #10141c;
|
||
--background-color-medium: #161c27;
|
||
--background-color-low: #1d2532;
|
||
|
||
--text-color-highest: #f1f4f9;
|
||
--text-color-high: #d9dfe9;
|
||
--text-color-medium: #96a2b6;
|
||
--text-color-low: #66728a;
|
||
|
||
--border-color-high: rgba(255, 255, 255, 0.20);
|
||
--border-color-medium: rgba(255, 255, 255, 0.13);
|
||
--border-color-low: rgba(255, 255, 255, 0.07);
|
||
|
||
--primary-color-high: #5da3f0;
|
||
--primary-color-medium: #4790e6;
|
||
--primary-color-low: #2f74cc;
|
||
--on-primary-color: #0b1220;
|
||
|
||
--error-color-high-rgb: 226, 98, 98;
|
||
--error-color-high: rgb(var(--error-color-high-rgb));
|
||
--error-color-medium: #cf5f5f;
|
||
--error-color-low: #b34a4a;
|
||
--on-error-color: #0b1220;
|
||
|
||
--success-color-high-rgb: 46, 185, 118;
|
||
--success-color-high: rgb(var(--success-color-high-rgb));
|
||
--success-color-medium: #38a86f;
|
||
--success-color-low: #2f8f5d;
|
||
--on-success-color: #0b1220;
|
||
|
||
--warn-color-high: #d9a33a;
|
||
--warn-color-medium: #c99430;
|
||
--warn-color-low: #b8842a;
|
||
--on-warn-color: #221a05;
|
||
|
||
--disabled-opacity: .45;
|
||
|
||
color-scheme: dark;
|
||
|
||
--glass-bg: rgba(24, 32, 48, calc(0.4 * var(--glass-opacity)));
|
||
--glass-bg-strong: rgba(22, 29, 44, calc(0.74 * var(--glass-opacity)));
|
||
--glass-bg-hover: rgba(42, 54, 78, calc(0.55 * var(--glass-opacity)));
|
||
/* picker 选项高亮:暗底需要更亮的选中/悬停色,否则看不清 */
|
||
--picker-option-hover: rgba(70, 100, 160, calc(0.55 * var(--glass-opacity)));
|
||
--picker-option-checked: rgba(60, 90, 150, calc(0.45 * var(--glass-opacity)));
|
||
--table-stripe: #1b2330;
|
||
--tab-active-text-color: var(--text-color-highest);
|
||
--glass-border: rgba(255, 255, 255, calc(0.16 * var(--glass-opacity)));
|
||
--glass-border-dark: rgba(0, 0, 0, calc(0.42 * var(--glass-opacity)));
|
||
--glass-inset: rgba(255, 255, 255, calc(0.10 * var(--glass-opacity)));
|
||
--glass-shadow: 0 26px 64px rgba(0, 0, 0, 0.6), 0 2px 10px rgba(0, 0, 0, 0.32);
|
||
--glass-shadow-soft: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||
|
||
--menu-bg: rgba(12, 17, 27, calc(0.52 * var(--glass-opacity)));
|
||
--field-bg: rgba(255, 255, 255, calc(0.07 * var(--glass-opacity)));
|
||
--track-bg: rgba(255, 255, 255, calc(0.10 * var(--glass-opacity)));
|
||
|
||
/* 移动端 chrome 条实色等价(暗):见亮色同名变量注释 */
|
||
--glass-chrome-solid: #151c2b;
|
||
--glass-menu-solid: #111621;
|
||
|
||
--accent-grad: linear-gradient(135deg, #7cbcff 0%, #5da3f0 48%, #2f74cc 100%);
|
||
--accent-glow: rgba(93, 163, 240, 0.4);
|
||
--lock-card-bg: rgba(13, 18, 28, calc(0.55 * var(--glass-opacity)));
|
||
--indicator-bg: var(--accent-glass-soft);
|
||
--indicator-border: rgba(255, 255, 255, 0.18);
|
||
--indicator-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||
--accent-glass: linear-gradient(135deg, rgba(93, 163, 240, 0.30), rgba(46, 58, 84, 0.5));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(93, 163, 240, 0.24), rgba(46, 58, 84, 0.45));
|
||
|
||
|
||
--body-grad:
|
||
radial-gradient(1100px 750px at 85% -10%, rgba(47, 102, 196, 0.42), transparent 60%),
|
||
radial-gradient(950px 700px at -10% 30%, rgba(109, 70, 220, 0.34), transparent 55%),
|
||
radial-gradient(900px 620px at 55% 118%, rgba(13, 148, 136, 0.26), transparent 55%),
|
||
linear-gradient(160deg, #0d1220 0%, #0a0f18 50%, #060a12 100%);
|
||
}
|
||
/* ==========================================================================
|
||
1b. Accent color themes (data-accent on <html>)
|
||
========================================================================== */
|
||
|
||
:root[data-accent="magenta"] {
|
||
--primary-color-high: #d63384;
|
||
--primary-color-medium: #bd2c72;
|
||
--primary-color-low: #9c1f5e;
|
||
--accent-grad: linear-gradient(135deg, #f270b5 0%, #d63384 48%, #a61e6a 100%);
|
||
--accent-glow: rgba(214, 51, 132, 0.38);
|
||
--accent-glass: linear-gradient(135deg, rgba(214, 51, 132, 0.30), rgba(255, 255, 255, 0.62));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(214, 51, 132, 0.24), rgba(255, 255, 255, 0.72));
|
||
}
|
||
|
||
:root[data-darkmode="true"][data-accent="magenta"] {
|
||
--primary-color-high: #e86fb0;
|
||
--primary-color-medium: #dc5aa0;
|
||
--primary-color-low: #c23e8f;
|
||
--accent-grad: linear-gradient(135deg, #f59ad0 0%, #e86fb0 48%, #c23e8f 100%);
|
||
--accent-glow: rgba(232, 111, 176, 0.4);
|
||
--accent-glass: linear-gradient(135deg, rgba(232, 111, 176, 0.28), rgba(46, 58, 84, 0.45));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(232, 111, 176, 0.22), rgba(46, 58, 84, 0.4));
|
||
}
|
||
|
||
:root[data-accent="amber"] {
|
||
--primary-color-high: #e8940f;
|
||
--primary-color-medium: #d1810d;
|
||
--primary-color-low: #b06e0a;
|
||
--accent-grad: linear-gradient(135deg, #ffc04a 0%, #e8940f 48%, #c47100 100%);
|
||
--accent-glow: rgba(232, 148, 15, 0.38);
|
||
--accent-glass: linear-gradient(135deg, rgba(232, 148, 15, 0.30), rgba(255, 255, 255, 0.62));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(232, 148, 15, 0.24), rgba(255, 255, 255, 0.72));
|
||
}
|
||
|
||
:root[data-darkmode="true"][data-accent="amber"] {
|
||
--primary-color-high: #f5b74a;
|
||
--primary-color-medium: #eda63a;
|
||
--primary-color-low: #d99414;
|
||
--accent-grad: linear-gradient(135deg, #ffd98a 0%, #f5b74a 48%, #d99414 100%);
|
||
--accent-glow: rgba(245, 183, 74, 0.4);
|
||
--accent-glass: linear-gradient(135deg, rgba(245, 183, 74, 0.28), rgba(46, 58, 84, 0.45));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(245, 183, 74, 0.22), rgba(46, 58, 84, 0.4));
|
||
}
|
||
|
||
:root[data-accent="purple"] {
|
||
--primary-color-high: #8e44ad;
|
||
--primary-color-medium: #7c3a99;
|
||
--primary-color-low: #672f82;
|
||
--accent-grad: linear-gradient(135deg, #c084fc 0%, #8e44ad 48%, #6a2f8e 100%);
|
||
--accent-glow: rgba(142, 68, 173, 0.38);
|
||
--accent-glass: linear-gradient(135deg, rgba(142, 68, 173, 0.30), rgba(255, 255, 255, 0.62));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(142, 68, 173, 0.24), rgba(255, 255, 255, 0.72));
|
||
}
|
||
|
||
:root[data-darkmode="true"][data-accent="purple"] {
|
||
--primary-color-high: #b483d8;
|
||
--primary-color-medium: #a470c8;
|
||
--primary-color-low: #8e44ad;
|
||
--accent-grad: linear-gradient(135deg, #d8b4fe 0%, #b483d8 48%, #8e44ad 100%);
|
||
--accent-glow: rgba(180, 131, 216, 0.4);
|
||
--accent-glass: linear-gradient(135deg, rgba(180, 131, 216, 0.28), rgba(46, 58, 84, 0.45));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(180, 131, 216, 0.22), rgba(46, 58, 84, 0.4));
|
||
}
|
||
|
||
:root[data-accent="lime"] {
|
||
--primary-color-high: #7fb52a;
|
||
--primary-color-medium: #709e24;
|
||
--primary-color-low: #5f8a1e;
|
||
--accent-grad: linear-gradient(135deg, #c0e35c 0%, #7fb52a 48%, #5f8a1e 100%);
|
||
--accent-glow: rgba(127, 181, 42, 0.38);
|
||
--accent-glass: linear-gradient(135deg, rgba(127, 181, 42, 0.30), rgba(255, 255, 255, 0.62));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(127, 181, 42, 0.24), rgba(255, 255, 255, 0.72));
|
||
}
|
||
|
||
:root[data-darkmode="true"][data-accent="lime"] {
|
||
--primary-color-high: #a3d04a;
|
||
--primary-color-medium: #93c23c;
|
||
--primary-color-low: #7fb52a;
|
||
--accent-grad: linear-gradient(135deg, #d8f08a 0%, #a3d04a 48%, #7fb52a 100%);
|
||
--accent-glow: rgba(163, 208, 74, 0.4);
|
||
--accent-glass: linear-gradient(135deg, rgba(163, 208, 74, 0.28), rgba(46, 58, 84, 0.45));
|
||
--accent-glass-soft: linear-gradient(135deg, rgba(163, 208, 74, 0.22), rgba(46, 58, 84, 0.4));
|
||
}
|
||
|
||
/* 自定义主题色(data-accent="custom"):主色来自 --accent-custom(JS 从
|
||
uci accent_custom / 输入框设置),派生色变量由 main.js 的
|
||
applyCustomAccent() 生成,此处给出默认蓝 fallback(JS 未跑时防闪烁) */
|
||
:root[data-accent="custom"] {
|
||
--primary-color-high: var(--accent-custom, #2f7fe0);
|
||
--primary-color-medium: var(--accent-custom-medium, #286cbe);
|
||
--primary-color-low: var(--accent-custom-low, #205698);
|
||
--accent-grad: var(--accent-custom-grad, linear-gradient(135deg, #639fe8 0%, #2f7fe0 48%, #1c4c86 100%));
|
||
--accent-glow: var(--accent-custom-glow, rgba(47, 127, 224, 0.38));
|
||
--accent-glass: var(--accent-custom-glass, linear-gradient(135deg, rgba(47, 127, 224, 0.30), rgba(255, 255, 255, 0.62)));
|
||
--accent-glass-soft: var(--accent-custom-glass-soft, linear-gradient(135deg, rgba(47, 127, 224, 0.24), rgba(255, 255, 255, 0.72)));
|
||
}
|
||
|
||
:root[data-darkmode="true"][data-accent="custom"] {
|
||
--primary-color-high: var(--accent-custom-dark-high, #6da5e9);
|
||
--primary-color-medium: var(--accent-custom-dark-medium, #488ee4);
|
||
--primary-color-low: var(--accent-custom-dark-low, #2f7fe0);
|
||
--accent-grad: var(--accent-custom-dark-grad, linear-gradient(135deg, #9cc8f7 0%, #6da5e9 48%, #488ee4 100%));
|
||
--accent-glow: var(--accent-custom-dark-glow, rgba(109, 165, 233, 0.42));
|
||
--accent-glass: var(--accent-custom-dark-glass, linear-gradient(135deg, rgba(109, 165, 233, 0.28), rgba(46, 58, 84, 0.45)));
|
||
--accent-glass-soft: var(--accent-custom-dark-glass-soft, linear-gradient(135deg, rgba(109, 165, 233, 0.22), rgba(46, 58, 84, 0.40)));
|
||
}
|
||
|
||
|
||
/* ==========================================================================
|
||
2. Reset & base
|
||
========================================================================== */
|
||
|
||
*, *::before, *::after {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html {
|
||
font-size: 100%;
|
||
min-height: 100%;
|
||
}
|
||
|
||
body {
|
||
font-family: var(--font-sans);
|
||
font-size: 14px;
|
||
line-height: 1.55;
|
||
color: var(--text-color-high);
|
||
background: var(--background-color-medium) var(--body-grad) fixed;
|
||
background-attachment: fixed;
|
||
height: 100%;
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
overflow: hidden;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
a {
|
||
color: var(--primary-color-high);
|
||
}
|
||
|
||
a:hover {
|
||
text-decoration: underline;
|
||
}
|
||
|
||
ul, ol {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
h1, h2, h3, h4 {
|
||
font-weight: 700;
|
||
line-height: 1.3;
|
||
margin: 0 0 .5em 0;
|
||
}
|
||
|
||
#view h2 {
|
||
font-size: 17px;
|
||
color: var(--text-color-highest);
|
||
margin: 6px 0 10px;
|
||
}
|
||
|
||
hr {
|
||
border: 0;
|
||
border-top: 1px solid var(--border-color-medium);
|
||
margin: .75em 0;
|
||
}
|
||
|
||
code, pre, kbd {
|
||
font-family: var(--font-mono);
|
||
}
|
||
|
||
img {
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* scrollbars */
|
||
::-webkit-scrollbar {
|
||
width: 10px;
|
||
height: 10px;
|
||
}
|
||
|
||
::-webkit-scrollbar-track {
|
||
background: transparent;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb {
|
||
background: var(--border-color-medium);
|
||
border: 2px solid transparent;
|
||
border-radius: 999px;
|
||
background-clip: content-box;
|
||
}
|
||
|
||
::-webkit-scrollbar-thumb:hover {
|
||
background: var(--border-color-high);
|
||
border: 2px solid transparent;
|
||
background-clip: content-box;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
3. Top bar
|
||
========================================================================== */
|
||
|
||
#menubar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 900;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-height: 56px;
|
||
padding: 0 18px;
|
||
background: var(--glass-bg-strong);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border-bottom: 1px solid var(--glass-border-dark);
|
||
box-shadow: 0 1px 0 var(--glass-inset), 0 6px 24px rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
#menubar .navigation {
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 38px;
|
||
height: 38px;
|
||
margin: 0;
|
||
flex-shrink: 0;
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
transition: background .15s ease, transform .15s ease;
|
||
}
|
||
|
||
#menubar .navigation:hover {
|
||
background: var(--glass-bg-hover);
|
||
}
|
||
|
||
#menubar .navigation a {
|
||
display: block;
|
||
position: relative;
|
||
width: 18px;
|
||
height: 14px;
|
||
text-indent: -9999px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
#menubar .navigation a::before,
|
||
#menubar .navigation a::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
width: 18px;
|
||
height: 2px;
|
||
border-radius: 2px;
|
||
background: var(--text-color-high);
|
||
transition: top .15s ease, transform .15s ease;
|
||
}
|
||
|
||
#menubar .navigation a::before {
|
||
top: 0;
|
||
box-shadow: 0 6px 0 var(--text-color-high);
|
||
}
|
||
|
||
#menubar .navigation a::after {
|
||
top: 12px;
|
||
}
|
||
|
||
#menubar .navigation.active a::before {
|
||
top: 6px;
|
||
transform: rotate(45deg);
|
||
box-shadow: none;
|
||
}
|
||
|
||
#menubar .navigation.active a::after {
|
||
top: 6px;
|
||
transform: rotate(-45deg);
|
||
}
|
||
|
||
#menubar .hostname {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
letter-spacing: .2px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
#menubar .hostname a {
|
||
color: var(--text-color-highest);
|
||
text-decoration: none;
|
||
}
|
||
|
||
/* 顶栏 logo(内联 SVG,跟随主题色 + 玻璃水滴感) */
|
||
#menubar .hostname a .liquid-logo {
|
||
display: inline-block;
|
||
width: 22px;
|
||
height: 22px;
|
||
margin: 0 8px -5px 0;
|
||
vertical-align: baseline;
|
||
/* 与登录页 logo 同款光晕效果:三层 drop-shadow 沿水滴轮廓 + 高光线 */
|
||
filter: drop-shadow(0 0 3px color-mix(in srgb, var(--primary-color-high) 35%, transparent))
|
||
drop-shadow(0 1px 6px color-mix(in srgb, var(--primary-color-high) 25%, transparent))
|
||
drop-shadow(0 0 12px color-mix(in srgb, var(--primary-color-high) 12%, transparent))
|
||
drop-shadow(0 0.5px 0 rgba(255, 255, 255, 0.7));
|
||
transition: filter .25s ease;
|
||
}
|
||
#menubar .hostname a .liquid-logo:hover {
|
||
filter: drop-shadow(0 0 5px color-mix(in srgb, var(--primary-color-high) 50%, transparent))
|
||
drop-shadow(0 2px 10px color-mix(in srgb, var(--primary-color-high) 35%, transparent))
|
||
drop-shadow(0 0 18px color-mix(in srgb, var(--primary-color-high) 18%, transparent))
|
||
drop-shadow(0 1px 0 rgba(255, 255, 255, 0.9));
|
||
}
|
||
|
||
#menubar .distversion {
|
||
color: var(--text-color-low);
|
||
font-size: 12px;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
#menubar #indicators {
|
||
margin-left: auto;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
header [data-indicator],
|
||
#menubar [data-indicator] {
|
||
padding: 5px 10px;
|
||
font-size: 10.5px;
|
||
font-weight: 700;
|
||
letter-spacing: .4px;
|
||
text-transform: uppercase;
|
||
color: var(--text-color-medium);
|
||
/* 淡化半透明玻璃质感(与二级菜单选中项同源) */
|
||
background: color-mix(in srgb, var(--accent-glass-soft) 55%, transparent);
|
||
border: 1px solid color-mix(in srgb, var(--primary-color-high) 22%, transparent);
|
||
border-radius: 999px;
|
||
white-space: nowrap;
|
||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
backdrop-filter: blur(8px);
|
||
transition: background .18s ease, color .18s ease, border-color .18s ease;
|
||
}
|
||
|
||
header [data-indicator][data-clickable],
|
||
#menubar [data-indicator][data-clickable] {
|
||
cursor: pointer;
|
||
}
|
||
|
||
header [data-indicator][data-clickable]:hover,
|
||
#menubar [data-indicator][data-clickable]:hover {
|
||
background: var(--accent-glass-soft);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 38%, transparent);
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
header [data-indicator][data-style="active"],
|
||
#menubar [data-indicator][data-style="active"] {
|
||
color: var(--text-color-highest);
|
||
background: var(--accent-glass-soft);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 38%, transparent);
|
||
box-shadow: 0 2px 10px color-mix(in srgb, var(--primary-color-high) 20%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
header [data-indicator][data-style="inactive"],
|
||
#menubar [data-indicator][data-style="inactive"] {
|
||
opacity: .7;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
4. Light / dark / auto mode switch
|
||
========================================================================== */
|
||
|
||
.liquid-mode-switch {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 2px;
|
||
margin-left: 4px;
|
||
padding: 3px;
|
||
border-radius: 999px;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: 0 4px 14px rgba(31, 45, 70, 0.16),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.liquid-mode-switch .liquid-mode-btn {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex: none;
|
||
width: 28px;
|
||
height: 28px;
|
||
padding: 0;
|
||
border: 0;
|
||
border-radius: 999px;
|
||
background: transparent;
|
||
color: var(--text-color-medium);
|
||
cursor: pointer;
|
||
transition: background .15s ease, color .15s ease, box-shadow .15s ease;
|
||
}
|
||
|
||
.liquid-mode-switch .liquid-mode-btn:hover {
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
.liquid-mode-switch .liquid-mode-btn.active {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
box-shadow: 0 3px 10px var(--accent-glow);
|
||
}
|
||
|
||
.liquid-mode-switch .liquid-mode-btn img {
|
||
width: 16px;
|
||
height: 16px;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 暗黑模式:模式开关图标(外链 svg 的 currentColor 在 <img> 里不继承
|
||
CSS color,解析为默认黑色)反色为浅色,避免深色背景下黑图标 */
|
||
:root[data-darkmode="true"] .liquid-mode-btn img {
|
||
filter: invert(1);
|
||
}
|
||
|
||
.liquid-mode-switch-fixed {
|
||
position: fixed;
|
||
top: 16px;
|
||
right: 16px;
|
||
z-index: 2001;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 顶栏模式开关(刷新指示区左侧) */
|
||
#menubar > .liquid-mode-switch {
|
||
margin: 0 12px 0 0;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 菜单栏顶部颜色胶囊(水平居中)。胶囊宽度 min(216px, 100%)(侧栏
|
||
胶囊宽度 = 内容宽度(max-content):8 个圆点(Bing + 5 色 + 自定义 + 占位)
|
||
永远排在同一行,宽度随内容自适应,任何侧栏宽度都不会换行、不会
|
||
超出胶囊;margin auto 居中保证左右两侧间距一致;圆点 flex:none
|
||
固定 18px 正圆不压缩、不缩小。 */
|
||
#mainmenu > .liquid-color-switch {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
flex-wrap: nowrap;
|
||
width: calc(100% + 8px);
|
||
max-width: calc(100% + 8px);
|
||
margin: 0 -4px 28px;
|
||
flex-shrink: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* accent color switch */
|
||
.liquid-color-switch {
|
||
position: relative;
|
||
height: 35px;
|
||
box-sizing: border-box;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 0;
|
||
margin-left: 6px;
|
||
padding: 0 18px;
|
||
border-radius: 999px;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: 0 4px 14px rgba(31, 45, 70, 0.16),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.liquid-color-btn {
|
||
flex: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
padding: 0;
|
||
border: 2px solid rgba(255, 255, 255, 0.85);
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
|
||
transition: transform .15s ease, box-shadow .15s ease;
|
||
}
|
||
|
||
/* Bing 壁纸按钮(五色之前):点亮 = 启用 Bing 每日壁纸。
|
||
方形外框(跟随 b logo 的圆角方形图标 rx=5),选中时白色描边也是方形 */
|
||
.liquid-bing-btn {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 18px;
|
||
height: 18px;
|
||
padding: 0;
|
||
border-radius: 5px;
|
||
border: 2px solid transparent;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
|
||
opacity: .55;
|
||
}
|
||
|
||
.liquid-bing-btn:hover {
|
||
transform: scale(1.15);
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 启用时只保留一层白色方形描边(border)。
|
||
不能有 box-shadow 白环:按钮同时带 .liquid-color-btn 类,
|
||
其 .active 的 2px 白环会叠成第二层圈,这里用 !important 压掉 */
|
||
.liquid-bing-btn.active {
|
||
transform: scale(1.15);
|
||
border-color: rgba(255, 255, 255, 0.85);
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important;
|
||
opacity: 1;
|
||
}
|
||
|
||
.liquid-color-btn:hover {
|
||
transform: scale(1.15);
|
||
}
|
||
|
||
.liquid-color-btn.active {
|
||
transform: scale(1.15);
|
||
box-shadow: 0 0 0 2px var(--text-color-high), 0 2px 6px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
.liquid-color-switch-fixed {
|
||
position: fixed;
|
||
top: 60px;
|
||
right: 16px;
|
||
z-index: 2001;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 自定义主题色按钮(5 色之后):彩虹圆点;激活时显示当前自定义色 */
|
||
.liquid-custom-btn {
|
||
background: conic-gradient(#f66, #fc3, #6c6, #3cf, #96f, #f66);
|
||
opacity: .85;
|
||
}
|
||
|
||
/* 选中时:内部显示自定义色(第一层,clip 到 padding-box),
|
||
描边(2px 边框区)为彩虹渐变(第二层,clip 到 border-box),
|
||
一眼识别"这是自定义颜色"。不能用 background 简写(color 不能放
|
||
在非最后层的 box 关键字后,整条会被浏览器丢弃) */
|
||
.liquid-custom-btn.active {
|
||
background-image:
|
||
linear-gradient(var(--accent-custom, #2f7fe0), var(--accent-custom, #2f7fe0)),
|
||
conic-gradient(#f66, #fc3, #6c6, #3cf, #96f, #f66);
|
||
background-clip: padding-box, border-box;
|
||
background-origin: padding-box, border-box;
|
||
border: 3px solid transparent;
|
||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 菜单搜索按钮:与颜色圆点完全同步的交互设计(白描边、hover
|
||
放大、启用黑圈),仅形状为圆角方框 + 浅灰底 + 白色放大镜。
|
||
所有状态显式声明,覆盖 .liquid-color-btn 的继承干扰 */
|
||
.liquid-search-btn {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 18px;
|
||
height: 18px;
|
||
padding: 0;
|
||
background: rgba(148, 163, 184, 0.55); /* 浅灰底(明亮模式) */
|
||
border-radius: 5px; /* 方框 */
|
||
border: 2px solid rgba(255, 255, 255, 0.85); /* 与其他圆点一致白描边 */
|
||
color: #ffffff !important; /* 白色放大镜,任何状态不变黑 */
|
||
cursor: pointer;
|
||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.28);
|
||
opacity: .95;
|
||
transition: transform .15s ease, box-shadow .15s ease, opacity .15s ease;
|
||
}
|
||
.liquid-search-btn svg {
|
||
width: 12px;
|
||
height: 12px;
|
||
display: block;
|
||
}
|
||
.liquid-search-btn:hover {
|
||
transform: scale(1.15); /* 描边一起放大,同其他圆点 */
|
||
background: rgba(148, 163, 184, 0.65);
|
||
opacity: 1;
|
||
}
|
||
.liquid-search-btn.active {
|
||
transform: scale(1.15);
|
||
background: rgba(148, 163, 184, 0.65);
|
||
box-shadow: 0 0 0 2px var(--text-color-high), /* 黑圈同其他圆点 */
|
||
0 2px 6px rgba(0, 0, 0, 0.3);
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 暗黑模式:白色方框底(用户指定),深色放大镜 */
|
||
:root[data-darkmode="true"] .liquid-search-btn {
|
||
background: rgba(255, 255, 255, 0.92);
|
||
color: #334155 !important;
|
||
border-color: rgba(255, 255, 255, 0.85);
|
||
}
|
||
:root[data-darkmode="true"] .liquid-search-btn:hover {
|
||
background: #ffffff;
|
||
}
|
||
:root[data-darkmode="true"] .liquid-search-btn.active {
|
||
background: #ffffff;
|
||
box-shadow: 0 0 0 2px var(--text-color-high),
|
||
0 2px 6px rgba(0, 0, 0, 0.3);
|
||
}
|
||
|
||
/* ── 菜单搜索框(抽屉式下推,圆角矩形)──────────────
|
||
与颜色胶囊同源玻璃设计,但圆角矩形(10px 非胶囊),
|
||
插在颜色胶囊下方,菜单被往下推(非悬浮)。
|
||
放大镜/清除图标绝对定位在输入框内部(不隔离) */
|
||
.liquid-menu-search {
|
||
display: flex;
|
||
position: relative;
|
||
align-items: center;
|
||
margin: 0 auto 12px;
|
||
height: 0; /* 默认收起:0 高不占位 */
|
||
padding: 0;
|
||
opacity: 0;
|
||
overflow: hidden; /* 圆角裁剪子元素(input 撑满后不露直角) */
|
||
border-radius: 10px;
|
||
border-width: 0; /* 收起时无边框 */
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: 0 4px 14px rgba(31, 45, 70, 0.16),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.6);
|
||
color: var(--text-color-medium);
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
box-sizing: border-box;
|
||
transition: height .16s ease, opacity .16s ease, padding .16s ease, margin .16s ease;
|
||
}
|
||
.liquid-menu-search.open {
|
||
height: 34px;
|
||
padding: 0;
|
||
opacity: 1;
|
||
border-width: 1px;
|
||
animation: none;
|
||
}
|
||
|
||
/* 收回:过渡动画由 height/opacity transition 处理(.open 移除即触发) */
|
||
|
||
@keyframes liquid-search-drop {
|
||
from { opacity: 0; transform: translateY(-8px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
|
||
.liquid-menu-search-icon {
|
||
position: absolute;
|
||
left: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
display: inline-flex;
|
||
color: var(--text-color-medium);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.liquid-menu-search-input {
|
||
flex: 1;
|
||
width: 100%;
|
||
height: 100%; /* 撑满容器高度,避免露出容器/页面背景色差 */
|
||
min-width: 0;
|
||
/* 用 !important 压掉 LuCI 全局 input[type="text"] 的 padding/背景/边框:
|
||
28px = 图标(8px 左偏移+12px 宽) + 8px 间距,文字贴图标右侧 */
|
||
padding: 4px 30px 4px 28px !important;
|
||
border: none !important;
|
||
outline: none;
|
||
background: transparent !important; /* 压掉 --field-bg 底色,融入容器 */
|
||
/* 圆角跟随容器(容器 10px + 1px 边框 → input 内缩用 9px),
|
||
避免撑满后直角盖掉容器圆角 */
|
||
border-radius: 9px !important;
|
||
display: block;
|
||
color: var(--text-color-high);
|
||
font-size: 12px;
|
||
box-sizing: border-box;
|
||
}
|
||
/* 聚焦时:恢复主题色高亮边框(用户要求),但保持透明底不破坏玻璃。
|
||
用容器 :focus-within 让整个搜索框显示主题色描边 + 淡光晕 */
|
||
.liquid-menu-search:focus-within {
|
||
border-color: var(--primary-color-high) !important;
|
||
box-shadow: 0 0 0 3px var(--accent-glow), 0 4px 14px rgba(31, 45, 70, 0.16) !important;
|
||
}
|
||
.liquid-menu-search-input:focus {
|
||
background: transparent !important;
|
||
border-color: transparent !important;
|
||
box-shadow: none !important;
|
||
}
|
||
/* 浏览器 autofill 会强加黄/白底,压掉保持玻璃透明 */
|
||
.liquid-menu-search-input:-webkit-autofill {
|
||
-webkit-box-shadow: 0 0 0 1000px transparent inset !important;
|
||
-webkit-text-fill-color: var(--text-color-high) !important;
|
||
transition: background-color 9999s ease-out !important;
|
||
}
|
||
.liquid-menu-search-input::placeholder {
|
||
color: var(--text-color-medium);
|
||
opacity: .7;
|
||
}
|
||
|
||
.liquid-menu-search-clear {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 18px;
|
||
height: 18px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: rgba(148, 163, 184, 0.35);
|
||
color: var(--text-color-medium);
|
||
font-size: 13px;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
display: none; /* 默认隐藏,有输入时 JS 显示 */
|
||
align-items: center;
|
||
justify-content: center;
|
||
opacity: .75;
|
||
}
|
||
.liquid-menu-search-clear:hover {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* 搜索过滤:不匹配的菜单项隐藏 */
|
||
#mainmenu li.liquid-menu-hidden {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 自定义颜色输入面板(点击彩虹圆点下拉) */
|
||
.liquid-custom-pop {
|
||
display: none;
|
||
position: absolute;
|
||
top: calc(100% + 8px);
|
||
right: 0;
|
||
z-index: 9999;
|
||
padding: 6px;
|
||
border-radius: 8px;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||
backdrop-filter: blur(var(--glass-blur));
|
||
animation: liquid-pop-in .3s ease;
|
||
transform-origin: top right;
|
||
}
|
||
|
||
@keyframes liquid-pop-in {
|
||
from { opacity: 0; transform: translateY(-8px) scale(.95); }
|
||
to { opacity: 1; transform: translateY(0) scale(1); }
|
||
}
|
||
|
||
.liquid-custom-input {
|
||
width: 112px;
|
||
padding: 4px 8px;
|
||
font-size: 12px;
|
||
font-family: ui-monospace, SFMono-Regular, Consolas, monospace;
|
||
color: var(--text-color);
|
||
background: var(--input-bg, rgba(255, 255, 255, 0.7));
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 6px;
|
||
outline: none;
|
||
}
|
||
|
||
.liquid-custom-input:focus {
|
||
border-color: var(--primary-color-high);
|
||
box-shadow: 0 0 0 2px var(--accent-glow);
|
||
}
|
||
|
||
/* 锁屏(无菜单栏,开关固定在右上角):输入面板向上弹出 */
|
||
.liquid-color-switch-fixed .liquid-custom-pop {
|
||
top: auto;
|
||
bottom: calc(100% + 8px);
|
||
transform-origin: bottom right;
|
||
}
|
||
|
||
/* ── 透明度滑杆(玻璃感,参考 iOS 26/macOS 26 slider)───────── */
|
||
/* 绝对定位在胶囊下方,不参与胶囊 flex 布局,不撑爆胶囊 */
|
||
.liquid-glass-slider-wrap {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: calc(100% + 4px);
|
||
display: flex;
|
||
align-items: center;
|
||
height: 16px;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
/* 内容流不参与胶囊 flex 布局,overflow: visible 确保 tick/bubble 可见 */
|
||
}
|
||
/* 胶囊需要 overflow:visible 才能显示溢出的滑杆 */
|
||
.liquid-color-switch {
|
||
overflow: visible;
|
||
}
|
||
.liquid-glass-slider {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
height: 4px;
|
||
border-radius: 4px;
|
||
outline: none;
|
||
cursor: pointer;
|
||
pointer-events: auto;
|
||
/* 轨道:亮色用深色半透明,暗色用浅色半透明 */
|
||
background: linear-gradient(to right, rgba(0,0,0,0.12), rgba(0,0,0,0.22));
|
||
backdrop-filter: blur(6px);
|
||
-webkit-backdrop-filter: blur(6px);
|
||
border: none;
|
||
box-shadow: 0 0 0 1px rgba(0,0,0,0.08), 0 1px 4px rgba(0,0,0,0.08);
|
||
}
|
||
:root[data-darkmode="true"] .liquid-glass-slider {
|
||
background: linear-gradient(to right, rgba(255,255,255,0.06), rgba(255,255,255,0.16));
|
||
box-shadow: 0 0 0 1px rgba(255,255,255,0.12), 0 1px 4px rgba(0,0,0,0.08);
|
||
}
|
||
.liquid-glass-slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none;
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, rgba(255,255,255,0.92), rgba(255,255,255,0.68));
|
||
border: 1px solid rgba(255, 255, 255, 0.95);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 0 0 2px rgba(255, 255, 255, 0.15);
|
||
backdrop-filter: blur(8px);
|
||
-webkit-backdrop-filter: blur(8px);
|
||
cursor: pointer;
|
||
}
|
||
.liquid-glass-slider::-moz-range-thumb {
|
||
width: 16px;
|
||
height: 16px;
|
||
border-radius: 50%;
|
||
background: linear-gradient(135deg, rgba(255,255,255,0.92), rgba(255,255,255,0.68));
|
||
border: 1px solid rgba(255, 255, 255, 0.95);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2), 0 0 0 2px rgba(255, 255, 255, 0.15);
|
||
cursor: pointer;
|
||
}
|
||
.liquid-glass-slider::-webkit-slider-thumb:hover {
|
||
background: linear-gradient(135deg, rgba(255,255,255,1), rgba(255,255,255,0.88));
|
||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25), 0 0 0 3px rgba(255, 255, 255, 0.2);
|
||
}
|
||
.liquid-glass-slider::-webkit-slider-thumb:active {
|
||
transform: scale(0.9);
|
||
}
|
||
/* 暗黑模式滑杆轨道稍微更亮 */
|
||
:root[data-darkmode="true"] .liquid-glass-slider {
|
||
background: linear-gradient(to right, rgba(255,255,255,0.06), rgba(255,255,255,0.16));
|
||
border-color: rgba(255, 255, 255, 0.12);
|
||
}
|
||
/* 默认值竖线标记:绝对定位在轨道上方,垂直居中,百分比对齐 thumb */
|
||
.liquid-glass-slider-tick {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
pointer-events: auto;
|
||
cursor: pointer;
|
||
transition: opacity .15s ease;
|
||
}
|
||
/* tick 内部的竖线指示器(垂直居中由父 flex 保证) */
|
||
.liquid-glass-slider-tick::before {
|
||
content: '';
|
||
display: block;
|
||
width: 4px;
|
||
height: 14px;
|
||
flex-shrink: 0;
|
||
background: rgba(90, 95, 110, 0.85);
|
||
border: 1px solid rgba(255, 255, 255, 0.6);
|
||
box-shadow: 0 0 2px rgba(0,0,0,0.2);
|
||
border-radius: 2px;
|
||
}
|
||
.liquid-glass-slider-tick:hover::before {
|
||
background: rgba(60, 65, 80, 1);
|
||
border-color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
/* 侧栏菜单里的胶囊下方不需要下移(胶囊本身就是菜单项,位置已定) */
|
||
#mainmenu > .liquid-color-switch > .liquid-glass-slider-wrap {
|
||
top: calc(100% + 2px);
|
||
}
|
||
|
||
/* 浮动数值气泡(拖动时跟随 thumb 显示当前值) */
|
||
.liquid-glass-slider-bubble {
|
||
position: absolute;
|
||
bottom: calc(100% + 6px);
|
||
transform: translateX(-50%);
|
||
padding: 3px 7px;
|
||
border-radius: 6px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
line-height: 1;
|
||
color: var(--text-color-highest);
|
||
background: var(--glass-bg-strong);
|
||
border: 1px solid var(--glass-border);
|
||
backdrop-filter: blur(12px);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
}
|
||
.liquid-glass-slider-bubble::after {
|
||
content: '';
|
||
position: absolute;
|
||
top: 100%;
|
||
left: 50%;
|
||
margin-left: -4px;
|
||
border: 4px solid transparent;
|
||
border-top-color: var(--glass-border);
|
||
}
|
||
|
||
/* 登录后暂存提示 toast */
|
||
.liquid-pending-toast {
|
||
position: fixed;
|
||
bottom: 24px;
|
||
left: 50%;
|
||
transform: translateX(-50%) translateY(20px);
|
||
opacity: 0;
|
||
transition: opacity .3s ease, transform .3s ease;
|
||
z-index: 9999;
|
||
display: inline-flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
padding: 10px 16px;
|
||
border-radius: 12px;
|
||
background: var(--glass-bg-strong);
|
||
border: 1px solid var(--glass-border);
|
||
backdrop-filter: blur(16px);
|
||
-webkit-backdrop-filter: blur(16px);
|
||
box-shadow: 0 8px 32px rgba(0,0,0,0.2);
|
||
pointer-events: auto;
|
||
max-width: 75vw;
|
||
}
|
||
.liquid-pending-toast.show {
|
||
opacity: 1;
|
||
transform: translateX(-50%) translateY(0);
|
||
}
|
||
.liquid-pending-toast-text {
|
||
font-size: 13px;
|
||
color: var(--text-color-high);
|
||
white-space: nowrap;
|
||
text-align: center;
|
||
}
|
||
.liquid-pending-toast-countdown {
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--accent-glow);
|
||
font-variant-numeric: tabular-nums;
|
||
min-width: 28px;
|
||
text-align: center;
|
||
}
|
||
.liquid-pending-toast-buttons {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
.liquid-pending-toast-btn {
|
||
flex-shrink: 0;
|
||
padding: 5px 14px;
|
||
border: none;
|
||
border-radius: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: box-shadow .15s ease, background .15s ease;
|
||
}
|
||
.liquid-pending-toast-btn[data-action="apply"] {
|
||
background: var(--accent-grad);
|
||
color: var(--on-primary-color, #fff);
|
||
box-shadow: 0 2px 8px var(--accent-glow);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="apply"]:hover {
|
||
box-shadow: 0 4px 16px var(--accent-glow);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="dismiss"] {
|
||
background: var(--glass-bg);
|
||
color: var(--text-color-medium);
|
||
border: 1px solid var(--glass-border);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="dismiss"]:hover {
|
||
color: var(--text-color-high);
|
||
}
|
||
min-width: 80px;
|
||
}
|
||
.liquid-pending-toast-btn[data-action="apply"] {
|
||
background: var(--accent-grad);
|
||
color: var(--on-primary-color, #fff);
|
||
box-shadow: 0 2px 8px var(--accent-glow);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="apply"]:hover {
|
||
box-shadow: 0 4px 16px var(--accent-glow);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="dismiss"] {
|
||
background: var(--glass-bg);
|
||
color: var(--text-color-medium);
|
||
border: 1px solid var(--glass-border);
|
||
}
|
||
.liquid-pending-toast-btn[data-action="dismiss"]:hover {
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
5. Layout
|
||
========================================================================== */
|
||
|
||
#maincontainer {
|
||
display: flex;
|
||
align-items: stretch;
|
||
width: 100%;
|
||
height: calc(100vh - 56px);
|
||
min-height: calc(100vh - 56px);
|
||
overflow-y: auto;
|
||
}
|
||
|
||
#maincontent {
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
width: 100%;
|
||
margin-left: 236px;
|
||
padding: 6px 22px 96px;
|
||
}
|
||
|
||
#view {
|
||
margin-top: 18px;
|
||
}
|
||
|
||
.skiplink {
|
||
position: absolute;
|
||
left: -9999px;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
6. Sidebar menu
|
||
========================================================================== */
|
||
|
||
#mainmenu {
|
||
position: fixed;
|
||
top: 56px;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 236px;
|
||
flex-shrink: 0;
|
||
height: auto;
|
||
overflow-y: auto;
|
||
padding: 12px 10px 24px;
|
||
animation: liquid-slide-in .5s cubic-bezier(.22, .9, .3, 1);
|
||
background: var(--menu-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border-right: 1px solid var(--glass-border-dark);
|
||
transition: width .2s ease, transform .2s ease;
|
||
}
|
||
|
||
#mainmenu ul.mainmenu {
|
||
display: block;
|
||
}
|
||
|
||
#mainmenu ul.mainmenu.l1 > li > a {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
position: relative;
|
||
z-index: 3;
|
||
padding: 10px 14px;
|
||
margin-bottom: 3px;
|
||
border-radius: 13px;
|
||
border: 1px solid transparent;
|
||
font-weight: 600;
|
||
font-size: 13.5px;
|
||
color: var(--text-color-high);
|
||
text-decoration: none;
|
||
transition: background .16s ease, border-color .16s ease,
|
||
box-shadow .16s ease, color .16s ease;
|
||
}
|
||
|
||
/* 悬停视觉由 .liquid-menu-indicator 滑块负责;这里仅文字变色 */
|
||
#mainmenu ul.mainmenu.l1 > li > a:hover {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
box-shadow: none;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
/* menu item selected — 与追踪滑块同款的透明蓝玻璃,蓝色略深(不被 hover 覆盖) */
|
||
#mainmenu ul.mainmenu.l1 > li.selected > a,
|
||
#mainmenu ul.mainmenu.l1 > li.selected.active > a,
|
||
#mainmenu ul.mainmenu.l1 > li.selected > a:hover,
|
||
#mainmenu ul.mainmenu.l1 > li.selected.active > a:hover {
|
||
background: var(--accent-glass);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, transparent);
|
||
color: var(--primary-color-low);
|
||
box-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color-high) 26%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
}
|
||
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l1 > li.selected > a,
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l1 > li.selected.active > a,
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l1 > li.selected > a:hover,
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l1 > li.selected.active > a:hover {
|
||
background: var(--accent-glass);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 48%, transparent);
|
||
color: #dcecff;
|
||
box-shadow: 0 4px 18px color-mix(in srgb, var(--primary-color-high) 38%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
/* 展开态(点击展开的其他一级菜单)— 次级高亮 */
|
||
#mainmenu ul.mainmenu.l1 > li.active > a,
|
||
#mainmenu ul.mainmenu.l1 > li.active > a:hover {
|
||
background: var(--glass-bg);
|
||
border-color: var(--glass-border);
|
||
color: var(--primary-color-high);
|
||
box-shadow: inset 0 0 0 1px var(--glass-border),
|
||
inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
#mainmenu ul.mainmenu.l1 > li.selected > a::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: -10px;
|
||
top: 22%;
|
||
height: 56%;
|
||
width: 4px;
|
||
border-radius: 0 4px 4px 0;
|
||
background: var(--accent-grad);
|
||
box-shadow: 0 0 12px var(--accent-glow);
|
||
}
|
||
|
||
/* expandable submenu indicator */
|
||
#mainmenu ul.mainmenu.l1 > li:has(> ul:not(:empty)) > a::after {
|
||
content: "";
|
||
width: 12px;
|
||
height: 12px;
|
||
margin-left: auto;
|
||
background: url(svg/chevron-down.svg) center / contain no-repeat;
|
||
opacity: .65;
|
||
transition: transform .18s ease;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
#mainmenu ul.mainmenu.l1 > li.active > a::after {
|
||
transform: rotate(180deg);
|
||
}
|
||
|
||
/* submenu */
|
||
#mainmenu li > ul,
|
||
#mainmenu li > ul.mainmenu {
|
||
display: none !important;
|
||
margin: 2px 0 6px 10px;
|
||
padding-left: 8px;
|
||
border-left: 1px solid var(--border-color-low);
|
||
}
|
||
|
||
#mainmenu li.active > ul,
|
||
#mainmenu li.active > ul.mainmenu {
|
||
display: block !important;
|
||
animation: liquid-fade-in .22s ease;
|
||
}
|
||
|
||
/* 二级菜单 hover 追踪滑块 */
|
||
ul.mainmenu.l2 {
|
||
position: relative;
|
||
}
|
||
|
||
.liquid-sub-indicator {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
height: 32px;
|
||
border-radius: 10px;
|
||
background: var(--indicator-bg);
|
||
border: 1px solid var(--indicator-border);
|
||
box-shadow: var(--indicator-shadow);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
transition: transform .25s cubic-bezier(.22, .8, .3, 1),
|
||
height .25s cubic-bezier(.22, .8, .3, 1),
|
||
opacity .18s ease;
|
||
}
|
||
|
||
ul.mainmenu.l2.liquid-sub-hovering .liquid-sub-indicator {
|
||
opacity: 1;
|
||
}
|
||
|
||
#mainmenu ul.mainmenu.l2 > li > a {
|
||
display: block;
|
||
position: relative;
|
||
z-index: 3;
|
||
padding: 8px 12px;
|
||
margin: 1px 0;
|
||
border-radius: 10px;
|
||
font-weight: 500;
|
||
font-size: 12.5px;
|
||
color: var(--text-color-medium);
|
||
text-decoration: none;
|
||
transition: background .15s ease, color .15s ease;
|
||
}
|
||
|
||
#mainmenu ul.mainmenu.l2 > li > a:hover {
|
||
background: transparent;
|
||
color: var(--primary-color-high);
|
||
}
|
||
|
||
/* 二级菜单选中项 — 淡蓝玻璃胶囊(亮色下可见,避免纯白难辨) */
|
||
#mainmenu ul.mainmenu.l2 > li.selected > a,
|
||
#mainmenu ul.mainmenu.l2 > li.selected > a:hover {
|
||
background: var(--accent-glass-soft);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 38%, transparent);
|
||
color: var(--primary-color-low);
|
||
font-weight: 600;
|
||
box-shadow: 0 2px 10px color-mix(in srgb, var(--primary-color-high) 20%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l2 > li.selected > a,
|
||
:root[data-darkmode="true"] #mainmenu ul.mainmenu.l2 > li.selected > a:hover {
|
||
background: var(--accent-glass-soft);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 34%, transparent);
|
||
color: #dcecff;
|
||
box-shadow: 0 2px 10px color-mix(in srgb, var(--primary-color-high) 32%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
#mainmenu:empty {
|
||
display: none;
|
||
}
|
||
|
||
/* hover 追踪滑块 */
|
||
.liquid-menu-indicator {
|
||
position: absolute;
|
||
left: 10px;
|
||
right: 10px;
|
||
top: 0;
|
||
height: 40px;
|
||
border-radius: 13px;
|
||
background: var(--indicator-bg);
|
||
border: 1px solid var(--indicator-border);
|
||
box-shadow: var(--indicator-shadow);
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
transition: transform .28s cubic-bezier(.22, .8, .3, 1),
|
||
height .28s cubic-bezier(.22, .8, .3, 1),
|
||
opacity .18s ease;
|
||
}
|
||
|
||
#mainmenu.liquid-menu-hovering .liquid-menu-indicator {
|
||
opacity: 1;
|
||
}
|
||
|
||
|
||
/* ==========================================================================
|
||
7. Mode menu (mobile breadcrumb)
|
||
========================================================================== */
|
||
|
||
#modemenu {
|
||
position: sticky;
|
||
top: 56px;
|
||
z-index: 800;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 4px 10px;
|
||
padding: 10px 22px;
|
||
margin: 0;
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||
backdrop-filter: blur(var(--glass-blur));
|
||
border-bottom: 1px solid var(--glass-border-dark);
|
||
}
|
||
|
||
#modemenu a {
|
||
color: var(--text-color-medium);
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
font-size: 12.5px;
|
||
padding: 5px 10px;
|
||
border-radius: 999px;
|
||
transition: background .15s ease, color .15s ease;
|
||
}
|
||
|
||
#modemenu a:hover {
|
||
background: var(--glass-bg-hover);
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
#modemenu .active a {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
box-shadow: 0 3px 10px var(--accent-glow);
|
||
}
|
||
|
||
#modemenu > div:not(.active) ~ div {
|
||
display: none;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
8. Tab menu
|
||
========================================================================== */
|
||
|
||
/* 通用 tab 按钮组(页面级 #tabmenu 与 cbi 表单内 tab 共用样式)。
|
||
胶囊形状保留;未选中项透明交给追踪滑块,选中项用菜单区
|
||
selected 同款 accent 渐变高亮。 */
|
||
:is(ul.cbi-tabmenu, ul.tabs) {
|
||
position: relative;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 0 0 14px;
|
||
padding: 0;
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li {
|
||
display: inline-block;
|
||
position: relative;
|
||
z-index: 1;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 覆盖 LuCI bootstrap 的 tab 容器主题色(bootstrap: color:var(--primary-color-high)),
|
||
统一改为文字色,明暗模式自动切换 */
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li {
|
||
color: inherit;
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li > a {
|
||
display: inline-block;
|
||
padding: 8px 18px;
|
||
border-radius: 999px;
|
||
font-weight: 600;
|
||
font-size: 12.5px;
|
||
color: var(--text-color-medium);
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
text-decoration: none;
|
||
transition: color .16s ease, box-shadow .16s ease;
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li > a:hover {
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
/* 选中项与左侧菜单 selected 同款玻璃质感高亮 */
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li.cbi-tab > a {
|
||
background: var(--accent-glass);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, transparent);
|
||
color: var(--text-color-highest);
|
||
box-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color-high) 26%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
}
|
||
|
||
/* iStoreOS tabs 选中项:Vue scoped 样式(li.active a { color:... })特异性高,
|
||
!important 强制主题玻璃样式(与 tabmenu-item 同策略) */
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li.cbi-tab > a {
|
||
background: var(--accent-glass) !important;
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, transparent) !important;
|
||
color: var(--text-color-highest) !important;
|
||
}
|
||
|
||
/* iStoreOS ul.tabs 的 Vue scoped 滑动条(div.auto)在 flex 容器内占位,
|
||
隐藏它避免干扰 tab 布局(主题自带 .liquid-tab-indicator 追踪滑块) */
|
||
ul.tabs > div.auto {
|
||
display: none !important;
|
||
}
|
||
|
||
:is(:root[data-darkmode="true"] ul.cbi-tabmenu, :root[data-darkmode="true"] ul.tabs) > li.cbi-tab > a {
|
||
background: var(--accent-glass);
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 48%, transparent);
|
||
color: var(--text-color-highest);
|
||
box-shadow: 0 4px 18px color-mix(in srgb, var(--primary-color-high) 38%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li.cbi-tab-disabled > a {
|
||
opacity: .75;
|
||
}
|
||
|
||
/* 第三方应用/页面导航 tab(li[class*="tabmenu-item"],如 homeproxy、
|
||
系统→管理页):显式强制与主题 cbi-tab 完全一致的样式,防任何
|
||
应用自带样式干扰导致观感不一致 */
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li[class*="tabmenu-item"] > a {
|
||
padding: 8px 18px;
|
||
border-radius: 999px;
|
||
font-weight: 600;
|
||
font-size: 12.5px;
|
||
color: var(--text-color-medium);
|
||
background: transparent;
|
||
border: 1px solid transparent;
|
||
text-decoration: none;
|
||
transition: color .16s ease, box-shadow .16s ease;
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li[class*="tabmenu-item"] > a:hover {
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
:is(ul.cbi-tabmenu, ul.tabs) > li[class*="tabmenu-item"].cbi-tab > a {
|
||
background: var(--accent-glass) !important;
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, transparent) !important;
|
||
color: var(--primary-color-low) !important;
|
||
box-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color-high) 26%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.9) !important;
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat)) !important;
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat)) !important;
|
||
}
|
||
|
||
:is(:root[data-darkmode="true"] ul.cbi-tabmenu, :root[data-darkmode="true"] ul.tabs) > li[class*="tabmenu-item"].cbi-tab > a {
|
||
background: var(--accent-glass) !important;
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 48%, transparent) !important;
|
||
color: #dcecff !important;
|
||
box-shadow: 0 4px 18px color-mix(in srgb, var(--primary-color-high) 38%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.14) !important;
|
||
}
|
||
|
||
/* tab hover 追踪滑块(main.js 驱动,跟随鼠标/选中项滑动) */
|
||
.liquid-tab-indicator {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
border-radius: 999px;
|
||
background: var(--indicator-bg);
|
||
border: 1px solid var(--indicator-border);
|
||
box-shadow: var(--indicator-shadow);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
z-index: 0;
|
||
transition: transform .26s cubic-bezier(.22, .8, .3, 1),
|
||
width .26s cubic-bezier(.22, .8, .3, 1),
|
||
opacity .18s ease;
|
||
}
|
||
|
||
/* cbi tab pane 显示控制(ui.tabs 通过 data-tab-title / data-tab-active 控制)
|
||
注意:tabmenu 的 li 也带 data-tab 属性,不能按 [data-tab] 全局隐藏,
|
||
必须用 [data-tab-title](仅 pane 内容容器带)折叠内容区——与官方
|
||
bootstrap 的机制一致(height/opacity 折叠而非 display:none)。 */
|
||
[data-tab-title] {
|
||
height: 0;
|
||
opacity: 0;
|
||
overflow: hidden;
|
||
}
|
||
|
||
[data-tab-active="true"] {
|
||
height: auto;
|
||
opacity: 1;
|
||
overflow: visible;
|
||
animation: liquid-fade-in .22s ease;
|
||
}
|
||
|
||
/* 卡片宽度:逐层按父可用宽 100% 套用;内容超宽时卡片内横向滚动,
|
||
不再溢出外层卡片 —— 仅限 modal 内嵌套(接口编辑等);
|
||
概况页等主内容卡片自适应内部模块高度/宽度,不设滚动条 */
|
||
#modal_overlay .cbi-map,
|
||
#modal_overlay .cbi-section {
|
||
width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* 软件包页(package-manager):filter / 下载 / 操作按钮组都是
|
||
div.controls > div > span.control-group > input + button。luci 的
|
||
.controls > * > * { flex-grow:1 } 会把组内 input 和按钮一起拉伸;
|
||
主题全局 input{width:100%} 又让输入框撑满 span,同组按钮被挤出、
|
||
与相邻控件重叠。控制组改 inline-flex:输入框占剩余空间、按钮按内容宽 */
|
||
.controls .control-group {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.controls .control-group input {
|
||
width: auto !important;
|
||
min-width: 100px;
|
||
flex: 1 1 auto !important;
|
||
}
|
||
|
||
.controls .control-group .btn {
|
||
flex: 0 0 auto !important;
|
||
margin: 0;
|
||
}
|
||
|
||
/* 打开下拉时该层解除裁剪(滚动容器会裁掉弹出的选项列表),
|
||
并提升整条祖先链的层级:backdrop-filter 会让每层都形成层叠
|
||
上下文,仅提升一层不够,需让包含它的所有容器都高于后续兄弟 */
|
||
.cbi-map:has(.cbi-dropdown[open]),
|
||
.cbi-section:has(.cbi-dropdown[open]),
|
||
.cbi-section-node:has(.cbi-dropdown[open]),
|
||
.cbi-section-table:has(.cbi-dropdown[open]),
|
||
#maincontent:has(.cbi-dropdown[open]) {
|
||
overflow-x: visible !important;
|
||
overflow-y: visible !important;
|
||
position: relative;
|
||
z-index: 1000;
|
||
}
|
||
|
||
#tabmenu {
|
||
padding: 16px 0 14px;
|
||
}
|
||
|
||
#tabmenu ul.cbi-tabmenu {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
#tabmenu ul.cbi-tabmenu > li {
|
||
display: inline-block;
|
||
margin: 0;
|
||
}
|
||
|
||
#tabmenu ul.cbi-tabmenu > li > a {
|
||
display: inline-block;
|
||
padding: 8px 18px;
|
||
border-radius: 999px;
|
||
font-weight: 600;
|
||
font-size: 12.5px;
|
||
color: var(--text-color-medium);
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
text-decoration: none;
|
||
-webkit-backdrop-filter: blur(10px);
|
||
backdrop-filter: blur(10px);
|
||
box-shadow: var(--glass-shadow-soft);
|
||
transition: background .16s ease, color .16s ease,
|
||
border-color .16s ease, box-shadow .16s ease;
|
||
}
|
||
|
||
#tabmenu ul.cbi-tabmenu > li > a:hover {
|
||
background: var(--glass-bg-hover);
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
#tabmenu ul.cbi-tabmenu > .cbi-tab > a {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
border-color: transparent;
|
||
box-shadow: 0 6px 18px var(--accent-glow),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
9. Footer
|
||
========================================================================== */
|
||
|
||
p.luci {
|
||
padding: 20px 22px 30px;
|
||
margin: 0;
|
||
text-align: center;
|
||
font-size: 11.5px;
|
||
color: var(--text-color-low);
|
||
}
|
||
|
||
/* 内容区底部 spacer:滚动到底时内容完整显示在 foot 之上 */
|
||
.luci-foot-spacer {
|
||
height: 80px;
|
||
}
|
||
|
||
/* 登录后 foot 横幅:从菜单栏右外侧到窗口右边缘,顶栏式玻璃横幅 */
|
||
body:not(.liquid-login) p.luci {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 236px;
|
||
right: 0;
|
||
margin: 0;
|
||
padding: 9px 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px 14px;
|
||
font-size: 11.5px;
|
||
color: var(--text-color-low);
|
||
background: var(--glass-bg-strong);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border-top: 1px solid var(--glass-border-dark);
|
||
box-shadow: 0 -1px 0 var(--glass-inset), 0 -6px 24px rgba(0, 0, 0, 0.06);
|
||
z-index: 700;
|
||
}
|
||
|
||
body:not(.liquid-login) p.luci a {
|
||
color: var(--text-color-medium);
|
||
text-decoration: none;
|
||
border-bottom: 1px solid transparent;
|
||
transition: color .15s ease, border-bottom-color .15s ease;
|
||
}
|
||
|
||
body:not(.liquid-login) p.luci a:hover {
|
||
color: var(--primary-color-high);
|
||
border-bottom-color: var(--primary-color-high);
|
||
}
|
||
|
||
.luci-sep {
|
||
opacity: .4;
|
||
}
|
||
|
||
/* ── 页脚版本号 → 检查更新卡片(对齐 pushbot 的四态 frosted toast)──
|
||
overlay 直接挂在 fixed 的页脚 p.luci 内:absolute 锚定页脚盒子,
|
||
水平居中自然是"页脚宽度内"(left:236px → 右缘),不是全屏居中。
|
||
非更新态 8s 自动消失(比 pushbot 多 4s)+ 点卡片跳作者仓库;
|
||
更新态不自动消失,按钮操作。 */
|
||
.liquid-ver-overlay {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
/* 紧贴页脚上沿向上展开:卡片浮在页脚上方滑出,不遮盖页脚横幅 */
|
||
bottom: 100%;
|
||
height: 160px;
|
||
z-index: 30;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
justify-content: center;
|
||
padding-bottom: 8px;
|
||
/* 不要灰黑蒙版/背景模糊:只保留浮出的卡片本身,下方页脚区内容
|
||
原样可见(用户反馈);卡片自带渐变底 + 投影,不靠蒙版衬托 */
|
||
background: none;
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
opacity: 0;
|
||
transition: opacity .5s ease;
|
||
/* 蒙版去掉后 overlay 自身透明,不能再拦在内容上方吃掉点击:
|
||
整层穿透,只有卡片(.liquid-ver-toast)恢复可点 */
|
||
pointer-events: none;
|
||
}
|
||
.liquid-ver-overlay.show { opacity: 1; }
|
||
/* 流程内错误提示(下载失败等):盖在"检测到更新"卡片上、卡片内上下左右
|
||
居中模糊弹出(对齐 pushbot pb_ota_showError 设计);遮罩随卡片圆角。
|
||
此态仍需拦截下层卡片的点击,避免错误提示期间误触"立即更新" */
|
||
.liquid-ver-overlay.card-centered {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
height: auto;
|
||
z-index: 2;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding-bottom: 0;
|
||
border-radius: inherit;
|
||
pointer-events: auto;
|
||
}
|
||
.liquid-ver-toast {
|
||
min-width: 200px;
|
||
max-width: min(460px, calc(100vw - 24px));
|
||
padding: 14px 22px;
|
||
border-radius: 12px;
|
||
position: relative;
|
||
text-align: center;
|
||
color: #ffffff;
|
||
box-shadow: 0 14px 34px rgba(15, 23, 42, 0.35);
|
||
transform: scale(.92) translateY(14px);
|
||
transition: transform .5s ease;
|
||
/* 父层 overlay 已 pointer-events:none,卡片需自行恢复可点
|
||
(按钮操作、点卡片跳作者仓库) */
|
||
pointer-events: auto;
|
||
}
|
||
.liquid-ver-overlay.show .liquid-ver-toast { transform: scale(1) translateY(0); }
|
||
/* 状态色:内测=紫 / 最新=绿 / 有更新=橙 / 失败=红(与 pushbot 一致) */
|
||
.liquid-ver-toast.is-ahead {
|
||
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
|
||
border: 1px solid rgba(167, 139, 250, 0.45);
|
||
}
|
||
.liquid-ver-toast.is-latest {
|
||
background: linear-gradient(135deg, #10b981, #059669);
|
||
border: 1px solid rgba(52, 211, 153, 0.45);
|
||
}
|
||
.liquid-ver-toast.is-update {
|
||
background: linear-gradient(135deg, #f59e0b, #ea580c);
|
||
border: 1px solid rgba(251, 146, 60, 0.5);
|
||
}
|
||
.liquid-ver-toast.is-err {
|
||
background: linear-gradient(135deg, #ef4444, #dc2626);
|
||
border: 1px solid rgba(248, 113, 113, 0.45);
|
||
}
|
||
.liquid-ver-toast-msg {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
line-height: 1.5;
|
||
}
|
||
.liquid-ver-toast-sub {
|
||
margin-top: 7px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
opacity: .9;
|
||
font-family: Menlo, Consolas, monospace;
|
||
}
|
||
/* 非更新态卡片可点击(整卡超链接作者仓库) */
|
||
.liquid-ver-toast.is-clickable { cursor: pointer; }
|
||
/* 页脚版本号:检查中闪烁(同 pushbot badge) */
|
||
.liquid-version-link.is-checking {
|
||
opacity: .6;
|
||
animation: liquid-blink .8s ease-in-out infinite;
|
||
}
|
||
@keyframes liquid-blink {
|
||
0%, 100% { opacity: 1; }
|
||
50% { opacity: .4; }
|
||
}
|
||
|
||
/* ── OTA 操作按钮行(与 pushbot 同款 frosted 白按钮)── */
|
||
.liquid-ota-btn-row {
|
||
display: flex;
|
||
flex-wrap: nowrap;
|
||
gap: 4px;
|
||
justify-content: center;
|
||
margin-top: 12px;
|
||
}
|
||
.liquid-ota-btn {
|
||
padding: 5px 10px;
|
||
border-radius: 8px;
|
||
border: 1px solid rgba(255, 255, 255, .35);
|
||
background: rgba(255, 255, 255, .18);
|
||
color: #fff;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background .2s, transform .15s;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
flex-shrink: 1;
|
||
min-width: 0;
|
||
}
|
||
.liquid-ota-btn:hover { background: rgba(255, 255, 255, .3); transform: translateY(-1px); }
|
||
.liquid-ota-btn:active { transform: scale(.96); }
|
||
.liquid-ota-btn:disabled { opacity: .55; cursor: not-allowed; transform: none; }
|
||
.liquid-ota-btn.liquid-ota-ready { background: rgba(16, 185, 129, .45); border-color: rgba(52, 211, 153, .6); }
|
||
/* 圆环进度(One-Click/Pull 下载进度) */
|
||
.liquid-ota-ring { display: inline-block; width: 18px; height: 18px; vertical-align: middle; }
|
||
.liquid-ota-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
|
||
.liquid-ota-ring-bg { fill: none; stroke: rgba(255, 255, 255, .2); stroke-width: 2.8; }
|
||
.liquid-ota-ring-fg {
|
||
fill: none;
|
||
stroke: #fff;
|
||
stroke-width: 2.8;
|
||
stroke-linecap: round;
|
||
transition: stroke-dasharray .35s ease;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
10. CBI form components
|
||
========================================================================== */
|
||
|
||
.cbi-map {
|
||
margin: 0 0 18px;
|
||
padding: 18px 22px 22px;
|
||
border-radius: var(--glass-radius-lg);
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
.cbi-map-descr {
|
||
color: var(--text-color-medium);
|
||
font-size: 13px;
|
||
padding: 0 0 12px;
|
||
}
|
||
|
||
.cbi-section {
|
||
margin: 0 0 16px;
|
||
padding: 16px 20px 18px;
|
||
border-radius: var(--glass-radius);
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
.cbi-section-node {
|
||
padding: 4px 0;
|
||
}
|
||
|
||
/* 多实例 section(如 Dropbear 实例):每个实例独立子卡片。
|
||
由 main.js 的 styleMultiSections 检测"同一 cbi-section 内有多个
|
||
平级 cbi-section-node"时加 liquid-multi-node 类(CSS :has 兄弟
|
||
选择器在部分渲染/嵌套结构下不可靠) */
|
||
.cbi-section-node.liquid-multi-node {
|
||
margin: 0 0 12px;
|
||
background: color-mix(in srgb, var(--glass-bg) 55%, transparent);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: var(--glass-radius);
|
||
padding: 12px 16px 10px;
|
||
box-shadow: inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
.cbi-section-node.liquid-multi-node:last-child {
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.cbi-section-node > .cbi-section {
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.cbi-title {
|
||
padding: 2px 0 10px;
|
||
}
|
||
|
||
.cbi-title h3 {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 15px;
|
||
color: var(--text-color-highest);
|
||
margin: 0;
|
||
}
|
||
|
||
.cbi-title h3::before {
|
||
content: "";
|
||
width: 4px;
|
||
height: 16px;
|
||
border-radius: 4px;
|
||
background: var(--accent-grad);
|
||
}
|
||
|
||
/* value rows */
|
||
.cbi-value {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 16px;
|
||
padding: 10px 0;
|
||
border-top: 1px solid var(--border-color-low);
|
||
}
|
||
|
||
.cbi-value:first-of-type {
|
||
border-top: 0;
|
||
}
|
||
|
||
.cbi-value.cbi-value-last {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
.cbi-value-title {
|
||
flex: 0 0 260px;
|
||
max-width: 46%;
|
||
padding-top: 7px;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
color: var(--text-color-high);
|
||
text-align: right;
|
||
}
|
||
|
||
.cbi-value-field {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
/* 输入框下方的按钮组(如"与浏览器同步/与 NTP 服务器同步"):
|
||
span 默认 inline,margin-top 不生效——改 block 并与上方输入框留缝 */
|
||
.cbi-value-field .control-group {
|
||
display: block;
|
||
margin-top: 8px;
|
||
}
|
||
|
||
.cbi-value-description {
|
||
font-size: 11.5px;
|
||
color: var(--text-color-low);
|
||
padding-top: 3px;
|
||
}
|
||
|
||
.cbi-value-field .cbi-value-description {
|
||
padding: 3px 0 0;
|
||
}
|
||
|
||
/* text / number / password / select / textarea */
|
||
input[type="text"],
|
||
input[type="password"],
|
||
input[type="number"],
|
||
input[type="email"],
|
||
input[type="tel"],
|
||
input[type="url"],
|
||
input[type="search"],
|
||
input[type="date"],
|
||
input[type="time"],
|
||
input[type="datetime-local"],
|
||
input[type="month"],
|
||
input[type="week"],
|
||
select,
|
||
textarea,
|
||
.cbi-input-text,
|
||
.cbi-input-password,
|
||
.cbi-input-number {
|
||
width: 100%;
|
||
max-width: 100%;
|
||
box-sizing: border-box;
|
||
padding: 8px 12px;
|
||
font-family: inherit;
|
||
font-size: 13px;
|
||
color: var(--text-color-high);
|
||
background: var(--field-bg);
|
||
border: 1px solid var(--border-color-medium);
|
||
border-radius: 10px;
|
||
-webkit-backdrop-filter: blur(6px);
|
||
backdrop-filter: blur(6px);
|
||
transition: border-color .15s ease, box-shadow .15s ease, background .15s ease;
|
||
}
|
||
|
||
input:focus,
|
||
select:focus,
|
||
textarea:focus {
|
||
outline: none;
|
||
border-color: var(--primary-color-high);
|
||
background: var(--glass-bg-hover);
|
||
box-shadow: 0 0 0 3px var(--accent-glow);
|
||
}
|
||
|
||
input[disabled],
|
||
select[disabled],
|
||
textarea[disabled] {
|
||
opacity: var(--disabled-opacity);
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* 原生裸 select(.cbi-input-select 等)现代化:去浏览器原生外观,
|
||
主题圆角/边框/聚焦。此规则作为「不支持 base-select」内核的回退:
|
||
Chrome 135+/Edge 135+/Safari 27 会被下方更高特异性的 base-select
|
||
规则覆盖(从而启用 ::picker(select) 自绘下拉);老内核/Firefox 则
|
||
保留 appearance:none(展开菜单退回系统原生,但折叠态仍是主题样式)。
|
||
不自绘箭头(避免在 pushbot 等第三方页面出现重复箭头)。 */
|
||
select:not([multiple]) {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
padding: 8px 12px;
|
||
background-color: var(--field-bg);
|
||
border: 1px solid var(--border-color-medium);
|
||
border-radius: 12px;
|
||
cursor: pointer;
|
||
transition: border-color .15s ease, box-shadow .15s ease;
|
||
}
|
||
|
||
select:not([multiple]):hover {
|
||
border-color: var(--primary-color-high);
|
||
}
|
||
|
||
/* .cbi-select(分割按钮风格)内部 select 由外层 ::after 提供箭头 */
|
||
|
||
/* 原生 select 弹出列表的选项项美化(浏览器原生面板本身无法定制,
|
||
但 option 的背景/文字色/字体在 Chrome/Edge/Firefox 上可生效) */
|
||
select option {
|
||
background: var(--field-bg, #fff);
|
||
color: var(--text-color-high);
|
||
font-size: 13px;
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
select option:checked {
|
||
background: var(--primary-color-high);
|
||
color: var(--on-primary-color, #fff);
|
||
}
|
||
|
||
|
||
textarea {
|
||
min-height: 72px;
|
||
resize: vertical;
|
||
}
|
||
|
||
/* checkbox / radio */
|
||
input[type="checkbox"],
|
||
input[type="radio"] {
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
width: 18px;
|
||
height: 18px;
|
||
margin: 0;
|
||
vertical-align: -4px;
|
||
flex-shrink: 0;
|
||
cursor: pointer;
|
||
background: var(--field-bg);
|
||
border: 1.5px solid var(--border-color-high);
|
||
transition: background .15s ease, border-color .15s ease, box-shadow .15s ease;
|
||
}
|
||
|
||
input[type="checkbox"] {
|
||
border-radius: 6px;
|
||
}
|
||
|
||
input[type="radio"] {
|
||
border-radius: 50%;
|
||
}
|
||
|
||
input[type="checkbox"]:checked,
|
||
input[type="radio"]:checked {
|
||
background: var(--accent-grad);
|
||
/* 白边 + 外发光:下拉选项高亮背景(accent)盖住勾选框时,
|
||
勾号和边框仍清晰可见 */
|
||
border: 2px solid rgba(255, 255, 255, 0.9);
|
||
box-shadow: 0 0 0 2px var(--accent-glow), 0 2px 8px var(--accent-glow);
|
||
}
|
||
|
||
input[type="checkbox"]:checked::after {
|
||
content: "";
|
||
position: absolute;
|
||
}
|
||
|
||
input[type="checkbox"]:checked {
|
||
position: relative;
|
||
}
|
||
|
||
input[type="checkbox"]:checked::after {
|
||
left: 4px;
|
||
top: 0;
|
||
width: 6px;
|
||
height: 10px;
|
||
border: solid #ffffff;
|
||
border-width: 0 2.5px 2.5px 0;
|
||
transform: rotate(45deg);
|
||
}
|
||
|
||
input[type="radio"]:checked::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 50%;
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: var(--on-primary-color);
|
||
transform: translate(-50%, -50%);
|
||
}
|
||
|
||
input[type="radio"] {
|
||
position: relative;
|
||
}
|
||
|
||
/* buttons */
|
||
.btn,
|
||
.cbi-button {
|
||
display: inline-block;
|
||
/* 原 9px 18px + 1px border;border 移除后各补 1px,整体尺寸不变 */
|
||
padding: 10px 19px;
|
||
font-family: inherit;
|
||
font-size: 12.5px;
|
||
font-weight: 600;
|
||
line-height: 1.4;
|
||
white-space: nowrap;
|
||
color: var(--text-color-high);
|
||
text-align: center;
|
||
text-decoration: none;
|
||
/* 去浏览器原生按钮外观:WebKit 等会叠加一层原生矩形描边,
|
||
不跟 border-radius,出现"两层按钮"的错觉 */
|
||
appearance: none;
|
||
-webkit-appearance: none;
|
||
background: var(--glass-bg);
|
||
/* 不用 border:部分引擎里 border 绘制层会与背景分层栅格化,
|
||
不跟 border-radius 的弧度——圆角处缺描边、左右缘露出方剪裁
|
||
竖缝/下层按钮。改用同色 inset box-shadow 描边:与背景同层绘制,
|
||
永远贴合圆角(各变体的 border-color 规则随之废弃) */
|
||
border: none;
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
/* 按钮不用 backdrop-filter:WebKit/老内核里它的滤镜区域是矩形、
|
||
不跟 border-radius 裁剪,圆角按钮左右缘会露出方形剪裁的
|
||
深/浅竖缝,四角也不跟弧度。按钮面积小、模糊收益低,
|
||
玻璃感靠半透明底+描边阴影即可(下拉胶囊同理,见
|
||
.cbi-dropdown.cbi-button-apply / -negative 的覆盖) */
|
||
box-shadow: var(--glass-shadow-soft), inset 0 0 0 1px var(--glass-border),
|
||
inset 0 1px 0 var(--glass-inset);
|
||
transition: transform .12s ease, box-shadow .15s ease,
|
||
background .15s ease, color .15s ease, opacity .15s ease;
|
||
}
|
||
|
||
.btn:hover,
|
||
.cbi-button:hover {
|
||
background: var(--glass-bg-hover);
|
||
text-decoration: none;
|
||
}
|
||
|
||
/* 彩色(accent/负面)按钮 hover 保持自身色系并微亮,不发白 */
|
||
.cbi-button-action:hover,
|
||
.cbi-button-apply:hover,
|
||
.cbi-button-reload:hover,
|
||
.cbi-button-edit:hover,
|
||
.cbi-button-positive:hover,
|
||
.cbi-button-save:hover {
|
||
background: var(--accent-grad);
|
||
filter: brightness(1.1);
|
||
}
|
||
|
||
.cbi-button-negative:hover,
|
||
.cbi-button-reset:hover,
|
||
.cbi-button-remove:hover {
|
||
background: linear-gradient(135deg, #f06767, var(--error-color-high));
|
||
filter: brightness(1.08);
|
||
}
|
||
|
||
.btn:active,
|
||
.cbi-button:active {
|
||
transform: translateY(1px);
|
||
}
|
||
|
||
.btn:disabled,
|
||
.cbi-button:disabled {
|
||
opacity: var(--disabled-opacity);
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.cbi-button-action,
|
||
.cbi-button-apply,
|
||
.cbi-button-reload,
|
||
.cbi-button-edit,
|
||
.btn.primary,
|
||
.cbi-button-positive,
|
||
.cbi-button-save {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
box-shadow: 0 6px 16px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
}
|
||
|
||
.cbi-button-negative,
|
||
.cbi-button-reset,
|
||
.cbi-button-remove,
|
||
.cbi-section-remove .cbi-button {
|
||
color: var(--on-error-color);
|
||
background: linear-gradient(135deg, #f06767, var(--error-color-high));
|
||
box-shadow: 0 6px 16px rgba(var(--error-color-high-rgb), 0.35);
|
||
}
|
||
|
||
/* 多实例 section(Dropbear 等)的删除按钮:在标题行右侧,
|
||
从卡片右上角向内部收一点,不顶着卡片边缘(float:right 默认贴边)。
|
||
liquid-multi-sec 由 main.js 检测多实例时加到外层 section */
|
||
.cbi-section.liquid-multi-sec > .cbi-section-remove {
|
||
margin: 14px 14px 0 0;
|
||
}
|
||
|
||
.cbi-button-neutral,
|
||
.cbi-button-download,
|
||
.cbi-button-find,
|
||
.cbi-button-link,
|
||
.cbi-button-up,
|
||
.cbi-button-down,
|
||
.cbi-button-pause {
|
||
color: var(--text-color-high);
|
||
background: var(--glass-bg);
|
||
/* 描边由基础规则的 inset 环形阴影提供(原 border-color 已废弃) */
|
||
}
|
||
|
||
/* page actions */
|
||
.cbi-page-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
gap: 10px;
|
||
justify-content: flex-end;
|
||
padding: 14px 4px 4px;
|
||
}
|
||
|
||
.cbi-page-actions::after {
|
||
content: "";
|
||
clear: both;
|
||
}
|
||
|
||
/* 原生 select 美化(Combobox 替换已禁用,所有 select 保留原生 DOM)。
|
||
折叠态 + 展开菜单均通过 CSS 美化(select::picker(select),Chrome 135+/
|
||
Edge 135+/Safari 27 支持),与 Combobox 视觉统一但无 DOM 替换开销。
|
||
无线设置页 formvalue 联动链因此完全正常。 */
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select) {
|
||
/* appearance: base-select 启用 select::picker(select) 自定义下拉菜单,
|
||
不用 appearance: none(那会禁用 picker)。再用显式属性覆盖默认外观。
|
||
支持 base-select 的浏览器此声明覆盖上方 appearance:none 回退;
|
||
不支持的浏览器此声明被判无效丢弃,自动回退到 appearance:none。 */
|
||
appearance: base-select;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 10px;
|
||
padding: 8px 34px 8px 12px;
|
||
min-height: 35px;
|
||
min-width: 140px;
|
||
font-size: 13px;
|
||
color: var(--text-color-high);
|
||
cursor: pointer;
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
transition: border-color .15s ease, box-shadow .15s ease;
|
||
}
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select):hover {
|
||
border-color: var(--primary-color-high);
|
||
}
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select):focus {
|
||
outline: none;
|
||
box-shadow: 0 0 0 2px var(--accent-glow);
|
||
}
|
||
|
||
/* select 下拉菜单美化(Chrome 135+/Edge 135+/Safari 27 的 select::picker(select)) */
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select)::picker(select) {
|
||
appearance: base-select;
|
||
background: var(--glass-bg-strong);
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 10px;
|
||
box-shadow: var(--glass-shadow);
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
padding: 4px;
|
||
/* 宽度:自适应最长选项内容 + 微余量;禁止撑满到窗口边缘 */
|
||
width: auto;
|
||
max-inline-size: calc(100vw - 20px);
|
||
overflow: auto;
|
||
/* 覆盖浏览器默认 span-inline-end(会横向撑满到右边缘) */
|
||
position-area: block-end;
|
||
/* 覆盖浏览器默认 max-block-size: stretch(纵向撑满) */
|
||
max-block-size: auto;
|
||
}
|
||
/* picker 内选项的背景/文字色:Chrome 不支持 ::picker(select) option 选择器,
|
||
直接用 option 伪类在 picker 内生效的继承机制覆盖。 */
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select) option {
|
||
padding: 8px 12px;
|
||
color: var(--text-color-high);
|
||
transition: background .12s ease;
|
||
}
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select) option:checked {
|
||
background: var(--picker-option-checked);
|
||
color: var(--text-color-highest);
|
||
}
|
||
select:not([multiple]):not(.cbi-dropdown select):not(.cbi-select select) option:hover {
|
||
background: var(--picker-option-hover);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
11. Tables
|
||
========================================================================== */
|
||
|
||
.table,
|
||
.cbi-section-table {
|
||
width: 100%;
|
||
border-collapse: separate;
|
||
border-spacing: 0;
|
||
margin: 6px 0;
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
border: 1px solid var(--border-color-low);
|
||
}
|
||
|
||
.table th,
|
||
.cbi-section-table th,
|
||
.table .th,
|
||
.cbi-section-table .th {
|
||
padding: 9px 12px;
|
||
font-size: 11.5px;
|
||
font-weight: 700;
|
||
text-align: left;
|
||
letter-spacing: .3px;
|
||
color: var(--text-color-medium);
|
||
background: var(--glass-bg);
|
||
border-bottom: 1px solid var(--border-color-low);
|
||
display: table-cell;
|
||
float: none;
|
||
}
|
||
|
||
.table td,
|
||
.cbi-section-table td,
|
||
.table .td,
|
||
.cbi-section-table .td {
|
||
padding: 8px 12px;
|
||
font-size: 13px;
|
||
vertical-align: middle;
|
||
border-bottom: 1px solid var(--border-color-low);
|
||
background: transparent;
|
||
display: table-cell;
|
||
float: none;
|
||
}
|
||
|
||
/* LuCI 新版用 .tr .td 类模拟表格,需要显式设置 display */
|
||
.tr {
|
||
display: table-row;
|
||
}
|
||
|
||
.td {
|
||
display: table-cell;
|
||
float: none;
|
||
}
|
||
|
||
.table tr:last-child td,
|
||
.cbi-section-table tr:last-child td {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
/* 双色交叉(斑马纹)与 hover 画在整行上,底色延伸到表格行头尾 */
|
||
.table tr.cbi-rowstyle-2,
|
||
.cbi-section-table tr.cbi-rowstyle-2,
|
||
.table tr:nth-child(even),
|
||
.cbi-section-table tr:nth-child(even) {
|
||
background: var(--table-stripe);
|
||
}
|
||
|
||
/* 接口/设备表格行:强制不透明。luci 的"hover 删除按钮"或渲染逻辑
|
||
会给行加内联 style="opacity:0.5",让已连接接口的图标/文字呈半透、
|
||
被误判为未连接;统一压回 1(连接状态由图标本身区分) */
|
||
.cbi-section-table tr {
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
.table tr:hover,
|
||
.cbi-section-table tr:hover {
|
||
background: var(--glass-bg-hover);
|
||
}
|
||
|
||
/* 标签/值两列表格(.table 内含 td[width="33%"]:概览"系统"表、存储/内存
|
||
用量表等多个页面共用同一标记 → 统一处理、不按页面特化;参考 Argon 让
|
||
列宽随内容自适应,再叠一层"放不下就换行"的弹性):
|
||
① 宽度足够:标签列 20ch 对齐、值同行(16ch 会把最长标签"CPU 使用率(%)"
|
||
挤成两行——实测 16ch 折行、18ch 起单行,20ch 留中文字体度量余量);
|
||
② 宽度不足:值整块换到下一行、占满行宽;
|
||
③ 极窄:标签列可压缩,标题与值的文字都换行(不再横向滚动/被裁)。
|
||
(原方案:标签锁死 20ch + 值 nowrap,只能靠横向滚动,窄屏很挤) */
|
||
.table:has(td[width="33%"]) {
|
||
display: block;
|
||
overflow-x: auto; /* 仅兜底:单个不可断的超长串仍可横滚 */
|
||
}
|
||
|
||
.table:has(td[width="33%"]) tr {
|
||
display: flex;
|
||
/* 放不下的列换到下一行,而不是挤压/横向滚动 */
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
/* 标题与值都允许换行 */
|
||
white-space: normal;
|
||
}
|
||
|
||
.table:has(td[width="33%"]) tr > td {
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
}
|
||
|
||
.table:has(td[width="33%"]) tr > td[width="33%"] {
|
||
/* 首选 20ch 保持各行列对齐。
|
||
!important 必须:单行规则(r13)给所有格设了 flex/width !important,
|
||
而 important 无视特异性 → 不加会把桌面固定列压成"文字宽"、
|
||
值列逐行错位(实测踩过) */
|
||
flex: 0 0 20ch !important;
|
||
width: 20ch !important;
|
||
max-width: 20ch !important;
|
||
white-space: normal !important;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.table:has(td[width="33%"]) tr > td:last-child {
|
||
/* 以内容宽为基准:同行放不下 → 整块换行并撑满该行 */
|
||
flex: 1 1 auto;
|
||
min-width: 0;
|
||
white-space: normal;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
12. Dropdown, dynamic list, progressbar
|
||
========================================================================== */
|
||
|
||
.cbi-dropdown {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
position: relative;
|
||
min-width: 140px;
|
||
/* 多选空值等"无当前值行"(ul 内无 li[display])时 ul 高度为 0,
|
||
胶囊会塌成扁条:给固定最小高度,与含当前值行的胶囊等高 */
|
||
min-height: 35px;
|
||
padding: 8px 34px 8px 12px;
|
||
font-size: 13px;
|
||
/* 闭合:仿左侧菜单选中项——主题色柔和渐变(左浓右淡)叠加玻璃,
|
||
透明带模糊,不再整块 accent 渐变填充 */
|
||
color: var(--text-color-high);
|
||
background:
|
||
linear-gradient(100deg,
|
||
color-mix(in srgb, var(--primary-color-high) 24%, transparent),
|
||
color-mix(in srgb, var(--primary-color-high) 5%, transparent) 62%,
|
||
color-mix(in srgb, var(--primary-color-high) 12%, transparent)),
|
||
var(--glass-bg);
|
||
border: 1px solid color-mix(in srgb, var(--primary-color-high) 42%, transparent);
|
||
border-radius: 10px;
|
||
box-shadow: 0 4px 16px color-mix(in srgb, var(--primary-color-high) 20%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.55);
|
||
-webkit-backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(10px) saturate(var(--glass-sat));
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 下拉图标区(ui.js 生成的 span.open):绝对定位到右侧,用竖线
|
||
与按钮主体分割。点击 ▾ 展开下拉;点击左侧主体执行主操作。
|
||
这是 ComboButton(保存并应用)等下拉按钮的下拉入口,
|
||
不能用背景箭头(点击背景图不命中 span.open,导致无法下拉)。 */
|
||
.cbi-dropdown > .open {
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 34px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-left: 1px solid rgba(255, 255, 255, 0.35);
|
||
color: var(--on-primary-color);
|
||
cursor: pointer;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* cbi-dropdown:未打开时 ul 作为当前值行(只显示选中项),
|
||
打开时变为玻璃下拉列表 */
|
||
.cbi-dropdown > ul {
|
||
display: flex;
|
||
width: 100%;
|
||
margin: 0 !important;
|
||
padding: 0;
|
||
list-style: none;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.cbi-dropdown > ul > li {
|
||
display: none;
|
||
}
|
||
|
||
/* 当前值行:与官方 bootstrap 一致,无条件显示 li[display]
|
||
(不依赖 :not([open]),防止关闭流程中断时值不可见) */
|
||
.cbi-dropdown > ul > li[display] {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 多选下拉:选中的多个选项在胶囊内换行展示(每个一行),
|
||
按钮随之动态拓高,页面上直观看得出已选哪些;
|
||
行高随内容在文档流中自然增高,不溢出所在行/卡片 */
|
||
.cbi-dropdown[multiple] > ul {
|
||
flex-wrap: wrap;
|
||
gap: 2px 8px;
|
||
}
|
||
|
||
.cbi-dropdown[multiple] > ul > li[display] {
|
||
/* 一个选项一行(竖向),不允许并行显示 */
|
||
flex: 0 0 100%;
|
||
white-space: normal;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
|
||
/* 打开时:触发器淡出(背景/边框/箭头/预览行全部隐去),
|
||
视觉上只剩一个弹出的选项列表——避免“框 + 弹出列表”看起来像
|
||
两个下拉菜单堆叠。ui.js 打开时会把原 ul 克隆为 ul.preview,
|
||
弹出规则只作用于 ul.dropdown。 */
|
||
/* 打开时提升自身层级:backdrop-filter 让胶囊形成层叠上下文,
|
||
内部弹出列表 z-index 9999 出不去,会被后续卡片/层盖住 */
|
||
.cbi-dropdown[open] {
|
||
z-index: 1000;
|
||
/* 打开时胶囊纯玻璃(preview 显示当前值),取消主题色渐变 */
|
||
background: var(--glass-bg);
|
||
border-color: var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
/* 暗黑下闭合/打开:外发光略强、内高光调暗(与左侧菜单选中项一致) */
|
||
:root[data-darkmode="true"] .cbi-dropdown {
|
||
box-shadow: 0 4px 18px color-mix(in srgb, var(--primary-color-high) 30%, transparent),
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.14);
|
||
}
|
||
|
||
/* 普通下拉(非"保存并应用"类按钮)无分隔:箭头区不画竖线,窄一点 */
|
||
.cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative) > .open {
|
||
border-left: none;
|
||
width: 26px;
|
||
}
|
||
|
||
/* 普通下拉文字强制主题文字色:on-primary-color 在亮/暗下分别是
|
||
白/黑,叠在新渐变玻璃上正好白底白字、黑底黑字看不清 */
|
||
.cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative),
|
||
.cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative) > ul > li,
|
||
.cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative) > ul > li[display] {
|
||
color: var(--text-color-high) !important;
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative),
|
||
:root[data-darkmode="true"] .cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative) > ul > li,
|
||
:root[data-darkmode="true"] .cbi-dropdown:not(.cbi-button-apply):not(.cbi-button-negative) > ul > li[display] {
|
||
color: #d9dfe9 !important;
|
||
}
|
||
|
||
.cbi-dropdown[open] > .more,
|
||
.cbi-dropdown[open] > div {
|
||
display: none;
|
||
}
|
||
|
||
/* ComboButton(保存并应用 / Apply unchecked):保持 accent 渐变按钮
|
||
样式(而非普通下拉框),右侧竖线分割下拉图标区 */
|
||
.cbi-dropdown.cbi-button-apply {
|
||
background: var(--accent-grad);
|
||
/* 与 .cbi-button 同理:废弃 border(透明边框也是独立绘制层,
|
||
引擎分层栅格化时不跟 border-radius,露出两层/方剪裁描边)。
|
||
padding 各边 +1px 补偿边框厚度(基础 8px 34px 8px 12px),尺寸不变 */
|
||
border: none;
|
||
padding: 9px 35px 9px 13px;
|
||
color: var(--on-primary-color);
|
||
box-shadow: 0 6px 16px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
/* 与 .cbi-button 同理:不透明渐变底本就用不上模糊,去掉继承来的
|
||
backdrop-filter,避免 WebKit 矩形滤镜区域在圆角缘露缝 */
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
/* 保存并应用:宽度只匹配文字,不继承普通下拉框的 min-width */
|
||
min-width: auto;
|
||
}
|
||
|
||
.cbi-dropdown.cbi-button-apply > .open {
|
||
border-left-color: rgba(255, 255, 255, 0.35);
|
||
color: var(--on-primary-color);
|
||
}
|
||
|
||
.cbi-dropdown.cbi-button-negative {
|
||
background: linear-gradient(135deg, #f06767, var(--error-color-high));
|
||
/* 与 apply 同理:废弃 border 以免独立绘制层不跟圆角;padding +1px 补偿 */
|
||
border: none;
|
||
padding: 9px 35px 9px 13px;
|
||
color: var(--on-error-color);
|
||
box-shadow: 0 6px 16px rgba(var(--error-color-high-rgb), 0.35);
|
||
/* 与 apply 同理:不透明渐变底用不上模糊,去掉以免矩形滤镜缝 */
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
min-width: auto;
|
||
}
|
||
|
||
.cbi-dropdown.cbi-button-negative > .open {
|
||
border-left-color: rgba(255, 255, 255, 0.35);
|
||
color: var(--on-error-color);
|
||
}
|
||
|
||
/* ComboButton 闭合时:按钮本身已有渐变底色,内部当前值行
|
||
不再叠加 accent 底色(打开后的弹出列表里选中项仍保留高亮) */
|
||
.cbi-dropdown:not([open]) > ul > li[selected],
|
||
.cbi-dropdown.cbi-button-apply:not([open]) > ul > li[selected],
|
||
.cbi-dropdown.cbi-button-negative:not([open]) > ul > li[selected] {
|
||
background: transparent;
|
||
}
|
||
|
||
/* 原生 select(.cbi-select):与 cbi-dropdown 统一的分割按钮风格 */
|
||
.cbi-select {
|
||
position: relative;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
min-width: 140px;
|
||
/* 与按钮族同理:废弃透明 border(独立绘制层不跟圆角),
|
||
padding 各边 +1px 补偿(原 8px 34px 8px 12px),尺寸不变 */
|
||
padding: 9px 35px 9px 13px;
|
||
/* 与 cbi-dropdown 统一的渐变按钮风格 */
|
||
background: var(--accent-grad);
|
||
border: none;
|
||
border-radius: 10px;
|
||
box-shadow: 0 6px 16px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.cbi-select select {
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
width: 100%;
|
||
background: transparent;
|
||
border: none;
|
||
outline: none;
|
||
height: auto;
|
||
padding: 0;
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
.cbi-select select option {
|
||
background: var(--field-bg);
|
||
color: var(--text-color-high);
|
||
}
|
||
|
||
.cbi-select::after {
|
||
content: "▾";
|
||
position: absolute;
|
||
right: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 34px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-left: 1px solid rgba(255, 255, 255, 0.35);
|
||
color: var(--on-primary-color);
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* 单选时多余的 “···”(多选超限指示)默认隐藏,与官方一致 */
|
||
.cbi-dropdown > .more {
|
||
display: none;
|
||
}
|
||
|
||
.cbi-dropdown[multiple][more] > .more {
|
||
display: flex;
|
||
}
|
||
|
||
/* 下拉打开时临时解除卡片链 backdrop-filter(JS holdCardBlur 加类):
|
||
玻璃卡片的 backdrop-filter 创建 stacking context + containing
|
||
block,内部下拉 z-index 出不去会被遮挡;解除后下拉可盖住后续卡片 */
|
||
.liquid-dd-bf-off,
|
||
.liquid-dd-bf-off * {
|
||
-webkit-backdrop-filter: none !important;
|
||
backdrop-filter: none !important;
|
||
}
|
||
|
||
.cbi-dropdown[open] > ul.dropdown {
|
||
display: block;
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
top: calc(100% + 4px);
|
||
z-index: 9999;
|
||
max-height: 260px;
|
||
overflow-y: auto;
|
||
padding: 5px;
|
||
background: rgba(255, 255, 255, 0.92);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||
backdrop-filter: blur(var(--glass-blur));
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 12px;
|
||
box-shadow: var(--glass-shadow);
|
||
}
|
||
|
||
/* 暗黑模式下下拉选项列表更实一些(alpha 0.74 → 0.97 + 更强模糊),
|
||
避免透出下方内容导致选项文字看不清;仍保留玻璃质感 */
|
||
:root[data-darkmode="true"] .cbi-dropdown[open] > ul.dropdown {
|
||
background: rgba(22, 29, 44, 0.97);
|
||
-webkit-backdrop-filter: blur(32px) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(32px) saturate(var(--glass-sat));
|
||
}
|
||
|
||
/* 替换型下拉(liquid-combo-pilot):不用主题色渐变,纯玻璃光色,
|
||
黑白场景各自适配(亮色浅玻璃 / 暗色深玻璃实底) */
|
||
.cbi-dropdown.liquid-combo-pilot {
|
||
min-height: 24px;
|
||
padding: 4px 34px 4px 10px;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot {
|
||
background: rgba(22, 29, 44, 0.92);
|
||
border-color: rgba(255, 255, 255, 0.16);
|
||
}
|
||
|
||
.cbi-dropdown.liquid-combo-pilot > ul > li[display],
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.preview > li[display] {
|
||
padding-top: 2px;
|
||
padding-bottom: 2px;
|
||
}
|
||
|
||
.cbi-dropdown.liquid-combo-pilot > .open {
|
||
border-left: none;
|
||
width: 24px;
|
||
color: var(--text-color-medium);
|
||
}
|
||
|
||
.cbi-dropdown.liquid-combo-pilot[open] {
|
||
background: var(--glass-bg);
|
||
border-color: var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
/* 暗黑下闭合框更实(glass-bg 0.4 太透,透出内容会淹没浅色文字):
|
||
已由全局暗色渐变覆盖,此处删除旧实底覆盖 */
|
||
|
||
/* 试点弹出列表悬浮:中性灰高亮,不用主题色 */
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.dropdown > li:hover {
|
||
background: rgba(127, 127, 127, 0.18) !important;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
/* 打开下拉瞬间:胶囊内 preview(当前值克隆)在鼠标下方会命中全局
|
||
主题色 hover——强制中性透明,避免"点击时当前选项突然变主题色" */
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.preview > li,
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.preview > li[selected],
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.preview > li:hover,
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.preview > li[display]:hover {
|
||
background: transparent !important;
|
||
color: var(--text-color-high) !important;
|
||
}
|
||
|
||
/* 打开时胶囊本体跟随全局主题色渐变玻璃(r43 的强制覆盖已由全局
|
||
[open] 样式取代,此处删除) */
|
||
|
||
/* 试点下拉全部文字强制主题文字色(防内联样式/其他规则把闭合或
|
||
列表文字染成黑色,暗色下看不清) */
|
||
.cbi-dropdown.liquid-combo-pilot > ul > li,
|
||
.cbi-dropdown.liquid-combo-pilot > ul > li[display],
|
||
.cbi-dropdown.liquid-combo-pilot > ul.preview > li,
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.dropdown > li {
|
||
color: var(--text-color-high) !important;
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot > ul > li,
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot > ul > li[display],
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot > ul.preview > li,
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot[open] > ul.dropdown > li {
|
||
color: #d9dfe9 !important;
|
||
}
|
||
|
||
/* 试点(接口页 proto,main.js 替换的 Combobox 加 liquid-combo-pilot):
|
||
弹出面板去掉 backdrop-filter blur(性能主因)改用高不透明度半透明底,
|
||
阴影降级——视觉几乎不变,打开/滚动明显更顺。仅试点范围生效。 */
|
||
.cbi-dropdown.liquid-combo-pilot[open] > ul.dropdown {
|
||
background: rgba(255, 255, 255, 0.95);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
box-shadow: 0 4px 16px rgba(31, 45, 70, 0.14);
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-dropdown.liquid-combo-pilot[open] > ul.dropdown {
|
||
background: rgba(22, 29, 44, 0.97);
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
|
||
}
|
||
|
||
/* 打开时:preview 克隆作为顶部当前值行(官方机制),
|
||
原 ul 弹出后顶部仍有当前值显示 */
|
||
.cbi-dropdown[open] > ul.preview {
|
||
display: flex;
|
||
flex: 1;
|
||
min-width: 0;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.cbi-dropdown[open] > ul.preview > li[display] {
|
||
display: flex;
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* 打开时胶囊内 preview(当前值克隆)无条件透明:点击瞬间鼠标压在其
|
||
上会命中全局主题色 hover,出现深色渐变背景——所有下拉统一清除 */
|
||
.cbi-dropdown[open] > ul.preview > li,
|
||
.cbi-dropdown[open] > ul.preview > li[selected],
|
||
.cbi-dropdown[open] > ul.preview > li[display],
|
||
.cbi-dropdown[open] > ul.preview > li:hover {
|
||
background: transparent !important;
|
||
}
|
||
|
||
.cbi-dropdown[open] > ul.dropdown > li {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 7px 10px;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: var(--text-color-medium);
|
||
cursor: pointer;
|
||
/* 长文本选项(如 wechatpush 推送模式带描述)允许换行显示全 */
|
||
white-space: normal;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
/* 富选项(cbiRichListValue):闭合显示标题,弹出列表内显示描述 */
|
||
.cbi-dropdown > ul > li .hide-close,
|
||
.cbi-dropdown[open] > ul.dropdown > li .hide-open {
|
||
display: none;
|
||
}
|
||
|
||
.cbi-dropdown > ul > li .hide-open,
|
||
.cbi-dropdown[open] > ul.dropdown > li .hide-close {
|
||
display: initial;
|
||
}
|
||
|
||
.cbi-dropdown > ul > li {
|
||
padding: 7px 10px;
|
||
border-radius: 8px;
|
||
font-size: 13px;
|
||
color: var(--text-color-medium);
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* 下拉选项 hover:更实的背景 + 高对比文字。
|
||
亮色用主题色浅 tint(白色背景上白 hover 看不清),暗黑用实色蓝灰 */
|
||
.cbi-dropdown > ul > li:hover {
|
||
background: color-mix(in srgb, var(--primary-color-high) 16%, #ffffff);
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-dropdown li:hover {
|
||
/* 暗黑 hover:半透明白磨砂高亮。
|
||
注意不能加 backdrop-filter —— li 已位于带玻璃背景的 ul 内,
|
||
再叠一层 blur 会把深色玻璃模糊加深,反而显得比胶囊更暗;
|
||
半透明白叠在深色上即是"凸起的白玻璃"质感 */
|
||
background: rgba(255, 255, 255, 0.07) !important;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.cbi-dropdown > ul > li[selected] {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
}
|
||
|
||
/* 下拉弹出列表里的当前选中项:玻璃磨砂白(半透明白,略强于 hover),
|
||
不取主题/胶囊色 —— 胶囊(含保存并应用的 positive/negative 变色)与
|
||
选中项不再出现"红胶囊 + 绿选项"式的颜色冲突 */
|
||
.cbi-dropdown[open] > ul.dropdown > li[selected] {
|
||
background: rgba(255, 255, 255, 0.12) !important;
|
||
color: #ffffff;
|
||
}
|
||
|
||
.cbi-dropdown > .placeholder,
|
||
.cbi-dropdown[open] > .placeholder {
|
||
color: var(--text-color-low);
|
||
}
|
||
|
||
.cbi-dynlist {
|
||
margin: 4px 0;
|
||
padding: 10px;
|
||
border-radius: 12px;
|
||
background: var(--field-bg);
|
||
border: 1px dashed var(--border-color-medium);
|
||
}
|
||
|
||
.cbi-dynlist .cbi-dynlist-remove {
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.cbi-progressbar {
|
||
position: relative;
|
||
width: 100%;
|
||
min-width: 60px;
|
||
height: 8px;
|
||
overflow: hidden;
|
||
border-radius: 999px;
|
||
background: var(--track-bg);
|
||
}
|
||
|
||
.cbi-progressbar > div {
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
border-radius: 999px;
|
||
background: var(--accent-grad);
|
||
box-shadow: 0 0 10px var(--accent-glow);
|
||
}
|
||
|
||
/* status-page (overview) memory/storage bars: taller, with used/total
|
||
text rendered inside the bar by liquid's main.js */
|
||
.cbi-section .cbi-progressbar {
|
||
height: 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.cbi-section .cbi-progressbar > div {
|
||
/* fill: sub-menu glass gradient (blue → white), theme color ~90%,
|
||
NO border/ring, NO outer drop shadow (it would bleed onto the
|
||
row separator on mobile) — edge implied by inner top highlight */
|
||
background: linear-gradient(135deg,
|
||
color-mix(in srgb, var(--primary-color-high) 90%, transparent),
|
||
rgba(255, 255, 255, 0.72));
|
||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.8),
|
||
inset 0 -1px 0 rgba(0, 0, 0, 0.06);
|
||
}
|
||
|
||
:root[data-darkmode="true"] .cbi-section .cbi-progressbar > div {
|
||
background: linear-gradient(135deg,
|
||
color-mix(in srgb, var(--primary-color-high) 88%, transparent),
|
||
rgba(46, 58, 84, 0.45));
|
||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
.cbi-section .cbi-progressbar > span.liquid-bar-label {
|
||
position: relative;
|
||
z-index: 2;
|
||
margin: 0 auto;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
line-height: 22px;
|
||
color: var(--text-color-highest);
|
||
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.45);
|
||
white-space: nowrap;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
13. Dashboard cards
|
||
========================================================================== */
|
||
|
||
.Dashboard .dashboard-bg {
|
||
border-radius: 20px;
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
.Dashboard .box-s1 {
|
||
min-height: 466px;
|
||
}
|
||
|
||
.Dashboard .title {
|
||
text-align: center;
|
||
padding: 18px 0 8px;
|
||
}
|
||
|
||
.Dashboard .title img {
|
||
width: 60px;
|
||
height: 60px;
|
||
object-fit: contain;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.Dashboard .title h3 {
|
||
margin-top: 8px;
|
||
font-size: 15px;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
.Dashboard .svgmonotone {
|
||
filter: invert(.42) hue-rotate(180deg) saturate(.6);
|
||
}
|
||
|
||
:root[data-darkmode="true"] .Dashboard .svgmonotone {
|
||
filter: invert(.75) hue-rotate(180deg) saturate(.8);
|
||
}
|
||
|
||
.Dashboard .section-content {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 20px;
|
||
padding: 20px 0 0;
|
||
}
|
||
|
||
.Dashboard .section-content > div {
|
||
width: 100%;
|
||
padding: 0 1.5em 1.5em;
|
||
}
|
||
|
||
.Dashboard .section-content > div:nth-child(2) {
|
||
margin-left: 0;
|
||
}
|
||
|
||
.Dashboard .settings-info {
|
||
padding-top: 1em;
|
||
}
|
||
|
||
.Dashboard .settings-info p,
|
||
.Dashboard .settings-info .mt-2 {
|
||
margin: 6px 0;
|
||
font-size: 12.5px;
|
||
}
|
||
|
||
.Dashboard .settings-info p span:first-child,
|
||
.Dashboard .settings-info .mt-2 span:first-child {
|
||
display: inline-block;
|
||
min-width: 9em;
|
||
color: var(--text-color-low);
|
||
text-align: right;
|
||
margin-right: 6px;
|
||
}
|
||
|
||
.Dashboard .settings-info p span:nth-child(2) {
|
||
color: var(--text-color-high);
|
||
font-weight: 500;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.Dashboard .router-status-self .router-status-info .settings-info {
|
||
padding-left: 22px;
|
||
}
|
||
|
||
.Dashboard .internet-status-self .internet-status-info .settings-info,
|
||
.Dashboard .router-status-wifi .wifi-info .settings-info,
|
||
.Dashboard .router-status-lan .lan-info .settings-info {
|
||
display: flex;
|
||
justify-content: space-around;
|
||
flex-wrap: wrap;
|
||
gap: 8px 20px;
|
||
}
|
||
|
||
.Dashboard .radio-info {
|
||
padding: 4px 0;
|
||
}
|
||
|
||
.Dashboard .devices-list .devices-info .td {
|
||
padding: 4px 8px;
|
||
}
|
||
|
||
.Dashboard .devices-list .devices-info .tr .td:first-child {
|
||
width: 33%;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.Dashboard .fade-in {
|
||
animation: liquid-fade-in .35s ease;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
14. Generic app / module card lists
|
||
(covers third party "applications" dashboards: name + description cards)
|
||
========================================================================== */
|
||
|
||
.app-item,
|
||
.app-card,
|
||
.app-box,
|
||
.app-entry {
|
||
border-radius: var(--glass-radius);
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
padding: 14px 18px;
|
||
margin-bottom: 12px;
|
||
transition: transform .15s ease, box-shadow .15s ease, background .15s ease;
|
||
}
|
||
|
||
.app-item:hover,
|
||
.app-card:hover,
|
||
.app-box:hover,
|
||
.app-entry:hover {
|
||
background: var(--glass-bg-hover);
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
.app-item .app-name,
|
||
.app-card .app-title,
|
||
.app-box .app-name,
|
||
.app-entry .app-name {
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
.app-item .app-description,
|
||
.app-card .app-description,
|
||
.app-box .app-description,
|
||
.app-entry .app-description {
|
||
font-size: 12px;
|
||
color: var(--text-color-medium);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
15. Lock screen (sysauth login)
|
||
========================================================================== */
|
||
|
||
body.liquid-login {
|
||
background-color: #0a0d13;
|
||
background-image: url(img/MontereyDark.jpg);
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-repeat: no-repeat;
|
||
background-attachment: fixed;
|
||
}
|
||
|
||
:root[data-darkmode="false"] body.liquid-login {
|
||
background-image: url(img/MontereyLight.jpg);
|
||
}
|
||
|
||
/* Bing 每日壁纸(仅锁屏界面):壁纸可用(data-liquid-bing-ok="1",由
|
||
ucode 模板按当日缓存是否就绪输出)时覆盖亮/暗两种模式的默认壁纸。
|
||
无互联网(ok=0)或按钮关闭时规则不匹配,回退默认 Monterey 壁纸
|
||
(亮色 MontereyLight / 暗色 MontereyDark) */
|
||
body.liquid-login[data-liquid-bing-ok="1"] {
|
||
background-image: var(--liquid-bing) !important;
|
||
background-size: cover;
|
||
background-position: center;
|
||
background-attachment: fixed;
|
||
}
|
||
|
||
body.liquid-login .skiplink {
|
||
display: none;
|
||
}
|
||
|
||
body.liquid-login #view .spinning {
|
||
display: none;
|
||
}
|
||
|
||
body.liquid-login #maincontainer {
|
||
height: 100vh;
|
||
}
|
||
|
||
body.liquid-login #liquid-login {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 1000;
|
||
overflow: auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(8, 11, 16, 0.22);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
/* 登录页纵向 stack:胶囊与登录卡片同处一个 flex 列,胶囊在卡片正上方
|
||
随卡片高度联动——输错密码出现错误提示使卡片变高时,胶囊自动上移,
|
||
不再与卡片重叠(此前胶囊 absolute 定位在中心上方固定 240px,
|
||
卡片变高不会带动它)。此布局同时天然兼容移动端。 */
|
||
body.liquid-login #liquid-login .liquid-login-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 16px;
|
||
max-width: calc(100vw - 40px);
|
||
}
|
||
|
||
/* 登录页胶囊容器:static 跟随 stack 流式布局(不再 absolute 偏移) */
|
||
body.liquid-login #liquid-login-capsules {
|
||
position: static;
|
||
transform: none;
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
flex-wrap: nowrap;
|
||
}
|
||
|
||
/* 登录页胶囊取消 fixed 定位,改为 static 跟随 flex 布局 */
|
||
body.liquid-login .liquid-mode-switch,
|
||
body.liquid-login .liquid-color-switch {
|
||
position: relative !important;
|
||
margin: 0 !important;
|
||
overflow: visible;
|
||
}
|
||
/* 登录页自定义颜色弹出框向上弹出,不被页面底部遮挡 */
|
||
body.liquid-login .liquid-custom-pop {
|
||
top: auto;
|
||
bottom: calc(100% + 8px);
|
||
transform-origin: bottom center;
|
||
}
|
||
/* 登录页透明度滑杆:胶囊正下方,宽度与胶囊总宽一致,紧凑 */
|
||
body.liquid-login .liquid-glass-slider-login {
|
||
position: relative;
|
||
left: 0;
|
||
right: 0;
|
||
top: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 5px;
|
||
margin-top: -1px;
|
||
pointer-events: none;
|
||
z-index: 2;
|
||
width: calc(100% - 60px);
|
||
margin-left: 30px;
|
||
margin-right: 30px;
|
||
}
|
||
body.liquid-login .liquid-glass-slider {
|
||
height: 1px;
|
||
}
|
||
body.liquid-login .liquid-glass-slider::-webkit-slider-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-width: 1px;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 0 0 1.5px rgba(255, 255, 255, 0.1);
|
||
}
|
||
body.liquid-login .liquid-glass-slider::-moz-range-thumb {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-width: 1px;
|
||
}
|
||
|
||
/* 登录页隐藏搜索按钮(无意义) */
|
||
body.liquid-login .liquid-search-btn {
|
||
display: none !important;
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login {
|
||
width: 392px;
|
||
max-width: calc(100vw - 40px);
|
||
margin: 0;
|
||
padding: 30px 34px 26px;
|
||
text-align: center;
|
||
border-radius: 24px;
|
||
background: var(--lock-card-bg);
|
||
-webkit-backdrop-filter: blur(30px) saturate(1.6);
|
||
backdrop-filter: blur(30px) saturate(1.6);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: 0 32px 90px rgba(0, 0, 0, 0.55),
|
||
inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
/* 登录页表单卡片:排除全局 overflow-x:auto(r43 为嵌套卡片滚动而加),
|
||
否则圆角 + 滚动容器会把左上角"用户名"裁掉一角 */
|
||
body.liquid-login .cbi-map,
|
||
body.liquid-login .cbi-section {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
/* 登录页 logo(main.js 注入内联 SVG,跟随主题色 + 玻璃水滴感) */
|
||
body.liquid-login #liquid-login .modal.login .liquid-logo {
|
||
display: block;
|
||
width: 78px;
|
||
height: 78px;
|
||
margin: 0 auto 14px;
|
||
}
|
||
body.liquid-login #liquid-login .modal.login .liquid-logo svg {
|
||
width: 100%;
|
||
height: 100%;
|
||
/* 光晕沿水滴轮廓:多层 drop-shadow 从内到外 */
|
||
filter: drop-shadow(0 0 6px color-mix(in srgb, var(--primary-color-high) 50%, transparent))
|
||
drop-shadow(0 2px 12px color-mix(in srgb, var(--primary-color-high) 40%, transparent))
|
||
drop-shadow(0 0 24px color-mix(in srgb, var(--primary-color-high) 20%, transparent))
|
||
drop-shadow(0 1px 0 rgba(255, 255, 255, 0.9));
|
||
transition: filter .3s ease;
|
||
}
|
||
body.liquid-login #liquid-login .modal.login .liquid-logo:hover svg {
|
||
filter: drop-shadow(0 0 10px color-mix(in srgb, var(--primary-color-high) 60%, transparent))
|
||
drop-shadow(0 4px 20px color-mix(in srgb, var(--primary-color-high) 50%, transparent))
|
||
drop-shadow(0 0 36px color-mix(in srgb, var(--primary-color-high) 25%, transparent))
|
||
drop-shadow(0 2px 0 rgba(255, 255, 255, 1));
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login h4 {
|
||
font-size: 17px;
|
||
font-weight: 700;
|
||
margin-bottom: 16px;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login .cbi-value {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 5px;
|
||
border-top: 0;
|
||
padding: 4px 0 12px;
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login .cbi-value-title {
|
||
flex: none;
|
||
max-width: none;
|
||
width: 100%;
|
||
padding-top: 0;
|
||
text-align: left;
|
||
font-size: 11.5px;
|
||
font-weight: 600;
|
||
color: var(--text-color-medium);
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login input {
|
||
padding: 11px 13px;
|
||
font-size: 14px;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login .alert-message {
|
||
text-align: left;
|
||
margin: 0 0 8px;
|
||
}
|
||
|
||
/* avoid nested glass inside the lock card */
|
||
body.liquid-login #liquid-login .modal.login .cbi-map,
|
||
body.liquid-login #liquid-login .modal.login .cbi-section,
|
||
body.liquid-login #liquid-login .modal.login .cbi-section-node {
|
||
margin: 0;
|
||
padding: 0;
|
||
background: transparent;
|
||
border: 0;
|
||
box-shadow: none;
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login button.important {
|
||
width: 100%;
|
||
margin-top: 6px;
|
||
padding: 12px;
|
||
font-size: 14px;
|
||
border-radius: 12px;
|
||
/* 实色主题底:accent-grad 是半透明渐变,在明亮卡片上
|
||
左边缘会透出深色(看起来像黑边) */
|
||
background: var(--primary-color-high);
|
||
border: 1px solid transparent;
|
||
box-shadow: 0 6px 16px var(--accent-glow), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
}
|
||
|
||
body.liquid-login #liquid-login .modal.login hr {
|
||
margin: 2px 0 12px;
|
||
border-color: var(--border-color-low);
|
||
}
|
||
|
||
body.liquid-login p.luci {
|
||
position: fixed;
|
||
bottom: 4px;
|
||
left: 0;
|
||
width: 100%;
|
||
margin: 0;
|
||
padding: 8px 22px 4px;
|
||
z-index: 2001;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-wrap: nowrap;
|
||
gap: 0 10px;
|
||
color: rgba(255, 255, 255, 0.6);
|
||
text-shadow: 0 1px 8px rgba(0, 0, 0, 0.55);
|
||
}
|
||
|
||
/* 登录页 foot 分隔符 · 更清晰(登录后的共享 .luci-sep 不动) */
|
||
body.liquid-login .luci-sep {
|
||
opacity: .7;
|
||
}
|
||
|
||
body.liquid-login p.luci a {
|
||
color: rgba(255, 255, 255, 0.88);
|
||
text-decoration: none;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.35);
|
||
transition: color .15s ease, border-bottom-color .15s ease;
|
||
}
|
||
|
||
body.liquid-login p.luci a:hover {
|
||
color: #fff;
|
||
border-bottom-color: rgba(255, 255, 255, 0.9);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
16. Modal dialogs
|
||
========================================================================== */
|
||
|
||
#modal_overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 2000;
|
||
overflow: auto;
|
||
background: rgba(8, 12, 18, 0.42);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
backdrop-filter: blur(10px);
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
transition: opacity .18s ease, visibility .18s ease;
|
||
/* flex 容器:普通 modal 顶部对齐(margin 5em auto),
|
||
应用提示(.alert-message)用 margin:auto 全屏居中。
|
||
超高 modal 由本容器 overflow:auto 滚动(不锁定、不脱离文档流) */
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: center;
|
||
}
|
||
|
||
body.modal-overlay-active {
|
||
overflow: hidden;
|
||
height: 100vh;
|
||
}
|
||
|
||
body.modal-overlay-active #modal_overlay {
|
||
left: 0;
|
||
right: 0;
|
||
opacity: 1;
|
||
visibility: visible;
|
||
}
|
||
|
||
#modal_overlay > .modal {
|
||
/* 弹出窗口宽 = 屏幕可用宽度 80%(嵌套卡片按此逐层套宽) */
|
||
width: 80%;
|
||
min-width: 270px;
|
||
max-width: 80vw;
|
||
margin: 5em auto;
|
||
min-height: 32px;
|
||
border-radius: 18px;
|
||
padding: 20px 24px;
|
||
background: var(--glass-bg-strong);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
/* ── 应用配置流程提示(正在等待/应用完成/无待应用配置等)──
|
||
背景:LuCI 的 displayStatus(type) 会把 type 字符串(如 'notice
|
||
spinning')作为 class 直接加到 modal 元素上,而主题的 .notice /
|
||
.spinning 是为按钮/文档流元素设计的(margin/padding/border、
|
||
inline-block/relative/padding-left),作用到 modal 自身会把布局
|
||
挤偏(spinning 的 position:relative 在 flex 中 margin:auto 垂直
|
||
居中行为异常,是安卓上"正在等待"偏右下的根因)。
|
||
修复:modal 上下文完整重置这些污染,恢复纯 static + flex 居中。 */
|
||
#modal_overlay > .modal.alert-message,
|
||
#modal_overlay > .modal.alert-message.notice,
|
||
#modal_overlay > .modal.alert-message.spinning {
|
||
/* 重置 .notice 污染(文档流内联提示样式) */
|
||
margin: auto !important;
|
||
padding: 20px 28px !important;
|
||
border-radius: 18px !important;
|
||
font-size: 13px !important;
|
||
background: var(--glass-bg-strong) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
/* 重置 .spinning 污染(按钮的 inline-block/relative/padding-left)。
|
||
注意 position 不用 !important —— JS 强制居中器(visualViewport)
|
||
需用 inline style 覆盖为 fixed,!important 会压死它 */
|
||
position: static;
|
||
display: block !important;
|
||
text-align: center !important;
|
||
width: min(420px, 90vw) !important;
|
||
max-width: 90vw !important;
|
||
max-height: 80vh !important;
|
||
overflow: auto !important;
|
||
z-index: 2100 !important;
|
||
}
|
||
|
||
#modal_overlay > .modal.alert-message h4:empty {
|
||
display: none;
|
||
}
|
||
|
||
/* spinner 图标作为行内元素与文字同行,参与整体居中 */
|
||
#modal_overlay > .modal.alert-message.spinning::before {
|
||
position: static;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
margin: 0 8px 0 0;
|
||
}
|
||
|
||
/* 页面顶部成功通知的独立遮罩(portalTopNotices 提升进来的,如"密码已更改"):
|
||
不复用 ui.js 的 #modal_overlay(里面常驻预创建空 .modal 底座,
|
||
混用会把空壳一起显示成两个框),主题自建居中层,点击/6s 自动消失 */
|
||
#liquid-notice-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
z-index: 2100;
|
||
display: none;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: auto;
|
||
background: rgba(8, 12, 18, 0.42);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
#liquid-notice-overlay.show {
|
||
display: flex;
|
||
}
|
||
#liquid-notice-overlay .alert-message {
|
||
/* 与 #modal_overlay > .modal.alert-message(如"配置更改已应用。")
|
||
完全一致的标准弹窗设计,避免到处不同弹框造成体验割裂 */
|
||
display: block !important;
|
||
width: min(420px, 90vw);
|
||
max-width: 90vw;
|
||
margin: 0;
|
||
padding: 20px 28px;
|
||
border-radius: 18px;
|
||
text-align: center;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
/* bootstrap 会给 .info 强制 on-primary 白字 + 蓝渐变,压回主题玻璃 */
|
||
color: var(--text-color-high);
|
||
background: var(--glass-bg-strong);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
animation: liquid-pop-in .25s ease;
|
||
}
|
||
#liquid-notice-overlay .alert-message p {
|
||
margin: 0;
|
||
}
|
||
/* 内置"关闭"按钮居中(按钮的 margin-left:auto 是内联样式,需要 !important 压) */
|
||
#liquid-notice-overlay .alert-message > div:last-child {
|
||
justify-content: center;
|
||
}
|
||
#liquid-notice-overlay .alert-message .btn {
|
||
margin: 10px 0 0 !important;
|
||
}
|
||
|
||
#modal_overlay > .modal > * {
|
||
line-height: normal;
|
||
margin-bottom: .5em;
|
||
max-width: 100%;
|
||
}
|
||
|
||
#modal_overlay > .modal h4 {
|
||
font-size: 15px;
|
||
color: var(--text-color-highest);
|
||
}
|
||
|
||
#modal_overlay > .modal .button-row {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
#modal_overlay > .modal .button-row > button:first-of-type {
|
||
margin-right: auto;
|
||
}
|
||
|
||
/* 按钮/操作元素并排时留空隙(表格操作列、内联按钮组、section 创建区等) */
|
||
.cbi-button + .cbi-button,
|
||
.cbi-button + button,
|
||
button + .cbi-button,
|
||
button + button,
|
||
.btn + .btn,
|
||
.btn + button,
|
||
button + .btn,
|
||
.td.cbi-section-actions > * + *,
|
||
.cbi-section-create > * + * {
|
||
margin-left: 8px;
|
||
}
|
||
|
||
/* 已是 flex gap 的容器(页面操作区、modal 按钮行)自带间距,
|
||
去掉叠加的 margin,避免间距翻倍 */
|
||
.cbi-page-actions > * + *,
|
||
#modal_overlay > .modal .button-row > * + * {
|
||
margin-left: 0;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
17. Labels, notices, alerts
|
||
========================================================================== */
|
||
|
||
.label {
|
||
display: inline-block;
|
||
padding: 3px 10px;
|
||
margin: 0 2px;
|
||
border-radius: 999px;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
letter-spacing: .4px;
|
||
text-transform: uppercase;
|
||
white-space: nowrap;
|
||
color: var(--text-color-medium);
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
}
|
||
|
||
.label.important,
|
||
.label.notice {
|
||
color: var(--on-primary-color);
|
||
background: var(--accent-grad);
|
||
border-color: transparent;
|
||
}
|
||
|
||
.label.success {
|
||
color: var(--on-success-color);
|
||
background: linear-gradient(135deg, #3dbf88, var(--success-color-high));
|
||
border-color: transparent;
|
||
}
|
||
|
||
.label.warning {
|
||
color: var(--on-warn-color);
|
||
background: linear-gradient(135deg, #f0c04a, var(--warn-color-high));
|
||
border-color: transparent;
|
||
}
|
||
|
||
.label-danger,
|
||
.label.error {
|
||
color: var(--on-error-color);
|
||
background: linear-gradient(135deg, #f06767, var(--error-color-high));
|
||
border-color: transparent;
|
||
}
|
||
|
||
.alert-message {
|
||
margin: 0 0 16px;
|
||
padding: 12px 16px;
|
||
border-radius: 12px;
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(10px);
|
||
backdrop-filter: blur(10px);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft);
|
||
}
|
||
|
||
.alert-message h4 {
|
||
font-size: 14px;
|
||
margin-bottom: 3px;
|
||
}
|
||
|
||
.alert-message p {
|
||
margin: .25em 0;
|
||
}
|
||
|
||
.alert-message.warning {
|
||
background: color-mix(in srgb, var(--warn-color-high) 22%, var(--glass-bg));
|
||
border-color: color-mix(in srgb, var(--warn-color-high) 45%, transparent);
|
||
}
|
||
|
||
.alert-message.error {
|
||
background: color-mix(in srgb, var(--error-color-high) 20%, var(--glass-bg));
|
||
border-color: color-mix(in srgb, var(--error-color-high) 45%, transparent);
|
||
}
|
||
|
||
.alert-message.success {
|
||
background: color-mix(in srgb, var(--success-color-high) 20%, var(--glass-bg));
|
||
border-color: color-mix(in srgb, var(--success-color-high) 45%, transparent);
|
||
}
|
||
|
||
.notice {
|
||
margin: 8px 0;
|
||
padding: 8px 12px;
|
||
border-radius: 10px;
|
||
font-size: 12px;
|
||
background: var(--field-bg);
|
||
border: 1px solid var(--border-color-low);
|
||
}
|
||
|
||
/* ==========================================================================
|
||
18. Misc LuCI components
|
||
========================================================================== */
|
||
|
||
/* loading spinner */
|
||
.spinning {
|
||
display: inline-block;
|
||
position: relative;
|
||
padding-left: 28px;
|
||
}
|
||
|
||
.spinning::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 50%;
|
||
width: 20px;
|
||
height: 20px;
|
||
margin-top: -10px;
|
||
color: var(--text-color-medium);
|
||
background: url(svg/spinner.svg) center / contain no-repeat;
|
||
animation: liquid-spin .9s linear infinite;
|
||
}
|
||
|
||
/* interface boxes (luci-mod-network) */
|
||
.ifacebox {
|
||
display: inline-block;
|
||
position: relative;
|
||
min-width: 0;
|
||
margin: 0 0 16px;
|
||
padding: 14px 18px;
|
||
border-radius: var(--glass-radius);
|
||
background: var(--glass-bg);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur));
|
||
backdrop-filter: blur(var(--glass-blur));
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset);
|
||
}
|
||
|
||
/* 未加入任何防火墙区域的接口:luci 渲染的 head 色条内联为浅灰/白色
|
||
(#eeeeee / #ffffff),且不同 luci 版本输出不同:
|
||
- '--zone-color-rgb:238, 238, 238'(冒号后有无空格两种)
|
||
- 或直接 'background-color: rgb(238, 238, 238)'
|
||
统一覆盖变量与直接色值,按模式改为中性灰:
|
||
亮色浅灰(黑字可读),暗色近黑(白字清晰) */
|
||
.ifacebox-head[style*="--zone-color-rgb:238, 238, 238"],
|
||
.ifacebox-head[style*="--zone-color-rgb: 238, 238, 238"],
|
||
.ifacebox-head[style*="--zone-color-rgb:255, 255, 255"],
|
||
.ifacebox-head[style*="--zone-color-rgb: 255, 255, 255"] {
|
||
--zone-color-rgb: 125, 125, 125;
|
||
background-color: rgb(125, 125, 125) !important;
|
||
}
|
||
|
||
:root[data-darkmode="true"] .ifacebox-head[style*="--zone-color-rgb:238, 238, 238"],
|
||
:root[data-darkmode="true"] .ifacebox-head[style*="--zone-color-rgb: 238, 238, 238"],
|
||
:root[data-darkmode="true"] .ifacebox-head[style*="--zone-color-rgb:255, 255, 255"],
|
||
:root[data-darkmode="true"] .ifacebox-head[style*="--zone-color-rgb: 255, 255, 255"] {
|
||
/* 暗色下:比黑色浅一点点(近黑),白色接口名清晰可读 */
|
||
--zone-color-rgb: 24, 26, 30;
|
||
background-color: rgb(24, 26, 30) !important;
|
||
}
|
||
|
||
.ifacebox-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
font-weight: 700;
|
||
font-size: 14px;
|
||
white-space: nowrap;
|
||
color: var(--text-color-highest);
|
||
/* 顶部防火墙分区色条:扩展到卡片边缘(抵消 ifacebox 的
|
||
14px/18px padding),整行显示分区底色,与下方信息区分割 */
|
||
margin: -14px -18px 14px;
|
||
padding: 7px 18px;
|
||
border-radius: calc(var(--glass-radius) - 2px) calc(var(--glass-radius) - 2px) 0 0;
|
||
border-bottom: 1px solid var(--glass-border);
|
||
}
|
||
|
||
.ifacebox-body {
|
||
white-space: nowrap;
|
||
text-align: center;
|
||
}
|
||
|
||
.ifacebox-body > img,
|
||
.ifacebox-body > .cbi-tooltip-container,
|
||
.ifacebox-body > span {
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 接口列表操作列(重启/停止/编辑/删除):不压缩、不换行;
|
||
按钮靠左(贴近接口信息,不往右让开太多) */
|
||
.cbi-section-table td.cbi-section-actions {
|
||
width: 1%;
|
||
white-space: nowrap;
|
||
text-align: left;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* GridSection(接口页网格卡片)的操作区同样不换行 */
|
||
.cbi-section-actions {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
|
||
/* 端口状态页:接口卡片宽度自适应内容、整体居中、间距 */
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"] {
|
||
display: flex !important;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
align-items: flex-start;
|
||
gap: 14px;
|
||
}
|
||
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"] .ifacebox,
|
||
.network-status-table .ifacebox {
|
||
width: auto !important;
|
||
max-width: none !important;
|
||
min-width: 70px;
|
||
margin: 0 !important;
|
||
}
|
||
|
||
/* GridSection 行内单元格强制等高:接口卡/详情/操作按钮底边对齐,
|
||
单元格分割线在同一高度(内容高度不同时不再错位) */
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"] > * {
|
||
align-self: stretch !important;
|
||
height: auto !important;
|
||
}
|
||
|
||
/* 接口页行:接口小块靠左(限定左侧),详情与按钮依次排开 */
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"]:has(.cbi-section-actions) {
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
/* 网络-接口页的接口卡片:宽度按内容(接口组 bridge 的多个图标)拓展,
|
||
防止图标溢出卡片;max-width 不限制,让列随内容拉伸 */
|
||
.cbi-section-table td[data-name="_ifacebox"] {
|
||
width: fit-content;
|
||
/* 卡片左右对称留空:多图标扩宽后右侧不贴相邻列 */
|
||
padding-left: 14px;
|
||
padding-right: 14px;
|
||
}
|
||
|
||
.cbi-section-table .ifacebox {
|
||
width: fit-content;
|
||
min-width: 0;
|
||
max-width: none;
|
||
}
|
||
|
||
/* 端口状态卡片:接口名/图标/连接状态水平居中 */
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"] .ifacebox {
|
||
text-align: center;
|
||
}
|
||
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"] .ifacebox-head {
|
||
justify-content: center;
|
||
}
|
||
|
||
/* 网络卡片(30_network):内容左对齐、上游标题居中、接口名行居中 */
|
||
.network-status-table .ifacebox {
|
||
text-align: left !important;
|
||
}
|
||
|
||
.network-status-table .ifacebox-body.left,
|
||
.network-status-table .ifacebox-body .nowrap {
|
||
text-align: left !important;
|
||
}
|
||
|
||
.network-status-table .ifacebox-head.center {
|
||
justify-content: center;
|
||
text-align: center;
|
||
}
|
||
|
||
.network-status-table .ifacebox > div:last-child {
|
||
text-align: center;
|
||
}
|
||
|
||
/* 网络卡片(30_network)整体居中、子卡片留空隙 */
|
||
.network-status-table {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
align-items: flex-start;
|
||
gap: 14px;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
/* 统计 tooltip:默认隐藏,悬停 📊/统计区域时在下方悬浮显示(DHCPv6/端口统计) */
|
||
.cbi-tooltip {
|
||
z-index: 9999 !important;
|
||
/* absolute 脱离文档流:不占位(否则设备页图标右侧会留白),
|
||
悬停时像概况页一样悬浮显示 */
|
||
position: absolute;
|
||
left: -10000px;
|
||
opacity: 0;
|
||
visibility: hidden;
|
||
/* 磨砂玻璃底(与概况页悬浮框一致,否则透明看不见文字) */
|
||
background: var(--glass-bg-strong);
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
border: 1px solid var(--glass-border);
|
||
border-radius: 10px;
|
||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2);
|
||
padding: 8px 12px;
|
||
white-space: normal;
|
||
line-height: 1.55;
|
||
color: var(--text-color-high);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.cbi-tooltip-container {
|
||
position: relative;
|
||
}
|
||
|
||
/* 悬浮内容框内不再重复显示接口图标(行内主图标已表示连接状态);
|
||
只针对 render_iface 的 ifacebadge.large 内重复图标,避免误伤
|
||
设备页等把主图标放在 tooltip 结构里的场景 */
|
||
.cbi-tooltip.ifacebadge.large > img {
|
||
display: none;
|
||
}
|
||
|
||
/* hover 时提升卡片本身的层叠顺序:
|
||
.ifacebox 的 backdrop-filter 会创建 stacking context,把 tooltip 的
|
||
z-index:9999 锁在卡片内部,卡片之间的遮挡由 DOM 顺序决定——后一张
|
||
卡片会盖住前一张卡片的 tooltip。hover 提升卡片(z-index:1001)即可
|
||
让 tooltip 随卡片整体盖过相邻卡片(tooltip 的 9999 在卡片内部仍生效)。 */
|
||
.ifacebox:hover {
|
||
z-index: 1001;
|
||
}
|
||
|
||
/* 悬浮内容框(tooltip)hover 时,提升其所在卡片本身的层叠顺序。
|
||
卡片的 backdrop-filter 会创建 stacking context 把 tooltip 的
|
||
z-index 锁在卡片内部,卡片之间的遮挡由 DOM 顺序决定,因此必须
|
||
整体提升卡片(而非只调 tooltip 的 z-index)。:has 覆盖 tooltip
|
||
可能挂载的各种卡片容器。 */
|
||
.ifacebox:has(.cbi-tooltip-container:hover),
|
||
.zonebadge:has(.cbi-tooltip-container:hover),
|
||
.cbi-section-node:has(.cbi-tooltip-container:hover),
|
||
.cbi-map:has(.cbi-tooltip-container:hover),
|
||
.network-status-table:has(.cbi-tooltip-container:hover) {
|
||
z-index: 99999 !important;
|
||
}
|
||
|
||
.ifacebox .cbi-tooltip-container {
|
||
position: relative;
|
||
}
|
||
|
||
.ifacebox .cbi-tooltip {
|
||
position: absolute;
|
||
left: -10000px;
|
||
top: 100%;
|
||
opacity: 0;
|
||
display: block;
|
||
min-width: 220px;
|
||
margin: 4px 0 0;
|
||
padding: 8px 10px;
|
||
border-radius: 10px;
|
||
background: var(--glass-bg-strong);
|
||
-webkit-backdrop-filter: blur(12px);
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid var(--glass-border);
|
||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
|
||
z-index: 9999;
|
||
white-space: normal;
|
||
line-height: 1.55;
|
||
pointer-events: none;
|
||
transition: opacity .15s ease;
|
||
}
|
||
|
||
/* 悬浮内容框被 portal 到 body 顶层后,原 .ifacebox .cbi-tooltip 的
|
||
背景规则因元素移出卡片而失效(会变成透明、文字看不清),
|
||
这里补上磨砂玻璃底色 */
|
||
.liquid-tip-ported {
|
||
background: var(--glass-bg-strong) !important;
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) !important;
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat)) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
border-radius: 10px !important;
|
||
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.2) !important;
|
||
padding: 8px 12px !important;
|
||
white-space: normal !important;
|
||
line-height: 1.55 !important;
|
||
color: var(--text-color-high) !important;
|
||
}
|
||
|
||
/* 接口总览表格:列宽自适应 */
|
||
.cbi-section-table {
|
||
table-layout: auto;
|
||
}
|
||
|
||
.cbi-section-table td,
|
||
.cbi-section-table .td {
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* 网络 tooltip 接口徽章:横杠/圆点/接口名/图标同一行垂直居中 */
|
||
.ifacebadge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
.ifacebadge .zonebadge {
|
||
flex-shrink: 0;
|
||
padding: 2px 6px;
|
||
line-height: 1;
|
||
}
|
||
|
||
/* 接口图标全局统一 24px:不同 svg viewBox / 不同容器会导致宽度不一,
|
||
统一强制宽度 24px(!important 防内联样式覆盖),占位紧凑 */
|
||
img[src*="icons/"][src$=".svg"] {
|
||
width: 24px !important;
|
||
height: auto !important;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* interfaces.js 里 ifacebox-body 的设备图内联 16px,覆盖为 24px */
|
||
.ifacebox-body > img {
|
||
width: 24px !important;
|
||
height: auto !important;
|
||
}
|
||
|
||
.ifacebadge img {
|
||
width: 24px;
|
||
height: auto;
|
||
vertical-align: middle;
|
||
}
|
||
|
||
/* luci 渲染会给接口徽章(ifacebadge)的图标/文字加内联
|
||
style="opacity:0.5"(离线样式),即使接口已连接也会残留,
|
||
造成半透明、被误判为未连接;统一压回不透明 */
|
||
.ifacebadge img,
|
||
.ifacebadge span {
|
||
opacity: 1 !important;
|
||
}
|
||
|
||
.zonebadge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
background: var(--glass-bg);
|
||
border: 1px solid var(--glass-border);
|
||
/* 与上方内容(概况页的 ifacebox-body 等)留出空隙 */
|
||
margin-top: 8px;
|
||
}
|
||
|
||
/* 区域名与成员接口横向排开;成员接口徽章之间用细竖线分隔,
|
||
结构更清晰(区域名 | 接口1 | 接口2) */
|
||
.zonebadge > strong {
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.zonebadge .ifacebadge + .ifacebadge {
|
||
border-left: 1px solid currentColor;
|
||
padding-left: 8px;
|
||
}
|
||
|
||
/* 胶囊内接口图标:全局 24px 偏大,缩到 15px 与紧凑胶囊匹配 */
|
||
.zonebadge img[src$=".svg"] {
|
||
width: 15px !important;
|
||
height: 15px !important;
|
||
}
|
||
|
||
/* 防火墙区域标签(zonebadge)文字:LuCI 内置的淡 zone 色背景在暗色
|
||
下不变,白字会看不清——仅对带 zone 色背景的(内联 --zone-color-rgb)
|
||
在暗色下改用深色字;无 zone 色的 empty 标签走玻璃底+继承浅字 */
|
||
:root[data-darkmode="true"] .zonebadge[style*="--zone-color-rgb"] {
|
||
color: #1d2633;
|
||
}
|
||
|
||
/* zonebadge-empty(如 ACCEPT / ACCEPT all others):无 zone 色、玻璃底,
|
||
亮色下默认玻璃边框太淡看不见——用中灰边框;暗色文字继承浅色清晰 */
|
||
.zonebadge-empty {
|
||
border-color: var(--border-color-medium);
|
||
}
|
||
|
||
/* code / pre blocks */
|
||
pre {
|
||
padding: 12px 14px;
|
||
border-radius: 12px;
|
||
background: var(--field-bg);
|
||
border: 1px solid var(--border-color-low);
|
||
overflow: auto;
|
||
}
|
||
|
||
/* horizontal form rows helpers */
|
||
.right {
|
||
float: right;
|
||
}
|
||
|
||
.left {
|
||
float: left;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
19. Animations
|
||
========================================================================== */
|
||
|
||
@keyframes liquid-spin {
|
||
to { transform: rotate(360deg); }
|
||
}
|
||
|
||
@keyframes liquid-fade-in {
|
||
from { opacity: 0; transform: translateY(4px); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
|
||
@keyframes liquid-jelly {
|
||
0% { opacity: 0; transform: translateY(-10px) scaleY(.88); }
|
||
55% { opacity: 1; transform: translateY(3px) scaleY(1.03); }
|
||
75% { opacity: 1; transform: translateY(-2px) scaleY(.98); }
|
||
100% { opacity: 1; transform: none; }
|
||
}
|
||
|
||
@keyframes liquid-slide-in {
|
||
from { opacity: 0; transform: translateX(-36px); }
|
||
to { opacity: 1; transform: none; }
|
||
}
|
||
|
||
/* ==========================================================================
|
||
20. Responsive
|
||
========================================================================== */
|
||
|
||
@media screen and (max-width: 854px) {
|
||
|
||
/* 移动端:弹出窗口(modal)宽 95%(80% 会把内容挤太小);
|
||
左右内边距收到 2px,让第一层卡片也按 2px 缩进(与嵌套每层一致) */
|
||
#modal_overlay > .modal {
|
||
width: 95%;
|
||
max-width: 95vw;
|
||
padding: 16px 2px 20px;
|
||
}
|
||
|
||
/* 移动端:多层嵌套卡片(network/dns/主机名记录等会套 2-4 层)压缩
|
||
各层左右缩进到 6px,避免嵌套层数多了最内层内容被挤出视口;
|
||
嵌套的每层再往内缩 2px(margin),层级越深内容越窄。
|
||
注意左右 padding 需 ≥ 6px:输入框/下拉聚焦高亮 ring 外扩 3px,
|
||
2px 时高亮会被卡片边缘裁断(微超被切),6px 才留足余量 */
|
||
.cbi-map {
|
||
padding: 10px 2px 12px;
|
||
}
|
||
|
||
.cbi-map .cbi-map,
|
||
.cbi-map .cbi-section,
|
||
.cbi-section .cbi-map,
|
||
.cbi-section .cbi-section {
|
||
margin-left: 2px;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.cbi-section {
|
||
padding: 10px 6px 12px;
|
||
}
|
||
|
||
.cbi-section-node {
|
||
/* 左右 4px 余量:滚动容器 overflow-x:auto 会在可视右缘横向裁剪
|
||
输入框聚焦 ring(3px),留 4px 保证 ring 不被切;嵌套仍紧凑 */
|
||
padding: 2px 4px;
|
||
}
|
||
|
||
/* 登录页表单容器:上面的 overflow-x:auto 同样会横裁登录输入框的
|
||
聚焦 ring;登录表单不超宽、无需横向滚动,改 visible 即可 */
|
||
body.liquid-login .cbi-section-node {
|
||
overflow-x: visible;
|
||
}
|
||
|
||
.cbi-section-node > .cbi-section {
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* 移动端:接口卡片列限宽(fit-content 拉伸会把相邻列挤爆,
|
||
拓宽后的卡片与文字重叠),子接口图标行允许换行避免溢出 */
|
||
.cbi-section-table td[data-name="_ifacebox"] {
|
||
width: auto;
|
||
max-width: 42%;
|
||
padding-left: 4px;
|
||
padding-right: 4px;
|
||
}
|
||
|
||
.cbi-section-table .ifacebox {
|
||
width: auto;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.ifacebox-body > span {
|
||
white-space: normal;
|
||
}
|
||
|
||
/* 移动端:接口图标强制统一 24px,防止被列宽挤压变形 */
|
||
.cbi-section-table .ifacebox img {
|
||
width: 24px !important;
|
||
height: auto !important;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 移动端:接口图标(主图标+子接口图标)换行居中,多图标整齐排列 */
|
||
.ifacebox-body {
|
||
white-space: normal;
|
||
}
|
||
|
||
.ifacebox-body > .cbi-tooltip-container,
|
||
.ifacebox-body > span,
|
||
.ifacebox-body > span .cbi-tooltip-container {
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
margin: 2px;
|
||
}
|
||
|
||
/* 移动端:接口表格按内容宽度排布(列不压缩,按钮紧贴信息:
|
||
不重叠、也不贴最右留空);超出视口时外层横向滚动 */
|
||
.cbi-section-table {
|
||
width: max-content;
|
||
min-width: 100%;
|
||
}
|
||
|
||
/* 移动端所有表格按内容宽度排布(列不压缩,MAC/MTU 等不重叠),
|
||
超出视口时允许横向滚动。
|
||
注意:横向滚动只放在表格的直接容器(.cbi-section-node 包裹式 /
|
||
.cbi-tblsection 直挂式,form.js 的两种渲染路径),不再放在
|
||
#maincontent——#maincontent 一旦带 overflow-x:auto 就成了嵌套滚动
|
||
容器,安卓 Chrome 对其上的纵向手势路由比 iOS 严格,会拖慢/拦掉
|
||
文档级滚动从而挡住地址栏收缩;宽表格在自己的容器内横滚即可 */
|
||
.cbi-section-node,
|
||
.cbi-tblsection {
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.cbi-section-table {
|
||
width: max-content !important;
|
||
min-width: 100%;
|
||
}
|
||
|
||
.cbi-section-table td.cbi-section-actions {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 接口页 GridSection(网格卡片,带操作按钮的行):按内容宽度排布,
|
||
重启/停止/编辑/删除按钮不挤压重叠文字,超出视口横向滚动 */
|
||
div[style*="grid-template-columns:repeat(auto-fit, minmax(70px, 1fr))"]:has(.cbi-section-actions) {
|
||
width: max-content;
|
||
min-width: 100%;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* 接口小框(接口名 + 设备/IP 信息)与其所在行卡片左缘间隔 4px:
|
||
把 td[data-name="_ifacebox"] 的左右 padding 从 14px 收到 4px
|
||
(小框往左移;对称留空保持,右侧也不贴相邻列) */
|
||
.cbi-section-table td[data-name="_ifacebox"] {
|
||
padding-left: 4px;
|
||
padding-right: 4px;
|
||
}
|
||
|
||
/* 移动端接口图标:宽度固定 24px,高度等比缩放(不强制高度) */
|
||
img[src*="icons/"][src$=".svg"] {
|
||
width: 24px !important;
|
||
height: auto !important;
|
||
}
|
||
|
||
/* 移动端接口页表格(含接口图标列的 GridSection):锁定视口宽,
|
||
覆盖上面的 max-content(行不再按内容撑宽、按钮不再溢出到屏幕
|
||
右侧),所有内容在视口内换行显示 */
|
||
.cbi-section-table:has(td[data-name="_ifacebox"]) {
|
||
width: 100% !important;
|
||
min-width: 100% !important;
|
||
max-width: 100% !important;
|
||
}
|
||
|
||
/* 移动端接口页:每接口两行 —— 第一行 = 接口图标 + 详细信息,
|
||
第二行 = 重启/停止/编辑/删除四个操作按钮(整行,右对齐) */
|
||
.cbi-section-table-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
/* 图标列与详细信息列等高对齐;二者合在一起超宽时才允许分行。
|
||
横向 gap 归零:各 td 本来自带的分割线(border-bottom)就能
|
||
首尾相连成一条连续线;垂直 gap 保留作换行/与按钮行的间距 */
|
||
align-items: stretch;
|
||
gap: 6px 0;
|
||
}
|
||
|
||
.cbi-section-table-row > td {
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
/* 移动端接口页:隐藏行分割线(线与列对齐难兼顾,先试无分割线) */
|
||
border-bottom: 0 !important;
|
||
}
|
||
|
||
.cbi-section-table-row > td.cbi-section-actions {
|
||
flex: 1 1 100%;
|
||
order: 99;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 2px 0 4px;
|
||
/* 四个按钮下方多 8px:让相邻两个接口之间稍微区分 */
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
#menubar .navigation {
|
||
display: flex;
|
||
}
|
||
|
||
#maincontainer {
|
||
flex-direction: column;
|
||
height: calc(100vh - 64px);
|
||
}
|
||
|
||
#mainmenu {
|
||
position: fixed;
|
||
left: 0;
|
||
top: 64px;
|
||
bottom: 0;
|
||
width: 264px;
|
||
transform: translateX(-102%);
|
||
animation: none;
|
||
z-index: 800;
|
||
overflow-y: auto;
|
||
overscroll-behavior: contain;
|
||
box-shadow: none;
|
||
}
|
||
|
||
#mainmenu.active {
|
||
transform: translateX(0);
|
||
box-shadow: 0 14px 50px rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
#maincontent {
|
||
/* 外层卡片到屏幕边缘的留白:16px → 4px(用户反馈移动端概览页
|
||
padding 偏大,大幅压缩)。内层卡片自身仍有 2~6px padding,
|
||
文字距边缘合计约 14px,输入框聚焦 ring(3px) 也不至于贴边 */
|
||
padding: 0 4px 64px;
|
||
margin-left: 0;
|
||
/* r19 起移动端页面纵向滚动由文档承担(见下方「文档级滚动」块,
|
||
为了让安卓浏览器地址栏随滚动收缩)。此处 flex-shrink:0 保证
|
||
#maincontent 不被压缩成滚动容器;overflow-x:auto 只负责宽表格
|
||
横向滚动。下拉打开时 :has(.cbi-dropdown[open]) 把 overflow 翻回
|
||
visible 不再影响文档滚动位置(滚动位置在文档上,与此处无关) */
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* ===== 文档级滚动:让安卓 Chrome/Edge 地址栏随滚动收缩(r19)=====
|
||
安卓只在「文档级滚动」时才收缩工具栏/地址栏。此前纵向滚动锁在
|
||
#maincontainer(body{overflow:hidden} + #maincontainer{overflow-y:auto})
|
||
这个嵌套滚动容器里,浏览器检测不到文档滚动,地址栏固定占高。
|
||
现改为文档自然滚动:body 放开 overflow、#maincontainer 去掉固定高度
|
||
与内层滚动、#menubar 靠 sticky 吸顶。仅登录后长页面生效,登录页
|
||
(body.liquid-login)保持原固定布局。宽表格横向滚动仍由
|
||
#maincontent{overflow-x:auto} 承担,不会外溢成文档横向滚动。 */
|
||
html {
|
||
overflow: visible;
|
||
height: auto;
|
||
}
|
||
|
||
body:not(.liquid-login) {
|
||
overflow: visible;
|
||
height: auto;
|
||
/* 用 svh(小视口=工具栏全展开的高度)而非 dvh:dvh 会随工具栏
|
||
收缩动画实时变化,导致 body 高度回流、与收缩手势互相干扰
|
||
(症状:须先回顶才肯收缩)。svh 恒定,背景经 canvas 铺满全屏 */
|
||
min-height: 100vh;
|
||
min-height: 100svh;
|
||
/* 安卓 Chrome/Edge 会禁用 background-attachment:fixed 页面的
|
||
地址栏收缩(fixed 背景需整幅重绘,浏览器直接关掉收缩动画)。
|
||
移动端改 scroll:渐变沿整个文档铺开,视觉无断层 */
|
||
background-attachment: scroll;
|
||
}
|
||
|
||
body:not(.liquid-login) #maincontainer {
|
||
height: auto;
|
||
min-height: 0;
|
||
overflow: visible;
|
||
}
|
||
|
||
/* ===== 移动端 chrome 条去模糊(r23)=====
|
||
安卓 Chrome/Edge 的地址栏收缩动画期间要重绘 sticky/fixed 元素背后
|
||
区域,backdrop-filter 元素需重采样、与收缩动画冲突——症状:
|
||
首次下滑不收缩,往上回一点点再下滑才行;iOS Safari 滤镜层无此问题。
|
||
吸顶菜单栏/面包屑、fixed 底部 foot、菜单抽屉全部去掉 blur,改等价
|
||
不透明实色(与 meta theme-color 同色,浏览器工具栏依旧无缝衔接)。
|
||
内容区卡片的 backdrop-filter 保留(在文档流内,不参与该冲突) */
|
||
#menubar,
|
||
#modemenu,
|
||
#mainmenu,
|
||
body:not(.liquid-login) p.luci {
|
||
-webkit-backdrop-filter: none;
|
||
backdrop-filter: none;
|
||
}
|
||
|
||
#menubar,
|
||
#modemenu,
|
||
body:not(.liquid-login) p.luci {
|
||
background: var(--glass-chrome-solid);
|
||
}
|
||
|
||
#mainmenu {
|
||
background: var(--glass-menu-solid);
|
||
}
|
||
|
||
body:not(.liquid-login) p.luci {
|
||
left: 0;
|
||
}
|
||
|
||
/* 移动端登录页 foot:窄屏允许换行(桌面一行) */
|
||
body.liquid-login p.luci {
|
||
flex-wrap: wrap;
|
||
gap: 2px 10px;
|
||
bottom: 2px;
|
||
}
|
||
|
||
/* 移动端 foot 横幅只保留「主题版本 + 固件版本」(版本号需可点检查更新),
|
||
其余(kernel/luci/多余分隔符/检查更新卡片容器)仍隐藏。
|
||
注意 overlay 也是 p.luci 的直接子元素——不排除会被这条规则
|
||
display:none 掉,移动端整个检查更新卡片直接消失 */
|
||
body:not(.liquid-login) p.luci > *:not(.luci-fw):not(.luci-theme-ver):not(.luci-sep-fw):not(.liquid-ver-overlay) {
|
||
display: none;
|
||
}
|
||
|
||
body:not(.liquid-login) p.luci {
|
||
/* 移动端 foot 加高 4px(上下 padding 各 +2px),与浏览器底部
|
||
工具栏衔接更饱满 */
|
||
padding: 8px 22px;
|
||
/* 字号调小一号 */
|
||
font-size: 10.5px;
|
||
/* 主题版本与固件版本间隔:较桌面 14px 大幅收紧 */
|
||
gap: 8px 4px;
|
||
/* 主题版本+固件版本一行放不下时:不换行、不压缩,
|
||
从左侧起排,右侧溢出屏幕的部分直接裁掉不显示 */
|
||
flex-wrap: nowrap;
|
||
justify-content: flex-start;
|
||
/* 关键:overflow:hidden 会把"向上滑出页脚"的检查更新卡片
|
||
整个裁掉(移动端点击无弹出的根因)。
|
||
overflow-x: clip 只裁横向溢出,且 clip/visible 配对不会
|
||
把 overflow-y 降级为 auto —— 横向照裁、纵向放行卡片 */
|
||
overflow-x: clip;
|
||
overflow-y: visible;
|
||
}
|
||
body:not(.liquid-login) p.luci > * {
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 更新卡片按钮:窄屏一排 6 个太挤 → 3+3 两行网格。
|
||
grid 自动布局同样遵循 flex 的 order(order-modified document order),
|
||
用 order 重排到目标顺序、列数固定 3,行划分确定不依赖宽度;
|
||
DOM 顺序不变,桌面端(media 外)仍是原来的一行。
|
||
第一行:一键更新 / 拉取新包 / 清理包
|
||
第二行:下载链接 / 作者仓库 / 忽略 */
|
||
.liquid-ota-btn-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, auto);
|
||
justify-content: center;
|
||
justify-items: center;
|
||
gap: 8px;
|
||
}
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(1) { order: 1; } /* 一键更新 */
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(2) { order: 2; } /* 拉取新包 */
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(4) { order: 3; } /* 清理包 */
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(3) { order: 5; } /* 下载链接 */
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(5) { order: 6; } /* 作者仓库 */
|
||
.liquid-ota-btn-row .liquid-ota-btn:nth-child(6) { order: 7; } /* 忽略 */
|
||
|
||
/* 更新卡片占屏宽 85%,按钮增大(触屏点按更友好) */
|
||
.liquid-ver-toast {
|
||
width: 85vw;
|
||
}
|
||
.liquid-ota-btn {
|
||
padding: 8px 12px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.luci-foot-spacer {
|
||
height: 100px;
|
||
}
|
||
|
||
#menubar .distversion {
|
||
display: none;
|
||
}
|
||
|
||
/* 文档级滚动后 modemenu(面包屑)需 sticky 吸顶在菜单栏下方,
|
||
否则会被当普通内容随文档滚走(内层滚动时期它是 static,因为在
|
||
滚动容器外、天然不动;改文档滚动后必须显式吸顶) */
|
||
#modemenu {
|
||
position: sticky;
|
||
top: 56px;
|
||
}
|
||
|
||
.cbi-value {
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
gap: 5px;
|
||
}
|
||
|
||
.cbi-value-title {
|
||
flex: none;
|
||
max-width: none;
|
||
width: 100%;
|
||
padding-top: 0;
|
||
text-align: left;
|
||
}
|
||
|
||
.cbi-section-table,
|
||
.table {
|
||
display: block;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
/* 移动端:tr 转 flex。分隔线画在 tr 底部(而非各 td)——
|
||
行内 td 高度不同(标题行矮、进度条行高),td 级 border-bottom
|
||
会左右错位;tr 级 border-bottom 只有一条边,横贯整行天然一致。
|
||
(斑马纹/hover 本就画在 tr 上,故左右始终对齐) */
|
||
.cbi-section-table tr,
|
||
.table tr {
|
||
display: flex !important;
|
||
align-items: stretch;
|
||
white-space: nowrap;
|
||
border-bottom: 1px solid var(--border-color-low);
|
||
}
|
||
|
||
.cbi-section-table tr > td,
|
||
.cbi-section-table tr > th,
|
||
.cbi-section-table tr > .td,
|
||
.cbi-section-table tr > .th,
|
||
.table tr > td,
|
||
.table tr > th,
|
||
.table tr > .td,
|
||
.table tr > .th {
|
||
flex: 0 1 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
/* 基础规则用类选择器(.table .td),特异性更高,必须 !important
|
||
才能移除 td 级分隔线(分隔线统一画在 tr 底部) */
|
||
border-bottom: 0 !important;
|
||
}
|
||
|
||
.cbi-section-table tr:last-child,
|
||
.table tr:last-child {
|
||
border-bottom: 0;
|
||
}
|
||
|
||
/* 移动端:进度条行(22px 高)与文字行高度差会让分隔线节奏不均,
|
||
收紧进度条行的上下 padding,让行高与文字行一致,横线间距均匀。
|
||
同时给该行 td 显式 min-height(22px 进度条 + 4px*2 padding),
|
||
兜底 stretch 未拉高左侧标题 td 的情况 → 左右 border-bottom 对齐 */
|
||
.cbi-section-table tr:has(.cbi-progressbar) > td,
|
||
.table tr:has(.cbi-progressbar) > td {
|
||
padding-top: 4px;
|
||
padding-bottom: 4px;
|
||
min-height: 30px;
|
||
}
|
||
|
||
.cbi-section-table tr > td:last-child,
|
||
.table tr > td:last-child {
|
||
flex: 1 1 100%;
|
||
}
|
||
|
||
.cbi-section-table tr > td[width="33%"],
|
||
.table tr > td[width="33%"] {
|
||
flex: 0 0 20ch;
|
||
max-width: 20ch;
|
||
white-space: normal;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.Dashboard .section-content {
|
||
flex-direction: column;
|
||
}
|
||
|
||
.Dashboard .box-s1 {
|
||
min-height: 0;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: .01ms !important;
|
||
transition-duration: .01ms !important;
|
||
}
|
||
}
|
||
|
||
|
||
/* Bootstrap 工具类:插件常用 class="hidden" 隐藏元素(隐藏的保存按钮、
|
||
提示面板、折叠区块等)。主题只加载自己的 css,必须自行提供,
|
||
否则插件声明 hidden 的元素会全部显示出来。 */
|
||
.hidden {
|
||
display: none;
|
||
}
|
||
|
||
/* ===== 下拉浮层突破卡片裁剪(纯 CSS,不动 JS/DOM) =====
|
||
1) 含下拉的表格:overflow:hidden(圆角裁剪)→ visible,弹出列表可见;
|
||
2) 含"打开"下拉的卡片提升 z-index:突破 backdrop-filter 形成的
|
||
stacking context,避免被相邻卡片盖住;
|
||
3) 移动端滚动容器(.cbi-section-node / #maincontent):两轴 visible,
|
||
不再裁剪弹出列表。r19 起页面纵向滚动由文档承担(#maincontent 仅
|
||
overflow-x:auto 负责宽表格横滚),此翻转不影响文档滚动位置 */
|
||
/* 只在"打开"时解除裁剪(加载/闭合时保持原样,不影响胶囊尺寸) */
|
||
.cbi-section-table:has(.cbi-dropdown[open]) {
|
||
overflow: visible !important;
|
||
}
|
||
|
||
.cbi-section:has(.cbi-dropdown[open]),
|
||
.cbi-map:has(.cbi-dropdown[open]) {
|
||
position: relative;
|
||
z-index: 1000;
|
||
}
|
||
|
||
@media screen and (max-width: 854px) {
|
||
.cbi-section-node:has(.cbi-dropdown[open]),
|
||
#maincontent:has(.cbi-dropdown[open]) {
|
||
overflow-x: visible !important;
|
||
overflow-y: visible !important;
|
||
}
|
||
}
|
||
/* ==========================================================================
|
||
OpenClash 页面适配(透明玻璃卡片 + 内容居中)
|
||
status 页上下两块大卡片(.main-card,各自嵌套 .card-row > .sub-card
|
||
多层)全部主题化:大卡片透明融入页面背景,内层 sub-card / 公告卡片
|
||
用主题玻璃配方(半透明 + 模糊 + 玻璃边框 + 内高光)。明暗由
|
||
openclash 自带变量(html[data-darkmode="true"] .oc)与主题
|
||
--glass-* 变量联动,无需单独处理暗色。
|
||
========================================================================== */
|
||
|
||
/* 大卡片(两块 .main-card):去掉白底/边框/阴影,透明融入主题背景 */
|
||
body[data-page^="admin-services-openclash"] #cbi-openclash > fieldset .main-card {
|
||
background: transparent !important;
|
||
border: none !important;
|
||
box-shadow: none !important;
|
||
padding: 0 10px 10px 10px;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] #cbi-openclash > fieldset .myip-main-card {
|
||
margin: 0;
|
||
padding: 10px;
|
||
border: none !important;
|
||
background: transparent !important;
|
||
}
|
||
|
||
/* 公告卡片:玻璃 */
|
||
body[data-page^="admin-services-openclash"] .oc .announcement-card {
|
||
background: var(--glass-bg) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
border-radius: var(--glass-radius);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset) !important;
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
}
|
||
|
||
/* 内层嵌套卡片(sub-card):主题玻璃配方,替代 openclash 白/灰实底 */
|
||
body[data-page^="admin-services-openclash"] .oc .sub-card {
|
||
background: var(--glass-bg) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
border-radius: var(--glass-radius);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset) !important;
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
}
|
||
|
||
/* hover:主题玻璃高亮(主色边框 + tint 背景),去掉 openclash 位移 */
|
||
body[data-page^="admin-services-openclash"] .oc .sub-card:hover {
|
||
background: var(--glass-bg-hover) !important;
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, var(--glass-border)) !important;
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset) !important;
|
||
transform: none;
|
||
}
|
||
|
||
/* myip 页:IP 地址 / 访问检查 输出块玻璃化 */
|
||
body[data-page^="admin-services-openclash"] .oc .myip-ip-item,
|
||
body[data-page^="admin-services-openclash"] .oc .myip-check-item {
|
||
background: var(--glass-bg) !important;
|
||
border: 1px solid var(--glass-border) !important;
|
||
border-radius: var(--glass-radius);
|
||
box-shadow: var(--glass-shadow-soft), inset 0 1px 0 var(--glass-inset) !important;
|
||
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .myip-ip-item:hover,
|
||
body[data-page^="admin-services-openclash"] .oc .myip-check-item:hover {
|
||
background: var(--glass-bg-hover) !important;
|
||
border-color: color-mix(in srgb, var(--primary-color-high) 55%, var(--glass-border)) !important;
|
||
box-shadow: var(--glass-shadow), inset 0 1px 0 var(--glass-inset) !important;
|
||
transform: none;
|
||
}
|
||
|
||
/* ---- 内容居中(运行状态 / 控制面板 IP:端口 / 混合代理) ---- */
|
||
body[data-page^="admin-services-openclash"] .oc .card-content {
|
||
align-items: center !important;
|
||
justify-content: center !important;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .card-controls {
|
||
align-self: center;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .core-main-controls {
|
||
justify-content: center !important;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .plugin-toggle-container {
|
||
margin-left: inherit !important;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .core-status-toggle {
|
||
flex: 0 !important;
|
||
min-width: auto !important;
|
||
}
|
||
|
||
body[data-page^="admin-services-openclash"] .oc .card-value {
|
||
justify-content: center;
|
||
}
|
||
|
||
/* ==========================================================================
|
||
宽表格优先折叠按钮(liquid-actions-overflow)
|
||
-------------------------------------------------------------------------
|
||
触发:main.js 判定表格“自然宽度”超出所在容器 / 当前屏幕时给 <table>
|
||
加 liquid-actions-overflow 类(判定始终在去类状态下进行,避免加/去类
|
||
来回震荡;仅作用于含操作列的表格,无按钮的表格维持原样)。
|
||
|
||
效果:操作列先让步,而不是先压缩文字——
|
||
1) 放开 form.js stabilizeActionColumnWidth 写死在 td/th 上的内联
|
||
width/min-width(必须 !important 才能压过内联样式);
|
||
2) 一旦进入折叠状态,按钮**每个独占一行**(列宽收到最宽按钮的
|
||
内容宽),不做“随宽度 2+2+1 逐步折行”的渐进换行——行为确定,
|
||
文字列也能拿回最多宽度。注意 LuCI 用 dom.append 逐个追加按钮,
|
||
按钮之间没有空白文本节点,inline 元素之间不存在换行点,只改
|
||
white-space 不会换行,必须让按钮容器(td 内的 div)转 flex;
|
||
3) 文字列因此拿回可用宽度,不再被挤成窄条多行换行 / 被容器裁切;
|
||
4) 窄屏(≤854px)表格按 max-content 撑宽(=行内所有格子排成一行的
|
||
总宽),按钮换行也拉不窄——把表宽收回 100%,行内换行才生效,
|
||
同时去掉横向滚动;此时操作列已独占整行、按钮放得下,维持一行
|
||
横排(放不下时仍可 flex-wrap 折行)。
|
||
按钮已折叠仍放不下时(文字本身超宽),文字单元格换行兜底。 */
|
||
table.liquid-actions-overflow td.cbi-section-actions,
|
||
table.liquid-actions-overflow th.cbi-section-actions {
|
||
white-space: normal !important;
|
||
/* 不设首选宽度:列宽=最宽按钮的内容宽(百分比会抢文字列的可用宽度) */
|
||
width: auto !important;
|
||
min-width: 0 !important;
|
||
flex-wrap: wrap;
|
||
gap: 4px 6px;
|
||
}
|
||
|
||
table.liquid-actions-overflow td.cbi-section-actions > div {
|
||
display: flex;
|
||
/* 每个按钮一行(纵排、靠左,按钮保持自然宽度不被拉伸) */
|
||
flex-direction: column;
|
||
align-items: flex-start;
|
||
gap: 4px 6px;
|
||
width: auto !important;
|
||
min-width: 0 !important;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* 已有 gap,清掉相邻按钮的 margin-left(否则间距叠加翻倍) */
|
||
table.liquid-actions-overflow td.cbi-section-actions > div > * + * {
|
||
margin-left: 0 !important;
|
||
}
|
||
|
||
/* 兜底:按钮已折叠后表格仍放不下(文字本身超宽,如长域名)时,
|
||
让文字单元格换行收缩,而不是被 .cbi-section 的 overflow:hidden
|
||
裁掉右侧——优先级仍是"先折叠按钮,最后才让文字换行" */
|
||
table.liquid-actions-overflow td:not(.cbi-section-actions),
|
||
table.liquid-actions-overflow th:not(.cbi-section-actions) {
|
||
white-space: normal !important;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
|
||
@media screen and (max-width: 854px) {
|
||
table.liquid-actions-overflow {
|
||
width: 100% !important;
|
||
min-width: 100% !important;
|
||
}
|
||
|
||
/* 窄屏下行内布局已把操作列推到独占的一整行、宽度足够摆下所有按钮,
|
||
维持横排一行;真的放不下时 flex-wrap 照样折行 */
|
||
table.liquid-actions-overflow td.cbi-section-actions > div {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
}
|
||
|
||
/* ==========================================================================
|
||
多列宽表响应式:一行三列 + 单元格自带表头(.liquid-grid,由 main.js 打标)
|
||
-------------------------------------------------------------------------
|
||
适用:DHCP 租约 / DDNS / WiFi / UPnP / 进程 等多列表(GridSection 与概览
|
||
include 表)。断点 1100px(实测:1150px 时"状态"列尚有 6.5 字/行,
|
||
1100px 掉到 4.3;移动端则整表按内容宽撑开只能横滚)。
|
||
|
||
断点判定放在 main.js 的 matchMedia(单一来源),这里只按 .liquid-grid 类
|
||
生效;关键声明用 !important —— ≤854px 的既有规则给 tr/td 设过
|
||
display:flex(同特异性会靠"后写"抢胜负,实测会输),必须显式压过。
|
||
|
||
排除两类已有专属布局的表:
|
||
- 接口页表(td[data-name="_ifacebox"]):图标行有独立两行布局;
|
||
- 标签/值两列表(td[width="33%"]):已有"20ch 标签列 + 值换行"方案。
|
||
单元格自带表头用 td::before 显示 data-title(Argon 同款机制,文案由
|
||
main.js 从同列 th 回填);原表头行隐藏,带筛选输入的 thead 保留。 */
|
||
.cbi-section-table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])),
|
||
.table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])) {
|
||
display: block;
|
||
width: 100% !important;
|
||
min-width: 100% !important;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.cbi-section-table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])) tr:not(.table-titles):not(.cbi-section-table-titles),
|
||
.table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])) tr:not(.table-titles):not(.cbi-section-table-titles) {
|
||
display: flex !important;
|
||
/* 放不下的列连同表头换到下一行,而不是挤压成一字一行/横向滚动 */
|
||
flex-wrap: wrap !important;
|
||
align-items: stretch;
|
||
white-space: normal !important;
|
||
/* 行与行之间的呼吸感:操作列换行到下一行时不至于贴着上一行 */
|
||
row-gap: 10px;
|
||
}
|
||
|
||
.cbi-section-table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])) tr > td:not(.cbi-section-actions),
|
||
.table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])) tr > td:not(.cbi-section-actions) {
|
||
flex: 1 1 33.33% !important; /* 一行三列,列边界落在 0/33.3/66.6 */
|
||
display: block !important;
|
||
min-width: 0 !important;
|
||
white-space: normal !important;
|
||
overflow-wrap: break-word;
|
||
word-break: normal; /* 按词换行,不再一字一断 */
|
||
border-bottom: 0 !important;
|
||
padding: 6px 10px;
|
||
}
|
||
|
||
/* 操作列前的内容格:跨满前两列(66.6%),使按钮正好从第三列起点开始
|
||
—— 与上一行第三列"同列对齐"(用户要求);右 padding 24px 拉开间距。
|
||
:has(td.cbi-section-actions) 限定"有操作列的表",避免误伤两列内容表。 */
|
||
/* 选择器必须带与通用规则相同的 :not(:has(...)) 链:通用规则元素更多,
|
||
同特异性会输(实测 66.6% 被 33.33% 盖掉),补齐链路 + .td + :nth-last-child 才赢 */
|
||
.cbi-section-table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])):has(td.cbi-section-actions) tr > td.td:nth-last-child(2),
|
||
.table.liquid-grid:not(:has(td[data-name="_ifacebox"])):not(:has(td[width="33%"])):has(td.cbi-section-actions) tr > td.td:nth-last-child(2) {
|
||
flex: 2 1 66.6% !important;
|
||
padding-right: 24px;
|
||
}
|
||
|
||
/* 操作列占第三列:按钮起点 = 上一行第三列内容起点(严格同列对齐);
|
||
顶对齐(align-self/items: flex-start);放不下时 flex-wrap 自动换到下一行 */
|
||
.cbi-section-table.liquid-grid tr > td.cbi-section-actions,
|
||
.table.liquid-grid tr > td.cbi-section-actions {
|
||
flex: 1 1 33.4% !important;
|
||
min-width: 0 !important;
|
||
align-self: flex-start;
|
||
align-items: flex-start;
|
||
margin-left: 0;
|
||
padding: 6px 10px;
|
||
white-space: normal !important;
|
||
}
|
||
|
||
/* 单元格自带表头(Argon 同款;文案由 main.js 从同列 th 回填 data-title) */
|
||
.cbi-section-table.liquid-grid td[data-title]::before,
|
||
.table.liquid-grid td[data-title]::before {
|
||
content: attr(data-title);
|
||
display: block;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
line-height: 1.45;
|
||
letter-spacing: .2px;
|
||
color: var(--text-color-medium);
|
||
opacity: .9;
|
||
margin-bottom: 1px;
|
||
}
|
||
|
||
/* 原表头行隐藏(每格已自带表头);带筛选输入的 thead 保留。
|
||
!important 必须:≤854px 的既有规则给 tr 设了 display:flex !important */
|
||
.cbi-section-table.liquid-grid tr.table-titles,
|
||
.cbi-section-table.liquid-grid tr.cbi-section-table-titles,
|
||
.table.liquid-grid tr.table-titles,
|
||
.cbi-section-table.liquid-grid thead:not(:has(input)),
|
||
.table.liquid-grid thead:not(:has(input)) {
|
||
display: none !important;
|
||
}
|
||
|
||
/* 三列网格下操作列仅占 1/3 宽:按钮相邻间距 8→4px,尽量保持一行
|
||
(1000px 实测:4 按钮约 290px,格内可用 284px,8px 间距会挤成两行) */
|
||
.cbi-section-table.liquid-grid td.cbi-section-actions > div > * + *,
|
||
.table.liquid-grid td.cbi-section-actions > div > * + * {
|
||
margin-left: 4px;
|
||
}
|
||
|
||
/* 折叠态(按钮优先折叠)在此模式下的适配:操作列已占整行 → 按钮横排折行;
|
||
文字格的"一字一断"兜底改回按词换行 */
|
||
/* 仅 grid 态(视口≤1100、操作列已独占整行)才把折叠按钮改横排;
|
||
桌面宽屏折叠态维持 r17 的"每个按钮一行"纵排 */
|
||
table.liquid-actions-overflow.liquid-grid td.cbi-section-actions > div {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
align-items: center;
|
||
}
|
||
|
||
table.liquid-actions-overflow.liquid-grid td:not(.cbi-section-actions),
|
||
table.liquid-actions-overflow.liquid-grid th:not(.cbi-section-actions) {
|
||
word-break: normal;
|
||
overflow-wrap: break-word;
|
||
}
|
||
|
||
/* ===== 移动端(≤854px):标签/值两列表的标签列改为按自身文字宽度收缩 =====
|
||
原来固定 20ch≈145px(为了让最长标签"CPU 使用率(%)"单行),但短标签
|
||
(主机名/型号/温度…只需 40~60px)右侧空出 ~100px,把值列挤到换行——
|
||
实测移动端值列因此少 ~90px。改为 flex:0 1 auto + max-width:20ch:
|
||
改为固定 5 个字的内容宽(5em):既让值列拿回空间(比 20ch≈145px 窄),
|
||
又保证全表标签列等宽、值列起点对齐(用户反馈:按文字收缩会让值列
|
||
逐行错位)。超过 5 字的标签(如 CPU 使用率(%))在本格内折行。
|
||
桌面(>854px)保持 20ch 固定列。 */
|
||
@media screen and (max-width: 854px) {
|
||
.table:has(td[width="33%"]) tr > td[width="33%"],
|
||
.cbi-section-table tr > td[width="33%"],
|
||
.table tr > td[width="33%"] {
|
||
flex: 0 0 5em !important;
|
||
box-sizing: content-box !important; /* 5em=5 个汉字的内容宽,外加自身 padding */
|
||
width: 5em !important;
|
||
max-width: none !important;
|
||
min-width: 0 !important;
|
||
/* 固定 5 字宽后,超过 5 字的标签(CPU 使用率(%))在本格内折行,
|
||
值列起点全表对齐(用户反馈:按文字收缩会导致值列不齐) */
|
||
white-space: normal !important;
|
||
overflow-wrap: break-word;
|
||
word-break: break-word;
|
||
}
|
||
}
|
||
|
||
/* ===== 启动项页(admin/system/startup):操作列 5 个按钮排 3+2 =====
|
||
该页移动端按钮容器为整行宽,5 个按钮自然折行会排成 4+1(最后孤零零
|
||
一个,不美观)。改为 3 列网格:第一行 3 个、第二行 2 个。
|
||
仅按该页 data-page 生效(用户要求"单独优化这里"),其它表格不受影响。 */
|
||
@media screen and (max-width: 854px) {
|
||
/* data-page 两种写法都匹配(连字符式 admin-system-startup /
|
||
路径式 admin/system/startup,主题与 openclash 用前者) */
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, max-content);
|
||
justify-content: start;
|
||
align-items: start;
|
||
gap: 6px 8px;
|
||
}
|
||
|
||
/* 网格自带 gap,清掉相邻按钮的 margin-left(否则间距叠加不均) */
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > * + * {
|
||
margin-left: 0;
|
||
}
|
||
}
|
||
|
||
/* ===== 有标题的操作格:标题在上、按钮在下、两者左对齐 =====
|
||
ui.Table(cbi_update_table)会把表头文案复制成单元格 data-title、并把
|
||
表头的 cbi-section-actions 类复制过来——如概览 DHCP 租约表的最后一列
|
||
(表头"静态租约" / 内容"设为静态"按钮)。该格在 ≤854px 被通用规则
|
||
变成 flex 行 → 标题与按钮排成左右两列,看起来像"两列东西"。
|
||
实际语义:标题=这列的表头,按钮=这列的内容 → 上下堆叠;
|
||
对齐方式经用户确认为左对齐(先居中后改回左对齐更合适)。 */
|
||
.cbi-section-table.liquid-grid td.cbi-section-actions[data-title],
|
||
.table.liquid-grid td.cbi-section-actions[data-title] {
|
||
display: block !important; /* ::before 标题与按钮上下排 */
|
||
text-align: left; /* 标题与按钮左对齐 */
|
||
}
|
||
|
||
/* 若格内是按钮容器(折叠/按钮组),同样左对齐 */
|
||
.cbi-section-table.liquid-grid td.cbi-section-actions[data-title] > div,
|
||
.table.liquid-grid td.cbi-section-actions[data-title] > div {
|
||
text-align: left;
|
||
justify-content: flex-start;
|
||
}
|
||
|
||
/* ===== 概览"系统"卡片(标签/值两列表 td[width="33%"]):每行强制单行 =====
|
||
用户要求:每行只占一行不换行;固件版本这类超长值不折行,改为整卡
|
||
左右滑动查看右侧内容(.table 本就是 overflow-x:auto 的滚动容器)。
|
||
注意这是对 r4(放不下就换行)与 r10(标签按文字收缩)的本卡覆盖:
|
||
- 行 flex-wrap: nowrap + 单元格 white-space: nowrap → 行=内容宽;
|
||
- 单元格 flex:0 0 auto + width:max-content → 行宽=标签+值的自然宽;
|
||
- 表宽超出容器时由 .table:has(td[width="33%"]){overflow-x:auto} 横滚。
|
||
!important 必须:要压过 r10 移动端的 width:max-content/20ch 封顶以及
|
||
桌面 20ch 固定列规则(同特异性会靠顺序,这里统一显式压过)。 */
|
||
.table:has(td[width="33%"]) tr {
|
||
flex-wrap: nowrap !important;
|
||
white-space: nowrap !important;
|
||
}
|
||
|
||
/* 值列(最后一格)必须占满剩余宽度:内存/存储的进度条要拉满到右侧。
|
||
r13 把所有格子改成 flex:0 0 auto + max-content 后,值列缩成内容宽 →
|
||
进度条看起来"等于文字长度"(用户反馈)。important 对 important 由
|
||
特异性决定(本规则带 :last-child 更高),故这里同样加 important。 */
|
||
.table:has(td[width="33%"]) tr > td:last-child {
|
||
flex: 1 1 auto !important;
|
||
width: auto !important;
|
||
}
|
||
|
||
.table:has(td[width="33%"]) tr > td {
|
||
flex: 0 0 auto !important;
|
||
width: max-content !important;
|
||
max-width: none !important;
|
||
white-space: nowrap !important;
|
||
overflow-wrap: normal !important;
|
||
word-break: normal !important;
|
||
}
|
||
|
||
/* ===== 启动项页(admin/system/startup):操作按钮按语义上色 =====
|
||
该页操作格内 5 个子元素顺序固定(startup.js rows.push):
|
||
① 启用开关(cbi-button-positive=已启用 / negative=已禁用)
|
||
② 启动 Start ③ 重启 Restart ④ 重新加载 Reload ⑤ 停止 Stop
|
||
需求:启用/启动=绿、重启/重新加载=橙、停止=红;仅本页生效。
|
||
主题现有 .cbi-button-positive 等走 accent 蓝渐变,这里按语义覆盖,
|
||
含 :hover 态(否则 hover 会被主题的 accent-grad 规则抢回蓝色)。 */
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(2),
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > .cbi-button-positive {
|
||
/* 启动 + 启用(已启用态) → 绿 */
|
||
color: var(--on-success-color);
|
||
background: linear-gradient(135deg, #34d399, var(--success-color-medium));
|
||
box-shadow: 0 6px 16px rgba(var(--success-color-high-rgb), 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
}
|
||
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(2):hover,
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > .cbi-button-positive:hover {
|
||
filter: brightness(1.08);
|
||
}
|
||
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(3),
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(4) {
|
||
/* 重启 + 重新加载 → 橙(复用主题 amber 强调色渐变) */
|
||
color: #ffffff;
|
||
background: linear-gradient(135deg, #ffc04a 0%, #e8940f 48%, #c47100 100%);
|
||
box-shadow: 0 6px 16px rgba(232, 148, 15, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.35);
|
||
}
|
||
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(3):hover,
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(4):hover {
|
||
filter: brightness(1.08);
|
||
}
|
||
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(5) {
|
||
/* 停止 → 红(与主题 negative 同配方,保证禁用态/其它页一致观感) */
|
||
color: var(--on-error-color);
|
||
background: linear-gradient(135deg, #f06767, var(--error-color-high));
|
||
box-shadow: 0 6px 16px rgba(var(--error-color-high-rgb), 0.35);
|
||
}
|
||
|
||
body:is([data-page="admin-system-startup"], [data-page="admin/system/startup"])
|
||
td.cbi-section-actions > div > :nth-child(5):hover {
|
||
filter: brightness(1.08);
|
||
}
|
||
|
||
/* ===== 小体积弹窗:含文件选择控件的对话框按内容收窄 =====
|
||
(用户反馈:"选择要上传的文件…取消/上传"这类弹窗异常宽,右侧大片空白)
|
||
根因:#modal_overlay > .modal 固定 width:80%(max 80vw)——1920 屏上
|
||
一个只有标题+文件框+两个按钮的对话框也占 1536px;再叠加 .button-row
|
||
的 flex-end + 首按钮 margin-right:auto(取消在最左、上传在最右),
|
||
空白被进一步拉开。
|
||
方案:只对含 input[type=file] 的弹窗收窄到 520px(按内容包裹、文字
|
||
正常换行),按钮行随之收拢;其它表单弹窗保持 80% 不变。 */
|
||
#modal_overlay > .modal:has(input[type="file"]) {
|
||
/* 按内容自适应(fit-content):内容已全部左对齐,固定 520px 会在右侧
|
||
留出大片空白(实测344px)。弹窗宽=最宽一行内容+内边距;超长说明文字
|
||
仍会被 max-width 限制并正常换行 */
|
||
/* 宽度策略(用户定量要求:约当前的 180% ≈545px,且按钮/内容不许换行):
|
||
- fit-content:内容长时随内容展开(上限 80vw),绝不逼元素换行;
|
||
- min-width 545:短内容也保底 545px —— 之前纯 fit-content 只有303px,
|
||
弹窗过小、按钮行(.button-row flex-wrap:wrap)被挤到换行。 */
|
||
width: fit-content;
|
||
min-width: min(545px, 92vw);
|
||
max-width: 80vw;
|
||
}
|
||
|
||
/* 文件框随容器宽度走,别用浏览器默认 intrinsic 宽撑破 */
|
||
#modal_overlay > .modal:has(input[type="file"]) input[type="file"] {
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* ui.js 的上传弹窗真实结构(hideModal 里的 uploadFile):
|
||
.modal > p(说明) + div[display:flex] > (.left[flex:1] = 选择文件按钮
|
||
.right[flex:1] = 取消 + 上传)
|
||
默认左右各占一半、右侧组贴中线,两组之间只有 129px 且上传按钮离右缘 56px。
|
||
用户要求拉开"选择文件"与"取消/上传"的间距 → 右侧整组靠右并加左间距。 */
|
||
/* 按钮行左组(选择文件)必须贴左:标题/说明的 text-align:center(方案2)
|
||
会被 .left 继承 → 按钮被推到 .left 中央,左侧出现空白间隔(用户反馈)。 */
|
||
#modal_overlay > .modal:has(input[type="file"]) > div[style*="display:flex"] .left {
|
||
text-align: left;
|
||
}
|
||
|
||
#modal_overlay > .modal:has(input[type="file"]) > div[style*="display:flex"] .right {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 10px;
|
||
margin-left: 32px;
|
||
}
|
||
|
||
/* 按钮收拢到右侧一组:.button-row 首按钮的 margin-right:auto 会把
|
||
"取消"甩到最左、"上传"顶到最右(间距 300+px 的空白带)。小弹窗里取消。 */
|
||
#modal_overlay > .modal:has(input[type="file"]) .button-row > button:first-of-type {
|
||
margin-right: 0;
|
||
}
|
||
|
||
/* 方案2(用户选定):标题与说明文字**居中**——左对齐时短文本行右侧会留
|
||
128~195px 空白,居中后空白分到两侧更平衡;文件框仍左对齐、按钮仍右对齐 */
|
||
#modal_overlay > .modal:has(input[type="file"]) > :not(input):not(.button-row) {
|
||
text-align: center;
|
||
}
|
||
|
||
/* 对齐方式(用户最终指定):标题、说明文字、选择文件按钮一律**左对齐**,
|
||
只有"取消/上传"两个按钮右对齐(.button-row 本就是 flex-end)。
|
||
(曾试过内容整体右对齐,用户反馈说明文字也要左 → 回到默认左对齐,
|
||
仅保留下方文件框的显式左推,防止被带 text-align 的包装层甩到右边。) */
|
||
|
||
#modal_overlay > .modal:has(input[type="file"]) input[type="file"] {
|
||
display: block;
|
||
/* 选择文件按钮保持左对齐(用户指定):块级 + margin-right:auto
|
||
推到左侧——只靠 text-align:right 会把它甩到右边 */
|
||
margin-right: auto;
|
||
margin-left: 0;
|
||
}
|
||
|
||
/* ===== 会话过期/重新登录弹窗(luci.js notifySessionExpiry)收窄 =====
|
||
结构(源码确认):.modal > h4 + div.alert-message.warning + div.right(登录按钮)。
|
||
它走通用规则 width:80% → 1920 屏上占 1536px,只有一句话却拉满整屏(用户反馈)。
|
||
按内容收窄:fit-content + 保底 420px(窄屏 92vw),上限 80vw;只命中"同时带
|
||
直接子级 .alert-message 与 .right"的弹窗,与上传弹窗(.right 在 flex 行内)不冲突。 */
|
||
#modal_overlay > .modal:has(> .alert-message):has(> .right) {
|
||
width: fit-content;
|
||
/* 窄屏上原来 min(420px,92vw) 等于 92% 屏宽 → 看着像贴边大卡片;
|
||
按用户要求改为"屏幕宽度的 75%":窄屏下 min 与 max 都落到 75vw,
|
||
长文本也被限在 75% 内换行;宽屏仍保底 420px 不会缩水 */
|
||
min-width: min(420px, 75vw);
|
||
max-width: 75vw;
|
||
}
|