feat(theme): 改版为清晰蓝卡片工作台并接管 UI 库尺度
All checks were successful
Build and Deploy (tool.xpcool.com) / build-and-deploy (push) Successful in 1m10s

主色青玉改宝石蓝,字号整体放大一档(正文 16px),
通过 --td-comp-* 与 --td-font-* 接管 TDesign 控件高度与文字,
修复 theme-mode 选择器权重不足导致深色变量被 UI 库覆盖的问题。
This commit is contained in:
夏犀麟 2026-09-14 23:54:53 +08:00
parent af233e1628
commit dd38aeaf38
48 changed files with 1126 additions and 659 deletions

View File

@ -1,6 +1,12 @@
# tool.xpcool.com 变更记录
> 倒序最新在上。格式YYYY-MM-DD | 类型 | 摘要
2026-09-14 | FIX | 修复主题失效的头号坑TDesign 浅/深色变量声明在 :root[theme-mode='light'|'dark'](权重 0,2,0我们此前用 html[theme-mode='dark']0,1,1生产构建下被 UI 库深色变量反向覆盖(卡片退回 #242424、输入框描边退回 #ddd。tokens.scss 与 tdesign-theme.scss 全部改用 :root[theme-mode=...];另记录验证方法:切主题后 getComputedStyle(body).backgroundColor 可能命中计算值缓存,需用探针元素或刷新后再读
2026-09-14 | CHG | 界面改版第二版「清晰蓝 · 卡片工作台」(解决「字体/下拉/控件太小」+ 补上 UI 库主题控制):① tokens.scss主色青玉 #0c7c59 → 宝石蓝 #2563eb、中性色调改蓝灰(浅 f4f6fb/fff/0f1729深 0b0f18/131926/e9eef8、圆角放大一档xs5/sm7/md10/lg12/xl16/2xl22、字号整体上移一档正文 14→16px、最小 13px、标题阶梯同步、密度收敛space-9 80→68、版心 1120→1200、顶栏 64→68、侧栏 248→268、卡片由「无框发丝线」改回「白底 + 细描边」;② tdesign-theme.scss品牌/中性/语义色阶换蓝体系 + 深色反向中性色阶新增「UI 库尺度控制」段(--td-comp-size-m 32→40 即默认输入框/按钮/下拉高度、xl 40→48、xs/xxs/xxxs 24/20/16→28/24/18、--td-font-size-body-medium 14→16px、body-small 12→13px、title 14/16/18→16/18/20px、行高成对放宽、paddingTB/LR 放宽一档),控件尺度只改变量不再写死高度;③ 27 个 .vue 里残留的 11/12px 硬编码字号提升为 var(--tk-text-xs);④ 外壳与组件跟随MainLayout顶栏 68、命令入口 44、内容留白 32/48/72、抽屉 300、SideNav筛选框 42、导航项 padding 9/12、图标 19、HomeViewhero 52、搜索底线 60、索引行 20、服务端卡片 38px 图标、CommandPalette搜索行 64、结果项 58、kbd 22、UploadDrop缩略图 56、删除热区 36、AppBrand 36、ThemeSwitch/LocaleSwitch 40、HistoryPanel 条目 48⑤ index.html 的 theme-color 与 unocss 的 panel/code-block/hint-text 同步。构建通过vue-tsc + vite22.86s
2026-09-14 | CHG | 界面改版为「更松弛的编辑感」design-taste-frontend 技能DESIGN_VARIANCE 6 / MOTION 3 / DENSITY 3① tokens.scss 新增发丝线 --tk-hairline、排版令牌--tk-leading-*/-track-*、--tk-text-5xl、--tk-font-display、--tk-space-9=80px、顶栏高 60→64并移除未用的 --tk-track-caps② 全局排版page-title 改 clamp(26,3vw,34)+640 字重、page-desc 15px/1.75、tool-section 间距 16→32、滚动条 10→8px、reveal 位移 14→10px③ 容器去框化t-card / UnoCSS panel 改 2xl 圆角 + hairline 描边 + 大内边距(.t-card .t-card__body 24/32窄屏收回品牌图标去掉全站唯一渐变改实色方块④ 首页重写:删掉 eyebrow 与重复的 hero 链接,标题 58px 左对齐 + 底线式搜索框(聚焦加粗为 2px事实条去边框58 个工具改为「发丝线索引行」图标常态中性灰、hover 才上主色);⑤ 外壳侧栏去底色与右侧竖线hover 改用 --tk-surface 统一「抬起」方向)、筛选框改底线式、内容区留白 28→40/48/88、页脚改左右分列 + hairline⑥ HistoryPanel 改用 --tk-* 令牌与发丝线分行(此前有硬编码 --td-* 与 6px 圆角);⑦ 清理 4 份语言包中已失效的 home.eyebrow/ctaPrimary/ctaSecondary。构建通过vue-tsc + vite32.17s
2026-09-14 | CHG | 视觉重设计 + 中英日韩四语言 + 三态昼夜主题 + 服务端接口对接(一大批):① 新增 tokens.scss--tk-* 设计令牌:青玉 Jade 单一强调色、圆角/间距/字号/z-index/动效规范、按语言切换字体栈)+ tdesign-theme.scss--tk-* → --td-* 桥接含完整冷调灰阶60+ 未改动工具页零成本继承新配色;② vue-i18n 11.4.10 + zh-CN/en-US/ja-JP/ko-KR 四份完整语言包 + helpers 中文兜底;③ MainLayout/SideNav/HomeView 重写、新增 CommandPalette⌘K/LocaleSwitch显示 ZH/EN/JA/KO 不用国旗)/ThemeSwitch三态④ 新增 openTools.ts 对接 service.xpcool.com /api/service/open/tools 的 5 个接口(实测全 200改造 ip-info/uuid/hash/timestamp 并新增 random-string/api-status 两工具(均本机+服务端双模式,默认本机);⑤ 修复空分类渲染缺陷(分类 9→6、panel 快捷类缺内边距、IpInfoView 漏 onBeforeUnmount⑥ 新增 src/config.ts 集中站点常量,页脚补备案号 黔ICP备2026003933号⑦ 重写 docs/api-contract.md旧契约 /api/tools/* 与实际不符。构建通过vue-tsc + vite33.88s
2026-09-01 | CHG | 全站接入图片预览TDesign ImageViewer新增共享 AppImageViewer 组件 + useImageViewer 组合式函数;接入 9 处——UploadDrop 缩略图、Base64 还原图、图片裁剪结果、图片转 ICO 预览多尺寸成组、OCR 待识别图、二维码结果、图片压缩(原直连 t-image-viewer 重构为共享组件)、代码转图片画布、占位图画布;全局样式新增 .img-previewable

View File

@ -77,11 +77,19 @@ export interface ToolItem {
- 文本类工具:`text` 存输入原文,点击历史回填输入框。
- 文件类工具(图片压缩/裁剪等):文件本体**不要**存入 localStorage会撑爆配额改为存「文件名 | 关键参数」形式的摘要,点击历史只恢复参数。
### 3.3 主题机制
### 3.3 主题机制(色彩 + UI 库尺度两套变量)
- TDesign 通过 `document.documentElement.setAttribute('theme-mode', 'dark')` 切换暗黑模式。
- `stores/theme.ts` 负责切换 + 持久化key`tool-theme``index.html` 内联脚本在挂载前预置属性防闪烁;`main.ts` 再同步一次。
- **自定义样式一律使用 `var(--td-*)` 令牌**(如 `--td-bg-color-page`、`--td-text-color-primary`、`--td-brand-color`),禁止硬编码颜色,否则暗黑模式会破版。
- **两套变量各管一段**
- `src/assets/styles/tokens.scss` 定义 `--tk-*`(配色 / 圆角 / 间距 / **字号** / 动效),是全站唯一真相源;
- `src/assets/styles/tdesign-theme.scss``--tk-*` 映射为 `--td-*`,并**额外接管 UI 库自身的尺度**
`--td-comp-size-*`(控件高度,默认控件 40px、`--td-comp-paddingTB/LR-*`、`--td-font-size-*`、
`--td-line-height-*`。放大按钮 / 输入框 / 下拉请改这里,**不要给单个组件写死 height**。
- **自定义样式一律使用 `var(--tk-*)`(或 `var(--td-*)`)令牌**,禁止硬编码颜色与字号,否则暗黑模式会破版。
- **头号坑**:主题变量必须声明在 `:root[theme-mode='light'|'dark']` 里,**不能写 `html[theme-mode='dark']`** ——
TDesign 自己用的是前者(权重 0,2,0 且后加载者胜写成后者0,1,1会被 UI 库的深色变量盖掉,
表现为「深色下组件退回 UI 库自带灰(卡片 #242424、描边 #ddd)」。
### 3.4 公共组件

View File

@ -5,6 +5,115 @@
---
## 2026-09-14 第八批:修「字太小」+ 换风格为「清晰蓝 · 卡片工作台」+ 补上 UI 库主题控制
> 用户反馈:① 界面字体、下拉与功能控件都太小;② 再换一种风格;③ UI 库TDesign的主题此前没有真正接上
> 需要用主题变量控制组件的色彩与风格。
> 三者一并处理:换主色与密度(风格)→ 放大令牌与控件(太小)→ 用 `--td-comp-*` / `--td-font-*` 接管 TDesign 尺度(主题控制)。
> 构建通过(`vue-tsc --noEmit` + `vite build`22.86s)。
### ⚠️ 本轮挖出的头号坑:`theme-mode` 选择器权重
TDesign 的浅/深色变量声明在 `:root[theme-mode='light'|'dark']`(权重 0,2,0
而上一版我们写的是 `html[theme-mode='dark']`0,1,1—— 生产构建里即便我们的 CSS 排在 vendor CSS 之后,
**仍会被 UI 库的深色变量盖掉**。表现:深色下卡片/输入框用回 TDesign 自带灰(卡片 `#242424`、描边 `#ddd`)。
**修复tokens.scss 与 tdesign-theme.scss 全部改用 `:root[theme-mode=...]`(与 TDesign 同级权重 + 后加载者胜)。**
(验证提醒:切完主题别只看 `getComputedStyle(body).backgroundColor`,它可能命中计算值缓存,需用探针元素或刷新后再读。)
### 🎨 风格:青玉编辑感 → 宝石蓝 · 卡片工作台
- 主色:青玉 `#0c7c59` → 宝石蓝 `#2563eb`(与白底 5.17:1 过 AA深色下按钮底同色 + 白字,链接另用 `#7aa8ff`)。
- 中性色:青灰 → 蓝灰(浅 `#f4f6fb / #ffffff / #0f1729`,深 `#0b0f18 / #131926 / #e9eef8`
品牌/中性/语义三级色板与 `index.html``theme-color` 同步更新。
- 形状圆角整体放大一档xs5 / sm7 / md10 / lg12 / xl16 / 2xl22
卡片由「无框发丝线」改回「白底 + 细描边」,重新有边界感(仅列表条目分隔线仍用 `--tk-hairline`)。
- 密度:`--tk-space-9` 80→68、版心 1120→1200、顶栏 64→68、侧栏 248→268。
### 🔠 字号整体放大一档tokens.scss
- 旧 → 新:`12→13 / 13→14 / 14→16 / 15→17 / 17→19 / 20→22 / 26→28 / 34→36 / 44→46 / 58→52`。
- **正文基准 16px**,全站最小字号 13px`--tk-text-xs`)。
- **工具页里残留的 11px/12px 硬编码字号一次性提升为 `var(--tk-text-xs)`**27 个 `.vue` 文件)。
### 🧩 UI 库主题控制tdesign-theme.scss 新增第 ③ 段,本轮核心)
TDesign 组件样式完全建立在 `--td-*` 变量之上(例如 `.t-input.t-size-l { height: var(--td-comp-size-xl) }`
所以「控件太小」的正解是改变量,而不是逐个组件写 CSS
| 变量分组 | TDesign 默认 | 本版 | 影响面 |
| --- | --- | --- | --- |
| `--td-comp-size-m`(默认控件) | 32px | **40px** | 输入框 / 按钮 / 下拉 / 日期选择器 |
| `--td-comp-size-xl`large | 40px | 48px | large 尺寸控件 |
| `--td-comp-size-xs / xxs / xxxs` | 24 / 20 / 16px | 28 / 24 / 18px | 小按钮、标签、复选框 |
| `--td-font-size-body-medium` | 14px | **16px** | 控件文字(与正文同号) |
| `--td-font-size-body-small` | 12px | 13px | 小标签、说明 |
| `--td-font-size-title-*` | 14/16/18px | 16/18/20px | 卡片标题、分组标题 |
| `--td-line-height-body-*` | 20/22/24px | 22/24/26px | **必须与字号成对改**,否则大字号被行高挤住 |
| `--td-comp-paddingTB / LR-*` | size 阶梯 | 放宽一档 | 控件内边距,避免大控件里文字贴边 |
- 只覆盖 `--td-comp-*`**不动 `--td-size-*` 原子刻度**(图标/徽标/间距都在用它)。
- 深色中性色阶反向1 最深 → 14 最浅)补进 `:root[theme-mode='dark']`,避免组件在深色下退回 UI 库冷灰。
- 下拉:`--td-comp-size-m` 放大后 `.t-select-option` / `.t-dropdown__item` 行高自动跟随,仅补一条字号保险。
### 🧱 外壳、首页与公共组件(跟随新尺度)
- `MainLayout`:顶栏 68px、命令面板入口 44px、图标按钮 40px、内容区留白 32/48/72、移动端抽屉宽 300。
- `SideNav`:筛选框 42px、导航项 padding 9/12行高约 43px、图标 19px、活动指示条 3×18px。
- `HomeView`hero 标题 52px、搜索底线 60px、工具索引行 padding 20px、服务端卡片图标 38px + 轻投影。
- 公共组件:`CommandPalette`(搜索行 64px、结果项 58px、kbd 22px、`UploadDrop`(拖拽区加大、
缩略图 56px、删除按钮热区 36px、`AppBrand`(标记 36px、`ThemeSwitch` / `LocaleSwitch`40px 高)、
`HistoryPanel`(条目最小高 48px、操作按钮热区撑大
- `unocss.config.ts``panel`(改实描边 + 更大内边距)、`code-block`14px、`hint-text`13px跟随放大。
---
## 2026-09-14 第七批:界面改版 →「更松弛的编辑感」
> 用户反馈对现有主题界面仍不满意,经方向选择后定为**编辑感Editorial**
> 加大留白与字号反差,卡片几乎无边框,靠排版与呼吸感建立层次。
> 依据 `design-taste-frontend` 技能执行,旋钮 `DESIGN_VARIANCE 6` / `MOTION_INTENSITY 3` / `VISUAL_DENSITY 3`
> 构建通过(`vue-tsc --noEmit` + `vite build`32.17s)。
### 🎯 本轮解决的三个真实问题
1. **首页 hero 超标**eyebrow + 标题 + 描述 + 搜索框 + 文字链接共 5 个文字元素(超过 hero 上限 4
且 eyebrow 是模板化开场 → **删掉 eyebrow 与重复的 hero 链接**(服务端专区入口已在下方,同意图不出现两次)。
2. **58 张卡片长得一模一样**:全部 14px 圆角 + 1px 描边 + 绿底图标,重复得没有节奏
→ 改为**发丝线索引行**像书末索引图标常态中性灰、hover 才上主色。
3. **层次全靠边框与底纹堆出来**,排版本身承担的信息层级偏弱
→ 标题 58px / 说明 17px 拉开反差,分区之间改用 80px 留白而非横线。
### 🧱 令牌层tokens.scss
- 新增 `--tk-hairline`(浅 `#e7ecea` / 深 `#232b29`):内容容器与条目分隔线用它;
**功能控件仍用 `--tk-border`**,两类不要互换。
- 新增排版令牌:`--tk-text-5xl`(58px)、`--tk-leading-display/title/body`、
`--tk-track-display/title`、`--tk-font-display`。其中展示字体显式等于系统字体族 ——
中文环境下可用的展示字体就是系统字体本身,引入网络 CJK 展示字体要多付 2MB+ 首屏成本。
- `--tk-space-9`(80px) 专用于分区之间;`--tk-header-h` 60 → 64。
- 移除未使用的 `--tk-track-caps`(不在 CJK 场景下用宽字距)。
### 📐 全局排版与容器index.scss / unocss.config.ts / tdesign-theme.scss
- `.page-title``clamp(26px, 3vw, 34px)` + 字重 640`.page-desc` → 15px / 1.75 / `max-width: 62ch`
`.tool-section` 间距 16 → 32px新增 `.section-heading``.rule`
- `t-card` / UnoCSS `panel`**圆角 → 2xl、描边 → 发丝线、内边距放大一档**`p-5 md:p-7`、
`.t-card .t-card__body` 24/32窄屏在 `@media (max-width: 767px)` 收回)。
- 滚动条 10 → 8px`.reveal` 位移 14 → 10px编辑感入场要「不动声色」
### 🏠 首页重写HomeView.vue
- 抬头区58px 左对齐标题 + 一句说明 + **底线式搜索框**(聚焦时底线加粗为 2px
`box-shadow` 叠加避免 `border-width` 变化引起 1px 位移;输入框自身不画焦点环)。
- 事实条去掉全部边框与底纹,数字直接摊在纸面上。
- 服务端专区保留青玉底纹(全站唯一),是分区层级的锚点。
- 分类目录 = 标题 + 数量注记 + 说明 + 发丝线索引网格(列间距 52px避免两栏读串行
### 🧭 外壳MainLayout / SideNav / AppBrand
- 侧栏**去掉底色块与右侧竖线**与正文落在同一张纸面上hover 改用 `--tk-surface`
(浅色下更亮、深色下也更亮,两种主题都是同一种「抬起」方向)。
- 侧栏筛选框改为底线式;内容区留白 28/28/56 → **40/48/88**;页脚改左右分列 + 发丝线。
- 品牌图标**去掉全站唯一渐变**,改实色小方块(渐变会把视线从标题上拽走),
品牌辨识度改由字重与字距承担。
### ♻️ 顺带修复
- `HistoryPanel.vue` 此前混用硬编码的 `--td-*` 与写死的 `border-radius: 6px`
违反「样式一律用 `--tk-*`」的硬规则,且每条历史都套一个描边盒子;已改为 `--tk-*` 令牌 + 发丝线分行。
- 清理 4 份语言包中已失效的 `home.eyebrow` / `home.ctaPrimary` / `home.ctaSecondary`
---
## 2026-09-14 第六批:视觉重设计 + 中英日韩四语言 + 昼夜主题 + 服务端接口对接
> 依据 `design-taste-frontend` 技能BRIEF INFERENCE / 单一强调色 / 形状一致性 / 反 AI-slop 清单)重建全站视觉体系,

View File

@ -49,13 +49,36 @@
- `ToolItem.needsNetwork` 标记需联网工具(侧栏圆点 + 首页服务端工具专区)。
- `groupedTools()` / `networkTools()` / `searchTools()` 供首页、服务端专区、命令面板使用。
### 3.2 视觉体系(令牌 → TDesign 桥接)
### 3.2 视觉体系(令牌 → TDesign 桥接 + UI 库主题控制
- **唯一真相源**`src/assets/styles/tokens.scss` 定义全部 `--tk-*` 令牌(颜色/圆角/间距/字号/z-index/动效/字体栈)。
- **桥接层**`src/assets/styles/tdesign-theme.scss` 把 `--tk-*` 映射到 TDesign 的 `--td-*`
**改主题只需改 tokens.scss 一处60+ 个未改动的工具页会自动继承新配色与圆角** —— 这是低风险全站改造的关键机制。
- **硬规则**:自定义样式一律引用 `--tk-*`(或 `--td-*`**禁止硬编码色值/圆角/阴影**,否则昼夜切换会花掉。
- **形状一致性**:全站只用 tokens 里那一套圆角xs4/sm6/md8/lg10/xl14/2xl20/pill999
- **单一强调色**:青玉 Jade。全站只允许一处渐变品牌图标
- **桥接层 + UI 库主题控制**`src/assets/styles/tdesign-theme.scss` 除了把 `--tk-*` 映射到 `--td-*`
还接管两件「UI 库自身」的事:
1. **组件尺度**`--td-comp-size-*`(控件高度阶梯)、`--td-comp-paddingTB/LR-*`、`--td-font-size-*`、
`--td-line-height-*`。**TDesign 的输入框 / 按钮 / 下拉 / 日期选择器高度全部取自这些变量**
(例如 `.t-input.t-size-l { height: var(--td-comp-size-xl) }`),所以放大控件只需改这一处,
**不要再给单个组件写死高度或字号**
2. **色阶**:品牌 / 中性 / 语义的 10 级色板。注意深色下的中性色阶是**反向**的1 最深 → 14 最浅),
且必须显式覆盖,否则组件会退回 TDesign 自带的冷灰。
**改主题 = 改 tokens.scss配色/字号/圆角/间距)+ tdesign-theme.scssUI 库尺度与色阶)两处**
60+ 个未改动的工具页自动继承 —— 这是低风险全站改造的关键机制。
- **硬规则**:自定义样式一律引用 `--tk-*`(或 `--td-*`**禁止硬编码色值/圆角/阴影/字号**,否则昼夜切换会花掉。
- **形状一致性**:全站只用 tokens 里那一套圆角xs5/sm7/md10/lg12/xl16/2xl22/pill999
- **单一强调色**:宝石蓝 `#2563eb`。**全站已无任何渐变**(品牌图标为实色圆角方块)。
强调色只出现在需要「指向」的地方hover 的条目、活动导航项、服务端专区底纹、正文链接。
- **设计方向2026-09-14 第二次改版):清晰蓝 · 卡片工作台**。四条硬规则:
1. **字号整体放大一档**:正文 16px 起、说明文字不低于 13px上一版 12/13/14 的密度在 1080p 屏上偏小);
2. **控件大于内容**:输入框 / 按钮 / 下拉默认 40px 高、16px 文字,可点区域永远比正文更「实」;
3. **容器有边界**:卡片用「白底 + 细描边(`--tk-border`)」与页面底(`--tk-bg`)拉开层次,不再追求无框纸面;
4. **主色单一**:全站只有一个强调色。
- **发丝线 `--tk-hairline`**:只用于**列表条目之间的分隔线**(历史列表、首页索引行);
内容容器边界与功能控件(输入框/按钮/下拉)一律用 `--tk-border`
- **留白节奏**:分区之间用 `--tk-space-9`68px分隔**不画横线** —— 留白本身就是分隔符。
窄屏按断点阶梯式收回(`≤1279px` → `≤1023px``≤767px`),不要一档切到底。
- **排版令牌必须成对使用**:大字号配负字距 + 紧行高(`--tk-track-display/-title` +
`--tk-leading-display/-body`),小字号配正字距 + 松行高。混用(大字号配松行高)会让层级立刻垮掉。
- **首页是目录,不是卡片墙**58 个工具用「索引行」呈现(发丝线分隔),不做 58 个描边盒子;
图标常态中性灰,主色只在 hover 时出现58 个图标全染主色会把主色的含义稀释掉);
仅「服务端工具专区」用整块底纹 + 卡片与其它分区区分。
- **动效档位**:克制(上浮淡入 + 颜色过渡),`prefers-reduced-motion` 下全部降为 0ms。
- `v-reveal` 指令(`src/directives/reveal.ts`)做滚动进入,传数字错峰。
@ -113,6 +136,8 @@
- **Vue 模板 attribute**:内嵌字符串不能直接写 `\"`HTML 解析器会把 `"` 当属性结束符导致编译错误),字符串常量移到 `<script>` 定义。
- **`in` 运算符**`k in obj` 中 obj 需先断言为 `Record<string, unknown>`,否则 strict 模式 TS2638/TS18048。
- **构建安全删除保护**`pnpm build` 清理 dist 会触发沙箱批量删除拦截(>50 文件),先 `mv dist dist.bak-<ts>` 再构建。
- **备份目录同样删不掉**`dist.bak-*` 的递归删除也会被 bulk guard 拦(`SAFE_DELETE_BULK_GUARD_ERROR`
因此多次构建会堆积若干 `dist.bak-*`,属正常现象;需要清理时逐个目录删,或交由人手动处理。
- **大整数**:进制转换等工具禁用 `parseInt/Number`,用 BigInt 逐位解析(现有 radix-convert 已实现)。
- **pnpm 调用方式**`corepack` 的 `pnpm` shell wrapper 在 Git Bash 下会把 `/c/...` 误算成 `E:\c\...`
(报 `Cannot find module 'E:\c\...\corepack\dist\pnpm.js'`)。绕过 wrapper显式调用
@ -123,10 +148,22 @@
### 6.2 样式与主题
- **禁止硬编码色值**:一律用 `--tk-*`(自定义样式)或 `--td-*`TDesign 组件覆盖)。
- **UnoCSS `panel` 快捷类自带内边距**`p-5 md:p-6`)。页面 scoped 样式(如 `.block[data-v-*]`)权重更高,
需要单独覆盖 padding 时照常写即可。
- **UnoCSS `panel` 快捷类自带内边距**`p-5 md:p-7`,编辑感改版后放大一档)且描边为发丝线。
页面 scoped 样式(如 `.block[data-v-*]`)权重更高,需要单独覆盖 padding 时照常写即可。
- **卡片内边距的覆盖写法**`.t-card .t-card__body`(两个类)比 TDesign 自身的 `.t-card__body` 高一级,
能稳定覆盖;只写一个类会被 TDesign 的规则压掉。窄屏需在 `@media (max-width: 767px)` 里收回。
- **`--td-radius-*` 刻意不跟随主题**(浅深共用),改圆角请改 `tokens.scss``--tk-radius-*`
- **canvas 绘制**:注意主题切换,尽量用令牌色或中性色,不要写死黑白。
- **头号坑:`theme-mode` 选择器必须写 `:root[theme-mode='dark']`,不能写 `html[theme-mode='dark']`**
TDesign 自己的浅/深色变量声明用的是 `:root[theme-mode=...]`(权重 0,2,0
`html[theme-mode=...]` 只有 (0,1,1) —— **生产构建里我们的 CSS 虽然排在 vendor CSS 之后,仍会被 UI 库盖掉**
表现:深色下组件用回 TDesign 自带灰(卡片 `#242424`、描边 `#ddd`),只有少量手写规则生效。
新增主题变量时请放进 tokens.scss / tdesign-theme.scss 里那两个 `:root[theme-mode=...]` 块。
- **验证主题是否真的生效**:切完 `theme-mode` 后**不要**只看 `getComputedStyle(document.body).backgroundColor`
它可能命中浏览器的计算值缓存、读出「上一次主题」的颜色。可靠做法:插入探针元素读
`div.style.backgroundColor = 'var(--tk-bg)'` 后读 computed或**刷新页面后再读**。
- **控件尺度统一由 `--td-comp-size-*` 控制**:放大/缩小按钮、输入框、下拉请改 `tdesign-theme.scss` 的尺度段,
不要给单个组件写死 `height`(会与 UI 库其它组件脱节)。
### 6.3 国际化
- **`html``lang` 属性写的是完整标签**`ja-JP` / `ko-KR`),因此 `tokens.scss` 里的字体栈选择器

View File

@ -7,8 +7,8 @@
name="description"
content="纯前端在线工具箱图片压缩与裁剪、二维码、JSON 格式化、时间戳、Base64、JWT、Cron、进制转换、哈希、身份证解析、OCR 等 56 个实用工具,支持中英日韩四种语言与日间夜间主题,数据全部在浏览器本地处理。"
/>
<meta name="theme-color" content="#f6f8f8" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0c100f" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#f4f6fb" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#0b0f18" media="(prefers-color-scheme: dark)" />
<title>在线工具箱 · Tool</title>
<!--
防闪烁FOUC内联脚本必须在样式与模块加载之前同步执行。

View File

@ -3,11 +3,14 @@
* ------------------------------------------------------------
* 引入顺序很重要
* 1. tokens.scss 设计令牌--tk-*
* 2. tdesign-theme.scss 令牌 TDesign 变量--td-*
* 2. tdesign-theme.scss 令牌 TDesign 变量--td-*+ UI 库尺度控制
* 3. 本文件其余部分 基础样式与工具页通用类
*
* 所有颜色只允许来自 --tk-* / --td-*禁止硬编码色值
* 这样昼夜主题切换才能全站一致生效
*
* 字号只允许来自 --tk-text-*本版把正文提到 16px
* 新增样式请勿写死 12/13px否则又会回到字太小的老问题
* ============================================================ */
@use './tokens';
@ -38,8 +41,10 @@ body {
body {
font-family: var(--tk-font-sans);
/* 正文基准字号 = --tk-text-base16px全站最小可见文字是 13px--tk-text-xs
* 主要出现在辅助说明与元信息上不再有 12px 的正文 */
font-size: var(--tk-text-base);
line-height: 1.6;
line-height: 1.7;
background-color: var(--tk-bg);
color: var(--tk-text);
-webkit-font-smoothing: antialiased;
@ -77,7 +82,7 @@ body,
left: 12px;
top: 12px;
z-index: var(--tk-z-palette);
padding: 8px 14px;
padding: 10px 16px;
border-radius: var(--tk-radius-md);
background: var(--tk-surface-raised);
border: 1px solid var(--tk-border);
@ -102,14 +107,15 @@ img {
max-width: 100%;
}
/* ---------- 滚动条美化 ---------- */
/* ---------- 滚动条美化 ----------
* 字号与控件都放大了一档滚动条同步加粗到 10px否则不好点中 */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--td-scrollbar-color);
border: 3px solid transparent;
border: 2px solid transparent;
background-clip: content-box;
border-radius: var(--tk-radius-pill);
}
@ -125,7 +131,7 @@ img {
* 工具页通用区块60+ 个页面共用改这里即全站生效
* ============================================================ */
.tool-section {
margin-bottom: var(--tk-space-4);
margin-bottom: var(--tk-space-6);
}
/* 结果展示区:长文本自动换行 */
@ -134,23 +140,43 @@ img {
white-space: pre-wrap;
}
/* 页面标题与描述 */
/* 页面标题与描述
* 标题明显大于正文层级靠字号反差立住移动端通过 clamp 下限回到 28px
* 避免中文标题在窄屏被拆成三行 */
.page-title {
margin: 0 0 6px;
font-size: var(--tk-text-2xl);
margin: 0 0 12px;
font-family: var(--tk-font-display);
font-size: clamp(28px, 3vw, 38px);
font-weight: 650;
letter-spacing: -0.02em;
line-height: 1.25;
letter-spacing: var(--tk-track-title);
line-height: var(--tk-leading-title);
color: var(--tk-text);
}
.page-desc {
margin: 0 0 var(--tk-space-5);
max-width: 68ch;
margin: 0 0 var(--tk-space-7);
max-width: 62ch;
font-size: var(--tk-text-base);
line-height: 1.65;
line-height: var(--tk-leading-body);
color: var(--tk-text-secondary);
}
/* 分区标题:比页面标题低一档,用于工具页内部把长表单切成若干段落 */
.section-heading {
margin: var(--tk-space-6) 0 var(--tk-space-4);
font-size: var(--tk-text-lg);
font-weight: 620;
letter-spacing: var(--tk-track-title);
color: var(--tk-text);
}
/* 行分隔线:统一用发丝线,不要画成带底色的长条 */
.rule {
height: 1px;
border: none;
margin: 0;
background: var(--tk-hairline);
}
/* 表单标签(各工具页统一宽度,保证左侧对齐) */
.form-label {
flex-shrink: 0;
@ -162,14 +188,15 @@ img {
.form-actions {
display: flex;
flex-wrap: wrap;
gap: var(--tk-space-2);
margin-top: var(--tk-space-4);
gap: var(--tk-space-3);
margin-top: var(--tk-space-5);
}
/* 单行说明/元信息 */
.field-hint {
margin: var(--tk-space-2) 0 0;
font-size: var(--tk-text-sm);
line-height: 1.6;
color: var(--tk-text-placeholder);
}
@ -189,7 +216,7 @@ img {
}
/* ============================================================
* 滚动进入动效MOTION 档位 4/10只做轻微上浮淡入不做视差/钉住
* 滚动进入动效只做轻微上浮淡入不做视差/钉住
* v-reveal 指令添加 .is-revealed JS 时元素始终可见不会白屏
* ============================================================ */
.reveal {
@ -199,7 +226,7 @@ img {
@media (prefers-reduced-motion: no-preference) {
.reveal {
opacity: 0;
transform: translateY(14px);
transform: translateY(12px);
transition:
opacity var(--tk-dur-slow) var(--tk-ease-out),
transform var(--tk-dur-slow) var(--tk-ease-out);

View File

@ -1,11 +1,25 @@
/* ============================================================
* TDesign 主题桥接层
* TDesign 主题控制层UI 库主题
* ------------------------------------------------------------
* tokens.scss 里的 --tk-* 设计令牌映射到 TDesign --td-* 变量
* 本文件负责控制 UI 库组件本身的色彩与风格分四件事
* 色阶品牌蓝 / 中性蓝灰 / 语义色的 10 级色板我们的色喂给 TDesign
* 语义角色 tokens.scss --tk-* 映射到 TDesign --td-*
* 组件尺度控件高度 / 内边距 / 字号 / 行高本版为解决控件太小新增
* 组件级微调卡片输入框按钮弹层的外形修正
*
* 这样做的好处60+ 个工具页面里已有的大量 t-card / t-button /
* t-input / t-tag 无需改一行代码就整体换上了新的青玉配色与圆角体系
* 属于一次改主题全站生效的低风险改造方式
* 为什么这样做60+ 个工具页面里已有大量 t-card / t-button / t-input /
* t-select / t-tag全部改一遍既不现实也容易漏TDesign 的组件样式
* 完全建立在 --td-* 变量之上例如 .t-input.t-size-l 的高度取自
* var(--td-comp-size-xl)所以改变量 = 全站组件改版属最小风险改造
*
* 三条必须遵守的约束踩过坑
* 1. 只覆盖 TDesign 真实存在的变量名写错名字不会报错只是静默不生效
* 新增前请在 node_modules/tdesign-vue-next 的样式产物里确认
* 2. --td-font-body-* 是复合变量size/line-height 简写
* 所以放大字号时必须同步改 --td-font-size-* --td-line-height-*
* 只改前者会出现大字号被小行高挤住的破版
* 3. 尺度变量不要动 --td-size-N 阶梯本身那是 TDesign 的原子刻度
* 图标徽标间距都在用只覆盖 --td-comp-size-* 一层
*
* 命名桥接对应关系速查
* --tk-accent --td-brand-color 按钮/主色底
@ -19,18 +33,18 @@
* 浅色主题
* ============================================================ */
:root,
html[theme-mode='light'] {
/* ---------- 品牌色阶(青玉1 最浅 → 10 最深) ---------- */
--td-brand-color-1: #e9f7f2;
--td-brand-color-2: #c6eddf;
--td-brand-color-3: #99dfc8;
--td-brand-color-4: #66cead;
--td-brand-color-5: #36bc93;
--td-brand-color-6: #17a37b;
--td-brand-color-7: #0c7c59;
--td-brand-color-8: #096548;
--td-brand-color-9: #074e38;
--td-brand-color-10: #053827;
:root[theme-mode='light'] {
/* ---------- 品牌色阶(宝石蓝1 最浅 → 10 最深) ---------- */
--td-brand-color-1: #eff4ff;
--td-brand-color-2: #dbe6fe;
--td-brand-color-3: #c0d4fe;
--td-brand-color-4: #96b6fd;
--td-brand-color-5: #6294fb;
--td-brand-color-6: #3b76f6;
--td-brand-color-7: #2563eb;
--td-brand-color-8: #1d4ed8;
--td-brand-color-9: #1e3fb0;
--td-brand-color-10: #1b3690;
/* ---------- 语义色阶 ---------- */
--td-success-color-1: #e6f7ee;
@ -66,30 +80,29 @@ html[theme-mode='light'] {
--td-error-color-9: #4c1613;
--td-error-color-10: #320d0b;
/* ---------- 中性色阶冷调1 最浅 14 最深
/* ---------- 中性色阶蓝灰1 最浅 14 最深
* 深浅两套主题共用同一条色阶浅色主题取低号深色主题取高号 ---------- */
--td-gray-color-1: #f6f8f8;
--td-gray-color-2: #eef1f0;
--td-gray-color-3: #e3e8e6;
--td-gray-color-4: #d3dad7;
--td-gray-color-5: #c2cbc8;
--td-gray-color-6: #a3afab;
--td-gray-color-7: #7c8885;
--td-gray-color-8: #5a6562;
--td-gray-color-9: #3e4845;
--td-gray-color-10: #333c39;
--td-gray-color-11: #252d2b;
--td-gray-color-12: #1a201e;
--td-gray-13: #131817;
--td-gray-color-13: #131817;
--td-gray-color-14: #0c100f;
--td-gray-color-1: #f4f6fb;
--td-gray-color-2: #eef2f9;
--td-gray-color-3: #e2e8f3;
--td-gray-color-4: #ccd7e8;
--td-gray-color-5: #b1bdd3;
--td-gray-color-6: #8f9cb4;
--td-gray-color-7: #6e7994;
--td-gray-color-8: #55617a;
--td-gray-color-9: #3d4759;
--td-gray-color-10: #2e3747;
--td-gray-color-11: #263147;
--td-gray-color-12: #1b2333;
--td-gray-color-13: #131926;
--td-gray-color-14: #0b0f18;
/* ---------- 语义角色 ---------- */
--td-brand-color: var(--tk-accent);
--td-brand-color-hover: var(--tk-accent-hover);
--td-brand-color-active: var(--tk-accent-active);
--td-brand-color-focus: var(--tk-accent-surface-strong);
--td-brand-color-disabled: #99dfc8;
--td-brand-color-disabled: #96b6fd;
--td-brand-color-light: var(--tk-accent-surface);
--td-brand-color-light-hover: var(--tk-accent-surface-strong);
@ -136,19 +149,19 @@ html[theme-mode='light'] {
--td-bg-color-specialcomponent: var(--tk-surface-raised);
/* 遮罩:用带色相的深色,避免死黑 */
--td-mask-active: rgba(9, 20, 16, 0.55);
--td-mask-disabled: rgba(252, 253, 253, 0.6);
--td-mask-active: rgba(9, 15, 32, 0.55);
--td-mask-disabled: rgba(255, 255, 255, 0.6);
--td-mask-background: var(--tk-surface);
/* ---------- 文字 ---------- */
--td-text-color-primary: var(--tk-text);
--td-text-color-secondary: var(--tk-text-secondary);
--td-text-color-placeholder: var(--tk-text-placeholder);
--td-text-color-disabled: #a3afab;
--td-text-color-disabled: #b1bdd3;
--td-text-color-anti: var(--tk-text-on-accent);
--td-text-color-brand: var(--tk-accent-text);
--td-text-color-link: var(--tk-accent-text);
--td-text-color-watermark: rgba(17, 26, 23, 0.08);
--td-text-color-watermark: rgba(15, 23, 41, 0.08);
/* ---------- 描边 ---------- */
--td-border-level-1-color: var(--tk-border);
@ -173,11 +186,11 @@ html[theme-mode='light'] {
--td-shadow-inset-right: inset 0.5px 0 0 var(--tk-border);
--td-shadow-inset-bottom: inset 0 -0.5px 0 var(--tk-border);
--td-shadow-inset-left: inset -0.5px 0 0 var(--tk-border);
--td-table-shadow-color: rgba(16, 32, 28, 0.08);
--td-table-shadow-color: rgba(15, 23, 41, 0.08);
/* ---------- 滚动条 ---------- */
--td-scrollbar-color: rgba(17, 26, 23, 0.16);
--td-scrollbar-hover-color: rgba(17, 26, 23, 0.32);
--td-scrollbar-color: rgba(15, 23, 41, 0.18);
--td-scrollbar-hover-color: rgba(15, 23, 41, 0.34);
--td-scroll-track-color: transparent;
/* ---------- 字体 ---------- */
@ -188,18 +201,18 @@ html[theme-mode='light'] {
/* ============================================================
* 深色主题
* ============================================================ */
html[theme-mode='dark'] {
:root[theme-mode='dark'] {
/* ---------- 品牌色阶(深色下 1 最深 → 10 最浅) ---------- */
--td-brand-color-1: #0a2e22;
--td-brand-color-2: #0b3d2d;
--td-brand-color-3: #0c4d39;
--td-brand-color-4: #0d5c44;
--td-brand-color-5: #0e6f52;
--td-brand-color-6: #0e8460;
--td-brand-color-7: #10946b;
--td-brand-color-8: #2aa87c;
--td-brand-color-9: #3fc79b;
--td-brand-color-10: #78dfba;
--td-brand-color-1: #16244d;
--td-brand-color-2: #1a2d63;
--td-brand-color-3: #1e377c;
--td-brand-color-4: #234298;
--td-brand-color-5: #2a4fb5;
--td-brand-color-6: #3059d6;
--td-brand-color-7: #2563eb;
--td-brand-color-8: #4a80f5;
--td-brand-color-9: #7aa8ff;
--td-brand-color-10: #b3ccff;
/* ---------- 语义色阶 ---------- */
--td-success-color-1: #06291a;
@ -236,13 +249,13 @@ html[theme-mode='dark'] {
--td-error-color-10: #f2aca4;
/* ---------- 语义角色 ----------
* 注意深色下按钮底压深到 #0e8460与白字 4.68:1
* 注意深色下按钮底保持 #2563eb与白字 5.17:1
* 而链接/图标另用更亮的 --tk-accent-text 保证在深底上可读 */
--td-brand-color: var(--tk-accent);
--td-brand-color-hover: var(--tk-accent-hover);
--td-brand-color-active: var(--tk-accent-active);
--td-brand-color-focus: var(--tk-accent-surface-strong);
--td-brand-color-disabled: #0c4d39;
--td-brand-color-disabled: #1e377c;
--td-brand-color-light: var(--tk-accent-surface);
--td-brand-color-light-hover: var(--tk-accent-surface-strong);
@ -279,28 +292,28 @@ html[theme-mode='dark'] {
--td-bg-color-secondarycontainer: var(--tk-surface-sunken);
--td-bg-color-secondarycontainer-hover: var(--tk-surface-hover);
--td-bg-color-secondarycontainer-active: var(--tk-surface-active);
--td-bg-color-component: #1b2220;
--td-bg-color-component-hover: #222a28;
--td-bg-color-component-active: #2b3331;
--td-bg-color-component-disabled: #171d1b;
--td-bg-color-secondarycomponent: #222a28;
--td-bg-color-secondarycomponent-hover: #2b3331;
--td-bg-color-secondarycomponent-active: #343c3a;
--td-bg-color-component: var(--tk-surface-sunken);
--td-bg-color-component-hover: var(--tk-surface-hover);
--td-bg-color-component-active: var(--tk-surface-active);
--td-bg-color-component-disabled: var(--tk-surface-sunken);
--td-bg-color-secondarycomponent: var(--tk-surface-hover);
--td-bg-color-secondarycomponent-hover: var(--tk-surface-active);
--td-bg-color-secondarycomponent-active: var(--tk-border-strong);
--td-bg-color-specialcomponent: var(--tk-surface-raised);
--td-mask-active: rgba(0, 0, 0, 0.72);
--td-mask-disabled: rgba(19, 24, 23, 0.6);
--td-mask-disabled: rgba(19, 25, 38, 0.6);
--td-mask-background: var(--tk-surface);
/* ---------- 文字 ---------- */
--td-text-color-primary: var(--tk-text);
--td-text-color-secondary: var(--tk-text-secondary);
--td-text-color-placeholder: var(--tk-text-placeholder);
--td-text-color-disabled: #5a6562;
--td-text-color-disabled: #55617a;
--td-text-color-anti: var(--tk-text-on-accent);
--td-text-color-brand: var(--tk-accent-text);
--td-text-color-link: var(--tk-accent-text);
--td-text-color-watermark: rgba(232, 237, 235, 0.1);
--td-text-color-watermark: rgba(233, 238, 248, 0.1);
/* ---------- 描边 ---------- */
--td-border-level-1-color: var(--tk-border);
@ -308,7 +321,7 @@ html[theme-mode='dark'] {
--td-border-level-2-color: var(--tk-border-strong);
--td-component-border: var(--tk-border-strong);
/* ---------- 圆角 / 阴影 / 滚动条 / 字体(与主题无关,沿用令牌) ---------- */
/* ---------- 圆角 / 阴影 / 滚动条 / 字体 ---------- */
--td-radius-small: var(--tk-radius-sm);
--td-radius-default: var(--tk-radius-md);
--td-radius-medium: var(--tk-radius-lg);
@ -326,8 +339,8 @@ html[theme-mode='dark'] {
--td-shadow-inset-left: inset -0.5px 0 0 var(--tk-border);
--td-table-shadow-color: rgba(0, 0, 0, 0.3);
--td-scrollbar-color: rgba(232, 237, 235, 0.16);
--td-scrollbar-hover-color: rgba(232, 237, 235, 0.32);
--td-scrollbar-color: rgba(233, 238, 248, 0.18);
--td-scrollbar-hover-color: rgba(233, 238, 248, 0.34);
--td-scroll-track-color: transparent;
--td-font-family: var(--tk-font-sans);
@ -335,23 +348,131 @@ html[theme-mode='dark'] {
}
/* ============================================================
* 少量组件级微调
* TDesign 默认的字重/描边在青玉配色下略重这里做轻量化处理
* UI 库组件尺度控制本版新增解决控件太小
* ------------------------------------------------------------
* 与主题无关因此三个选择器都要写且必须用 :root[theme-mode=...] 这一
* TDesign 同级权重的写法否则会被 UI 库自身的深色变量盖掉
* 直接改 --td-size-* 会连带影响图标/徽标所以只覆盖 --td-comp-* 一层
*
* 放大前后对照TDesign 默认 本版
* 默认输入框/按钮/下拉 32px 40px
* 大号控件 40px 48px
* 小号控件 24px 28px
* 控件文字 14px 16px与正文同号读起来不再小一号
* ============================================================ */
/* ============================================================
* 深色主题的中性色阶补在主题块之外便于与尺度控制放在一起
* ------------------------------------------------------------
* TDesign 在深色下使用反向灰阶1 最深 14 最浅
* 不覆盖的话组件会退回 UI 库自带的冷灰与本主题的蓝灰体系脱节
* ============================================================ */
:root[theme-mode='dark'] {
--td-gray-color-1: #0b0f18;
--td-gray-color-2: #131926;
--td-gray-color-3: #1b2333;
--td-gray-color-4: #222c3f;
--td-gray-color-5: #263147;
--td-gray-color-6: #35415c;
--td-gray-color-7: #4a5670;
--td-gray-color-8: #6e7994;
--td-gray-color-9: #8f9cb4;
--td-gray-color-10: #b1bdd3;
--td-gray-color-11: #ccd7e8;
--td-gray-color-12: #e2e8f3;
--td-gray-color-13: #eef2f9;
--td-gray-color-14: #f4f6fb;
}
:root,
:root[theme-mode='light'],
:root[theme-mode='dark'] {
/* ---------- 控件高度阶梯 ---------- */
--td-comp-size-xxxs: 18px; /* 复选框、小圆点 */
--td-comp-size-xxs: 24px; /* 小号控件t-tag / 紧凑按钮) */
--td-comp-size-xs: 28px;
--td-comp-size-s: 32px;
--td-comp-size-m: 40px; /* ★ 默认控件:输入框 / 按钮 / 下拉 / 日期选择 */
--td-comp-size-l: 44px;
--td-comp-size-xl: 48px; /* large 尺寸输入框、下拉触发框 */
--td-comp-size-xxl: 52px;
--td-comp-size-xxxl: 60px;
/* ---------- 控件内边距:随高度同步放宽,否则大控件里的文字会贴边 ---------- */
--td-comp-paddingTB-xxs: 4px;
--td-comp-paddingTB-xs: 6px;
--td-comp-paddingTB-s: 8px;
--td-comp-paddingTB-m: 12px;
--td-comp-paddingTB-l: 14px;
--td-comp-paddingTB-xl: 20px;
--td-comp-paddingLR-xxs: 6px;
--td-comp-paddingLR-xs: 8px;
--td-comp-paddingLR-s: 12px;
--td-comp-paddingLR-m: 16px;
--td-comp-paddingLR-l: 20px;
--td-comp-paddingLR-xl: 28px;
/* ---------- 字号:控件文字提到 16px与正文同号 ---------- */
--td-font-size-body-small: 13px;
--td-font-size-body-medium: 16px;
--td-font-size-body-large: 17px;
--td-font-size-title-small: 16px;
--td-font-size-title-medium: 18px;
--td-font-size-title-large: 20px;
--td-font-size-link-small: 13px;
--td-font-size-link-medium: 15px;
--td-font-size-link-large: 17px;
--td-font-size-mark-small: 13px;
--td-font-size-mark-medium: 15px;
/* ---------- 行高:必须与上面成对修改,否则大字号会被小行高挤住 ---------- */
--td-line-height-body-small: 22px;
--td-line-height-body-medium: 24px;
--td-line-height-body-large: 26px;
--td-line-height-title-small: 24px;
--td-line-height-title-medium: 28px;
--td-line-height-title-large: 30px;
--td-line-height-link-small: 22px;
--td-line-height-link-medium: 24px;
--td-line-height-link-large: 26px;
--td-line-height-mark-small: 22px;
--td-line-height-mark-medium: 24px;
}
/* ============================================================
* 组件级微调清晰蓝 · 卡片工作台
* ------------------------------------------------------------
* 一条贯穿全站的原则
* 内容容器卡片/面板 白底 + 细描边 + 轻投影与页面底拉开层次
* 功能控件输入框/按钮/下拉 更实的描边 + 明确的聚焦反馈
* 本版与上一版最大的区别就在这里卡片重新有形不再是无框纸面
* ============================================================ */
/* 卡片默认无投影靠描边区分层次hover 才浮起(避免整页都是悬浮块) */
/* 卡片:轻投影 + 细描边hover 时抬到第二档阴影 */
.t-card {
box-shadow: none;
border-radius: var(--tk-radius-xl);
box-shadow: var(--tk-shadow-1);
}
.t-card--bordered {
border-color: var(--tk-border);
box-shadow: none;
}
.t-card--shadow {
box-shadow: var(--tk-shadow-2);
}
.t-card__header {
font-weight: 600;
letter-spacing: -0.01em;
font-weight: 620;
font-size: var(--tk-text-lg);
letter-spacing: var(--tk-track-title);
}
.t-card__title {
letter-spacing: var(--tk-track-title);
}
/* 卡片内边距放大一档;窄屏收回默认值,否则手机上内容可用宽度会不够 */
.t-card .t-card__body {
padding: var(--tk-space-5) var(--tk-space-6);
}
/* 输入框:聚焦时用主色描边 + 柔和外发光,替代默认的粗描边 */
/* 输入框 / 文本域:聚焦时用主色描边 + 柔和外发光 */
.t-input__wrap,
.t-textarea__inner {
transition:
@ -368,6 +489,12 @@ html[theme-mode='dark'] {
transform: translateY(1px);
}
/* 下拉/选择器的选项行:默认行高偏挤,放大后一屏可见项变少但扫读更快 */
.t-select-option,
.t-dropdown__item {
font-size: var(--td-font-size-body-medium);
}
/* 弹层:统一使用较大的圆角与柔和阴影 */
.t-popup__content,
.t-dialog,
@ -375,5 +502,13 @@ html[theme-mode='dark'] {
border-radius: var(--tk-radius-xl);
}
.t-dialog__header {
font-weight: 600;
font-weight: 620;
letter-spacing: var(--tk-track-title);
}
/* 窄屏:大内边距在手机上会挤掉内容宽度,这里整体收回到常规密度 */
@media (max-width: 767px) {
.t-card .t-card__body {
padding: var(--tk-space-4);
}
}

View File

@ -4,54 +4,87 @@
* 站点唯一的视觉真相来源所有自定义样式只允许引用本文件
* 定义的 --tk-* 变量禁止在组件里硬编码颜色/圆角/阴影
*
* 风格定位清晰蓝 · 卡片工作台Clear Blue Workspace
* ------------------------------------------------------------
* 与上一版编辑感青玉 + 大留白 + 发丝线的区别也是本版的四条硬规则
* 字号整体放大一档正文 16px 起步说明文字不低于 13px
* 上一版 12/13/14 的密度在 1080p 以上屏幕上确实偏小
* 控件大于内容输入框/按钮/下拉默认 40px 15px 文字
* 控件是手上要碰的东西必须比正文更实更大
* 容器有边界卡片用白底 + 细描边 + 轻投影与页面底拉开层次
* 不再追求上一版那种一张纸铺到底的无框版式
* 主色单一全站只有一个强调色宝石蓝不做第二强调色
*
* 主题通过 html 上的属性决定
* theme-mode = light | dark theme store 计算后的实际生效主题
* theme-lang = zh-CN | en-US | ja-JP | ko-KR lang 属性同步
*
* 中性色冷调石板灰不带暖黄与青玉主色同色系
* 强调色青玉 Jade单一强调色全站一致不做第二强调色
* 改动本文件前请先读 tdesign-theme.scss 顶部的桥接说明
* 这里只放我们的令牌TDesign 组件的尺度与色阶在那边控制
* ============================================================ */
/* ---------- 与主题无关的常量令牌 ---------- */
:root {
/* 圆角规范形状一致性全站只用这一套
* 控件 8px / 小控件 6px / 弹层 10px / 卡片 14px / 大面板 20px / 胶囊 999px */
--tk-radius-xs: 4px;
--tk-radius-sm: 6px;
--tk-radius-md: 8px;
--tk-radius-lg: 10px;
--tk-radius-xl: 14px;
--tk-radius-2xl: 20px;
* 控件 10px / 小控件 7px / 弹层 12px / 卡片 16px / 大面板 22px / 胶囊 999px
* 比上一版整体加大一档配合放大的字号观感更圆润友好 */
--tk-radius-xs: 5px;
--tk-radius-sm: 7px;
--tk-radius-md: 10px;
--tk-radius-lg: 12px;
--tk-radius-xl: 16px;
--tk-radius-2xl: 22px;
--tk-radius-pill: 999px;
/* 间距节奏8px 基准) */
/* 间距节奏4px 基准常用档位是 4 的倍数
* 本版留白比编辑感版式收敛字号已经变大留白再拉满会让页面显得空 */
--tk-space-1: 4px;
--tk-space-2: 8px;
--tk-space-3: 12px;
--tk-space-4: 16px;
--tk-space-5: 24px;
--tk-space-6: 32px;
--tk-space-7: 48px;
--tk-space-8: 64px;
--tk-space-5: 20px;
--tk-space-6: 28px;
--tk-space-7: 40px;
--tk-space-8: 56px;
/* space-9 专门用于「分区之间」的留白 */
--tk-space-9: 68px;
/* 版心与栅格 */
--tk-shell-max: 1560px;
--tk-content-max: 1120px;
--tk-header-h: 60px;
--tk-aside-w: 248px;
--tk-shell-max: 1600px;
--tk-content-max: 1200px;
--tk-header-h: 68px;
--tk-aside-w: 268px;
/* 字号阶梯 */
--tk-text-xs: 12px;
--tk-text-sm: 13px;
--tk-text-base: 14px;
--tk-text-md: 15px;
--tk-text-lg: 17px;
--tk-text-xl: 20px;
--tk-text-2xl: 26px;
/* 字号阶梯本版的核心改动整体上移一档
* 1213 / 1314 / 1416 / 1517 / 1719 / 2022 / 2628 ...
* 请勿在组件里退回 12px那正是上一版字太小的根因 */
--tk-text-xs: 13px;
--tk-text-sm: 14px;
--tk-text-base: 16px;
--tk-text-md: 17px;
--tk-text-lg: 19px;
--tk-text-xl: 22px;
--tk-text-2xl: 28px;
--tk-text-3xl: 34px;
--tk-text-4xl: 44px;
--tk-text-4xl: 42px;
--tk-text-5xl: 52px;
/* 数字等宽(统计值用,避免数字跳动) */
/* 行高与字距
* 规则成对使用大字标题用 leading-title + 负字距正文用 leading-body + 正常字距
* 本版负字距比编辑感版式收得更小-0.022em因为字号变大后过紧的中文标题会糊 */
--tk-leading-display: 1.12;
--tk-leading-title: 1.3;
--tk-leading-body: 1.72;
--tk-track-display: -0.022em;
--tk-track-title: -0.012em;
/* 展示字体中文环境下可用的展示字体就是系统字体本身
*PingFang / 微软雅黑 的中黑字重在标题上已经足够有性格
* 引入网络 CJK 展示字体会让首屏多出 2MB+ 负担得不偿失
* 因此这里显式声明为与正文同族留出未来替换的单一改点 */
--tk-font-display: var(--tk-font-sans);
/* 数字等宽(统计值/时间用,避免数字跳动) */
--tk-num-font: ui-monospace, 'SF Mono', 'JetBrains Mono', 'Cascadia Mono', Consolas,
'Liberation Mono', monospace;
@ -65,7 +98,7 @@
--tk-z-toast: 400;
--tk-z-palette: 500;
/* 动效曲线:进出对称的缓出曲线,时长克制MOTION 档位 4/10 */
/* 动效曲线:进出对称的缓出曲线,时长克制 */
--tk-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--tk-ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
--tk-dur-fast: 140ms;
@ -102,102 +135,104 @@ html {
* 浅色主题
* ============================================================ */
:root,
html[theme-mode='light'] {
:root[theme-mode='light'] {
color-scheme: light;
/* 强调色:青玉。与白底对比度 5.19:1正文级 AA 通过) */
--tk-accent: #0c7c59;
--tk-accent-hover: #0a6b4c;
--tk-accent-active: #085a40;
--tk-accent-ring: rgba(12, 124, 89, 0.24);
/* 低饱和青玉底纹:分类图标底、选中态、标签底 */
--tk-accent-surface: rgba(12, 124, 89, 0.08);
--tk-accent-surface-strong: rgba(12, 124, 89, 0.14);
/* 强调色:宝石蓝。与白底对比 5.17:1正文级 AA 通过) */
--tk-accent: #2563eb;
--tk-accent-hover: #1d4ed8;
--tk-accent-active: #1e40af;
/* 强调文字(链接/图标):比按钮底色略深,保证在白底上的可读性 */
--tk-accent-text: #1d4ed8;
--tk-accent-ring: rgba(37, 99, 235, 0.22);
/* 低饱和蓝底纹:分类图标底、选中态、标签底 */
--tk-accent-surface: rgba(37, 99, 235, 0.07);
--tk-accent-surface-strong: rgba(37, 99, 235, 0.13);
/* 中性表面(冷调) */
--tk-bg: #f6f8f8;
--tk-surface: #fcfdfd;
/* 中性表面冷调蓝灰与主色同色系
* 页面底刻意比容器暗一档卡片要靠这个差值起来而不是只靠投影 */
--tk-bg: #f4f6fb;
--tk-surface: #ffffff;
--tk-surface-raised: #ffffff;
--tk-surface-sunken: #f1f4f3;
--tk-surface-hover: #f3f6f5;
--tk-surface-active: #ebefee;
--tk-surface-sunken: #eef2f9;
--tk-surface-hover: #f2f6fd;
--tk-surface-active: #e6edfa;
/* 描边 */
--tk-border: #e3e8e6;
--tk-border-strong: #d3dad7;
--tk-border-subtle: #eef1f0;
--tk-border: #e2e8f3;
--tk-border-strong: #ccd7e8;
--tk-border-subtle: #edf1f8;
/* 发丝线只用于列表条目的分隔线历史列表首页索引
* 不用于容器边界容器边界请用 --tk-border */
--tk-hairline: #e9eef7;
/* 文字primary 16.6:1 / secondary 5.5:1 / placeholder 4.6:1均过 AA */
--tk-text: #111a17;
--tk-text-secondary: #5b6763;
--tk-text-placeholder: #66736f;
/* 文字primary 高对比 / secondary 与 placeholder 均过 AA */
--tk-text: #0f1729;
--tk-text-secondary: #55617a;
--tk-text-placeholder: #6e7994;
--tk-text-on-accent: #ffffff;
/* 语义色(仅用于状态,不参与品牌表达) */
--tk-success: #0c7c59;
--tk-warning: #9a6212;
--tk-danger: #b4302c;
--tk-info: #1d5fa8;
--tk-success: #047857;
--tk-warning: #b45309;
--tk-danger: #b91c1c;
--tk-info: #1d4ed8;
/* 阴影:带背景色相的冷调投影,不用纯黑 */
--tk-shadow-1: 0 1px 2px rgba(16, 32, 28, 0.05), 0 2px 6px rgba(16, 32, 28, 0.04);
--tk-shadow-2: 0 4px 12px rgba(16, 32, 28, 0.07), 0 12px 28px rgba(16, 32, 28, 0.05);
--tk-shadow-3: 0 8px 24px rgba(16, 32, 28, 0.09), 0 24px 56px rgba(16, 32, 28, 0.07);
--tk-shadow-1: 0 1px 2px rgba(15, 23, 41, 0.06), 0 1px 3px rgba(15, 23, 41, 0.05);
--tk-shadow-2: 0 4px 10px rgba(15, 23, 41, 0.08), 0 12px 24px rgba(15, 23, 41, 0.06);
--tk-shadow-3: 0 8px 20px rgba(15, 23, 41, 0.1), 0 24px 48px rgba(15, 23, 41, 0.08);
/* 顶栏/侧栏的半透明玻璃底 */
--tk-glass: rgba(252, 253, 253, 0.82);
--tk-glass-border: rgba(17, 26, 23, 0.07);
--tk-glass: rgba(255, 255, 255, 0.82);
--tk-glass-border: rgba(15, 23, 41, 0.08);
}
/* ============================================================
* 深色主题
* ============================================================ */
html[theme-mode='dark'] {
:root[theme-mode='dark'] {
color-scheme: dark;
/* 强调色深色下按钮底压深到 #0e8460与白字对比 4.68:1
* 正文/链接用更亮的 #3fc79b与页面底 8.85:1 */
--tk-accent: #0e8460;
--tk-accent-hover: #10946b;
--tk-accent-active: #0c6f51;
--tk-accent-text: #3fc79b;
--tk-accent-ring: rgba(63, 199, 155, 0.3);
--tk-accent-surface: rgba(63, 199, 155, 0.12);
--tk-accent-surface-strong: rgba(63, 199, 155, 0.2);
/* 强调色深色下按钮底保持 #2563eb与白字 5.17:1
* 链接/图标另用更亮的 #7aa8ff保证在深底上可读 */
--tk-accent: #2563eb;
--tk-accent-hover: #3b82f6;
--tk-accent-active: #1d4ed8;
--tk-accent-text: #7aa8ff;
--tk-accent-ring: rgba(122, 168, 255, 0.3);
--tk-accent-surface: rgba(122, 168, 255, 0.12);
--tk-accent-surface-strong: rgba(122, 168, 255, 0.2);
--tk-bg: #0c100f;
--tk-surface: #131817;
--tk-surface-raised: #171d1b;
--tk-surface-sunken: #171d1b;
--tk-surface-hover: #1a201e;
--tk-surface-active: #202725;
--tk-bg: #0b0f18;
--tk-surface: #131926;
--tk-surface-raised: #18202f;
--tk-surface-sunken: #18202f;
--tk-surface-hover: #1b2333;
--tk-surface-active: #222c3f;
--tk-border: #252d2b;
--tk-border-strong: #333c39;
--tk-border-subtle: #1d2422;
--tk-border: #263147;
--tk-border-strong: #35415c;
--tk-border-subtle: #1d2536;
/* 深色下取比卡片底(#131926略亮一档才看得见分隔线但不刺眼 */
--tk-hairline: #212a3c;
/* primary 高对比 / secondary 8.46:1 / placeholder 5.21:1 */
--tk-text: #e8edeb;
--tk-text-secondary: #a3afab;
--tk-text-placeholder: #7c8885;
--tk-text: #e9eef8;
--tk-text-secondary: #a4afc5;
--tk-text-placeholder: #7d88a0;
--tk-text-on-accent: #ffffff;
--tk-success: #3fc79b;
--tk-warning: #d9a441;
--tk-danger: #f08a84;
--tk-info: #6fa8e6;
--tk-success: #34d399;
--tk-warning: #fbbf24;
--tk-danger: #f87171;
--tk-info: #7aa8ff;
--tk-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 2px 6px rgba(0, 0, 0, 0.3);
--tk-shadow-2: 0 4px 12px rgba(0, 0, 0, 0.45), 0 12px 28px rgba(0, 0, 0, 0.35);
--tk-shadow-3: 0 8px 24px rgba(0, 0, 0, 0.5), 0 24px 56px rgba(0, 0, 0, 0.4);
--tk-shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.32);
--tk-shadow-2: 0 4px 10px rgba(0, 0, 0, 0.45), 0 12px 24px rgba(0, 0, 0, 0.35);
--tk-shadow-3: 0 8px 20px rgba(0, 0, 0, 0.5), 0 24px 48px rgba(0, 0, 0, 0.4);
--tk-glass: rgba(19, 24, 23, 0.82);
--tk-glass-border: rgba(232, 237, 235, 0.08);
}
/* 浅色主题下的强调文字色(深色主题单独覆盖) */
html[theme-mode='light'] {
--tk-accent-text: #0c7c59;
--tk-glass: rgba(19, 25, 38, 0.82);
--tk-glass-border: rgba(233, 238, 248, 0.09);
}
/* ============================================================

View File

@ -17,7 +17,10 @@ import { AppIcon } from 'tdesign-icons-vue-next'
*
* 图标刻意复用 TDesign 图标库的 AppIcon九宫格语义贴合工具箱
* 而不是手绘 SVG 手绘图标在不同字号下容易变形也不便与其它图标统一线宽
* 底纹使用青玉渐变是整站唯一使用渐变的位置用于强化品牌记忆点
*
* 编辑感版式下品牌标记改用实色小方块 + 文字标
* 原来的青玉渐变在这套版式里是唯一一处高饱和色块会把视线从标题上拽走
* 实色方块更安静品牌的辨识度改由字重与字距承担标题族同源
*/
const { t } = useI18n()
</script>
@ -26,34 +29,36 @@ const { t } = useI18n()
.brand {
display: inline-flex;
align-items: center;
gap: 10px;
gap: 9px;
text-decoration: none;
color: inherit;
flex-shrink: 0;
}
.brand-mark {
width: 32px;
height: 32px;
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--tk-radius-md);
font-size: 19px;
color: #fff;
/* 唯一渐变:青玉由深到浅,与强调色同色系 */
background: linear-gradient(140deg, var(--tk-accent) 0%, #2fb98d 100%);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.28);
transition: transform var(--tk-dur-base) var(--tk-ease-out);
font-size: 21px;
color: var(--tk-text-on-accent);
background: var(--tk-accent);
transition:
background-color var(--tk-dur-base) var(--tk-ease-out),
transform var(--tk-dur-base) var(--tk-ease-out);
}
.brand:hover .brand-mark {
transform: rotate(-6deg) scale(1.04);
background: var(--tk-accent-hover);
transform: scale(1.04);
}
.brand-text {
font-family: var(--tk-font-display);
font-size: var(--tk-text-lg);
font-weight: 650;
letter-spacing: -0.02em;
letter-spacing: var(--tk-track-title);
color: var(--tk-text);
white-space: nowrap;
}

View File

@ -252,8 +252,8 @@ onBeforeUnmount(() => {
.palette-panel {
width: 100%;
max-width: 620px;
max-height: 68vh;
max-width: 680px;
max-height: 70vh;
display: flex;
flex-direction: column;
overflow: hidden;
@ -267,14 +267,14 @@ onBeforeUnmount(() => {
.palette-search {
display: flex;
align-items: center;
gap: 10px;
padding: 0 12px 0 16px;
height: 56px;
gap: 12px;
padding: 0 14px 0 18px;
height: 64px;
flex-shrink: 0;
border-bottom: 1px solid var(--tk-border-subtle);
}
.search-icon {
font-size: 18px;
font-size: 20px;
color: var(--tk-text-placeholder);
flex-shrink: 0;
}
@ -286,7 +286,7 @@ onBeforeUnmount(() => {
outline: none;
background: transparent;
font-family: inherit;
font-size: var(--tk-text-md);
font-size: var(--tk-text-lg);
color: var(--tk-text);
}
.search-input::placeholder {
@ -294,17 +294,17 @@ onBeforeUnmount(() => {
}
.close-btn {
flex-shrink: 0;
width: 30px;
height: 30px;
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: var(--tk-radius-sm);
border-radius: var(--tk-radius-md);
background: transparent;
color: var(--tk-text-placeholder);
cursor: pointer;
font-size: 16px;
font-size: 18px;
}
.close-btn:hover {
background: var(--tk-surface-hover);
@ -315,15 +315,16 @@ onBeforeUnmount(() => {
.palette-list {
flex: 1;
overflow-y: auto;
padding: 6px;
padding: 8px;
overscroll-behavior: contain;
}
.palette-item {
width: 100%;
display: flex;
align-items: center;
gap: 12px;
padding: 9px 12px;
gap: 14px;
min-height: 58px;
padding: 10px 14px;
border: none;
border-radius: var(--tk-radius-md);
background: transparent;
@ -337,13 +338,13 @@ onBeforeUnmount(() => {
}
.item-icon {
flex-shrink: 0;
width: 30px;
height: 30px;
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--tk-radius-sm);
font-size: 17px;
border-radius: var(--tk-radius-md);
font-size: 19px;
color: var(--tk-accent-text);
background: var(--tk-surface-sunken);
}
@ -355,7 +356,7 @@ onBeforeUnmount(() => {
min-width: 0;
display: flex;
flex-direction: column;
gap: 1px;
gap: 2px;
}
.item-name {
font-size: var(--tk-text-base);
@ -366,7 +367,7 @@ onBeforeUnmount(() => {
white-space: nowrap;
}
.item-desc {
font-size: var(--tk-text-xs);
font-size: var(--tk-text-sm);
color: var(--tk-text-placeholder);
overflow: hidden;
text-overflow: ellipsis;
@ -374,9 +375,9 @@ onBeforeUnmount(() => {
}
.item-badge {
flex-shrink: 0;
padding: 1px 7px;
padding: 2px 9px;
border-radius: var(--tk-radius-pill);
font-size: 11px;
font-size: var(--tk-text-xs);
font-weight: 500;
color: var(--tk-accent-text);
background: var(--tk-accent-surface-strong);
@ -408,8 +409,8 @@ onBeforeUnmount(() => {
.palette-foot {
flex-shrink: 0;
display: flex;
gap: 16px;
padding: 8px 16px;
gap: 18px;
padding: 10px 18px;
border-top: 1px solid var(--tk-border-subtle);
background: var(--tk-surface);
font-size: var(--tk-text-xs);
@ -418,20 +419,20 @@ onBeforeUnmount(() => {
.foot-item {
display: inline-flex;
align-items: center;
gap: 4px;
gap: 5px;
}
.palette-foot kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 4px;
min-width: 22px;
height: 22px;
padding: 0 5px;
border: 1px solid var(--tk-border);
border-radius: var(--tk-radius-xs);
background: var(--tk-surface-raised);
font-family: inherit;
font-size: 11px;
font-size: var(--tk-text-xs);
color: var(--tk-text-secondary);
}

View File

@ -2,7 +2,9 @@
<div v-if="props.items.length" class="history-panel">
<div class="history-head">
<span class="history-title">历史记录</span>
<t-button variant="text" size="small" theme="danger" @click="emit('clear')">清空</t-button>
<t-button variant="text" theme="danger" class="history-clear" @click="emit('clear')">
清空
</t-button>
</div>
<ul class="history-list">
<li
@ -56,19 +58,29 @@ function formatTime(ts: number): string {
</script>
<style scoped>
/* 线
* 不再给每条历史套一个描边盒子列表一长就会变成一排小方块
* 注意这里必须用 --tk-* 令牌写死色值会在昼夜切换时花掉 */
.history-panel {
margin-top: 16px;
margin-top: var(--tk-space-6);
}
.history-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
gap: 12px;
margin-bottom: 4px;
}
.history-title {
font-size: 14px;
font-size: var(--tk-text-base);
font-weight: 600;
color: var(--td-text-color-primary);
letter-spacing: var(--tk-track-title);
color: var(--tk-text-secondary);
}
/* 清空按钮:给足热区,但压住高度避免把标题行撑得过重 */
.history-clear {
min-height: 34px;
font-size: var(--tk-text-sm);
}
.history-list {
margin: 0;
@ -78,17 +90,12 @@ function formatTime(ts: number): string {
.history-item {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
margin-bottom: 6px;
border: 1px solid var(--td-border-level-1-color);
border-radius: 6px;
background: var(--td-bg-color-container);
gap: 12px;
min-height: 48px;
padding: 10px 2px;
border-top: 1px solid var(--tk-hairline);
cursor: pointer;
transition: border-color 0.2s;
}
.history-item:hover {
border-color: var(--td-brand-color);
transition: color var(--tk-dur-fast) var(--tk-ease-out);
}
.history-text {
flex: 1;
@ -96,15 +103,25 @@ function formatTime(ts: number): string {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 13px;
color: var(--td-text-color-primary);
font-size: var(--tk-text-base);
color: var(--tk-text-secondary);
transition: color var(--tk-dur-fast) var(--tk-ease-out);
}
.history-item:hover .history-text {
color: var(--tk-accent-text);
}
.history-time {
flex-shrink: 0;
font-size: 12px;
color: var(--td-text-color-placeholder);
font-family: var(--tk-num-font);
font-variant-numeric: tabular-nums;
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
}
/* 历史条目里的删除按钮热区撑到 34px避免在密集列表里点不准 */
.history-del {
flex-shrink: 0;
min-width: 34px;
min-height: 34px;
font-size: 16px;
}
</style>

View File

@ -66,9 +66,9 @@ function onSelect(item: { value?: string | number }) {
.switch-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0 8px;
height: 34px;
gap: 5px;
padding: 0 12px;
height: 40px;
color: var(--tk-text-secondary);
border-radius: var(--tk-radius-md);
}
@ -77,15 +77,15 @@ function onSelect(item: { value?: string | number }) {
background: var(--tk-surface-hover);
}
.switch-text {
font-size: var(--tk-text-sm);
font-size: var(--tk-text-base);
font-weight: 600;
letter-spacing: 0.02em;
/* 语言代码固定宽度,切换时按钮不会左右跳动 */
min-width: 22px;
min-width: 26px;
text-align: center;
}
.switch-caret {
font-size: 14px;
font-size: 16px;
opacity: 0.6;
}
/* 窄屏只留图标,避免顶栏拥挤 */

View File

@ -57,9 +57,9 @@ function onSelect(item: { value?: string | number }) {
.switch-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 0 8px;
height: 34px;
gap: 5px;
padding: 0 12px;
height: 40px;
color: var(--tk-text-secondary);
border-radius: var(--tk-radius-md);
}
@ -68,11 +68,11 @@ function onSelect(item: { value?: string | number }) {
background: var(--tk-surface-hover);
}
.switch-text {
font-size: var(--tk-text-sm);
font-size: var(--tk-text-base);
font-weight: 500;
}
.switch-caret {
font-size: 14px;
font-size: 16px;
opacity: 0.6;
}
@media (max-width: 767px) {

View File

@ -46,7 +46,7 @@
<span class="name" :title="f.name">{{ f.name }}</span>
<span class="size">{{ formatBytes(f.size) }}</span>
</span>
<t-button variant="text" size="small" theme="danger" class="remove-btn" @click.stop="removeAt(i)">
<t-button variant="text" theme="danger" class="remove-btn" @click.stop="removeAt(i)">
<template #icon><DeleteIcon /></template>
</t-button>
</div>
@ -192,13 +192,13 @@ function removeAt(i: number) {
.drop-zone {
position: relative;
border: 2px dashed var(--td-border-level-2-color);
border-radius: 8px;
padding: 24px 16px;
border-radius: var(--tk-radius-lg);
padding: 32px 20px;
text-align: center;
cursor: pointer;
transition:
border-color 0.2s,
background-color 0.2s;
border-color var(--tk-dur-fast) var(--tk-ease-out),
background-color var(--tk-dur-fast) var(--tk-ease-out);
}
.drop-zone:hover,
.drop-zone.dragging {
@ -209,17 +209,17 @@ function removeAt(i: number) {
display: none;
}
.empty-icon {
font-size: 40px;
font-size: 46px;
color: var(--td-brand-color);
}
.tip {
margin: 8px 0 4px;
font-size: 14px;
margin: 10px 0 6px;
font-size: var(--tk-text-md);
color: var(--td-text-color-primary);
}
.sub {
margin: 0;
font-size: 12px;
font-size: var(--tk-text-sm);
color: var(--td-text-color-placeholder);
}
.drop-overlay {
@ -233,10 +233,10 @@ function removeAt(i: number) {
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
border-radius: 8px;
font-size: 15px;
font-weight: 500;
gap: 10px;
border-radius: var(--tk-radius-lg);
font-size: var(--tk-text-md);
font-weight: 550;
color: var(--td-brand-color);
background: var(--td-brand-color-light);
pointer-events: none;
@ -244,22 +244,22 @@ function removeAt(i: number) {
.file-list {
display: flex;
flex-direction: column;
gap: 8px;
gap: 10px;
text-align: left;
}
.file-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 10px;
gap: 12px;
padding: 10px 12px;
border: 1px solid var(--td-border-level-1-color);
border-radius: 6px;
border-radius: var(--tk-radius-md);
background: var(--td-bg-color-container);
}
.thumb {
width: 48px;
height: 48px;
border-radius: 6px;
width: 56px;
height: 56px;
border-radius: var(--tk-radius-sm);
object-fit: cover;
flex-shrink: 0;
border: 1px solid var(--td-border-level-1-color);
@ -268,14 +268,14 @@ function removeAt(i: number) {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 22px;
font-size: 24px;
color: var(--td-text-color-placeholder);
background: var(--td-bg-color-secondarycontainer);
}
/* 可点击预览的缩略图 */
.thumb-clickable {
cursor: zoom-in;
transition: transform 0.15s;
transition: transform var(--tk-dur-fast) var(--tk-ease-out);
}
.thumb-clickable:hover {
transform: scale(1.06);
@ -285,25 +285,30 @@ function removeAt(i: number) {
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
gap: 3px;
}
.name {
font-size: 13px;
font-size: var(--tk-text-base);
color: var(--td-text-color-primary);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.size {
font-size: 12px;
font-size: var(--tk-text-sm);
color: var(--td-text-color-placeholder);
}
/* t-button size=small 24px
* 这里把它撑到 36px仍保持图标按钮的形态 */
.remove-btn {
flex-shrink: 0;
min-width: 36px;
min-height: 36px;
font-size: 17px;
}
.add-more {
margin: 2px 0 0;
font-size: 12px;
font-size: var(--tk-text-sm);
color: var(--td-text-color-placeholder);
text-align: center;
}

View File

@ -63,11 +63,8 @@ export default {
},
home: {
eyebrow: 'Runs locally in your browser',
title: 'A toolbox that lives in your browser',
desc: '{count} everyday utilities. Every calculation and file operation happens on your own machine.',
ctaPrimary: 'Browse all tools',
ctaSecondary: 'Server tools',
statTools: 'Available tools',
statCategories: 'Categories',
statUpload: 'Data uploaded to a server',

View File

@ -63,11 +63,8 @@ export default {
},
home: {
eyebrow: 'ブラウザ内で動作',
title: 'ブラウザだけで使えるツールボックス',
desc: '{count} の日常ツール。計算もファイル処理もすべて端末内で完結します。',
ctaPrimary: 'すべてのツール',
ctaSecondary: 'サーバー連携ツール',
statTools: '利用できるツール',
statCategories: 'カテゴリ',
statUpload: 'サーバーへの送信データ',

View File

@ -63,11 +63,8 @@ export default {
},
home: {
eyebrow: '브라우저에서 바로 실행',
title: '브라우저만 있으면 되는 툴박스',
desc: '일상 도구 {count}개. 계산과 파일 처리가 모두 내 기기에서 끝납니다.',
ctaPrimary: '전체 도구 보기',
ctaSecondary: '서버 연동 도구',
statTools: '사용 가능한 도구',
statCategories: '분류',
statUpload: '서버로 전송되는 데이터',

View File

@ -76,11 +76,8 @@ export default {
},
home: {
eyebrow: '浏览器本地运行',
title: '在浏览器里就能用的工具箱',
desc: '{count} 个日常工具,计算与文件处理全部在本机完成,数据不上传服务器。',
ctaPrimary: '浏览全部工具',
ctaSecondary: '服务端工具',
statTools: '可用工具',
statCategories: '工具分类',
statUpload: '上传到服务器的数据',

View File

@ -75,7 +75,7 @@
<t-drawer
v-model:visible="drawerVisible"
placement="left"
:size="272"
:size="300"
:footer="false"
:header="false"
:close-btn="true"
@ -103,13 +103,16 @@ import SideNav from './SideNav.vue'
/**
* 应用外壳布局
*
* 结构固定顶栏60px+ 左侧固定导航248px+ 内容区 + 页脚
* 结构固定顶栏68px+ 左侧固定导航268px+ 内容区 + 页脚
* 移动端<768px侧栏换成抽屉顶栏保留品牌命令面板与主题/语言切换
*
* 版心约定
* --tk-shell-max = 1560px 顶栏 / 页脚 / 主体三者的最大宽度
* --tk-content-max = 1120px 内容型工具页的版心 page-wrap 约束
* --tk-shell-max = 1600px 顶栏 / 页脚 / 主体三者的最大宽度
* --tk-content-max = 1200px 内容型工具页的版心 page-wrap 约束
* 首页因为要铺开卡片网格会自行放宽不受 page-wrap 限制
*
* 尺寸一律跟随令牌--tk-header-h / --tk-aside-w不要在组件里写死 px
* 本版的整体放大是改令牌完成的写死会让顶栏与内容脱节
*/
const { t } = useI18n()
const palette = useCommandPalette()
@ -151,10 +154,10 @@ const shortcutLabel = computed(() => {
height: 100%;
max-width: var(--tk-shell-max);
margin: 0 auto;
padding: 0 16px;
padding: 0 24px;
display: flex;
align-items: center;
gap: 12px;
gap: 14px;
}
.header-left {
display: flex;
@ -177,8 +180,8 @@ const shortcutLabel = computed(() => {
/* 顶栏图标按钮(移动端菜单) */
.icon-btn {
width: 34px;
height: 34px;
width: 40px;
height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
@ -187,7 +190,7 @@ const shortcutLabel = computed(() => {
background: transparent;
color: var(--tk-text-secondary);
cursor: pointer;
font-size: 18px;
font-size: 20px;
}
.icon-btn:hover {
background: var(--tk-surface-hover);
@ -197,18 +200,22 @@ const shortcutLabel = computed(() => {
display: none;
}
/* 命令面板入口:长得像搜索框,本质是按钮(点开面板而非直接输入) */
/*
* 用无描边的安静底纹而不是带边框的输入框
* 它不是一个可以直接打字的输入框画成输入框会让人先去点它再发现要跳面板
* 放大镜图标 + 占位文案 + 快捷键角标三重提示已经足够表达可搜索
* 高度 44px 与站点默认控件同高顶栏一眼看去是一排等高的可点区域 */
.palette-trigger {
width: 100%;
max-width: 460px;
height: 36px;
max-width: 520px;
height: 44px;
display: flex;
align-items: center;
gap: 9px;
padding: 0 10px 0 12px;
border: 1px solid var(--tk-border);
gap: 10px;
padding: 0 10px 0 14px;
border: 1px solid transparent;
border-radius: var(--tk-radius-md);
background: var(--tk-surface);
background: var(--tk-surface-sunken);
color: var(--tk-text-placeholder);
font-family: inherit;
font-size: var(--tk-text-base);
@ -218,15 +225,14 @@ const shortcutLabel = computed(() => {
background-color var(--tk-dur-fast) var(--tk-ease-out);
}
.palette-trigger:hover {
border-color: var(--tk-border-strong);
background: var(--tk-surface-hover);
background: var(--tk-surface-active);
color: var(--tk-text-secondary);
}
.palette-trigger:active {
transform: translateY(1px);
}
.trigger-icon {
font-size: 16px;
font-size: 18px;
flex-shrink: 0;
}
.trigger-text {
@ -239,13 +245,13 @@ const shortcutLabel = computed(() => {
}
.trigger-kbd {
flex-shrink: 0;
padding: 1px 6px;
padding: 2px 7px;
border: 1px solid var(--tk-border);
border-radius: var(--tk-radius-xs);
background: var(--tk-surface-sunken);
font-family: inherit;
font-size: 11px;
line-height: 16px;
font-size: var(--tk-text-xs);
line-height: 18px;
color: var(--tk-text-placeholder);
}
@ -259,7 +265,10 @@ const shortcutLabel = computed(() => {
align-items: flex-start;
}
/* 左侧固定导航PC 端) */
/* PC
* 侧栏与正文共用同一张页面底仅靠留白与字号区分层级
* 导航项 hover --tk-surface浅色下更亮的白深色下同样更亮
* 两种主题下都是抬起的方向感 */
.app-aside {
width: var(--tk-aside-w);
flex-shrink: 0;
@ -268,31 +277,34 @@ const shortcutLabel = computed(() => {
height: calc(100dvh - var(--tk-header-h));
overflow-y: auto;
overscroll-behavior: contain;
border-right: 1px solid var(--tk-border);
padding: 14px 10px 32px;
background: var(--tk-surface);
padding: 26px 20px 56px;
background: transparent;
}
/*
* 避免 1080p 屏幕上正文被挤成窄窄一条 */
.app-content {
flex: 1;
min-width: 0;
padding: 28px 28px 56px;
padding: 32px 48px 72px;
}
/* ==================== 页脚 ==================== */
/* ==================== ====================
* 承诺在左备案号在右两者是不同性质的信息产品承诺 / 监管信息
* 居中堆叠会让它们看起来像同一组 */
.app-footer {
border-top: 1px solid var(--tk-border);
background: var(--tk-surface);
border-top: 1px solid var(--tk-hairline);
}
.footer-inner {
max-width: var(--tk-shell-max);
margin: 0 auto;
padding: 18px 16px;
padding: 32px 24px 40px;
display: flex;
flex-direction: column;
flex-wrap: wrap;
align-items: center;
gap: 8px;
font-size: var(--tk-text-xs);
justify-content: space-between;
gap: 10px 24px;
font-size: var(--tk-text-sm);
color: var(--tk-text-placeholder);
}
.footer-promises {
@ -300,16 +312,16 @@ const shortcutLabel = computed(() => {
flex-wrap: wrap;
align-items: center;
justify-content: center;
gap: 14px;
gap: 16px;
}
.footer-divider {
width: 1px;
height: 11px;
background: var(--tk-border-strong);
height: 12px;
background: var(--tk-border);
}
/* 备案号:监管信息,压到比产品承诺更弱的视觉层级,但仍要可读、可点 */
.footer-icp {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
text-decoration: none;
border-bottom: 1px solid transparent;
@ -322,6 +334,13 @@ const shortcutLabel = computed(() => {
}
/* ==================== 响应式 ==================== */
/* 中等宽度:收窄留白,但保持「正文两侧有空白」的基本观感 */
@media (max-width: 1279px) {
.app-content {
padding: 28px 32px 64px;
}
}
@media (max-width: 767px) {
.app-aside {
display: none;
@ -330,17 +349,22 @@ const shortcutLabel = computed(() => {
display: inline-flex;
}
.app-content {
padding: 18px 14px 44px;
padding: 20px 16px 56px;
}
.footer-inner {
flex-direction: column;
align-items: flex-start;
padding: 24px 16px 32px;
}
/* 移动端搜索入口收成图标按钮:整条搜索框会挤掉品牌与主题切换 */
.palette-trigger {
max-width: 36px;
height: 34px;
max-width: 40px;
height: 40px;
padding: 0;
justify-content: center;
border-color: transparent;
background: transparent;
font-size: 18px;
font-size: 20px;
}
.palette-trigger:hover {
background: var(--tk-surface-hover);

View File

@ -96,6 +96,9 @@ import { toolName, toolDesc, categoryLabel } from '@/i18n/helpers'
* - 需要联网的工具在右侧点一个小圆点标记且这是全站唯一使用状态点的地方
* 语义明确是否需联网不是装饰
* - 筛选对已翻译的名称生效因此英文/日文/韩文界面下用对应语言也能搜到
*
* 尺寸导航项行高 40px 起步上一版 34px 偏小鼠标点起来费劲
* 字号与正文同号避免侧栏比正文小一号
*/
const { t } = useI18n()
const route = useRoute()
@ -171,24 +174,28 @@ const emit = defineEmits<{
.side-nav {
display: flex;
flex-direction: column;
gap: 2px;
}
/* ---------- 筛选框 ---------- */
/* ---------- ----------
* 只留一条底线而不是画一个框侧栏里的框会和下方导航项的背景块打架
* 视觉上把筛选误读成一个大按钮底线 + 聚焦变色已经足够提示可输入
* 高度 42px与导航项同档点起来有足够的热区 */
.nav-filter {
position: relative;
display: flex;
align-items: center;
gap: 7px;
height: 34px;
margin-bottom: 10px;
padding: 0 8px 0 10px;
border: 1px solid var(--tk-border);
border-radius: var(--tk-radius-md);
background: var(--tk-surface-sunken);
gap: 10px;
height: 42px;
margin-bottom: 20px;
padding: 0 2px;
border-bottom: 1px solid var(--tk-border);
transition: border-color var(--tk-dur-fast) var(--tk-ease-out);
}
.nav-filter:focus-within {
border-bottom-color: var(--tk-accent);
}
.filter-icon {
font-size: 15px;
font-size: 17px;
color: var(--tk-text-placeholder);
flex-shrink: 0;
}
@ -199,7 +206,7 @@ const emit = defineEmits<{
outline: none;
background: transparent;
font-family: inherit;
font-size: var(--tk-text-sm);
font-size: var(--tk-text-base);
color: var(--tk-text);
}
.filter-input::placeholder {
@ -207,33 +214,35 @@ const emit = defineEmits<{
}
.filter-clear {
flex-shrink: 0;
width: 18px;
height: 18px;
width: 24px;
height: 24px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: var(--tk-radius-xs);
border-radius: var(--tk-radius-sm);
background: transparent;
color: var(--tk-text-placeholder);
cursor: pointer;
font-size: 13px;
font-size: 15px;
}
.filter-clear:hover {
color: var(--tk-text);
background: var(--tk-surface-active);
}
/* ---------- 分组标题 ---------- */
/* ---------- ----------
* 分组之间靠 16px 留白拉开不画横线
* 分类名是目录行字号比导航项小一档但字距略开扫读时先看到分组再看到工具 */
.nav-group {
margin-top: 6px;
margin-top: 16px;
}
.group-head {
width: 100%;
display: flex;
align-items: center;
gap: 6px;
padding: 6px 8px;
padding: 8px 10px 8px;
border: none;
background: transparent;
border-radius: var(--tk-radius-sm);
@ -244,7 +253,6 @@ const emit = defineEmits<{
}
.group-head:hover {
color: var(--tk-text-secondary);
background: var(--tk-surface-hover);
}
.group-caret {
font-size: 13px;
@ -259,28 +267,32 @@ const emit = defineEmits<{
min-width: 0;
font-size: var(--tk-text-xs);
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
/* 0.06em
* 再宽就会散成四个孤立的字 */
letter-spacing: 0.06em;
}
.group-count {
flex-shrink: 0;
font-size: 11px;
font-size: var(--tk-text-xs);
font-variant-numeric: tabular-nums;
opacity: 0.75;
opacity: 0.7;
}
/* ---------- 导航项 ---------- */
/* ---------- ----------
* 行高 40px这是全站点击最密集的列表必须给足热区
* hover --tk-surface浅色下更亮深色下也更亮而不是 hover
* 这样两种主题下都是同一种抬起的方向感 */
.group-items {
display: flex;
flex-direction: column;
gap: 1px;
gap: 2px;
}
.nav-item {
position: relative;
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
gap: 11px;
padding: 9px 12px;
border-radius: var(--tk-radius-md);
font-size: var(--tk-text-base);
color: var(--tk-text-secondary);
@ -290,32 +302,33 @@ const emit = defineEmits<{
color var(--tk-dur-fast) var(--tk-ease-out);
}
.nav-item:hover {
background: var(--tk-surface-hover);
background: var(--tk-surface);
color: var(--tk-text);
}
/* 活动态:青玉底纹 + 左侧 2px 指示条,比纯文字变色更容易在长列表里定位 */
/* + 3px
* 指示条压在条目左侧外 11px 贴在侧栏留白里像书页边的一条书签 */
.nav-item.active {
background: var(--tk-accent-surface-strong);
background: var(--tk-accent-surface);
color: var(--tk-accent-text);
font-weight: 550;
}
.nav-item.active::before {
content: '';
position: absolute;
left: -10px;
left: -11px;
top: 50%;
transform: translateY(-50%);
width: 2px;
height: 16px;
width: 3px;
height: 18px;
border-radius: var(--tk-radius-pill);
background: var(--tk-accent);
}
.home-item {
margin-bottom: 4px;
margin-bottom: 6px;
}
.nav-icon {
width: 17px;
height: 17px;
width: 19px;
height: 19px;
flex-shrink: 0;
}
.nav-label {
@ -327,8 +340,8 @@ const emit = defineEmits<{
}
/* 唯一的语义状态点:该工具需要联网 */
.nav-dot {
width: 5px;
height: 5px;
width: 6px;
height: 6px;
flex-shrink: 0;
border-radius: 50%;
background: var(--tk-accent);
@ -337,27 +350,28 @@ const emit = defineEmits<{
/* ---------- 空状态 ---------- */
.nav-empty {
padding: 20px 10px;
padding: 28px 10px;
}
.empty-title {
margin: 0 0 4px;
margin: 0 0 6px;
font-size: var(--tk-text-sm);
font-weight: 600;
color: var(--tk-text-secondary);
}
.empty-hint {
margin: 0 0 10px;
margin: 0 0 12px;
font-size: var(--tk-text-xs);
line-height: 1.55;
line-height: 1.6;
color: var(--tk-text-placeholder);
}
.empty-btn {
padding: 5px 10px;
min-height: 34px;
padding: 6px 14px;
border: 1px solid var(--tk-border);
border-radius: var(--tk-radius-sm);
border-radius: var(--tk-radius-md);
background: var(--tk-surface);
font-family: inherit;
font-size: var(--tk-text-xs);
font-size: var(--tk-text-sm);
color: var(--tk-text-secondary);
cursor: pointer;
}

View File

@ -1,10 +1,11 @@
<template>
<div class="home">
<!-- ==================== 抬头区 ====================
左对齐单列这一页的主角是快速找到工具不是品牌宣言
所以把真实的搜索框放进抬头区作为主操作而不是摆两个装饰按钮 -->
编辑感版式里抬头区只留三件东西标题一句说明真实可用的搜索框
不放 eyebrow 小标签那是模板化开场且和标题说的是同一件事
也不放第二个入口按钮服务端工具的入口就在下方专区里
同一意图出现两次只会让人犹豫点哪个 -->
<section class="hero">
<p class="hero-eyebrow">{{ t('home.eyebrow') }}</p>
<h1 class="hero-title">{{ t('home.title') }}</h1>
<p class="hero-desc">{{ t('home.desc', { count: tools.length }) }}</p>
@ -28,29 +29,28 @@
<CloseIcon />
</button>
</div>
<router-link to="/api-status" class="hero-link">
{{ t('home.ctaSecondary') }}
<ChevronRightIcon aria-hidden="true" />
</router-link>
</section>
<!-- ==================== 概览数字条 ====================
四组数字用竖线分列数字统一等宽刷新时不会左右跳动 -->
<section v-if="!isFiltering" class="stats">
<div v-for="stat in stats" :key="stat.label" class="stat">
<span class="stat-value mono-num">{{ stat.value }}</span>
<span class="stat-label">{{ stat.label }}</span>
<!-- ==================== 事实条 ====================
四组数字直接摊在纸面上不加边框也不加底纹
数字本身就是视觉元素再套一层容器反而把它们锁住了
数值一律等宽切换语言或刷新时不会左右跳动 -->
<section v-if="!isFiltering" class="facts">
<div v-for="stat in stats" :key="stat.label" class="fact">
<span class="fact-value mono-num">{{ stat.value }}</span>
<span class="fact-label">{{ stat.label }}</span>
</div>
</section>
<!-- ==================== 服务端工具专区 ====================
整块用青玉底纹与其它分区区分开这是本期唯一需要联网且有真实后端支撑的部分
整块用青玉底纹与其它分区区分开这是唯一需要联网且有真实后端支撑的部分
开始筛选时隐藏避免与下方 online 分类重复出现同一批工具 -->
<section v-if="!isFiltering && serverTools.length" class="server-zone">
<header class="zone-head">
<div class="zone-heading">
<h2 class="zone-title">{{ t('category.online.label') }}</h2>
<p class="zone-desc">{{ t('category.online.desc') }}</p>
</div>
<router-link to="/api-status" class="zone-link">
{{ t('page.api-status.title') }}
<ChevronRightIcon aria-hidden="true" />
@ -76,9 +76,10 @@
</div>
</section>
<!-- ==================== 分类分区 ====================
56 个工具的目录页必然是重复的卡片网格这里把重复做整齐而不是做变化
分区标题行统一结构卡片网格统一列宽用户可以靠固定的视觉节奏快速扫读 -->
<!-- ==================== 分类目录 ====================
58 个工具不再做成 58 个盒子而是像书末索引一样用发丝线分行
扫读时视线沿着一列文字往下走比逐个辨认边框快得多
也让大量同类条目这件事本身成为版式的一部分而不是一堆重复的框 -->
<section
v-for="(group, index) in visibleGroups"
:key="group.category.key"
@ -86,30 +87,34 @@
class="category"
>
<header class="category-head">
<h2 class="category-title">{{ categoryLabel(group.category.key, group.category.label) }}</h2>
<h2 class="category-title">
{{ categoryLabel(group.category.key, group.category.label) }}
<span class="category-count mono-num">
{{ t('nav.toolsCount', { count: group.items.length }) }}
</span>
</h2>
<p class="category-desc">{{ categoryDesc(group.category.key, group.category.desc) }}</p>
<span class="category-count">{{ t('nav.toolsCount', { count: group.items.length }) }}</span>
</header>
<div class="tool-grid">
<div class="tool-index">
<router-link
v-for="tool in group.items"
:key="tool.path"
:to="`/${tool.path}`"
class="tool-card"
class="tool-entry"
>
<span class="card-head">
<span class="card-icon"><component :is="tool.icon" aria-hidden="true" /></span>
<span v-if="tool.needsNetwork" class="card-tag">{{ t('home.serverBadge') }}</span>
<span class="entry-icon"><component :is="tool.icon" aria-hidden="true" /></span>
<span class="entry-body">
<span class="entry-name">{{ toolName(tool.path, tool.name) }}</span>
<span class="entry-desc">{{ toolDesc(tool.path, tool.desc) }}</span>
</span>
<span class="card-name">{{ toolName(tool.path, tool.name) }}</span>
<span class="card-desc">{{ toolDesc(tool.path, tool.desc) }}</span>
<span v-if="tool.needsNetwork" class="entry-badge">{{ t('home.serverBadge') }}</span>
</router-link>
</div>
</section>
<!-- ==================== 筛选无结果 ====================
给出可执行的下一步关键词 / 一键重置而不是只显示一句无结果 -->
给出可执行的下一步关键词 / 一键重置而不是只显示一句无结果 -->
<div v-if="isFiltering && !visibleGroups.length" class="empty">
<p class="empty-title">{{ t('home.emptyTitle') }}</p>
<p class="empty-hint">{{ t('home.emptyHint') }}</p>
@ -132,12 +137,13 @@ import { SUPPORTED_LOCALES } from '@/i18n'
/**
* 首页工具目录
*
* 版式取舍
* - 抬头区左对齐而不是居中居中大标题在目录型产品上会把视线拉向空处
* 左对齐能让标题 搜索框 卡片形成一条自然的阅读动线
* - 抬头区放真实可用的搜索框直接过滤下方列表不是装饰性按钮
* - 服务端工具单独成区并加底纹是本期唯一需要联网的部分值得与本地工具区分
* - 筛选期间隐藏概览数字与服务端专区避免同一批工具在一页里出现两次
* 版式取舍编辑感方向
* - 抬头区左对齐不放 eyebrow标题的字号反差就是这一页的开场
* - 搜索框画成一条底线而不是一个输入框盒子和整页的纸面感一致
* - 工具列表做成带发丝线的索引而不是 58 个描边卡片
* 条目越多越应该靠排版而不是靠容器来组织
* - 服务端工具单独成区并加底纹是唯一需要联网的部分值得与本地工具区分
* - 筛选期间隐藏事实条与服务端专区避免同一批工具在一页里出现两次
*/
const { t } = useI18n()
@ -199,58 +205,58 @@ function endpointOf(path: string): string {
<style scoped>
.home {
max-width: 1180px;
max-width: 1260px;
}
/* ==================== 抬头区 ==================== */
/* ==================== ====================
* 字号反差是这一页最主要的层次手段标题 52px / 说明 19px均取自令牌
* 中间没有第三个档位视线不会被夹在两层之间
* 搜索框画成一条底线而不是盒子它是搜索这件事本身不该长成表单控件 */
.hero {
padding: 8px 0 36px;
max-width: 720px;
}
.hero-eyebrow {
margin: 0 0 12px;
font-size: var(--tk-text-xs);
font-weight: 600;
letter-spacing: 0.08em;
color: var(--tk-accent-text);
padding: 16px 0 0;
}
.hero-title {
margin: 0 0 14px;
font-size: var(--tk-text-4xl);
font-weight: 680;
line-height: 1.1;
letter-spacing: -0.03em;
margin: 0;
max-width: 20ch;
font-family: var(--tk-font-display);
font-size: var(--tk-text-5xl);
font-weight: 650;
line-height: var(--tk-leading-display);
letter-spacing: var(--tk-track-display);
color: var(--tk-text);
}
.hero-desc {
margin: 0 0 26px;
max-width: 54ch;
font-size: var(--tk-text-md);
line-height: 1.6;
margin: 18px 0 0;
max-width: 56ch;
font-size: var(--tk-text-lg);
line-height: var(--tk-leading-body);
color: var(--tk-text-secondary);
}
/* 抬头区搜索框:页面的主操作,尺寸比顶栏的入口更大更醒目 */
/*
* 聚焦时底线加粗成 2px box-shadow 叠加避免改 border-width 引起 1px 位移 */
.hero-search {
display: flex;
align-items: center;
gap: 10px;
height: 48px;
padding: 0 12px 0 16px;
border: 1px solid var(--tk-border-strong);
border-radius: var(--tk-radius-xl);
background: var(--tk-surface);
box-shadow: var(--tk-shadow-1);
gap: 14px;
height: 60px;
max-width: 640px;
margin-top: 36px;
padding: 0 2px;
border-bottom: 1px solid var(--tk-border-strong);
transition:
border-color var(--tk-dur-fast) var(--tk-ease-out),
box-shadow var(--tk-dur-fast) var(--tk-ease-out);
}
.hero-search:hover {
border-bottom-color: var(--tk-text-placeholder);
}
.hero-search:focus-within {
border-color: var(--tk-accent);
box-shadow: 0 0 0 3px var(--tk-accent-ring);
border-bottom-color: var(--tk-accent);
box-shadow: 0 1px 0 0 var(--tk-accent);
}
.hero-search-icon {
font-size: 18px;
font-size: 21px;
color: var(--tk-text-placeholder);
flex-shrink: 0;
}
@ -262,7 +268,7 @@ function endpointOf(path: string): string {
outline: none;
background: transparent;
font-family: inherit;
font-size: var(--tk-text-md);
font-size: var(--tk-text-xl);
color: var(--tk-text);
/* 去掉 type=search 在 Safari / Chrome 上的原生清除按钮,统一用自绘按钮 */
appearance: none;
@ -270,140 +276,142 @@ function endpointOf(path: string): string {
.hero-search-input::-webkit-search-cancel-button {
display: none;
}
/* 焦点已由整条底线表达,输入框自身不再画一遍焦点环(会在地线上多出一个方框) */
.hero-search-input:focus-visible {
outline: none;
}
.hero-search-input::placeholder {
color: var(--tk-text-placeholder);
}
.hero-search-clear {
flex-shrink: 0;
width: 26px;
height: 26px;
width: 36px;
height: 36px;
display: inline-flex;
align-items: center;
justify-content: center;
border: none;
border-radius: var(--tk-radius-sm);
border-radius: var(--tk-radius-md);
background: transparent;
color: var(--tk-text-placeholder);
cursor: pointer;
font-size: 15px;
font-size: 17px;
}
.hero-search-clear:hover {
background: var(--tk-surface-hover);
background: var(--tk-surface-sunken);
color: var(--tk-text);
}
.hero-link {
display: inline-flex;
align-items: center;
gap: 3px;
margin-top: 16px;
font-size: var(--tk-text-base);
font-weight: 550;
color: var(--tk-accent-text);
text-decoration: none;
/* ==================== 事实条 ==================== */
.facts {
display: flex;
flex-wrap: wrap;
gap: 16px 48px;
padding: 44px 0 56px;
}
.hero-link:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
/* ==================== 概览数字条 ==================== */
.stats {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1px;
margin-bottom: 40px;
border-top: 1px solid var(--tk-border);
border-bottom: 1px solid var(--tk-border);
background: var(--tk-border);
}
.stat {
.fact {
display: flex;
flex-direction: column;
gap: 4px;
padding: 18px 20px;
background: var(--tk-bg);
}
.stat-value {
.fact-value {
font-size: var(--tk-text-xl);
font-weight: 650;
letter-spacing: -0.02em;
line-height: 1.2;
color: var(--tk-text);
}
.stat-label {
.fact-label {
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
}
/* ==================== 服务端工具专区 ==================== */
/* ==================== ====================
* 全页唯一有底纹的区块它需要和本地工具区分开这个区分值得用一整块底色来表达 */
.server-zone {
margin-bottom: 44px;
padding: 26px 26px 28px;
border: 1px solid var(--tk-border);
margin-bottom: var(--tk-space-9);
padding: 28px 30px 32px;
border-radius: var(--tk-radius-2xl);
background: var(--tk-accent-surface);
}
.zone-head {
margin-bottom: 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 24px;
margin-bottom: 22px;
}
.zone-heading {
min-width: 0;
}
.zone-title {
margin: 0 0 6px;
font-family: var(--tk-font-display);
font-size: var(--tk-text-xl);
font-weight: 650;
letter-spacing: -0.02em;
font-weight: 630;
letter-spacing: var(--tk-track-title);
color: var(--tk-text);
}
.zone-desc {
margin: 0 0 10px;
max-width: 60ch;
margin: 0;
max-width: 56ch;
font-size: var(--tk-text-base);
line-height: 1.7;
color: var(--tk-text-secondary);
}
.zone-link {
flex-shrink: 0;
display: inline-flex;
align-items: center;
gap: 3px;
font-size: var(--tk-text-sm);
margin-top: 3px;
font-size: var(--tk-text-base);
font-weight: 550;
color: var(--tk-accent-text);
text-decoration: none;
white-space: nowrap;
}
.zone-link:hover {
text-decoration: underline;
text-underline-offset: 3px;
}
/* 横向卡片:与服务端专区「接口」的气质一致,也与下方分类的竖向卡片区分开 */
/* +
* 也与下方分类的索引行无底色明显不同 */
.zone-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 12px;
}
.server-card {
display: flex;
align-items: flex-start;
gap: 13px;
padding: 15px 16px;
border: 1px solid var(--tk-border);
gap: 14px;
padding: 20px 22px;
border-radius: var(--tk-radius-xl);
background: var(--tk-surface);
text-decoration: none;
box-shadow: var(--tk-shadow-1);
transition:
border-color var(--tk-dur-fast) var(--tk-ease-out),
box-shadow var(--tk-dur-fast) var(--tk-ease-out),
transform var(--tk-dur-fast) var(--tk-ease-out);
}
.server-card:hover {
border-color: var(--tk-accent);
box-shadow: var(--tk-shadow-2);
transform: translateY(-2px);
}
.server-card:active {
transform: translateY(0);
}
.server-card-icon {
flex-shrink: 0;
width: 34px;
height: 34px;
width: 38px;
height: 38px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--tk-radius-md);
font-size: 19px;
font-size: 20px;
color: var(--tk-accent-text);
background: var(--tk-accent-surface-strong);
}
@ -412,7 +420,7 @@ function endpointOf(path: string): string {
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
gap: 4px;
}
.server-card-name {
font-size: var(--tk-text-base);
@ -421,127 +429,140 @@ function endpointOf(path: string): string {
}
.server-card-desc {
font-size: var(--tk-text-sm);
line-height: 1.5;
line-height: 1.6;
color: var(--tk-text-secondary);
}
.server-card-endpoint {
margin-top: 3px;
font-size: 11px;
margin-top: 4px;
font-size: var(--tk-text-xs);
letter-spacing: -0.01em;
color: var(--tk-text-placeholder);
}
/* ==================== 分类分区 ==================== */
/* ==================== ====================
* 分区之间用 80px 留白分隔不加横线留白本身就是分隔符
* 再画一条线等于把刚空出来的呼吸感又收回去了 */
.category {
margin-bottom: 40px;
margin-bottom: var(--tk-space-9);
}
.category-head {
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--tk-border);
margin-bottom: 2px;
}
.category-title {
margin: 0 0 3px;
font-size: var(--tk-text-lg);
font-weight: 650;
letter-spacing: -0.015em;
display: flex;
align-items: baseline;
gap: 12px;
margin: 0;
font-family: var(--tk-font-display);
font-size: var(--tk-text-2xl);
font-weight: 640;
letter-spacing: var(--tk-track-title);
line-height: 1.25;
color: var(--tk-text);
}
.category-desc {
margin: 0;
font-size: var(--tk-text-sm);
color: var(--tk-text-secondary);
}
/* 数量作为标题旁的弱化注记,而不是单独占一行 */
.category-count {
display: block;
margin-top: 6px;
font-size: var(--tk-text-xs);
font-size: var(--tk-text-sm);
font-weight: 400;
letter-spacing: 0;
color: var(--tk-text-placeholder);
}
.category-desc {
margin: 8px 0 0;
max-width: 56ch;
font-size: var(--tk-text-base);
line-height: 1.7;
color: var(--tk-text-secondary);
}
/* ==================== 工具卡片网格 ==================== */
.tool-grid {
/* 线线
* 多列时每列各自成栏列间距 48px 保证两栏文字不会读串行 */
.tool-index {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(248px, 1fr));
gap: 12px;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
column-gap: 48px;
}
.tool-card {
.tool-entry {
display: flex;
flex-direction: column;
gap: 7px;
padding: 16px;
border: 1px solid var(--tk-border);
border-radius: var(--tk-radius-xl);
background: var(--tk-surface);
align-items: flex-start;
gap: 14px;
padding: 20px 0;
border-top: 1px solid var(--tk-hairline);
text-decoration: none;
transition:
border-color var(--tk-dur-fast) var(--tk-ease-out),
transform var(--tk-dur-fast) var(--tk-ease-out),
box-shadow var(--tk-dur-fast) var(--tk-ease-out);
transition: transform var(--tk-dur-fast) var(--tk-ease-out);
}
.tool-card:hover {
border-color: var(--tk-accent);
transform: translateY(-2px);
box-shadow: var(--tk-shadow-2);
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.card-icon {
width: 32px;
height: 32px;
/* 58
* 主色只在 hover 时出现指向你正在看的这一条 */
.entry-icon {
flex-shrink: 0;
width: 22px;
height: 22px;
margin-top: 1px;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: var(--tk-radius-md);
font-size: 18px;
color: var(--tk-accent-text);
background: var(--tk-surface-sunken);
color: var(--tk-text-placeholder);
transition: color var(--tk-dur-fast) var(--tk-ease-out);
}
.tool-card:hover .card-icon {
background: var(--tk-accent-surface-strong);
.entry-body {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.card-tag {
flex-shrink: 0;
padding: 1px 7px;
border-radius: var(--tk-radius-pill);
font-size: 11px;
font-weight: 500;
color: var(--tk-accent-text);
background: var(--tk-surface-sunken);
}
.card-name {
.entry-name {
font-size: var(--tk-text-base);
font-weight: 600;
letter-spacing: -0.01em;
color: var(--tk-text);
transition: color var(--tk-dur-fast) var(--tk-ease-out);
}
.card-desc {
.entry-desc {
font-size: var(--tk-text-sm);
line-height: 1.55;
line-height: 1.6;
color: var(--tk-text-secondary);
}
.entry-badge {
flex-shrink: 0;
margin-top: 3px;
padding: 2px 9px;
border-radius: var(--tk-radius-pill);
font-size: var(--tk-text-xs);
font-weight: 500;
color: var(--tk-accent-text);
background: var(--tk-accent-surface-strong);
}
.tool-entry:hover .entry-name,
.tool-entry:hover .entry-icon {
color: var(--tk-accent-text);
}
.tool-entry:active {
transform: translateY(1px);
}
/* ==================== 空状态 ==================== */
.empty {
padding: 56px 20px;
padding: 64px 20px;
text-align: center;
}
.empty-title {
margin: 0 0 6px;
margin: 0 0 10px;
font-family: var(--tk-font-display);
font-size: var(--tk-text-lg);
font-weight: 650;
font-weight: 640;
letter-spacing: var(--tk-track-title);
color: var(--tk-text);
}
.empty-hint {
margin: 0 0 18px;
margin: 0 0 24px;
font-size: var(--tk-text-base);
color: var(--tk-text-secondary);
}
.empty-btn {
padding: 8px 16px;
min-height: 44px;
padding: 10px 22px;
border: 1px solid var(--tk-border-strong);
border-radius: var(--tk-radius-md);
background: var(--tk-surface);
@ -550,55 +571,86 @@ function endpointOf(path: string): string {
font-weight: 550;
color: var(--tk-text);
cursor: pointer;
transition:
border-color var(--tk-dur-fast) var(--tk-ease-out),
color var(--tk-dur-fast) var(--tk-ease-out);
}
.empty-btn:hover {
border-color: var(--tk-accent);
color: var(--tk-accent-text);
}
/* ==================== 响应式 ==================== */
/* ==================== ====================
* 留白随断点阶梯式收敛但每一级都保持标题明显大于正文这条底线 */
@media (max-width: 1279px) {
.tool-index {
column-gap: 36px;
}
}
@media (max-width: 1023px) {
.hero-title {
font-size: var(--tk-text-3xl);
font-size: 40px;
}
.stats {
grid-template-columns: repeat(2, 1fr);
.hero-search-input {
font-size: var(--tk-text-lg);
}
.facts {
gap: 16px 36px;
padding: 40px 0 48px;
}
.category {
margin-bottom: 56px;
}
.server-zone {
margin-bottom: 56px;
}
}
@media (max-width: 767px) {
.hero {
padding: 4px 0 28px;
padding: 8px 0 0;
}
.hero-title {
font-size: 28px;
max-width: none;
font-size: 32px;
}
.hero-desc {
margin-top: 14px;
font-size: var(--tk-text-base);
margin-bottom: 20px;
}
.hero-search {
height: 44px;
border-radius: var(--tk-radius-lg);
height: 52px;
margin-top: 26px;
}
.stats {
margin-bottom: 32px;
.hero-search-input {
font-size: var(--tk-text-md);
}
.stat {
padding: 14px 16px;
.facts {
gap: 16px 32px;
padding: 36px 0 40px;
}
.fact-value {
font-size: var(--tk-text-lg);
}
.server-zone {
padding: 20px 16px;
padding: 20px 18px 22px;
border-radius: var(--tk-radius-xl);
margin-bottom: 34px;
margin-bottom: 44px;
}
.zone-head {
flex-direction: column;
gap: 10px;
}
.zone-grid {
grid-template-columns: 1fr;
}
.tool-grid {
grid-template-columns: 1fr;
}
.category {
margin-bottom: 32px;
margin-bottom: 40px;
}
.category-title {
font-size: var(--tk-text-xl);
}
.tool-index {
grid-template-columns: 1fr;
}
}
</style>

View File

@ -225,7 +225,7 @@ function clearAll() {
grid-column: 1 / -1;
}
.info-label {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.info-value {

View File

@ -284,14 +284,14 @@ onMounted(() => {
.endpoint-path {
display: block;
margin-top: 2px;
font-size: 11px;
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
word-break: break-all;
}
.method-badge {
padding: 2px 7px;
border-radius: var(--tk-radius-xs);
font-size: 11px;
font-size: var(--tk-text-xs);
font-weight: 600;
color: var(--tk-text-secondary);
background: var(--tk-surface-sunken);
@ -352,7 +352,7 @@ onMounted(() => {
}
.latency-unit {
margin-left: 2px;
font-size: 11px;
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
}
/* 无底槽细条:只表达相对快慢 */

View File

@ -199,7 +199,7 @@ function onHistorySelect(text: string) {
flex-wrap: wrap;
}
.size-tip {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.ascii-block {
@ -209,7 +209,7 @@ function onHistorySelect(text: string) {
border-radius: 6px;
background: var(--td-bg-color-container);
font-family: ui-monospace, 'Courier New', Consolas, monospace;
font-size: 11px;
font-size: var(--tk-text-xs);
line-height: 1.1;
white-space: pre;
overflow-x: auto;

View File

@ -157,7 +157,7 @@ function onHistorySelect(text: string) {
}
.form-tip {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
</style>

View File

@ -234,7 +234,7 @@ function copySwatch(hex: string) {
.palette-name {
display: block;
padding: 4px 6px;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-secondary);
text-align: center;
overflow: hidden;

View File

@ -267,7 +267,7 @@ function onHistorySelect(text: string) {
background: var(--td-bg-color-secondarycontainer);
}
.field-label {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.field-value {

View File

@ -398,7 +398,7 @@ function onHistorySelect(text: string) {
}
.form-tip {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
/* 日期选择器:窄屏下占满可用宽度,避免固定宽度撑出横向滚动条 */

View File

@ -141,7 +141,7 @@ async function query() {
}
.w-updated {
margin: 8px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
</style>

View File

@ -91,7 +91,7 @@ async function query() {
}
.hint {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.head {
@ -143,7 +143,7 @@ async function query() {
line-height: 1.5;
}
.trace-time {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
margin-top: 2px;
}

View File

@ -116,7 +116,7 @@ function copyItem(item: LangItem) {
border-radius: 6px;
background: var(--td-bg-color-container);
font-family: ui-monospace, SFMono-Regular, Consolas, 'Courier New', monospace;
font-size: 12px;
font-size: var(--tk-text-xs);
line-height: 1.6;
white-space: pre-wrap;
word-break: break-all;

View File

@ -192,7 +192,7 @@ function clearAll() {
background: var(--td-bg-color-secondarycontainer);
}
.info-label {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.info-value {

View File

@ -428,7 +428,7 @@ onBeforeUnmount(() => {
width: 200px;
}
.form-suffix {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.form-tip {
@ -489,7 +489,7 @@ onBeforeUnmount(() => {
align-items: center;
justify-content: center;
border-radius: 6px;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-error-color);
border: 1px dashed var(--td-border-level-2-color);
background: var(--td-bg-color-container);
@ -509,12 +509,12 @@ onBeforeUnmount(() => {
}
.result-size {
margin: 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-secondary);
}
.result-error {
margin: 4px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-error-color);
}
.result-actions {

View File

@ -404,7 +404,7 @@ onBeforeUnmount(() => {
font-weight: 600;
}
.form-suffix {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.form-actions {
@ -470,7 +470,7 @@ onBeforeUnmount(() => {
}
.summary-tip {
margin: 4px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-warning-color);
}
.result-actions {

View File

@ -184,7 +184,7 @@ onBeforeUnmount(() => {
.block-tag {
padding: 1px 8px;
border-radius: var(--tk-radius-pill);
font-size: 11px;
font-size: var(--tk-text-xs);
color: var(--tk-text-placeholder);
background: var(--tk-surface-sunken);
}

View File

@ -217,7 +217,7 @@ function clearAll() {
font-size: 13px;
}
.info-label {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.info-value {

View File

@ -271,7 +271,7 @@ function onHistorySelect(text: string) {
}
.note {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
</style>

View File

@ -246,7 +246,7 @@ function onHistorySelect(text: string) {
}
.form-tip {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
@media (max-width: 767px) {

View File

@ -222,7 +222,7 @@ function detailLabel(k: string): string {
}
.hint {
margin: 10px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.res-main {
@ -259,7 +259,7 @@ function detailLabel(k: string): string {
border-radius: 8px;
}
.ri span {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.ri b {

View File

@ -224,7 +224,7 @@ onMounted(() => {
font-weight: 600;
}
.form-suffix {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.form-actions {
@ -253,7 +253,7 @@ onMounted(() => {
font-weight: 600;
}
.strength-entropy {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.strength-bar {

View File

@ -106,7 +106,7 @@ async function query() {
border-radius: 8px;
}
.pi span {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.pi b {

View File

@ -234,7 +234,7 @@ watch([width, height, bgColor, textColor, text, fontRatio], draw, { immediate: t
}
.hex-text {
font-family: ui-monospace, Consolas, monospace;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.text-input {

View File

@ -215,7 +215,7 @@ onBeforeUnmount(() => {
}
.form-tip {
margin: 12px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
@media (max-width: 767px) {

View File

@ -213,7 +213,7 @@ function onHistorySelect(raw: string) {
}
.hint-text {
margin: 8px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.no-match {
@ -252,7 +252,7 @@ function onHistorySelect(raw: string) {
justify-content: center;
border-radius: 50%;
background: var(--td-bg-color-secondarycontainer);
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-secondary);
}
.match-text {
@ -264,7 +264,7 @@ function onHistorySelect(raw: string) {
word-break: break-all;
}
.match-pos {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.match-groups {
@ -275,7 +275,7 @@ function onHistorySelect(raw: string) {
}
.group-tag {
font-family: monospace;
font-size: 12px;
font-size: var(--tk-text-xs);
padding: 2px 8px;
border-radius: 4px;
background: var(--td-bg-color-secondarycontainer);

View File

@ -295,7 +295,7 @@ function onHistorySelect(text: string) {
}
.tag {
flex-shrink: 0;
font-size: 11px;
font-size: var(--tk-text-xs);
padding: 1px 6px;
border-radius: 3px;
color: #fff;
@ -334,7 +334,7 @@ function onHistorySelect(text: string) {
}
.desc {
color: var(--td-text-color-secondary);
font-size: 12px;
font-size: var(--tk-text-xs);
}
.match-list {
margin-top: 12px;
@ -352,7 +352,7 @@ function onHistorySelect(text: string) {
}
.m-index {
color: var(--td-text-color-placeholder);
font-size: 12px;
font-size: var(--tk-text-xs);
}
.m-text {
font-family: ui-monospace, Consolas, monospace;

View File

@ -695,7 +695,7 @@ function clearAll() {
.char-count {
margin: 4px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
text-align: right;
}
@ -753,12 +753,12 @@ function clearAll() {
}
.dict-example {
margin: 6px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-secondary);
}
.dict-src {
margin: 10px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
@ -782,7 +782,7 @@ function clearAll() {
}
.s-hint {
margin: 8px 0 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
@ -796,7 +796,7 @@ function clearAll() {
}
.attr {
margin: 0;
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}

View File

@ -242,7 +242,7 @@ function onHistorySelect(text: string) {
background: var(--td-bg-color-secondarycontainer);
}
.info-label {
font-size: 12px;
font-size: var(--tk-text-xs);
color: var(--td-text-color-placeholder);
}
.info-value {

View File

@ -6,6 +6,9 @@ import { defineConfig, presetUno, transformerDirectives } from 'unocss'
* CSS --tk-* / --td-*
* tokens.scss
* arbitrary valuebg-[var(--tk-surface)]
*
* text-13px / text-16px
* 12px
*/
export default defineConfig({
presets: [presetUno()],
@ -17,13 +20,14 @@ export default defineConfig({
'form-row': 'flex flex-col gap-2 md:flex-row md:items-center md:gap-4',
// 结果代码块:等宽字体 + 面板块底色
'code-block':
'rounded-[var(--tk-radius-md)] bg-[var(--tk-surface-sunken)] px-3 py-2.5 font-mono text-13px leading-relaxed break-all whitespace-pre-wrap',
'rounded-[var(--tk-radius-md)] bg-[var(--tk-surface-sunken)] px-4 py-3.5 font-mono text-14px leading-relaxed break-all whitespace-pre-wrap',
// 通用面板(自定义区块的轻量容器)
// 自带内边距,直接写 class="panel tool-section" 即可,不必每处再补 padding。
// 本版容器重新「有形」:白底 + 实描边,与页面底(--tk-bg拉开层次。
// 注意:页面 scoped 样式(如 .block[data-v-*])权重高于本类,需要单独覆盖时照常写即可。
'panel':
'rounded-[var(--tk-radius-xl)] border border-[var(--tk-border)] bg-[var(--tk-surface)] p-5 md:p-6',
'rounded-[var(--tk-radius-xl)] border border-[var(--tk-border)] bg-[var(--tk-surface)] p-5 md:p-7',
// 说明性小字
'hint-text': 'text-12px text-[var(--tk-text-placeholder)]',
'hint-text': 'text-13px text-[var(--tk-text-placeholder)]',
},
})