/* 标博士用户端 · 页面框架与共用小件 * 视觉来源:docs/design/system/tokens.css + components.css(main.ts 通过 @design 别名直接引用,不复制)。 * 本文件只放这两份文件里还没有的「页面框架层」:侧栏、顶栏、页头、项目头,以及原型 docs/prototype/assets/app.css * 第 1、2 部分里本应用用到的那几块(B1 图标、B3 按钮状态、B4 输入、B7 复选、B8 标签页、B11 对话框、B13 提示气泡、 * B14 空状态与进度、B15 杂项、B16 AI 块、B17 统计、B19 分类导航、B20 项目行),选择器和写法照搬原型。 * 只用 tokens.css 的变量,不写裸色值。 * TODO:这一层应提升到 docs/design/system(或 packages/ui)作为唯一来源,届时删掉本文件里的副本。 * 与原型的差别:原型把框架挂在 body.app 上,这里挂在 AppShell 的根元素 .app-frame 上。 */ /* ===================================================================== * 1. 页面框架:96px 墨色渐变侧栏 + 透明顶栏 + 内容区左上角圆角 * ===================================================================== */ .app-frame{display:grid;grid-template-columns:var(--rail-w) minmax(0,1fr);min-height:100vh;min-width:1280px;background:linear-gradient(180deg,var(--side-top) 0%,var(--side-bottom) 100%) fixed} .sidebar{position:sticky;top:0;height:100vh;display:flex;flex-direction:column;padding:0 0 var(--sp-4) 10px;color:var(--side-fg);min-width:0} .sidebar::after{content:"";position:absolute;left:0;right:0;bottom:0;height:260px;pointer-events:none;background-image:radial-gradient(var(--side-dots) 1px,transparent 1.2px);background-size:14px 14px;-webkit-mask-image:linear-gradient(to top,black,transparent);mask-image:linear-gradient(to top,black,transparent)} .sidebar > *{position:relative;z-index:1} .sidebar .brand{display:flex;justify-content:center;padding:var(--sp-5) 10px var(--sp-5) 0;border-radius:var(--radius-ctl)} .sidebar .brand img{width:60px;height:auto;display:block} .sidebar .nav{display:flex;flex-direction:column;gap:var(--sp-1)} .sidebar .nav a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--sp-1);height:62px;margin-right:10px;border-radius:var(--radius-card);font-size:var(--fs-caption);line-height:16px;color:var(--side-fg);text-align:center;text-decoration:none;white-space:nowrap;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} .sidebar .nav a .ic{width:22px;height:22px} .sidebar .nav a:hover{background:var(--side-hover);color:var(--side-fg-strong)} .sidebar .nav a[aria-current]{background:var(--side-active);color:var(--side-fg-strong);font-weight:var(--fw-bold)} .sidebar a:focus-visible,.sidebar button:focus-visible{outline:2px solid var(--side-fg-strong);outline-offset:-2px} .sidebar .me{display:flex;flex-direction:column;align-items:center;margin-top:auto;padding:var(--sp-4) 10px var(--sp-1) 0;color:var(--side-muted);text-align:center} .sidebar .avatar{display:inline-grid;place-items:center;width:44px;height:44px;margin-bottom:var(--sp-2);border-radius:50%;background:var(--side-avatar-bg);box-shadow:0 0 0 1px var(--side-avatar-ring);color:var(--side-fg-strong);font-size:var(--fs-title);font-weight:var(--fw-bold)} .sidebar .uname{color:var(--side-fg-strong);font-size:var(--fs-body);line-height:var(--lh-body);font-weight:var(--fw-bold);max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} /* 套餐标签:只在账户区使用;免费版中性、专业版浅金、企业版金色实底 */ .plan{display:inline-block;margin-top:var(--sp-1);padding:0 var(--sp-2);border-radius:var(--radius-xs);font-size:var(--fs-caption);line-height:20px;font-weight:var(--fw-bold);white-space:nowrap} .plan[data-plan="free"]{background:var(--plan-free-bg);color:var(--side-fg)} .plan[data-plan="pro"]{background:var(--plan-pro-bg);color:var(--plan-pro-fg);box-shadow:inset 0 0 0 1px var(--plan-pro-line)} .plan[data-plan="ent"]{background:var(--plan-ent-bg);color:var(--c-ink)} .main{min-width:0;display:flex;flex-direction:column;min-height:100vh;background:var(--bg-page);border-top-left-radius:var(--radius-card)} .top{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:var(--sp-3);height:var(--topbar-h);padding:0 var(--pad-page);background:transparent;border-bottom:var(--line) solid transparent;border-top-left-radius:var(--radius-card);transition:background-color var(--dur-base) var(--ease),border-color var(--dur-base) var(--ease)} .top.scrolled{background:var(--topbar-glass);-webkit-backdrop-filter:blur(14px) saturate(1.2);backdrop-filter:blur(14px) saturate(1.2);border-bottom-color:var(--border-subtle)} .crumbs{display:flex;align-items:center;gap:6px;min-width:0;color:var(--text-secondary);white-space:nowrap;overflow:hidden} .crumbs > *{min-width:0;overflow:hidden;text-overflow:ellipsis} .crumbs > span{flex:none} .crumbs .org{flex:none;color:var(--text-primary);font-weight:var(--fw-bold)} .crumbs a{color:var(--text-secondary);text-decoration:none} .crumbs a:hover{color:var(--text-primary);text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px} .crumbs b{font-weight:var(--fw-regular)} .top-right{display:flex;align-items:center;gap:var(--sp-1);flex:none;margin-left:auto} /* 顶栏额度:用到 ≥ 80% 时该项数字变赭色加粗(.warn);不用红 */ .quota-chip{display:inline-flex;align-items:center;gap:6px;height:var(--control-h-sm);padding:0 10px;border-radius:var(--radius-ctl);color:var(--text-secondary);text-decoration:none;white-space:nowrap} .quota-chip .q{display:inline-flex;align-items:baseline;gap:6px} .quota-chip .fig{color:var(--text-primary);font-size:var(--fs-figure-sm)} .quota-chip .q.warn .fig{color:var(--warn-text);font-weight:var(--fw-bold)} .quota-chip .sep{width:1px;height:12px;margin:0 var(--sp-1);background:var(--border-subtle)} .top .vsep{flex:none;width:1px;height:20px;margin:0 var(--sp-1);background:var(--border-subtle)} .content{width:100%;padding:20px var(--pad-page) var(--sp-12)} .content.narrow{max-width:800px} /* 页头:标题 + 一句话;右侧放页面级主按钮(Q12:页面级一个) */ .page-title{display:flex;align-items:flex-end;justify-content:space-between;gap:var(--sp-6);margin-bottom:20px} .page-title h1{font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);margin:0} .page-title p{margin:2px 0 0;color:var(--text-secondary)} .page-title .actions{margin-left:auto;display:flex;gap:var(--sp-3);align-items:center} .stack > * + *{margin-top:var(--gap-card)} /* 项目头(项目内页面):名称 + 信息一行;右侧截止、状态;下方标签页 */ .ph{padding:var(--sp-1) var(--pad-page) 0} .ph-l1{display:flex;align-items:flex-end;gap:var(--sp-8)} .ph-l1 > div{min-width:0} .ph h1{margin:0;font:var(--fw-bold) var(--fs-h1)/var(--lh-h1) var(--font-serif);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .ph-meta{margin:2px 0 0;color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .ph .facts{display:flex;gap:var(--sp-8);margin:0 0 0 auto;flex:none} .ph .facts > div{display:flex;flex-direction:column;gap:2px} .ph .facts dt{font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .ph .facts dd{display:flex;align-items:center;gap:6px;min-height:var(--lh-body);margin:0;white-space:nowrap} .ph .tabs{margin-top:var(--sp-3)} .ph + .content{padding-top:var(--sp-5)} /* ===================================================================== * 2. 共用小件 * ===================================================================== */ /* B1 图标(Lucide 线性,1.5px 描边,16 / 20px;侧栏菜单 22px) */ .ic{width:16px;height:16px;flex:none;fill:none;stroke:currentColor;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;vertical-align:-3px} .ic.lg{width:20px;height:20px} .ic.xl{width:32px;height:32px;stroke-width:1.25} .vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} /* B3 按钮补充状态:加载中两个文字叠在同一格,切换时宽度不变 */ .btn .ic{margin:0 -2px} .btn.swap{display:inline-grid;place-items:center} .btn.swap > span{grid-area:1/1;white-space:nowrap} .btn.swap > .busy,.btn.swap.is-loading > .idle{visibility:hidden} .btn.swap.is-loading > .busy{visibility:visible} .btn.is-loading{cursor:progress} .btn.primary.is-loading{background:var(--primary-hover);border-color:var(--primary-hover)} .icon-btn{position:relative;flex:none;display:inline-grid;place-items:center;width:var(--control-h);height:var(--control-h);padding:0;border:var(--line) solid transparent;border-radius:var(--radius-ctl);background:none;color:var(--text-primary);cursor:pointer;transition:background-color var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} .icon-btn:hover{background:var(--c-n100)} .icon-btn.sm{width:var(--control-h-sm);height:var(--control-h-sm)} /* B4 输入框 */ .input{width:100%;transition:border-color var(--dur-fast) var(--ease)} .input:hover{border-color:var(--c-ink)} .input:focus{outline:0;border-color:var(--c-ink);box-shadow:0 0 0 1px var(--c-ink)} .field.err .input:focus{border-color:var(--warn);box-shadow:0 0 0 1px var(--warn)} .input[disabled]{background:var(--c-n100);border-color:var(--c-n100);color:var(--text-disabled);cursor:not-allowed} .field label{display:flex;gap:var(--sp-1);align-items:baseline} .field label small{margin-left:2px} .field .choice{display:inline-flex;gap:var(--sp-2);align-items:flex-start;font-weight:var(--fw-regular)} .hint-warn{display:flex;gap:6px;align-items:flex-start;margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--warn-text)} .hint-warn .ico{flex:none;width:14px;height:14px;font-size:10px;margin:3px 0 0} .field + .field{margin-top:0} .search{position:relative;display:block} .search > .ic{position:absolute;left:11px;top:10px;color:var(--text-secondary);pointer-events:none} .search .input{padding-left:34px} /* B7 复选框 */ .ck{appearance:none;flex:none;display:inline-grid;place-content:center;width:16px;height:16px;margin:0;border:var(--line) solid var(--border-control);border-radius:var(--radius-xs);background:var(--bg-surface);cursor:pointer;vertical-align:-3px} .ck:hover{border-color:var(--c-ink)} .ck::after{content:"";display:none} .ck:checked{background:var(--c-ink);border-color:var(--c-ink)} .ck:checked::after{display:block;width:8px;height:4px;border:solid var(--c-white);border-width:0 0 1.75px 1.75px;transform:translateY(-1px) rotate(-45deg)} .choice{display:inline-flex;gap:var(--sp-2);align-items:flex-start;cursor:pointer;line-height:var(--lh-body)} .choice .ck{margin-top:3px} /* B8 标签页 */ .tabs{display:flex;gap:var(--sp-6);border-bottom:var(--line) solid var(--border-subtle)} .tabs [role=tab]{display:flex;gap:6px;align-items:center;margin-bottom:-1px;padding:var(--sp-2) 0 10px;border:0;border-bottom:2px solid transparent;background:none;font:var(--fs-body) var(--font-sans);color:var(--text-secondary);cursor:pointer;text-decoration:none;white-space:nowrap} .tabs [role=tab]:hover{color:var(--text-primary)} .tabs [role=tab][aria-selected="true"]{color:var(--text-primary);font-weight:var(--fw-bold);border-bottom-color:var(--c-ink)} /* B11 对话框(480 / 640,圆角 12;对话框里可以有自己的主按钮) */ .scrim{position:fixed;inset:0;z-index:50;display:grid;overflow:auto;padding:var(--sp-8) var(--sp-4);background:var(--scrim)} .dialog{position:relative;margin:auto;width:480px;max-width:100%;padding:var(--sp-6);background:var(--bg-surface);border-radius:var(--radius-card);box-shadow:var(--shadow-overlay)} .dialog h2{font:var(--fw-bold) var(--fs-title)/var(--lh-title) var(--font-sans);margin:0 var(--sp-8) var(--sp-3) 0} .dialog .body > p{margin:0 0 var(--sp-3)} .dialog .foot{display:flex;justify-content:flex-end;align-items:center;gap:var(--sp-2);margin-top:var(--sp-6)} /* B12 提醒补充 */ .alert > div{flex:1;min-width:0} .alert > p{flex:1} .alert .sub{display:block;margin-top:2px;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} .errno{font:var(--fw-medium) var(--fs-figure-xs)/1 var(--font-figures);color:var(--text-secondary);letter-spacing:.02em} /* B13 提示气泡(悬停 / 聚焦显示) */ [data-tip]{position:relative} [data-tip]:hover::after,[data-tip]:focus-visible::after{content:attr(data-tip);position:absolute;z-index:55;bottom:calc(100% + 8px);left:50%;transform:translateX(-50%);width:max-content;max-width:280px;padding:6px 10px;border-radius:var(--radius-ctl);background:var(--c-ink);color:var(--text-on-dark);font:var(--fw-regular) var(--fs-caption)/var(--lh-caption) var(--font-sans);white-space:normal;text-align:left;pointer-events:none} [data-tip].tip-below:hover::after,[data-tip].tip-below:focus-visible::after{bottom:auto;top:calc(100% + 8px)} [data-tip].tip-left:hover::after,[data-tip].tip-left:focus-visible::after{left:auto;right:0;transform:none} /* B14 空状态、骨架屏、进度(骨架屏静止,Q8) */ .empty{display:flex;flex-direction:column;align-items:center;text-align:center;padding:var(--sp-12) var(--sp-6)} .empty > .ic{color:var(--text-secondary);margin-bottom:var(--sp-3)} .empty b{font-size:var(--fs-title);line-height:var(--lh-title)} .empty p{margin:var(--sp-1) 0 var(--sp-5);color:var(--text-secondary);max-width:30em} .empty.sm{padding:var(--sp-8) var(--sp-4)} .empty.sm b{font-size:var(--fs-body);line-height:var(--lh-body)} .empty.sm p{margin-bottom:0} .empty.sm > .btn{margin-top:var(--sp-4)} .skel{display:block;height:12px;border-radius:var(--radius-xs);background:var(--c-n100)} .skel.t{height:16px;width:40%} .skel + .skel{margin-top:var(--sp-2)} .progress{display:flex;align-items:center;gap:10px} .progress .bar{flex:1;margin:0} .progress .fig{min-width:3em;text-align:right} .steps .dot{line-height:1;font-family:var(--font-sans)} .steps li > span:nth-child(2){flex:1} .steps .fail .dot{border-color:var(--warn);color:var(--warn-text);font-weight:var(--fw-bold)} .steps .fail{color:var(--warn-text)} /* B15 杂项小件 */ .kbd{display:inline-flex;align-items:center;justify-content:center;min-width:22px;height:22px;padding:0 6px;border-radius:var(--radius-tag);background:var(--c-n100);font:var(--fw-bold) var(--fs-caption)/1 var(--font-figures);color:var(--text-secondary);white-space:nowrap} .muted{color:var(--text-secondary)} .cap{font-size:var(--fs-caption);line-height:var(--lh-caption)} .fig.m{font-weight:var(--fw-medium)} .fig.xs{font-size:var(--fs-figure-xs)} :is(.hint-warn,.stat p,.quota p) .fig{font-size:var(--fs-figure-xs)} .kv{display:grid;grid-template-columns:96px minmax(0,1fr);gap:var(--sp-2) var(--sp-4);margin:0} .kv dt{color:var(--text-secondary)} .kv dd{margin:0} .drop{display:flex;flex-direction:column;align-items:center;gap:var(--sp-1);border:1.5px dashed var(--border-control);border-radius:var(--radius-card);background:var(--bg-surface);padding:var(--sp-8) var(--sp-6);text-align:center;cursor:pointer;transition:background-color var(--dur-fast) var(--ease),border-color var(--dur-fast) var(--ease)} .drop:hover,.drop.is-over{border-color:var(--c-ink);background:var(--c-n50)} .drop > .ic{color:var(--text-secondary)} .drop b{font-size:var(--fs-title);line-height:var(--lh-title)} .drop span{color:var(--text-secondary);font-size:var(--fs-caption);line-height:var(--lh-caption)} .drop[aria-disabled="true"]{pointer-events:none;color:var(--text-disabled);border-color:var(--border-subtle);background:var(--c-n50)} .file-row{display:grid;grid-template-columns:40px minmax(0,1fr);grid-auto-flow:column;grid-auto-columns:auto;gap:var(--sp-3);align-items:center;padding:var(--sp-3) var(--sp-4);background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card)} .file-row .ext{font:var(--fw-bold) 11px/1 var(--font-figures);background:var(--c-n100);border-radius:var(--radius-xs);padding:5px 0;text-align:center} .file-row b{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .file-row .acts{display:flex;gap:var(--sp-1);align-items:center} /* B16 AI 产出块:确认前「AI」小标签 + 左侧 2px 蓝铅笔虚线;正文始终墨色(Q2) */ .ai-block{border-left:2px dashed var(--ai);padding:var(--sp-1) 0 var(--sp-1) var(--sp-3);color:var(--text-primary)} /* B17 卡片头、统计 */ .card-h{display:flex;align-items:center;gap:var(--sp-3);margin-bottom:var(--sp-3)} .stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));background:var(--bg-surface);border:var(--line) solid var(--border-subtle);border-radius:var(--radius-card);overflow:hidden} .stat{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-width:0;padding:var(--sp-4) var(--card-x);border:0;border-left:var(--line) solid var(--border-subtle);background:none;font:inherit;color:inherit;text-align:left} .stat:first-child{border-left:0} .stat small{display:block;font-size:var(--fs-body);line-height:var(--lh-body);color:var(--text-secondary)} .stat .big{font:var(--fw-bold) var(--fs-figure)/var(--lh-figure) var(--font-figures);font-variant-numeric:tabular-nums} .stat .big.fatal{color:var(--sev-fatal)} .stat .big.due{color:var(--warn-text)} /* B19 列表卡片:左侧分类导航 + 列表;列表里的分组标题(.ai 是清单顶部的「待确认」分组) */ .cmp-list{display:grid;grid-template-columns:176px minmax(0,1fr);min-height:0;overflow:hidden} @media (max-width:1399px){.cmp-list{grid-template-columns:152px minmax(0,1fr)}} .cmp-list > .lst{position:relative;min-width:0} .cats{display:flex;flex-direction:column;gap:2px;border-right:var(--line) solid var(--border-subtle);padding:var(--sp-3) var(--sp-2)} .cats button{display:flex;flex:none;width:100%;align-items:center;justify-content:space-between;height:var(--control-h-sm);padding:0 10px;border:0;background:none;border-radius:var(--radius-ctl);font:var(--fs-body) var(--font-sans);color:var(--text-primary);cursor:pointer;text-align:left;transition:background-color var(--dur-fast) var(--ease)} .cats button:hover{background:var(--c-n100)} .cats button[aria-current="true"]{background:var(--c-n100);font-weight:var(--fw-bold)} .cats button .fig{font-size:var(--fs-figure-sm);font-weight:var(--fw-medium);color:var(--text-secondary)} .cats button.ai,.cats button.ai .fig{color:var(--ai)} .cats hr{width:100%;margin:var(--sp-2) 0;border:0;border-top:var(--line) solid var(--border-subtle)} .grp-h{display:flex;align-items:baseline;gap:var(--sp-2);padding:var(--sp-5) var(--card-x) var(--sp-2)} .grp-h b{font-weight:var(--fw-bold)} .grp-h .fig{font-weight:var(--fw-medium);color:var(--text-secondary)} .grp-h.ai b,.grp-h.ai .fig{color:var(--ai)} .grp-h p{margin:0;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} /* B20 项目行:栅格对齐的列表,整行可点 */ :root{--prow-cols:minmax(0,1fr) 120px 120px 64px 140px var(--control-h-sm);--prow-gap:var(--sp-4)} @media (max-width:1399px){:root{--prow-cols:minmax(0,1fr) 108px 108px 56px 132px var(--control-h-sm);--prow-gap:var(--sp-3)}} .prow-head,.prow{display:grid;grid-template-columns:var(--prow-cols);column-gap:var(--prow-gap);align-items:center;padding:0 var(--card-x)} .prow-head{padding-bottom:var(--sp-2);font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary)} :is(.prow-head,.prow) > .c{justify-self:center;text-align:center} .prows{list-style:none;margin:0;padding:0} .prows > .empty{border-top:var(--line) solid var(--border-subtle)} .prow{position:relative;min-height:var(--row-h);padding-top:10px;padding-bottom:10px;border-top:var(--line) solid var(--border-subtle);cursor:pointer;transition:background-color var(--dur-fast) var(--ease)} .prow:hover,.prow:focus-within{background:var(--bg-hover)} .prow:last-child{border-radius:0 0 var(--radius-card) var(--radius-card)} .prow:has(.pname:focus-visible){outline:2px solid var(--focus-ring);outline-offset:-2px} .prow .pt{min-width:0} .pname{display:block;font-weight:var(--fw-bold);color:var(--text-primary);text-decoration:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;outline:0} .prow .pname::after{content:"";position:absolute;inset:0} .psub{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .prow .dl b{display:block;font-weight:var(--fw-regular);white-space:nowrap} .prow .dl b.due-today{font-weight:var(--fw-bold)} .prow .dl small{display:block;font-size:var(--fs-caption);line-height:var(--lh-caption);color:var(--text-secondary);white-space:nowrap} .prow .st{display:flex;align-items:center;gap:var(--sp-2);white-space:nowrap} .prow .st .hint-u{position:relative;z-index:1} .dash{color:var(--text-secondary)} .nx{position:relative;z-index:1;display:inline-flex;align-items:center;gap:var(--sp-1);height:var(--control-h-sm);padding:0;border:0;background:none;font:inherit;color:var(--text-primary);white-space:nowrap;cursor:pointer;text-decoration:none} .nx span{text-decoration:underline;text-decoration-color:var(--link-underline);text-underline-offset:3px;transition:text-decoration-color var(--dur-fast) var(--ease)} .nx:hover span{text-decoration-color:var(--c-ink)} .nx .ic{color:var(--text-secondary);transition:transform var(--dur-fast) var(--ease),color var(--dur-fast) var(--ease)} .prow:hover .nx .ic,.prow:focus-within .nx .ic{transform:translateX(3px);color:var(--text-primary)}