Files
qlib/frontend/web/app/globals.css
T
Simon 50a1030afa fix(web): 参数表单收束 + 一句话说明改多行(按截图实测修两处失调)
用户给了真实截图,指出两处肉眼可见的不协调,逐项定位后修复:

1. 「一句话说明」是单行 input 被塞进 ~360px 的格子,placeholder
   「例:全市场股息率最高的 2」直接截断 —— 内容太多、框太小。
   → 改为整宽多行 <textarea>(rows=2、min-height 64px、可纵向拉伸),
     与定宽 340px 的策略名并排(.params-meta flex),窄屏自动上下堆叠;
     实时字数 (N/300) 已在提示里。

2. 「太多地方没有对齐」的根因:表单铺满 ~1900px 内容区,5 列各 ~360px,
   控件拉得太开;因子下拉用 1fr 撑到 ~1500px 而权重框才 118px,比例失调;
   数字输入还有 max-width:180px 上限,比同行下拉窄一截。
   → .params-form 约束到 ~1100px(每列 ~210px,所有控件按 1fr 等宽,
     左右边缘严格对齐);移除已多余的 max-width:180px(限宽后它反而
     制造新的不等宽),数值/日期改为填满单元格;因子行首列封顶 460px、
     整行限宽 720px,条件行同理(首列 ≤360px、限宽 760px);
     复权口径选项去掉括号里的「股息策略推荐」(已在 hint 说明),不再截断。

验证方式也升级了:这次是**截图后用 read_image 看渲染像素**确认,而不只量几何。
对齐自检同步把 textarea(高度随行数变、本就该比单行高)排除出「同排等高 /
高度归一」比较,并把数字过宽阈值调到 240px(限宽后 ~210px 属正常)。

门禁:tsc 0 错误、图表单测 7/7、next build 成功、页面 200、
对齐自检 140/140、契约自检 59/59;/backtest 与 /strategies 截图复核通过。
2026-09-30 19:56:40 +08:00

2029 lines
40 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.
/* ============================================================
qlib-platform 设计系统(ui-ux-pro-max · Financial Terminal Dark)
------------------------------------------------------------
语义令牌 → 组件类:深浅分层、高对比文本、金融语义色
(涨=绿 / 跌=红 / 警示=琥珀 / 主色=天蓝)、1px 低透明边框、
统一 4px 间距刻度、可见焦点环、reduced-motion 友好。
============================================================ */
/* 本地自托管字体(Noto Sans SC 可变 400–700,见 fonts.css / public/fonts)。
不再依赖 fonts.googleapis.com / fonts.gstatic.com 等外部网络。 */
@import "./fonts.css";
:root {
color-scheme: dark;
/* 表面分层(自底向上抬升) */
--bg-0: #060912;
--bg-1: #0a0f1c;
--bg-2: #101729;
--bg-3: #172035;
--surface-1: #101827;
--surface-2: #141d30;
--surface-3: #1b2540;
/* 文本 */
--text-1: #e9eef7;
--text-2: #a8b3c9;
--text-3: #8b98b2;
/* 边框 / 分隔 */
--line: rgba(150, 165, 195, 0.13);
--line-strong: rgba(150, 165, 195, 0.28);
/* 语义色 */
--accent: #3fb6ff;
--accent-strong: #0d99f2;
--accent-soft: rgba(63, 182, 255, 0.14);
--violet: #8b95ff;
--pos: #3ddc97;
--pos-strong: #1fb871;
--pos-soft: rgba(61, 220, 151, 0.13);
--neg: #ff7a7a;
--neg-soft: rgba(255, 122, 122, 0.13);
--warn: #f5b82e;
--warn-soft: rgba(245, 184, 46, 0.14);
--focus: rgba(63, 182, 255, 0.55);
/* 圆角 */
--r-xs: 6px;
--r-sm: 9px;
--r-md: 12px;
--r-lg: 16px;
/* 控件高度令牌 —— 对齐的根基。
为什么必须显式定高:input / select / button 的原生行高与内边距各不相同,
靠 padding 拼高度会得到 34 / 36.8 / 38.8 三种结果(Chrome 的 date 输入还会
多出 2px),同一行里就参差不齐。显式 height 是唯一能保证等高的做法。 */
--ctl-h-sm: 28px; /* 密集行的小按钮 / 图标按钮 */
--ctl-h-md: 34px; /* 标准控件:输入框 / 下拉 / 按钮 */
--ctl-h-lg: 38px; /* 主操作控件(大号输入框、登录式表单) */
/* 控件横向内边距:与高度同源,保证左右留白一致 */
--ctl-px: 12px;
--ctl-px-sm: 10px;
/* 间距(4px 节奏) */
--sp-1: 4px;
--sp-2: 8px;
--sp-3: 12px;
--sp-4: 16px;
--sp-5: 24px;
--sp-6: 32px;
--sp-7: 48px;
/* 字体 */
--font-sans: "Noto Sans SC", -apple-system, "Segoe UI", "PingFang SC",
"Hiragino Sans GB", "Microsoft YaHei", system-ui, sans-serif;
--font-mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Code",
Consolas, "Noto Sans SC", monospace;
/* 阴影:克制的两层 */
--shadow-1: 0 1px 0 rgba(255, 255, 255, 0.03) inset, 0 8px 24px rgba(0, 0, 0, 0.25);
--shadow-pop: 0 12px 40px rgba(0, 0, 0, 0.45);
/* 字号(px 由浏览器缩放) */
--fs-xs: 12px;
--fs-sm: 13px;
--fs-md: 14px;
--fs-lg: 16px;
--fs-xl: 20px;
--fs-2xl: 26px;
--nav-w: 236px;
}
/* ---------- 基础 ---------- */
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
background: var(--bg-0);
color: var(--text-1);
font-family: var(--font-sans);
font-size: var(--fs-md);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
min-height: 100vh;
}
::selection {
background: var(--accent-soft);
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
h1,
h2,
h3,
p {
margin: 0;
}
button,
input,
select,
textarea {
font: inherit;
color: inherit;
}
:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
/* 视觉隐藏但保留给屏幕阅读器(表单控件 label 用) */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0 0 0 0);
white-space: nowrap;
border: 0;
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
/* ---------- 应用外壳(侧栏 + 内容) ---------- */
.shell {
display: grid;
grid-template-columns: var(--nav-w) minmax(0, 1fr);
min-height: 100vh;
}
/* 左侧导航 */
.rail {
position: sticky;
top: 0;
height: 100vh;
display: flex;
flex-direction: column;
gap: var(--sp-2);
background: linear-gradient(180deg, rgba(16, 23, 40, 0.96), rgba(10, 15, 28, 0.98));
border-right: 1px solid var(--line);
padding: var(--sp-4) var(--sp-3);
overflow-y: auto;
z-index: 30;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
padding: 2px var(--sp-2) var(--sp-3);
font-weight: 700;
letter-spacing: 0.2px;
}
.brand .logo {
display: inline-flex;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
border-radius: 9px;
color: #04121f;
background: linear-gradient(135deg, var(--accent), var(--violet));
box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14) inset;
flex: none;
}
.brand small {
display: block;
color: var(--text-3);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.4px;
line-height: 1.2;
}
.nav-label {
margin: var(--sp-3) var(--sp-2) var(--sp-1);
color: var(--text-3);
font-size: var(--fs-xs);
font-weight: 600;
letter-spacing: 1px;
text-transform: uppercase;
}
.nav a.nav-link {
display: flex;
align-items: center;
gap: 10px;
padding: 7px 10px;
border-radius: var(--r-sm);
color: var(--text-2);
font-size: var(--fs-sm);
font-weight: 500;
text-decoration: none;
transition: background 0.15s ease, color 0.15s ease;
}
.nav a.nav-link:hover {
background: rgba(150, 165, 195, 0.09);
color: var(--text-1);
text-decoration: none;
}
.nav a.nav-link.active {
background: var(--accent-soft);
color: #dff1ff;
box-shadow: inset 0 0 0 1px rgba(63, 182, 255, 0.22);
}
.nav a.nav-link.active .nav-ic {
color: var(--accent);
}
.nav-ic {
display: inline-flex;
color: var(--text-3);
flex: none;
transition: color 0.15s ease;
}
.rail-foot {
margin-top: auto;
padding: var(--sp-3) var(--sp-2) 0;
border-top: 1px solid var(--line);
color: var(--text-3);
font-size: var(--fs-xs);
line-height: 1.5;
}
/* 顶栏(窄屏) */
.topbar {
display: none;
}
/* 抽屉与遮罩仅窄屏启用:桌面端隐藏,避免它们作为 shell 网格子元素
把 main 挤到第二行(导致主内容被压在侧栏下方)。 */
.drawer,
.scrim {
display: none;
}
.content {
min-width: 0;
padding: var(--sp-5) clamp(16px, 3vw, 40px) var(--sp-7);
}
.page {
max-width: 1180px;
margin: 0 auto;
}
/* 抽屉(窄屏) */
.scrim {
display: none;
}
/* ---------- 页头 ---------- */
.page-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: var(--sp-4);
margin-bottom: var(--sp-5);
flex-wrap: wrap;
}
.page-title {
font-size: var(--fs-2xl);
font-weight: 700;
letter-spacing: -0.2px;
line-height: 1.25;
}
.page-sub {
margin-top: 4px;
color: var(--text-2);
font-size: var(--fs-sm);
max-width: 720px;
}
.page-actions {
display: flex;
align-items: center;
gap: var(--sp-2);
flex-wrap: wrap;
}
/* ---------- 卡片 ---------- */
.card {
background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
border: 1px solid var(--line);
border-radius: var(--r-md);
box-shadow: var(--shadow-1);
margin-bottom: var(--sp-4);
}
.card__head {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
border-bottom: 1px solid var(--line);
}
.card__title {
font-size: var(--fs-md);
font-weight: 600;
display: flex;
align-items: center;
gap: var(--sp-2);
color: var(--text-1);
}
.card__title .ic {
color: var(--text-3);
}
.card__sub {
color: var(--text-3);
font-size: var(--fs-xs);
font-weight: 400;
margin-top: 1px;
}
.card__tools {
display: flex;
align-items: center;
gap: var(--sp-2);
flex: none;
}
.card__body {
padding: var(--sp-4);
}
.card__body--flush {
padding: 0;
}
.card--flat {
background: transparent;
box-shadow: none;
}
/* ---------- 按钮 ---------- */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 7px;
border: 1px solid var(--line-strong);
background: var(--surface-3);
color: var(--text-1);
border-radius: var(--r-sm);
/* 显式高度 + 横向内边距(不再用「上下 padding 拼高度」):
这样按钮与同排的输入框/下拉必然等高,换字体或换浏览器也不会错位。 */
height: var(--ctl-h-md);
padding: 0 var(--ctl-px);
font-size: var(--fs-sm);
font-weight: 500;
cursor: pointer;
white-space: nowrap;
transition: background 0.15s ease, border-color 0.15s ease,
transform 0.05s ease, box-shadow 0.15s ease;
}
.btn:hover {
background: #243152;
border-color: rgba(150, 165, 195, 0.4);
text-decoration: none;
}
.btn:active {
transform: translateY(0.5px);
}
.btn:disabled {
opacity: 0.55;
cursor: not-allowed;
transform: none;
}
.btn--primary {
background: linear-gradient(180deg, #4dc0ff, var(--accent-strong));
color: #061423;
border-color: transparent;
font-weight: 600;
}
.btn--primary:hover {
filter: brightness(1.07);
background: linear-gradient(180deg, #4dc0ff, var(--accent-strong));
}
.btn--danger {
color: var(--neg);
border-color: rgba(255, 122, 122, 0.35);
}
.btn--danger:hover {
background: var(--neg-soft);
}
.btn--sm {
height: var(--ctl-h-sm);
padding: 0 var(--ctl-px-sm);
font-size: var(--fs-xs);
border-radius: var(--r-xs);
}
.btn .ic {
flex: none;
}
/* 图标按钮 */
.icon-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: var(--ctl-h-sm);
height: var(--ctl-h-sm);
border-radius: var(--r-sm);
color: var(--text-2);
background: transparent;
border: 1px solid transparent;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
}
.icon-btn:hover {
background: rgba(150, 165, 195, 0.12);
color: var(--text-1);
}
/* 大号图标按钮:顶栏等需要更舒服点击目标的位置(仍属于 28/34/38 三级刻度) */
.icon-btn--lg {
width: var(--ctl-h-lg);
height: var(--ctl-h-lg);
}
/* ---------- 表单 ---------- */
.input,
select.input,
textarea.input {
width: 100%;
/* 与 .btn 同一个高度令牌:输入框与按钮同排必然等高。
Chrome 的原生 date 输入比其它控件高 2px,靠 height 一并压平。 */
height: var(--ctl-h-md);
background: #0d1424;
border: 1px solid var(--line-strong);
color: var(--text-1);
border-radius: var(--r-sm);
padding: 0 var(--ctl-px-sm);
font-size: var(--fs-sm);
transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
/* 多行输入不套用控件定高(内容行数决定高度) */
textarea.input {
height: auto;
min-height: 76px;
padding: 8px var(--ctl-px-sm);
line-height: 1.6;
resize: vertical;
}
.input:hover {
border-color: rgba(150, 165, 195, 0.42);
}
.input:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.input::placeholder {
color: var(--text-3);
}
select.input {
appearance: none;
background-image: linear-gradient(45deg, transparent 50%, var(--text-2) 50%),
linear-gradient(135deg, var(--text-2) 50%, transparent 50%);
background-position: calc(100% - 16px) 55%, calc(100% - 11px) 55%;
background-size: 5px 5px, 5px 5px;
background-repeat: no-repeat;
padding-right: 28px;
}
input[type="date"].input {
padding-right: 8px;
}
/* 原生勾选控件:尺寸统一到 16px,并把「点击目标」交给外层 label(.check / .radio-row)——
16px 的方块本身永远达不到 28px 触控目标,靠 label 撑开热区才是正确做法。 */
input[type="checkbox"],
input[type="radio"] {
width: 16px;
height: 16px;
margin: 0;
flex: none;
accent-color: var(--accent);
cursor: pointer;
}
/* 单行勾选项(与相邻的下拉/输入同高,保证整行对齐一致) */
.check {
display: inline-flex;
align-items: center;
gap: var(--sp-2);
min-height: var(--ctl-h-md);
color: var(--text-2);
font-size: var(--fs-sm);
cursor: pointer;
}
.check:hover {
color: var(--text-1);
}
/* 表格单元里的勾选框:把整个单元做成热区(16px 方块在表格里几乎点不中)。
用 label 包住 → 点文字/空白都能选中,行高即为常规控件高度。 */
.check--cell {
min-height: var(--ctl-h-md);
width: 100%;
justify-content: center;
padding: 0 var(--sp-1);
}
input[type="number"].input,
input[type="date"].input {
width: auto;
min-width: 0;
}
/* 数值/日期字段:在已限宽(.params-form ~1100px)的栅格里直接填满单元格,
与同行的下拉/文本框等宽 → 左右边缘严格对齐。早先的 max-width:180px 在表单
铺满整屏时有意义,限宽后反而让数字框比相邻下拉窄一截(截图实测),故移除。 */
.form-grid .field > input[type="number"].input,
.form-grid .field > input[type="date"].input {
width: 100%;
}
.field {
display: flex;
flex-direction: column;
gap: 5px;
min-width: 0;
}
.field__label {
color: var(--text-2);
font-size: var(--fs-xs);
font-weight: 500;
letter-spacing: 0.2px;
/* 无标签时也占一行高度(栅格里标签行高不一致会让控件上下错位) */
min-height: 17px;
line-height: 17px;
}
.field__hint {
color: var(--text-3);
font-size: var(--fs-xs);
line-height: 1.5;
/* 提示不裁切:说明文字被 line-clamp 截断等于把信息藏起来。
跨行对齐由 .form-grid 的 align-items:end 保证,不靠裁切文案。 */
}
/* 工具条里的检索框 / 筛选器:宽度由类统一,避免每处写内联像素宽度 */
.input--search {
width: min(240px, 100%);
}
.input--filter {
width: 130px;
}
/* 选项文案较长的选择器(如「代码 名称(收益)」)给更宽的宽度 */
.input--picker {
width: min(320px, 100%);
}
/* ============================================================
参数表单布局:把 sprawling 的多列表单收进一个可读的最大宽度。
------------------------------------------------------------
为什么需要:表单直接铺满 ~1900px 的内容区时,5 列各 ~360px,控件拉得太开、
数字输入和下拉宽度还不一致(截图实测)。约束到 ~1100px 后每列 ~210px,
所有控件按 1fr 等宽 → 同行左右边缘严格对齐,数字也不会宽得离谱。
============================================================ */
.params-form {
max-width: 1100px;
}
/* 策略名 + 一句话说明:定宽名 + 占满剩余宽度的多行说明 */
.params-meta {
display: flex;
gap: var(--sp-4);
align-items: flex-start;
margin-bottom: var(--sp-4);
}
.params-meta__name {
flex: 0 0 340px;
min-width: 0;
}
.params-meta__desc {
flex: 1 1 auto;
min-width: 0;
}
.params-meta__desc textarea.input {
min-height: 64px;
line-height: 1.6;
resize: vertical;
}
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
gap: var(--sp-3);
align-items: end;
}
/* 整行字段(长文本框、日期区间、条件编辑器) */
.field--wide {
grid-column: 1 / -1;
}
.form-grid .btn {
align-self: end;
height: var(--ctl-h-md);
}
/* 小节标题(表单内的分组标题,与 .card__title 区分:更小、不抢层级) */
.form-section__title {
font-size: var(--fs-sm);
font-weight: 600;
color: var(--text-1);
}
/* 因子行:表头 + 栅格行,靠栅格列宽保证「因子 / 权重 / 操作」三列严格对齐。
反面做法是在 JSX 里给每个控件写死像素宽度(style={{width:220}}),
一旦文案或字体变化就会错位。 */
.factor-rows {
display: grid;
gap: var(--sp-2);
}
.factor-rows__head,
.factor-row {
display: grid;
/* 因子名列封顶 460px:再宽就是「momentum_60」配 1500px 下拉的失调(截图实测)。
权重列固定,操作列按内容。整行也限宽,避免在宽屏上拉成一条长线。 */
grid-template-columns: minmax(150px, 460px) 118px auto;
align-items: center;
gap: var(--sp-2);
max-width: 720px;
}
.factor-rows__head {
color: var(--text-3);
font-size: var(--fs-xs);
padding-bottom: 2px;
border-bottom: 1px solid var(--line);
}
.factor-row .btn {
justify-self: start;
}
/* 选股条件行:与因子行同一套栅格思路(表头 + 成列),
列宽由 CSS 决定,不在 JSX 里写内联像素宽度。 */
.cond-rows {
display: grid;
gap: var(--sp-2);
max-width: 760px;
}
.cond-rows__head,
.cond-row {
display: grid;
grid-template-columns: minmax(150px, 360px) 84px minmax(96px, 160px) auto;
align-items: center;
gap: var(--sp-2);
}
.cond-rows__head {
color: var(--text-3);
font-size: var(--fs-xs);
padding-bottom: 2px;
border-bottom: 1px solid var(--line);
}
.cond-row .btn {
justify-self: start;
}
.row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-2);
}
.between {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--sp-3);
flex-wrap: wrap;
}
.chart-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(430px, 100%), 1fr));
gap: var(--sp-4);
}
.chart-grid .card {
margin-bottom: 0;
}
.stack {
display: flex;
flex-direction: column;
gap: var(--sp-2);
}
/* 搜索框 */
.searchbox {
position: relative;
min-width: min(320px, 100%);
}
.searchbox .ic {
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: var(--text-3);
pointer-events: none;
}
.searchbox .input {
padding-left: 32px;
}
.searchbox .clear {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
}
/* ---------- 徽标 / 状态 ---------- */
.pill {
display: inline-flex;
align-items: center;
gap: 5px;
border-radius: 999px;
padding: 2px 9px;
font-size: var(--fs-xs);
font-weight: 500;
line-height: 1.6;
border: 1px solid transparent;
white-space: nowrap;
}
.pill--default {
color: var(--text-2);
background: rgba(150, 165, 195, 0.12);
border-color: rgba(150, 165, 195, 0.18);
}
.pill--accent {
color: #bfe9ff;
background: var(--accent-soft);
border-color: rgba(63, 182, 255, 0.3);
}
.pill--pos {
color: #9ff0cb;
background: var(--pos-soft);
border-color: rgba(61, 220, 151, 0.3);
}
.pill--neg {
color: #ffb1b1;
background: var(--neg-soft);
border-color: rgba(255, 122, 122, 0.3);
}
.pill--warn {
color: #ffe3a3;
background: var(--warn-soft);
border-color: rgba(245, 184, 46, 0.32);
}
.pill--violet {
color: #cdd2ff;
background: rgba(139, 149, 255, 0.14);
border-color: rgba(139, 149, 255, 0.3);
}
.tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 7px;
border-radius: var(--r-xs);
background: rgba(150, 165, 195, 0.1);
color: var(--text-2);
font-size: var(--fs-xs);
border: 1px solid var(--line);
}
.tag--mono {
font-family: var(--font-mono);
font-size: var(--fs-xs);
}
/* ---------- 指标卡 ---------- */
.metric-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
gap: var(--sp-3);
margin-bottom: var(--sp-4);
}
.metric {
background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
border: 1px solid var(--line);
border-radius: var(--r-md);
padding: var(--sp-3) var(--sp-4);
position: relative;
overflow: hidden;
}
.metric__label {
display: flex;
align-items: center;
gap: 7px;
color: var(--text-2);
font-size: var(--fs-xs);
font-weight: 500;
}
.metric__label .ic {
color: var(--text-3);
}
.metric__value {
margin-top: 6px;
font-size: 24px;
font-weight: 700;
letter-spacing: -0.3px;
font-variant-numeric: tabular-nums;
line-height: 1.2;
}
.metric__value--sm {
font-size: var(--fs-lg);
}
.metric__sub {
margin-top: 4px;
color: var(--text-3);
font-size: var(--fs-xs);
font-variant-numeric: tabular-nums;
}
.metric__value.tone-pos,
.tone-pos {
color: var(--pos);
}
.metric__value.tone-neg,
.tone-neg {
color: var(--neg);
}
.metric__value.tone-warn,
.tone-warn {
color: var(--warn);
}
.metric__value.tone-accent,
.tone-accent {
color: var(--accent);
}
.metric__value.tone-plain,
.tone-plain {
color: var(--text-1);
}
/* ---------- 表格 ---------- */
.table-wrap {
overflow-x: auto;
}
table.tbl {
width: 100%;
border-collapse: collapse;
font-size: var(--fs-sm);
font-variant-numeric: tabular-nums;
}
.tbl th,
.tbl td {
text-align: left;
padding: 9px 12px;
border-bottom: 1px solid var(--line);
vertical-align: middle;
}
.tbl th {
color: var(--text-3);
font-weight: 600;
font-size: var(--fs-xs);
letter-spacing: 0.4px;
background: rgba(150, 165, 195, 0.045);
white-space: nowrap;
}
.tbl tbody tr {
transition: background 0.12s ease;
}
.tbl tbody tr:hover {
background: rgba(150, 165, 195, 0.055);
}
.tbl tr:last-child td {
border-bottom: none;
}
.tbl tbody tr.row-active {
background: var(--accent-soft);
}
.cell-mono,
.mono {
font-family: var(--font-mono);
}
.cell-strong {
font-weight: 600;
color: var(--text-1);
}
.tbl .num,
.num {
font-variant-numeric: tabular-nums;
}
.tbl .tone-pos,
.tbl td.tone-pos {
color: var(--pos);
}
.tbl .tone-neg,
.tbl td.tone-neg {
color: var(--neg);
}
.tbl .tone-warn,
.tbl td.tone-warn {
color: var(--warn);
}
/* 扩展行 / 详情内嵌 */
.expand-cell {
background: rgba(10, 15, 28, 0.6);
}
.expand-cell > div {
border-left: 2px solid var(--accent-soft);
margin: 4px 12px 12px;
padding: 10px 12px;
border-radius: var(--r-sm);
background: var(--bg-1);
}
/* ---------- 进度 / 加载 / 空态 ---------- */
.progress {
height: 6px;
border-radius: 999px;
background: rgba(150, 165, 195, 0.16);
overflow: hidden;
margin: var(--sp-2) 0;
}
.progress__bar {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, var(--accent-strong), var(--accent));
transition: width 0.3s ease;
}
.skeleton {
border-radius: var(--r-sm);
background: linear-gradient(90deg, rgba(150, 165, 195, 0.08), rgba(150, 165, 195, 0.16), rgba(150, 165, 195, 0.08));
background-size: 200% 100%;
animation: shimmer 1.4s infinite linear;
}
@keyframes shimmer {
to {
background-position: -200% 0;
}
}
.empty {
padding: var(--sp-6) var(--sp-4);
text-align: center;
color: var(--text-3);
}
.empty .ic {
color: var(--text-3);
opacity: 0.6;
margin-bottom: var(--sp-2);
}
.empty strong {
display: block;
color: var(--text-2);
margin-bottom: 2px;
}
/* 提示条 */
.banner {
display: flex;
align-items: flex-start;
gap: 9px;
padding: 10px 13px;
border-radius: var(--r-sm);
font-size: var(--fs-sm);
margin-bottom: var(--sp-3);
border: 1px solid;
line-height: 1.55;
overflow-wrap: anywhere;
}
.banner .ic {
flex: none;
margin-top: 2px;
}
.banner--error {
color: #ffc9c9;
background: var(--neg-soft);
border-color: rgba(255, 122, 122, 0.32);
}
.banner--warn {
color: #ffe6ae;
background: var(--warn-soft);
border-color: rgba(245, 184, 46, 0.32);
}
.banner--info {
color: #d6efff;
background: var(--accent-soft);
border-color: rgba(63, 182, 255, 0.3);
}
.banner--muted {
color: var(--text-2);
background: rgba(150, 165, 195, 0.08);
border-color: var(--line);
}
/* 说明文本 */
.hint {
color: var(--text-3);
font-size: var(--fs-xs);
line-height: 1.7;
}
/* 数值条(分层收益等) */
.vbars {
display: flex;
align-items: flex-end;
gap: 8px;
height: 96px;
padding: var(--sp-3) 0;
}
.vbars__item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
min-width: 0;
}
.vbars__bar {
width: min(46px, 70%);
min-height: 3px;
border-radius: 4px 4px 0 0;
transition: height 0.3s ease;
}
.vbars__bar.is-pos {
background: linear-gradient(180deg, var(--pos), var(--pos-strong));
}
.vbars__bar.is-neg {
background: linear-gradient(180deg, #ff9d9d, var(--neg));
}
.vbars__val {
font-size: var(--fs-xs);
color: var(--text-1);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.vbars__cap {
color: var(--text-3);
font-size: var(--fs-xs);
}
/* 选中清单 chips */
.chips {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
}
.chip {
display: inline-flex;
align-items: center;
gap: 6px;
/* 历史记录 chip 是可点击控件:高度必须达到点击目标下限,
不能被 padding 压到 27px 这种「看得见点不准」的尺寸 */
min-height: var(--ctl-h-sm);
background: var(--surface-3);
border: 1px solid var(--line-strong);
border-radius: 999px;
padding: 3px 6px 3px 11px;
font-size: var(--fs-xs);
color: var(--text-2);
}
button.chip {
cursor: pointer;
}
button.chip:hover {
border-color: var(--accent);
color: var(--text-1);
}
.chip b {
color: var(--text-1);
font-weight: 600;
}
.chip__x {
display: inline-flex;
padding: 3px;
border-radius: 50%;
cursor: pointer;
color: var(--text-3);
background: transparent;
border: none;
}
.chip__x:hover {
color: var(--neg);
background: var(--neg-soft);
}
/* 月度收益着色单元格 */
.cell-heat {
font-variant-numeric: tabular-nums;
border-radius: var(--r-xs);
padding: 2px 7px;
display: inline-block;
min-width: 58px;
text-align: right;
}
.cell-heat.pos {
color: #c7ffe4;
background: var(--pos-soft);
}
.cell-heat.neg {
color: #ffd2d2;
background: var(--neg-soft);
}
.cell-heat.flat {
color: var(--text-2);
background: rgba(150, 165, 195, 0.1);
}
/* 旋转加载图标 */
.spin {
animation: spin 0.9s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.ic {
display: inline-block;
flex: none;
vertical-align: -2px;
}
.quick-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
gap: var(--sp-3);
margin-bottom: var(--sp-4);
}
.quick-card {
display: flex;
gap: 13px;
align-items: flex-start;
padding: var(--sp-4);
background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
border: 1px solid var(--line);
border-radius: var(--r-md);
color: var(--text-1);
text-decoration: none;
transition: border-color 0.15s ease, transform 0.12s ease, background 0.15s ease;
}
.quick-card:hover {
border-color: rgba(63, 182, 255, 0.4);
text-decoration: none;
background: linear-gradient(180deg, var(--surface-3), var(--surface-2));
}
.quick-card .quick-ic {
display: inline-flex;
width: 38px;
height: 38px;
flex: none;
align-items: center;
justify-content: center;
border-radius: 11px;
background: var(--accent-soft);
color: var(--accent);
}
.quick-card--violet .quick-ic {
background: rgba(139, 149, 255, 0.14);
color: var(--violet);
}
.quick-card--pos .quick-ic {
background: var(--pos-soft);
color: var(--pos);
}
.quick-card--warn .quick-ic {
background: var(--warn-soft);
color: var(--warn);
}
.quick-card h3 {
font-size: var(--fs-md);
font-weight: 600;
margin-bottom: 2px;
}
.quick-card p {
color: var(--text-2);
font-size: var(--fs-xs);
line-height: 1.55;
}
.quick-card .go {
margin-top: 8px;
font-size: var(--fs-xs);
font-weight: 600;
color: var(--accent);
display: inline-flex;
align-items: center;
gap: 4px;
}
/* 运行面板(回测/组合配置) */
.runpanel {
position: sticky;
bottom: 12px;
z-index: 20;
display: flex;
align-items: center;
gap: var(--sp-3);
flex-wrap: wrap;
background: rgba(13, 19, 35, 0.92);
backdrop-filter: blur(8px);
border: 1px solid var(--line-strong);
border-radius: var(--r-md);
padding: var(--sp-3) var(--sp-4);
box-shadow: var(--shadow-pop);
}
.runpanel__info {
color: var(--text-2);
font-size: var(--fs-xs);
flex: 1;
min-width: 200px;
}
.runpanel__info b {
color: var(--text-1);
}
/* 数值差箭头 */
.delta {
display: inline-flex;
align-items: center;
gap: 3px;
font-weight: 600;
}
/* 列表(说明/步骤) */
.steplist {
margin: 0;
padding-left: 18px;
display: flex;
flex-direction: column;
gap: 6px;
color: var(--text-2);
font-size: var(--fs-sm);
}
.steplist b {
color: var(--text-1);
}
/* ---------- 响应式 ---------- */
@media (max-width: 940px) {
:root {
--nav-w: 0px;
}
.shell {
grid-template-columns: 1fr;
}
.rail {
display: none;
}
.topbar {
position: sticky;
top: 0;
z-index: 40;
display: flex;
align-items: center;
gap: var(--sp-2);
height: 52px;
padding: 0 var(--sp-3);
background: rgba(10, 15, 28, 0.92);
backdrop-filter: blur(8px);
border-bottom: 1px solid var(--line);
}
.topbar .brand {
padding: 0;
}
.drawer {
position: fixed;
inset: 0 auto 0 0;
width: min(78vw, 290px);
background: var(--bg-1);
border-right: 1px solid var(--line-strong);
z-index: 60;
transform: translateX(-102%);
transition: transform 0.22s cubic-bezier(0.2, 0.8, 0.3, 1);
display: flex;
flex-direction: column;
padding: var(--sp-3);
gap: var(--sp-1);
overflow-y: auto;
box-shadow: var(--shadow-pop);
}
.drawer.open {
transform: translateX(0);
}
.drawer .brand {
padding-bottom: var(--sp-3);
margin-bottom: var(--sp-1);
border-bottom: 1px solid var(--line);
}
.scrim {
display: block;
position: fixed;
inset: 0;
background: rgba(3, 6, 12, 0.6);
z-index: 50;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
}
.scrim.show {
opacity: 1;
pointer-events: auto;
}
.content {
padding: var(--sp-4) 14px var(--sp-6);
}
.runpanel {
position: static;
border-radius: var(--r-md);
}
}
@media (max-width: 560px) {
.metric-grid {
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: var(--sp-2);
}
.metric__value {
font-size: 20px;
}
.form-grid {
grid-template-columns: 1fr 1fr;
}
/* 窄屏:策略名与说明上下堆叠,名占满整行 */
.params-meta {
flex-direction: column;
gap: var(--sp-3);
}
.params-meta__name {
flex: 1 1 auto;
width: 100%;
}
.page-head {
flex-direction: column;
}
}
/* ============================================================
图表(TradingView Lightweight Charts)—— 图例 / tooltip / 容器
============================================================ */
.lw-wrap {
display: flex;
flex-direction: column;
gap: var(--sp-2);
min-width: 0;
}
.lw-legend {
display: flex;
flex-wrap: wrap;
gap: var(--sp-1) var(--sp-2);
align-items: center;
}
.lw-legend-item {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 8px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--surface-2);
color: var(--text-2);
font-size: var(--fs-xs);
cursor: pointer;
transition: opacity 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.lw-legend-item:hover {
border-color: var(--line-strong);
background: var(--surface-3);
}
.lw-legend-item:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 1px;
}
.lw-legend-item i {
width: 9px;
height: 9px;
border-radius: 2px;
flex: 0 0 auto;
}
.lw-legend-item b {
color: var(--text-1);
font-weight: 600;
}
.lw-legend-item.is-off {
opacity: 0.42;
text-decoration: line-through;
}
.lw-canvas {
position: relative;
width: 100%;
min-width: 0;
}
/* tooltip:LW 无内置实现,由 LwChart 订阅十字光标渲染 */
.lw-tip {
position: absolute;
z-index: 5;
pointer-events: none;
min-width: 132px;
padding: 7px 9px;
border: 1px solid rgba(150, 165, 195, 0.28);
border-radius: var(--r-sm);
background: rgba(20, 29, 48, 0.96);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
font-size: var(--fs-xs);
}
.lw-tip-time {
color: var(--text-3);
margin-bottom: 4px;
}
.lw-tip-row {
display: flex;
align-items: center;
gap: 6px;
line-height: 1.6;
}
.lw-tip-row i {
width: 8px;
height: 8px;
border-radius: 2px;
flex: 0 0 auto;
}
.lw-tip-label {
color: var(--text-2);
flex: 1 1 auto;
white-space: nowrap;
}
.lw-tip-row b {
color: var(--text-1);
}
/* ============================================================
股票代码 + 名称(任何出现代码的地方都必须成对出现且可点击)
============================================================ */
.symlink {
display: inline-flex;
align-items: baseline;
gap: 6px;
max-width: 100%;
padding: 1px 3px;
margin: -1px -3px;
border-radius: var(--r-xs);
color: inherit;
text-decoration: none;
transition: background 0.12s ease;
}
.symlink:hover {
background: var(--accent-soft);
text-decoration: none;
}
.symlink:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 1px;
}
.symlink .sym-code {
font-family: var(--font-mono);
font-size: 0.92em;
color: var(--text-2);
white-space: nowrap;
}
.symlink:hover .sym-code {
color: var(--accent);
}
.symlink .sym-name {
color: var(--text-1);
font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 9em;
}
.symlink .sym-name--missing {
color: var(--text-3);
font-weight: 400;
}
/* 名称列优先:窄屏时允许名称换行而不是把代码挤掉 */
.sym-cell {
min-width: 132px;
}
/* ============================================================
策略说明与计算公式
============================================================ */
.doc-block {
display: flex;
flex-direction: column;
gap: var(--sp-2);
}
.doc-summary {
display: flex;
align-items: flex-start;
gap: var(--sp-2);
padding: 10px 12px;
border: 1px solid var(--line);
border-left: 3px solid var(--accent);
border-radius: var(--r-sm);
background: linear-gradient(90deg, rgba(63, 182, 255, 0.08), transparent 70%);
color: var(--text-1);
line-height: 1.65;
}
.doc-formula {
display: flex;
flex-direction: column;
gap: 6px;
}
.doc-formula pre {
margin: 0;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: var(--r-sm);
background: var(--bg-1);
color: var(--text-1);
font-family: var(--font-mono);
font-size: var(--fs-xs);
line-height: 1.75;
overflow-x: auto;
white-space: pre;
}
.doc-steps {
margin: 0;
padding-left: 18px;
color: var(--text-2);
font-size: var(--fs-sm);
line-height: 1.8;
}
.doc-steps li b {
color: var(--text-1);
}
/* 策略卡片 */
.strategy-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: var(--sp-4);
}
.strategy-desc {
color: var(--text-2);
line-height: 1.7;
font-size: var(--fs-sm);
}
.strategy-actions {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
}
/* ============================================================
实验对比
============================================================ */
.cmp-toolbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-2);
}
.cmp-diff-add {
color: var(--pos);
}
.cmp-diff-del {
color: var(--neg);
}
.cmp-diff-same {
color: var(--text-3);
}
.cmp-badge {
display: inline-flex;
align-items: center;
gap: 6px;
font-size: var(--fs-xs);
color: var(--text-3);
}
/* ============================================================
易用性:粘性操作条 / 结果锚点 / 键盘提示
============================================================ */
.sticky-bar {
position: sticky;
top: 0;
z-index: 20;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--sp-3);
padding: 10px 12px;
margin-bottom: var(--sp-3);
border: 1px solid var(--line);
border-radius: var(--r-md);
background: rgba(10, 15, 28, 0.92);
backdrop-filter: blur(8px);
}
.kbd {
display: inline-block;
padding: 1px 5px;
border: 1px solid var(--line-strong);
border-bottom-width: 2px;
border-radius: 4px;
background: var(--surface-2);
color: var(--text-2);
font-family: var(--font-mono);
font-size: 11px;
line-height: 1.5;
}
/* 表单校验:错误态 + 行内提示 */
.field--invalid input,
.field--invalid select {
border-color: var(--neg);
}
.field-err {
color: var(--neg);
font-size: var(--fs-xs);
margin-top: 4px;
}
/* 可点击行(表格) */
.tbl tbody tr.row-clickable {
cursor: pointer;
}
/* 进度阶段列表 */
.stages {
display: flex;
flex-wrap: wrap;
gap: var(--sp-2);
align-items: center;
}
.stage {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 3px 9px;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--surface-2);
color: var(--text-3);
font-size: var(--fs-xs);
}
.stage.is-done {
color: var(--pos);
border-color: rgba(61, 220, 151, 0.35);
}
.stage.is-active {
color: var(--accent);
border-color: rgba(63, 182, 255, 0.45);
background: var(--accent-soft);
}
/* 面包屑 / 返回 */
.crumb {
display: inline-flex;
align-items: center;
gap: 6px;
color: var(--text-3);
font-size: var(--fs-sm);
}
.crumb a {
color: var(--text-2);
}
.crumb a:hover {
color: var(--accent);
}
/* ---------- 补充:既有页面已在用但此前没有样式规则的类 ---------- */
/* .dim:次要信息(日期/计数),此前无规则导致与正文同色,层级不清 */
.dim {
color: var(--text-3);
}
/* .input--invalid:单个输入框的错误态(.field--invalid 是整字段态) */
.input--invalid,
.input--invalid:focus {
border-color: var(--neg);
box-shadow: 0 0 0 3px var(--neg-soft);
}
/* ---------- 单选组(买不进补位策略:三选一) ---------- */
.radio-col {
display: flex;
flex-direction: column;
gap: var(--sp-1);
}
.radio-row {
display: flex;
align-items: center;
gap: var(--sp-2);
/* 点击目标由整行承担(16px 的圆点本身太小),并与 .check 同一高度节奏 */
min-height: var(--ctl-h-sm);
padding: 2px 0;
font-size: var(--fs-sm);
color: var(--text-2);
cursor: pointer;
line-height: 1.45;
}
.radio-row input {
flex: none;
}
.radio-row:hover {
color: var(--text-1);
}
/* 单选行里的说明文字:与选项标题同一行时用弱化色,不抢视线 */
.radio-row .hint {
color: var(--text-3);
}
/* ---------- 归档详情:元数据条 / KV / 规格表 / 代码块 ---------- */
/* 归档元数据与完整度提示条:信息密度高但不抢图表注意力 */
.archive-bar {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
padding: 9px 12px;
margin: 0 0 10px;
border: 1px solid var(--line);
border-radius: var(--r-sm);
background: linear-gradient(180deg, var(--surface-2), var(--surface-1));
}
.kv-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: 8px 18px;
}
.kv {
display: flex;
align-items: baseline;
gap: 8px;
font-size: 13px;
min-width: 0;
}
.kv__k {
flex: none;
color: var(--text-3);
}
.kv__v {
color: var(--text-1);
word-break: break-all;
}
.kv__v--warn {
color: var(--warn);
}
/* 规格表:选股条件 / 交易执行依据 */
.spec-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 14px;
}
.spec-block {
border: 1px solid var(--line);
border-radius: var(--r-sm);
padding: 10px 12px;
background: var(--surface-1);
}
.spec-block__title {
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--text-2);
margin-bottom: 8px;
padding-bottom: 6px;
border-bottom: 1px dashed var(--line);
}
.spec-row {
display: flex;
gap: 10px;
align-items: baseline;
font-size: 13px;
padding: 3px 0;
line-height: 1.5;
}
.spec-row__k {
flex: none;
min-width: 96px;
color: var(--text-3);
}
.spec-row__v {
color: var(--text-1);
min-width: 0;
word-break: break-word;
}
.codeblock {
max-height: 420px;
overflow: auto;
font-size: 12px;
line-height: 1.55;
padding: 10px 12px;
border: 1px solid var(--line);
border-radius: var(--r-sm);
background: var(--bg-1);
white-space: pre-wrap;
word-break: break-word;
}
/* 行内代码:后端说明文本用 `…` 标记字段/命令名 */
.inline-code {
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
font-size: 0.92em;
padding: 1px 5px;
border: 1px solid var(--line);
border-radius: var(--r-xs);
background: var(--bg-2);
color: var(--text-1);
}