Files
smart-fin-chart/css/style.css
T
2026-09-24 17:14:03 +08:00

536 lines
13 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.
:root {
--ink: #333;
--muted: #8c8c8c;
--line: #e8e8e8;
--bg: #f5f4f1;
--surface: #fff;
--accent: #6b4a2b;
--accent-soft: #f0e9e1;
}
* { box-sizing: border-box; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font-family: "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}
button {
font: inherit;
font-size: 13px;
color: var(--ink);
cursor: pointer;
}
.card-title,
.sidebar-title { margin: 0; }
.reload-button,
.export-button,
.cart-button,
.cart-export-button,
.primary-button {
padding: 7px 13px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--surface);
transition: border-color .15s, color .15s, background .15s;
}
.reload-button {
position: fixed;
top: 16px;
right: 18px;
z-index: 10;
width: 36px;
height: 36px;
padding: 0;
display: grid;
place-items: center;
color: #fff;
background: var(--accent);
border-color: var(--accent);
border-radius: 50%;
}
/* 图标继承按钮的 color(currentColor),尺寸只在这里定义,换图标不用动 JS。
20px 是配 36px 圆钮的光学最优值:新图标的弧线比原 ↻ 字形细,19px 会显轻。 */
.reload-button .reload-icon {
display: block;
width: 20px;
height: 20px;
fill: currentColor;
}
/* 悬停转一圈:刷新语义的标准反馈,比原先 ↻ 字形的 -45° 倾斜更贴合新图标 */
.reload-button:hover { opacity: .88; }
.reload-button:hover .reload-icon { animation: reload-spin .6s ease-in-out; }
@keyframes reload-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
.reload-button:hover .reload-icon { animation: none; }
}
.export-button:hover { color: var(--accent); border-color: var(--accent); }
/* ── 合并队列(购物车)────────────────────────────── */
.cart-button { font-size: 12px; }
.cart-button:hover { color: var(--accent); border-color: var(--accent); }
.cart-button[data-in-cart="true"] {
color: #fff;
background: var(--accent);
border-color: var(--accent);
}
.cart-bar[hidden] { display: none; }
.cart-bar {
position: fixed;
left: 50%;
bottom: 18px;
transform: translateX(-50%);
z-index: 20;
display: flex;
align-items: center;
gap: 10px;
max-width: min(1160px, calc(100vw - 32px));
padding: 9px 12px;
background: rgba(255, 255, 255, .97);
border: 1px solid var(--line);
border-radius: 12px;
box-shadow: 0 6px 24px rgba(0, 0, 0, .12);
}
.cart-label {
color: var(--muted);
font-size: 12px;
font-weight: 600;
white-space: nowrap;
}
.cart-chips {
display: flex;
gap: 6px;
max-width: 44vw;
overflow-x: auto;
scrollbar-width: none;
}
.cart-chips::-webkit-scrollbar { display: none; }
.cart-chip {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 2px;
padding: 3px 4px 3px 10px;
background: var(--accent-soft);
border-radius: 6px;
}
.cart-chip-name {
max-width: 150px;
overflow: hidden;
color: var(--ink);
font-size: 12px;
text-overflow: ellipsis;
white-space: nowrap;
}
.cart-chip-button {
padding: 2px 4px;
border: 0;
border-radius: 4px;
background: transparent;
color: var(--muted);
font-size: 12px;
line-height: 1.4;
}
.cart-chip-button:hover:not(:disabled) { color: var(--accent); }
.cart-chip-button:disabled { opacity: .3; cursor: default; }
.cart-divider { width: 1px; height: 22px; background: var(--line); }
.cart-modes {
display: flex;
flex: 0 0 auto;
padding: 2px;
background: #f4f2ee;
border-radius: 6px;
}
.cart-mode-button {
padding: 4px 8px;
border: 0;
border-radius: 5px;
background: transparent;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.cart-mode-button[data-active="true"] {
color: var(--accent);
background: var(--surface);
box-shadow: 0 1px 2px rgba(0, 0, 0, .08);
}
.cart-export-button { color: #fff; background: var(--accent); border-color: var(--accent); white-space: nowrap; }
.cart-export-button:hover { opacity: .9; }
.cart-clear-button { padding: 7px 8px; border: 0; background: transparent; color: var(--muted); font-size: 12px; white-space: nowrap; }
.cart-clear-button:hover { color: #a63f32; }
/* ── 合并预览弹窗 ────────────────────────────────── */
.modal-overlay {
position: fixed;
inset: 0;
z-index: 30;
display: grid;
place-items: center;
padding: 24px;
background: rgba(51, 51, 51, .45);
}
.modal {
display: flex;
flex-direction: column;
max-width: min(860px, 100%);
max-height: calc(100vh - 48px);
background: var(--surface);
border-radius: 12px;
overflow: hidden;
}
.modal-head {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
border-bottom: 1px solid var(--line);
}
.modal-title { margin: 0; font-size: 14px; }
.modal-notice { color: #a67c2b; font-size: 12px; }
.modal-close {
margin-left: auto;
padding: 2px 8px;
border: 0;
background: transparent;
color: var(--muted);
font-size: 16px;
}
.modal-close:hover { color: var(--ink); }
.modal-body {
display: grid;
gap: 20px;
overflow-y: auto;
padding: 16px;
}
.preview-item {
display: grid;
gap: 10px;
justify-items: center;
}
.preview-canvas {
max-width: 100%;
height: auto;
border: 1px solid var(--line);
border-radius: 6px;
}
.modal-foot {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 12px 16px;
border-top: 1px solid var(--line);
}
.primary-button { color: #fff; background: var(--accent); border-color: var(--accent); }
.primary-button:hover { opacity: .9; }
.app-layout {
display: grid;
grid-template-columns: 184px minmax(0, 1fr) 184px;
gap: 24px;
max-width: 1600px;
margin: 0 auto;
padding: 20px 24px 64px;
}
.sidebar {
align-self: start;
position: sticky;
top: 20px;
padding: 14px 10px;
background: rgba(255, 255, 255, .58);
border: 1px solid rgba(232, 232, 232, .9);
border-radius: 10px;
}
.sidebar-title {
padding: 0 8px 10px;
color: var(--muted);
font-size: 12px;
font-weight: 600;
}
.company-list,
.anchor-list { display: grid; gap: 3px; }
.company-button,
.anchor-button,
.tab-button,
.view-button {
border: 0;
border-radius: 6px;
background: transparent;
text-align: left;
transition: background .15s, color .15s;
}
.company-button {
width: 100%;
padding: 8px;
}
.company-button:hover,
.anchor-button:hover,
.tab-button:hover { color: var(--accent); background: var(--accent-soft); }
.company-button[data-active="true"],
.tab-button[data-active="true"] {
color: var(--accent);
font-weight: 600;
background: var(--accent-soft);
}
.anchor-button {
padding: 6px 8px;
color: #5f5f5f;
font-size: 12px;
line-height: 1.45;
}
.content { min-width: 0; }
/* ── 页头:tab 栏 + 参数行 ─────────────────────────
sticky 只挂在容器上,里面两行一起钉住。底色比过去(.6)更实、并加一层模糊:
现在压在卡片上方的是两行内容,透过去的字会干扰读数。 */
.tab-header {
position: sticky;
top: 20px;
z-index: 5;
margin-bottom: 18px;
padding: 5px;
background: rgba(255, 255, 255, .82);
border: 1px solid var(--line);
border-radius: 9px;
backdrop-filter: blur(10px);
}
.tabs {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.tab-button { padding: 7px 11px; }
/* ── tab 级参数控件(现价) ─────────────────────────
独立一行,贴在 tab 栏下沿;一条虚线分隔说明「这不是导航,是改口径的输入」。
没有参数的 tab 会被 JS 置 hidden,此时整行(含分隔线)不占位。 */
.tab-controls {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
margin-top: 5px;
padding: 6px 3px 1px;
border-top: 1px dashed var(--line);
}
/* hidden 属性会被上面的 display:flex 盖掉,显式收回来 */
.tab-controls[hidden] { display: none; }
.param-field {
display: flex;
align-items: baseline;
gap: 5px;
padding: 4px 8px;
background: #f4f2ee;
border: 1px solid transparent;
border-radius: 6px;
transition: border-color .15s, background .15s;
}
.param-field:hover { border-color: var(--line); }
.param-field:focus-within { background: var(--surface); border-color: var(--accent); }
.param-label { color: var(--muted); font-size: 12px; white-space: nowrap; }
/* 输入框只留一条下划线:数字是这里的主角,不要给它加边框抢视线 */
.param-input {
width: 94px;
padding: 1px 2px;
border: 0;
border-bottom: 1px solid var(--line);
background: transparent;
color: var(--ink);
font: inherit;
font-size: 13px;
font-variant-numeric: tabular-nums;
font-weight: 600;
text-align: right;
}
.param-input:focus { outline: none; border-bottom-color: var(--accent); }
.param-input[data-invalid="true"] { color: #a63f32; border-bottom-color: #a63f32; }
/* 上下箭头在 Chrome 里只在悬停/聚焦时出现,保留(步进 1 元比手敲快) */
.param-input::-webkit-inner-spin-button { opacity: .45; }
.param-unit { color: var(--muted); font-size: 12px; }
/* 合法区间常驻:输入越界时它变红,不用猜上下限 */
.param-range { color: var(--muted); font-size: 11px; white-space: nowrap; }
.param-input[data-invalid="true"] ~ .param-range { color: #a63f32; }
.param-reset {
padding: 5px 8px;
border: 1px solid transparent;
border-radius: 6px;
background: transparent;
color: var(--muted);
font-size: 12px;
white-space: nowrap;
}
.param-reset:hover:not(:disabled) { color: var(--accent); border-color: var(--line); }
.param-reset:disabled { opacity: .35; cursor: default; }
.cards { display: grid; gap: 28px; }
/* scroll-margin-top = 页头底边(sticky top 20 + 页头实测高 89)+ 18px 呼吸:
页头是 tab 栏 + 参数行两行,比过去的单行 tab 栏高约 43px,锚点跳转时标题才不会被盖住。
页头高度会随 tab 换行浮动,这里按单行 tab 的最常见形态取值。 */
.card {
overflow: hidden;
background: var(--surface);
border-radius: 10px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .06);
scroll-margin-top: 128px;
}
.card-bar {
display: flex;
align-items: center;
gap: 12px;
min-height: 52px;
padding: 9px 16px;
border-bottom: 1px solid var(--line);
}
.card-heading { min-width: 0; margin-right: auto; }
.card-title { font-size: 14px; font-weight: 600; }
.src {
display: block;
overflow: hidden;
margin-top: 2px;
color: var(--muted);
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 11px;
text-overflow: ellipsis;
white-space: nowrap;
}
.view-switcher {
display: flex;
padding: 2px;
background: #f4f2ee;
border-radius: 6px;
}
.view-button { padding: 4px 7px; color: var(--muted); font-size: 12px; }
.view-button[data-active="true"] { color: var(--accent); background: var(--surface); box-shadow: 0 1px 2px rgba(0, 0, 0, .08); }
.chart { width: 100%; }
.chart-error,
.empty-state,
.sidebar-empty {
margin: 0;
padding: 36px 20px;
color: #a63f32;
font-size: 14px;
text-align: center;
}
.sidebar-empty { padding: 8px; color: var(--muted); text-align: left; font-size: 12px; }
@media (max-width: 1040px) {
.app-layout { grid-template-columns: 160px minmax(0, 1fr); }
.chart-sidebar { display: none; }
}
@media (max-width: 880px) {
.app-layout { display: block; padding: 0 12px 40px; }
.sidebar { margin-bottom: 12px; }
.company-sidebar {
position: sticky;
top: 0;
z-index: 7;
margin: 0 -12px;
padding: 7px 12px;
border-width: 0 0 1px;
border-radius: 0;
background: rgba(245, 244, 241, .96);
backdrop-filter: blur(10px);
}
.company-sidebar .sidebar-title { display: none; }
.company-list { display: flex; overflow-x: auto; }
/* 隐藏横向滚动条(与 .tabs 同处理):classic 滚动条会占掉 15px 高度,
把贴在公司栏下方的页头压住 —— 而页头的 sticky top 是写死的 50px。 */
.company-list { scrollbar-width: none; }
.company-list::-webkit-scrollbar { display: none; }
.company-button { flex: 0 0 auto; width: auto; }
.tab-header {
top: 50px;
z-index: 6;
margin: 0 -12px 18px;
padding: 5px 12px 7px;
border-width: 0 0 1px;
border-radius: 0;
background: rgba(245, 244, 241, .96);
}
/* tab 按钮多时横向滚动 —— sticky 容器不能自己滚(会把参数行一起带走),
所以滚动只加在 .tabs 上,并用负 margin 抵消容器横向 padding 让滚动区撑到屏幕边 */
.tabs {
overflow-x: auto;
overflow-y: hidden;
flex-wrap: nowrap;
margin: 0 -12px;
padding: 0 12px;
scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }
.tab-button { flex: 0 0 auto; }
/* 参数行自成一行,不再跟 tab 栏抢横向空间:窄屏直接换行,分隔线通到屏幕两边 */
.tab-controls { margin: 5px -12px 0; padding: 7px 12px 1px; }
/* 公司栏 50 + 页头 91(tab 栏 33 + 参数行 41 及间距)+ 18px 呼吸 */
.card { scroll-margin-top: 160px; }
.card-bar { align-items: flex-start; flex-wrap: wrap; }
.cart-button { margin-left: auto; }
.cart-bar { left: 12px; right: 12px; transform: none; flex-wrap: wrap; }
.cart-chips { max-width: none; }
.chart { min-width: 560px; }
.card { overflow-x: auto; }
}