-

-
@ DOMAIN HUNTER
+

+
{settings?.copyright_text ?? "© WanMi · 玩米"}
diff --git a/src/client/styles/admin.css b/src/client/styles/admin.css
index 46037c1..371ac46 100644
--- a/src/client/styles/admin.css
+++ b/src/client/styles/admin.css
@@ -37,23 +37,37 @@ svg.lucide {
.keywords-cell { min-width: 220px; max-width: 320px; }
.keywords-cell > div { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }
+/* 登录:深墨绿场景 + 左右分栏白卡(手机单栏,品牌介绍压缩置顶) */
.login-shell {
min-height: 100vh; display: grid; place-items: center; padding: 24px;
background:
- radial-gradient(circle at 50% 8%, color-mix(in oklab, var(--brand) 12%, transparent), transparent 42%),
- radial-gradient(circle at 12% 88%, color-mix(in oklab, var(--brand) 6%, transparent), transparent 40%),
- var(--surface-1);
+ radial-gradient(circle at 82% 12%, rgba(200, 152, 72, 0.10), transparent 34%),
+ radial-gradient(circle at 8% 92%, rgba(30, 107, 89, 0.22), transparent 46%),
+ linear-gradient(165deg, var(--brand-primary), var(--brand-primary-deep));
}
.login-card {
- width: min(420px, 100%); padding: 36px;
- background: color-mix(in oklab, var(--surface-2) 92%, transparent);
- backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
- border: 1px solid var(--border); border-radius: var(--radius-xl);
- box-shadow: var(--shadow-l);
+ width: min(880px, 100%); padding: 0; overflow: hidden;
+ display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
+ background: var(--surface-elevated);
+ border: 1px solid rgba(255, 255, 255, 0.14); border-radius: var(--radius-xl);
+ box-shadow: var(--shadow-modal);
animation: pop-in var(--dur-3) var(--ease);
}
-.login-brand { margin-bottom: 38px; }
-.login-heading > span { color: var(--brand); font-size: 10px; letter-spacing: .16em; font-weight: 700; }
+.login-intro {
+ display: flex; flex-direction: column; gap: 26px; padding: 38px 34px;
+ color: rgba(255, 255, 255, 0.86);
+ background:
+ radial-gradient(circle at 90% 0%, rgba(200, 152, 72, 0.14), transparent 40%),
+ linear-gradient(180deg, var(--brand-primary), var(--brand-primary-deep));
+}
+.login-intro .brand { color: #ffffff; }
+.login-intro-body { margin-top: auto; }
+.login-intro-body h2 { margin: 0 0 10px; font-size: 24px; letter-spacing: -.02em; color: #ffffff; }
+.login-intro-body p { margin: 0; font-size: 13px; line-height: 1.7; color: rgba(255, 255, 255, 0.66); }
+.login-intro-foot { margin-top: auto; color: var(--gold-bright); font-family: var(--font-mono); font-size: 10px; letter-spacing: .14em; }
+.login-form-side { padding: 38px 36px; }
+.login-brand { margin-bottom: 0; }
+.login-heading > span { color: var(--brand-primary-hover); font-size: 10px; letter-spacing: .16em; font-weight: 700; }
.login-heading h1 { font-size: 30px; letter-spacing: -.04em; margin: 9px 0 4px; }
.login-heading p { margin: 0 0 25px; color: var(--muted); font-size: 13px; }
.login-card form { display: grid; gap: 15px; }
@@ -91,7 +105,9 @@ svg.lucide {
}
.admin-sidebar nav button > svg { width: 18px; height: 18px; flex: 0 0 18px; text-align: center; font-size: 15px; }
.admin-sidebar nav button:hover { background: color-mix(in oklab, oklch(100% 0 0) 6%, transparent); color: oklch(92% .008 85); }
-.admin-sidebar nav button.active { background: linear-gradient(180deg, var(--sidebar-active-a), var(--sidebar-active-b)); color: var(--sidebar-active-fg); font-weight: 650; box-shadow: 0 6px 16px color-mix(in oklab, oklch(0% 0 0) 28%, transparent); }
+.admin-sidebar nav button.active { position: relative; background: var(--sidebar-active-bg); color: var(--sidebar-active-fg); font-weight: 650; box-shadow: none; }
+.admin-sidebar nav button.active::before { content: ""; position: absolute; inset: 9px auto 9px 0; width: 3px; background: var(--sidebar-active-mark); border-radius: 0 2px 2px 0; }
+.admin-sidebar nav button.active > svg { color: var(--gold-bright); }
.sidebar-user { margin-top: auto; border-top: 1px solid color-mix(in oklab, oklch(100% 0 0) 10%, transparent); padding: 14px 4px 0; position: relative; min-width: 0; }
.sidebar-user summary { list-style: none; display: grid; grid-template-columns: 32px minmax(0, 1fr) 16px; align-items: center; gap: 8px; cursor: pointer; }
.sidebar-user summary::-webkit-details-marker { display: none; }
@@ -99,8 +115,8 @@ svg.lucide {
.sidebar-user summary strong { overflow: hidden; text-overflow: ellipsis; font-size: 11px; }
.sidebar-user summary small { color: var(--sidebar-muted); font-size: 10px; }
.user-avatar { width: 32px; height: 32px; border-radius: 50%; display: grid; place-items: center; background: var(--brand); color: white; font-weight: 800; }
-.user-menu { position: absolute; bottom: calc(100% + 8px); inset-inline: 0; padding: 6px; border-radius: 10px; background: var(--sidebar-active-a); box-shadow: var(--shadow); display: grid; gap: 3px; }
-.user-menu button { display: flex; align-items: center; gap: 8px; padding: 9px; color: var(--sidebar-active-fg); }
+.user-menu { position: absolute; bottom: calc(100% + 8px); inset-inline: 0; padding: 6px; border-radius: 10px; background: color-mix(in oklab, var(--sidebar-bg-b) 86%, #ffffff); box-shadow: var(--shadow-panel); display: grid; gap: 3px; }
+.user-menu button { display: flex; align-items: center; gap: 8px; padding: 9px; color: rgba(255, 255, 255, 0.85); border-radius: 7px; }
.user-menu svg { width: 15px; height: 15px; }
.sidebar-user > div { min-width: 0; flex: 1; display: grid; gap: 2px; }
.sidebar-user strong { overflow: hidden; text-overflow: ellipsis; font-size: 10px; font-weight: 600; }
@@ -310,6 +326,15 @@ svg.lucide {
.stats-chart { min-width: 0; }
@media (max-width: 760px) { .stats-overview { grid-template-columns: 1fr; } .stats-kpis { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 720px) { .reminder-editor { grid-template-columns: 1fr; } }
+@media (max-width: 720px) {
+ .login-shell { padding: 16px; }
+ .login-card { grid-template-columns: 1fr; }
+ .login-intro { flex-direction: column; gap: 14px; padding: 22px 22px 18px; }
+ .login-intro-body { margin-top: 0; }
+ .login-intro-body h2 { font-size: 19px; }
+ .login-intro-foot { display: none; }
+ .login-form-side { padding: 24px 20px 28px; }
+}
.channel-card { min-height: 58px; display: grid; grid-template-columns: 120px minmax(180px, 1fr) auto; align-items: center; gap: 10px; padding: 9px 12px; border: 1px solid var(--border); border-radius: var(--radius-m); transition: border-color var(--dur-1) var(--ease); }
.channel-card:hover { border-color: var(--border-strong); }
.channel-card label { font-size: 11px; font-weight: 600; }
@@ -484,7 +509,7 @@ svg.lucide {
content: ""; position: absolute; inset: 0; pointer-events: none;
background: radial-gradient(120% 90% at 100% 0%, rgba(255, 255, 255, 0.55), transparent 55%);
}
-.stat-card.tone-c::after { background: radial-gradient(120% 90% at 100% 0%, rgba(212, 175, 55, 0.18), transparent 55%); }
+.stat-card.tone-c::after { background: radial-gradient(120% 90% at 100% 0%, rgba(200, 152, 72, 0.18), transparent 55%); }
.stat-card span { color: var(--text-secondary); font-weight: 600; }
.stat-card strong { color: var(--text-primary); }
.stat-card.tone-c strong { color: var(--gold-text); }
@@ -496,10 +521,10 @@ svg.lucide {
padding: 14px 18px; border: 1px solid var(--border); border-radius: var(--radius-md);
background: linear-gradient(175deg, rgba(255, 255, 255, 0.55), rgba(255, 255, 255, 0.20)), var(--surface);
color: var(--text-primary); font-size: 13.5px; font-weight: 700; cursor: pointer;
- box-shadow: var(--glass-highlight), 0 2px 8px rgba(90, 62, 43, 0.14);
+ box-shadow: var(--glass-highlight), 0 2px 8px rgba(17, 38, 30, 0.10);
transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
-.quick-actions button:hover { border-color: var(--brand-border); background: linear-gradient(175deg, rgba(90, 62, 43, 0.07), rgba(90, 62, 43, 0.02)), var(--surface); }
+.quick-actions button:hover { border-color: var(--brand-border); background: linear-gradient(175deg, rgba(19, 52, 41, 0.07), rgba(19, 52, 41, 0.02)), var(--surface); }
.quick-actions button:active { transform: scale(.98); }
.quick-actions button svg { width: 17px; height: 17px; color: var(--brand); }
@@ -516,7 +541,7 @@ svg.lucide {
/* 分类分布静态行:结构与后缀分布一致但不可点击 */
.distribution-static { display: grid; grid-template-columns: minmax(64px, auto) 1fr auto; gap: 10px; align-items: center; }
.distribution-static span { font-size: 12.5px; color: var(--text-secondary); }
-.distribution-static .bar { height: 7px; border-radius: 999px; background: rgba(46, 30, 19, 0.10); overflow: hidden; }
+.distribution-static .bar { height: 7px; border-radius: 999px; background: rgba(31, 42, 36, 0.10); overflow: hidden; }
.distribution-static .bar i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--brand-primary-light), var(--brand-primary)); }
.distribution-static strong { font-size: 12.5px; font-variant-numeric: tabular-nums; }
diff --git a/src/client/styles/app.css b/src/client/styles/app.css
index 8ccc915..b9cdac4 100644
--- a/src/client/styles/app.css
+++ b/src/client/styles/app.css
@@ -13,9 +13,11 @@
============================================================ */
:root {
- /* 品牌与状态 */
+ /* 品牌与状态:品牌绿承担主按钮/激活态/链接;香槟金仅表达精品资产。
+ --brand 可由站点设置 accent_color 动态覆盖(applyAccentColor 整套派生)。 */
--brand: var(--brand-primary);
- --brand-strong: var(--brand-primary-deep);
+ --brand-strong: var(--brand-primary-strong);
+ --brand-hover: var(--brand-primary-hover);
--premium: var(--gold);
--premium-fg: var(--gold-text);
@@ -30,18 +32,20 @@
--muted: var(--text-secondary);
--faint: var(--text-tertiary);
- /* 深棕皮革侧边栏(拟物参考图:深棕侧栏 + 米白内容区 + 金色高亮当前项) */
- --sidebar-bg-a: var(--brand-primary);
- --sidebar-bg-b: var(--brand-primary-ink);
- --sidebar-fg: color-mix(in oklab, var(--text-inverse) 82%, transparent);
- --sidebar-active-a: var(--gold-bright);
- --sidebar-active-b: var(--gold);
- --sidebar-active-fg: var(--brand-primary-ink);
+ /* 深墨绿侧边栏:从 --brand 派生,accent_color 覆盖时整体跟随;
+ active 为半透明浅底 + 白字 + 左侧香槟金短条(不做整块金渐变) */
+ --sidebar-bg-a: var(--brand);
+ --sidebar-bg-b: color-mix(in oklab, var(--brand) 72%, #04120d);
+ --sidebar-fg: rgba(255, 255, 255, 0.70);
+ --sidebar-muted: rgba(255, 255, 255, 0.44);
+ --sidebar-hover-bg: rgba(255, 255, 255, 0.06);
+ --sidebar-active-bg: rgba(255, 255, 255, 0.10);
+ --sidebar-active-fg: #ffffff;
+ --sidebar-active-mark: var(--gold);
/* 派生 */
--brand-soft: var(--brand-bg);
- --brand-ring: var(--brand-border);
- --shadow-color: var(--background);
+ --shadow-color: #11261e;
--shadow-s: var(--shadow-card);
--shadow-m: var(--shadow-card);
--shadow-l: var(--shadow-card-hover);
@@ -65,7 +69,7 @@
font-family: var(--font-sans);
color: var(--fg);
background: var(--bg);
- /* color-scheme 由 tokens.css 声明:单一深色主题 */
+ /* color-scheme 由 tokens.css 声明:单一浅色主题 */
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
@@ -252,8 +256,8 @@ svg.lucide {
display: flex; flex-direction: column; gap: 14px;
box-shadow:
var(--glass-highlight),
- 0 1px 2px rgba(0, 0, 0, 0.25),
- 0 4px 14px rgba(0, 0, 0, 0.22);
+ 0 1px 2px rgba(17, 38, 30, 0.10),
+ 0 4px 14px rgba(17, 38, 30, 0.09);
transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
animation: card-in var(--dur-3) var(--ease) backwards;
}
@@ -267,7 +271,7 @@ svg.lucide {
.domain-card.featured {
background: linear-gradient(160deg, color-mix(in oklab, var(--premium) 14%, var(--surface-2)), color-mix(in oklab, var(--premium) 5%, var(--surface-2)));
border-color: color-mix(in oklab, var(--premium) 45%, var(--border));
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.60), 0 2px 6px rgba(90, 62, 43, 0.16), 0 8px 24px color-mix(in oklab, var(--premium) 16%, transparent);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.60), 0 2px 6px rgba(184, 128, 56, 0.14), 0 8px 24px color-mix(in oklab, var(--premium) 16%, transparent);
}
.domain-card.featured::before { opacity: 1; background: linear-gradient(90deg, color-mix(in oklab, var(--premium) 30%, transparent), var(--premium), color-mix(in oklab, var(--premium) 30%, transparent)); }
.domain-featured-badge {
@@ -326,7 +330,7 @@ svg.lucide {
font-size: 12px; font-weight: 600; letter-spacing: .02em;
color: #ffffff; background: linear-gradient(135deg, var(--brand-primary-light), var(--brand-primary));
text-decoration: none; align-self: flex-start;
- box-shadow: 0 2px 8px rgba(90, 62, 43, 0.30);
+ box-shadow: 0 2px 8px color-mix(in oklab, var(--brand) 25%, transparent);
transition: transform var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}
.domain-visit:active { transform: scale(.97); }
@@ -912,8 +916,8 @@ svg.lucide {
.public-footer > div { display: flex; align-items: center; gap: 16px; }
.public-footer strong { color: var(--fg); font-size: 14px; }
.public-footer button { display: inline-flex; align-items: center; gap: 8px; min-height: 40px; border: 0; color: var(--fg-2); background: transparent; }
-.modal-backdrop { background: rgba(12, 14, 17, .48); backdrop-filter: blur(3px); }
-.contact-modal { border-radius: 12px; box-shadow: 0 24px 64px rgba(12, 14, 17, .22); }
+.modal-backdrop { background: rgba(15, 45, 36, 0.42); backdrop-filter: blur(3px); }
+.contact-modal { border-radius: 12px; box-shadow: var(--shadow-modal); }
.contact-modal h2 { margin-top: 0; }
@media (max-width: 1120px) {
@@ -988,7 +992,7 @@ svg.lucide {
.public-shell .public-header {
border-bottom: 0;
background: color-mix(in oklab, var(--surface-2) 88%, transparent);
- box-shadow: 0 1px 0 color-mix(in oklab, var(--border) 54%, transparent), 0 16px 42px -34px rgba(0, 0, 0, .40);
+ box-shadow: 0 1px 0 color-mix(in oklab, var(--border) 54%, transparent), 0 16px 42px -34px rgba(17, 38, 30, 0.34);
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
@@ -997,7 +1001,7 @@ svg.lucide {
max-width: none;
border-color: color-mix(in oklab, var(--border-strong) 68%, transparent);
border-radius: 15px;
- box-shadow: 0 12px 32px -28px rgba(0, 0, 0, .45);
+ box-shadow: 0 12px 32px -28px rgba(17, 38, 30, 0.42);
overflow: hidden;
}
.public-shell .filter-search .search-submit { border-radius: 0 14px 14px 0; }
@@ -1009,7 +1013,7 @@ svg.lucide {
border-radius: 20px;
padding: 13px 15px;
background: color-mix(in oklab, var(--surface-1) 60%, var(--surface-2));
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 46%, transparent), 0 16px 36px -34px rgba(0, 0, 0, .40);
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 46%, transparent), 0 16px 36px -34px rgba(17, 38, 30, 0.36);
}
.public-shell .catalogue-toolbar .filter-search { grid-column: 1 / -1; }
.public-shell .catalogue-toolbar .toolbar-filters { grid-column: 1; }
@@ -1020,14 +1024,14 @@ svg.lucide {
.public-shell .domain-list.card-view .domain-card {
border-color: transparent;
border-radius: 19px;
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 58%, transparent), 0 14px 36px -31px rgba(0, 0, 0, .42);
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 58%, transparent), 0 14px 36px -31px rgba(17, 38, 30, 0.40);
transition: transform 190ms var(--ease), box-shadow 190ms var(--ease), background 190ms var(--ease);
}
.public-shell .domain-list.card-view .domain-card:hover {
transform: translateY(-3px);
border-color: transparent;
background: color-mix(in oklab, var(--brand-soft) 34%, var(--surface-2));
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand) 30%, transparent), 0 22px 45px -31px rgba(0, 0, 0, .46);
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--brand) 30%, transparent), 0 22px 45px -31px rgba(17, 38, 30, 0.40);
}
.public-shell .domain-list.card-view .domain-card.featured {
border: 0;
@@ -1042,7 +1046,7 @@ svg.lucide {
border: 0;
border-radius: 26px;
background: color-mix(in oklab, var(--surface-1) 62%, var(--surface-2));
- box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 42%, transparent), 0 22px 52px -44px rgba(0, 0, 0, .40);
+ box-shadow: inset 0 0 0 1px color-mix(in oklab, var(--border) 42%, transparent), 0 22px 52px -44px rgba(17, 38, 30, 0.34);
}
@media (max-width: 780px) {
@@ -1210,7 +1214,7 @@ svg.lucide {
.domain-detail-dialog {
width: min(760px, calc(100vw - 32px)); max-height: min(860px, calc(100vh - 40px)); padding: 0;
border: 1px solid color-mix(in oklab, var(--gold) 24%, var(--border)); border-radius: 24px;
- color: var(--fg); background: var(--surface-elevated); box-shadow: 0 36px 100px rgba(0, 0, 0, .36); overflow-x: hidden; overflow-y: auto;
+ color: var(--fg); background: var(--surface-elevated); box-shadow: var(--shadow-modal); overflow-x: hidden; overflow-y: auto;
overscroll-behavior: contain;
}
/* 模态从中心轻微放大进入(modal 不做 origin-aware,保持居中);从 scale(0) 出现违背物理直觉 */
@@ -1224,7 +1228,7 @@ svg.lucide {
transition: opacity var(--dur-2) var(--ease);
@starting-style { opacity: 0; }
}
-.domain-detail-dialog::backdrop { background: rgba(9, 9, 8, .62); backdrop-filter: blur(8px); }
+.domain-detail-dialog::backdrop { background: rgba(15, 45, 36, 0.48); backdrop-filter: blur(8px); }
.domain-detail-dialog > section { position: relative; padding: clamp(24px, 5vw, 44px); }
.domain-detail-dialog h2 { max-width: calc(100% - 42px); margin: 0; overflow-wrap: anywhere; font-family: var(--font-display); font-size: clamp(30px, 6vw, 54px); line-height: 1.08; letter-spacing: .01em; }
.domain-detail-dialog h2 span { color: var(--muted); }
@@ -2183,7 +2187,7 @@ svg.lucide {
overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
font-size: 12px; font-weight: 600; color: var(--text-secondary);
- background: rgba(46, 30, 19, 0.04);
+ background: rgba(31, 42, 36, 0.04);
}
.public-shell .category-badge svg { width: 12px; height: 12px; flex-shrink: 0; color: var(--gold); fill: var(--gold); }
.public-shell .domain-card .domain-actions { margin-left: auto; display: inline-flex; gap: 2px; }
@@ -2277,20 +2281,20 @@ svg.lucide {
display: inline-flex; align-items: center; gap: 6px; padding: 8px 16px;
border: 1px solid var(--border); border-radius: var(--radius-pill);
font-size: 13px; font-weight: 600; color: var(--text-secondary);
- background: rgba(46, 30, 19, 0.04); cursor: pointer;
+ background: rgba(31, 42, 36, 0.04); cursor: pointer;
transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}
-.public-shell .filter-pill:hover { background: rgba(46, 30, 19, 0.08); color: var(--text-primary); }
+.public-shell .filter-pill:hover { background: rgba(31, 42, 36, 0.08); color: var(--text-primary); }
.public-shell .filter-pill:active { transform: scale(.97); }
.public-shell .filter-pill em { font-style: normal; font-size: 11px; font-weight: 700; color: var(--text-tertiary); }
.public-shell .filter-pill.active {
color: #ffffff; background: linear-gradient(180deg, var(--brand-primary-light), var(--brand-primary));
- border-color: transparent; box-shadow: 0 2px 10px rgba(90, 62, 43, 0.35);
+ border-color: transparent; box-shadow: 0 2px 10px color-mix(in oklab, var(--brand) 30%, transparent);
}
.public-shell .filter-pill.active em { color: rgba(255, 255, 255, 0.72); }
.public-shell .filter-pill.pill-premium.active {
- color: #3a2c08; background: linear-gradient(180deg, var(--gold-bright), var(--gold-soft));
- box-shadow: 0 2px 10px rgba(212, 178, 82, 0.35);
+ color: var(--gold-text); background: linear-gradient(180deg, var(--gold-bright), var(--gold-soft));
+ box-shadow: 0 2px 10px rgba(184, 128, 56, 0.32);
}
@media (max-width: 720px) {
.public-shell .pill-row { gap: 7px; }
diff --git a/src/worker/index.ts b/src/worker/index.ts
index f9cc6d1..3500252 100644
--- a/src/worker/index.ts
+++ b/src/worker/index.ts
@@ -98,8 +98,8 @@ app.get("/", async (c) => {
).all<{ full_domain: string; description: string }>(),
c.env.DB.prepare("SELECT COUNT(*) AS total FROM domains WHERE is_listed = 1").first<{ total: number }>(),
]);
- const site = settings?.site_name ?? "DOMAIN HUNTER";
- const title = "DOMAIN HUNTER";
+ const site = settings?.site_name ?? "玩米";
+ const title = `${site} · 域名展示`;
const description = settings?.site_description || "精选域名资产展示";
const canonical = `${url.origin}/`;
const image = settings?.logo_url ? absoluteAsset(settings.logo_url, url.origin) : `${url.origin}/favicon.svg`;
diff --git a/src/worker/routes/public/og.ts b/src/worker/routes/public/og.ts
index 8d885db..4dc642a 100644
--- a/src/worker/routes/public/og.ts
+++ b/src/worker/routes/public/og.ts
@@ -60,12 +60,12 @@ export async function renderFeaturedDomainOg(c: Context
): Promise
-
-
-
- ${safeDomain}
-
- DOMAIN HUNTER · 精选域名资产
+
+
+
+ ${safeDomain}
+
+ 玩米 · 精选域名资产
`;
const resvg = await Resvg.async(svg, {
fitTo: { mode: "width", value: 1200 },
diff --git a/src/worker/services/featured-domain.ts b/src/worker/services/featured-domain.ts
index 53e3327..41662bb 100644
--- a/src/worker/services/featured-domain.ts
+++ b/src/worker/services/featured-domain.ts
@@ -56,7 +56,7 @@ function serializeRecommendation(row: RecommendationRow): FeaturedDomainRecommen
}
export function featuredDomainDescription(domain: FeaturedDomainRecord): string {
- return domain.description || `${domain.domain} 是 DOMAIN HUNTER 精选域名资产,主体简洁、辨识度高,适合品牌、产品或数字项目使用。`;
+ return domain.description || `${domain.domain} 是玩米精选域名资产,主体简洁、辨识度高,适合品牌、产品或数字项目使用。`;
}
export async function loadFeaturedDomainDetail(db: D1Database, normalizedDomain: string): Promise {
@@ -106,7 +106,7 @@ export async function loadFeaturedDomainDetail(db: D1Database, normalizedDomain:
same_tld: (sameTldResult.results as unknown as RecommendationRow[]).map(serializeRecommendation),
same_length: (sameLengthResult.results as unknown as RecommendationRow[]).map(serializeRecommendation),
site: {
- name: settings?.site_name || "DOMAIN HUNTER",
+ name: settings?.site_name || "玩米",
description: settings?.site_description || "精选域名资产展示",
logo_url: settings?.logo_url ?? null,
favicon_url: settings?.favicon_url ?? null,
@@ -154,6 +154,7 @@ export function renderFeaturedDomainSsr(detail: FeaturedDomainDetail): string {
← 返回域名目录
+ ★ 精品域名
${escapeHtml(domain.domain)}
${keywordMarkup}
${descriptionMarkup}
diff --git a/tests/e2e/visual-baseline-admin.spec.ts b/tests/e2e/visual-baseline-admin.spec.ts
index fe15e0d..538ed2d 100644
--- a/tests/e2e/visual-baseline-admin.spec.ts
+++ b/tests/e2e/visual-baseline-admin.spec.ts
@@ -58,6 +58,8 @@ test.describe("后台视觉基线", () => {
page.locator(".stats-overview"),
page.locator(".admin-two-columns"),
page.locator(".activity-list"),
+ // recharts Tooltip 会因指针残留在截图瞬间偶发渲染,遮盖避免抖动
+ page.locator(".recharts-tooltip-wrapper"),
],
});
});
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-darwin.png
deleted file mode 100644
index f7fae5a..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-win32.png
new file mode 100644
index 0000000..8eff383
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1024-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-darwin.png
deleted file mode 100644
index 121bf9f..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-win32.png
new file mode 100644
index 0000000..06080e2
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-1440-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-darwin.png
deleted file mode 100644
index 071100a..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-win32.png
new file mode 100644
index 0000000..f9b9e03
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-domains-390-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-darwin.png
deleted file mode 100644
index 4044d2a..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-win32.png
new file mode 100644
index 0000000..b0d065f
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1024-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-darwin.png
deleted file mode 100644
index 11d9bc0..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-win32.png
new file mode 100644
index 0000000..6430f39
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-1440-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-darwin.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-darwin.png
deleted file mode 100644
index bdf1f01..0000000
Binary files a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-win32.png b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-win32.png
new file mode 100644
index 0000000..8145941
Binary files /dev/null and b/tests/e2e/visual-baseline-admin.spec.ts-snapshots/admin-overview-390-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts b/tests/e2e/visual-baseline.spec.ts
index 90230ca..ec30f82 100644
--- a/tests/e2e/visual-baseline.spec.ts
+++ b/tests/e2e/visual-baseline.spec.ts
@@ -40,22 +40,23 @@ test.describe("视觉基线", () => {
await page.evaluate(() => document.fonts.ready);
// 卡片用 content-visibility(README 性能策略),屏幕外内容按 contain-intrinsic-size
// 估算高度;fullPage 截图滚动时才渲染成真实高度,导致页面总高在两次运行间抖动。
- // 先整页滚一遍强制渲染,再回到顶部截图。
+ // 逐屏滚动触发真实渲染后,还要等 scrollHeight 收敛(渲染是异步的,
+ // 快速滚过时部分卡片仍按估算高度参与布局),两次读数一致才算稳定。
await page.evaluate(async () => {
- await new Promise((resolve) => {
- let previous = -1;
- const step = () => {
- window.scrollBy(0, window.innerHeight);
- if (window.scrollY === previous) {
- resolve();
- return;
- }
- previous = window.scrollY;
- requestAnimationFrame(step);
- };
- step();
- });
+ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
+ let stable = 0;
+ for (let pass = 0; pass < 12 && stable < 2; pass += 1) {
+ const before = document.documentElement.scrollHeight;
+ for (let y = 0; y <= before; y += window.innerHeight) {
+ window.scrollTo(0, y);
+ await wait(40);
+ }
+ window.scrollTo(0, document.documentElement.scrollHeight);
+ await wait(200);
+ stable = document.documentElement.scrollHeight === before ? stable + 1 : 0;
+ }
window.scrollTo(0, 0);
+ await wait(160);
});
await expect(page).toHaveScreenshot(`${target.name}-${width}.png`, {
fullPage: true,
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-darwin.png
deleted file mode 100644
index ed12364..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-win32.png
new file mode 100644
index 0000000..daf0f96
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1024-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-darwin.png
deleted file mode 100644
index 43c13ff..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-win32.png
new file mode 100644
index 0000000..ff28309
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1440-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-darwin.png
deleted file mode 100644
index fc324df..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-win32.png
new file mode 100644
index 0000000..8cdd767
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-1920-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-darwin.png
deleted file mode 100644
index 22e04cc..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-win32.png
new file mode 100644
index 0000000..e87f585
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-320-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-darwin.png
deleted file mode 100644
index 72ac308..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-win32.png
new file mode 100644
index 0000000..c9d4ed6
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-375-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-darwin.png
deleted file mode 100644
index a612433..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-win32.png
new file mode 100644
index 0000000..130a6d3
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-390-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-darwin.png
deleted file mode 100644
index a948e11..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-win32.png
new file mode 100644
index 0000000..12c3658
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-430-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-darwin.png
deleted file mode 100644
index 3d3ec94..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-win32.png
new file mode 100644
index 0000000..c55f9d0
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/featured-detail-768-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-darwin.png
deleted file mode 100644
index 2b5dee7..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-win32.png
new file mode 100644
index 0000000..a3256c9
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1024-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-darwin.png
deleted file mode 100644
index 6847a96..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-win32.png
new file mode 100644
index 0000000..8c6ac40
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1440-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-darwin.png
deleted file mode 100644
index c332513..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-win32.png
new file mode 100644
index 0000000..9456705
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-1920-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-darwin.png
deleted file mode 100644
index 1afce54..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-win32.png
new file mode 100644
index 0000000..84625e3
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-320-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-darwin.png
deleted file mode 100644
index 86c8460..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-win32.png
new file mode 100644
index 0000000..ecfd16f
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-375-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-darwin.png
deleted file mode 100644
index 3ef41ab..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-win32.png
new file mode 100644
index 0000000..37987c7
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-390-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-darwin.png
deleted file mode 100644
index b8141cf..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-win32.png
new file mode 100644
index 0000000..9178166
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-430-chromium-win32.png differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-darwin.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-darwin.png
deleted file mode 100644
index 9ccda7d..0000000
Binary files a/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-darwin.png and /dev/null differ
diff --git a/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-win32.png b/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-win32.png
new file mode 100644
index 0000000..31cda27
Binary files /dev/null and b/tests/e2e/visual-baseline.spec.ts-snapshots/home-768-chromium-win32.png differ
diff --git a/tests/e2e/wanmi.spec.ts b/tests/e2e/wanmi.spec.ts
index 07cab25..83235c8 100644
--- a/tests/e2e/wanmi.spec.ts
+++ b/tests/e2e/wanmi.spec.ts
@@ -75,12 +75,12 @@ test.describe.serial("WanMi 生产流程", () => {
await route.fulfill({ response, json: { ...body, data: { ...body.data, contact_email: "955555@gmail.com", contact_x: "iWangGang", contact_qq: "307203" } } });
});
await page.goto("/", { waitUntil: "domcontentloaded" });
- await expect(page).toHaveTitle("DOMAIN HUNTER");
- await expect(page.locator('meta[name="wanmi-build"]')).toHaveAttribute("content", "domain-hunter-2026-07-19-v3");
+ await expect(page).toHaveTitle("WanMi · 域名展示");
+ await expect(page.locator('meta[name="wanmi-build"]')).toHaveAttribute("content", "elegant-green-gold-2026-07-20-v4");
await expect(page.locator(".domain-total-pill")).toHaveText("859 个域名");
- await expect(page.locator(".public-header .brand-title")).toHaveText("DOMAIN HUNTER");
+ await expect(page.locator(".public-header .brand-title")).toHaveText("WanMi");
await expect(page.locator(".public-header .brand-title")).toBeVisible();
- await expect(page.getByRole("heading", { name: "DOMAIN HUNTER", exact: true })).toHaveCount(1);
+ await expect(page.getByRole("heading", { name: "WanMi", exact: true })).toHaveCount(1);
await expect(page.locator(".brand-statement, .hero-stats")).toHaveCount(0);
await expect(page.getByRole("group", { name: "状态筛选" })).toHaveCount(0);
await expect(page.getByText("DOMAIN ASSET GALLERY", { exact: true })).toHaveCount(0);
@@ -92,9 +92,11 @@ test.describe.serial("WanMi 生产流程", () => {
await expect(page.locator(".view-switch")).toHaveCount(0);
await expect(page.locator(".domain-list.card-view")).toBeVisible();
await expect(page.locator(".domain-list.compact-view")).toHaveCount(0);
+ /* 首卡因排序打分随库数据浮动,且首屏卡未必有注册日期:
+ 这里只断言两种合法形态;完整日期区间格式由 mx.ooo 链路用例守护。 */
const firstCatalogueCard = page.locator(".domain-card:not(.skeleton)").first();
- await expect(firstCatalogueCard.locator(".registration-range")).toHaveText(/^\d{4}\.\d{2}\.\d{2}-\d{4}\.\d{2}\.\d{2}$/);
- await expect(firstCatalogueCard.locator(".remaining-days")).toHaveText(/^(余\d+天|已过期\d+天)$/);
+ await expect(firstCatalogueCard.locator(".registration-range")).toHaveText(/^(\d{4}\.\d{2}\.\d{2}-\d{4}\.\d{2}\.\d{2}|\d{4}\.\d{2}\.\d{2}|日期待补充)$/);
+ await expect(firstCatalogueCard.locator(".remaining-days")).toHaveText(/^(余\d+天|已过期\d+天|有效期未知)$/);
const cardGeometry = await firstCatalogueCard.evaluate((card) => {
const rect = (selector: string) => card.querySelector(selector)!.getBoundingClientRect();
const cardRect = card.getBoundingClientRect();
@@ -190,7 +192,7 @@ test.describe.serial("WanMi 生产流程", () => {
await expect(page.getByRole("link", { name: "通过 QQ 联系 307203" })).toHaveAttribute("href", "https://wpa.qq.com/msgrd?v=3&uin=307203&site=qq&menu=yes");
await expect(page.locator(".public-footer .contact-icons-wrap")).toHaveCount(0);
await expect(page.locator(".footer-logo")).toHaveAttribute("src", "/logo.svg");
- await expect(page.locator(".footer-copyright")).toHaveText("@ DOMAIN HUNTER");
+ await expect(page.locator(".footer-copyright")).toHaveText("© WanMi · 玩米");
const footerGeometry = await page.locator(".public-footer").evaluate((footer) => {
const logo = footer.querySelector(".footer-logo")!.getBoundingClientRect();
const content = footer.querySelector(".footer-copyright")!.getBoundingClientRect();
@@ -232,7 +234,7 @@ test.describe.serial("WanMi 生产流程", () => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto("/", { waitUntil: "domcontentloaded" });
await expect(page.locator(".brand-statement")).toHaveCount(0);
- await expect(page.getByRole("heading", { name: "DOMAIN HUNTER", level: 1 })).toHaveCount(1);
+ await expect(page.getByRole("heading", { name: "WanMi", level: 1 })).toHaveCount(1);
await expect(page.locator(".public-header .brand-title")).toBeVisible();
await expect(page.locator(".public-header .brand-icon")).toBeVisible();
await expect(page.locator(".public-header .header-actions")).toBeHidden();
@@ -241,7 +243,7 @@ test.describe.serial("WanMi 生产流程", () => {
await expect(page.locator(".domain-list.compact-view")).toBeVisible();
await expect(page.locator(".domain-list.card-view")).toHaveCount(0);
await expect(page.getByRole("navigation", { name: "移动端快捷导航" })).toHaveCount(0);
- await expect(page.locator(".footer-copyright")).toHaveText("@ DOMAIN HUNTER");
+ await expect(page.locator(".footer-copyright")).toHaveText("© WanMi · 玩米");
expect(await page.locator(".toolbar-filters option:checked").allInnerTexts()).toEqual(["分类", "后缀", "位数", "排序"]);
const metrics = await page.evaluate(() => ({
viewportWidth: window.innerWidth,
@@ -327,8 +329,8 @@ test.describe.serial("WanMi 生产流程", () => {
await page.getByLabel("密码").fill(credentials.password);
await page.getByRole("button", { name: "登录", exact: true }).click();
await expect(page.getByRole("heading", { name: "概览", exact: true })).toBeVisible();
- await expect(page.locator(".admin-brand")).toContainText("DOMAIN HUNTER");
- await expect(page.locator(".admin-header")).toContainText("DOMAIN HUNTER 管理后台");
+ await expect(page.locator(".admin-brand")).toContainText("WanMi");
+ await expect(page.locator(".admin-header")).toContainText("WanMi 管理后台");
await expect(page.getByRole("link", { name: "查看前台" })).toHaveText("");
await expect(page.getByRole("link", { name: "查看前台" }).locator("svg")).toHaveCount(1);
const adminNavigation = page.locator(".admin-sidebar nav");
@@ -345,11 +347,11 @@ test.describe.serial("WanMi 生产流程", () => {
await expect(page.locator(".quick-actions button").filter({ hasText: "导出 CSV" })).toBeVisible();
await adminNavigation.getByRole("button", { name: "站点设置", exact: true }).click();
- await expect(page.getByLabel("站点名称")).toHaveValue("DOMAIN HUNTER");
+ await expect(page.getByLabel("站点名称")).toHaveValue("WanMi");
await expect(page.getByLabel("站点 Slogan")).toHaveValue("精选域名资产展示");
await expect(page.locator(".upload-card").nth(0).locator("img")).toHaveAttribute("src", "/logo.svg");
await expect(page.locator(".upload-card").nth(1).locator("img")).toHaveAttribute("src", "/favicon.svg");
- await expect(page.getByLabel("版权文字")).toHaveValue("@ DOMAIN HUNTER");
+ await expect(page.getByLabel("版权文字")).toHaveValue("© WanMi · 玩米");
await expect(page.getByText("页首显示管理入口", { exact: true })).toBeVisible();
await expect(page.getByText("页脚显示管理入口", { exact: true })).toHaveCount(0);
diff --git a/tests/unit/accent-color.test.ts b/tests/unit/accent-color.test.ts
new file mode 100644
index 0000000..2f21a97
--- /dev/null
+++ b/tests/unit/accent-color.test.ts
@@ -0,0 +1,63 @@
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+
+import { THEME_BRAND_HEX, applyAccentColor } from "../../src/client/lib/accent-color";
+
+/* node 环境无 DOM:以最小 stub 模拟 documentElement.style 的自定义属性读写 */
+function createStyleStub() {
+ const store = new Map();
+ return {
+ store,
+ setProperty: (key: string, value: string) => void store.set(key, value),
+ removeProperty: (key: string) => void store.delete(key),
+ };
+}
+
+describe("applyAccentColor", () => {
+ let style: ReturnType;
+
+ beforeEach(() => {
+ style = createStyleStub();
+ (globalThis as Record).document = { documentElement: { style } };
+ });
+
+ afterEach(() => {
+ delete (globalThis as Record).document;
+ });
+
+ it("当前主题默认色视为未定制,清除全部覆盖", () => {
+ style.store.set("--brand", "#000000");
+ applyAccentColor(THEME_BRAND_HEX);
+ expect(style.store.size).toBe(0);
+ });
+
+ it("历史各轮默认色(黑金/暖金/珊瑚橙)同样视为未定制", () => {
+ for (const legacy of ["#d8b638", "#c4a242", "#b89530", "#f97316", "#e85d2a"]) {
+ style.store.set("--brand", "#000000");
+ applyAccentColor(legacy);
+ expect(style.store.size, `${legacy} 应清除覆盖`).toBe(0);
+ }
+ });
+
+ it("非法值不覆盖任何属性", () => {
+ applyAccentColor("red");
+ applyAccentColor("#12345");
+ applyAccentColor(null);
+ expect(style.store.size).toBe(0);
+ });
+
+ it("自定义色整套派生:主色/strong/hover/bg/bg-strong/border/ring 同步", () => {
+ applyAccentColor("#3b5bdb");
+ expect(style.store.get("--brand")).toBe("#3b5bdb");
+ for (const property of ["--brand-strong", "--brand-hover", "--brand-bg", "--brand-bg-strong", "--brand-border", "--brand-ring"]) {
+ const value = style.store.get(property);
+ expect(value, `${property} 必须派生`).toBeTruthy();
+ expect(value).toContain("#3b5bdb");
+ expect(value).toContain("color-mix");
+ }
+ });
+
+ it("大小写与空白归一化后仍识别默认色", () => {
+ applyAccentColor(" #133429 ".toUpperCase().trim());
+ expect(style.store.size).toBe(0);
+ });
+});
diff --git a/tokens.css b/tokens.css
index 40706e9..0e6b51c 100644
--- a/tokens.css
+++ b/tokens.css
@@ -1,91 +1,99 @@
/* ============================================================
- WanMi Design Tokens · Warm Craft —— 拟物化暖色:米白纸感 + 皮革棕 + 金
- —— v3.0(2026-07-18):配色对齐拟物化风格指南(参考图 IMG_3490):
- 米白纸感底、奶油白卡片、皮革深棕主操作、金色精品强调
+ WanMi Design Tokens · Elegant Green Gold 雅致绿金
+ —— v4.0(2026-07-20):墨绿为主(品牌/主按钮/激活态)、暖白米白为底、
+ 香槟金点缀(仅精品与重点资产,占比 ≤2%)
—— 前台 / 详情 / 后台 / 登录共用这一套令牌
- —— 组件内禁止硬编码色值,一律引用这里的变量
- —— 单一浅色暖调主题(不提供明暗切换);后台侧栏为深棕皮革例外区
+ —— 组件内禁止硬编码色值,一律引用这里的变量
+ —— 单一浅色主题:不提供暗色模式,不读取系统深浅色偏好
+ —— teal / accent / cream 令牌为历史各轮遗留的兼容别名,禁止新用
============================================================ */
:root {
color-scheme: light;
- /* Background —— 米白纸感,暖调 */
- --background: #f5e9d6;
- --background-soft: #efe1c8;
-
- /* Surface —— 奶油白卡片分层(弹窗/下拉必须不透底),材质感靠内高光与暖棕投影 */
- --surface: #fbf5ea;
- --surface-secondary: #f3ead8;
- --surface-elevated: #fdf9f0;
- --surface-hover: #f1e5cf;
-
- /* Border —— 深棕透明度分级,拟物描边 */
- --border: rgba(46, 30, 19, 0.14);
- --border-strong: rgba(46, 30, 19, 0.24);
- --border-subtle: rgba(46, 30, 19, 0.08);
-
- /* Teal —— v1 深色遗留,仅兼容保留,禁止新用 */
- --teal: #2fbf9a;
- --teal-bright: #4adcb4;
- --teal-deep: #17564a;
- --teal-dim: #1d8570;
- --teal-bg: rgba(47, 191, 154, 0.10);
- --teal-bg-strong: rgba(47, 191, 154, 0.16);
- --teal-border: rgba(47, 191, 154, 0.28);
-
- /* Brand Primary —— v3.0 皮革深棕主色(拟物参考图 Primary 按钮色)
- 浅底上前景/背景双达标:作前景 8.1:1,作实底配白字 9.7:1 */
- --brand-primary: #5a3e2b;
- --brand-primary-light: #6f4e36; /* 渐变亮端 / hover(白字 7.5:1) */
- --brand-primary-deep: #3e2a1c; /* 按压态 / 渐变暗端 */
- --brand-primary-ink: #2e1e13; /* 近黑棕:正文级最强 */
- --brand-bg: rgba(90, 62, 43, 0.10);
- --brand-bg-strong: rgba(90, 62, 43, 0.16);
- --brand-border: rgba(90, 62, 43, 0.32);
-
- /* Accent —— 品牌 logo「一线成域」珊瑚红节点色:
- 仅限 logo 与极小品牌点缀;UI 强调一律用金色/语义色,不得大面积使用 */
- --accent: #ff5c38;
- --accent-light: #ff7a5c;
- --accent-bg: rgba(255, 92, 56, 0.12);
-
- /* Brand Gold —— 精品/资产强调(拟物参考图金色 #D4AF37) */
- --gold: #d4af37;
- --gold-bright: #e5c55c;
- --gold-soft: #c4a242;
+ /* Background —— 暖白纸感 */
+ --background: #f6f5f0;
+ --background-soft: #f1efe8;
+
+ /* Surface —— 奶油白卡片分层(弹窗/下拉必须不透底) */
+ --surface: #fefdfa;
+ --surface-secondary: #f4f2ec;
+ --surface-elevated: #ffffff;
+ --surface-hover: #efede6;
+
+ /* Border —— 极浅绿灰边框,靠透明度分级 */
+ --border-subtle: rgba(31, 42, 36, 0.06);
+ --border: rgba(31, 42, 36, 0.10);
+ --border-strong: rgba(31, 42, 36, 0.17);
+
+ /* Brand Green —— 深墨绿品牌主色
+ 浅底上前景/背景双达标:作前景 12.8:1,作实底配白字 12.8:1 */
+ --brand-primary: #133429;
+ --brand-primary-strong: #0f3c32;
+ --brand-primary-hover: #1e6b59;
+ --brand-secondary: #2d4f40;
+ /* main 侧 Warm Craft 组件引用的派生名,映射到绿金等价色 */
+ --brand-primary-light: #1e6b59; /* 渐变亮端 / hover */
+ --brand-primary-deep: #0b2b23; /* 按压态 / 渐变暗端 */
+ --brand-primary-ink: #10241c; /* 近黑绿:正文级最强 */
+ --brand-bg: rgba(19, 52, 41, 0.08);
+ --brand-bg-strong: rgba(19, 52, 41, 0.14);
+ --brand-border: rgba(19, 52, 41, 0.26);
+ --brand-ring: rgba(30, 107, 89, 0.20);
+
+ /* Accent —— 品牌 logo 点缀位:雅致绿金下用香槟金取代珊瑚红;
+ 仅限 logo 与极小品牌点缀,UI 强调一律用品牌绿/语义色 */
+ --accent: #c89848;
+ --accent-light: #d8b66f;
+ --accent-bg: rgba(200, 152, 72, 0.12);
+
+ /* Teal —— v1 深色遗留,仅兼容保留,禁止新用(映射品牌绿系) */
+ --teal: #1e6b59;
+ --teal-bright: #2d8a72;
+ --teal-deep: #133429;
+ --teal-dim: #2d4f40;
+ --teal-bg: rgba(30, 107, 89, 0.10);
+ --teal-bg-strong: rgba(30, 107, 89, 0.16);
+ --teal-border: rgba(30, 107, 89, 0.28);
+
+ /* Champagne Gold —— 只用于精品域名、重点资产、重要数值与极小面积装饰;
+ 禁止用于普通正文、普通筛选、TLD 标签、大面积背景与菜单激活态。 */
+ --gold: #c89848;
+ --gold-deep: #b88038;
+ --gold-bright: #d8b66f;
+ --gold-soft: #e8d8b8;
--gold-muted: #9a8850;
- /* 浅底金色文字(≥4.5:1);gold/gold-bright 仅作渐变与装饰,禁止直接作浅底文字色 */
- --gold-text: #7d641c;
- --gold-bg: rgba(212, 175, 55, 0.14);
- --gold-bg-strong: rgba(212, 175, 55, 0.22);
- --gold-border: rgba(212, 175, 55, 0.45);
- --gold-divider: rgba(212, 175, 55, 0.35);
-
- /* Text —— 深棕墨色系(纸上的墨) */
- --text-primary: #2e1e13;
- --text-secondary: #5c4a3a;
- --text-tertiary: #75644f;
- --text-inverse: #fbf5ea;
+ /* 浅底金色文字(≥4.5:1);gold/gold-bright 仅作渐变与装饰,禁止直接作浅底文字色 */
+ --gold-text: #79551f;
+ --gold-bg: rgba(200, 152, 72, 0.11);
+ --gold-bg-strong: rgba(200, 152, 72, 0.18);
+ --gold-border: rgba(184, 128, 56, 0.38);
+ --gold-divider: rgba(184, 128, 56, 0.30);
+
+ /* Text —— 深绿墨色系 */
+ --text-primary: #1f2a24;
+ --text-secondary: #5d675f;
+ --text-tertiary: #7e877f;
+ --text-inverse: #ffffff;
--text-on-brand: #ffffff;
- /* Semantic —— 浅底加深(全部 ≥4.5:1) */
- --success: #297040;
- --warning: #8a5b10;
- --danger: #b23327;
- --info: #38648f;
+ /* Semantic —— 浅底加深(全部 ≥4.5:1) */
+ --success: #34745a;
+ --warning: #a76b22;
+ --danger: #b95343;
+ --info: #4e7469;
- --success-bg: rgba(41, 112, 64, 0.12);
- --warning-bg: rgba(138, 91, 16, 0.12);
- --danger-bg: rgba(178, 51, 39, 0.12);
- --info-bg: rgba(56, 100, 143, 0.12);
+ --success-bg: rgba(52, 116, 90, 0.10);
+ --warning-bg: rgba(167, 107, 34, 0.10);
+ --danger-bg: rgba(185, 83, 67, 0.10);
+ --info-bg: rgba(78, 116, 105, 0.10);
- /* 高亮卡:暖调浅高光层(沿用 cream 令牌名,避免全量替换引用) */
- --cream-a: #fdf8ee;
- --cream-b: #f6ecd9;
- --cream-fg: #2e1e13;
- --cream-fg-soft: rgba(46, 30, 19, 0.62);
- --cream-rule: rgba(46, 30, 19, 0.14);
+ /* 高亮卡:暖白高光层(沿用 cream 令牌名,避免全量替换引用) */
+ --cream-a: #fefdfa;
+ --cream-b: #f4f2ec;
+ --cream-fg: #1f2a24;
+ --cream-fg-soft: rgba(31, 42, 36, 0.62);
+ --cream-rule: rgba(31, 42, 36, 0.14);
/* Typography */
--font-display: "Cormorant Garamond", "Songti SC", "Noto Serif SC", ui-serif, Georgia, serif;
@@ -114,16 +122,16 @@
--space-xl: 48px;
--space-2xl: 64px;
- /* Elevation —— 浅色拟物:暖棕投影 + 强白内高光(材质表面感) */
- --shadow-card: 0 1px 2px rgba(90, 62, 43, 0.10), 0 2px 8px rgba(90, 62, 43, 0.10);
- --shadow-card-hover: 0 10px 24px rgba(90, 62, 43, 0.18), 0 2px 6px rgba(90, 62, 43, 0.12);
- --shadow-panel: 0 12px 36px rgba(90, 62, 43, 0.16), 0 2px 8px rgba(90, 62, 43, 0.10);
- --shadow-modal: 0 24px 64px rgba(46, 30, 19, 0.28);
- --glow-gold: 0 0 18px rgba(212, 175, 55, 0.20);
- --glow-teal: 0 0 18px rgba(47, 191, 154, 0.12); /* 兼容保留 */
- --glow-brand: 0 0 18px rgba(90, 62, 43, 0.16); /* v3.0 品牌光晕(暖棕) */
- /* 拟物卡顶部内高光:叠加在 box-shadow 首位使用 */
- --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.75);
+ /* Elevation —— 带绿色倾向的低透明度阴影,禁止纯黑阴影 */
+ --shadow-card: 0 1px 2px rgba(17, 38, 30, 0.035), 0 7px 20px rgba(17, 38, 30, 0.055);
+ --shadow-card-hover: 0 2px 5px rgba(17, 38, 30, 0.055), 0 16px 34px rgba(17, 38, 30, 0.10);
+ --shadow-panel: 0 12px 34px rgba(17, 38, 30, 0.075);
+ --shadow-modal: 0 24px 64px rgba(15, 45, 36, 0.18);
+ --glow-gold: 0 0 18px rgba(200, 152, 72, 0.16);
+ --glow-teal: 0 0 18px rgba(30, 107, 89, 0.10); /* 兼容保留 */
+ --glow-brand: 0 0 18px rgba(19, 52, 41, 0.14);
+ /* 拟物卡顶部内高光:叠加在 box-shadow 首位使用 */
+ --glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.76);
/* Motion */
--ease: cubic-bezier(0.22, 1, 0.36, 1);
@@ -132,6 +140,6 @@
--dur-2: 200ms;
--dur-3: 250ms;
- /* 移动端底部导航高度:正文用它预留滚动空间 */
+ /* 移动端底部导航高度:正文用它预留滚动空间 */
--bottom-nav-h: 76px;
}