Files
Notes/src/styles/05-sidebar.css
2026-10-16 16:34:15 +08:00

1149 lines
42 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.
/* ===========================================================================
Sidebar + Viewer + Editor Split Layout
自动从 src/styles.css 拆分(Stage 4a)
=========================================================================== */
/* ============================================================
Sidebar
============================================================ */
.sidebar {
background: var(--bg-sidebar);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
min-height: 0;
z-index: var(--z-sidebar);
/* 宽度由 .app-shell 的 grid 模板通过 --sidebar-width 决定;这里不再写死 px */
min-width: 0;
/* container query 锚点:让 .sidebar 内部元素能根据侧栏实际宽度自适应。
下方可用 @container sidebar (max-width: ...) 写响应式规则,
跟随 .sidebar 实际渲染宽度,跟主窗口大小解耦。
命名空间 "sidebar" 与配套 @container 一致,避免与未来其他容器冲突 */
container-type: inline-size;
container-name: sidebar;
}
/* (.sidebar-header 已删除 —— 2026-09 设计优化:搜索框上移到工具栏,
侧栏垂直空间让给文件列表 + 排序条。首行直接是 sort + 新建笔记合并行) */
/* (.sidebar-new-row 已删除 —— 2026-09 设计优化:把「新建笔记」按钮合并到
.sidebar-sort 同一行的右端(用 .sidebar-new-btn--inline + margin-left:auto),
节省侧栏首屏的垂直空间。原独立行的顶部呼吸转移到了 .sidebar-sort 的 padding-top) */
/* 主操作按钮:全宽、带文字(accent 文字 + 软底色 + 1px accent 边框的主操作语义)。
新建文件 / 新建文件夹挪到右键菜单后,这个 base 类现在只被侧栏底部 AI 按钮(#btn-ai)复用。
2026-08 把字号从 12px 提到 14px、字重 500 → 600、加 8% accent 底色 + 32% accent 边框,
解决按钮文字发淡、按钮太瘦的问题 */
.sidebar-new-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
width: 100%;
height: 34px;
padding: 0 12px;
border-radius: var(--radius-sm);
/* audit fix (Round 9):font-size 13px (fs-sm) 的文字走 --accent-text(混色 65/70%),
不能用装饰色 token --accent(饱和度太高,13px 文字看着刺眼)。
见 [[feedback-colored-text-tier]]。background / border 仍走 --accent 因为
它们是装饰而非文字。 */
color: var(--accent-text);
background: color-mix(in srgb, var(--accent) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--accent) 32%, transparent);
font-size: var(--fs-sm);
font-weight: 600;
letter-spacing: 0;
cursor: pointer;
transition: color var(--transition-fast), background var(--transition-fast), border-color var(--transition-fast), transform var(--transition-fast);
}
.sidebar-new-btn:hover {
/* audit fix (CSS P2 / sidebar new-btn hover):之前 color 走 --accent-hover
(纯 accent 饱和色),13px 小字直接用饱和色会"扎眼"、在 dark 调色板
还会出现 vibration(红绿高频抖动)现象。base 态已经走 --accent-text
(accent 65/70% 混色),hover 应当保持同套语义色 token 一致,只是
把混色比例上调一档或换更暖的 --accent-text 变体,让视觉权重随交互
上升但 chroma 仍温和(见 [[feedback-colored-text-tier]])。 */
color: var(--accent-text);
border-color: var(--accent);
background: var(--accent-soft-hover);
}
.sidebar-new-btn:active {
transform: scale(0.98);
}
.sidebar-new-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
/* (.sidebar-new-btn--inline 已删除 —— 2026-09 设计优化:新建文件 / 新建文件夹
从侧栏「+」按钮挪到右键菜单(侧栏空白右键、文件夹条目右键),内联变体
.sidebar-new-btn--inline 及其窄侧栏 container query 规则随之移除。base 类
.sidebar-new-btn 仍被侧栏底部 AI 按钮(#btn-ai)复用) */
/* (search-wrap / search-icon / search-input 已移到 04-toolbar.css —— 2026-09
设计优化:搜索框从侧栏上移到工具栏,样式跟随 HTML 一起走) */
/* ============================================================
Sidebar 排序工具条
紧贴它作用的 .file-list 上方 —— 这是「列表的工具」,
不应该埋进三层深的设置对话框(见 [[feedback-prefer-near-affected-area]])。
设计目标:紧凑到不抢搜索框注意力,active 态明确但不过重。
新建文件 / 新建文件夹已挪到右键菜单,侧栏首行只剩排序工具条。
============================================================ */
.sidebar-sort {
display: flex;
align-items: center;
/* gap:sort 触发器与未来同组控件之间的间距("列表的工具"成组) */
gap: 4px;
/* 顶 8px / 底 8px:sort 是侧栏首屏第一行,工具栏底边已经有 ~8px breathing,
这里再给 8px 顶距足够拉开层次(10px → 8px 省 2px 给文件列表);
上下对称让 sort 行居中感更强,border-bottom 自然分隔 */
padding: 8px 10px;
border-bottom: 1px solid var(--border-subtle);
/* 切主题时 border-subtle 颜色变化需要 fade —— 否则 1px 分隔线在 body
320ms 慢淡变过程中硬切。背景是 transparent,没 background 跳变可淡 */
transition: border-color var(--transition-slow);
}
/* Sort 下拉容器:
2026-10 设计改造 —— 把原来并排两个 sort 按钮(.sidebar-sort-btn)换成下拉:
- 一个触发器占 sort 行左段,按内容宽度(flex:0 0 auto),不抢其它控件空间
- 菜单 absolute 定位在触发器下方,absolute 让它能浮在文件列表之上不受
flex 行高度限制,也不挤压下方列表布局
drop-shadow 比纯 border 更柔和:菜单浮起感强,与触发器同源色调一致
*/
.sidebar-sort-dropdown {
position: relative;
flex: 1 1 0;
min-width: 0;
}
.sidebar-sort-trigger {
display: inline-flex;
align-items: center;
gap: 5px;
width: 100%;
min-width: 0;
padding: 5px 6px;
background: transparent;
border: 1px solid transparent;
border-radius: var(--radius-sm);
color: var(--fg-secondary);
font-size: var(--fs-sm);
font-weight: 600;
letter-spacing: var(--tracking-wide);
cursor: pointer;
/* 防止长 label「按修改时间→旧」在窄侧栏里换行塌成两行 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition:
background var(--transition-fast),
color var(--transition-fast),
border-color var(--transition-fast);
}
/* 触发器打开态:accent-soft 底色 + accent 边框让「选中的排序」在行内就有
视觉反馈,不必打开菜单也能看出当前排序 */
.sidebar-sort-trigger[aria-expanded="true"],
.sidebar-sort-trigger.is-active {
color: var(--accent-text);
background: var(--accent-soft);
border-color: color-mix(in srgb, var(--accent) 20%, transparent);
}
.sidebar-sort-trigger:hover {
color: var(--fg-secondary);
background: var(--bg-hover);
}
.sidebar-sort-trigger[aria-expanded="true"]:hover,
.sidebar-sort-trigger.is-active:hover {
/* accent-soft + bg-hover 18% 混合:暗色调色板上 hover 不冲掉选中底色 */
background: color-mix(in srgb, var(--accent) 18%, var(--bg-hover));
}
.sidebar-sort-trigger:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
/* chevron 在打开时旋转 180°,暗示菜单已展开。
旋转而非切图标 —— 单一 SVG,CSS transform 更顺。
hover 提亮到 1.0:与 .status-chip-mode:hover svg 同源(默认 0.7 → 满透明),
鼠标停上时给 chevron 一抹"亮起来"的反馈,配合 trigger 底色变化更协调 */
.sidebar-sort-trigger-chevron {
flex: 0 0 auto;
margin-left: auto;
opacity: 0.7;
transition:
transform var(--transition-fast),
opacity var(--transition-fast);
}
.sidebar-sort-trigger[aria-expanded="true"] .sidebar-sort-trigger-chevron {
transform: rotate(180deg);
}
.sidebar-sort-trigger:hover .sidebar-sort-trigger-chevron {
opacity: 1;
}
/* trigger label 同样吃掉多余宽度,触发器 overflow:hidden 时它先被截。
flex:1 1 auto + min-width:0 是 flex 子项允许收缩到 content 以下的标配 —— 不加 min-width:0,
label 默认 min-content 宽度(最长不可断行片段),触发器 overflow:hidden 时它不会缩,
而是溢出 trigger 边缘把 chevron 顶出去。ellipsis 也得在 label 自己被约束时才生效。 */
.sidebar-sort-trigger-label {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
/* Sort 菜单:
- absolute 在触发器下方 left:0 让左对齐(与触发器同宽),top:calc(100% + 4px) 留 4px gap
- min-width:100% 而不是 width:100%:菜单可超出触发器宽度,长 label 「按修改时间」不挤
- padding:4px 让菜单项四周呼吸,menu 自身 padding 不参与 grid 列表
bg-elevated + drop-shadow + radius-sm:浮起卡片感,区别于触发器内联工具条 */
.sidebar-sort-menu {
position: absolute;
left: 0;
top: calc(100% + 4px);
/* min-width 让菜单始终覆盖触发器宽度,长 label(如「按修改时间」)不挤压;
长 label 单独成项时菜单按最长一项自然变宽 */
min-width: 100%;
max-width: 240px;
margin: 0;
padding: 4px;
list-style: none;
background: var(--bg-elevated);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
/* var(--shadow-md) 与 .focus-exit-hint 同源(4px 12px + 2px 4px 投影组合),
浮层用它来给菜单"轻浮起"层次。原来的硬编码 0.18/0.08 透明度是 dark-only 的
调参值,亮主题下显得偏重;用 token 后亮 / 暗两套主题的阴影会自动套用
02-palettes.css 已校准好的 0.07-0.32 区间,色彩系统内自洽 */
box-shadow: var(--shadow-md);
z-index: 50;
/* 淡入比瞬切更柔和;50ms 极短避免拖慢菜单 */
animation: sidebar-sort-menu-in 80ms ease-out;
}
@keyframes sidebar-sort-menu-in {
from { opacity: 0; transform: translateY(-2px); }
to { opacity: 1; transform: translateY(0); }
}
.sidebar-sort-menu-item {
display: flex;
align-items: center;
gap: 8px;
padding: 5px 8px;
border-radius: var(--radius-sm);
color: var(--fg-secondary);
font-size: var(--fs-sm);
font-weight: 500;
cursor: pointer;
user-select: none;
transition: background var(--transition-fast), color var(--transition-fast);
}
.sidebar-sort-menu-item:hover,
.sidebar-sort-menu-item:focus-visible {
background: var(--bg-hover);
color: var(--fg-primary);
}
/* 键盘 focus 需要可见环 —— menuitem 默认 outline:none(tabindex="-1" 的 li
浏览器不再画默认环),用 :focus-visible 而非 :focus:点击菜单项瞬间
focus 会落到它身上,若用 :focus 也会触发"高亮",给视觉多余干扰。
负 offset 让环贴 item 内沿,与 trigger focus-visible 的 +1px 外环形成
"内 vs 外"层次(trigger 是浮起控件,环外贴;menu item 已有 padding 4px
的 ul 容器,环内贴不会跟外沿视觉挤在一起) */
.sidebar-sort-menu-item:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -2px;
}
.sidebar-sort-menu-item.is-active {
/* 与触发器一致:accent-text + accent-soft;选中态双处呼应(触发器 + 菜单项) */
color: var(--accent-text);
background: var(--accent-soft);
}
.sidebar-sort-menu-item.is-active:hover {
background: color-mix(in srgb, var(--accent) 18%, var(--bg-hover));
}
/* 选中态左侧打勾;占位用 inline-block 让未选中项也对齐预留 13px + 8px gap。
比 flex 子项条件 display 更稳 —— 不参与排序语义,避免影响 item 高度 */
.sidebar-sort-menu-check {
display: inline-flex;
width: 13px;
height: 13px;
flex: 0 0 auto;
align-items: center;
justify-content: center;
color: var(--accent);
opacity: 0;
}
.sidebar-sort-menu-item.is-active .sidebar-sort-menu-check {
opacity: 1;
}
.sidebar-sort-menu-text {
flex: 1 1 auto;
display: inline-flex;
align-items: baseline;
gap: 6px;
min-width: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-sort-menu-hint {
/* hint 用 fg-muted + 小字号,与主 label 拉开对比;不抢主 label 视觉重量 */
color: var(--fg-muted);
font-size: var(--fs-xs);
font-weight: 400;
flex: 0 0 auto;
}
/* 菜单分组分隔:1px 水平细线 + 上下各 4px 留白。
margin 而不是 padding 让 flex 容器布局不挤压 —— padding 会撑大 li 高度,
上下 4px margin 让「组间距」发生在 li 边缘,与菜单项 5px padding 对称。 */
.sidebar-sort-menu-separator {
list-style: none;
height: 1px;
margin: 4px 6px;
padding: 0;
background: var(--border-subtle);
pointer-events: none;
}
.file-list {
flex: 1;
overflow-y: auto;
/* 上下 8px 比之前的 12px 更紧凑;列表项自身有 padding 已经够了 */
padding: 8px 10px;
/* 列表项间距:用 gap 而非 item 的 margin-bottom,避免 list 总高累加偏差。
1px gap 与原 margin-bottom:1px 视觉一致,但语义更清晰 */
display: flex;
flex-direction: column;
gap: 1px;
/* 滚动条预留空间 —— 让滚动条 hover 才出现,不抢视觉 */
scrollbar-gutter: stable;
/* audit fix (R7a P1-15):windowed render 需要把 .file-item 用
position:absolute + top:N*h 钉在指定位置,所以容器得 position:relative
作为锚点。flex + position:relative 不冲突(容器本身仍是 flex 布局,
决定 sentinel / 非 windowed 模式项的位置)。 */
position: relative;
}
/* audit fix (R7a P1-15):windowed 虚拟滚动 sentinel。
- 真实高度 = N * itemHeight,给滚动条撑出总高
- 默认 flex 项目,不抢视觉(aria-hidden + 0 padding)
- JS 切到 windowed 模式时 appendChild 它;切回 full / 空 状态时 remove
用 <li> 而非 <div> 是因为 <ul> 只能装 <li> 子元素,HTML 规范硬约束 */
.file-list-sentinel {
flex-shrink: 0;
padding: 0;
margin: 0;
list-style: none;
/* 高度由 JS 直接 style.height 设置;这里只保证不引入意外 padding */
pointer-events: none;
}
.file-list:empty::after {
/* 空列表:淡灰色提示。设置里改数据目录、或把 .md 拖到目录里都会自动消失。
原来用 --fg-muted 在 bg-sidebar 上仅 ~3.2:1(default dark),低于 WCAG AA。
提到 --fg-tertiary(~5.4:1)让这段引导文字真能"看清"——
无列表可看时用户全部注意力都落在这段字上,发淡等于无效提示 */
content: attr(data-empty);
display: block;
padding: var(--space-7) var(--space-3);
text-align: center;
color: var(--fg-tertiary);
font-size: var(--fs-sm);
line-height: var(--lh-relaxed);
/* 留点上方间距,与上方 sort 行拉开 */
margin-top: var(--space-2);
/* 避免一行字过长在窄侧栏里截断为半个字 */
word-break: break-word;
}
.file-item {
/* grid 两列:16px 图标 + 1fr 文本区。
用 grid 而不是 padding-left+absolute 是为了:
1) 文本区能正常占满剩余宽度(min-width:0 走 grid 才生效)
2) 图标列固定,文本列弹性,缩窄侧栏不挤图标 */
display: grid;
grid-template-columns: 16px 1fr;
align-items: center;
column-gap: 10px;
/* audit fix (R7a P1-15):windowed 虚拟滚动需要固定高度做索引 × h 的
位置数学。padding 仍由 box-sizing:border-box 包含在 height 内。
49px (12 padding + 37 content) → 显式 50px,差 1px 由 align-items:center
吸收,视觉无变化但 JS 可以用 N * 50 直接算 top/总高。 */
height: 50px;
/* 上下 padding 9px → 6px:原 9px 在 13px name + 13px meta 两行下让单条 file-item
撑到 ~60px,50 个文件就要 3000px 滚动,太松。压到 6px 后 ~50px/item,
列表更紧凑、可见项更多,仍保留点击区域与 hover 反馈呼吸 */
padding: 6px 12px 6px 10px;
/* margin-bottom 去掉,改用 file-list 的 gap 统一间距;
同时消除"两行间距靠 margin 累加"导致的列表总高计算偏差 */
border-radius: var(--radius-sm);
cursor: pointer;
/* transition 增加 border-color / box-shadow,让激活态色条 + 微弱内阴影的进入更顺滑 */
transition:
background var(--transition-fast),
color var(--transition-fast),
transform var(--transition-fast);
position: relative;
}
.file-text {
/* 文本列:name + meta 仍然竖排;min-width:0 让 ellipsis 真正生效 */
display: flex;
flex-direction: column;
/* name↔meta gap 2px → 1px:与 file-item 上下 padding 同步收紧,让整条紧凑 */
gap: 1px;
min-width: 0;
}
.file-icon {
/* 平时 muted,hover/active 时跟随主题升级。
用 grid 让图标"漂"在 item 左侧 + 上下居中,比 padding-left+absolute 更稳 */
color: var(--fg-tertiary);
flex-shrink: 0;
transition: color var(--transition-fast);
}
.file-item:hover .file-icon {
color: var(--fg-secondary);
}
.file-item:active {
transform: scale(0.985);
}
.file-item.is-active {
/* 激活态:accent-soft 底色 + 左侧 3px 色条。
用 accent-soft 替代 accent-soft-hover:hover 态是临时高亮(更深一档),
active 态是稳定选中,两者用不同 token 区分意图 */
background: var(--accent-soft);
}
.file-item.is-active:hover {
/* 激活态 hover 走更深一档 —— 与 idle 态 hover(bg-hover)形成层次差 */
background: var(--accent-soft-hover);
}
.file-item.is-active .file-icon {
/* 激活态:图标与正文同色,构成一致的视觉语言 */
color: var(--accent);
}
.file-item.is-active::before {
content: "";
position: absolute;
/* 从 -3px 移到 0,让色条贴着 item 内沿,更紧凑不"漂" */
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
/* 60% → 70%:原 60% 在 13px name + 12px meta 共 31px 行高下占 18px,
加上色条本身的圆角显得"瘦长"。提到 70% (22px) 后,与 file-name +
meta 二行总高的占比更平衡,色条"贴"住视觉单元,不漂在 item 中央。
仍留 30% 上下气口,避免色条顶到 file-name / meta 行边沿 */
height: 70%;
border-radius: 0 2px 2px 0;
background: var(--accent);
/* 加微 box-shadow 让色条在 accent-soft 底色上"浮"起来一点 —
纯色 accent 在 #2a325c 类 accent-soft 背景下,RGB 距离很短,
加 4px 投影能把色条对比拉高一档 */
box-shadow: 0 0 4px color-mix(in srgb, var(--accent) 40%, transparent);
/* 选中态色条从左侧滑入:切换当前文件时给一点"落位"的过渡,
比硬切更有手感,又不拖沓(200ms)。reduced-motion 下被全局 .01ms 规则收掉 */
transform-origin: left center;
animation: file-active-bar 200ms var(--ease-out);
}
@keyframes file-active-bar {
from { transform: translateY(-50%) scaleX(0); opacity: 0; }
to { transform: translateY(-50%) scaleX(1); opacity: 1; }
}
.file-name {
font-size: var(--fs-sm);
color: var(--fg-primary);
/* 字重 500 → 600:让 file-name 成为主视觉。
之前 file-meta 也是 600 + 同字号,导致 meta 看起来比 name 还"实",
层级倒挂(次要信息喧宾夺主)。现在 name 600 / meta 500,名字优先 */
font-weight: 600;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
letter-spacing: var(--tracking-tight);
transition: color var(--transition-fast);
}
.file-item .file-name mark {
/* 搜索高亮:在文件名里命中搜索关键字时的视觉标识。
22% accent 背景已经给出"高亮"信号,文字再纯 accent 会与背景同色相"贴
一起"。改用 --accent-text —— 仍可识别色相(青蓝/紫/红等色调随 palette),
但 chroma 降到不会与底色"撞色"的程度。
字重保持 700,因为 mark 通常只有 1-3 字,需要靠粗细扛"高亮"识别。 */
background: color-mix(in srgb, var(--accent) 22%, transparent);
color: var(--accent-text);
padding: 0 2px;
border-radius: 3px;
font-weight: 700;
}
.file-item.is-active .file-name {
/* accent #5e6ad2 在 #08090a 上只有 ~4.0:1,作为正文偏暗。
用与正文链接同色相的 --md-link(已在 #08090a 上 ~5.5:1)替代 */
color: var(--md-link);
/* 已与 idle file-name 同字重(600),保持选中态只是颜色升级、不再额外加粗 */
font-weight: 600;
}
.file-meta {
/* 字号 13px → 12px(--fs-xs-readable),比 file-name 小一档;
字重 600 → 500 让 meta 视觉退到次级,与 file-name 的 600 形成对比。
两层降级(更小 + 更轻)让 meta 真正成为"辅助信息"而不是与 name 平级 */
font-size: var(--fs-xs-readable);
color: var(--fg-secondary);
font-weight: 500;
font-variant-numeric: tabular-nums;
letter-spacing: var(--tracking-wide);
/* 窄侧栏(拖到 180px)时 meta 行可能换行成两行,让 file-item 高度跳变。
强制单行 + 省略号,保持列表项高度稳定 */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.file-item.is-active .file-meta {
color: var(--md-link);
opacity: 0.75;
}
.sidebar-footer {
/* 现在侧栏底部只剩 AI 一个按钮(文件夹管理已搬到状态栏左段,
见 [[feedback-folder-actions-to-statusbar]]),padding 收紧到 10px 12px
让按钮离文件列表稍近、视觉关系更紧 */
padding: 10px 12px;
border-top: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
gap: 2px;
/* 与 .sidebar-sort 同源:footer 顶 1px 分隔线在切主题时也需 fade,
避免与 main 320ms 慢淡变错位 */
transition: border-color var(--transition-slow);
}
/* ============================================================
Viewer
============================================================ */
.viewer {
background: var(--bg-viewer);
overflow-y: auto;
min-width: 0;
display: flex;
flex-direction: column;
scroll-behavior: smooth;
}
.markdown-body {
flex: 1;
max-width: var(--w-reader-max);
width: 100%;
margin: 0 auto;
padding: var(--space-9) var(--space-7) calc(var(--space-9) * 1.5);
font-family: var(--font-reading);
font-size: var(--reader-font-size);
line-height: var(--reader-line-height);
color: var(--fg-primary);
user-select: text;
letter-spacing: 0.005em;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* ============================================================
阅读进度条:viewer 顶部的 3px 细线,随滚动从左向右填充。
- sticky top:0:作为 .viewer 滚动容器的首个子元素,滚动时钉在顶部,
正文从它下方滑过(markdown-body 顶部有 48px padding,细线落在留白里不压字)。
- pointer-events:none:纯视觉反馈,不挡滚动 / 点击。
- 底色透明,只有填充段可见;90ms linear 跟随滚动手感顺滑。
- 宽度由 JS(updateStatusScroll)按 scrollTop / (scrollHeight-client) 写入。
============================================================ */
.reading-progress {
position: sticky;
top: 0;
z-index: 2;
flex: 0 0 auto;
width: 100%;
height: 3px;
background: transparent;
pointer-events: none;
}
.reading-progress-bar {
display: block;
height: 100%;
width: 0%;
/* 渐变比纯 accent 多一档层次,末端圆角呼应 file-item 色条 */
background: linear-gradient(90deg, var(--accent) 0%, var(--accent-hover) 100%);
border-radius: 0 2px 2px 0;
/* 极轻辉光,让细线在深色底上"浮"起来一点,但不喧宾夺主 */
box-shadow: 0 0 6px color-mix(in srgb, var(--accent) 50%, transparent);
transform-origin: left center;
transition: width 90ms linear;
}
/* 空状态:更有温度的视觉 */
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
/* gap 用 0,靠各元素的 margin 控制间距:
- icon → 标题:靠 icon 自身的 margin-bottom
- 标题 → 主 hint:12px,让主信息与提示分隔
- 主 hint → 次 hint:6px,形成层次
用统一 gap 会让主 hint 与次 hint 之间也有 12px,反而拉得太平 */
gap: 0;
color: var(--fg-tertiary);
text-align: center;
padding: var(--space-7);
/* 顶部加一点点品牌色辉光,强化「这是 Notes 应用」的氛围 */
position: relative;
}
.empty-state-icon {
width: 88px;
height: 88px;
border-radius: 50%;
/* 三层渐变叠出「呼吸光晕」:从中心发散到外圈的柔光 */
background:
radial-gradient(circle at 50% 45%,
color-mix(in srgb, var(--accent) 22%, transparent) 0%,
color-mix(in srgb, var(--accent) 8%, transparent) 35%,
transparent 70%);
display: flex;
align-items: center;
justify-content: center;
color: var(--accent);
margin-bottom: var(--space-3);
position: relative;
/* 让 halo 缓慢呼吸,强化「等待/邀请」感。
1.04 → 1.02:旧值在 4s 周期下肉眼明显能看出"放大再缩回",
用户长时间停留空状态(常见:刚启动、刚切数据文件夹)会被这个 4% 缩放
持续吸引注意力;改 2% 后仍保留"呼吸"暗示,但不再"主动拉视线" */
animation: empty-halo 4s ease-in-out infinite;
}
.empty-state-icon::after {
/* 内圈实色圆 + 微阴影:让图标有「浮起」感 */
content: "";
position: absolute;
inset: 16px;
border-radius: 50%;
background: color-mix(in srgb, var(--accent) 12%, transparent);
box-shadow:
inset 0 0 0 1px color-mix(in srgb, var(--accent) 22%, transparent),
0 4px 12px color-mix(in srgb, var(--accent) 8%, transparent);
z-index: -1;
}
.empty-state-icon svg {
/* 图标本身略浮起,配合 halo 制造层次 */
filter: drop-shadow(0 2px 4px color-mix(in srgb, var(--accent) 18%, transparent));
}
@keyframes empty-halo {
/* 振幅 1 → 1.02(+2%):保留"呼吸"质感的同时不抢占视线。
透明度仍 0.95 ↔ 1,让 halo 在淡入淡出之间柔和过渡 */
0%, 100% {
transform: scale(1);
opacity: 0.95;
}
50% {
transform: scale(1.02);
opacity: 1;
}
}
.empty-state p {
margin: 0;
font-size: var(--fs-lg);
color: var(--fg-primary);
font-weight: 700;
/* 标题与下面 hint 之间的留白由 gap 控制,避免再加 margin */
letter-spacing: var(--tracking-tight);
/* 标题用次级 fg-primary 突出 —— primary 比 secondary 更有视觉重量 */
}
/* 标题("选择一个 Markdown 文件开始阅读")需要与图标 / 提示都拉出层次 */
.empty-state > p:not(.hint) {
margin-bottom: var(--space-3);
}
.empty-state .hint {
font-size: var(--fs-sm);
/* 13px 提示文本:用 secondary(2026-08 提亮到 ~8.5:1),避开 tertiary 旧值的 3.5:1
—— 注释说要用 secondary 但实际代码仍是 tertiary,已修复 */
color: var(--fg-secondary);
/* max-width 从 360 提到 400:让"放进数据文件夹后," + ".md" chip 一行内放得下,
避免折成两行导致后段「自动出现」只剩一两个汉字成行(orphan 字)。
text-wrap: pretty 是 Chromium 114+ 的 CSS Text Level 4 优化,
优先避免单字成行(对 CJK 也生效:算法看 line-end penalty) */
max-width: 400px;
line-height: var(--lh-relaxed);
/* 主 hint 与标题之间已经由标题 margin-bottom 12px 撑开,
主 hint 自身的 margin-top 设为 0 避免与上面 margin 叠加 */
margin-top: 0;
text-wrap: pretty;
}
/* 二级提示:用 secondary + 略小字号,与主 hint 拉开视觉权重
P2-26:tertiary → secondary。dark palette 下
--fg-tertiary #8e95a1 vs --fg-secondary #b0b6c0
后者在 #08090a 底色上对比度 ~7.5:1(前者 ~5.0:1,边缘档)。
改 secondary 满足 WCAG AAA (7:1+),且不破坏主 hint(用 tertiary 仍
区分"主 → 次"权重)。其他 4 个 palette 同理。 */
.empty-state .hint.hint-secondary {
color: var(--fg-secondary);
font-size: var(--fs-xs-readable);
/* margin-top 6 → 10px:原 6px 让主 hint 与次 hint 之间几乎贴住,
加上两行 lh-relaxed (1.7) 后视觉上更挤。提到 10px 让两组 hint
之间形成明确的"主 → 次"层次(主是引导、次是补充操作) */
margin-top: 10px;
max-width: 380px;
/* 1.55 让次级提示的换行节奏与主 hint(1.6)接近,避免出现"次 hint 文字挤一行"的突兀感 */
line-height: 1.55;
}
/* 空状态 CTA ——「+ 新建笔记」按钮(audit P2-25)
与 .sidebar-new-btn 共享 accent 配色语义但不走 width:100%:
空状态是居中布局,紧凑 auto-width 让视觉重心聚焦在按钮上,
不会撑满整行让空白左右显得"摊"。
设计选择:
- 颜色用 --accent + --btn-on-accent(与 .btn-primary 一致)—— 走主按钮语义,
让"下一步该做什么"的指令最强
- padding 9px 16px:比 .btn (12px 20px) 收紧一点,与 .sidebar-new-btn 视觉重量一致
- hover 不上 transform scale —— 空状态按钮是用户在"看到指引后第一次行动",
缩放会让人犹豫到底点没点中;改用 box-shadow 上抬作"立起来"反馈
- focus-visible 沿用全局 .btn 的 2px accent outline(见 08-modal.css),
这里只声明 offset 让焦点环在按钮内贴边,与全局外 2px 视觉一致 */
.empty-state-cta {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
/* 9px 16px → 10px 18px:与下方 hint secondary 拉开一档垂直间距的同时,
比 .sidebar-new-btn (34px 高) 略紧凑 —— 空状态是「轻量引导」,
不需要 34px 那么"正式"的按钮高度 */
padding: 10px 18px;
margin-top: var(--space-4);
background: var(--accent);
color: var(--btn-on-accent, #ffffff);
border: 1px solid transparent;
border-radius: var(--radius-sm);
font-size: var(--fs-sm);
font-weight: 600;
letter-spacing: var(--tracking-tight);
cursor: pointer;
/* hover 时阴影抬升比 scale 更克制;同时阴影从 accent-soft 升级到
accent-soft-hover,给"按钮立起来"的微动效暗示 */
box-shadow: 0 2px 6px var(--accent-soft);
transition:
background var(--transition-fast),
box-shadow var(--transition-fast),
transform var(--transition-fast);
}
.empty-state-cta:hover {
background: var(--accent-hover);
box-shadow: 0 4px 12px var(--accent-soft-hover);
}
.empty-state-cta:active {
/* 0.97 比其它按钮的 0.96 略小 —— 空状态 CTA 是"重决策",按下时
反馈更明显一点,避免用户怀疑"按了没生效" */
transform: scale(0.97);
}
.empty-state-cta:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}
.empty-state .hint kbd {
/* .md 这种代码 chip 用 kbd 标签更语义化 */
/* padding 2/7 → 1/6:在 12px 字号下原 2/7 让 kbd chip 显得"鼓鼓的",
跟相邻的中文小字节奏不齐。收 1px 上下让 chip 更贴字高基线 */
padding: 1px 6px;
background: var(--bg-hover);
border: 1px solid var(--border-subtle);
border-radius: var(--radius-sm);
/* 2026-08 从 11px 提到 12px:11px 在低 DPI 上偏糊,挤在提示文本里看着小 */
font-size: var(--fs-xs-readable);
font-family: var(--font-mono);
color: var(--fg-secondary);
font-weight: 600;
box-shadow: 0 1px 0 var(--border-subtle);
/* 上下微 margin 让 kbd 与相邻文字垂直居中对齐(默认 baseline 会让 kbd 显得"沉") */
margin: 0 1px;
}
.empty-state code {
padding: 2px 8px;
background: var(--bg-hover);
border-radius: var(--radius-sm);
/* 0.85em of 13px ≈ 11px —— 提到 12px(--fs-xs-readable)让 .md chip 跟 kbd 同档 */
font-size: var(--fs-xs-readable);
font-family: var(--font-mono);
/* accent 文字在 bg-hover 上仅 ~4:1;改用 fg-secondary 满足 AA */
color: var(--fg-secondary);
}
/* ============================================================
Editor Pane & Split View
三种模式:preview(仅 viewer)/ edit(仅 editor)/ split(双栏 + 分割条)
============================================================ */
body[data-editor-mode="split"] .app-shell {
/* 五列:sidebar | sb-splitter | editor | splitter | viewer
editor 用 px (--w-editor) 由 app.js 拖动时写在 .app-shell 的 style 上;
viewer 用 1fr 自动填剩余空间。
这样在 split + AI 模式下拖 ai-splitter 不会挤编辑器(详见 src/app.js mountSplitter)。
minmax(280px, ...) 给编辑器最小宽度 —— 编辑器内容至少要够阅读宽度才不挤;
默认 var(--w-editor, 50%) 在 CSS fallback 阶段给一个等分初始值,
app.js 启动时根据 splitRatio 重算覆盖。 */
grid-template-columns:
var(--sidebar-width)
4px
minmax(280px, var(--w-editor, 50%))
4px
minmax(0, 1fr);
}
body[data-editor-mode="preview"] .app-shell {
/* 三列:sidebar | sb-splitter | viewer */
grid-template-columns: var(--sidebar-width) 4px minmax(0, 1fr);
}
body[data-editor-mode="edit"] .app-shell {
/* 三列:sidebar | sb-splitter | editor */
grid-template-columns: var(--sidebar-width) 4px minmax(0, 1fr);
}
.editor-pane {
background: var(--bg-viewer);
overflow: hidden; /* CM6 自己处理滚动 */
min-width: 0;
min-height: 0;
display: none; /* 默认隐藏(preview 模式) */
user-select: text;
}
body[data-editor-mode="edit"] .editor-pane,
body[data-editor-mode="split"] .editor-pane {
display: flex;
flex-direction: column;
}
/* 让编辑器与预览区域保持一致的宽度处理:共用 --w-reader-max。
默认 token 设为 none(content 完全填满区域,最大限度显示内容),
若未来想恢复"最优阅读宽度"上限,把 01-tokens.css 里的 --w-reader-max
改回 px 值(如 720px)即可,不用改这里。margin auto 仍保留,方便
切回 px 上限后让内容在更宽的窗口里居中。 */
.editor-pane .cm-editor {
max-width: var(--w-reader-max);
margin: 0 auto;
width: 100%;
}
/* 窄窗口(< 720px):三栏挤到 1xx px 时退化为「单栏 + 隐藏阅读视图」——
至少保留可读可编辑区域。minWidth: 600 仍由主进程限制,这里只处理 600~720 区间 */
@media (max-width: 720px) {
body[data-editor-mode="split"] .app-shell {
/* 侧栏 splitter 仍保留,方便用户把侧栏压到最小留更多编辑区 */
grid-template-columns: var(--sidebar-width) 4px minmax(0, 1fr);
}
/* 直接隐藏 editor/viewer 之间的 splitter 元素,避免它作为孤儿占据第 5 个 grid 单元 */
body[data-editor-mode="split"] .splitter {
display: none !important;
}
body[data-editor-mode="split"] .viewer {
display: none !important;
}
/* 窄屏默认侧栏宽度变窄 */
:root { --sidebar-width: var(--w-sidebar-narrow); }
}
/* edit 模式:隐藏阅读视图 —— 编辑器已经占据右侧,不需要再显示预览 */
body[data-editor-mode="edit"] .viewer {
display: none;
}
.splitter {
background: var(--border-subtle);
cursor: col-resize;
position: relative;
display: none;
transition: background var(--transition-fast);
}
body[data-editor-mode="split"] .splitter {
display: block;
}
.splitter:hover,
.splitter.is-dragging {
background: var(--accent-soft);
}
.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);
}
.splitter:hover::after,
.splitter.is-dragging::after {
background: var(--accent);
box-shadow: 3px 0 0 var(--accent);
}
/* 编辑器底色 / 文字色已由 editor-theme.js 的 spec(var(--bg-viewer) / var(--fg-primary))
覆盖;老的"dark 强制 bg-viewer、light 强制 bg-viewer"那段代码冗余且容易出错,删除。 */
.cm-editor .cm-tooltip {
background: var(--bg-elevated);
color: var(--fg-primary);
border: 1px solid var(--border-default);
border-radius: var(--radius-sm);
}
.cm-editor .cm-tooltip-autocomplete ul li[aria-selected] {
background: var(--accent-soft);
color: var(--accent);
}
/* ============================================================
Folder Browser(Stage 8):面包屑 + 文件夹 / 二进制条目样式
面包屑放在 .sidebar-sort 下方、.file-list 上方,贴近它作用的列表。
根目录时整个 <nav> hidden(HTML 里默认带 hidden 属性),
不需要 CSS 处理空态。
文件夹(.file-item.is-folder):
- 文件夹图标 + 名称略加粗(区别于普通文件)
- meta 行简化为「文件夹」
二进制(.file-item.is-binary):
- opacity 0.45 + cursor not-allowed —— 视觉与文件管理器对不可执行项的灰态一致
- 仍可被点击(事件不被禁用,渲染端弹 toast 提示)
============================================================ */
.sidebar-breadcrumb {
display: flex;
align-items: center;
gap: 4px;
padding: 6px 10px 4px;
/* 横向溢出 + 不换行:用户进入很深层子目录时(如 archive/2026/03/15)
末尾「当前段」是最重要的,前面的历史段可以横滑出可见区 */
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
/* 让滚动条只在 hover 时更显眼,平时几乎不可见 —— 避免视觉噪音 */
scrollbar-width: thin;
scrollbar-color: transparent transparent;
}
.sidebar-breadcrumb:hover {
scrollbar-color: var(--border-subtle) transparent;
}
/* 回到根按钮:图标 + 文字,单独占左侧 */
.sidebar-breadcrumb-root {
display: inline-flex;
align-items: center;
gap: 3px;
flex: 0 0 auto;
padding: 2px 6px;
border: none;
background: transparent;
color: var(--fg-secondary);
font-size: var(--fs-xs);
cursor: pointer;
border-radius: 3px;
font-family: inherit;
}
.sidebar-breadcrumb-root:hover {
background: var(--bg-hover);
color: var(--fg-primary);
}
.sidebar-breadcrumb-root:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
.sidebar-breadcrumb-root svg {
flex: 0 0 auto;
}
/* 面包屑分段按钮(中间段,可点击跳转) */
.sidebar-breadcrumb-btn {
display: inline-flex;
align-items: center;
flex: 0 0 auto;
padding: 2px 6px;
border: none;
background: transparent;
color: var(--fg-secondary);
font-size: var(--fs-xs);
cursor: pointer;
border-radius: 3px;
font-family: inherit;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-breadcrumb-btn:hover {
background: var(--bg-hover);
color: var(--accent);
}
.sidebar-breadcrumb-btn:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 1px;
}
/* 当前段(非可点击,纯文本):稍亮,与「根 / 中间段」做对比 */
.sidebar-breadcrumb-current {
flex: 0 0 auto;
padding: 2px 6px;
font-size: var(--fs-xs);
color: var(--fg-primary);
font-weight: 600;
max-width: 160px;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar-breadcrumb-sep {
flex: 0 0 auto;
color: var(--fg-muted);
font-size: var(--fs-xs);
opacity: 0.6;
}
/* 文件夹条目:粗体名称 + 文件夹图标颜色稍亮 */
.file-item.is-folder .file-name {
font-weight: 600;
color: var(--fg-primary);
}
.file-item.is-folder .file-icon {
/* 文件夹图标用 accent 而不是 fg-muted:让「这是目录」的视觉信号更直接 */
color: var(--accent);
}
/* 二进制条目:灰态 + cursor 提示不可编辑,但仍可点击(弹 toast) */
.file-item.is-binary {
opacity: 0.45;
cursor: not-allowed;
}
.file-item.is-binary .file-icon {
/* 与灰度视觉统一:图标也走 fg-muted */
color: var(--fg-muted);
}
.file-item.is-binary:hover {
/* 灰态项 hover 不要让背景喧宾夺主 —— 用更弱的 hover 背景 */
background: var(--bg-hover);
}
.file-item.is-binary.is-active {
/* 即使被键盘选中也不打破「不可编辑」的视觉信号 —— 不用 accent 高亮 */
background: var(--bg-hover);
}
.file-item.is-binary.is-active::before {
/* 不画左侧色条:用户已经看 opacity 知道这一项不可编辑 */
opacity: 0.4;
}
/* ============================================================
Folder Browser:拖拽 + 剪贴板视觉反馈
- .file-item 默认 draggable=true(文件 / 文件夹 / 二进制都可拖)
- 拖拽中源项 .is-dragging 半透明;合法投放目标 folder .is-drop-target 给
accent 虚线描边 + 软底高亮;剪切到剪贴板的源项 .is-clipboard-cut 用
虚线 + 弱化提示「将被移走」。
============================================================ */
.file-item[draggable="true"] {
/* 默认已是 cursor:pointer;拖拽态单独给 grabbing 反馈 */
cursor: grab;
}
.file-item.is-dragging {
opacity: 0.4;
cursor: grabbing;
}
.file-item.is-drop-target {
/* 合法投放目标:accent 虚线描边 + 软底,明显区别于 hover 态 */
outline: 2px dashed var(--accent);
outline-offset: -2px;
background: var(--accent-soft);
}
.file-item.is-clipboard-cut {
/* 已剪切待移动的源项:弱化 + 虚线边框,暗示「即将离开此处」 */
opacity: 0.6;
outline: 1px dashed var(--border-default);
outline-offset: -2px;
}
/* audit R7a P1-20:分页「加载更多」按钮
视觉上是一个普通 li 但居中、有虚线分隔;hover 时背景色加深 + cursor:pointer。
跟普通 .file-item 共享 padding / font 但不要 icon,简洁一些。 */
.file-list-load-more {
list-style: none;
padding: 10px 12px;
margin: 4px 8px;
border: 1px dashed var(--border-default);
border-radius: 6px;
text-align: center;
color: var(--fg-secondary);
font-size: var(--fs-sm);
cursor: pointer;
user-select: none;
transition: background 0.1s, border-color 0.1s;
}
.file-list-load-more:hover,
.file-list-load-more:focus-visible {
background: var(--bg-hover);
border-color: var(--accent);
color: var(--fg-primary);
outline: none;
}
/* audit R7a P1-15:windowed 虚拟滚动 —— JS 切到该模式时给 .file-item 加
.is-windowed 类,position:absolute 让 N*50 位置数学生效,left/right
撑满容器,padding 仍然在 .file-item 上(box-sizing:border-box,
height:50px 已含 padding)。
top 由 JS 直接 style.top = N*50+'px' 设置,不写进 CSS(每个 item 不同)。
切回 full 模式时 JS 移除 .is-windowed 与 style.top,flex 布局自然复位。 */
.file-item.is-windowed {
position: absolute;
left: 10px; /* 抵消 .file-list 的 padding-left 10px */
right: 10px; /* 抵消 .file-list 的 padding-right 10px */
}