Files
Quick-Paste/renderer/styles.css
2026-09-20 23:49:24 +08:00

2125 lines
49 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.
/* ============================================================
Quick Paste — 精致深色主题(基于 Linear/Notion 设计语言)
============================================================ */
:root {
/* Backgrounds */
--bg-base: #08090A;
--bg-elevated: #0D0E10;
--bg-overlay: #161719;
--bg-hover: #1B1C1F;
--bg-active: #262830;
--bg-input: #0F1012;
/* Borders */
--border-subtle: rgba(255, 255, 255, 0.06);
--border-default: rgba(255, 255, 255, 0.09);
--border-strong: rgba(255, 255, 255, 0.14);
/* Text */
--text-primary: #F7F8F8;
--text-secondary: rgba(247, 248, 248, 0.66);
--text-tertiary: rgba(247, 248, 248, 0.42);
--text-disabled: rgba(247, 248, 248, 0.25);
/* Accent */
--accent: #5E6AD2;
--accent-hover: #7170FF;
--accent-active: #4F5BC8;
--accent-subtle: rgba(94, 106, 210, 0.13);
--accent-glow: rgba(94, 106, 210, 0.28);
/* Accent 半透明色:用于 toggle 激活态 hover、激活边线、占位符 mark 等 */
--accent-hover-bg: rgba(94, 106, 210, 0.18);
--accent-border: rgba(94, 106, 210, 0.22);
--accent-mark-bg: rgba(94, 106, 210, 0.22);
--accent-pulse: rgba(94, 106, 210, 0.45);
/* Status */
--danger: #F87171;
--danger-hover: #FCA5A5;
--danger-subtle: rgba(248, 113, 113, 0.13);
--success: #4CB782;
--success-subtle: rgba(76, 183, 130, 0.13);
--warning: #FBBF24;
--warning-subtle: rgba(251, 191, 36, 0.13);
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4), 0 1px 3px rgba(0, 0, 0, 0.3);
--shadow-lg: 0 16px 32px rgba(0, 0, 0, 0.55), 0 2px 8px rgba(0, 0, 0, 0.3);
--shadow-glow: 0 0 0 1px var(--accent-glow), 0 0 16px var(--accent-glow);
/* Radius */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 10px;
--radius-xl: 14px;
/* Motion */
--ease: cubic-bezier(0.16, 1, 0.3, 1);
--ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
--duration-fast: 120ms;
--duration-base: 180ms;
--duration-slow: 260ms;
}
/* ===== 亮色主题覆盖 ===== */
body[data-theme="light"] {
--bg-base: #FAFAF9;
--bg-elevated: #FFFFFF;
--bg-overlay: #FFFFFF;
--bg-hover: #F4F4F2;
--bg-active: #E8E8E5;
--bg-input: #FAFAF9;
--border-subtle: rgba(15, 17, 21, 0.06);
--border-default: rgba(15, 17, 21, 0.09);
--border-strong: rgba(15, 17, 21, 0.14);
--text-primary: #1A1B1E;
--text-secondary: rgba(15, 17, 21, 0.66);
--text-tertiary: rgba(15, 17, 21, 0.42);
--text-disabled: rgba(15, 17, 21, 0.25);
--accent: #5563D3;
--accent-hover: #4F5BC8;
--accent-active: #414FB8;
--accent-subtle: rgba(85, 99, 211, 0.10);
--accent-glow: rgba(85, 99, 211, 0.22);
--accent-hover-bg: rgba(85, 99, 211, 0.14);
--accent-border: rgba(85, 99, 211, 0.22);
--accent-mark-bg: rgba(85, 99, 211, 0.20);
--accent-pulse: rgba(85, 99, 211, 0.35);
--danger: #DC2626;
--danger-hover: #B91C1C;
--danger-subtle: rgba(220, 38, 38, 0.08);
--success: #15803D;
--success-subtle: rgba(21, 128, 61, 0.08);
--warning: #B45309;
--warning-subtle: rgba(180, 83, 9, 0.10);
--shadow-sm: 0 1px 2px rgba(15, 17, 21, 0.06);
--shadow-md: 0 4px 12px rgba(15, 17, 21, 0.08), 0 1px 3px rgba(15, 17, 21, 0.04);
--shadow-lg: 0 16px 32px rgba(15, 17, 21, 0.12), 0 2px 8px rgba(15, 17, 21, 0.06);
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html, body {
height: 100%;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI",
"Microsoft YaHei", "PingFang SC", sans-serif;
font-size: 13px;
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
color: var(--text-primary);
background: var(--bg-base);
overflow: hidden;
user-select: none;
letter-spacing: -0.01em;
transition: background var(--duration-base) var(--ease),
color var(--duration-base) var(--ease);
}
button {
font-family: inherit;
cursor: pointer;
border: none;
outline: none;
background: transparent;
color: var(--text-primary);
letter-spacing: inherit;
}
input, textarea {
font-family: inherit;
font-size: 13px;
color: var(--text-primary);
background: var(--bg-input);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
padding: 8px 10px;
outline: none;
user-select: text;
transition: border-color var(--duration-fast) var(--ease),
box-shadow var(--duration-fast) var(--ease),
background var(--duration-fast) var(--ease);
}
input::placeholder, textarea::placeholder {
color: var(--text-tertiary);
}
input:hover, textarea:hover {
border-color: var(--border-strong);
}
input:focus, textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-subtle);
}
/* ===== 通用图标系统 ===== */
.icon {
width: 14px;
height: 14px;
flex-shrink: 0;
display: inline-block;
vertical-align: middle;
fill: currentColor;
}
/* ===== 标题栏 ===== */
#titlebar {
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px 0 12px;
background: var(--bg-elevated);
border-bottom: 1px solid var(--border-subtle);
-webkit-app-region: drag;
position: relative;
z-index: 10;
}
#titlebar::after {
content: '';
position: absolute;
left: 0; right: 0; bottom: -1px; height: 1px;
background: linear-gradient(90deg,
transparent 0%,
var(--accent-subtle) 30%,
var(--accent-subtle) 70%,
transparent 100%);
opacity: 0.65;
pointer-events: none;
}
#titlebar .title {
display: flex;
align-items: center;
gap: 10px;
}
#titlebar .logo {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 6px;
/* 图标本身已经有自己的配色和背景,不再叠渐变;
浅薄边用主题边框变量(rgba(255,255,255,0.08) 在亮色主题下完全不可见)。 */
background: transparent;
box-shadow: 0 0 0 1px var(--border-default), 0 2px 8px var(--accent-glow);
}
/* 不再铺满 logo 容器,缩小到 18px 留 3px 内边距:和标题栏文字字号(13px)拉开一档,
视觉上更精致,也避免紧贴边框显得拥挤。
ICO 内置 16/32/48 多尺寸,Chromium 会自动挑最接近的那一档,
无需指定具体 width/height(反而会让高 DPI 屏显得模糊)。 */
#titlebar .logo .logo-img {
width: 18px;
height: 18px;
display: block;
/* 注意:不要写 image-rendering: -webkit-optimize-contrast ——
它在 Chromium 里等价于 crisp-edges(像素化),与直觉相反。
默认值 auto 就是平滑缩放,18px 渲染 ICO 内置位图无需额外干预。 */
}
#titlebar .title-name {
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
letter-spacing: -0.01em;
}
#titlebar .title-actions {
display: flex;
align-items: center;
gap: 2px;
-webkit-app-region: no-drag;
}
/* ===== 下拉菜单(标题栏 ⋯ / 分组 ⋯)已移除:右键分组用的是 .context-menu,
标题栏没有 ⋯ 按钮。下方的 .dropdown-* 选择器和 @keyframes dropdown-in 已清理。 ===== */
.icon-btn {
width: 28px;
height: 28px;
border-radius: var(--radius-md);
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
background: transparent;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease);
position: relative;
}
.icon-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.icon-btn:active {
background: var(--bg-active);
transform: scale(0.94);
}
.icon-btn .icon {
width: 15px;
height: 15px;
}
/* 标题栏里的开关按钮(如置顶):激活时高亮,使用 accent 色暗示「已开启」 */
.icon-btn.toggle-on {
background: var(--accent-subtle);
color: var(--accent);
}
.icon-btn.toggle-on:hover {
background: var(--accent-hover-bg);
color: var(--accent-hover);
}
/* 键盘焦点可见性:button 全局 outline:none 后,Tab 焦点需要明确的视觉反馈(WCAG 2.4.7)。
:focus-visible 只对键盘聚焦生效,鼠标点击不会出现多余的框。 */
button:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
/* 链接(设置面板「关于」区的开发者链接等)同上面的理由 */
a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
/* 主题化选区颜色:输入框/文本域里选中文字时用 accent 色而不是浏览器默认蓝 */
::selection {
background: var(--accent-mark-bg);
}
/* ===== 通用按钮 ===== */
.mini-btn {
width: 22px;
height: 22px;
border-radius: var(--radius-sm);
background: transparent;
color: var(--text-secondary);
display: inline-flex;
align-items: center;
justify-content: center;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease);
}
.mini-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.mini-btn:active {
background: var(--bg-active);
transform: scale(0.92);
}
.mini-btn .icon {
width: 12px;
height: 12px;
}
.text-btn {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 5px 9px;
border-radius: var(--radius-md);
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
background: transparent;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease);
}
.text-btn:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.text-btn:active {
background: var(--bg-active);
}
.text-btn.danger:hover {
background: var(--danger-subtle);
color: var(--danger);
}
.text-btn .icon {
width: 12px;
height: 12px;
}
.primary-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 7px 14px;
border-radius: var(--radius-md);
font-size: 12.5px;
font-weight: 500;
background: var(--accent);
color: #fff;
transition: background var(--duration-fast) var(--ease),
box-shadow var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease);
box-shadow: 0 0 0 1px rgba(255,255,255,0.05) inset,
0 1px 2px rgba(0,0,0,0.3);
}
.primary-btn:hover {
background: var(--accent-hover);
box-shadow: 0 0 0 1px rgba(255,255,255,0.08) inset,
0 0 0 4px var(--accent-subtle),
0 1px 2px rgba(0,0,0,0.3);
}
.primary-btn:active {
background: var(--accent-active);
transform: translateY(0.5px);
}
.primary-btn:disabled {
background: var(--bg-active);
color: var(--text-disabled);
cursor: not-allowed;
box-shadow: none;
transform: none;
}
.primary-btn:disabled:hover {
background: var(--bg-active);
box-shadow: none;
}
.primary-btn .icon {
width: 14px;
height: 14px;
}
/* ===== 主体 ===== */
#main {
display: flex;
height: calc(100% - 44px);
}
/* ===== 左侧分组 ===== */
#sidebar {
width: 112px;
background: var(--bg-elevated);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
flex-shrink: 0;
overflow: hidden;
transition: width var(--duration-base) var(--ease),
border-right-width var(--duration-base) var(--ease),
opacity var(--duration-fast) var(--ease);
}
/* 折叠态:width 收到 0,content 自然铺满剩余宽度 */
#main.sidebar-collapsed #sidebar {
width: 0;
border-right-width: 0;
opacity: 0;
pointer-events: none;
}
/* 折叠 / 展开按钮(原 content-expand-btn)已统一到 .add-bar 的左侧,这里不再保留样式。
新位置请见 .add-bar 段。*/
/* ===== 侧栏顶部:收藏 / 最近(独立区域,跨分组的快捷入口) ===== */
.sidebar-quick {
flex-shrink: 0;
padding: 8px 4px 6px;
border-bottom: 1px solid var(--border-subtle);
}
.quick-item {
position: relative;
display: flex;
align-items: center;
gap: 6px;
width: 100%;
padding: 6px 8px 6px 10px;
/* 与 .group-item 的间距节奏保持一致(同为侧栏列表项) */
margin-bottom: 2px;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
background: transparent;
border: 1px solid transparent;
text-align: left;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease);
}
.quick-item:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.quick-item.active {
background: var(--accent-subtle);
color: var(--text-primary);
border-color: var(--accent-border);
}
.quick-item .icon {
width: 14px;
height: 14px;
flex-shrink: 0;
color: var(--text-tertiary);
}
.quick-item[data-filter="fav"] .icon {
color: var(--warning);
}
.quick-item[data-filter="recent"] .icon {
color: var(--accent-hover);
}
.quick-item .name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.quick-item .count {
margin-left: auto;
font-size: 10.5px;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: var(--text-tertiary);
padding: 1px 6px;
border-radius: 999px;
background: var(--bg-hover);
min-width: 18px;
text-align: center;
}
.quick-item.active .count {
color: var(--text-secondary);
/* 同 .group-item.active .count:用主题变量,亮色主题下白 7% 不可见 */
background: var(--bg-active);
}
.sidebar-section {
flex: 1;
display: flex;
flex-direction: column;
min-height: 0;
}
.sidebar-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 10px 8px;
font-size: 10.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-tertiary);
}
#group-list {
flex: 1;
list-style: none;
overflow-y: auto;
/* padding-top 至少留 2px:拖拽排序时第一条分组的 drop-above 插入线
* 是 box-shadow: 0 -2px,伸出 li 上边缘 2px,padding 为 0 会被 overflow 裁掉 */
padding: 4px 4px 10px;
}
.group-item {
position: relative;
padding: 6px 8px 6px 10px;
/* margin-bottom 至少 2px:drop-below 插入线是 box-shadow: 0 2px,
* 间隙只有 1px 时线的下半截会被后绘制的兄弟项背景盖住,
* 导致 drop-below(1px) 比 drop-above(2px) 细一半 */
margin-bottom: 2px;
border-radius: var(--radius-md);
/* 与 .phrase-item 统一的拖拽光标:分组同样「点击选中 + 拖拽排序」,
抓手光标暗示可拖动(短语项已建立这个先例) */
cursor: grab;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 6px;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease),
box-shadow var(--duration-fast) var(--ease);
border: 1px solid transparent;
}
.group-item:active {
cursor: grabbing;
}
.group-item:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.group-item.active {
background: var(--accent-subtle);
color: var(--text-primary);
border-color: var(--accent-border);
}
.group-item.active::before {
content: '';
position: absolute;
/* 不能小于 -4px:#group-list 的 padding-left 是 4px,overflow 在 padding box 处裁剪,
-6px 时 3px 宽的指示条有 2px 在裁剪区外,只显示 1px */
left: -4px;
top: 50%;
transform: translateY(-50%) scaleY(0.6);
width: 3px;
height: 14px;
background: var(--accent);
border-radius: 2px;
box-shadow: 0 0 8px var(--accent-glow);
opacity: 0;
animation: active-indicator-in 0.32s var(--ease) forwards;
}
@keyframes active-indicator-in {
from { opacity: 0; transform: translateY(-50%) scaleY(0.4); }
to { opacity: 1; transform: translateY(-50%) scaleY(1); }
}
.group-item.dragging {
opacity: 0.4;
}
.group-item.drop-above {
box-shadow: 0 -2px 0 var(--accent);
}
.group-item.drop-below {
box-shadow: 0 2px 0 var(--accent);
}
/* 把短语拖到分组上时整条高亮,区别于 drop-above/below 的「上下插入线」。
* 视觉上沿用 .active 的着色但多一层 inset 描边,让用户立刻意识到「整条分组是落点」,
* 而不是「在这里上下插入」。 */
.group-item.phrase-drop-target {
background: var(--accent-subtle);
border-color: var(--accent);
color: var(--text-primary);
box-shadow: inset 0 0 0 1px var(--accent);
}
.group-item .name {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.group-item .count {
margin-left: auto;
font-size: 10.5px;
font-weight: 500;
font-variant-numeric: tabular-nums;
color: var(--text-tertiary);
padding: 1px 6px;
border-radius: 999px;
background: var(--bg-hover);
min-width: 18px;
text-align: center;
}
.group-item.active .count {
color: var(--text-secondary);
/* 用主题变量而不是 rgba(255,255,255,0.07):亮色主题下白 7% 的胶囊底不可见 */
background: var(--bg-active);
}
/* 侧栏底部:只放一个折叠按钮,靠右下对齐——尺寸与 add-bar 里的展开按钮一致 */
.sidebar-footer {
flex-shrink: 0;
padding: 10px 10px 10px 12px;
border-top: 1px solid var(--border-subtle);
display: flex;
justify-content: flex-end;
}
/* 折叠 / 展开按钮有两个位置:
① 侧栏展开时:放在 .sidebar-footer 的右下角;
② 侧栏折叠时:放在 .add-bar 左侧(与「+ 添加短语」并排)。
两份按钮的尺寸统一为 32×32,让用户在任何状态下看到的「折叠/展开」控件大小一致。 */
.sidebar-toggle-btn {
flex-shrink: 0;
width: 32px;
height: 32px;
border-radius: var(--radius-md);
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease);
}
.sidebar-toggle-btn .icon {
width: 14px;
height: 14px;
}
/* add-bar 里的展开按钮:与「+ 添加短语」同高,左对齐;尺寸与 .sidebar-toggle-btn 一致 */
.sidebar-expand-btn {
width: 32px;
height: 32px;
flex-shrink: 0;
border-radius: var(--radius-md);
}
.sidebar-expand-btn .icon {
width: 14px;
height: 14px;
}
/* 互斥:侧栏展开时,add-bar 里的展开按钮隐藏;折叠时由 .sidebar-footer 自然被 width:0 裁掉 */
#main:not(.sidebar-collapsed) .sidebar-expand-btn {
display: none;
}
.dim {
color: var(--text-tertiary);
}
/* ===== 右侧内容 ===== */
#content {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
background: var(--bg-base);
min-width: 0;
}
.content-header {
padding: 12px 14px;
border-bottom: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
background: var(--bg-base);
flex-shrink: 0;
}
.content-header-left {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
}
.content-header h2 {
font-size: 14px;
font-weight: 600;
letter-spacing: -0.015em;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.phrase-count {
font-size: 11px;
font-weight: 500;
color: var(--text-tertiary);
font-variant-numeric: tabular-nums;
flex-shrink: 0;
}
/* ===== 搜索 ===== */
.search-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--bg-base);
position: relative;
flex-shrink: 0;
}
.search-bar .search-icon {
position: absolute;
left: 22px;
top: 50%;
transform: translateY(-50%);
color: var(--text-tertiary);
pointer-events: none;
width: 13px;
height: 13px;
}
.search-bar input {
width: 100%;
/* 右侧要避开同位置互斥的两个控件:清空按钮(18px)和「/」快捷键提示(约 22px),
取较宽的提示计算:right:14px(距输入框右缘 2px)+ 22px + 8px 间隙 = 32px;
之前的 60px 会让长文本提前 34px 开始横向滚动 */
padding: 6px 32px 6px 28px;
font-size: 12.5px;
}
/* 空搜索框时显示在输入框右侧的快捷键提示,与清空按钮同位置互斥(JS 同步 hidden) */
.search-hint-kbd {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
padding: 1px 7px;
font-size: 10.5px;
color: var(--text-tertiary);
pointer-events: none;
}
.search-clear {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
width: 18px;
height: 18px;
border-radius: 50%;
color: var(--text-tertiary);
background: var(--bg-hover);
display: inline-flex;
align-items: center;
justify-content: center;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease);
}
.search-clear:hover {
background: var(--bg-active);
color: var(--text-primary);
}
.search-clear .icon {
width: 9px;
height: 9px;
}
/* ===== 短语列表 ===== */
#phrase-list {
flex: 1;
list-style: none;
overflow-y: auto;
padding: 4px 8px 12px;
}
.phrase-item {
padding: 10px 12px;
margin-bottom: 4px;
border-radius: var(--radius-md);
cursor: pointer;
background: var(--bg-elevated);
border: 1px solid var(--border-subtle);
transition: background var(--duration-fast) var(--ease),
border-color var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease),
opacity var(--duration-fast) var(--ease),
box-shadow var(--duration-fast) var(--ease);
position: relative;
}
.phrase-item:hover {
background: var(--bg-hover);
border-color: var(--border-default);
}
.phrase-item.selected {
background: var(--accent-subtle);
border-color: var(--accent);
box-shadow: 0 0 0 1px var(--accent-subtle);
}
.phrase-item.fired {
background: var(--accent-subtle);
border-color: var(--accent);
animation: phrase-pulse 0.55s var(--ease);
}
@keyframes phrase-pulse {
0% { box-shadow: 0 0 0 0 var(--accent-pulse); transform: scale(1); }
35% { box-shadow: 0 0 0 6px transparent; transform: scale(1.012); }
100% { box-shadow: 0 0 0 0 transparent; transform: scale(1); }
}
.phrase-item.dragging {
opacity: 0.4;
}
/* 可拖拽短语:鼠标变抓手,光标暗示「可以拖动」
* 注意不要作用在按钮上:移动端/键盘用户点击操作会更舒服 */
.phrase-item[draggable="true"] {
cursor: grab;
}
.phrase-item[draggable="true"]:active {
cursor: grabbing;
}
.phrase-item[draggable="true"] button {
cursor: pointer;
}
.phrase-item.drop-above {
box-shadow: 0 -2px 0 var(--accent);
}
.phrase-item.drop-below {
box-shadow: 0 2px 0 var(--accent);
}
/* 选中项作为放置目标时:box-shadow 不跨 class 叠加(后声明的 box-shadow 会整体覆盖),
* 需要显式把选中描边和插入线写在一起,否则拖过选中项时选中描边会闪没 */
.phrase-item.selected.drop-above {
box-shadow: 0 -2px 0 var(--accent), 0 0 0 1px var(--accent-subtle);
}
.phrase-item.selected.drop-below {
box-shadow: 0 2px 0 var(--accent), 0 0 0 1px var(--accent-subtle);
}
.phrase-top {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 3px;
}
.phrase-fav {
flex-shrink: 0;
color: var(--text-tertiary);
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
border-radius: 4px;
transition: color var(--duration-fast) var(--ease),
background var(--duration-fast) var(--ease);
}
.phrase-fav:hover {
background: var(--bg-active);
}
.phrase-fav.on {
color: var(--warning);
}
.phrase-fav.on .icon {
animation: star-pop 0.36s var(--ease);
}
.phrase-fav .icon {
width: 12px;
height: 12px;
transition: transform var(--duration-fast) var(--ease);
}
.phrase-fav:active .icon {
transform: scale(0.82);
}
@keyframes star-pop {
0% { transform: scale(1) rotate(0deg); }
40% { transform: scale(1.45) rotate(-6deg); }
70% { transform: scale(0.95) rotate(2deg); }
100% { transform: scale(1) rotate(0deg); }
}
.phrase-title {
font-weight: 500;
font-size: 13px;
color: var(--text-primary);
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.phrase-actions {
flex-shrink: 0;
display: flex;
gap: 1px;
opacity: 0;
transition: opacity var(--duration-fast) var(--ease);
}
.phrase-item:hover .phrase-actions,
.phrase-item.selected .phrase-actions,
.phrase-actions:focus-within {
/* focus-within:按钮虽然平时透明,但仍可被 Tab 聚焦——
键盘焦点进入时必须显示,否则焦点落在不可见元素上 */
opacity: 1;
}
.phrase-actions .action-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 5px;
color: var(--text-secondary);
background: transparent;
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease),
transform var(--duration-fast) var(--ease);
}
.phrase-actions .action-btn:hover {
background: var(--bg-active);
color: var(--text-primary);
}
.phrase-actions .action-btn:active {
transform: scale(0.92);
}
.phrase-actions .action-btn.danger:hover {
background: var(--danger-subtle);
color: var(--danger);
}
.phrase-actions .action-btn .icon {
width: 12px;
height: 12px;
}
.phrase-preview {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-word;
letter-spacing: -0.005em;
}
/* 搜索高亮:highlightText 同时用于标题和预览,两处样式必须一致——
只写 .phrase-preview 的话,标题里的 <mark> 会落到浏览器默认样式(黄底黑字),
深色主题下非常刺眼 */
.phrase-title mark,
.phrase-preview mark {
background: rgba(251, 191, 36, 0.18);
color: var(--warning);
padding: 0 2px;
border-radius: 2px;
}
.phrase-meta {
display: flex;
align-items: center;
gap: 8px;
margin-top: 6px;
font-size: 10.5px;
color: var(--text-tertiary);
}
/* 收藏/最近视图下来源分组名可能很长(分组名最长 60 字符),
不截断会撑破短语卡片的右边界 */
.phrase-meta .dim {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.phrase-meta .use-count {
display: inline-flex;
align-items: center;
gap: 3px;
font-variant-numeric: tabular-nums;
/* 空间不足时先截断分组名(.dim),「已用 N 次」始终保持完整 */
flex-shrink: 0;
}
/* ===== 空状态 ===== */
.empty-hint {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
/* safe center:空间够时居中;窗口最小高 360 时内容(约 230px)会超出剩余高度(约 177px),
普通 center 会从顶部裁掉插画、底部按钮也不可达——safe 让它退化为顶端对齐 + 可滚动 */
justify-content: safe center;
overflow-y: auto;
text-align: center;
padding: 24px 20px;
gap: 6px;
}
.empty-hint.hidden { display: none; }
.empty-illustration {
width: 56px;
height: 56px;
color: var(--text-tertiary);
margin-bottom: 6px;
opacity: 0.6;
animation: empty-breathe 3.6s var(--ease) infinite;
}
.empty-illustration svg {
width: 100%;
height: 100%;
}
@keyframes empty-breathe {
0%, 100% { opacity: 0.55; transform: scale(1) translateY(0); }
50% { opacity: 0.78; transform: scale(1.04) translateY(-2px); }
}
/* 进入空状态时让插画先轻快地“落一下”,再进入循环 —— 否则每次显示都会从中间态跳一下 */
.empty-illustration.run-in {
animation: empty-breathe-in 0.5s var(--ease) both,
empty-breathe 3.6s var(--ease) 0.5s infinite;
}
@keyframes empty-breathe-in {
from { opacity: 0; transform: scale(0.85) translateY(4px); }
to { opacity: 0.55; transform: scale(1) translateY(0); }
}
.empty-title {
font-size: 13px;
font-weight: 600;
color: var(--text-secondary);
letter-spacing: -0.01em;
}
.empty-desc {
font-size: 11.5px;
color: var(--text-tertiary);
line-height: 1.55;
max-width: 220px;
margin-bottom: 4px;
}
.empty-action {
margin-top: 6px;
padding: 5px 12px;
font-size: 12px;
font-weight: 500;
color: var(--accent);
background: var(--accent-subtle);
border: 1px solid transparent;
border-radius: var(--radius-md);
transition: background var(--duration-fast) var(--ease),
border-color var(--duration-fast) var(--ease);
}
.empty-action:hover {
background: var(--accent-hover-bg);
border-color: var(--accent-pulse);
}
.empty-action.hidden { display: none; }
/* ===== 添加栏 ===== */
.add-bar {
padding: 10px 12px;
border-top: 1px solid var(--border-subtle);
background: var(--bg-base);
flex-shrink: 0;
display: flex;
align-items: center;
gap: 8px;
}
.add-bar .primary-btn {
flex: 1;
height: 32px;
min-width: 0;
}
/* ===== 通用 .hidden ===== */
.hidden {
display: none !important;
}
/* ===== 滚动条 ===== */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.08);
border-radius: 999px;
border: 2px solid transparent;
background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.16);
background-clip: padding-box;
border: 2px solid transparent;
}
body[data-theme="light"] ::-webkit-scrollbar-thumb {
background: rgba(15, 17, 21, 0.10);
background-clip: padding-box;
border: 2px solid transparent;
}
body[data-theme="light"] ::-webkit-scrollbar-thumb:hover {
background: rgba(15, 17, 21, 0.20);
background-clip: padding-box;
border: 2px solid transparent;
}
/* ============================================================
弹窗
============================================================ */
.modal {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.55);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
animation: modal-fade var(--duration-base) var(--ease);
}
.modal.hidden { display: none; }
.modal-card {
width: 360px;
/* 窗口最小 280px 宽:不限制 max-width 时卡片两边被视口裁掉,输入框贴边 */
max-width: calc(100vw - 24px);
background: var(--bg-overlay);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
overflow: hidden;
/* 与 .panel-card 一致的高度约束:内容(如可纵向拉高的 textarea)超出窗口时
由 .modal-body 滚动兜底,否则弹窗会被顶出窗口、底部按钮不可达 */
max-height: calc(100% - 32px);
display: flex;
flex-direction: column;
animation: modal-pop var(--duration-base) var(--ease);
}
body[data-theme="light"] .modal {
background: rgba(15, 17, 21, 0.30);
}
.modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid var(--border-subtle);
font-size: 13.5px;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.01em;
/* .modal-card 是 flex 列容器:内容超高时头部不压缩,由 body 滚动 */
flex-shrink: 0;
}
.modal-title-wrap {
display: inline-flex;
align-items: center;
gap: 8px;
}
.modal-title-icon {
width: 16px;
height: 16px;
color: var(--accent);
}
.modal-body {
padding: 16px;
/* .modal-card 限高后的滚动区(textarea 拉高 / 长提示消息时兜底) */
overflow-y: auto;
}
.field-label {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-tertiary);
margin-bottom: 6px;
margin-top: 12px;
}
.field-label:first-child { margin-top: 0; }
.field-counter {
font-size: 10.5px;
font-weight: 500;
color: var(--text-tertiary);
text-transform: none;
letter-spacing: 0;
font-variant-numeric: tabular-nums;
}
.field-counter.warn {
color: var(--warning);
}
.field-counter.over {
color: var(--danger);
}
.field-row {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 12px;
padding: 8px 10px;
background: var(--bg-input);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
}
.field-label-inline {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
cursor: pointer;
user-select: none;
}
.field-label-inline input[type="checkbox"] {
appearance: none;
width: 14px;
height: 14px;
border: 1px solid var(--border-strong);
border-radius: 3px;
background: var(--bg-base);
cursor: pointer;
display: inline-grid;
place-content: center;
transition: background var(--duration-fast) var(--ease),
border-color var(--duration-fast) var(--ease);
margin: 0;
padding: 0;
}
.field-label-inline input[type="checkbox"]:checked {
background: var(--accent);
border-color: var(--accent);
}
.field-label-inline input[type="checkbox"]:checked::after {
content: '';
width: 8px;
height: 5px;
border: 1.5px solid #fff;
border-top: none;
border-right: none;
transform: rotate(-45deg) translate(1px, -1px);
}
.field-label-inline input[type="checkbox"]:focus-visible {
/* appearance:none 后默认焦点框也没了,用 accent 光晕补回键盘焦点反馈 */
box-shadow: 0 0 0 3px var(--accent-subtle);
}
.field-label-inline .icon {
width: 12px;
height: 12px;
color: var(--warning);
}
.field-tip {
font-size: 10.5px;
color: var(--text-tertiary);
}
.modal-body input,
.modal-body textarea {
width: 100%;
resize: vertical;
}
.modal-body textarea {
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', "Microsoft YaHei", monospace;
font-size: 12.5px;
line-height: 1.55;
min-height: 96px;
color: var(--text-primary);
}
/* 占位符展开预览:仅当内容含 {date|time|...} 时显示 */
.placeholder-preview {
margin-top: 10px;
padding: 8px 10px;
background: var(--accent-subtle);
border: 1px solid var(--border-default);
border-radius: var(--radius-md);
}
.placeholder-preview-label {
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-tertiary);
margin-bottom: 4px;
}
.placeholder-preview-text {
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', "Microsoft YaHei", monospace;
font-size: 11.5px;
line-height: 1.55;
color: var(--text-secondary);
white-space: pre-wrap;
word-break: break-word;
max-height: 96px;
overflow-y: auto;
}
.placeholder-preview-text mark {
background: var(--accent-mark-bg);
color: var(--accent-hover);
padding: 0 3px;
border-radius: 3px;
}
/* 占位符变量说明:折叠面板,节省编辑区空间 */
.placeholder-hint {
margin-top: 8px;
font-size: 11px;
color: var(--text-tertiary);
}
.placeholder-hint summary {
cursor: pointer;
user-select: none;
padding: 4px 0;
font-weight: 500;
color: var(--text-secondary);
list-style: none;
display: flex;
align-items: center;
gap: 4px;
}
.placeholder-hint summary::before {
content: '▸';
font-size: 10px;
color: var(--text-tertiary);
transition: transform var(--duration-fast) var(--ease);
}
.placeholder-hint[open] summary::before {
transform: rotate(90deg);
}
.placeholder-hint summary:hover {
color: var(--text-primary);
}
.placeholder-hint summary:focus-visible {
/* summary 可键盘聚焦(Enter/Space 展开),list-style:none 后也要补焦点框 */
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: var(--radius-sm);
}
.placeholder-hint .placeholder-list {
display: flex;
flex-direction: column;
gap: 4px;
padding: 6px 0 4px 14px;
font-size: 11px;
color: var(--text-tertiary);
}
.placeholder-hint code {
font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace;
font-size: 10.5px;
padding: 1px 5px;
margin-right: 4px;
background: var(--bg-hover);
color: var(--accent-hover);
border-radius: 3px;
font-weight: 500;
}
/* 通用提示弹窗 */
.prompt-card {
width: 340px;
}
.prompt-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 22px;
height: 22px;
border-radius: 6px;
background: var(--accent-subtle);
color: var(--accent);
}
.prompt-icon.danger {
background: var(--danger-subtle);
color: var(--danger);
}
.prompt-icon.success {
background: var(--success-subtle);
color: var(--success);
}
.prompt-icon .icon {
width: 13px;
height: 13px;
}
.prompt-message {
font-size: 13px;
line-height: 1.55;
color: var(--text-secondary);
margin-bottom: 14px;
word-break: break-word;
user-select: text;
/* 消息是用 textContent 写进去的,导入确认框等场景带 \n 换行。
默认 white-space 会把换行折叠成空格,两行提示挤成一行。 */
white-space: pre-line;
}
.prompt-input {
width: 100%;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 18px;
}
@keyframes modal-fade {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes modal-pop {
from {
opacity: 0;
transform: scale(0.96) translateY(6px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* ============================================================
Toast 栈(从右下角滑入)
============================================================ */
.toast-stack {
position: fixed;
bottom: 16px;
right: 16px;
display: flex;
flex-direction: column-reverse;
gap: 8px;
z-index: 200;
pointer-events: none;
/* 窗口最小 280px 宽:固定 320px 会让右对齐的 toast 超出左边缘 */
max-width: min(320px, calc(100vw - 32px));
}
.toast {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: var(--bg-overlay);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
font-size: 12.5px;
font-weight: 500;
color: var(--text-primary);
box-shadow: var(--shadow-md);
pointer-events: auto;
animation: toast-in var(--duration-base) var(--ease) both;
min-width: 200px;
position: relative;
overflow: hidden;
}
.toast.fade-out {
animation: toast-out var(--duration-base) var(--ease) both;
}
.toast::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: var(--text-tertiary);
}
.toast.success::before { background: var(--success); }
.toast.error::before { background: var(--danger); }
.toast.info::before { background: var(--accent); }
.toast .toast-icon {
flex-shrink: 0;
width: 16px;
height: 16px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
}
.toast.success .toast-icon { color: var(--success); }
.toast.error .toast-icon { color: var(--danger); }
.toast.info .toast-icon { color: var(--accent); }
.toast .toast-icon .icon {
width: 14px;
height: 14px;
}
.toast .toast-text {
flex: 1;
line-height: 1.4;
word-break: break-word;
}
.toast .toast-close {
flex-shrink: 0;
width: 16px;
height: 16px;
border-radius: 4px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--text-tertiary);
transition: background var(--duration-fast) var(--ease),
color var(--duration-fast) var(--ease);
}
.toast .toast-close:hover {
background: var(--bg-hover);
color: var(--text-primary);
}
.toast .toast-close .icon {
width: 9px;
height: 9px;
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateX(24px) scale(0.96);
}
to {
opacity: 1;
transform: translateX(0) scale(1);
}
}
@keyframes toast-out {
from {
opacity: 1;
transform: translateX(0) scale(1);
}
to {
opacity: 0;
transform: translateX(20px) scale(0.96);
}
}
/* ============================================================
上下文菜单
============================================================ */
.context-menu {
position: fixed;
background: var(--bg-overlay);
border: 1px solid var(--border-default);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
padding: 4px;
min-width: 180px;
z-index: 150;
animation: ctx-menu-in var(--duration-fast) var(--ease);
user-select: none;
}
.context-menu.hidden { display: none; }
.context-menu .ctx-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 10px;
border-radius: var(--radius-md);
font-size: 12.5px;
color: var(--text-primary);
cursor: pointer;
transition: background var(--duration-fast) var(--ease);
}
.context-menu .ctx-item:hover {
background: var(--bg-hover);
}
.context-menu .ctx-item.danger {
color: var(--danger);
}
.context-menu .ctx-item.danger:hover {
background: var(--danger-subtle);
}
.context-menu .ctx-item .icon {
width: 13px;
height: 13px;
color: var(--text-tertiary);
}
.context-menu .ctx-item.danger .icon {
color: var(--danger);
}
.context-menu .ctx-divider {
height: 1px;
background: var(--border-subtle);
margin: 4px 0;
}
@keyframes ctx-menu-in {
from {
opacity: 0;
transform: scale(0.96) translateY(-4px);
}
to {
opacity: 1;
transform: scale(1) translateY(0);
}
}
/* ============================================================
面板(设置/帮助)
============================================================ */
.panel {
position: fixed;
inset: 0;
z-index: 90;
display: flex;
align-items: center;
justify-content: center;
pointer-events: auto;
}
.panel.hidden { display: none; }
.panel-backdrop {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(6px);
-webkit-backdrop-filter: blur(6px);
animation: modal-fade var(--duration-base) var(--ease);
}
body[data-theme="light"] .panel-backdrop {
background: rgba(15, 17, 21, 0.25);
}
.panel-card {
position: relative;
background: var(--bg-overlay);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
max-height: calc(100% - 32px);
/* 设置卡片宽 440px,窗口最小 280px —— 不限制宽度会在小窗口下两边被裁 */
max-width: calc(100vw - 24px);
display: flex;
flex-direction: column;
animation: modal-pop var(--duration-base) var(--ease);
overflow: hidden;
}
.panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
border-bottom: 1px solid var(--border-subtle);
flex-shrink: 0;
}
.panel-title {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 13.5px;
font-weight: 600;
letter-spacing: -0.01em;
}
.panel-title .icon {
width: 15px;
height: 15px;
color: var(--accent);
}
.panel-body {
flex: 1;
overflow-y: auto;
padding: 4px 4px 16px;
}
/* ===== 设置面板 ===== */
.settings-card {
width: 440px;
}
.setting-group {
padding: 14px 16px;
}
.setting-group + .setting-group {
border-top: 1px solid var(--border-subtle);
}
.setting-group-title {
font-size: 10.5px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--text-tertiary);
margin-bottom: 8px;
}
.setting-row {
display: flex;
align-items: center;
gap: 8px 16px;
padding: 10px 8px;
margin: 0 -8px;
border-radius: var(--radius-md);
transition: background var(--duration-fast) var(--ease);
/* 窄窗口(设置卡片被 max-width 收窄到 ~256px)时右侧控件换到第二行,
而不是把左侧文字压成竖排单字列 */
flex-wrap: wrap;
}
/* 换行后控件仍然靠右,保持与宽屏一致的视觉锚点 */
.setting-row > :last-child {
margin-left: auto;
}
.setting-row:hover {
background: var(--bg-hover);
}
.setting-row + .setting-row {
border-top: 1px solid var(--border-subtle);
}
.setting-row-text {
flex: 1;
min-width: 0;
}
.setting-label {
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
margin-bottom: 2px;
}
.setting-desc {
font-size: 11.5px;
color: var(--text-tertiary);
line-height: 1.4;
}
.setting-desc.mono {
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 11px;
word-break: break-all;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.setting-actions {
display: flex;
gap: 4px;
}
/* ===== 快捷键录制行 ===== */
/* 视觉上与 .setting-actions 同宽,但 kbd 用衬底框表现「键帽」感,
两个按钮紧跟在后;窄窗口下允许内部换行(按钮掉到 kbd 下一行)。 */
.shortcut-display {
display: inline-flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
justify-content: flex-end;
}
/* 模仿 VSCode 等 IDE 里的键帽样式:
- 等宽字体 + 浅灰底 + 圆角,让「Ctrl+Shift+V」一眼可读
- 不设置固定 min-width:组合键长度不一(Ctrl+Shift+V vs F1),硬编码会留空 */
kbd {
display: inline-block;
padding: 3px 8px;
font-family: 'JetBrains Mono', 'SF Mono', Consolas, monospace;
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
background: var(--bg-input);
border: 1px solid var(--border-default);
border-radius: 4px;
line-height: 1.4;
white-space: nowrap;
user-select: none;
transition: background var(--duration-fast) var(--ease),
border-color var(--duration-fast) var(--ease);
}
/* 录制态:kbd 底色变 accent,提示「现在按的键会被接收」,
再叠加一个轻微脉冲,让用户意识到等待状态中。 */
.shortcut-display.recording kbd {
background: var(--accent-subtle);
border-color: var(--accent);
color: var(--accent);
animation: kbd-pulse 1.4s var(--ease) infinite;
}
@keyframes kbd-pulse {
0%, 100% { box-shadow: 0 0 0 0 var(--accent-subtle); }
50% { box-shadow: 0 0 0 4px var(--accent-subtle); }
}
/* 录制按钮在录制态下变成「取消」语义:accent 底 + 白字 */
.text-btn.recording {
background: var(--accent);
color: #fff;
}
.text-btn.recording:hover {
background: var(--accent-hover);
color: #fff;
}
/* 设置面板里的可点击链接(关于区开发者链接等):
取消默认下划线,用 accent 色 + hover 强化,避免和段落正文混在一起 */
.setting-link {
color: var(--accent);
text-decoration: none;
word-break: break-all;
transition: color var(--duration-fast) var(--ease);
}
.setting-link:hover {
color: var(--accent-hover);
text-decoration: underline;
text-underline-offset: 2px;
}
/* ===== Segmented Control ===== */
.segmented {
display: inline-flex;
background: var(--bg-input);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-md);
padding: 2px;
gap: 2px;
}
.seg-btn {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 4px;
font-size: 11.5px;
font-weight: 500;
color: var(--text-tertiary);
background: transparent;
transition: all var(--duration-fast) var(--ease);
}
.seg-btn:hover {
color: var(--text-secondary);
}
.seg-btn.active {
background: var(--bg-elevated);
color: var(--text-primary);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2), 0 0 0 1px var(--border-default);
}
.seg-btn .icon {
width: 11px;
height: 11px;
}
/* ===== Switch ===== */
.switch {
position: relative;
display: inline-block;
width: 34px;
height: 18px;
flex-shrink: 0;
}
.switch input {
opacity: 0;
position: absolute;
width: 0;
height: 0;
}
.switch-slider {
position: absolute;
inset: 0;
background: var(--bg-input);
border: 1px solid var(--border-default);
border-radius: 999px;
cursor: pointer;
transition: background var(--duration-fast) var(--ease),
border-color var(--duration-fast) var(--ease);
}
.switch-slider::after {
content: '';
position: absolute;
left: 2px;
top: 50%;
transform: translateY(-50%);
width: 12px;
height: 12px;
background: var(--text-secondary);
border-radius: 50%;
transition: left 220ms cubic-bezier(0.34, 1.45, 0.64, 1),
background var(--duration-fast) var(--ease),
width var(--duration-base) var(--ease),
box-shadow var(--duration-fast) var(--ease);
}
.switch input:checked + .switch-slider {
background: var(--accent);
border-color: var(--accent);
}
.switch input:checked + .switch-slider::after {
left: 18px;
background: #fff;
box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.15);
}
.switch input:focus-visible + .switch-slider {
/* input 本体是 opacity:0 的,键盘焦点反馈要画在可视的滑块上 */
box-shadow: 0 0 0 3px var(--accent-subtle);
border-color: var(--accent);
}
/* ===== Slider ===== */
.slider {
-webkit-appearance: none;
appearance: none;
width: 140px;
height: 4px;
background: var(--bg-input);
border-radius: 999px;
outline: none;
padding: 0;
border: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
background: #fff;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
transition: transform var(--duration-fast) var(--ease);
}
.slider::-webkit-slider-thumb:hover {
transform: scale(1.1);
}
.slider::-moz-range-thumb {
width: 14px;
height: 14px;
background: #fff;
border-radius: 50%;
cursor: pointer;
border: none;
}
/* ============================================================
Accessibility: respect user's motion preferences
============================================================ */
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}