/*! * 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 * (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 ) ========================================================================== */ :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 在 里不继承 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 判定表格“自然宽度”超出所在容器 / 当前屏幕时给 加 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; }