Files
verdaccio-cache-manager/doc/bright/design-mockup-v2.html
T
陈银军 338c1a0a95 docs: 需求文档合并、设计稿与设计规范
- 合并两份需求文档为完整版(功能需求 + 前端界面设计)
- 新增 bright/dark 双主题设计稿(7 画板 + 登录页效果图)
- 输出设计规范 design-spec.md(token/页面/组件/响应式/动效/部署)
- 需求文档补充缓存目录配置(3.7)与两种部署方式(9.1/9.2)
2026-09-19 14:30:21 +08:00

1415 lines
77 KiB
HTML
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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Verdaccio 缓存可视化管理面板 — 多巴胺效果图</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=ZCOOL+QingKe+HuangYou&display=swap" rel="stylesheet">
<style>
:root{
--green:#32CD32;
--green-soft:#E9F9E9;
--orange:#FF8C00;
--orange-soft:#FFF3E2;
--red:#FF4500;
--red-soft:#FFE9E2;
--blue:#1E90FF;
--blue-soft:#E6F2FF;
--ink:#2B2B33;
--ink-dim:#8A8A94;
--ink-faint:#B9B9C2;
--line:#F1E7DA;
--card:#FFFFFF;
--bg:#FFF8EF;
--r-lg:22px;
--r-md:16px;
--shadow:0 10px 30px -12px rgba(180,120,60,.18), 0 2px 8px -2px rgba(180,120,60,.08);
--shadow-hover:0 18px 44px -14px rgba(180,120,60,.30), 0 4px 14px -4px rgba(180,120,60,.12);
--num:'Fredoka','PingFang SC',sans-serif;
--sans:'PingFang SC','Hiragino Sans GB','Microsoft YaHei',sans-serif;
--display:'ZCOOL QingKe HuangYou','PingFang SC',sans-serif;
}
*{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth}
body{
background:var(--bg);color:var(--ink);font-family:var(--sans);
-webkit-font-smoothing:antialiased;padding-bottom:120px;
}
/* 暖色背景氛围 */
.ambient{
position:fixed;inset:0;z-index:-1;pointer-events:none;
background:
radial-gradient(900px 460px at 82% -8%, rgba(255,140,0,.10), transparent 58%),
radial-gradient(760px 400px at -4% 6%, rgba(50,205,50,.07), transparent 55%),
radial-gradient(700px 420px at 55% 110%, rgba(30,144,255,.07), transparent 60%);
}
.ambient::after{
content:"";position:absolute;inset:0;opacity:.5;
background-image:radial-gradient(rgba(140,90,30,.05) 1px, transparent 1px);
background-size:22px 22px;
}
/* ===== 设计稿画板外壳 ===== */
.board{max-width:1480px;margin:0 auto;padding:56px 44px 0}
.board-head{
display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:28px;
}
.board-head h1{
font-size:30px;font-weight:700;letter-spacing:-.5px;color:var(--ink);
display:flex;align-items:center;gap:14px;
}
.board-head h1 .num{
font-family:var(--num);font-size:14px;font-weight:600;color:var(--orange);
background:var(--orange-soft);border:1.5px solid #FFC98F;
padding:5px 12px;border-radius:99px;
}
.board-head p{color:var(--ink-dim);font-size:13.5px;margin-top:9px;font-weight:400}
.board-head .meta{font-family:var(--num);font-size:12px;color:var(--ink-faint);text-align:right;line-height:1.7}
.board-head .meta b{color:var(--ink-dim);font-weight:600}
.frame-wrap{position:relative;margin-bottom:74px}
.frame{
position:relative;
border:1px solid var(--line);
border-radius:26px;
background:#fffdf9;
overflow:hidden;
box-shadow:0 30px 70px -30px rgba(180,110,40,.25);
}
.frame-tag{
position:absolute;top:-14px;left:28px;z-index:5;
font-family:var(--num);font-size:12px;font-weight:600;letter-spacing:.4px;
color:#fff;background:linear-gradient(120deg,#FF8C00,#FF5E1A);
border-radius:99px;padding:6px 16px;
box-shadow:0 8px 20px -6px rgba(255,120,0,.55);
}
/* ===== 应用主体 ===== */
.app{display:flex;flex-direction:column;min-height:600px}
.topbar{
display:flex;align-items:center;justify-content:space-between;gap:24px;
height:64px;padding:0 28px;
background:#fff;border-bottom:1px solid var(--line);
}
.brand{display:flex;align-items:center;gap:11px;cursor:pointer}
.brand .logo-emoji{
width:38px;height:38px;border-radius:13px;display:grid;place-items:center;font-size:20px;
background:linear-gradient(135deg,#FFB347,#FF8C00);
box-shadow:0 8px 18px -6px rgba(255,140,0,.55);
}
.brand-name{font-size:16.5px;font-weight:700;color:var(--orange);letter-spacing:-.2px}
.brand-sub{font-size:11px;color:var(--ink-faint);margin-top:1px}
.nav{display:flex;align-items:center;gap:6px;flex:1;justify-content:center}
.nav-item{
display:inline-flex;align-items:center;gap:7px;
padding:9px 18px;border-radius:99px;
font-size:13.5px;font-weight:600;color:var(--ink-dim);
cursor:pointer;transition:.18s;white-space:nowrap;
}
.nav-item:hover{background:#FFF4E6;color:var(--orange)}
.nav-item.active{
background:linear-gradient(120deg,#FFB347,#FF8C00);color:#fff;
box-shadow:0 8px 18px -6px rgba(255,140,0,.5);
}
.topbar-right{display:flex;align-items:center;gap:14px}
.avatar{
width:34px;height:34px;border-radius:50%;
background:linear-gradient(135deg,#7CC6FF,#1E90FF);
color:#fff;display:grid;place-items:center;font-size:14px;
box-shadow:0 6px 14px -4px rgba(30,144,255,.5);
cursor:pointer;
}
.content{padding:26px 28px 40px}
/* ===== 统计卡 第一行 4 个 ===== */
.stats-1{display:grid;grid-template-columns:repeat(4,1fr);gap:18px;margin-bottom:18px}
.stat1{
position:relative;background:var(--card);border:1.5px solid var(--line);
border-radius:var(--r-lg);padding:20px 22px;box-shadow:var(--shadow);
animation:fadeUp .5s both;overflow:hidden;
}
.stat1:nth-child(1){animation-delay:.03s}
.stat1:nth-child(2){animation-delay:.09s}
.stat1:nth-child(3){animation-delay:.15s}
.stat1:nth-child(4){animation-delay:.21s}
.stat1:hover{transform:translateY(-3px);box-shadow:var(--shadow-hover)}
.stat1::after{
content:"";position:absolute;right:-34px;top:-34px;width:120px;height:120px;border-radius:50%;
background:var(--tone-soft);opacity:.7;
}
.stat1 .top{display:flex;align-items:center;justify-content:space-between}
.stat1 .ico{
width:42px;height:42px;border-radius:13px;display:grid;place-items:center;font-size:20px;
background:var(--tone-soft);
}
.stat1 .label{font-size:13px;color:var(--ink-dim);font-weight:600}
.stat1 .value{
font-family:var(--num);font-size:32px;font-weight:700;letter-spacing:-.5px;
color:var(--tone);line-height:1.05;margin-top:12px;
display:flex;align-items:baseline;gap:6px;
}
.stat1 .value .unit{font-size:13px;color:var(--ink-faint);font-weight:500}
.stat1.t-green{--tone:var(--green);--tone-soft:var(--green-soft)}
.stat1.t-orange{--tone:var(--orange);--tone-soft:var(--orange-soft)}
.stat1.t-red{--tone:var(--red);--tone-soft:var(--red-soft)}
.stat1.t-blue{--tone:var(--blue);--tone-soft:var(--blue-soft)}
/* 预警卡呼吸光晕 */
.stat1.t-red{border-color:#FFB89B;animation:fadeUp .5s both, breathe 2.4s ease-in-out infinite}
.stat1 .cta{
display:inline-flex;align-items:center;gap:6px;
background:linear-gradient(120deg,#FFB347,#FF8C00);color:#fff;
font-size:12.5px;font-weight:600;padding:8px 15px;border-radius:99px;cursor:pointer;
box-shadow:0 8px 16px -6px rgba(255,140,0,.5);transition:.18s;position:relative;z-index:2;
}
.stat1 .cta:hover{transform:translateY(-1px);box-shadow:0 12px 22px -6px rgba(255,140,0,.6)}
/* ===== 统计卡 第二行 5 个 ===== */
.stats-2{display:grid;grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:24px}
.stat2{
background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-md);
padding:15px 17px;box-shadow:var(--shadow);cursor:pointer;
animation:fadeUp .5s both;transition:.18s;
}
.stat2:nth-child(1){animation-delay:.27s}.stat2:nth-child(2){animation-delay:.33s}
.stat2:nth-child(3){animation-delay:.39s}.stat2:nth-child(4){animation-delay:.45s}
.stat2:nth-child(5){animation-delay:.51s}
.stat2:hover{transform:translateY(-3px);box-shadow:var(--shadow-hover)}
.stat2 .s2-top{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--ink-dim);font-weight:600}
.stat2 .s2-top .ico2{font-size:16px}
.stat2 .s2-val{
font-family:var(--num);font-size:20px;font-weight:700;margin-top:9px;color:var(--ink);
display:flex;align-items:baseline;gap:5px;min-width:0;
}
.stat2 .s2-val .main{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.stat2 .s2-val .u{font-size:11.5px;color:var(--ink-faint);font-weight:500}
.stat2 .s2-sub{font-family:var(--num);font-size:11px;color:var(--ink-faint);margin-top:4px}
.stat2 .link{
font-size:11.5px;font-weight:600;padding:4px 11px;border-radius:99px;margin-left:auto;
background:var(--blue-soft);color:var(--blue);cursor:pointer;
}
.stat2.c-blue{border-top:3px solid var(--blue)}
.stat2.c-green{border-top:3px solid var(--green)}
.stat2.c-orange{border-top:3px solid var(--orange)}
.stat2.c-purple{border-top:3px solid #9B5DE5}
.stat2.c-pink{border-top:3px solid #FF5E8E}
.stat2.c-blue .s2-val{color:var(--blue)} .stat2.c-green .s2-val{color:#22A622}
.stat2.c-orange .s2-val{color:var(--orange)} .stat2.c-purple .s2-val{color:#9B5DE5}
.stat2.c-pink .s2-val{color:#FF5E8E}
/* ===== Top 30 排行 ===== */
.top30{
background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-lg);
box-shadow:var(--shadow);overflow:hidden;margin-bottom:26px;
animation:fadeUp .55s .55s both;
}
.top30-head{
display:flex;align-items:center;justify-content:space-between;
padding:17px 22px;border-bottom:1px dashed var(--line);
}
.top30-head .t{display:flex;align-items:center;gap:11px;font-size:15px;font-weight:700}
.top30-head .t .ico{font-size:19px}
.top30-head .tag30{
font-family:var(--num);font-size:11.5px;font-weight:600;color:var(--blue);
background:var(--blue-soft);padding:5px 13px;border-radius:99px;
}
table{width:100%;border-collapse:collapse}
.rank-tbl th{
font-size:11px;font-weight:600;letter-spacing:1px;color:var(--ink-faint);
text-align:left;padding:11px 22px;border-bottom:1px solid var(--line);
background:#FFFDF8;white-space:nowrap;
}
.rank-tbl td{
padding:11px 22px;font-size:13.5px;border-bottom:1px solid #FBF4E9;vertical-align:middle;
}
.rank-tbl tr:last-child td{border-bottom:none}
.rank-tbl tr{transition:background .15s}
.rank-tbl tr:hover{background:#FFF8EF}
.rank-no{
width:30px;height:30px;border-radius:10px;display:inline-grid;place-items:center;
font-family:var(--num);font-weight:700;font-size:14px;
background:#F4F4F6;color:var(--ink-dim);
}
.rank-no.gold{background:linear-gradient(135deg,#FFE082,#FFC107);color:#7A5B00;box-shadow:0 4px 10px -3px rgba(255,193,7,.5)}
.rank-no.silver{background:linear-gradient(135deg,#E8E8EC,#C9C9CF);color:#5A5A62;box-shadow:0 4px 10px -3px rgba(200,200,210,.5)}
.rank-no.bronze{background:linear-gradient(135deg,#F2C9A0,#D9A066);color:#6E4520;box-shadow:0 4px 10px -3px rgba(217,160,102,.5)}
.rank-pkg{display:flex;align-items:center;gap:10px;font-weight:600}
.rank-pkg .mini-ico{
width:30px;height:30px;border-radius:9px;background:var(--orange-soft);
display:grid;place-items:center;font-size:14px;
}
.rank-pkg .scope{color:var(--ink-faint);font-weight:500}
.rank-visit{font-family:var(--num);font-weight:700;font-size:14.5px;color:var(--ink)}
.heat{display:flex;align-items:center;gap:12px;min-width:180px}
.heat-bar{flex:1;height:8px;border-radius:99px;background:#F4F0E8;overflow:hidden}
.heat-bar i{
display:block;height:100%;border-radius:99px;
background:linear-gradient(90deg,#FFB347,#FF8C00);
animation:grow .9s cubic-bezier(.25,.9,.35,1) both;transform-origin:left;
}
.heat-pct{font-family:var(--num);font-size:11.5px;color:var(--ink-faint);font-weight:600;width:34px}
/* ===== 包卡片列表 ===== */
.sec-head{display:flex;align-items:center;justify-content:space-between;margin:4px 2px 16px}
.sec-title{font-size:17px;font-weight:700;display:flex;align-items:center;gap:10px}
.sec-title .emo{font-size:19px}
.sec-sub{font-family:var(--num);font-size:12px;color:var(--ink-faint);font-weight:600}
.pkg-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px}
.pkg-card{
position:relative;background:var(--card);border:1.5px solid var(--line);
border-radius:var(--r-lg);padding:18px 20px;box-shadow:var(--shadow);
transition:.2s;animation:fadeUp .5s both;overflow:hidden;
display:flex;flex-direction:column;min-height:212px;
}
.pkg-card:hover{transform:translateY(-4px);box-shadow:var(--shadow-hover)}
.pkg-card .p-ops{margin-top:auto}
.pkg-card.warn{
border-color:#FFA17A;
animation:fadeUp .5s both, breathe 2.6s ease-in-out infinite;
}
.pkg-card.ignored{background:#FBFBFD;opacity:.82}
.pkg-card .p-top{display:flex;align-items:center;gap:12px}
.pkg-card .pkg-ico{
width:44px;height:44px;border-radius:14px;display:grid;place-items:center;font-size:20px;
background:linear-gradient(135deg,#FFE9C7,#FFD9A0);
box-shadow:inset 0 0 0 1.5px rgba(255,140,0,.18);
}
.pkg-card .pkg-name{font-size:15px;font-weight:700;letter-spacing:-.2px;display:flex;align-items:center;gap:8px;min-width:0}
.pkg-card .pkg-name .nm{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0}
.pkg-card .pkg-name .scope{color:var(--ink-faint);font-weight:500}
.pkg-card .p-meta{display:flex;gap:18px;margin:14px 0 15px;padding:11px 0;border-top:1px dashed var(--line);border-bottom:1px dashed var(--line)}
.pkg-card .m-item .m-v{font-family:var(--num);font-size:17px;font-weight:700}
.pkg-card .m-item .m-l{font-size:11px;color:var(--ink-faint);margin-top:2px}
.pkg-card.warn .m-item .m-v{color:var(--red)}
.pkg-card .p-ops{display:flex;align-items:center;gap:9px}
.btn{
display:inline-flex;align-items:center;gap:6px;border:none;cursor:pointer;
font-family:var(--sans);font-size:12.5px;font-weight:600;padding:8px 15px;border-radius:99px;
transition:.18s;white-space:nowrap;
}
.btn.clean{
background:linear-gradient(120deg,#FFB347,#FF8C00);color:#fff;
box-shadow:0 8px 16px -6px rgba(255,140,0,.55);
}
.btn.clean:hover{transform:translateY(-1px);box-shadow:0 12px 22px -6px rgba(255,140,0,.65)}
.btn.ignore{background:var(--blue-soft);color:var(--blue)}
.btn.ignore:hover{background:#D8ECFF}
.btn.unignore{background:#F4F4F6;color:var(--ink-dim)}
.btn.unignore:hover{color:var(--ink)}
.btn.disabled{background:#F4F4F6;color:var(--ink-faint);cursor:not-allowed}
.tag{
display:inline-flex;align-items:center;gap:5px;font-size:11.5px;font-weight:700;
padding:5px 12px;border-radius:99px;white-space:nowrap;
}
.tag.warn{background:var(--red-soft);color:var(--red)}
.tag.ok{background:var(--green-soft);color:#1F9E1F}
.tag.ign{background:var(--blue-soft);color:var(--blue)}
.tag.broken{background:#FFF1D6;color:#C77B00}
/* ===== 检索页 ===== */
.search-hero{padding:34px 0 26px;display:flex;flex-direction:column;align-items:center}
.search-box{
display:flex;align-items:center;gap:12px;width:min(680px,92%);
height:52px;background:#fff;border:2px solid var(--line);
border-radius:99px;padding:0 10px 0 22px;
box-shadow:0 14px 34px -14px rgba(180,120,60,.25);
transition:.2s;
}
.search-box:focus-within{border-color:var(--orange);box-shadow:0 0 0 4px rgba(255,140,0,.14), 0 14px 34px -14px rgba(255,140,0,.3)}
.search-box .s-ico{font-size:18px;color:var(--ink-faint)}
.search-box input{
flex:1;border:none;outline:none;font-size:14.5px;font-family:var(--sans);color:var(--ink);background:transparent;
}
.search-box input::placeholder{color:var(--ink-faint)}
.search-box .s-go{
width:auto;display:inline-flex;align-items:center;gap:7px;height:38px;padding:0 22px;
border:none;border-radius:99px;cursor:pointer;
background:linear-gradient(120deg,#FFB347,#FF8C00);color:#fff;
font-size:13.5px;font-weight:600;font-family:var(--sans);
box-shadow:0 8px 18px -6px rgba(255,140,0,.6);transition:.18s;
}
.search-box .s-go:hover{transform:translateY(-1px)}
.filter-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;justify-content:center;margin-top:20px}
.pill{
font-size:12.5px;font-weight:600;padding:8px 17px;border-radius:99px;cursor:pointer;
background:#fff;border:1.5px solid var(--line);color:var(--ink-dim);transition:.16s;
}
.pill:hover{border-color:#FFC98F;color:var(--orange)}
.pill.on{background:var(--green);border-color:var(--green);color:#fff;box-shadow:0 6px 14px -4px rgba(50,205,50,.5)}
.pill.split{background:transparent;border:none;color:var(--ink-faint);cursor:default}
.result-note{
text-align:center;font-family:var(--num);font-size:12.5px;color:var(--ink-faint);
margin:22px 0 14px;font-weight:600;
}
.result-note b{color:var(--orange)}
.vtable{
background:#fff;border:1.5px solid var(--line);border-radius:var(--r-lg);
box-shadow:var(--shadow);overflow:hidden;
}
.vtable th{
font-size:11px;font-weight:600;letter-spacing:1px;color:var(--ink-faint);text-align:left;
padding:13px 20px;border-bottom:1px solid #F0F0F0;background:#FFFDF8;white-space:nowrap;
}
.vtable td{
padding:14px 20px;border-bottom:1px solid #F0F0F0;font-size:13.5px;vertical-align:middle;
}
.vtable tr:last-child td{border-bottom:none}
.vtable tbody tr{cursor:pointer;transition:background .13s}
.vtable tbody tr:hover{background:#FFF8F0}
.num-cell{font-family:var(--num);font-weight:600;font-size:14px}
.date-cell{font-family:var(--num);font-size:12.5px;color:var(--ink-dim)}
/* ===== 抽屉 ===== */
.drawer-layer{
position:relative;min-height:620px;display:flex;
background:
linear-gradient(rgba(255,248,239,.55),rgba(255,248,239,.55)),
linear-gradient(120deg,#FFE3C2,#FFEFD8);
}
.drawer-overlay{position:absolute;inset:0;background:rgba(60,45,30,.32);backdrop-filter:blur(2px)}
.drawer{
position:relative;z-index:2;width:600px;margin-left:auto;background:#fff;
box-shadow:-30px 0 60px -24px rgba(120,70,20,.35);
display:flex;flex-direction:column;animation:slideIn .35s cubic-bezier(.2,.8,.25,1) both;
}
.drawer-head{
display:flex;align-items:center;justify-content:space-between;
padding:20px 24px 16px;border-bottom:1px solid var(--line);
}
.drawer-title{display:flex;align-items:center;gap:12px;font-size:17px;font-weight:700}
.drawer-title .pkg-ico{
width:42px;height:42px;border-radius:13px;display:grid;place-items:center;font-size:19px;
background:linear-gradient(135deg,#FFE9C7,#FFD9A0);
}
.drawer-close{width:32px;height:32px;border-radius:50%;display:grid;place-items:center;cursor:pointer;background:#F4F4F6;color:var(--ink-dim);transition:.15s}
.drawer-close:hover{background:#FFE9E2;color:var(--red)}
.drawer-body{padding:20px 24px;flex:1;overflow:hidden;display:flex;flex-direction:column}
.d-kv{display:flex;gap:26px;flex-wrap:wrap;margin-bottom:18px;padding:14px 16px;background:#FFFBF3;border-radius:14px;border:1.5px dashed #FFD9A8}
.d-kv .kv .k{font-size:11px;color:var(--ink-faint);font-weight:600}
.d-kv .kv .v{font-family:var(--num);font-size:15px;font-weight:700;margin-top:3px}
.d-sub{font-size:13px;font-weight:700;margin:2px 0 12px;display:flex;align-items:center;gap:8px}
.d-sub .cnt{
font-family:var(--num);font-size:11px;font-weight:600;color:var(--blue);
background:var(--blue-soft);padding:3px 10px;border-radius:99px;
}
.ver-list{flex:1;overflow:hidden}
.ver-row{
display:flex;align-items:center;gap:14px;padding:11px 14px;
border:1px solid var(--line);border-radius:14px;margin-bottom:9px;background:#fff;transition:.15s;
}
.ver-row:hover{border-color:#FFC98F;box-shadow:0 6px 16px -8px rgba(255,140,0,.3)}
.ver-row .ver-no{
font-family:var(--num);font-weight:700;font-size:14.5px;color:var(--ink);
min-width:88px;padding:5px 12px;border-radius:10px;background:#F4F4F6;text-align:center;
}
.ver-row .ver-size{font-family:var(--num);font-size:12px;color:var(--ink-dim)}
.ver-row .ver-date{font-family:var(--num);font-size:12px;color:var(--ink-faint);margin-left:auto}
.del-btn{
border:none;cursor:pointer;width:30px;height:30px;border-radius:10px;
background:var(--red-soft);color:var(--red);font-size:14px;transition:.15s;display:grid;place-items:center;
}
.del-btn:hover{background:var(--red);color:#fff}
.drawer-foot{
display:flex;gap:12px;padding:16px 24px;border-top:1px solid var(--line);background:#FFFDF8;
}
.btn.block{flex:1;justify-content:center;padding:12px 0}
/* ===== 忽略列表 ===== */
.page-inner{padding:30px 28px 40px}
.page-title{display:flex;align-items:center;gap:11px;font-size:19px;font-weight:700;margin-bottom:6px}
.page-title .emo{font-size:22px}
.page-sub{font-size:12.5px;color:var(--ink-dim);margin-bottom:20px}
.page-sub b{color:var(--blue);font-family:var(--num)}
.ign-table{background:#fff;border:1.5px solid var(--line);border-radius:var(--r-lg);box-shadow:var(--shadow);overflow:hidden}
.ign-table th{
font-size:11px;font-weight:600;letter-spacing:1px;color:var(--ink-faint);text-align:left;
padding:14px 20px;border-bottom:1px solid var(--line);background:#FFFDF8;white-space:nowrap;
}
.ign-table td{padding:15px 20px;border-bottom:1px solid #FBF4E9;font-size:13.5px;vertical-align:middle}
.ign-table tr:last-child td{border-bottom:none}
.ign-table tr:hover{background:#F7FBFF}
.ign-time{font-family:var(--num);font-size:12.5px;color:var(--ink-dim)}
/* ===== 最近动态 ===== */
.log-live{
display:inline-flex;align-items:center;gap:7px;font-family:var(--num);font-size:11.5px;font-weight:600;
color:#1F9E1F;background:var(--green-soft);border:1px solid #BDEBBE;
padding:6px 13px;border-radius:99px;
}
.log-live i{width:7px;height:7px;border-radius:50%;background:#22C22C;box-shadow:0 0 8px #22C22C;animation:blink 1.6s infinite}
.log-ver{font-family:var(--num);font-size:12.5px;color:var(--red)}
.chip{font-family:var(--num);font-size:12px;font-weight:700;padding:4px 12px;border-radius:9px;display:inline-block}
.chip.n{color:var(--red);background:var(--red-soft)}
.chip.s{color:#1F9E1F;background:var(--green-soft)}
.note{
margin-top:-40px;margin-bottom:60px;text-align:center;
font-family:var(--num);font-size:12px;color:var(--ink-faint);letter-spacing:.5px;
}
/* ===== 阈值配置 ===== */
.th-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;margin-bottom:16px}
.th-card{background:var(--card);border:1.5px solid var(--line);border-radius:var(--r-lg);padding:20px 22px;box-shadow:var(--shadow);animation:fadeUp .5s both;transition:.18s}
.th-card:hover{transform:translateY(-2px);box-shadow:var(--shadow-hover)}
.th-head{display:flex;align-items:center;justify-content:space-between}
.th-title{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:700}
.th-title .ico{font-size:19px}
.th-tag{font-size:11.5px;font-weight:700;padding:4px 11px;border-radius:99px;background:var(--orange-soft);color:var(--orange);white-space:nowrap}
.th-desc{font-size:12px;color:var(--ink-dim);margin:9px 0 16px;line-height:1.75}
.th-row{display:flex;align-items:center;gap:14px}
.th-num{display:flex;align-items:center;gap:8px;background:var(--card);border:1.5px solid var(--line);border-radius:14px;padding:6px;transition:.16s}
.th-num:focus-within{border-color:var(--orange);box-shadow:0 0 0 3px rgba(255,140,0,.12)}
.th-num button{width:34px;height:34px;border-radius:10px;border:none;cursor:pointer;font-size:17px;font-weight:700;color:var(--orange);background:var(--orange-soft);transition:.15s}
.th-num button:hover{background:var(--orange);color:#fff}
.th-num input{width:104px;border:none;outline:none;text-align:center;background:transparent;font-family:var(--num);font-size:22px;font-weight:700;color:var(--ink)}
.th-unit{font-size:12.5px;color:var(--ink-faint);font-weight:600}
.th-actions{margin-left:auto}
.th-reset{font-size:12px;color:var(--ink-faint);cursor:pointer;text-decoration:underline dotted}
.th-reset:hover{color:var(--orange)}
.th-tip{display:flex;align-items:center;gap:9px;font-size:12.5px;font-weight:600;color:var(--green);background:var(--green-soft);border:1.5px dashed rgba(61,219,77,.4);border-radius:14px;padding:12px 16px}
.btn.remove{background:var(--red-soft);color:var(--red)}
.btn.remove:hover{background:var(--red);color:#fff}
.add-ex{display:flex;align-items:center;gap:10px;background:var(--card);border:1.5px dashed var(--orange);border-radius:var(--r-lg);padding:14px}
.add-ex input{background:var(--card);border:1.5px solid var(--line);border-radius:12px;padding:10px 14px;font-size:13px;color:var(--ink);font-family:var(--sans);outline:none;transition:.16s}
.add-ex input::placeholder{color:var(--ink-faint)}
.add-ex input:focus{border-color:var(--orange);box-shadow:0 0 0 3px rgba(255,140,0,.12)}
.add-ex .ae-pkg{flex:1;min-width:200px}
.add-ex .ae-num{width:130px}
/* ===== 明暗主题切换入口 ===== */
.theme-toggle{
display:inline-flex;align-items:center;gap:7px;border:1.5px solid var(--line);
background:var(--card);color:var(--ink-dim);font-size:12.5px;font-weight:600;
padding:8px 15px;border-radius:99px;cursor:pointer;transition:.16s;font-family:var(--sans);white-space:nowrap;
}
.theme-toggle:hover{border-color:var(--orange);color:var(--orange);box-shadow:0 6px 14px -6px rgba(255,140,0,.4);transform:translateY(-1px)}
/* ===== 移动端竖版 ===== */
.phone{
width:402px;margin:34px auto;padding:14px;border-radius:48px;
background:linear-gradient(160deg,#3A3A40,#26262B);
box-shadow:0 40px 90px -30px rgba(0,0,0,.55), inset 0 0 0 2px rgba(255,255,255,.08);
}
.phone-screen{
position:relative;height:844px;border-radius:34px;overflow:hidden;
background:var(--bg);border:1px solid var(--line);
}
.phone-notch{position:absolute;top:0;left:50%;transform:translateX(-50%);z-index:20;width:150px;height:26px;border-radius:0 0 16px 16px;background:#000}
.m-app{display:flex;flex-direction:column;height:100%}
.m-topbar{display:flex;align-items:center;justify-content:space-between;padding:44px 16px 10px;background:var(--card);border-bottom:1px solid var(--line)}
.m-brand{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:700}
.m-brand .m-logo{width:30px;height:30px;border-radius:10px;display:grid;place-items:center;font-size:15px;background:linear-gradient(135deg,#FFB347,#FF8C00)}
.m-brand b{color:var(--orange)}
.m-brand span{color:var(--ink-faint);font-weight:600;font-size:12px}
.m-menu{width:34px;height:34px;border-radius:11px;display:grid;place-items:center;background:var(--card-2);border:1px solid var(--line);font-size:16px;color:var(--ink-dim);cursor:pointer}
.m-body{flex:1;overflow:hidden;padding:12px 14px}
.m-stats{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.m-stat{display:flex;align-items:center;gap:10px;background:var(--card);border:1px solid var(--line);border-radius:16px;padding:12px}
.m-stat .m-ico{width:36px;height:36px;border-radius:11px;display:grid;place-items:center;font-size:17px;background:var(--tone-soft)}
.m-stat b{font-family:var(--num);font-size:19px;font-weight:700;color:var(--tone);display:block;line-height:1.15}
.m-stat span{font-size:10.5px;color:var(--ink-faint);font-weight:600}
.m-stat.t-green{--tone:var(--green);--tone-soft:var(--green-soft)}
.m-stat.t-orange{--tone:var(--orange);--tone-soft:var(--orange-soft)}
.m-stat.t-red{--tone:var(--red);--tone-soft:var(--red-soft)}
.m-stat.t-blue{--tone:var(--blue);--tone-soft:var(--blue-soft)}
.m-banner{
display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:12px;
background:linear-gradient(120deg,rgba(255,86,48,.16),rgba(255,160,51,.14));
border:1px solid rgba(255,86,48,.45);border-radius:16px;padding:13px 14px;
animation:breathe 2.4s ease-in-out infinite;
}
.m-banner span{font-size:13px;font-weight:700;color:var(--red)}
.m-banner button{background:linear-gradient(120deg,#FFB347,#FF8C00);color:#fff;border:none;font-size:12px;font-weight:600;padding:8px 14px;border-radius:99px;cursor:pointer;white-space:nowrap}
.m-sec-title{font-size:13px;font-weight:700;margin:14px 2px 2px;display:flex;align-items:center;gap:6px}
.m-card{background:var(--card);border:1.5px solid var(--line);border-radius:16px;padding:12px 14px;margin-top:10px}
.m-card.warn{border-color:rgba(255,86,48,.45);animation:breathe 2.6s ease-in-out infinite}
.m-card-top{display:flex;align-items:center;gap:10px}
.m-pkg-ico{width:34px;height:34px;border-radius:10px;display:grid;place-items:center;font-size:15px;background:linear-gradient(135deg,#FFE9C7,#FFD9A0)}
.m-card-top .m-info{flex:1;min-width:0}
.m-card-top .m-info b{font-size:14px;font-weight:700;display:block}
.m-card-top .m-info span{font-size:11px;color:var(--ink-faint)}
.m-card-ops{display:flex;gap:8px;margin-top:10px}
.m-card-ops .btn{flex:1;justify-content:center;padding:8px 0;font-size:12px}
.m-scroll-tip{text-align:center;font-family:var(--num);font-size:10.5px;color:var(--ink-faint);margin-top:12px;letter-spacing:1px}
.m-tabbar{display:flex;border-top:1px solid var(--line);background:var(--card);padding:8px 6px 10px}
.m-tabbar .tab{flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;font-size:19px;color:var(--ink-faint);padding:4px 0;border-radius:12px;cursor:pointer}
.m-tabbar .tab i{font-style:normal;font-size:10px;font-weight:600}
.m-tabbar .tab.active{color:var(--orange)}
.m-tabbar .tab.active i{color:var(--orange)}
.m-home-bar{width:130px;height:5px;border-radius:99px;background:var(--ink-faint);margin:6px auto 8px;opacity:.6}
/* ===== 移动端设置抽屉(展开态) ===== */
.m-phone-row{display:flex;gap:56px;justify-content:center;align-items:flex-start;padding:30px 20px 40px;flex-wrap:wrap}
.m-phone-col{display:flex;flex-direction:column;align-items:center}
.phone-caption{font-family:var(--num);font-size:12px;color:var(--ink-faint);margin-bottom:14px;letter-spacing:.5px;text-align:center}
.m-drawer-overlay{position:absolute;inset:0;background:rgba(0,0,0,.45);z-index:30}
.m-drawer{
position:absolute;top:0;right:0;bottom:0;width:76%;z-index:31;display:flex;flex-direction:column;
background:var(--card);box-shadow:-22px 0 50px -16px rgba(0,0,0,.55);
animation:slideIn .35s cubic-bezier(.2,.8,.25,1) both;
}
.m-drawer-head{display:flex;align-items:center;justify-content:space-between;padding:18px 16px;border-bottom:1px solid var(--line);font-size:14px;font-weight:700}
.m-drawer-close{width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:var(--card);border:1px solid var(--line);color:var(--ink-dim);font-size:12px;cursor:pointer}
.m-drawer-body{flex:1;overflow:hidden;padding:14px}
.m-user{display:flex;align-items:center;gap:11px;padding:12px;background:var(--card-2);border-radius:14px;margin-bottom:12px}
.m-user-avatar{width:38px;height:38px;border-radius:50%;color:#fff;display:grid;place-items:center;font-size:16px;background:linear-gradient(135deg,#7CC6FF,#1E90FF)}
.m-user b{font-size:13.5px;display:block}
.m-user span{font-size:11px;color:var(--ink-faint)}
.m-drawer-item{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:13px 12px;font-size:13px;font-weight:600;border-radius:12px;margin-bottom:6px;cursor:pointer;transition:.15s}
.m-drawer-item:hover{background:var(--orange-soft);color:var(--orange)}
.m-drawer-item span{font-family:var(--num);font-size:11px;color:var(--ink-faint);font-weight:600;white-space:nowrap}
.m-drawer-foot{padding:12px 14px;border-top:1px solid var(--line)}
.m-logout{width:100%;padding:12px 0;border:none;border-radius:14px;cursor:pointer;font-size:13px;font-weight:700;font-family:var(--sans);background:var(--red-soft);color:var(--red);transition:.15s}
.m-logout:hover{background:var(--red);color:#fff}
/* ===== 窄屏响应式(手机直接打开设计稿时自适应) ===== */
@media (max-width:560px){
.board{padding:24px 10px 0}
.board-head{flex-direction:column;align-items:flex-start;gap:12px}
.frame{border-radius:18px}
.topbar{height:56px;padding:0 12px;gap:10px}
.nav{display:none}
.brand-sub{display:none}
.content{padding:14px 12px 28px}
.page-inner{padding:18px 12px 28px}
.search-hero{padding:22px 0 18px}
.search-box{height:48px;width:100%}
.stats-1{grid-template-columns:repeat(2,1fr);gap:10px}
.stats-2{grid-template-columns:repeat(2,1fr);gap:10px}
.pkg-grid{grid-template-columns:1fr;gap:12px}
.top30{overflow-x:auto}
.rank-tbl{min-width:620px}
.drawer{width:100%}
.th-grid{grid-template-columns:1fr}
.add-ex{flex-direction:column;align-items:stretch}
.theme-toggle{margin-left:auto}
.phone{width:100%;margin:20px auto}
}
/* ===== 动效 ===== */
@keyframes fadeUp{from{opacity:0;transform:translateY(14px)}to{opacity:1;transform:translateY(0)}}
@keyframes breathe{
0%,100%{box-shadow:0 0 0 5px rgba(255,69,0,.22), 0 12px 30px -12px rgba(255,69,0,.3)}
50%{box-shadow:0 0 0 12px rgba(255,69,0,.06), 0 14px 34px -10px rgba(255,69,0,.4)}
}
@keyframes grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@keyframes slideIn{from{transform:translateX(80px);opacity:.4}to{transform:translateX(0);opacity:1}}
@keyframes blink{0%,100%{opacity:1}50%{opacity:.35}}
@media (max-width:1180px){
.stats-1{grid-template-columns:repeat(2,1fr)}
.stats-2{grid-template-columns:repeat(3,1fr)}
.pkg-grid{grid-template-columns:repeat(2,1fr)}
}
</style>
</head>
<body>
<div class="ambient"></div>
<div class="board">
<div class="board-head">
<div>
<h1><span class="num">DESIGN-02</span>Verdaccio 缓存可视化管理面板</h1>
<p>依据《需求文档.docx 前端界面设计文档》· 多巴胺 C 端风格 · 暖白底 + 大圆角卡片 + 预警呼吸光晕</p>
</div>
<div class="meta">设计稿 · v0.2<br>画面:概览 / 检索 / 详情抽屉 / 忽略列表 / 最近动态<br><b>2026-09</b></div>
</div>
<!-- ============ FRAME 1 缓存概览 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 01 · 缓存概览(9 项统计 + Top30 排行 + 包卡片)</div>
<section class="frame" id="frame-overview">
<div class="app">
<header class="topbar">
<div class="brand">
<div class="logo-emoji">📦</div>
<div>
<div class="brand-name">Verdaccio Manager</div>
<div class="brand-sub">你的缓存 · 尽在掌握</div>
</div>
</div>
<nav class="nav">
<span class="nav-item active">🏠 概览</span>
<span class="nav-item">🔍 检索</span>
<span class="nav-item">⚙️ 阈值</span>
<span class="nav-item">🕒 最近动态</span>
</nav>
<div class="topbar-right">
<button class="theme-toggle" onclick="location.href='../dark/design-mockup-v2-dark.html'+location.hash" title="切换到暗色调">🌙 切到暗色</button>
<div class="avatar">👤</div>
</div>
</header>
<div class="content">
<!-- 第一行 · 核心指标 4 -->
<div class="stats-1">
<div class="stat1 t-green">
<div class="top">
<span class="label">📦 包的数量</span>
<span class="ico">🗃️</span>
</div>
<div class="value">1,247<span class="unit">个缓存包</span></div>
</div>
<div class="stat1 t-orange">
<div class="top">
<span class="label">💾 占用空间</span>
<span class="ico">🧊</span>
</div>
<div class="value">12.8<span class="unit">GB</span></div>
</div>
<div class="stat1 t-red">
<div class="top">
<span class="label">🚨 预警数量</span>
<span class="ico">🔥</span>
</div>
<div class="value">23<span class="unit">个需关注</span></div>
</div>
<div class="stat1 t-blue">
<div class="top">
<span class="label">🧹 可清理空间</span>
<span class="ico">✨</span>
</div>
<div class="value">3.2<span class="unit">GB</span></div>
<span class="cta" style="position:absolute;right:20px;bottom:18px">✨ 立即清理</span>
</div>
</div>
<!-- 第二行 · 辅助指标 5 -->
<div class="stats-2">
<div class="stat2 c-blue">
<div class="s2-top"><span class="ico2">🏆</span>访问排行<span class="link">展开</span></div>
<div class="s2-val">Top 30</div>
<div class="s2-sub">lodash · axios · react</div>
</div>
<div class="stat2 c-green">
<div class="s2-top"><span class="ico2">📈</span>今日下载量</div>
<div class="s2-val">1,234</div>
<div class="s2-sub">较昨日 +18%</div>
</div>
<div class="stat2 c-orange">
<div class="s2-top"><span class="ico2">🐘</span>最大包</div>
<div class="s2-val"><span class="main">tensorflow</span><span class="u">2.1 GB</span></div>
<div class="s2-sub">24 个版本</div>
</div>
<div class="stat2 c-purple">
<div class="s2-top"><span class="ico2">⏳</span>最老包时间</div>
<div class="s2-val">3 年前<span class="u">⏰</span></div>
<div class="s2-sub">legacy-utils · 未更新</div>
</div>
<div class="stat2 c-pink">
<div class="s2-top"><span class="ico2">🧩</span>版本数分布<span class="link">查看详情</span></div>
<div class="s2-val">&gt;50 版本<span class="u">12 个</span></div>
<div class="s2-sub">>30: 41 · >15: 128</div>
</div>
</div>
<!-- Top 30 排行(展开态) -->
<div class="top30">
<div class="top30-head">
<div class="t"><span class="ico">🏆</span>访问次数 Top 30<span class="tag30">按热度排序</span></div>
<span class="link" style="font-size:12px;font-weight:600;color:var(--ink-faint);cursor:pointer">收起 ▴</span>
</div>
<table class="rank-tbl">
<thead><tr><th style="width:70px">排名</th><th>包名</th><th style="width:110px">访问次数</th><th>热度</th></tr></thead>
<tbody>
<tr><td><span class="rank-no gold">1</span></td><td><span class="rank-pkg"><span class="mini-ico">📚</span>lodash</span></td><td><span class="rank-visit">12,456</span></td><td><div class="heat"><div class="heat-bar"><i style="width:85%"></i></div><span class="heat-pct">85%</span></div></td></tr>
<tr><td><span class="rank-no silver">2</span></td><td><span class="rank-pkg"><span class="mini-ico">🔌</span>axios</span></td><td><span class="rank-visit">9,832</span></td><td><div class="heat"><div class="heat-bar"><i style="width:72%;animation-delay:.1s"></i></div><span class="heat-pct">72%</span></div></td></tr>
<tr><td><span class="rank-no bronze">3</span></td><td><span class="rank-pkg"><span class="mini-ico">📊</span><span><span class="scope">@antv/</span>g2</span></span></td><td><span class="rank-visit">7,654</span></td><td><div class="heat"><div class="heat-bar"><i style="width:58%;animation-delay:.2s"></i></div><span class="heat-pct">58%</span></div></td></tr>
<tr><td><span class="rank-no">4</span></td><td><span class="rank-pkg"><span class="mini-ico">⚛️</span>react</span></td><td><span class="rank-visit">6,543</span></td><td><div class="heat"><div class="heat-bar"><i style="width:49%;animation-delay:.3s"></i></div><span class="heat-pct">49%</span></div></td></tr>
<tr><td><span class="rank-no">5</span></td><td><span class="rank-pkg"><span class="mini-ico">💚</span>vue</span></td><td><span class="rank-visit">5,218</span></td><td><div class="heat"><div class="heat-bar"><i style="width:41%;animation-delay:.4s"></i></div><span class="heat-pct">41%</span></div></td></tr>
<tr><td><span class="rank-no">6</span></td><td><span class="rank-pkg"><span class="mini-ico">🚀</span>express</span></td><td><span class="rank-visit">4,376</span></td><td><div class="heat"><div class="heat-bar"><i style="width:35%;animation-delay:.5s"></i></div><span class="heat-pct">35%</span></div></td></tr>
</tbody>
</table>
</div>
<!-- 包卡片列表 -->
<div class="sec-head">
<span class="sec-title"><span class="emo">🎁</span>你的缓存</span>
<span class="sec-sub">共 1,247 个包 · 预警置顶 · 同状态按版本数降序</span>
</div>
<div class="pkg-grid">
<div class="pkg-card warn" style="animation-delay:.6s">
<div class="p-top">
<div class="pkg-ico">📚</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm">lodash</span> <span class="tag warn">🔥 预警</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">超阈值 10 个版本</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">25</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">128.5 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops">
<button class="btn clean">✨ 清理</button>
<button class="btn ignore">💤 忽略</button>
</div>
</div>
<div class="pkg-card warn" style="animation-delay:.66s">
<div class="p-top">
<div class="pkg-ico">📊</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm"><span class="scope">@antv/</span>g2</span> <span class="tag warn">🔥 预警</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">超阈值 17 个版本</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">32</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">256.8 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops">
<button class="btn clean">✨ 清理</button>
<button class="btn ignore">💤 忽略</button>
</div>
</div>
<div class="pkg-card warn" style="animation-delay:.72s">
<div class="p-top">
<div class="pkg-ico">🔌</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm">axios</span> <span class="tag warn">🔥 预警</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">超阈值 3 个版本</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">18</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">45.2 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops">
<button class="btn clean">✨ 清理</button>
<button class="btn ignore">💤 忽略</button>
</div>
</div>
<div class="pkg-card" style="animation-delay:.78s">
<div class="p-top">
<div class="pkg-ico">⚛️</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm">react</span> <span class="tag ok">✅ 正常</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">最近更新 2026-09-12</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">8</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">42.1 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops"><button class="btn disabled">—</button></div>
</div>
<div class="pkg-card" style="animation-delay:.84s">
<div class="p-top">
<div class="pkg-ico">🚀</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm">express</span> <span class="tag ok">✅ 正常</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">最近更新 2026-09-08</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">6</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">18.9 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops"><button class="btn disabled">—</button></div>
</div>
<div class="pkg-card ignored" style="animation-delay:.9s">
<div class="p-top">
<div class="pkg-ico">🛡️</div>
<div style="flex:1">
<div class="pkg-name"><span class="nm">vue</span> <span class="tag ign">💤 已忽略</span></div>
<div style="font-size:11.5px;color:var(--ink-faint);margin-top:3px">标记于 2026-08-30</div>
</div>
</div>
<div class="p-meta">
<div class="m-item"><div class="m-v">19</div><div class="m-l">版本数</div></div>
<div class="m-item"><div class="m-v">88.3 MB</div><div class="m-l">占用空间</div></div>
</div>
<div class="p-ops"><button class="btn unignore">↩ 取消忽略</button></div>
</div>
</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 2 全局检索 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 02 · 全局检索(大圆角搜索框 + pill 筛选 + 无边框虚拟表格)</div>
<section class="frame" id="frame-search">
<div class="app">
<header class="topbar">
<div class="brand">
<div class="logo-emoji">📦</div>
<div>
<div class="brand-name">Verdaccio Manager</div>
<div class="brand-sub">你的缓存 · 尽在掌握</div>
</div>
</div>
<nav class="nav">
<span class="nav-item">🏠 概览</span>
<span class="nav-item active">🔍 检索</span>
<span class="nav-item">⚙️ 阈值</span>
<span class="nav-item">🕒 最近动态</span>
</nav>
<div class="topbar-right">
<button class="theme-toggle" onclick="location.href='../dark/design-mockup-v2-dark.html'+location.hash" title="切换到暗色调">🌙 切到暗色</button>
<div class="avatar">👤</div>
</div>
</header>
<div class="search-hero">
<div class="search-box">
<span class="s-ico">🔍</span>
<input type="text" value="antv" placeholder="输入包名、版本、大小进行模糊搜索…">
<button class="s-go">搜索</button>
</div>
<div class="filter-row">
<span class="pill on">全部</span>
<span class="pill">🔥 预警包</span>
<span class="pill">💤 已忽略</span>
<span class="pill split">|</span>
<span class="pill">按大小排序</span>
<span class="pill">按版本数排序</span>
</div>
<div class="result-note">共找到 <b>3</b> 个包 · 实时检索(防抖 300ms)</div>
</div>
<div style="padding:0 28px 36px">
<div class="vtable">
<table>
<thead><tr><th style="width:34%">包名</th><th style="width:100px">版本数</th><th style="width:130px">占用空间</th><th style="width:110px">状态</th><th>最后更新</th></tr></thead>
<tbody>
<tr><td><span class="rank-pkg"><span class="mini-ico">📊</span><span><span class="scope">@antv/</span>g2</span></span></td><td><span class="num-cell" style="color:var(--red)">32</span></td><td><span class="num-cell">256.8 MB</span></td><td><span class="tag warn">🔥 预警</span></td><td><span class="date-cell">2026-09-14</span></td></tr>
<tr><td><span class="rank-pkg"><span class="mini-ico">🧭</span><span><span class="scope">@antv/</span>g6</span></span></td><td><span class="num-cell" style="color:var(--red)">21</span></td><td><span class="num-cell">98.4 MB</span></td><td><span class="tag warn">🔥 预警</span></td><td><span class="date-cell">2026-09-10</span></td></tr>
<tr><td><span class="rank-pkg"><span class="mini-ico">📉</span><span><span class="scope">@antv/</span>data-set</span></span></td><td><span class="num-cell">12</span></td><td><span class="num-cell">23.6 MB</span></td><td><span class="tag ok">✅ 正常</span></td><td><span class="date-cell">2026-08-27</span></td></tr>
<tr><td><span class="rank-pkg"><span class="mini-ico">🎨</span><span><span class="scope">@antv/</span>f2</span></span></td><td><span class="num-cell">9</span></td><td><span class="num-cell">18.1 MB</span></td><td><span class="tag ok">✅ 正常</span></td><td><span class="date-cell">2026-08-15</span></td></tr>
</tbody>
</table>
</div>
<div style="text-align:center;margin-top:16px;font-family:var(--num);font-size:12px;color:var(--ink-faint)">▼ 滚动到底部自动加载更多(Infinite Scroll · 虚拟滚动)</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 3 阈值配置 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 03 · 阈值配置(全局默认 + 单包例外 · 命中即预警)</div>
<section class="frame" id="frame-threshold">
<div class="app">
<header class="topbar">
<div class="brand">
<div class="logo-emoji">📦</div>
<div>
<div class="brand-name">Verdaccio Manager</div>
<div class="brand-sub">你的缓存 · 尽在掌握</div>
</div>
</div>
<nav class="nav">
<span class="nav-item">🏠 概览</span>
<span class="nav-item">🔍 检索</span>
<span class="nav-item active">⚙️ 阈值</span>
<span class="nav-item">🕒 最近动态</span>
</nav>
<div class="topbar-right">
<button class="theme-toggle" onclick="location.href='../dark/design-mockup-v2-dark.html'+location.hash" title="切换到暗色调">🌙 切到暗色</button>
<div class="avatar">👤</div>
</div>
</header>
<div class="page-inner">
<div class="page-title"><span class="emo">⚙️</span>阈值配置</div>
<div class="page-sub">命中阈值即预警 · 全局默认 + 单包例外 · 保存后立即生效,历史缓存包即时重算状态</div>
<div class="th-grid">
<div class="th-card">
<div class="th-head">
<div class="th-title"><span class="ico">📦</span>版本数阈值<span class="th-tag">命中即预警</span></div>
<span class="th-reset">恢复默认 50</span>
</div>
<div class="th-desc">包内缓存版本数达到阈值后,该包被标记为「预警」,并置顶展示在概览与检索结果中。</div>
<div class="th-row">
<div class="th-num">
<button>−</button><input type="number" value="50"><button>+</button>
</div>
<span class="th-unit">个版本</span>
<div class="th-actions"><button class="btn clean">✨ 保存</button></div>
</div>
</div>
<div class="th-card">
<div class="th-head">
<div class="th-title"><span class="ico">🧊</span>包大小阈值<span class="th-tag">命中即预警</span></div>
<span class="th-reset">恢复默认 500</span>
</div>
<div class="th-desc">包占用空间达到阈值后标记预警。预警包内的低版本可一键清理,释放可清理空间。</div>
<div class="th-row">
<div class="th-num">
<button>−</button><input type="number" value="500"><button>+</button>
</div>
<span class="th-unit">MB</span>
<div class="th-actions"><button class="btn clean">✨ 保存</button></div>
</div>
</div>
</div>
<div class="th-tip">💡 保存后立即生效 · 历史缓存包将即时重算预警状态 · 无需重启服务</div>
<div class="sec-head" style="margin-top:26px">
<span class="sec-title"><span class="emo">🔧</span>单包例外</span>
<span class="sec-sub">例外的阈值优先于全局配置 · 用于个别大包 / 特殊包</span>
</div>
<div class="ign-table" style="margin-bottom:14px">
<table>
<thead><tr><th style="width:30%">包名</th><th style="width:130px">版本数阈值</th><th style="width:140px">包大小阈值</th><th style="width:150px">命中状态</th><th>操作</th></tr></thead>
<tbody>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">📊</span><span><span class="scope">@antv/</span>g2</span></span></td>
<td><span class="num-cell">15</span></td>
<td><span class="num-cell">200 MB</span></td>
<td><span class="tag warn">🔥 命中 2 项</span></td>
<td><button class="btn remove">🗑️ 删除</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">📚</span>lodash</span></td>
<td><span class="num-cell">30</span></td>
<td><span class="num-cell">1 GB</span></td>
<td><span class="tag ok">✅ 正常</span></td>
<td><button class="btn remove">🗑️ 删除</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">🐘</span>tensorflow</span></td>
<td><span class="num-cell">20</span></td>
<td><span class="num-cell">3 GB</span></td>
<td><span class="tag ok">✅ 正常</span></td>
<td><button class="btn remove">🗑️ 删除</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">🗃️</span>zod</span></td>
<td><span class="num-cell">10</span></td>
<td><span class="num-cell">100 MB</span></td>
<td><span class="tag warn">🔥 命中 1 项</span></td>
<td><button class="btn remove">🗑️ 删除</button></td>
</tr>
</tbody>
</table>
</div>
<div class="add-ex">
<input class="ae-pkg" placeholder="输入包名,如 @antv/g2">
<input class="ae-num" type="number" placeholder="版本数">
<input class="ae-num" type="number" placeholder="大小 (MB)">
<button class="btn clean">+ 添加例外</button>
</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 4 包详情抽屉 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 04 · 包详情抽屉(版本列表 / 删除单版本 / 批量操作)</div>
<section class="frame" id="frame-drawer">
<div class="drawer-layer">
<div class="drawer-overlay"></div>
<div class="drawer">
<div class="drawer-head">
<div class="drawer-title">
<span class="pkg-ico">📚</span>
lodash
<span class="tag warn" style="margin-left:4px">🔥 预警</span>
</div>
<span class="drawer-close">✕</span>
</div>
<div class="drawer-body">
<div class="d-kv">
<div class="kv"><div class="k">版本数</div><div class="v">25</div></div>
<div class="kv"><div class="k">占用空间</div><div class="v">128.5 MB</div></div>
<div class="kv"><div class="k">最后更新</div><div class="v">2026-09-15</div></div>
<div class="kv"><div class="k">首次缓存</div><div class="v">2019-03-10</div></div>
</div>
<div class="d-sub">版本列表 <span class="cnt">25 个版本 · 按 semver 降序</span></div>
<div class="ver-list">
<div class="ver-row">
<span class="ver-no">4.17.21</span>
<span class="ver-size">1.2 MB</span>
<span class="ver-date">2026-09-15</span>
<button class="del-btn">🗑️</button>
</div>
<div class="ver-row">
<span class="ver-no">4.17.20</span>
<span class="ver-size">1.2 MB</span>
<span class="ver-date">2025-08-22</span>
<button class="del-btn">🗑️</button>
</div>
<div class="ver-row">
<span class="ver-no">4.17.19</span>
<span class="ver-size">1.1 MB</span>
<span class="ver-date">2025-03-10</span>
<button class="del-btn">🗑️</button>
</div>
<div class="ver-row">
<span class="ver-no">4.17.18</span>
<span class="ver-size">1.1 MB</span>
<span class="ver-date">2024-11-02</span>
<button class="del-btn">🗑️</button>
</div>
<div class="ver-row" style="opacity:.55">
<span class="ver-no">4.17.17</span>
<span class="ver-size">1.1 MB</span>
<span class="ver-date">2024-06-28</span>
<button class="del-btn" style="pointer-events:none;opacity:.5">🗑️</button>
</div>
<div style="text-align:center;font-family:var(--num);font-size:11.5px;color:var(--ink-faint);padding:6px 0">▾ 共 25 个版本 · 支持虚拟滚动</div>
</div>
</div>
<div class="drawer-foot">
<button class="btn clean block">🧹 清理全部低版本</button>
<button class="btn ignore block">💤 忽略此包</button>
</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 4 忽略列表 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 05 · 忽略列表(已忽略包管理 / 取消忽略)</div>
<section class="frame" id="frame-ignore">
<div class="app">
<header class="topbar">
<div class="brand">
<div class="logo-emoji">📦</div>
<div>
<div class="brand-name">Verdaccio Manager</div>
<div class="brand-sub">你的缓存 · 尽在掌握</div>
</div>
</div>
<nav class="nav">
<span class="nav-item">🏠 概览</span>
<span class="nav-item">🔍 检索</span>
<span class="nav-item">⚙️ 阈值</span>
<span class="nav-item">🕒 最近动态</span>
</nav>
<div class="topbar-right">
<button class="theme-toggle" onclick="location.href='../dark/design-mockup-v2-dark.html'+location.hash" title="切换到暗色调">🌙 切到暗色</button>
<div class="avatar">👤</div>
</div>
</header>
<div class="page-inner">
<div class="page-title"><span class="emo">💤</span>忽略列表</div>
<div class="page-sub">已标记忽略的包不再触发预警 · 共 <b>15</b> 个已忽略包 · 取消忽略后自动恢复状态判断</div>
<div class="ign-table">
<table>
<thead><tr><th style="width:32%">包名</th><th style="width:100px">版本数</th><th style="width:140px">占用空间</th><th style="width:150px">忽略时间</th><th>操作</th></tr></thead>
<tbody>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">📚</span>lodash</span></td>
<td><span class="num-cell">25</span></td>
<td><span class="num-cell">128.5 MB</span></td>
<td><span class="ign-time">2026-08-30 14:20</span></td>
<td><button class="btn unignore">↩ 取消忽略</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">💚</span>vue</span></td>
<td><span class="num-cell">19</span></td>
<td><span class="num-cell">88.3 MB</span></td>
<td><span class="ign-time">2026-08-30 14:21</span></td>
<td><button class="btn unignore">↩ 取消忽略</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">🗃️</span>zod</span></td>
<td><span class="num-cell">22</span></td>
<td><span class="num-cell">31.6 MB</span></td>
<td><span class="ign-time">2026-09-02 09:15</span></td>
<td><button class="btn unignore">↩ 取消忽略</button></td>
</tr>
<tr>
<td><span class="rank-pkg"><span class="mini-ico">🗓️</span>moment</span></td>
<td><span class="num-cell">17</span></td>
<td><span class="num-cell">26.4 MB</span></td>
<td><span class="ign-time">2026-09-05 16:42</span></td>
<td><button class="btn unignore">↩ 取消忽略</button></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 5 最近动态 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 06 · 最近动态(操作日志 · 最近 50 条 · 时间倒序)</div>
<section class="frame" id="frame-logs">
<div class="app">
<header class="topbar">
<div class="brand">
<div class="logo-emoji">📦</div>
<div>
<div class="brand-name">Verdaccio Manager</div>
<div class="brand-sub">你的缓存 · 尽在掌握</div>
</div>
</div>
<nav class="nav">
<span class="nav-item">🏠 概览</span>
<span class="nav-item">🔍 检索</span>
<span class="nav-item">⚙️ 阈值</span>
<span class="nav-item active">🕒 最近动态</span>
</nav>
<div class="topbar-right">
<button class="theme-toggle" onclick="location.href='../dark/design-mockup-v2-dark.html'+location.hash" title="切换到暗色调">🌙 切到暗色</button>
<div class="avatar">👤</div>
</div>
</header>
<div class="page-inner">
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
<div style="display:flex;align-items:center;gap:12px">
<span class="page-title" style="margin-bottom:0"><span class="emo">🕒</span>最近动态</span>
<span style="font-size:12px;color:var(--ink-faint)">每次清理自动记录 · 最近 50 条 · 时间倒序</span>
</div>
<span class="log-live"><i></i>实时</span>
</div>
<div class="vtable">
<table>
<thead><tr><th style="width:170px">时间</th><th style="width:24%">包名</th><th>删除版本</th><th style="width:110px">删除数量</th><th style="width:120px">释放空间</th></tr></thead>
<tbody>
<tr>
<td><span class="date-cell">2026-09-18 14:32</span></td>
<td><span class="rank-pkg"><span class="mini-ico">📚</span>lodash</span></td>
<td><span class="log-ver">2.0.0 · 2.1.0 · 2.4.0 · 3.0.0 · 3.1.0 …</span></td>
<td><span class="chip n">10</span></td>
<td><span class="chip s">64 MB</span></td>
</tr>
<tr>
<td><span class="date-cell">2026-09-17 09:05</span></td>
<td><span class="rank-pkg"><span class="mini-ico">🔌</span>axios</span></td>
<td><span class="log-ver">0.18.0 · 0.19.0 · 0.21.1 …</span></td>
<td><span class="chip n">6</span></td>
<td><span class="chip s">22 MB</span></td>
</tr>
<tr>
<td><span class="date-cell">2026-09-15 18:47</span></td>
<td><span class="rank-pkg"><span class="mini-ico">📊</span><span><span class="scope">@antv/</span>g2</span></span></td>
<td><span class="log-ver">4.0.15 · 4.1.0 · 4.1.2 …</span></td>
<td><span class="chip n">7</span></td>
<td><span class="chip s">31 MB</span></td>
</tr>
<tr>
<td><span class="date-cell">2026-09-12 11:20</span></td>
<td><span class="rank-pkg"><span class="mini-ico">🗓️</span>moment</span></td>
<td><span class="log-ver">2.24.0 · 2.26.0 · 2.27.0 · 2.28.0 …</span></td>
<td><span class="chip n">7</span></td>
<td><span class="chip s">12 MB</span></td>
</tr>
<tr>
<td><span class="date-cell">2026-09-08 16:03</span></td>
<td><span class="rank-pkg"><span class="mini-ico">🚀</span>react-router</span></td>
<td><span class="log-ver">5.2.0 · 5.3.0 …</span></td>
<td><span class="chip n">4</span></td>
<td><span class="chip s">9 MB</span></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</section>
</div>
<!-- ============ FRAME 7 移动端竖版 ============ -->
<div class="frame-wrap">
<div class="frame-tag">FRAME 07 · 移动端竖版(390×844 · 收起态 / 展开态 · 底部 Tab)</div>
<section class="frame" id="frame-mobile">
<div class="m-phone-row">
<div class="m-phone-col">
<div class="phone-caption">收起态 · 点击右上角 ☰ 展开</div>
<div class="phone">
<div class="phone-screen">
<div class="phone-notch"></div>
<div class="m-app">
<div class="m-topbar">
<div class="m-brand"><span class="m-logo">📦</span><b>Verdaccio</b><span>Manager</span></div>
<button class="m-menu">☰</button>
</div>
<div class="m-body">
<div class="m-stats">
<div class="m-stat t-green"><span class="m-ico">🗃️</span><div><b>1,247</b><span>缓存包</span></div></div>
<div class="m-stat t-orange"><span class="m-ico">🧊</span><div><b>12.8 GB</b><span>占用空间</span></div></div>
<div class="m-stat t-red"><span class="m-ico">🔥</span><div><b>23</b><span>预警数量</span></div></div>
<div class="m-stat t-blue"><span class="m-ico">✨</span><div><b>3.2 GB</b><span>可清理</span></div></div>
</div>
<div class="m-banner">
<span>🔥 23 个包超出阈值</span>
<button>✨ 立即清理</button>
</div>
<div class="m-sec-title">🎁 预警包</div>
<div class="m-card warn">
<div class="m-card-top">
<span class="m-pkg-ico">📚</span>
<div class="m-info"><b>lodash</b><span>25 个版本 · 128.5 MB · 访问 Top 1</span></div>
<span class="tag warn">🔥 预警</span>
</div>
<div class="m-card-ops"><button class="btn clean">✨ 清理</button><button class="btn ignore">💤 忽略</button></div>
</div>
<div class="m-card warn">
<div class="m-card-top">
<span class="m-pkg-ico">📊</span>
<div class="m-info"><b><span class="scope">@antv/</span>g2</b><span>32 个版本 · 256.8 MB · 访问 Top 3</span></div>
<span class="tag warn">🔥 预警</span>
</div>
<div class="m-card-ops"><button class="btn clean">✨ 清理</button><button class="btn ignore">💤 忽略</button></div>
</div>
<div class="m-card">
<div class="m-card-top">
<span class="m-pkg-ico">⚛️</span>
<div class="m-info"><b>react</b><span>8 个版本 · 42.1 MB · 最近更新 09-12</span></div>
<span class="tag ok">✅ 正常</span>
</div>
</div>
<div class="m-scroll-tip">▼ 向下滚动查看更多</div>
</div>
<div class="m-tabbar">
<span class="tab active">🏠<i>概览</i></span>
<span class="tab">🔍<i>检索</i></span>
<span class="tab">⚙️<i>阈值</i></span>
<span class="tab">🕒<i>动态</i></span>
</div>
<div class="m-home-bar"></div>
</div>
</div>
</div>
</div>
<div class="m-phone-col">
<div class="phone-caption">展开态 · 点击 ☰ 右侧滑出设置抽屉</div>
<div class="phone">
<div class="phone-screen">
<div class="phone-notch"></div>
<div class="m-app">
<div class="m-topbar">
<div class="m-brand"><span class="m-logo">📦</span><b>Verdaccio</b><span>Manager</span></div>
<button class="m-menu">☰</button>
</div>
<div class="m-body">
<div class="m-stats">
<div class="m-stat t-green"><span class="m-ico">🗃️</span><div><b>1,247</b><span>缓存包</span></div></div>
<div class="m-stat t-orange"><span class="m-ico">🧊</span><div><b>12.8 GB</b><span>占用空间</span></div></div>
<div class="m-stat t-red"><span class="m-ico">🔥</span><div><b>23</b><span>预警数量</span></div></div>
<div class="m-stat t-blue"><span class="m-ico">✨</span><div><b>3.2 GB</b><span>可清理</span></div></div>
</div>
<div class="m-banner">
<span>🔥 23 个包超出阈值</span>
<button>✨ 立即清理</button>
</div>
<div class="m-sec-title">🎁 预警包</div>
<div class="m-card warn">
<div class="m-card-top">
<span class="m-pkg-ico">📚</span>
<div class="m-info"><b>lodash</b><span>25 个版本 · 128.5 MB · 访问 Top 1</span></div>
<span class="tag warn">🔥 预警</span>
</div>
<div class="m-card-ops"><button class="btn clean">✨ 清理</button><button class="btn ignore">💤 忽略</button></div>
</div>
<div class="m-card warn">
<div class="m-card-top">
<span class="m-pkg-ico">📊</span>
<div class="m-info"><b><span class="scope">@antv/</span>g2</b><span>32 个版本 · 256.8 MB · 访问 Top 3</span></div>
<span class="tag warn">🔥 预警</span>
</div>
<div class="m-card-ops"><button class="btn clean">✨ 清理</button><button class="btn ignore">💤 忽略</button></div>
</div>
<div class="m-card">
<div class="m-card-top">
<span class="m-pkg-ico">⚛️</span>
<div class="m-info"><b>react</b><span>8 个版本 · 42.1 MB · 最近更新 09-12</span></div>
<span class="tag ok">✅ 正常</span>
</div>
</div>
<div class="m-scroll-tip">▼ 向下滚动查看更多</div>
</div>
<div class="m-tabbar">
<span class="tab active">🏠<i>概览</i></span>
<span class="tab">🔍<i>检索</i></span>
<span class="tab">⚙️<i>阈值</i></span>
<span class="tab">🕒<i>动态</i></span>
</div>
<div class="m-home-bar"></div>
</div>
<div class="m-drawer-overlay"></div>
<div class="m-drawer">
<div class="m-drawer-head">⚙️ 更多与设置<span class="m-drawer-close">✕</span></div>
<div class="m-drawer-body">
<div class="m-user">
<div class="m-user-avatar">👤</div>
<div><b>缓存管理员</b><span>admin@verdaccio.local</span></div>
</div>
<div class="m-drawer-item">🌙 主题切换<span>当前:亮色</span></div>
<div class="m-drawer-item">⚙️ 阈值配置<span>版本数 50 · 500MB</span></div>
<div class="m-drawer-item">📦 版本信息<span>v0.2.0</span></div>
</div>
<div class="m-drawer-foot"><button class="m-logout">🚪 退出登录</button></div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
<!-- ============ FRAME 8 登录页 ============ -->
<style>
.login-wrap{flex:1;min-height:600px;display:flex;align-items:center;justify-content:center;padding:74px 24px}
.login-card{width:420px;max-width:100%;background:var(--card);border-radius:24px;box-shadow:0 24px 60px -18px rgba(0,0,0,.38);padding:42px 40px 34px;text-align:center}
.login-logo{width:64px;height:64px;margin:0 auto 14px;border-radius:18px;background:linear-gradient(120deg,#FFB347,#FF8C00);display:flex;align-items:center;justify-content:center;font-size:30px;box-shadow:0 10px 22px -8px rgba(255,140,0,.55)}
.login-title{font:700 24px/1.3 var(--num);color:var(--orange);letter-spacing:.5px}
.login-sub{font-size:13.5px;color:var(--ink-dim);margin:8px 0 26px}
.login-field{position:relative;margin-bottom:14px}
.login-field input{width:100%;height:48px;border:1px solid var(--line);border-radius:12px;background:var(--bg);padding:0 42px;font-size:14px;color:var(--ink);outline:none;transition:.2s}
.login-field input:focus{border-color:var(--orange);box-shadow:0 0 0 3px rgba(255,140,0,.16)}
.login-ico{position:absolute;left:14px;top:50%;transform:translateY(-50%);font-size:15px;opacity:.85}
.login-eye{position:absolute;right:14px;top:50%;transform:translateY(-50%);cursor:pointer;opacity:.6}
.login-btn{width:100%;height:48px;border:none;border-radius:12px;background:linear-gradient(120deg,#FF8C00,#FF4500);color:#fff;font-size:16px;font-weight:600;letter-spacing:4px;cursor:pointer;box-shadow:0 10px 22px -8px rgba(255,90,20,.55);margin-top:6px;transition:.2s}
.login-btn:hover{transform:translateY(-1px);box-shadow:0 14px 28px -8px rgba(255,90,20,.68)}
.login-extra{display:flex;justify-content:space-between;align-items:center;margin-top:16px;font-size:12.5px;color:var(--ink-dim)}
.login-extra label{display:flex;align-items:center;gap:6px;cursor:pointer}
.login-extra input{accent-color:var(--orange)}
.login-extra a{color:var(--blue);cursor:pointer}
.login-foot{margin-top:26px;padding-top:18px;border-top:1px solid var(--line);font-size:12px;color:var(--ink-faint);text-align:left;line-height:1.9}
.login-foot b{color:var(--ink-dim)}
</style>
<div class="frame-wrap">
<div class="frame-tag">FRAME 08 · 登录页(渐变背景 + 居中卡片 · 三端适配:桌面 420px / 平板自适应 / 手机 100%)</div>
<section class="frame" id="frame-login" style="padding:0;background:linear-gradient(135deg,#32CD32,#1E90FF);display:flex;border-radius:0">
<div class="login-wrap">
<div class="login-card">
<div class="login-logo">📦</div>
<div class="login-title">Verdaccio Manager</div>
<div class="login-sub">欢迎回来,请登录你的账户</div>
<div class="login-field"><span class="login-ico">👤</span><input placeholder="用户名 / 邮箱"></div>
<div class="login-field"><span class="login-ico">🔒</span><input type="password" placeholder="密码"><span class="login-eye">👁️</span></div>
<button class="login-btn">登 录</button>
<div class="login-extra"><label><input type="checkbox"> 记住我</label><a>忘记密码?</a></div>
<div class="login-foot"><b>三端适配</b> · 桌面 420px 居中 · 平板 100%(max 420px)· 手机 100% 全宽 + 输入框 44px · 横屏左右分栏</div>
</div>
</div>
</section>
</div>
<div class="note">Verdaccio Cache Manager · 设计稿 v0.2 · 多巴胺 C 端风格 · 依据 doc/Verdaccio 缓存可视化管理面板 — 需求文档.docx(前端界面设计文档)输出</div>
<script>
/* 截图辅助:URL hash 指定画板时,仅展示该画板(用于生成单帧截图) */
(function () {
var hash = location.hash.replace('#', '');
var frames = ['frame-overview','frame-search','frame-threshold','frame-drawer','frame-ignore','frame-logs','frame-mobile','frame-login'];
if (frames.indexOf(hash) === -1) return;
document.querySelectorAll('.frame-wrap').forEach(function (w) {
var f = w.querySelector('.frame');
if (f.id !== hash) w.style.display = 'none';
});
document.querySelector('.board-head').style.display = 'none';
document.querySelector('.note').style.display = 'none';
document.querySelector('.board').style.paddingTop = '0';
document.body.style.paddingBottom = '0';
document.getElementById(hash).style.borderRadius = '0';
})();
</script>
</body>
</html>