/* Ant Design 风格的令牌与组件，全部手写。
   不引 antd / Element 的产物：/admin/ 的 CSP 是 default-src 'self'; script-src 'self'，
   CDN 上的 JS 和 CSS 会被浏览器直接拦掉，把整包下载进仓库又会让这个只跑在 loopback 的
   工作台多出几 MB 与安全边界无关的第三方代码。所以只照抄设计令牌，不照抄依赖。 */
:root{
 --primary:#1677ff;--primary-hover:#4096ff;--primary-active:#0958d9;--primary-bg:#e6f4ff;--primary-border:#91caff;
 --success:#52c41a;--success-bg:#f6ffed;--success-border:#b7eb8f;
 --warning:#faad14;--warning-bg:#fffbe6;--warning-border:#ffe58f;
 --error:#ff4d4f;--error-bg:#fff2f0;--error-border:#ffccc7;
 --text:rgba(0,0,0,.88);--text-2:rgba(0,0,0,.65);--text-3:rgba(0,0,0,.45);--text-4:rgba(0,0,0,.25);
 --border:#d9d9d9;--split:rgba(5,5,5,.06);--fill:rgba(0,0,0,.04);--fill-2:#fafafa;
 --bg:#fff;--layout-bg:#f5f5f5;--sider:#001529;--sider-sub:#000c17;
 --radius:6px;--radius-lg:8px;--control:32px;
 --shadow:0 1px 2px 0 rgba(0,0,0,.03),0 1px 6px -1px rgba(0,0,0,.02),0 2px 4px 0 rgba(0,0,0,.02);
 --shadow-up:0 6px 16px 0 rgba(0,0,0,.08),0 3px 6px -4px rgba(0,0,0,.12),0 9px 28px 8px rgba(0,0,0,.05);
 --mono:"SFMono-Regular",Consolas,"Liberation Mono",Menlo,monospace;
}
*{box-sizing:border-box}
[hidden]{display:none!important}
body{margin:0;background:var(--layout-bg);color:var(--text);font:14px/1.5714 -apple-system,"PingFang SC","Microsoft YaHei","Segoe UI",Roboto,sans-serif;-webkit-font-smoothing:antialiased}
h1,h2,h3,h4,p{margin:0}
button,input,select,textarea{font:inherit;color:inherit}
button{cursor:pointer;background:none;border:0;padding:0}
button:disabled{cursor:not-allowed}
code,pre,.mono{font-family:var(--mono)}
a{color:var(--primary);text-decoration:none}
a:hover{color:var(--primary-hover)}
:focus-visible{outline:2px solid var(--primary);outline-offset:1px}

/* ---------- layout ---------- */
.layout{display:flex;min-height:100vh}
.sider{width:212px;flex:none;background:var(--sider);color:#fff;display:flex;flex-direction:column;position:sticky;top:0;height:100vh}
.logo{height:56px;display:flex;align-items:center;gap:10px;padding:0 20px;flex:none}
.logo b{width:28px;height:28px;flex:none;display:grid;place-items:center;background:var(--primary);border-radius:var(--radius);font:600 16px/1 Georgia,serif}
.logo span{font-size:15px;font-weight:600;letter-spacing:.3px}
.logo small{display:block;font:400 10px/1.4 var(--mono);color:rgba(255,255,255,.45);letter-spacing:1px}
.menu{padding:8px 0;flex:1;overflow-y:auto}
.menu a{display:flex;align-items:center;gap:10px;height:40px;margin:4px 8px;padding:0 12px;border-radius:var(--radius);color:rgba(255,255,255,.65);font-size:14px;transition:background .2s,color .2s}
.menu a:hover{background:rgba(255,255,255,.08);color:#fff}
.menu a.on{background:var(--primary);color:#fff;font-weight:500}
.menu svg{flex:none;opacity:.9}
.menu .badge{margin-left:auto;min-width:20px;height:18px;padding:0 6px;border-radius:9px;background:rgba(255,255,255,.15);font:500 11px/18px var(--mono);text-align:center}
.menu a.on .badge{background:rgba(255,255,255,.25)}
.sider-foot{flex:none;padding:16px 20px;border-top:1px solid rgba(255,255,255,.1);font-size:11px;line-height:1.9;color:rgba(255,255,255,.45)}
.sider-foot b{display:block;color:rgba(255,255,255,.85);font:500 12px var(--mono)}
.main{flex:1;min-width:0;display:flex;flex-direction:column}
.header{height:56px;flex:none;background:var(--bg);border-bottom:1px solid var(--split);display:flex;align-items:center;gap:16px;padding:0 24px;position:sticky;top:0;z-index:8}
.crumb{font-size:14px;color:var(--text-3);display:flex;gap:8px;align-items:center}
.crumb b{color:var(--text);font-weight:500}
.header-right{margin-left:auto;display:flex;align-items:center;gap:12px}
.content{flex:1;padding:24px;max-width:1680px;width:100%}
.view[hidden]{display:none}
.page-head{margin-bottom:20px;display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap}
.page-head h1{font-size:20px;font-weight:600;line-height:1.4}
.page-head p{margin-top:4px;color:var(--text-3);font-size:13px}
.page-head .actions{margin-left:auto;display:flex;gap:8px;align-items:center}

/* ---------- card ---------- */
.card{background:var(--bg);border-radius:var(--radius-lg);box-shadow:var(--shadow);margin-bottom:16px}
.card:last-child{margin-bottom:0}
.card-head{display:flex;align-items:center;flex-wrap:wrap;gap:12px;padding:16px 24px;border-bottom:1px solid var(--split);min-height:56px}
.card-head h2{font-size:16px;font-weight:600}
.card-head .actions{margin-left:auto;display:flex;gap:8px;align-items:center}
.card-body{padding:24px}
.card-body.tight{padding:16px 24px}
.hint{color:var(--text-3);font-size:12px;line-height:1.7}
.card-head .hint{margin-left:8px}

/* ---------- buttons ---------- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;height:var(--control);padding:0 15px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);color:var(--text);font-size:14px;white-space:nowrap;transition:all .2s}
.btn:hover{color:var(--primary-hover);border-color:var(--primary-hover)}
.btn:active{color:var(--primary-active);border-color:var(--primary-active)}
.btn.primary{background:var(--primary);border-color:var(--primary);color:#fff;box-shadow:0 2px 0 rgba(5,145,255,.1)}
.btn.primary:hover{background:var(--primary-hover);border-color:var(--primary-hover);color:#fff}
.btn.danger{color:var(--error);border-color:var(--error)}
.btn.danger:hover{color:#ff7875;border-color:#ff7875}
.btn.text{border-color:transparent;background:transparent;padding:0 8px}
.btn.text:hover{background:var(--fill)}
.btn.text.danger:hover{background:var(--error-bg)}
.btn.lg{height:40px;padding:0 20px;font-size:15px}
.btn.sm{height:24px;padding:0 8px;font-size:13px}
.btn:disabled,.btn:disabled:hover{color:var(--text-4);border-color:var(--border);background:var(--fill);box-shadow:none}
.btn.primary:disabled,.btn.primary:disabled:hover{background:var(--fill);border-color:var(--border);color:var(--text-4)}
.link{color:var(--primary);font-size:13px;padding:0 4px;background:none;border:0;white-space:nowrap}
.link:hover{color:var(--primary-hover)}
.link.danger{color:var(--error)}
.link.danger:hover{color:#ff7875}
.link:disabled{color:var(--text-4)}
.row-actions{display:flex;align-items:center;gap:2px;white-space:nowrap}
.row-actions .sep{display:inline-block;width:1px;height:14px;background:var(--split)}

/* ---------- form ---------- */
.field{display:flex;flex-direction:column;gap:6px}
.field>span,.field-label{font-size:14px;color:var(--text)}
.field .tip{font-size:12px;color:var(--text-3)}
.field.req>span::before{content:"*";color:var(--error);margin-right:4px;font-family:SimSun,sans-serif}
input[type=text],input[type=url],input[type=password],input:not([type]),select,textarea{width:100%;min-width:0;height:var(--control);padding:4px 11px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);font-size:14px;transition:border-color .2s,box-shadow .2s}
textarea{height:auto;padding:8px 11px}
select{appearance:none;padding-right:28px;background-image:url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2 4.5L6 8.5L10 4.5' stroke='rgba(0,0,0,.45)' stroke-width='1.4' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;background-size:12px}
input:hover,select:hover{border-color:var(--primary-hover)}
input:focus,select:focus,textarea:focus{outline:0;border-color:var(--primary-hover);box-shadow:0 0 0 2px rgba(5,145,255,.1)}
input:read-only,input:disabled,select:disabled{background:var(--fill-2);color:var(--text-3);cursor:not-allowed}
input:user-invalid{border-color:var(--error)}
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:20px 24px}
.grid.one{grid-template-columns:1fr}
.grid .wide{grid-column:1/-1}
.check{display:inline-flex;align-items:center;gap:8px;font-size:14px;cursor:pointer}
.check input{width:16px;height:16px;flex:none;accent-color:var(--primary);cursor:pointer}
.search{position:relative;flex:1 1 200px;max-width:260px}
.search input{padding-left:32px}
.search svg{position:absolute;left:10px;top:9px;pointer-events:none}
.filters{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.filters select{width:auto;min-width:130px}

/* ---------- tag / status ---------- */
.tag{display:inline-flex;align-items:center;gap:5px;height:22px;padding:0 8px;border-radius:4px;border:1px solid var(--split);background:var(--fill-2);color:var(--text-2);font-size:12px;line-height:20px;white-space:nowrap}
.tag.blue{color:var(--primary);background:var(--primary-bg);border-color:var(--primary-border)}
.tag.green{color:#389e0d;background:var(--success-bg);border-color:var(--success-border)}
.tag.orange{color:#d46b08;background:#fff7e6;border-color:#ffd591}
.tag.red{color:#cf1322;background:var(--error-bg);border-color:var(--error-border)}
.tag.purple{color:#531dab;background:#f9f0ff;border-color:#d3adf7}
.tag .dot{width:6px;height:6px;border-radius:50%;background:currentColor}
.dot-state{display:inline-flex;align-items:center;gap:8px;white-space:nowrap}
.dot-state::before{content:"";width:6px;height:6px;border-radius:50%;background:var(--text-3);flex:none}
.dot-state.queued::before{background:var(--text-3)}
.dot-state.running::before{background:var(--primary);box-shadow:0 0 0 3px var(--primary-bg)}
.dot-state.completed::before{background:var(--success)}
.dot-state.failed::before{background:var(--error)}
.dot-state.published::before{background:#722ed1}

/* ---------- table ---------- */
.table-wrap{overflow-x:auto}
table.tbl{width:100%;border-collapse:separate;border-spacing:0;font-size:14px}
.tbl th{background:var(--fill-2);color:var(--text);font-weight:600;text-align:left;padding:12px 16px;border-bottom:1px solid var(--split);white-space:nowrap}
.tbl th:first-child,.tbl td:first-child{padding-left:24px}
.tbl th:last-child,.tbl td:last-child{padding-right:24px}
.tbl td{padding:12px 16px;border-bottom:1px solid var(--split);vertical-align:middle}
.tbl tbody tr:hover td{background:var(--fill-2)}
.tbl tbody tr:last-child td{border-bottom:0}
.tbl .num{font-family:var(--mono);font-size:12px;color:var(--text-2);white-space:nowrap}
.tbl .strong{font-weight:500;white-space:nowrap}
.tbl td>.strong+.tag{margin-left:6px}
.tbl .sub{display:block;color:var(--text-3);font-size:12px;margin-top:2px;white-space:nowrap}
.tbl code{font-size:12px;color:var(--text-2);word-break:break-all}
.col-actions{width:1%;text-align:right}
.empty{padding:56px 24px;text-align:center;color:var(--text-3);position:sticky;left:0}
.empty svg{display:block;margin:0 auto 12px;opacity:.55}
.pagination{display:flex;align-items:center;flex-wrap:wrap;gap:8px;padding:16px 24px;border-top:1px solid var(--split);justify-content:flex-end;font-size:14px}
.pagination .total{margin-right:auto;color:var(--text-3);font-size:13px}
.pagination button{min-width:32px;height:32px;padding:0 6px;border:1px solid var(--border);border-radius:var(--radius);background:var(--bg);font-size:14px}
.pagination button.on{border-color:var(--primary);color:var(--primary)}
.pagination button:disabled{color:var(--text-4);background:var(--fill)}

/* ---------- statistic ---------- */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;margin-bottom:16px}
.stat{background:var(--bg);border-radius:var(--radius-lg);box-shadow:var(--shadow);padding:20px 24px}
.stat span{display:block;color:var(--text-3);font-size:13px}
.stat b{display:block;margin-top:6px;font-size:28px;font-weight:500;line-height:1.2}
.stat small{display:block;margin-top:6px;font-size:12px;color:var(--text-3)}

/* ---------- descriptions ---------- */
.desc{margin:0;display:grid;grid-template-columns:auto 1fr;gap:0;border:1px solid var(--split);border-radius:var(--radius-lg);overflow:hidden}
.desc dt{padding:12px 16px;background:var(--fill-2);color:var(--text-2);font-size:13px;border-bottom:1px solid var(--split);white-space:nowrap}
.desc dd{margin:0;padding:12px 16px;border-bottom:1px solid var(--split);font-size:13px;word-break:break-all;white-space:pre-line}
.desc dt:last-of-type,.desc dd:last-of-type{border-bottom:0}

/* ---------- alert / toast ---------- */
.alert{display:flex;gap:8px;align-items:flex-start;padding:9px 12px;border-radius:var(--radius);font-size:14px;margin-bottom:16px;border:1px solid transparent}
.alert.error{background:var(--error-bg);border-color:var(--error-border);color:var(--text)}
.alert.warning{background:var(--warning-bg);border-color:var(--warning-border)}
.alert.info{background:var(--primary-bg);border-color:var(--primary-border)}
.alert[hidden]{display:none}
#toasts{position:fixed;top:16px;left:0;right:0;z-index:1200;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none}
.toast{display:flex;align-items:center;gap:8px;background:var(--bg);box-shadow:var(--shadow-up);border-radius:var(--radius);padding:9px 16px;font-size:14px;animation:drop .2s ease}
.toast b{font-size:15px}
.toast.success b{color:var(--success)}
.toast.error b{color:var(--error)}
.toast.info b{color:var(--primary)}
@keyframes drop{from{opacity:0;transform:translateY(-12px)}to{opacity:1;transform:none}}

/* ---------- drawer ---------- */
.mask{position:fixed;inset:0;background:rgba(0,0,0,.45);z-index:1000;animation:fade .2s}
.mask[hidden]{display:none}
@keyframes fade{from{opacity:0}to{opacity:1}}
.drawer{position:fixed;top:0;right:0;bottom:0;width:min(760px,96vw);background:var(--bg);z-index:1001;display:flex;flex-direction:column;box-shadow:var(--shadow-up);animation:slide .25s ease}
.drawer[hidden]{display:none}
@keyframes slide{from{transform:translateX(100%)}to{transform:none}}
.drawer-head{flex:none;display:flex;align-items:center;gap:12px;padding:16px 24px;border-bottom:1px solid var(--split)}
.drawer-head h2{font-size:16px;font-weight:600}
.drawer-head .close{margin-left:auto;width:32px;height:32px;border-radius:var(--radius);display:grid;place-items:center;color:var(--text-3)}
.drawer-head .close:hover{background:var(--fill);color:var(--text)}
.drawer-body{flex:1;overflow-y:auto;padding:24px}
.drawer-body>*+*{margin-top:16px}
.drawer-foot{flex:none;display:flex;justify-content:flex-end;gap:8px;padding:12px 24px;border-top:1px solid var(--split)}
.section-title{font-size:14px;font-weight:600;margin-bottom:10px;display:flex;align-items:center;gap:8px}

/* ---------- modal ---------- */
dialog{border:0;border-radius:var(--radius-lg);padding:0;width:min(520px,94vw);box-shadow:var(--shadow-up);color:var(--text)}
dialog::backdrop{background:rgba(0,0,0,.45)}
dialog h2{font-size:16px;font-weight:600;margin-bottom:8px}
dialog form{padding:20px 24px 16px}
dialog .modal-body{margin:12px 0;display:flex;flex-direction:column;gap:12px;font-size:14px}
dialog .dialog-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}

/* ---------- code blocks ---------- */
code.block,pre.block{display:block;background:#f6f8fa;border:1px solid var(--split);border-radius:var(--radius);padding:10px 12px;font-size:12px;line-height:1.7;white-space:pre-wrap;word-break:break-all;margin:0;overflow-x:auto}
pre.log{background:#141414;color:#d9d9d9;max-height:340px;overflow:auto;border-color:#141414}
.copy-line{display:flex;gap:8px;align-items:center}
.copy-line code{flex:1}

/* ---------- editor page ---------- */
.editor-grid{display:grid;grid-template-columns:minmax(0,1fr) 340px;gap:16px;align-items:start}
.preview-card{position:sticky;top:80px}
.preview-card .card-body>*+*{margin-top:16px}
.seg{display:inline-flex;padding:2px;background:var(--fill);border-radius:var(--radius);gap:2px}
.seg button{height:28px;padding:0 16px;border-radius:4px;font-size:14px;color:var(--text-2)}
.seg button[aria-pressed=true]{background:var(--bg);color:var(--text);font-weight:500;box-shadow:0 1px 2px rgba(0,0,0,.06)}
.step{display:inline-grid;place-items:center;width:22px;height:22px;margin-right:8px;border-radius:50%;background:var(--primary-bg);color:var(--primary);font:500 12px var(--mono)}
.ad-table{border:1px solid var(--split);border-radius:var(--radius);overflow:hidden}
.ad-header,.ad-row{display:grid;grid-template-columns:120px minmax(0,1fr) 120px minmax(0,1.4fr);gap:12px;align-items:center;padding:10px 14px}
.ad-header{background:var(--fill-2);font-size:13px;color:var(--text-2);font-weight:500}
.ad-row{border-top:1px solid var(--split)}
.modules{display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));gap:10px}
.modules .check{padding:6px 10px;border:1px solid var(--split);border-radius:var(--radius);font-family:var(--mono);font-size:12px}
.modules .check:has(input:checked){border-color:var(--primary-border);background:var(--primary-bg)}
.modules .check:has(input:disabled){opacity:.7;cursor:not-allowed}
.review-line{display:flex;justify-content:space-between;gap:12px;font-size:13px;padding:8px 0;border-bottom:1px dashed var(--split)}
.review-line span{color:var(--text-3)}
.review-line b{font-weight:500;text-align:right;word-break:break-all}
.editor-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
#saveStatus{font-size:13px;color:var(--success)}
details>summary{cursor:pointer;font-size:13px;color:var(--text-2);padding:6px 0}
details>summary:hover{color:var(--primary)}
details>*+*{margin-top:8px}
.file-line{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:8px 0;border-bottom:1px solid var(--split);font-size:13px}
.file-line .link{padding:0;text-align:left}
.file-line code{font-size:11px;color:var(--text-3);word-break:break-all}

@media(max-width:1180px){.editor-grid{grid-template-columns:1fr}.preview-card{position:static}}
@media(max-width:820px){.header{padding:0 16px;gap:8px}.header-right .tag{display:none}.crumb{min-width:0;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.search{max-width:none}.filters select{flex:1 1 120px;min-width:0}.page-head .actions{margin-left:0}.stats{grid-template-columns:1fr 1fr}.sider{width:64px}.logo span,.menu a span,.sider-foot,.menu .badge{display:none}.logo{justify-content:center;padding:0}.menu a{justify-content:center;padding:0}.content{padding:16px}.ad-header,.ad-row{grid-template-columns:1fr}.ad-header{display:none}}

/* 间距工具类。页面的 CSP 是 style-src 'self'，行内 style="" 会被浏览器整条拒绝，
   所以这些一次性间距也只能落在样式表里。 */
.gap-t-sm{margin-top:6px}
.gap-t{margin-top:12px}
.gap-t-lg{margin-top:16px}
.gap-b-sm{margin-bottom:6px}
.gap-b{margin-bottom:16px}
.gap-b-lg{margin-bottom:20px}
.gap-y{margin:12px 0 20px}
.full{width:100%}
.saved-select{min-width:220px}
.preview-name{font-size:18px;font-weight:600}

/* ---------- 登录（只有公网域名入口会用到；loopback 隧道内没有登录这回事） ---------- */
.login{position:fixed;inset:0;z-index:1500;background:var(--layout-bg);display:grid;place-items:center;padding:24px}
.login-card{width:min(400px,100%);background:var(--bg);border-radius:var(--radius-lg);box-shadow:var(--shadow-up);padding:32px}
.login-card>*+*{margin-top:16px}
.login-brand{display:flex;align-items:center;gap:12px}
.login-brand b{width:36px;height:36px;flex:none;display:grid;place-items:center;background:var(--primary);color:#fff;border-radius:var(--radius);font:600 20px/1 Georgia,serif}
.login-brand span{font-size:17px;font-weight:600}
.login-brand small{display:block;font:400 10px/1.6 var(--mono);letter-spacing:1.5px;color:var(--text-3)}
