diff --git a/HANDOFF.md b/HANDOFF.md index cd6cd40..67486a9 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -1,6 +1,20 @@ # WanMi HANDOFF -## 最新进度(2026-07-18 · Dark Vault 改版 + 国内访问卡顿修复) +## 最新进度(2026-07-20 · Elegant Green Gold 雅致绿金主题) + +- 全站由浅色暖金升级为「雅致绿金」:墨绿 `#133429` 为品牌主色(主按钮/激活态/链接/侧栏/登录场景),暖白 `#f6f5f0`/`#fefdfa` 为底,香槟金 `#c89848` 降级为精品与重点资产专用(≤2% 面积)。`tokens.css` 全量重写并**移除暗色 `prefers-color-scheme` 媒体块**(按要求固定单一浅色)。 +- 前台:搜索按钮/分页当前页/视图切换器改墨绿实心白字;「访问域名」由金字金边改为绿字绿边次按钮;精品卡身份改由金细边 + 顶部 44×2px 短金线 + 浅金底「★精选」徽章表达(正文保持深色);到期状态改三级(≤30 天警告、≤7 天与已过期危险,文案「已过期」);速览弹窗白面板 + 深绿遮罩,QR/价值维度低饱和底保留。 +- 精品详情页:大标题由亮金改深色,访问按钮改实心墨绿,kicker 改中文「★ 精品域名」金棕字;Worker SSR 模板同步删掉遗留的 FEATURED DOMAIN ASSET / DISCOVER MORE 英文 kicker(上一轮只删了客户端侧)。 +- 后台:登录页重做为深墨绿场景 + 左右分栏白卡(左品牌介绍区深绿 + 金点缀、右白表单,手机自动单栏);侧栏深墨绿渐变(从 `--brand` color-mix 派生,accent 覆盖时整体跟随),active 为白 10% 底 + 左侧 3px 香槟金短条 + 金图标;概览统计卡白卡 + 顶部语义细线(总数墨绿/展示绿/隐藏灰/精品金)+ 语义数值色;表头米白 `--background-soft`、行悬停 `--surface-hover`。admin.css 的黑金基础层与浅色覆盖层合并为单层。 +- 概览 PV/UV 图修复既有 bug:UV 线原用 `var(--ink)`(浅色主题下=白色,白卡上不可见),改香槟金 `--gold-deep` 作第二数据线。 +- accent_color 机制重做:新增 `src/client/lib/accent-color.ts`,历史默认色(含黑金 `#d8b638`、暖金 `#c4a242` 等 5 个)视为「未定制」清除覆盖;真自定义色整套派生 brand/strong/hover/bg/bg-strong/border/ring,配 5 项单测。新增迁移 `0022_elegant_green_accent.sql` 把历史默认值归一为 `#133429`(沿 0014 惯例,不动真自定义色)。**远程执行该迁移会把生产 accent_color 从 `#c4a242` 更新为 `#133429`,属主题配套变更。** +- 品牌资产同步:`favicon.svg` 墨绿底白菱金芯;`manifest.webmanifest` theme_color `#133429`;`index.html` theme-color `#f6f5f0`;精品 OG 图由黑底金字改墨绿底金字。 +- CSS 清理:删除 hero-*、header-discover、premium-corner、copy-button、group-tabs、section-kicker、mobile-stats、domain-featured-dot、keyword-pill/domain-keywords、copy-filter-link、contact-modal/contact-list/qr-code、domain-list-head、catalogue-intro、row-details、integration-details、bulk-keywords、status-list、highlighted 等确认死类(TSX 全量交叉验证,含动态拼接 `density-${…}`/`badge-…` 检查后保留活类);棕色系历史阴影(rgba(64,42,20) 等 6 处)与纯黑阴影全部换绿倾向令牌;`--cream-*`、`--glow-gold`、`--gold-muted`、`--premium-fg`、`--ink`、`--foreground` 等死令牌删除;`--fg-3` 补定义(原引用 8 处但从未定义)。app.css 2110→1936 行,前台 CSS 产物 100.63→90.00KB(gzip 17.68→15.85)。 +- 已知限制:管理员在站点设置输入过浅的自定义强调色仍可能破坏主按钮白字对比度(schema 仅校验 hex 格式);`--gold-bg`/`--gold-soft`/`--brand-secondary` 等主题 API 令牌暂无 CSS 引用,保留作设计系统接口。 +- 生产数据核验(经 Cloudflare MCP 只读查询)发现两处生产侧问题并已随本轮修复:① `site_settings.site_name` 仍是历史品牌 `DOMAIN HUNTER`(copyright 同),新增 `0023_unify_brand_names.sql` 精确匹配修正为 `WanMi` / `© WanMi · 玩米`;② 生产 `/logo.svg`(藏蓝橙点图形)不在 git 中——是上次部署时构建机上未跟踪的本地文件被打进产物,下次从 git 构建部署必然 404 导致页首 Logo 死链,已新增绿金品牌 `public/logo.svg`(与 favicon 同构)补位。 +- 部署前备份:本地 wrangler OAuth 过期无法跑 `pnpm db:backup`,改经 Cloudflare MCP 完成增量逻辑备份 `backups/wanmi-20260720-mcp-pre-green-gold.sql`——以 7-17 全量备份为基线,全表 `updated_at` 核验后仅 3 行变更(mx.ooo、site_settings、notification_settings),两份文件合并即完整现状;敏感表(sessions/密码哈希/渠道密文)本次部署零触碰,由 D1 Time Travel 30 天兜底。 + +## 前序进度(2026-07-18 · Dark Vault 改版 + 国内访问卡顿修复) ### 性能(「访问很卡」根因与修复) - 根因一(主因):`index.html` 仍有 Google Fonts 的 Noto Sans SC `` 外链。渲染阻塞资源在大陆不可达时要等 TCP 超时才放行渲染,国内用户白屏可达 20 秒以上。已彻底移除(含两个 preconnect),中文走系统字体栈;CSP 同步收紧不再放行 `fonts.googleapis.com`/`fonts.gstatic.com`(集成测试断言已反转)。 diff --git a/README.md b/README.md index 2a76a5c..d8a725b 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ - D1:`wanmi-db`(绑定 `DB`) - R2:`wanmi-assets`(绑定 `UPLOADS`) - Cron:每天 `01:00 UTC`,即 `Asia/Shanghai 09:00` -- 当前设计基线:单一浅色暖金主题(2026-07-17,由黑金改版);域名简介为纯手动维护(AI 生成已于同日移除) +- 当前设计基线:Elegant Green Gold 雅致绿金主题(2026-07-20,由浅色暖金改版)——墨绿为主、暖白为底、香槟金点缀;域名简介为纯手动维护(AI 生成已于 2026-07-17 移除) 权威业务源为 `data/source/WanMi.csv`:859 条有效唯一记录。生产库还保留 3 条历史人工域名,因此线上合计 862 条;本次迁移与发布不得覆盖或归档这 3 条记录。 @@ -26,10 +26,11 @@ ## 设计系统 -- 仅提供单一黑金主题,不提供明暗主题切换,也不读取系统配色偏好。 +- 仅提供单一浅色「雅致绿金」主题,不提供明暗主题切换,也不读取系统配色偏好。 - 根目录 `tokens.css` 是唯一设计令牌源;组件与运行时样式只引用 CSS 变量,不硬编码色值。 -- 背景使用纯黑 OLED `#000000`,表面依次使用 `#151515`、`#1a1a1a`、`#1e1e1e`,唯一强调色为香槟金 `#d8b638`。 -- 主、次、三级文字依次使用 `#f5f5f7`、`#a1a1a6`、`#6e6e73`。 +- 暖白背景 `#f6f5f0`、奶油白卡片 `#fefdfa`,品牌主色为深墨绿 `#133429`(hover `#1e6b59`),香槟金 `#c89848` 仅表达精品与重点资产(占比 ≤2%),浅底金色小字一律 `--gold-text #79551f`。 +- 主、次、三级文字依次使用 `#1f2a24`、`#5d675f`、`#7e877f`;卡片阴影统一为绿倾向低透明度令牌,禁止纯黑阴影。 +- 站点设置 `accent_color` 由 `applyAccentColor` 整套派生(主色/hover/bg/border/ring 同步),历史默认值视为未定制。 - 保留 `prefers-reduced-motion` 与 `prefers-reduced-transparency` 退化;完整规范见 [`design.md`](design.md)。 ## 公开站点 diff --git a/index.html b/index.html index c9dfd4f..241abe6 100644 --- a/index.html +++ b/index.html @@ -3,16 +3,16 @@ - - - - + + + + - + @@ -21,7 +21,7 @@ - DOMAIN HUNTER + 玩米 · 域名展示
diff --git a/migrations/0022_elegant_green_accent.sql b/migrations/0022_elegant_green_accent.sql new file mode 100644 index 0000000..0683e23 --- /dev/null +++ b/migrations/0022_elegant_green_accent.sql @@ -0,0 +1,5 @@ +-- 雅致绿金主题:将历史各轮默认强调色(珊瑚橙/黑金/暖金)归一为墨绿品牌色。 +-- 仅匹配历史默认值,不覆盖管理员真正自定义的主题色(与 0014 同一惯例)。 +UPDATE site_settings +SET accent_color = '#133429', updated_at = CURRENT_TIMESTAMP +WHERE accent_color IN ('#f97316', '#e85d2a', '#d8b638', '#b89530', '#c4a242'); diff --git a/migrations/0023_unify_brand_names.sql b/migrations/0023_unify_brand_names.sql new file mode 100644 index 0000000..1c86106 --- /dev/null +++ b/migrations/0023_unify_brand_names.sql @@ -0,0 +1,10 @@ +-- 品牌统一:中文产品名「玩米」、英文产品名「WanMi」。 +-- 生产 site_settings 仍残留历史品牌 DOMAIN HUNTER(前台 title/页首/页脚可见), +-- 仅精确匹配历史品牌值,不覆盖管理员设置的其他内容。 +UPDATE site_settings +SET site_name = 'WanMi', updated_at = CURRENT_TIMESTAMP +WHERE site_name IN ('DOMAIN HUNTER', 'DomainHunter', 'domain hunter'); + +UPDATE site_settings +SET copyright_text = '© WanMi · 玩米', updated_at = CURRENT_TIMESTAMP +WHERE copyright_text IN ('@ DOMAIN HUNTER', '© DOMAIN HUNTER', '@DOMAIN HUNTER'); diff --git a/migrations/0025_elegant_green_brand_final.sql b/migrations/0025_elegant_green_brand_final.sql new file mode 100644 index 0000000..63e9d98 --- /dev/null +++ b/migrations/0025_elegant_green_brand_final.sql @@ -0,0 +1,18 @@ +-- 雅致绿金主题的最终品牌与主题色收敛。 +-- 背景:0023_domain_hunter_branding / 0024_footer_brand_text(darwin 线)与 +-- 0022_elegant_green_accent / 0023_unify_brand_names(win32 线)在两台机器上的 +-- 应用顺序不同:远程按提交时序(DOMAIN HUNTER 线先、绿金线后)已收敛为绿金; +-- 本地合并后 0023_domain_hunter / 0024_footer 会晚于绿金迁移执行、把品牌改回 +-- DOMAIN HUNTER + 黑金。本迁移排在所有品牌迁移之后,保证两端最终态一致。 +-- 幂等:条件匹配历史品牌/历史默认色,不覆盖管理员自定义内容。 +UPDATE site_settings +SET site_name = 'WanMi', updated_at = CURRENT_TIMESTAMP +WHERE site_name IN ('DOMAIN HUNTER', 'DomainHunter', 'domain hunter'); + +UPDATE site_settings +SET copyright_text = '© WanMi · 玩米', updated_at = CURRENT_TIMESTAMP +WHERE copyright_text IN ('@ DOMAIN HUNTER', '© DOMAIN HUNTER', '@DOMAIN HUNTER', '@2026'); + +UPDATE site_settings +SET accent_color = '#133429', updated_at = CURRENT_TIMESTAMP +WHERE accent_color IN ('#f97316', '#e85d2a', '#d8b638', '#b89530', '#c4a242', '#d4b252', '#2fbf9a', '#5a3e2b'); diff --git a/public/favicon.svg b/public/favicon.svg index 2f63ed1..b527a11 100644 --- a/public/favicon.svg +++ b/public/favicon.svg @@ -1,7 +1,5 @@ - - - - - + + + + diff --git a/public/logo.svg b/public/logo.svg index 5a14f28..029185d 100644 --- a/public/logo.svg +++ b/public/logo.svg @@ -1,7 +1,5 @@ - - - - - + + + + diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest index 2ae15f8..37158ff 100644 --- a/public/manifest.webmanifest +++ b/public/manifest.webmanifest @@ -1,12 +1,12 @@ { - "name": "DOMAIN HUNTER", - "short_name": "DOMAIN HUNTER", - "description": "DOMAIN HUNTER 精选域名资产展示", + "name": "玩米", + "short_name": "玩米", + "description": "玩米域名展示与管理系统", "lang": "zh-CN", "start_url": "/", "display": "standalone", - "background_color": "#f7f7f5", - "theme_color": "#b89530", + "background_color": "#f6f5f0", + "theme_color": "#133429", "icons": [ { "src": "/favicon.svg", diff --git a/src/client/App.tsx b/src/client/App.tsx index c9980d3..c1ae50c 100644 --- a/src/client/App.tsx +++ b/src/client/App.tsx @@ -6,7 +6,7 @@ import { FeaturedDomainPage } from "./pages/public/FeaturedDomainPage"; const AdminApp = lazy(() => import("./pages/admin/AdminApp").then((module) => ({ default: module.AdminApp }))); function RouteLoading() { - return

正在打开 DOMAIN HUNTER…

; + return

正在打开玩米…

; } export function App() { diff --git a/src/client/lib/accent-color.ts b/src/client/lib/accent-color.ts new file mode 100644 index 0000000..4c5707f --- /dev/null +++ b/src/client/lib/accent-color.ts @@ -0,0 +1,58 @@ +/* 站点设置 accent_color 的运行时应用。 + 雅致绿金主题的默认品牌色链在 tokens.css / app.css 中静态调校(观感最优); + 管理员自定义颜色时必须整套派生 strong/hover/bg/border/ring, + 只覆盖一个 --brand 会出现「按钮新色、分页/聚焦环仍是旧色」的割裂。 */ + +/** 当前主题的默认品牌色(tokens.css --brand-primary)。 */ +export const THEME_BRAND_HEX = "#133429"; + +/* 历史各轮主题的默认 accent(珊瑚橙 → 黑金 → 暖金 → teal → 皮革棕 → 雅致绿金)。 + 命中即视为「管理员未定制」,走 CSS 静态令牌;仅真正的自定义色才做动态派生。 */ +const HISTORICAL_DEFAULT_ACCENTS = new Set([ + "#f97316", + "#e85d2a", + "#d8b638", + "#b89530", + "#c4a242", + "#d4b252", + "#2fbf9a", + "#5a3e2b", + THEME_BRAND_HEX, +]); + +const ACCENT_PROPERTIES = [ + "--brand", + "--brand-strong", + "--brand-hover", + "--brand-primary-light", + "--brand-primary-deep", + "--brand-bg", + "--brand-bg-strong", + "--brand-border", + "--brand-ring", +] as const; + +function isValidHex(value: string): boolean { + return /^#[0-9a-f]{6}$/i.test(value); +} + +/** 应用(或清除)管理员自定义强调色;派生规则与 tokens.css 的绿金默认链保持同一透明度节奏。 */ +export function applyAccentColor(accent: string | null | undefined): void { + const root = document.documentElement.style; + const normalized = accent?.trim().toLowerCase() ?? ""; + + if (!isValidHex(normalized) || HISTORICAL_DEFAULT_ACCENTS.has(normalized)) { + for (const property of ACCENT_PROPERTIES) root.removeProperty(property); + return; + } + + root.setProperty("--brand", normalized); + root.setProperty("--brand-strong", `color-mix(in oklab, ${normalized} 90%, #04120d)`); + root.setProperty("--brand-hover", `color-mix(in oklab, ${normalized} 72%, #ffffff)`); + root.setProperty("--brand-primary-light", `color-mix(in oklab, ${normalized} 72%, #ffffff)`); + root.setProperty("--brand-primary-deep", `color-mix(in oklab, ${normalized} 78%, #04120d)`); + root.setProperty("--brand-bg", `color-mix(in oklab, ${normalized} 8%, transparent)`); + root.setProperty("--brand-bg-strong", `color-mix(in oklab, ${normalized} 14%, transparent)`); + root.setProperty("--brand-border", `color-mix(in oklab, ${normalized} 26%, transparent)`); + root.setProperty("--brand-ring", `color-mix(in oklab, ${normalized} 20%, transparent)`); +} diff --git a/src/client/pages/admin/AdminApp.tsx b/src/client/pages/admin/AdminApp.tsx index fd6c02f..9f41e8d 100644 --- a/src/client/pages/admin/AdminApp.tsx +++ b/src/client/pages/admin/AdminApp.tsx @@ -98,15 +98,24 @@ function LoginPage({ onLogin }: { onLogin: (user: AdminUser) => void }) { return (
- DOMAIN HUNTER -
安全管理控制台

欢迎回来

请使用管理员账号继续。

-
- - - {error &&
{error}
} - -
- ← 返回域名展示页 + +
+
安全管理控制台

欢迎回来

请使用管理员账号继续。

+
+ + + {error &&
{error}
} + +
+ ← 返回域名展示页 +
); @@ -955,9 +964,9 @@ export function AdminApp() { const notify = useCallback((text: string, tone: "success" | "error" = "success") => setToast({ id: Date.now(), text, tone }), []); useEffect(() => { api("/api/auth/me").then(setUser).catch((reason: unknown) => { if (!(reason instanceof ApiError) || reason.status !== 401) notify(reason instanceof Error ? reason.message : "会话检查失败", "error"); }).finally(() => setChecking(false)); }, [notify]); useEffect(() => { api<{ site_name: string; logo_url: string | null }>("/api/public/settings").then(setBranding).catch(() => undefined); }, []); - if (checking) return

正在验证 DOMAIN HUNTER 会话…

; + if (checking) return

正在验证玩米会话…

; if (!user) return { setUser(loggedIn); setView("overview"); }} />; const nav: Array<[AdminView, string, LucideIcon]> = [["overview", "概览", LayoutDashboard], ["domains", "域名管理", Globe], ["categories", "分类", Tag], ["settings", "站点设置", Settings], ["notifications", "到期提醒", Bell], ["security", "账户安全", ShieldCheck], ["logs", "操作日志", History]]; async function logout() { try { await api("/api/auth/logout", { method: "POST" }); } finally { setUser(null); } } - return
DOMAIN HUNTER 管理后台

{nav.find(([key]) => key === view)?.[1]}

{view === "overview" && { setDomainsPresetTld(tld); setView("domains"); }} onNavigate={setView} notify={notify} />}{view === "domains" &&}{view === "categories" && }{view === "settings" && }{view === "notifications" && }{view === "security" && }{view === "logs" && }
setToast(null)} />
; + return
{`${branding?.site_name ?? "玩米"} 管理后台`}

{nav.find(([key]) => key === view)?.[1]}

{view === "overview" && { setDomainsPresetTld(tld); setView("domains"); }} onNavigate={setView} notify={notify} />}{view === "domains" &&}{view === "categories" && }{view === "settings" && }{view === "notifications" && }{view === "security" && }{view === "logs" && }
setToast(null)} />
; } diff --git a/src/client/pages/public/FeaturedDomainPage.tsx b/src/client/pages/public/FeaturedDomainPage.tsx index 3debf8b..d92362b 100644 --- a/src/client/pages/public/FeaturedDomainPage.tsx +++ b/src/client/pages/public/FeaturedDomainPage.tsx @@ -39,7 +39,7 @@ export function FeaturedDomainPage() { useTracker(window.location.pathname); if (!detail) { - return
DOMAIN HUNTER

正在打开精选域名…

; + return
玩米

正在打开精选域名…

; } const domain = detail.domain; @@ -56,6 +56,7 @@ export function FeaturedDomainPage() {
← 返回域名目录
+ ★ 精品域名

{domain.domain}

{categories.length > 0 &&
{categories.map((category) => {category})}
} {domain.description &&

{domain.description}

} diff --git a/src/client/pages/public/PublicPage.tsx b/src/client/pages/public/PublicPage.tsx index da94062..50b5b0a 100644 --- a/src/client/pages/public/PublicPage.tsx +++ b/src/client/pages/public/PublicPage.tsx @@ -7,6 +7,7 @@ import { DomainDetailDialog } from "../../components/DomainDetailDialog"; import { Toast, type ToastMessage } from "../../components/Toast"; import { useSearchHistory } from "../../hooks/useSearchHistory"; import { useTracker } from "../../hooks/useTracker"; +import { applyAccentColor } from "../../lib/accent-color"; import { api } from "../../lib/api"; import { clearCatalogueCache, loadCatalogue } from "../../lib/catalogue-cache"; import { copyText } from "../../lib/clipboard"; @@ -166,8 +167,6 @@ function pageItems(current: number, total: number): Array { return result; } -// 旧版默认品牌色:库里存这些值说明管理员从未自定义过,跳过注入、回退 CSS 新品牌色(皮革棕) -const LEGACY_ACCENT_DEFAULTS = new Set(["#2fbf9a", "#c4a242", "#b89530", "#d4b252"]); const MOBILE_CATALOGUE_QUERY = "(max-width: 720px)"; function SearchIcon() { @@ -225,11 +224,8 @@ export function PublicPage() { if (!active) return; if (settingsResult.status === "fulfilled") { setSettings(settingsResult.value); - const accent = settingsResult.value.accent_color?.trim(); - if (accent && /^#[0-9a-f]{6}$/i.test(accent) && !LEGACY_ACCENT_DEFAULTS.has(accent.toLowerCase())) { - document.documentElement.style.setProperty("--brand", accent); - } - document.title = "DOMAIN HUNTER"; + applyAccentColor(settingsResult.value.accent_color); + document.title = `${settingsResult.value.site_name} · 域名展示`; const description = document.querySelector('meta[name="description"]'); description?.setAttribute("content", settingsResult.value.site_description); if (settingsResult.value.favicon_url) { @@ -351,9 +347,9 @@ export function PublicPage() {
- + - DOMAIN HUNTER + {settings?.site_name ?? "玩米"}
{facets ? facets.total_domains.toLocaleString("zh-CN") : "—"} 个域名 @@ -365,7 +361,7 @@ export function PublicPage() {
-

DOMAIN HUNTER

+

{settings?.site_name ?? "玩米"}

- DOMAIN HUNTER Logo - @ DOMAIN HUNTER + {`${settings?.site_name + {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 {
← 返回域名目录