feat(cards): 卡片外观可配置(不透明度 / 毛玻璃)

- 站点设置新增 card.opacity(40–100)与 card.blur,后台「首页管理 → 卡片外观」可调
- 前台所有卡片(首页 / 分组页 / 详情 / 侧栏)共用一套 CSS 变量
- 顺带带上上一轮:背景缓存、搜索记忆、明暗图标跟随实际状态、卡片描边/阴影、分组页卡片外壳
This commit is contained in:
2026-11-05 12:45:11 +08:00
parent a9a913942a
commit 4ce1b7e7c1
8 changed files with 229 additions and 19 deletions
+35
View File
@@ -13,6 +13,8 @@ pub const SETTINGS_KEY: &str = "site";
pub const MAX_HEADER_ITEMS: usize = 8;
pub const MIN_SPEED: u32 = 8;
pub const MAX_SPEED: u32 = 120;
pub const MIN_CARD_OPACITY: u32 = 40;
pub const MAX_CARD_OPACITY: u32 = 100;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(rename_all = "snake_case")]
@@ -119,6 +121,25 @@ pub struct BackgroundSettings {
pub dark: String,
}
/// 卡片外观:透明度与毛玻璃。前台所有卡片共用一套(首页 / 分组页 / 详情页 / 侧栏)。
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(default)]
pub struct CardSettings {
/// 卡片底色的不透明度(百分比,40–100)
pub opacity: u32,
/// 毛玻璃:背景模糊 + 提饱和度
pub blur: bool,
}
impl Default for CardSettings {
fn default() -> Self {
Self {
opacity: 100,
blur: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
#[serde(default)]
pub struct SearchEngineSettings {
@@ -288,6 +309,7 @@ pub struct Settings {
pub header: HeaderSettings,
pub sidebar: SidebarSettings,
pub background: BackgroundSettings,
pub card: CardSettings,
pub search: SearchSettings,
pub profile: ProfileSettings,
pub site: SiteMetaSettings,
@@ -354,6 +376,11 @@ pub fn validate(settings: &Settings) -> Result<(), StashError> {
"暗色背景地址最多 500 个字符".into(),
));
}
if !(MIN_CARD_OPACITY..=MAX_CARD_OPACITY).contains(&settings.card.opacity) {
return Err(StashError::InvalidDirectory(format!(
"卡片不透明度需在 {MIN_CARD_OPACITY}–{MAX_CARD_OPACITY} 之间"
)));
}
if settings.search.engines.len() > 10 {
return Err(StashError::InvalidDirectory("搜索引擎最多 10 个".into()));
}
@@ -551,6 +578,14 @@ mod tests {
let mut empty = Settings::default();
empty.header.items.clear();
assert!(save(&mut conn, empty, 0).is_err());
let mut too_transparent = Settings::default();
too_transparent.card.opacity = MIN_CARD_OPACITY - 1;
assert!(save(&mut conn, too_transparent, 0).is_err());
let mut clear = Settings::default();
clear.card.opacity = MAX_CARD_OPACITY;
assert!(save(&mut conn, clear, 0).is_ok());
}
#[test]