/* =========================================================================== Toolbar + App Shell 自动从 src/styles.css 拆分(Stage 4a) =========================================================================== */ /* ============================================================ Toolbar (custom titlebar) ============================================================ */ .toolbar { display: flex; align-items: center; justify-content: space-between; height: var(--h-toolbar); /* 右侧用更小的内边距,避免在默认 1200px 窗口下按钮组被裁; 顶部标题区仍保留 --space-5 的呼吸感 */ padding: 0 4px 0 var(--space-5); background: var(--bg-toolbar); /* audit fix (P2):去掉 backdrop-filter —— toolbar 在 bg-toolbar 实色之上, 毛玻璃的 saturate(180%) blur(20px) 强制把整条 1200×40 px 区域放到独立 compositor layer,且每帧滚动主内容时 GPU 都要重算这条带覆盖区域的模糊。 视觉上完全无差异(实色背景上看不出毛玻璃),但每次滚动都是几十 KB 像素的 重复 GPU 工作。 */ border-bottom: 1px solid var(--border-subtle); /* 切主题时与 body 同步淡入淡出:03-base.css body 已声明 transition: background var(--transition-slow), color var(--transition-slow); 不在 .toolbar 显式声明会让 --bg-toolbar 跳色而 body 还在过渡, 形成 ~320ms 的色块错位(顶部硬切、主区淡变)。 border-color 同理:1px 底分隔线用 --border-subtle 不加 transition 会 出现"主色在淡、这条 1px 边线硬切"的视觉跳变。background 已经在 body 那条 慢节奏下淡变,border-color 用同样的 --transition-slow 节奏保持一致。 */ transition: background var(--transition-slow), border-color var(--transition-slow); z-index: var(--z-toolbar); position: relative; /* 长文件名截断(fix:长文件名导致 toolbar 偏移): - toolbar 是 body grid 子元素,min-width 默认是 auto = min-content。 .app-title-text 的 white-space:nowrap + 长文本让 min-content 极大, toolbar 默认会撑到比视口还宽,把 .toolbar-right(关闭等按钮)挤出视口。 - min-width:0 覆盖默认 min-content,让 toolbar 接受 body grid 给它的宽度 - overflow:hidden 兜底:万一有其他内容溢出,clip 在 toolbar 内部而不是溢出到 body (body 也是 overflow:hidden,但那是最后一道防线,视觉上能看到 toolbar 自身的偏移) */ min-width: 0; overflow: hidden; } .toolbar::after { content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; background: var(--toolbar-divider); } .drag-region { -webkit-app-region: drag; flex: 1; display: flex; align-items: center; gap: var(--space-3); min-width: 0; height: 100%; padding-left: 2px; } .no-drag { -webkit-app-region: no-drag; display: flex; align-items: center; gap: var(--space-1); } /* 工具栏分组:将同一类别的按钮聚拢(视觉相邻),组间用 divider 隔开 */ .toolbar-group { display: inline-flex; align-items: center; gap: 2px; } .toolbar-divider { display: inline-block; width: 1px; height: 18px; /* 用半透明白色渐变,比纯色 border 更精致;亮主题用对应浅色 */ background: linear-gradient(180deg, transparent 0%, var(--border-default) 25%, var(--border-default) 75%, transparent 100%); /* 从 6px 减到 5px:默认 1200px 窗口下 6 个分组 + 7 个 divider 累计 84px gap, 改 5px 后 70px,让工具栏横向更紧凑 */ margin: 0 5px; flex-shrink: 0; } .app-title { font-size: var(--fs-base); font-weight: 600; color: var(--fg-primary); letter-spacing: var(--tracking-wide); display: flex; align-items: center; gap: 10px; /* 选中态:避免拖拽时被高亮 */ user-select: none; /* 长文件名截断(fix:长文件名导致 toolbar 偏移): - min-width:0 是关键 —— flex 容器默认 min-content 是整段文本, 没有它 .app-title 会拒绝收缩,把 .toolbar-right(按钮组)挤出视口右侧 - overflow:hidden 让超出部分不可见,配合 .app-title-text 的 ellipsis 截断 */ min-width: 0; flex: 1 1 auto; overflow: hidden; } /* 长文件名截断(fix:长文件名导致 toolbar 偏移): - 真正承载文本的子 span —— 直接文本节点在 flex 容器里无法触发 ellipsis - min-width:0 + flex:1 1 auto 让它优先吃光可用空间,超出才走截断 - text-overflow:ellipsis + white-space:nowrap 让长文件名从右侧省略 */ .app-title-text { display: block; min-width: 0; flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .app-title::before { content: ""; width: 20px; height: 20px; border-radius: 6px; background: linear-gradient(135deg, var(--accent) 0%, var(--accent-hover) 100%); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.18); /* 固定图标尺寸,不参与收缩(flex-shrink:0) */ flex: 0 0 auto; } /* 应用图标(来自 icon.ico 的 PNG data URL): - 20×20 + 6px 圆角,与 .app-title::before 占位形状一致,替换它而不是叠加 - draggable="false" 防选中后拖出浏览器(默认 选中状态可拖) - 与 .app-title 共享同一行(display:flex 在 .toolbar-left) */ .app-icon { width: 20px; height: 20px; border-radius: 6px; flex: 0 0 auto; /* 与 ::before 占位的多层 box-shadow 对齐,亮 / 暗主题下都不漂浮 */ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18), 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent), inset 0 1px 0 rgba(255, 255, 255, 0.18); } /* 真图标加载成功 → 隐藏 .app-title::before 占位,避免双图标 */ body.has-app-icon .app-title::before { display: none; } /* Toolbar 按钮:图标按钮 + 悬浮效果 默认色用 secondary(暗色 ~8.5:1, 亮色 ~8:1), 旧值 secondary 在暗色仅 ~5.5:1,加上 13px 字号看着发灰,2026-08 统一提亮 */ .btn-icon { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: var(--radius-sm); color: var(--fg-secondary); /* 加 transition 阴影和位移,让 hover 反馈更细腻 */ transition: background var(--transition-fast), color var(--transition-fast), transform var(--transition-fast), box-shadow var(--transition-fast); } .btn-icon:hover { background: var(--bg-hover); color: var(--fg-primary); } .btn-icon:active { /* scale 从 0.94 减到 0.96:原值 0.94 在 34px 按钮上缩 2px, 视觉跳动明显;0.96 只缩 1px,反馈更克制 */ background: var(--bg-active); transform: scale(0.96); } .btn-icon.is-active { background: var(--accent-soft); color: var(--accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent); } .btn-icon.is-active:hover { background: var(--accent-soft-hover); } .window-controls { display: flex; align-items: center; /* 左侧 4px padding + 0 右侧:让最小化按钮与上一组 toolbar-divider 之间 留出和 chip 一致的呼吸(原本用 margin-left:4px),但保留右侧 flush 视觉(贴近视口右沿,沿用 Win/Linux 平台 UX)。原来用 margin-right:-2px 把关闭按钮"挤"到视口边;改 padding-right:0 让容器本身在 toolbar 4px 右 padding 之内、关闭按钮不再依赖负 margin 也能落在合理边缘。 */ padding-left: 4px; padding-right: 0; margin-left: 0; gap: 0; /* 用渐变分隔线替代纯色 —— 比 border 精致,符合 macOS 视觉 */ position: relative; } .window-controls::before { /* 分隔线相对容器定位在 -4px:与 padding-left 等距,让「上一组按钮 → divider → 窗口控制组」节奏与工具栏内其他 divider 间距一致。 */ content: ""; position: absolute; left: -4px; top: 50%; transform: translateY(-50%); height: 20px; width: 1px; background: linear-gradient(180deg, transparent 0%, var(--border-default) 25%, var(--border-default) 75%, transparent 100%); } .btn-window { display: inline-flex; align-items: center; justify-content: center; width: 34px; height: 34px; border-radius: var(--radius-sm); /* 同 .btn-icon:用 secondary 替代 tertiary,避免暗色下窗口控制按钮发灰 */ color: var(--fg-secondary); transition: background var(--transition-fast), color var(--transition-fast); } .btn-window:hover { background: var(--bg-hover); color: var(--fg-primary); } .btn-window.btn-close:hover { background: var(--danger); color: var(--btn-on-danger, #ffffff); } /* 工具栏按钮组(视图模式三连按钮) */ .btn-group { display: inline-flex; align-items: center; /* 按钮组之间的间距由 .toolbar-divider 提供,这里去掉额外 margin-left */ margin-left: 0; gap: 1px; /* padding: 2px + 按钮 30px = 34px,与外层独立 .btn-icon 同高,保证视觉对齐 */ padding: 2px; /* 容器底色比 hover 更轻,避免和单个按钮 hover 态重叠太深 */ background: color-mix(in srgb, var(--bg-hover) 70%, transparent); border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px var(--border-subtle); } .btn-group .btn-icon { width: 30px; height: 30px; border-radius: 5px; } .btn-group .btn-icon:hover:not(.is-active) { background: var(--bg-hover); } .btn-group .btn-icon.is-active { background: var(--bg-elevated); color: var(--accent); box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent); } /* 图标 + 文字按钮(视图模式专用): 让核心交互(预览/编辑/双栏)一眼可识别, 同时保留 segmented control 的紧凑感。 12px 字号 + secondary(暗色 ~8.5:1)才能保证文字不糊; 2026-08 把 secondary 从 ~5.5:1 提到 ~8.5:1 后视图模式按钮显著更清晰 */ .btn-icon-text { display: inline-flex; align-items: center; justify-content: center; gap: 5px; height: 30px; padding: 0 10px; border-radius: 5px; color: var(--fg-secondary); font-size: var(--fs-xs-readable); font-weight: 500; letter-spacing: var(--tracking-wide); transition: background var(--transition-fast), color var(--transition-fast), transform var(--transition-fast); white-space: nowrap; } .btn-icon-text:hover:not(.is-active) { background: var(--bg-hover); } .btn-icon-text:active { transform: scale(0.96); } .btn-icon-text .btn-label { line-height: 1; /* 与按钮对齐基线 */ } .btn-icon-text.is-active { background: var(--bg-elevated); /* 文字版(--accent-text):view mode "预览 / 编辑 / 双栏"、autosave "自动保存" 这种带字按钮 active 时若用纯 accent,"预览" "自动保存" 在工具栏里整片 accent 短词同时亮起来会让眼睛辛苦。降一档后 bg-elevated + inset 1px accent 边框仍清楚表达"按下",文字不再贴脸 */ color: var(--accent-text); font-weight: 600; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12), inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent); } /* (audit fix Round 9: .toolbar-status-dot / .btn-icon-text > .toolbar-status-dot 死 CSS —— 保存按钮改 is-dirty 状态后该指示点彻底弃用,见 feedback-save-button-always-visible.md。iOS pill switch 替代品见下) */ /* iOS pill switch —— 替代 .toolbar-status-dot 作为开关语义。 26×14px 嵌入 .btn-icon-text,与现有 24px 按钮中线对齐。 视觉态由 [aria-pressed="true|false"] 选择器驱动,与项目 「aria-pressed 表示开关态」的模式一致(参考 btn-theme)。 保留 .btn-icon-text 外壳让按钮仍享有 hover/active 视觉反馈。 */ .toolbar-toggle .toggle-switch { position: relative; display: inline-block; width: 26px; height: 14px; margin-left: 4px; flex: 0 0 auto; } .toolbar-toggle .toggle-switch-track { position: absolute; inset: 0; background: var(--fg-muted); opacity: 0.5; border-radius: 7px; transition: background var(--transition-fast), opacity var(--transition-fast); } .toolbar-toggle .toggle-switch-thumb { position: absolute; top: 1px; left: 1px; width: 12px; height: 12px; background: var(--bg-elevated); border-radius: 50%; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.18); transition: transform var(--transition-fast); } .toolbar-toggle[aria-pressed="true"] .toggle-switch-track { background: var(--accent); opacity: 1; } .toolbar-toggle[aria-pressed="true"] .toggle-switch-thumb { transform: translateX(12px); } /* hover 微微提亮,与其它 btn-icon-text hover 一致 */ .toolbar-toggle:hover .toggle-switch-track { opacity: 0.85; } .toolbar-toggle[aria-pressed="true"]:hover .toggle-switch-track { opacity: 1; } /* 关态时整体暗化,与工具栏 fg-tertiary 一致,强调「关闭」语义 */ .toolbar-toggle[aria-pressed="false"] { color: var(--fg-tertiary); } /* a11y:prefers-reduced-motion 下关闭 transform 过渡 */ @media (prefers-reduced-motion: reduce) { .toolbar-toggle .toggle-switch-track, .toolbar-toggle .toggle-switch-thumb { transition: none; } } .btn-icon.is-dirty, .btn-icon-text.is-dirty { /* 脏状态:使用 warning 色作为视觉提示,并轻微脉动 */ color: var(--warning); background: color-mix(in srgb, var(--warning) 12%, transparent); animation: dirty-pulse 1.8s ease-in-out infinite; } .btn-icon.is-dirty:hover, .btn-icon-text.is-dirty:hover { color: var(--warning); background: color-mix(in srgb, var(--warning) 18%, transparent); } /* 保存中:与 is-dirty 的 warning 区分,用 fg-secondary 中性色 + 微微透明度。 cursor: progress 给鼠标用户明确提示(参见 [[feedback-save-button-always-visible]] ——按钮始终可见,这里只是临时视觉变化,不切换 hidden)。 */ .btn-icon.is-saving, .btn-icon-text.is-saving { cursor: progress; opacity: 0.6; color: var(--fg-secondary); background: var(--bg-hover); /* 关掉 dirty-pulse 动画:避免 is-saving + is-dirty 叠加造成闪烁 */ animation: none; /* Round 13 fix (M22):正在保存时锁住点击 —— 否则双击 save 会触发两次 writeFile,第二次 mtime 比预期新 → FILE_CHANGED_EXTERNALLY 误判。 savingCount 防并发应该兜底,但鼠标双击事件触发极快,savingCount 在第二次 进入前未必 ++;UI 层锁一道最稳。 */ pointer-events: none; } /* a11y:prefers-reduced-motion 下保持稳定视觉,不透明度也保留 */ @media (prefers-reduced-motion: reduce) { .btn-icon.is-saving, .btn-icon-text.is-saving { opacity: 0.7; cursor: progress; } } /* 主题按钮图标:按当前主题显示"代表当前状态"的那一态—— 暗色主题下显示月亮,亮色主题下显示太阳(点击图标会切到对侧主题)。 动画:两个 SVG 同位置叠加,靠 opacity + 180° rotate 切换。 - opacity 让图形柔和渐入渐出(避免硬切) - rotate 让图形过渡时有"翻转"暗示:从太阳转到月亮是 180° 翻转的语义 - 用 position:absolute 把两者钉到 button 中心,flex 自然布局让其中一个盖在另一个之上 用 [data-theme] 选择器控制「哪一态」当前显示,状态切换由 renderer 通过 toggleTheme() 改 body.dataset.theme 完成,CSS 自动追平。 */ #btn-theme { position: relative; } #btn-theme .theme-icon { position: absolute; inset: 0; margin: auto; display: block; opacity: 0; transform: rotate(-90deg) scale(0.6); transition: opacity var(--transition-fast), transform var(--transition-fast); } body[data-theme="dark"] #btn-theme .theme-icon-moon, body[data-theme="light"] #btn-theme .theme-icon-sun { opacity: 1; transform: rotate(0) scale(1); } /* (audit fix Round 9: .toolbar-status-dot 死 CSS,详见 04-toolbar.css 顶部注释) */ /* dirty-pulse:脉动从 1 → 0.65 太深,warning 色按钮在 50% 帧会瞬间 看着像"warning 没了"。改成 1 → 0.78:仍能看出脉动,但始终保持 warning 视觉在场,"未保存"语义不中断。1.8s 节奏不变 */ @keyframes dirty-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.78; } } /* 工具栏图标按钮的 disabled 态(如非 md 文件下 preview/split 按钮) 复用 .context-menu-item 的禁用色(fg-tertiary + opacity),保持一致 */ .btn-icon:disabled, .btn-icon[disabled] { color: var(--fg-tertiary); cursor: not-allowed; opacity: 0.4; pointer-events: none; } /* 图标+文字按钮(视图模式 预览/编辑/双栏)的 disabled 态。 非 Markdown 文件打开时 preview/split 按钮会被禁用 —— 这里让灰态视觉一致 */ .btn-icon-text:disabled, .btn-icon-text[disabled] { color: var(--fg-tertiary); cursor: not-allowed; opacity: 0.4; pointer-events: none; } /* ============================================================ Toolbar Search(设计优化 2026-09:从侧栏 header 迁来) ------------------------------------------------------------ 把搜索文件框从侧栏上移到底部顶栏首位,让侧栏垂直空间专注于文件列表 (侧栏的新建按钮已挪到右键菜单,首屏只留排序条)。 - flex:0 1 240px:理想宽度 240px,可缩不可涨 —— 窗口变窄时它先缩, 不挤右侧按钮组;不抢占侧栏上方视觉重量 - min-width:160px:极限窄窗口下保留可用宽度(低于此值键盘用户看不清) - max-width:320px:超宽窗口里也不无止境扩张,避免视觉失衡 - 顶栏背景 + 输入框对比:bg-card 让搜索框"沉下去",与工具栏按钮区分层级 CSS 顺序说明:base 规则必须放在 media query 之前,否则等特异性下后者 会被覆盖。 ============================================================ */ .toolbar-search { position: relative; display: flex; align-items: center; /* belt-and-suspenders:toolbar-right 已设 no-drag,但显式再标一次, 防止未来重构时把 .toolbar-search 误放进 .drag-region */ -webkit-app-region: no-drag; flex: 0 1 240px; min-width: 160px; max-width: 320px; height: 30px; /* 与后面按钮组之间留点呼吸 —— 视觉上把搜索框归到"独立区",不和按钮混在一起 */ margin-right: 2px; } .toolbar-search .search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: var(--fg-secondary); pointer-events: none; transition: color var(--transition-fast); } .toolbar-search .search-input { width: 100%; height: 30px; /* 右侧多 4px 内边距:input[type="search"] 在 Webkit / Chromium 会渲染 内置的「×」清除按钮,避开它和占位文字重叠 */ padding: 0 30px 0 32px; background: var(--bg-card); border: 1px solid transparent; border-radius: var(--radius-sm); font-size: var(--fs-sm); color: var(--fg-primary); transition: border-color var(--transition-fast), background var(--transition-fast), box-shadow var(--transition-fast); /* 搜索框是输入区,需要能选中文本。工具栏其余区域 user-select:none(避免拖拽时 高亮一片),这里覆盖掉只对输入区解禁 —— 比整个 toolbar 开 selection 干净 */ -webkit-user-select: text; user-select: text; } .toolbar-search .search-input::placeholder { color: var(--fg-tertiary); } .toolbar-search .search-input:hover { border-color: var(--border-default); } .toolbar-search .search-input:focus { /* 鼠标点击不画外圈:键盘 focus-visible 由全局规则接管(见 :focus-visible 基线) */ outline: none; border-color: var(--accent); box-shadow: var(--shadow-glow); background: var(--bg-elevated); } @supports (focus-visible) { .toolbar-search .search-input:focus:not(:focus-visible) { box-shadow: none; } .toolbar-search .search-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } } /* 输入框 focus 时把左侧放大镜也跟着亮起来 —— 视觉一致性。 用 :focus-within 而不是兄弟选择器(.search-input:focus + .search-icon), 因为 DOM 里 icon 在 input 前面,兄弟选择器匹配不到 */ .toolbar-search:focus-within .search-icon { color: var(--accent); } /* 聚焦模式隐藏整个 toolbar 时,搜索框随之隐藏 —— 此时连侧栏都没有, 搜索框无处可用。这与 09-misc.css 的 `body[data-focus-mode="true"] .toolbar { ... }` 配套,无需单独写规则。 窄窗口(< 820px)下,工具栏按钮文字隐藏但搜索框保留(核心功能)。 */ /* 窄窗口(< 820px):toolbar 文字标签隐藏,只保留图标。 与 sidebar 720px 阈值错开,留出中间过渡区,避免一次性把全部 toolbar 砍塌。 CSS 顺序:media query 必须在 base .toolbar-search 之后,等特异性下后者覆盖前者 */ @media (max-width: 820px) { .btn-icon-text .btn-label { display: none; } .btn-icon-text { /* 没文字时收紧 padding,让按钮保持图标按钮的紧凑感 */ padding: 0 6px; } /* 820px 时所有文字按钮已退化为图标按钮,省下约 100px; 搜索框也按比例收紧到 160~200px,给 title 留更多空间 */ .toolbar-search { flex: 0 1 200px; min-width: 140px; } } /* 极窄窗口(< 720px):与侧栏窄屏阈值对齐。搜索框最小化到只保留图标宽度, 用户仍能用 Ctrl+F 聚焦输入(即使视觉上更紧凑,键盘可达性不丢)。 ::placeholder 无法设 content(浏览器不支持),自然截断即可 —— 输入框太窄时 占位文字会被输入框边缘裁掉,但输入体验(点 / Ctrl+F / Esc 清空)都还在 */ @media (max-width: 720px) { .toolbar-search { flex: 0 1 140px; min-width: 96px; } } /* 极窄窗口(≤ 720px,含 600px 最小窗口):工具栏右簇(搜索 + 外观 + 视图 + 重置布局 + 自动保存 + 保存/关闭/设置 + 窗口控件)在 600px 视口下会溢出约 90px, 把最右侧的窗口控件(最小化/最大化/关闭)挤出视口 —— frameless 窗口下这是唯一 的关闭入口,被裁掉等于「关不掉窗口」。这里只收起最低优先级的控件腾出空间, 保证窗口控件始终可达: - 隐藏「重置布局」按钮组 + 它后面的分隔线(低频操作,仍可经右键菜单/设置恢复) - 自动保存折叠为纯图标(隐藏文字标签 + iOS 开关,保留按钮本体与 aria-pressed 状态色;完整开关仍在设置里),约省 27px - 搜索框 min-width 进一步降到 72px(720px 断点晚于 720px 断点,这里后定义以覆盖, 同时兜住 701–719px 这段原始布局仍会轻微溢出的过渡带) 三者合计约省 90px,窗口控件在 600px 最小宽度下不再被裁。 */ @media (max-width: 720px) { #tg-reset-layout, #tg-reset-layout + .toolbar-divider { display: none; } #toolbar-autosave .btn-label, #toolbar-autosave .toggle-switch { display: none; } #toolbar-autosave { padding: 0 6px; } .toolbar-search { flex: 0 1 120px; min-width: 72px; } } /* ============================================================ App Shell ============================================================ */ .app-shell { display: grid; /* 侧栏宽度走 --sidebar-width(运行时可拖拽调整),默认值来自 --w-sidebar; 这样改 token 不必同时改 grid 模板,也避免与窄屏媒体查询耦合 */ grid-template-columns: var(--sidebar-width) 4px 1fr; min-height: 0; overflow: hidden; } /* 侧栏拖拽分割条:只在 split / preview / edit 单栏模式下显示。 视觉宽度 4px。命中区扩到 ±6px(共 12px):由 app.js 的 mountSidebarSplitter 监听 document mousedown + mousemove,在鼠标距 splitter 中线 ≤6px 时启动拖动, 并把光标改成 col-resize 提示可拖。这是 grid track 4px 限制下的标准做法。 */ .sidebar-splitter { background: var(--border-subtle); cursor: col-resize; position: relative; width: 4px; transition: background var(--transition-fast); } .sidebar-splitter:hover, .sidebar-splitter.is-dragging { background: var(--accent-soft); } .sidebar-splitter::after { /* 抓握提示:两条短竖线 */ content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 1px; height: 24px; background: var(--border-default); box-shadow: 3px 0 0 var(--border-default); } .sidebar-splitter:hover::after, .sidebar-splitter.is-dragging::after { background: var(--accent); box-shadow: 3px 0 0 var(--accent); } /* 侧栏恢复默认宽度按钮(hover splitter 时显形) - 4px splitter 视觉宽度里塞不下按钮,所以按钮是 absolute 定位居中 + 视觉溢出; 但 splitter 本身 hover 区域扩到 ±6px(HIT_RADIUS),按钮顺势接管这块区域 - 默认 opacity:0 + pointer-events:none → 完全不挡 drag 命中区; hover splitter / 按钮自身 / focus-visible 时显形 */ .sidebar-splitter-reset { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 18px; height: 22px; display: flex; align-items: center; justify-content: center; background: var(--bg-card); border: 1px solid var(--border-default); border-radius: 3px; color: var(--fg-secondary); cursor: pointer; padding: 0; opacity: 0; pointer-events: none; transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast); } .sidebar-splitter:hover .sidebar-splitter-reset, .sidebar-splitter-reset:focus-visible { opacity: 1; pointer-events: auto; } .sidebar-splitter-reset:hover { background: var(--accent-soft); color: var(--accent); border-color: var(--accent); } .sidebar-splitter-reset:active { background: var(--accent); color: var(--bg-card); }