Files
MailUI4Agents/client/electron/src/index.css

495 lines
17 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@tailwind base;
@tailwind components;
@tailwind utilities;
/*
* ─── 主题色板 ───
*
* 全站颜色都经 tailwind.config.js 指向这些变量,因此深色模式**不需要**
* 在组件里写 dark: 前缀。逐处加前缀的方案在这里必然失败:约 700 处颜色
* 散在 21 个组件里,漏一处就是深色下的白底白字,而且只有肉眼能发现。
*
* 深色模式的做法是**反转灰阶**white → 近黑、gray-900 → 近白。
* 这套代码里灰阶本身就是语义色阶(表面层次 / 分隔线 / 文字主次),
* 反转之后 `bg-white text-gray-900` 自动变成「深色卡片 + 浅色文字」。
* 新加的组件照常写浅色类名也自动适配。
*
* 值存 RGB 三元组而不是 #hex代码里有 bg-blue-50/70 这类透明度修饰符,
* Tailwind 会生成 rgb(var(--x) / 0.7),而 rgb(#f9fafb / 0.7) 是无效 CSS
* —— 那些半透明高亮会静默失效(不报错,只是不透明)。
*/
:root {
/*
* 表面色(卡片 / 输入框底)。深色模式下变暗。
*/
--c-white: 255 255 255;
/*
* 彩色按钮与深色框架上的文字。**不随主题反转。**
*
* 它与 --c-white 必须分开,因为 `white` 在这套代码里服务两种互相冲突的用途:
* - `bg-white` = 卡片表面 → 深色模式必须变暗
* - `text-white` = 按钮上文字 → 深色模式必须保持浅色
*
* 共用一个变量时后者跟着变暗,实测激活导航项的「收件」在 bg-chrome-700 上
* 只剩 1.34:1 —— 几乎看不见。见 tailwind.config.js 的 textColor 覆盖。
*/
--c-on-accent: 255 255 255;
--c-gray-50: 249 250 251;
--c-gray-100: 243 244 246;
--c-gray-200: 229 231 235;
--c-gray-300: 209 213 219;
/* 最小字号与 placeholder 也会使用这一档;在白底上保持至少 4.5:1。 */
--c-gray-400: 107 114 128;
--c-gray-500: 107 114 128;
--c-gray-600: 75 85 99;
--c-gray-700: 55 65 81;
--c-gray-800: 31 41 55;
--c-gray-900: 17 24 39;
--c-gray-950: 3 7 18;
/*
* 应用框架(侧栏 / 底部导航)。
*
* 独立成一条色阶而不跟 gray 走:这两块**在浅色模式下本来就是深色的**
* (深色侧栏配浅色内容区是这套 UI 的原本设计)。并入反转的 gray 之后,
* 深色模式下 bg-slate-900 会变成近白色 —— 侧栏比内容区还亮,
* 整个层次翻过来(实测 rgb(243,245,248) vs 内容区 rgb(17,19,24))。
*/
--c-chrome-100: 241 245 249;
--c-chrome-200: 226 232 240;
--c-chrome-400: 148 163 184;
--c-chrome-600: 71 85 105;
--c-chrome-700: 51 65 85;
--c-chrome-800: 30 41 59;
--c-chrome-900: 15 23 42;
/*
* ─── 强调色(浅色)───
*
* 值就是 Tailwind 的官方色阶,浅色模式下视觉零变化。
*
* 这条色阶与灰阶一样是**语义色阶**,而且两段的用途相反:
* - 50300 = 表面chip 底、提示条底、徽标底、边框)→ 深色下变暗
* - 400900 = 前景(文字、图标) → 深色下变亮
*
* 上一版把它们写成 tailwind.config.js 里的固定 hex同时又留了一份
* accent() 定义 —— JS 对象字面量重复键后者胜出,而当时 index.css 里
* 没有对应的 --c-red-* 等变量。`rgb(var(--c-red-600) / 1)` 里变量未定义会让
* **整条声明失效**,于是 bg-red-600 退回透明、白字落在白卡片上:
* 按钮看不见但点得动生产实测32 个变量全部缺失)。
*/
/* blue */
--c-blue-50: 239 246 255;
--c-blue-100: 219 234 254;
--c-blue-200: 191 219 254;
--c-blue-300: 147 197 253;
--c-blue-400: 96 165 250;
--c-blue-500: 59 130 246;
--c-blue-600: 37 99 235;
--c-blue-700: 29 78 216;
--c-blue-800: 30 64 175;
--c-blue-900: 30 58 138;
/* red */
--c-red-50: 254 242 242;
--c-red-100: 254 226 226;
--c-red-200: 254 202 202;
--c-red-300: 252 165 165;
--c-red-400: 248 113 113;
--c-red-500: 239 68 68;
/* 比 Tailwind 官方的 220 38 38 略暗:官方值落在 red-50 上只有 4.41:1
而 `bg-red-50 text-red-600` 正是错误提示条 —— 差 2% 也是差。
这一档同时用在白卡片上4.83 → 5.10),压暗后两处都过线。 */
--c-red-600: 213 37 37;
--c-red-700: 185 28 28;
--c-red-800: 153 27 27;
--c-red-900: 127 29 29;
/* green */
--c-green-50: 240 253 244;
--c-green-100: 220 252 231;
--c-green-200: 187 247 208;
--c-green-300: 134 239 172;
--c-green-400: 74 222 128;
--c-green-500: 34 197 94;
--c-green-600: 22 163 74;
--c-green-700: 21 128 61;
--c-green-800: 22 101 52;
--c-green-900: 20 83 45;
/* amber */
--c-amber-50: 255 251 235;
--c-amber-100: 254 243 199;
--c-amber-200: 253 230 138;
--c-amber-300: 252 211 77;
--c-amber-400: 251 191 36;
--c-amber-500: 245 158 11;
--c-amber-600: 217 119 6;
--c-amber-700: 180 83 9;
--c-amber-800: 146 64 14;
--c-amber-900: 120 53 15;
/* orange */
--c-orange-50: 255 247 237;
--c-orange-100: 255 237 213;
--c-orange-200: 254 215 170;
--c-orange-300: 253 186 116;
--c-orange-400: 251 146 60;
--c-orange-500: 249 115 22;
--c-orange-600: 234 88 12;
--c-orange-700: 194 65 12;
--c-orange-800: 154 52 18;
--c-orange-900: 124 45 18;
/* yellow */
--c-yellow-50: 254 252 232;
--c-yellow-100: 254 249 195;
--c-yellow-200: 254 240 138;
--c-yellow-300: 253 224 71;
--c-yellow-400: 250 204 21;
--c-yellow-500: 234 179 8;
--c-yellow-600: 202 138 4;
--c-yellow-700: 161 98 7;
--c-yellow-800: 133 77 14;
--c-yellow-900: 113 63 18;
/*
* ─── 实心按钮/徽标的底色 ───
*
* 与上面 --c-* 的 400900 **同值,但两种模式下都不变**。
*
* 为什么要单独一组那六档在深色模式下被提亮成了浅色red-600 →
* rgb(246,141,141)),因为 `text-red-600` 得在深底上读得动。而
* `bg-red-600 text-white` 的白字落在那个浅红上只有 1.6:1。
* 一个名字服务两种语义就必然坏掉一头 —— 与 text-white/bg-white 那次同理。
*
* 只有 backgroundColor 走这组(见 tailwind.config.js
*/
--s-blue-400: 96 165 250;
--s-blue-500: 59 130 246;
--s-blue-600: 37 99 235;
--s-blue-700: 29 78 216;
--s-blue-800: 30 64 175;
--s-blue-900: 30 58 138;
--s-red-400: 248 113 113;
--s-red-500: 239 68 68;
--s-red-600: 220 38 38;
--s-red-700: 185 28 28;
--s-red-800: 153 27 27;
--s-red-900: 127 29 29;
--s-green-400: 74 222 128;
--s-green-500: 34 197 94;
--s-green-600: 22 163 74;
--s-green-700: 21 128 61;
--s-green-800: 22 101 52;
--s-green-900: 20 83 45;
--s-amber-400: 251 191 36;
--s-amber-500: 245 158 11;
--s-amber-600: 217 119 6;
--s-amber-700: 180 83 9;
--s-amber-800: 146 64 14;
--s-amber-900: 120 53 15;
--s-orange-400: 251 146 60;
--s-orange-500: 249 115 22;
--s-orange-600: 234 88 12;
--s-orange-700: 194 65 12;
--s-orange-800: 154 52 18;
--s-orange-900: 124 45 18;
--s-yellow-400: 250 204 21;
--s-yellow-500: 234 179 8;
--s-yellow-600: 202 138 4;
--s-yellow-700: 161 98 7;
--s-yellow-800: 133 77 14;
--s-yellow-900: 113 63 18;
color-scheme: light;
}
/*
* 深色模式。
*
* # 灰阶整体反转
*
* - `white`(卡片底)→ 近黑的深灰。**不用纯黑**:纯黑上的浅色文字
* 对比过强,长时间看更累,也看不出层次。
* - `gray-50`(页面底)→ 比卡片**更暗**。浅色下页面底比卡片浅,
* 深色下必须反过来,否则卡片会陷进背景失去边界。
* - `gray-200/300`(分隔线)→ 中低亮度灰。照搬浅色值会得到刺眼的白线。
* - `gray-400/500`(次要文字)→ **提亮**。深底上的浅色 gray-400 只有
* 约 2:1 对比度,远低于 WCAG AA 的 4.5:1 —— 看得见但读不动。
*
* # 强调色不反转
*
* blue/red/green/... 在 tailwind.config.js 里是**固定值**,不走变量。
* 按钮底色在深色模式下依然是 blue-600 那样的彩色,跟着变会让主按钮
* 在深色页面上失去「这是主操作」的视觉重量。
*
* # 框架色阶只微调
*
* 见下面 --c-chrome-* 的注释。
*/
.dark {
--c-white: 24 27 33;
/* 近白而非纯白:深色页面上纯白字偏刺眼。关键是它不跟着 --c-white 变暗。 */
--c-on-accent: 244 246 250;
--c-gray-50: 17 19 24;
--c-gray-100: 32 36 44;
--c-gray-200: 44 49 59;
--c-gray-300: 61 68 81;
--c-gray-400: 138 146 161;
--c-gray-500: 165 173 186;
--c-gray-600: 190 197 208;
--c-gray-700: 212 217 225;
--c-gray-800: 231 235 240;
--c-gray-900: 243 245 248;
--c-gray-950: 250 251 253;
/*
* 框架色阶**不反转,只微调**:比内容区(--c-gray-50 = 17 19 24
* 再深一档,保持「框架比内容更沉」这个浅色下就有的关系。
* 文字档位相应提亮 —— 底色变深后原来的 chrome-400 只剩约 2.9:1。
*/
--c-chrome-100: 236 240 246;
--c-chrome-200: 214 221 232;
--c-chrome-400: 148 158 175;
--c-chrome-600: 58 65 78;
--c-chrome-700: 44 50 61;
--c-chrome-800: 30 35 44;
--c-chrome-900: 12 14 18;
/*
* ─── 强调色(深色)───
*
* 两段走向相反,理由都是实测出来的:
*
* **表面段 50300 变暗**(朝色相方向偏移卡片色)。照搬浅色值的话
* red-50 (#fef2f2) 在深色页面上是一块近白亮斑 —— 那是「错误提示条」的底,
* 结果比正文还抢眼,而它上面的红字反而读不动。
*
* **前景段 400900 变亮**。照搬浅色值时 red-700 (#b91c1c) 落在深色卡片上
* 只有 2.67:1、amber-900 只有 1.90:1 —— 远低于 WCAG AA 的 4.5:1。
* 这里每一档都拉到 ≥4.5(实测最低 red-400 = 5.93
* 由 test/theme.test.mjs 逐档断言。
*
* 实心按钮底不在这里 —— 那组是 --s-*,定义在 :root 且两种模式同值。
*/
/* blue */
--c-blue-50: 28 37 54;
--c-blue-100: 30 43 67;
--c-blue-200: 32 52 84;
--c-blue-300: 36 62 105;
--c-blue-400: 92 152 247;
--c-blue-500: 108 162 248;
--c-blue-600: 128 175 249;
--c-blue-700: 154 191 250;
--c-blue-800: 183 210 251;
--c-blue-900: 213 228 253;
/* red */
--c-red-50: 46 31 37;
--c-red-100: 58 34 39;
--c-red-200: 76 37 41;
--c-red-300: 97 41 45;
--c-red-400: 243 109 109;
--c-red-500: 244 124 124;
--c-red-600: 246 141 141;
--c-red-700: 248 164 164;
--c-red-800: 250 191 191;
--c-red-900: 252 217 217;
/* green */
--c-green-50: 25 44 39;
--c-green-100: 26 54 43;
--c-green-200: 26 68 48;
--c-green-300: 27 85 54;
--c-green-400: 34 197 94;
--c-green-500: 56 203 110;
--c-green-600: 83 210 129;
--c-green-700: 118 219 155;
--c-green-800: 158 229 184;
--c-green-900: 198 240 213;
/* amber */
--c-amber-50: 46 40 31;
--c-amber-100: 59 48 29;
--c-amber-200: 77 58 28;
--c-amber-300: 99 72 26;
--c-amber-400: 245 158 11;
--c-amber-500: 246 168 35;
--c-amber-600: 247 179 65;
--c-amber-700: 249 195 104;
--c-amber-800: 251 212 148;
--c-amber-900: 252 230 192;
/* orange */
--c-orange-50: 47 36 32;
--c-orange-100: 60 41 31;
--c-orange-200: 78 48 30;
--c-orange-300: 101 57 29;
--c-orange-400: 249 115 22;
--c-orange-500: 250 129 45;
--c-orange-600: 250 146 73;
--c-orange-700: 251 168 111;
--c-orange-800: 252 193 152;
--c-orange-900: 253 219 194;
/* yellow */
--c-yellow-50: 45 42 31;
--c-yellow-100: 58 51 29;
--c-yellow-200: 74 63 27;
--c-yellow-300: 95 79 25;
--c-yellow-400: 234 179 8;
--c-yellow-500: 236 187 33;
--c-yellow-600: 239 196 62;
--c-yellow-700: 242 208 102;
--c-yellow-800: 246 222 146;
--c-yellow-900: 250 235 191;
/* 让浏览器把滚动条、表单控件、autofill 一并切深色 */
color-scheme: dark;
}
@layer base {
:root {
/* main.tsx 会用 Visual Viewport 覆盖;这里保证脚本执行前也有正确高度。 */
--app-height: 100vh;
}
@supports (height: 100dvh) {
:root {
--app-height: 100dvh;
}
}
html, body, #root {
height: var(--app-height);
min-height: 0;
}
body {
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
/*
* 显式给 body 底色。移动端橡皮筋回弹时露出的是 body 背景 ——
* 不设的话深色模式下滑到边界会闪出一条白边。
*/
background-color: rgb(var(--c-gray-50));
color: rgb(var(--c-gray-900));
}
/* 统一作者样式,避免 Chromium/Electron autofill 在深色卡片上画白底。 */
input:not([type='checkbox']):not([type='radio']):not([type='file']),
textarea,
select {
background-color: rgb(var(--c-white));
color: rgb(var(--c-gray-900));
}
input::placeholder,
textarea::placeholder {
color: rgb(var(--c-gray-400));
}
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-text-fill-color: rgb(var(--c-gray-900));
box-shadow: 0 0 0 1000px rgb(var(--c-white)) inset;
transition: background-color 9999s ease-out;
}
}
@layer components {
/* react-markdown 的本地排版层;不依赖固定色板的 typography 插件。 */
.markdown {
color: rgb(var(--c-gray-800));
overflow-wrap: anywhere;
}
.markdown > * + * { margin-top: 0.75rem; }
.markdown h1 { font-size: 1.5rem; line-height: 2rem; font-weight: 700; }
.markdown h2 { font-size: 1.25rem; line-height: 1.75rem; font-weight: 700; }
.markdown h3 { font-size: 1.05rem; line-height: 1.5rem; font-weight: 600; }
.markdown ul { list-style: disc; padding-left: 1.35rem; }
.markdown ol { list-style: decimal; padding-left: 1.35rem; }
.markdown li + li { margin-top: 0.25rem; }
.markdown a { color: rgb(var(--c-blue-600)); text-decoration: underline; }
.markdown blockquote {
border-left: 3px solid rgb(var(--c-gray-300));
padding-left: 0.75rem;
color: rgb(var(--c-gray-600));
}
.markdown code {
border-radius: 0.25rem;
background: rgb(var(--c-gray-100));
padding: 0.1rem 0.3rem;
font-size: 0.875em;
}
.markdown pre {
overflow-x: auto;
border: 1px solid rgb(var(--c-gray-200));
border-radius: 0.5rem;
background: rgb(var(--c-gray-100));
padding: 0.75rem;
}
.markdown pre code { background: transparent; padding: 0; }
.markdown table { width: 100%; border-collapse: collapse; display: block; overflow-x: auto; }
.markdown th, .markdown td { border: 1px solid rgb(var(--c-gray-200)); padding: 0.4rem 0.55rem; }
.markdown th { background: rgb(var(--c-gray-100)); text-align: left; }
}
@layer utilities {
/*
* .tap —— 保证 44x44 的触摸命中区,但不改变视觉尺寸。
*
* 移动端 44x44 是通行下限Apple HIG 与 Material 都取这个数),而这些图标/
* 小字按钮视觉上只有 15-24px 高 —— 实测「标记已读」48x16、「转发」42x16、
* 「抄送」20x15。直接加 padding 会把本来就挤的头部撑散,在 320px 屏上还会换行。
*
* 改用居中的透明伪元素扩大命中区:视觉一像素不动,手指够得到。
*
* 只在窄屏生效:桌面用鼠标,精度足够;而扩大后的命中区在密排的工具栏里
* 会互相重叠,点一个可能命中隔壁那个。
*/
/* 与 useIsNarrow / Tailwind lg 统一:竖屏平板也需要触摸命中区。 */
@media (max-width: 1023px) {
.safe-frame {
padding-top: env(safe-area-inset-top);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.narrow-nav {
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
}
.form-editing .narrow-nav {
display: none;
}
.tap {
position: relative;
}
.tap::after {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 100%;
height: 100%;
min-width: 44px;
min-height: 44px;
transform: translate(-50%, -50%);
}
}
/*
* .reveal —— 悬停才显形的次要动作(写信 / 归档)。
*
* 原先直接写 `opacity-0 group-hover:opacity-100`。**触摸设备没有 hover**
* 于是这些按钮永远是透明的,却仍然接收点击 —— 实测在联系人列表上
* elementFromPoint 命中的就是那个看不见的「归档」。一个看不见却按得动的
* 破坏性按钮比没有按钮更糟:人以为自己点的是卡片,实际归档了一条会话。
*
* 因此默认可见,只在**真的支持悬停**的设备上才隐藏。判据用
* `(hover: hover) and (pointer: fine)`:单看 hover 会把带触摸板的平板算进去。
*/
.reveal {
opacity: 1;
}
@media (hover: hover) and (pointer: fine) {
.reveal {
opacity: 0;
transition: opacity 150ms;
}
.group:hover .reveal,
.reveal:focus-within {
opacity: 1;
}
}
}