diff --git a/sql/edge_seed_visual.sql b/sql/edge_seed_visual.sql
index 33a56df..93cf330 100644
--- a/sql/edge_seed_visual.sql
+++ b/sql/edge_seed_visual.sql
@@ -21,9 +21,10 @@ WHERE code = 'demo';
UPDATE cms_banner SET status = 0 WHERE id = 5;
-- 2) 院系子站(demo 的子站:header 出"专业院系"下拉;不配 public_base_url=按请求派生)
+-- 主题应用:jdx→cherry-bloom(樱语粉韵)、xxgc→violet-iris(鸢尾紫韵)——两套新皮肤随种子落地可视验收
INSERT INTO cms_site (id, code, parent_site_id, sch_id, sch_name, name, theme, config_json, status, create_by, create_time) VALUES
-(6, 'jdx', 1, 990001, '云海大学', '机电学院', 'default', '{"copyright": "机电学院"}', 1, 'cmssuper', NOW()),
-(7, 'xxgc', 1, 990001, '云海大学', '信息工程学院', 'default', '{"copyright": "信息工程学院"}', 1, 'cmssuper', NOW());
+(6, 'jdx', 1, 990001, '云海大学', '机电学院', 'cherry-bloom', '{"copyright": "机电学院"}', 1, 'cmssuper', NOW()),
+(7, 'xxgc', 1, 990001, '云海大学', '信息工程学院', 'violet-iris', '{"copyright": "信息工程学院"}', 1, 'cmssuper', NOW());
INSERT INTO cms_category (id, site_id, parent_id, name, type, link_url, content, sort_no, status, create_by, create_time) VALUES
(61, 6, 0, '学院概况', 2, NULL, '
机电学院(视觉精修种子)。
', 1, 1, 'cmssuper', NOW()),
(62, 6, 0, '学院新闻', 1, NULL, NULL, 2, 1, 'cmssuper', NOW()),
diff --git a/src/main/resources/templates/themes/cherry-bloom/README.md b/src/main/resources/templates/themes/cherry-bloom/README.md
new file mode 100644
index 0000000..ebf6e2e
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/README.md
@@ -0,0 +1,35 @@
+# cherry-bloom 皮肤包
+
+由 mint-breeze 派生的温柔樱粉风皮肤(docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。
+
+
+## 风格定位
+
+温柔、甜美,适合幼儿园与小学门户:樱花粉品牌色、大圆角 16px、卡片纯白+暖粉柔和阴影、细线顶栏+居中 logo(桌面吸顶,移动端取消)、列表卡片网格(一行 3 张)、花瓣式圆点装饰与暖白留白;无纹理、无通栏大图、无复杂动效。
+
+## 数据契约(模板可取用的 model 属性全集,契约 §3)
+
+| 页面 | 属性 |
+| --- | --- |
+| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/asset/search/siteHome)、`themeBase`(=`themes/cherry-bloom`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) |
+| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) |
+| list / search | `listData`(分页)、`category`(list)、`q` + `searchMode`(search) |
+| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` |
+| single | `category`(含 `content`)、`currentCategoryId` |
+
+原则:皮肤不得假设"首页一定有轮播",对空集合优雅降级。
+
+## 轮播图尺寸约定(bannerSize)
+
+1920x560(管理端上传端按此引导裁剪,D2 决策点)。
+
+## 可调参数
+
+见 `theme.json` 的 `params`:
+
+- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(即强调色,用于品牌文字与悬停加深)/ `primaryLight` 主色提亮 / `bg` 页面底色 / `cardBg` 卡片底色 / `border` 边框色 / `text` 正文文字 / `textLight` 次要文字。
+- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张)或 `row`(传统行列表)。
+
+换色建议:主色系三值同步调整(primary / primaryDark / primaryLight),bg 与 border、primaryLight 保持同族浅色调,避免底色与卡片对比失衡。
+
+优先级:`custom_css` > `themeVarsCss` > `main.css`。
diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css b/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css
new file mode 100644
index 0000000..ad7c1c9
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/assets/css/main.css
@@ -0,0 +1,616 @@
+/* ============================================================
+ * cherry-bloom 皮肤包样式(mint-breeze 派生,docs/10 皮肤包契约 §5)
+ * 温柔樱粉风:樱花粉品牌色 + 大圆角 16px + 柔和暖粉阴影 + 卡片网格列表
+ * + 花瓣式圆点装饰与暖白留白;无纹理、无通栏大图、无复杂动效。
+ * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。
+ * ============================================================ */
+:root {
+ --primary: #DB5A8B;
+ --primary-dark: #B23A68;
+ --primary-light: #FDECF3;
+ --text: #43333B;
+ --text-light: #9C828C;
+ --bg: #FFFAFB;
+ --card-bg: #FFFFFF;
+ --border: #F7DDE7;
+ --radius: 16px;
+ --radius-sm: 10px;
+ --radius-pill: 999px;
+ --shadow-soft: 0 6px 18px rgba(178, 58, 104, .08);
+ --shadow-hover: 0 12px 28px rgba(178, 58, 104, .14);
+ --max-width: 1200px;
+}
+
+* { box-sizing: border-box; }
+body {
+ margin: 0;
+ font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
+ color: var(--text);
+ background: var(--bg);
+}
+a { color: inherit; text-decoration: none; transition: color .15s ease; }
+a:hover { color: var(--primary); }
+img { max-width: 100%; }
+time, .item-meta { color: var(--text-light); font-size: 13px; }
+.empty { color: var(--text-light); text-align: center; padding: 32px 0; }
+
+/* ===== 头部:细线顶栏 + 居中品牌行 + 白底导航(桌面吸顶) ===== */
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 90;
+ background: var(--card-bg);
+ box-shadow: var(--shadow-soft);
+}
+/* 细线顶栏:院系切换在左、站内搜索在右;院系未配置时搜索仍靠右 */
+.top-bar {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 16px;
+ padding: 7px max(16px, calc((100% - var(--max-width)) / 2 + 16px));
+ border-bottom: 1px solid var(--border);
+ font-size: 13px;
+}
+.top-bar .site-colleges { margin-right: auto; }
+/* 居中品牌行:两侧几何圆点装饰(纯 CSS,无纹理) */
+.brand-row {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ padding: 18px 16px 14px;
+}
+.brand-row::before, .brand-row::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 46px;
+ height: 10px;
+ opacity: .45;
+ background-image:
+ radial-gradient(circle, var(--primary) 2.2px, transparent 2.8px),
+ radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px),
+ radial-gradient(circle, var(--primary) 1.1px, transparent 1.7px);
+ background-repeat: no-repeat;
+ background-position: left center, center center, right center;
+}
+.brand-row::before { left: 6%; }
+.brand-row::after { right: 6%; }
+.site-brand { display: flex; align-items: center; gap: 14px; }
+.site-logo { height: 52px; }
+.site-name {
+ font-size: 28px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ color: var(--primary-dark);
+}
+.site-name:hover { color: var(--primary); }
+.site-search { display: flex; align-items: center; }
+.site-search input {
+ border: 1px solid var(--border);
+ border-right: none;
+ border-radius: var(--radius-pill) 0 0 var(--radius-pill);
+ padding: 7px 16px;
+ width: 220px;
+ outline: none;
+ background: var(--card-bg);
+ color: var(--text);
+ transition: border-color .15s ease, box-shadow .15s ease;
+}
+.site-search input:focus {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px var(--primary-light);
+}
+.site-search button {
+ border: none;
+ background: var(--primary);
+ color: #fff;
+ padding: 7px 20px;
+ border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
+ cursor: pointer;
+ transition: background .15s ease;
+}
+.site-search button:hover { background: var(--primary-dark); }
+
+/* ===== 专业院系下拉(院系子站切换):胶囊开关 + 圆角白卡面板 ===== */
+.site-colleges { position: relative; }
+.site-colleges .colleges-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ padding: 5px 14px;
+ cursor: pointer;
+ color: var(--primary-dark);
+ font-weight: 600;
+ white-space: nowrap;
+ transition: border-color .15s ease, background .15s ease;
+}
+.site-colleges:hover .colleges-toggle {
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--primary); }
+.site-colleges .colleges-menu {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 200px;
+ max-height: 420px;
+ overflow-y: auto;
+ margin: 0;
+ padding: 8px 0;
+ list-style: none;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-hover);
+ z-index: 110;
+}
+.site-colleges:hover .colleges-menu { display: block; }
+.site-colleges .colleges-menu li a {
+ display: block;
+ margin: 0 8px;
+ padding: 9px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text);
+ white-space: nowrap;
+}
+.site-colleges .colleges-menu li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+.site-colleges .colleges-menu li.active a { color: var(--primary); font-weight: 600; }
+
+/* ===== 主导航:白底 + 柔和文字层级,当前项圆点标记 ===== */
+.site-nav { background: var(--card-bg); border-top: 1px solid var(--border); }
+.site-nav ul {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 0 16px;
+ list-style: none;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+.site-nav li a {
+ position: relative;
+ display: block;
+ padding: 13px 20px;
+ color: var(--text);
+ font-weight: 500;
+}
+.site-nav li a:hover { color: var(--primary); }
+.site-nav li.active a { color: var(--primary); font-weight: 600; }
+/* 几何圆点:当前栏目下方的小圆点 */
+.site-nav li.active > a::before {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: 5px;
+ width: 5px;
+ height: 5px;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ background: var(--primary);
+}
+
+/* ===== 二级下拉导航:圆角白卡面板 ===== */
+.site-nav > ul > li { position: relative; }
+.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; }
+.site-nav ul.sub-nav {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 168px;
+ max-width: none;
+ margin: 0;
+ padding: 8px 0;
+ list-style: none;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-hover);
+ z-index: 100;
+}
+.site-nav li:hover > .sub-nav { display: block; }
+.site-nav .sub-nav li a {
+ margin: 0 8px;
+ padding: 9px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text);
+ font-weight: 400;
+ white-space: nowrap;
+ background: none;
+}
+.site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+.site-nav .sub-nav li.active a { color: var(--primary); font-weight: 600; }
+
+/* ===== 首页轮播:容器内圆角卡片(非通栏),几何圆点指示器 ===== */
+.banner {
+ position: relative;
+ width: min(var(--max-width), calc(100% - 32px));
+ margin: 24px auto 0;
+ border-radius: var(--radius);
+ overflow: hidden;
+ box-shadow: var(--shadow-soft);
+}
+.banner-slides { position: relative; height: 400px; }
+.banner-slide { display: none; position: absolute; inset: 0; }
+.banner-slide.active { display: block; }
+.banner-slide img { width: 100%; height: 100%; object-fit: cover; }
+.banner-title {
+ position: absolute;
+ left: 0; right: 0; bottom: 0;
+ padding: 14px 20px;
+ color: #fff;
+ font-weight: 500;
+ background: linear-gradient(transparent, rgba(20, 63, 61, .62));
+}
+.banner-dots {
+ position: absolute;
+ bottom: 14px; right: 20px;
+ display: flex; gap: 8px;
+}
+.banner-dots button {
+ width: 9px; height: 9px;
+ border-radius: 50%;
+ border: none;
+ background: rgba(255, 255, 255, .55);
+ cursor: pointer;
+ transition: background .15s ease, transform .15s ease;
+}
+.banner-dots button.active { background: #fff; transform: scale(1.25); }
+
+/* ===== 首页区块:卡片网格(头条通栏 + 栏目块一行 3 张) ===== */
+.home-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 24px;
+}
+.top-strip, .category-block {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 24px;
+}
+.top-strip { grid-column: 1 / -1; }
+/* 栏目标题:几何圆点标记(环形点),标题中等粗细、层级柔和 */
+.section-title, .block-head h2 {
+ margin: 0 0 14px;
+ font-size: 19px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.section-title::before, .block-head h2::before {
+ content: "";
+ flex: none;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 3.5px var(--primary-light);
+}
+.block-head { display: flex; align-items: center; justify-content: space-between; }
+.more {
+ flex: none;
+ font-size: 13px;
+ color: var(--text-light);
+ padding: 4px 12px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+}
+.more:hover {
+ color: var(--primary-dark);
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+.top-strip ul, .block-list { list-style: none; margin: 0; padding: 0; }
+.top-strip li, .block-list li {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 0;
+ border-bottom: 1px dashed var(--border);
+}
+.top-strip li:last-child, .block-list li:last-child { border-bottom: none; }
+.top-strip li a, .block-list li a {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.top-badge {
+ flex: none;
+ color: var(--primary-dark);
+ background: var(--primary-light);
+ font-size: 12px;
+ font-weight: 500;
+ padding: 2px 10px;
+ border-radius: var(--radius-pill);
+}
+
+/* ===== 列表页:卡片网格(默认一行 3 张;listLayout=row 切换行列表) ===== */
+.list-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+}
+.breadcrumb { color: var(--text-light); font-size: 13px; margin-bottom: 14px; }
+.breadcrumb a { margin-right: 8px; }
+.breadcrumb span { margin-left: 8px; }
+.page-title {
+ font-size: 24px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ margin: 0 0 22px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.page-title::before {
+ content: "";
+ flex: none;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 3.5px var(--primary-light);
+}
+.article-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 20px;
+}
+.article-list li {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 22px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ transition: transform .18s ease, box-shadow .18s ease;
+}
+.article-list li:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
+.item-title {
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.55;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+.article-list li:hover .item-title { color: var(--primary); }
+.item-summary {
+ color: var(--text-light);
+ font-size: 13.5px;
+ line-height: 1.7;
+ margin: 0;
+ flex: 1;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+}
+.item-meta {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin-top: auto;
+ padding-top: 10px;
+ border-top: 1px dashed var(--border);
+}
+/* 布局变体:传统行列表(themeParams listLayout=row,见 list.html classappend) */
+.list-rows .article-list { display: block; }
+.list-rows .article-list li { display: block; margin-bottom: 16px; }
+.list-rows .item-summary { -webkit-line-clamp: 2; flex: none; }
+.list-rows .item-meta { border-top: none; padding-top: 0; margin-top: 6px; }
+
+/* ===== 分页:胶囊页码 ===== */
+.pagination {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ flex-wrap: wrap;
+ padding: 28px 0 4px;
+}
+.pagination a {
+ min-width: 36px;
+ text-align: center;
+ padding: 6px 12px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ background: var(--card-bg);
+ font-size: 14px;
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+}
+.pagination a:hover { color: var(--primary-dark); border-color: var(--primary); }
+.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); }
+
+/* ===== 详情/单页:整卡承载,正文舒适行距 ===== */
+.detail-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+}
+.detail-main .article {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 36px 40px;
+}
+.article-title {
+ margin: 4px 0 14px;
+ font-size: 26px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ text-align: center;
+}
+.article-meta {
+ display: flex;
+ gap: 18px;
+ justify-content: center;
+ flex-wrap: wrap;
+ color: var(--text-light);
+ font-size: 13px;
+ padding-bottom: 18px;
+ border-bottom: 1px solid var(--border);
+}
+.article-content { line-height: 1.9; font-size: 16px; padding-top: 22px; word-break: break-word; }
+.article-content img { height: auto; border-radius: var(--radius-sm); }
+.article-content table { border-collapse: collapse; width: 100%; }
+.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 6px 10px; }
+.article-sibling { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; font-size: 14px; }
+.article-sibling a {
+ flex: 1 1 320px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 14px 18px;
+ transition: transform .18s ease, box-shadow .18s ease, color .15s ease;
+}
+.article-sibling a:hover {
+ color: var(--primary-dark);
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-hover);
+}
+
+/* ===== 页脚:浅樱粉底 + 圆点分隔条(三态渲染逻辑不动) ===== */
+.site-footer {
+ margin-top: 40px;
+ background: var(--primary-light);
+ color: var(--text);
+ border-top: 1px solid var(--border);
+}
+.site-footer::before {
+ content: "";
+ display: block;
+ height: 8px;
+ width: min(var(--max-width), calc(100% - 32px));
+ margin: 18px auto 0;
+ background-image: radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px);
+ background-size: 16px 8px;
+ background-repeat: repeat-x;
+ opacity: .4;
+}
+.footer-links {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 18px 16px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.links-group { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; }
+.group-name { font-weight: 600; margin-right: 4px; color: var(--primary-dark); }
+.links-group a {
+ padding: 3px 10px;
+ border-radius: var(--radius-pill);
+ transition: background .15s ease, color .15s ease;
+}
+.links-group a:hover { background: rgba(255, 255, 255, .75); color: var(--primary-dark); }
+.footer-info {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 14px 16px 24px;
+ text-align: center;
+ font-size: 13px;
+ color: var(--text-light);
+}
+.footer-info p { margin: 4px 0; }
+
+/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
+.links-group a .link-logo {
+ height: 20px;
+ width: auto;
+ max-width: 64px;
+ object-fit: contain;
+ vertical-align: middle;
+ margin-right: 4px;
+ border-radius: 4px;
+}
+
+/* ===== 平板(≤992px):网格降为两列,隐藏品牌行圆点装饰 ===== */
+@media (max-width: 992px) {
+ .home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .brand-row::before, .brand-row::after { display: none; }
+}
+
+/* ===== 移动端(≤768px):取消吸顶,网格单列,导航横向滑动 ===== */
+@media (max-width: 768px) {
+ .site-header { position: static; }
+ .top-bar { padding: 8px 14px; justify-content: flex-start; flex-wrap: wrap; }
+ .top-bar .site-colleges { margin-right: 0; }
+ .site-search { flex: 1; }
+ .site-search input { flex: 1; width: auto; min-width: 0; }
+ .brand-row { padding: 14px 16px 10px; }
+ .site-logo { height: 40px; }
+ .site-name { font-size: 22px; }
+ .site-nav ul {
+ justify-content: flex-start;
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+ .banner-slides { height: 200px; }
+ .home-main { grid-template-columns: 1fr; padding: 18px 14px 40px; }
+ .article-list { grid-template-columns: 1fr; }
+ .detail-main .article { padding: 22px 16px; }
+ .article-title { font-size: 21px; }
+ /* 触屏无 hover:子菜单静态平铺(缩进列表),不做浮层 */
+ .site-nav ul.sub-nav {
+ display: block;
+ position: static;
+ border: none;
+ box-shadow: none;
+ background: transparent;
+ min-width: 0;
+ padding: 0 0 6px;
+ }
+ .site-nav .sub-nav li a { margin: 0; padding: 8px 16px 8px 34px; border-radius: 0; color: var(--text-light); }
+ .site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+}
+
+
+/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */
+.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; }
+.has-sidenav .list-body { flex: 1; min-width: 0; }
+.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; }
+.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; }
+.side-nav-list { list-style: none; margin: 0; padding: 0; }
+.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); }
+.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; }
+.side-nav-item a:hover { color: var(--primary); background: var(--bg); }
+.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; }
+/* 顶导航二级下拉当前项高亮(docs/11 顺带) */
+.site-nav ul.sub-nav li.active a { color: var(--primary); }
+@media (max-width: 768px) {
+ .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; }
+ .side-nav { width: 100%; }
+ .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; }
+ .side-nav-item { flex: none; }
+ .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); }
+ .side-nav-item a { white-space: nowrap; }
+}
+@media print {
+ .side-nav { display: none; }
+ .list-main.has-sidenav, .detail-main.has-sidenav { display: block; }
+}
diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png
new file mode 100644
index 0000000..497cae1
Binary files /dev/null and b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.png differ
diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg
new file mode 100644
index 0000000..56ed83c
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/assets/favicon.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js b/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js
new file mode 100644
index 0000000..a077c85
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/assets/js/main.js
@@ -0,0 +1,40 @@
+/* cherry-bloom 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */
+(function () {
+ var slides = document.querySelectorAll('.banner-slide');
+ var dots = document.querySelectorAll('.banner-dots button');
+ if (!slides.length) {
+ return;
+ }
+ var current = 0;
+ var timer = null;
+
+ function show(index) {
+ slides[current].classList.remove('active');
+ if (dots[current]) {
+ dots[current].classList.remove('active');
+ }
+ current = (index + slides.length) % slides.length;
+ slides[current].classList.add('active');
+ if (dots[current]) {
+ dots[current].classList.add('active');
+ }
+ }
+
+ function start() {
+ if (slides.length > 1) {
+ timer = window.setInterval(function () {
+ show(current + 1);
+ }, 5000);
+ }
+ }
+
+ dots.forEach(function (dot, index) {
+ dot.addEventListener('click', function () {
+ window.clearInterval(timer);
+ show(index);
+ start();
+ });
+ });
+
+ start();
+})();
diff --git a/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png b/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png
new file mode 100644
index 0000000..b6a7d0c
Binary files /dev/null and b/src/main/resources/templates/themes/cherry-bloom/assets/screenshot.png differ
diff --git a/src/main/resources/templates/themes/cherry-bloom/detail.html b/src/main/resources/templates/themes/cherry-bloom/detail.html
new file mode 100644
index 0000000..8eab71f
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/detail.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 文章标题
+
+
+ 作者
+ 来源
+ 浏览量
+
+
+ 正文
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html b/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html
new file mode 100644
index 0000000..1f3f83d
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/fragments/footer.html
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/head.html b/src/main/resources/templates/themes/cherry-bloom/fragments/head.html
new file mode 100644
index 0000000..1809482
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/fragments/head.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ 页面标题
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/fragments/header.html b/src/main/resources/templates/themes/cherry-bloom/fragments/header.html
new file mode 100644
index 0000000..903691c
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/fragments/header.html
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/home.html b/src/main/resources/templates/themes/cherry-bloom/home.html
new file mode 100644
index 0000000..8d4f072
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/home.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 头条推荐
+
+ -
+ 置顶
+ 文章标题
+
+
+
+
+
+
+
+
+
+ -
+ 文章标题
+
+
+
+ 暂无内容
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/list.html b/src/main/resources/templates/themes/cherry-bloom/list.html
new file mode 100644
index 0000000..14c47c4
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/list.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
栏目名
+
搜索词
+
+
+ -
+ 文章标题
+
摘要
+
+
+ 置顶
+
+
+
+
暂无内容
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/single.html b/src/main/resources/templates/themes/cherry-bloom/single.html
new file mode 100644
index 0000000..65292dc
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/single.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 栏目名
+
+ 单页内容
+ 暂无内容
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/cherry-bloom/theme.json b/src/main/resources/templates/themes/cherry-bloom/theme.json
new file mode 100644
index 0000000..5d24a11
--- /dev/null
+++ b/src/main/resources/templates/themes/cherry-bloom/theme.json
@@ -0,0 +1,22 @@
+{
+ "specVersion": 1,
+ "name": "cherry-bloom",
+ "title": "樱语粉韵",
+ "version": "1.0.0",
+ "author": "yunhai",
+ "description": "温柔樱粉风门户:细线顶栏+居中 logo 吸顶、樱花粉品牌色、大圆角 16px 卡片网格列表、花瓣式圆点装饰与暖白留白,甜美亲和,适合幼儿园与小学门户。",
+ "screenshot": "assets/screenshot.png",
+ "bannerSize": "1920x560",
+ "params": [
+ { "key": "primary", "cssVar": "--primary", "label": "主色", "type": "color", "default": "#DB5A8B" },
+ { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深(强调色)", "type": "color", "default": "#B23A68" },
+ { "key": "primaryLight", "cssVar": "--primary-light", "label": "主色提亮", "type": "color", "default": "#FDECF3" },
+ { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#FFFAFB" },
+ { "key": "cardBg", "cssVar": "--card-bg", "label": "卡片底色", "type": "color", "default": "#FFFFFF" },
+ { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#F7DDE7" },
+ { "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#43333B" },
+ { "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#9C828C" },
+ { "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid",
+ "options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] }
+ ]
+}
diff --git a/src/main/resources/templates/themes/violet-iris/README.md b/src/main/resources/templates/themes/violet-iris/README.md
new file mode 100644
index 0000000..747398a
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/README.md
@@ -0,0 +1,35 @@
+# violet-iris 皮肤包
+
+由 mint-breeze 派生的雅致鸢尾紫皮肤(docs/10 契约 §5 工作流)。**升级随版本发布,不在站点间复制目录**。
+
+
+## 风格定位
+
+沉静、书卷气,适合高校与教科研机构门户:鸢尾紫品牌色、圆角 12px、卡片纯白+淡紫柔和阴影、细线顶栏+居中 logo(桌面吸顶,移动端取消)、列表卡片网格(一行 3 张)、几何圆点装饰与大留白;无纹理、无通栏大图、无复杂动效。
+
+## 数据契约(模板可取用的 model 属性全集,契约 §3)
+
+| 页面 | 属性 |
+| --- | --- |
+| 全部 | `site`(快照:config/分类树/轮播/友链组/院系子站)、`url`(PortalUrlHelper:home/category/article/asset/search/siteHome)、`themeBase`(=`themes/violet-iris`)、`navCategories`、`pageTitle`、`seoDescription`、`canonical`、`themeParams`(微调参数,仅 theme.json 声明过的 key)、`themeVarsCss`(服务端预构建 `:root{}` 覆盖串,无参数为空) |
+| home | `banners`(时间窗内 home 位)、`topArticles`(置顶+最新 6)、`categoryBlocks`(LIST 型根栏目块,各 top 8) |
+| list / search | `listData`(分页)、`category`(list)、`q` + `searchMode`(search) |
+| detail | `article`、`prevArticle`、`nextArticle`、`currentCategoryId` |
+| single | `category`(含 `content`)、`currentCategoryId` |
+
+原则:皮肤不得假设"首页一定有轮播",对空集合优雅降级。
+
+## 轮播图尺寸约定(bannerSize)
+
+1920x560(管理端上传端按此引导裁剪,D2 决策点)。
+
+## 可调参数
+
+见 `theme.json` 的 `params`:
+
+- **color**(进 CSS 变量):`primary` 主色 / `primaryDark` 主色加深(即强调色,用于品牌文字与悬停加深)/ `primaryLight` 主色提亮 / `bg` 页面底色 / `cardBg` 卡片底色 / `border` 边框色 / `text` 正文文字 / `textLight` 次要文字。
+- **select**:`listLayout` = `grid`(默认,卡片网格一行 3 张)或 `row`(传统行列表)。
+
+换色建议:主色系三值同步调整(primary / primaryDark / primaryLight),bg 与 border、primaryLight 保持同族浅色调,避免底色与卡片对比失衡。
+
+优先级:`custom_css` > `themeVarsCss` > `main.css`。
diff --git a/src/main/resources/templates/themes/violet-iris/assets/css/main.css b/src/main/resources/templates/themes/violet-iris/assets/css/main.css
new file mode 100644
index 0000000..29966fd
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/assets/css/main.css
@@ -0,0 +1,616 @@
+/* ============================================================
+ * violet-iris 皮肤包样式(mint-breeze 派生,docs/10 皮肤包契约 §5)
+ * 雅致鸢尾紫风:鸢尾紫品牌色 + 圆角 12px + 柔和紫阴影 + 卡片网格列表
+ * + 几何圆点装饰与淡薰衣草留白;无纹理、无通栏大图、无复杂动效。
+ * 轮播图设计尺寸约定:1920×560(管理端上传端按此引导裁剪)。
+ * ============================================================ */
+:root {
+ --primary: #6552D9;
+ --primary-dark: #4A39B0;
+ --primary-light: #EFECFC;
+ --text: #332F45;
+ --text-light: #847E9C;
+ --bg: #F8F7FD;
+ --card-bg: #FFFFFF;
+ --border: #E4DFF6;
+ --radius: 12px;
+ --radius-sm: 8px;
+ --radius-pill: 999px;
+ --shadow-soft: 0 6px 18px rgba(74, 57, 176, .08);
+ --shadow-hover: 0 12px 28px rgba(74, 57, 176, .14);
+ --max-width: 1200px;
+}
+
+* { box-sizing: border-box; }
+body {
+ margin: 0;
+ font-family: "PingFang SC", "Microsoft YaHei", "Helvetica Neue", Arial, sans-serif;
+ color: var(--text);
+ background: var(--bg);
+}
+a { color: inherit; text-decoration: none; transition: color .15s ease; }
+a:hover { color: var(--primary); }
+img { max-width: 100%; }
+time, .item-meta { color: var(--text-light); font-size: 13px; }
+.empty { color: var(--text-light); text-align: center; padding: 32px 0; }
+
+/* ===== 头部:细线顶栏 + 居中品牌行 + 白底导航(桌面吸顶) ===== */
+.site-header {
+ position: sticky;
+ top: 0;
+ z-index: 90;
+ background: var(--card-bg);
+ box-shadow: var(--shadow-soft);
+}
+/* 细线顶栏:院系切换在左、站内搜索在右;院系未配置时搜索仍靠右 */
+.top-bar {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 16px;
+ padding: 7px max(16px, calc((100% - var(--max-width)) / 2 + 16px));
+ border-bottom: 1px solid var(--border);
+ font-size: 13px;
+}
+.top-bar .site-colleges { margin-right: auto; }
+/* 居中品牌行:两侧几何圆点装饰(纯 CSS,无纹理) */
+.brand-row {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ padding: 18px 16px 14px;
+}
+.brand-row::before, .brand-row::after {
+ content: "";
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ width: 46px;
+ height: 10px;
+ opacity: .45;
+ background-image:
+ radial-gradient(circle, var(--primary) 2.2px, transparent 2.8px),
+ radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px),
+ radial-gradient(circle, var(--primary) 1.1px, transparent 1.7px);
+ background-repeat: no-repeat;
+ background-position: left center, center center, right center;
+}
+.brand-row::before { left: 6%; }
+.brand-row::after { right: 6%; }
+.site-brand { display: flex; align-items: center; gap: 14px; }
+.site-logo { height: 52px; }
+.site-name {
+ font-size: 28px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ color: var(--primary-dark);
+}
+.site-name:hover { color: var(--primary); }
+.site-search { display: flex; align-items: center; }
+.site-search input {
+ border: 1px solid var(--border);
+ border-right: none;
+ border-radius: var(--radius-pill) 0 0 var(--radius-pill);
+ padding: 7px 16px;
+ width: 220px;
+ outline: none;
+ background: var(--card-bg);
+ color: var(--text);
+ transition: border-color .15s ease, box-shadow .15s ease;
+}
+.site-search input:focus {
+ border-color: var(--primary);
+ box-shadow: 0 0 0 3px var(--primary-light);
+}
+.site-search button {
+ border: none;
+ background: var(--primary);
+ color: #fff;
+ padding: 7px 20px;
+ border-radius: 0 var(--radius-pill) var(--radius-pill) 0;
+ cursor: pointer;
+ transition: background .15s ease;
+}
+.site-search button:hover { background: var(--primary-dark); }
+
+/* ===== 专业院系下拉(院系子站切换):胶囊开关 + 圆角白卡面板 ===== */
+.site-colleges { position: relative; }
+.site-colleges .colleges-toggle {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ padding: 5px 14px;
+ cursor: pointer;
+ color: var(--primary-dark);
+ font-weight: 600;
+ white-space: nowrap;
+ transition: border-color .15s ease, background .15s ease;
+}
+.site-colleges:hover .colleges-toggle {
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+.site-colleges .colleges-arrow { font-style: normal; font-size: 12px; color: var(--primary); }
+.site-colleges .colleges-menu {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 200px;
+ max-height: 420px;
+ overflow-y: auto;
+ margin: 0;
+ padding: 8px 0;
+ list-style: none;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-hover);
+ z-index: 110;
+}
+.site-colleges:hover .colleges-menu { display: block; }
+.site-colleges .colleges-menu li a {
+ display: block;
+ margin: 0 8px;
+ padding: 9px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text);
+ white-space: nowrap;
+}
+.site-colleges .colleges-menu li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+.site-colleges .colleges-menu li.active a { color: var(--primary); font-weight: 600; }
+
+/* ===== 主导航:白底 + 柔和文字层级,当前项圆点标记 ===== */
+.site-nav { background: var(--card-bg); border-top: 1px solid var(--border); }
+.site-nav ul {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 0 16px;
+ list-style: none;
+ display: flex;
+ justify-content: center;
+ flex-wrap: wrap;
+}
+.site-nav li a {
+ position: relative;
+ display: block;
+ padding: 13px 20px;
+ color: var(--text);
+ font-weight: 500;
+}
+.site-nav li a:hover { color: var(--primary); }
+.site-nav li.active a { color: var(--primary); font-weight: 600; }
+/* 几何圆点:当前栏目下方的小圆点 */
+.site-nav li.active > a::before {
+ content: "";
+ position: absolute;
+ left: 50%;
+ bottom: 5px;
+ width: 5px;
+ height: 5px;
+ transform: translateX(-50%);
+ border-radius: 50%;
+ background: var(--primary);
+}
+
+/* ===== 二级下拉导航:圆角白卡面板 ===== */
+.site-nav > ul > li { position: relative; }
+.site-nav li.has-sub > a::after { content: " ▾"; font-size: 12px; opacity: .7; }
+.site-nav ul.sub-nav {
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ min-width: 168px;
+ max-width: none;
+ margin: 0;
+ padding: 8px 0;
+ list-style: none;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-hover);
+ z-index: 100;
+}
+.site-nav li:hover > .sub-nav { display: block; }
+.site-nav .sub-nav li a {
+ margin: 0 8px;
+ padding: 9px 12px;
+ border-radius: var(--radius-sm);
+ color: var(--text);
+ font-weight: 400;
+ white-space: nowrap;
+ background: none;
+}
+.site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+.site-nav .sub-nav li.active a { color: var(--primary); font-weight: 600; }
+
+/* ===== 首页轮播:容器内圆角卡片(非通栏),几何圆点指示器 ===== */
+.banner {
+ position: relative;
+ width: min(var(--max-width), calc(100% - 32px));
+ margin: 24px auto 0;
+ border-radius: var(--radius);
+ overflow: hidden;
+ box-shadow: var(--shadow-soft);
+}
+.banner-slides { position: relative; height: 400px; }
+.banner-slide { display: none; position: absolute; inset: 0; }
+.banner-slide.active { display: block; }
+.banner-slide img { width: 100%; height: 100%; object-fit: cover; }
+.banner-title {
+ position: absolute;
+ left: 0; right: 0; bottom: 0;
+ padding: 14px 20px;
+ color: #fff;
+ font-weight: 500;
+ background: linear-gradient(transparent, rgba(20, 63, 61, .62));
+}
+.banner-dots {
+ position: absolute;
+ bottom: 14px; right: 20px;
+ display: flex; gap: 8px;
+}
+.banner-dots button {
+ width: 9px; height: 9px;
+ border-radius: 50%;
+ border: none;
+ background: rgba(255, 255, 255, .55);
+ cursor: pointer;
+ transition: background .15s ease, transform .15s ease;
+}
+.banner-dots button.active { background: #fff; transform: scale(1.25); }
+
+/* ===== 首页区块:卡片网格(头条通栏 + 栏目块一行 3 张) ===== */
+.home-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 24px;
+}
+.top-strip, .category-block {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 24px;
+}
+.top-strip { grid-column: 1 / -1; }
+/* 栏目标题:几何圆点标记(环形点),标题中等粗细、层级柔和 */
+.section-title, .block-head h2 {
+ margin: 0 0 14px;
+ font-size: 19px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.section-title::before, .block-head h2::before {
+ content: "";
+ flex: none;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 3.5px var(--primary-light);
+}
+.block-head { display: flex; align-items: center; justify-content: space-between; }
+.more {
+ flex: none;
+ font-size: 13px;
+ color: var(--text-light);
+ padding: 4px 12px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+}
+.more:hover {
+ color: var(--primary-dark);
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+.top-strip ul, .block-list { list-style: none; margin: 0; padding: 0; }
+.top-strip li, .block-list li {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 12px;
+ padding: 10px 0;
+ border-bottom: 1px dashed var(--border);
+}
+.top-strip li:last-child, .block-list li:last-child { border-bottom: none; }
+.top-strip li a, .block-list li a {
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.top-badge {
+ flex: none;
+ color: var(--primary-dark);
+ background: var(--primary-light);
+ font-size: 12px;
+ font-weight: 500;
+ padding: 2px 10px;
+ border-radius: var(--radius-pill);
+}
+
+/* ===== 列表页:卡片网格(默认一行 3 张;listLayout=row 切换行列表) ===== */
+.list-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+}
+.breadcrumb { color: var(--text-light); font-size: 13px; margin-bottom: 14px; }
+.breadcrumb a { margin-right: 8px; }
+.breadcrumb span { margin-left: 8px; }
+.page-title {
+ font-size: 24px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ margin: 0 0 22px;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+}
+.page-title::before {
+ content: "";
+ flex: none;
+ width: 9px;
+ height: 9px;
+ border-radius: 50%;
+ background: var(--primary);
+ box-shadow: 0 0 0 3.5px var(--primary-light);
+}
+.article-list {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 20px;
+}
+.article-list li {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 22px 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ transition: transform .18s ease, box-shadow .18s ease;
+}
+.article-list li:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
+.item-title {
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 1.55;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+.article-list li:hover .item-title { color: var(--primary); }
+.item-summary {
+ color: var(--text-light);
+ font-size: 13.5px;
+ line-height: 1.7;
+ margin: 0;
+ flex: 1;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+}
+.item-meta {
+ display: flex;
+ gap: 10px;
+ align-items: center;
+ margin-top: auto;
+ padding-top: 10px;
+ border-top: 1px dashed var(--border);
+}
+/* 布局变体:传统行列表(themeParams listLayout=row,见 list.html classappend) */
+.list-rows .article-list { display: block; }
+.list-rows .article-list li { display: block; margin-bottom: 16px; }
+.list-rows .item-summary { -webkit-line-clamp: 2; flex: none; }
+.list-rows .item-meta { border-top: none; padding-top: 0; margin-top: 6px; }
+
+/* ===== 分页:胶囊页码 ===== */
+.pagination {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ flex-wrap: wrap;
+ padding: 28px 0 4px;
+}
+.pagination a {
+ min-width: 36px;
+ text-align: center;
+ padding: 6px 12px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-pill);
+ background: var(--card-bg);
+ font-size: 14px;
+ transition: color .15s ease, border-color .15s ease, background .15s ease;
+}
+.pagination a:hover { color: var(--primary-dark); border-color: var(--primary); }
+.pagination a.active { background: var(--primary); color: #fff; border-color: var(--primary); }
+
+/* ===== 详情/单页:整卡承载,正文舒适行距 ===== */
+.detail-main {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 28px 16px 56px;
+}
+.detail-main .article {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 36px 40px;
+}
+.article-title {
+ margin: 4px 0 14px;
+ font-size: 26px;
+ font-weight: 600;
+ letter-spacing: .02em;
+ text-align: center;
+}
+.article-meta {
+ display: flex;
+ gap: 18px;
+ justify-content: center;
+ flex-wrap: wrap;
+ color: var(--text-light);
+ font-size: 13px;
+ padding-bottom: 18px;
+ border-bottom: 1px solid var(--border);
+}
+.article-content { line-height: 1.9; font-size: 16px; padding-top: 22px; word-break: break-word; }
+.article-content img { height: auto; border-radius: var(--radius-sm); }
+.article-content table { border-collapse: collapse; width: 100%; }
+.article-content table td, .article-content table th { border: 1px solid var(--border); padding: 6px 10px; }
+.article-sibling { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 20px; font-size: 14px; }
+.article-sibling a {
+ flex: 1 1 320px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-soft);
+ padding: 14px 18px;
+ transition: transform .18s ease, box-shadow .18s ease, color .15s ease;
+}
+.article-sibling a:hover {
+ color: var(--primary-dark);
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-hover);
+}
+
+/* ===== 页脚:浅鸢尾底 + 圆点分隔条(三态渲染逻辑不动) ===== */
+.site-footer {
+ margin-top: 40px;
+ background: var(--primary-light);
+ color: var(--text);
+ border-top: 1px solid var(--border);
+}
+.site-footer::before {
+ content: "";
+ display: block;
+ height: 8px;
+ width: min(var(--max-width), calc(100% - 32px));
+ margin: 18px auto 0;
+ background-image: radial-gradient(circle, var(--primary) 1.6px, transparent 2.2px);
+ background-size: 16px 8px;
+ background-repeat: repeat-x;
+ opacity: .4;
+}
+.footer-links {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 18px 16px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+.links-group { display: flex; flex-wrap: wrap; gap: 6px 14px; align-items: center; font-size: 13px; }
+.group-name { font-weight: 600; margin-right: 4px; color: var(--primary-dark); }
+.links-group a {
+ padding: 3px 10px;
+ border-radius: var(--radius-pill);
+ transition: background .15s ease, color .15s ease;
+}
+.links-group a:hover { background: rgba(255, 255, 255, .75); color: var(--primary-dark); }
+.footer-info {
+ max-width: var(--max-width);
+ margin: 0 auto;
+ padding: 14px 16px 24px;
+ text-align: center;
+ font-size: 13px;
+ color: var(--text-light);
+}
+.footer-info p { margin: 4px 0; }
+
+/* B4 友链 Logo:与名称并排小图(高度 ~20px,垂直居中;过宽截断不撑破页脚) */
+.links-group a .link-logo {
+ height: 20px;
+ width: auto;
+ max-width: 64px;
+ object-fit: contain;
+ vertical-align: middle;
+ margin-right: 4px;
+ border-radius: 4px;
+}
+
+/* ===== 平板(≤992px):网格降为两列,隐藏品牌行圆点装饰 ===== */
+@media (max-width: 992px) {
+ .home-main { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .article-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
+ .brand-row::before, .brand-row::after { display: none; }
+}
+
+/* ===== 移动端(≤768px):取消吸顶,网格单列,导航横向滑动 ===== */
+@media (max-width: 768px) {
+ .site-header { position: static; }
+ .top-bar { padding: 8px 14px; justify-content: flex-start; flex-wrap: wrap; }
+ .top-bar .site-colleges { margin-right: 0; }
+ .site-search { flex: 1; }
+ .site-search input { flex: 1; width: auto; min-width: 0; }
+ .brand-row { padding: 14px 16px 10px; }
+ .site-logo { height: 40px; }
+ .site-name { font-size: 22px; }
+ .site-nav ul {
+ justify-content: flex-start;
+ flex-wrap: nowrap;
+ overflow-x: auto;
+ -webkit-overflow-scrolling: touch;
+ }
+ .banner-slides { height: 200px; }
+ .home-main { grid-template-columns: 1fr; padding: 18px 14px 40px; }
+ .article-list { grid-template-columns: 1fr; }
+ .detail-main .article { padding: 22px 16px; }
+ .article-title { font-size: 21px; }
+ /* 触屏无 hover:子菜单静态平铺(缩进列表),不做浮层 */
+ .site-nav ul.sub-nav {
+ display: block;
+ position: static;
+ border: none;
+ box-shadow: none;
+ background: transparent;
+ min-width: 0;
+ padding: 0 0 6px;
+ }
+ .site-nav .sub-nav li a { margin: 0; padding: 8px 16px 8px 34px; border-radius: 0; color: var(--text-light); }
+ .site-nav .sub-nav li a:hover { background: var(--primary-light); color: var(--primary-dark); }
+}
+
+
+/* ===== 左侧栏目导航(docs/11):有二级节点的模块,栏目/文章页左侧展示兄弟目录 ===== */
+.list-main.has-sidenav, .detail-main.has-sidenav { display: flex; align-items: flex-start; gap: 24px; }
+.has-sidenav .list-body { flex: 1; min-width: 0; }
+.side-nav { flex: none; width: 220px; background: var(--card-bg, #fff); border: 1px solid var(--border); overflow: hidden; }
+.side-nav-head { background: var(--primary); color: #fff; font-size: 20px; font-weight: 700; text-align: center; padding: 18px 12px; letter-spacing: 2px; }
+.side-nav-list { list-style: none; margin: 0; padding: 0; }
+.side-nav-item + .side-nav-item { border-top: 1px dashed var(--border); }
+.side-nav-item a { display: block; padding: 12px 16px; color: var(--text); text-decoration: none; font-size: 15px; transition: background .15s ease, color .15s ease; }
+.side-nav-item a:hover { color: var(--primary); background: var(--bg); }
+.side-nav-item.active a { background: var(--primary); color: #fff; font-weight: 600; text-decoration: none; }
+/* 顶导航二级下拉当前项高亮(docs/11 顺带) */
+.site-nav ul.sub-nav li.active a { color: var(--primary); }
+@media (max-width: 768px) {
+ .list-main.has-sidenav, .detail-main.has-sidenav { flex-direction: column; gap: 12px; }
+ .side-nav { width: 100%; }
+ .side-nav-list { display: flex; overflow-x: auto; -webkit-overflow-scrolling: touch; }
+ .side-nav-item { flex: none; }
+ .side-nav-item + .side-nav-item { border-top: none; border-left: 1px dashed var(--border); }
+ .side-nav-item a { white-space: nowrap; }
+}
+@media print {
+ .side-nav { display: none; }
+ .list-main.has-sidenav, .detail-main.has-sidenav { display: block; }
+}
diff --git a/src/main/resources/templates/themes/violet-iris/assets/favicon.png b/src/main/resources/templates/themes/violet-iris/assets/favicon.png
new file mode 100644
index 0000000..497cae1
Binary files /dev/null and b/src/main/resources/templates/themes/violet-iris/assets/favicon.png differ
diff --git a/src/main/resources/templates/themes/violet-iris/assets/favicon.svg b/src/main/resources/templates/themes/violet-iris/assets/favicon.svg
new file mode 100644
index 0000000..eed749f
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/assets/favicon.svg
@@ -0,0 +1,8 @@
+
diff --git a/src/main/resources/templates/themes/violet-iris/assets/js/main.js b/src/main/resources/templates/themes/violet-iris/assets/js/main.js
new file mode 100644
index 0000000..8261c6b
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/assets/js/main.js
@@ -0,0 +1,40 @@
+/* violet-iris 皮肤包脚本:仅首页轮播自动播放与圆点切换,无任何外部依赖 */
+(function () {
+ var slides = document.querySelectorAll('.banner-slide');
+ var dots = document.querySelectorAll('.banner-dots button');
+ if (!slides.length) {
+ return;
+ }
+ var current = 0;
+ var timer = null;
+
+ function show(index) {
+ slides[current].classList.remove('active');
+ if (dots[current]) {
+ dots[current].classList.remove('active');
+ }
+ current = (index + slides.length) % slides.length;
+ slides[current].classList.add('active');
+ if (dots[current]) {
+ dots[current].classList.add('active');
+ }
+ }
+
+ function start() {
+ if (slides.length > 1) {
+ timer = window.setInterval(function () {
+ show(current + 1);
+ }, 5000);
+ }
+ }
+
+ dots.forEach(function (dot, index) {
+ dot.addEventListener('click', function () {
+ window.clearInterval(timer);
+ show(index);
+ start();
+ });
+ });
+
+ start();
+})();
diff --git a/src/main/resources/templates/themes/violet-iris/assets/screenshot.png b/src/main/resources/templates/themes/violet-iris/assets/screenshot.png
new file mode 100644
index 0000000..b6a7d0c
Binary files /dev/null and b/src/main/resources/templates/themes/violet-iris/assets/screenshot.png differ
diff --git a/src/main/resources/templates/themes/violet-iris/detail.html b/src/main/resources/templates/themes/violet-iris/detail.html
new file mode 100644
index 0000000..8eab71f
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/detail.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 文章标题
+
+
+ 作者
+ 来源
+ 浏览量
+
+
+ 正文
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/fragments/footer.html b/src/main/resources/templates/themes/violet-iris/fragments/footer.html
new file mode 100644
index 0000000..1f3f83d
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/fragments/footer.html
@@ -0,0 +1,21 @@
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/fragments/head.html b/src/main/resources/templates/themes/violet-iris/fragments/head.html
new file mode 100644
index 0000000..1809482
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/fragments/head.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ 页面标题
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/fragments/header.html b/src/main/resources/templates/themes/violet-iris/fragments/header.html
new file mode 100644
index 0000000..462364e
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/fragments/header.html
@@ -0,0 +1,56 @@
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/home.html b/src/main/resources/templates/themes/violet-iris/home.html
new file mode 100644
index 0000000..8d4f072
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/home.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 头条推荐
+
+ -
+ 置顶
+ 文章标题
+
+
+
+
+
+
+
+
+
+ -
+ 文章标题
+
+
+
+ 暂无内容
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/list.html b/src/main/resources/templates/themes/violet-iris/list.html
new file mode 100644
index 0000000..14c47c4
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/list.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
栏目名
+
搜索词
+
+
+ -
+ 文章标题
+
摘要
+
+
+ 置顶
+
+
+
+
暂无内容
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/single.html b/src/main/resources/templates/themes/violet-iris/single.html
new file mode 100644
index 0000000..65292dc
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/single.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 栏目名
+
+ 单页内容
+ 暂无内容
+
+
+
+
+
+
+
diff --git a/src/main/resources/templates/themes/violet-iris/theme.json b/src/main/resources/templates/themes/violet-iris/theme.json
new file mode 100644
index 0000000..607917f
--- /dev/null
+++ b/src/main/resources/templates/themes/violet-iris/theme.json
@@ -0,0 +1,22 @@
+{
+ "specVersion": 1,
+ "name": "violet-iris",
+ "title": "鸢尾紫韵",
+ "version": "1.0.0",
+ "author": "yunhai",
+ "description": "雅致鸢尾紫门户:细线顶栏+居中 logo 吸顶、鸢尾紫品牌色、圆角 12px 卡片网格列表、几何圆点装饰与淡薰衣草留白,沉静书卷气,适合高校与教科研机构。",
+ "screenshot": "assets/screenshot.png",
+ "bannerSize": "1920x560",
+ "params": [
+ { "key": "primary", "cssVar": "--primary", "label": "主色", "type": "color", "default": "#6552D9" },
+ { "key": "primaryDark", "cssVar": "--primary-dark", "label": "主色加深(强调色)", "type": "color", "default": "#4A39B0" },
+ { "key": "primaryLight", "cssVar": "--primary-light", "label": "主色提亮", "type": "color", "default": "#EFECFC" },
+ { "key": "bg", "cssVar": "--bg", "label": "页面底色", "type": "color", "default": "#F8F7FD" },
+ { "key": "cardBg", "cssVar": "--card-bg", "label": "卡片底色", "type": "color", "default": "#FFFFFF" },
+ { "key": "border", "cssVar": "--border", "label": "边框色", "type": "color", "default": "#E4DFF6" },
+ { "key": "text", "cssVar": "--text", "label": "正文文字色", "type": "color", "default": "#332F45" },
+ { "key": "textLight", "cssVar": "--text-light", "label": "次要文字色", "type": "color", "default": "#847E9C" },
+ { "key": "listLayout", "label": "列表布局", "type": "select", "default": "grid",
+ "options": [ { "value": "grid", "label": "卡片网格(一行 3 张)" }, { "value": "row", "label": "传统行列表" } ] }
+ ]
+}
diff --git a/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java b/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java
index 0c97266..c76c5f6 100644
--- a/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java
+++ b/src/test/java/com/yunhai/cms/theme/ThemeRegistryTest.java
@@ -104,6 +104,24 @@ class ThemeRegistryTest {
}
}
+ @Test
+ @DisplayName("新增包: cherry-bloom/violet-iris 注册成功,primary 与 default 及彼此互异(拷贝忘改哨兵)")
+ void cherryBloomAndVioletIrisRegisteredWithDistinctPrimary() {
+ String defaultPrimary = registry.get(ThemeRegistry.DEFAULT_THEME).param("primary").getDefaultValue();
+ java.util.Map seen = new java.util.LinkedHashMap<>();
+ for (String name : new String[] {"cherry-bloom", "violet-iris"}) {
+ ThemeInfo info = registry.get(name);
+ assertThat(info).as("%s 皮肤包必须注册(清单+必备模板+截图齐全)", name).isNotNull();
+ assertThat(info.getTitle()).isNotBlank();
+ ThemeParam primary = info.param("primary");
+ assertThat(primary).as("%s 应声明 primary 参数", name).isNotNull();
+ assertThat(primary.getDefaultValue())
+ .as("%s 的 primary 不得与 default 相同", name).isNotEqualTo(defaultPrimary);
+ seen.put(name, primary.getDefaultValue());
+ }
+ assertThat(seen.get("cherry-bloom")).isNotEqualTo(seen.get("violet-iris"));
+ }
+
@Test
@DisplayName("default 包缺失时 fail-fast(方案 §5.1,启动失败防线)")
void defaultMissingFailsFast() {