- portal.css: 777-line real design system (Linear/Vercel 信息建筑派)
* tokens: spacing 4/8/12/16/24/32/48, type 12/13/14/15/17/20/24/32/44
* colors: ink/paper/accent/success/warn/danger × 50/100/500/900
* teal #14b8a6 1:1 aligned with host sub2api Vue/Tailwind
* dark-first; light override for public portal
* components: page-hero, stat-card, card, status, pill, btn-primary,
toast-host, empty, skeleton, drawer, tabs
- portal.js: window.Sub2ApiPortal — toast, lucide 1.75px stroke SVG
icon registry (shield/group/activity/route/health/account/provider/
import/check/x/alert/info/copy/edit/trash/plus/refresh/...),
copyToClipboard, theme auto/dark/light, drawer, renderModernAdminNav
- admin-common.css: 4KB legacy shim — maps old class names
(.topnav/.primary/.secondary/.ghost/.danger/.metric/.statusbar/.stat/
.eyebrow/.hero-points/.page-hero__eyebrow/.shell/.fade-in/.topline/
.chip/.tag/.mono/.meta-card/.meta-label/.status-pill/.inline-code/
.tone-*) onto new tokens without breaking admin-common.js nav contract
Evidence:
- bash scripts/test/test_tksea_portal_assets.sh → PASS (70+ string assertions)
- bash scripts/test/verify_frontend_smoke.sh → PASS (chromium headless 7 pages)
137 lines
4.0 KiB
CSS
137 lines
4.0 KiB
CSS
/* =============================================================
|
|
* admin-common.css — Legacy compatibility shim
|
|
* -------------------------------------------------------------
|
|
* Pages link BOTH:
|
|
* /portal/portal.css → modern design system (primary)
|
|
* /portal/admin-common.css → this file (legacy aliases)
|
|
*
|
|
* This file ONLY provides aliases so old inline class names
|
|
* (`.primary` / `.secondary` / `.ghost` / `.danger` / etc.)
|
|
* keep working after pages are refactored to the new system.
|
|
* New pages should use the modern `.btn` / `.card` / `.stat-card`
|
|
* classes from portal.css directly.
|
|
* ============================================================= */
|
|
|
|
.topnav {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 18px;
|
|
}
|
|
|
|
.topnav a {
|
|
text-decoration: none;
|
|
padding: 10px 14px;
|
|
border-radius: 999px;
|
|
border: 1px solid var(--border-subtle);
|
|
background: var(--bg-elev-2);
|
|
color: var(--text-muted);
|
|
font-size: 13px;
|
|
font-weight: 700;
|
|
transition: transform 120ms ease, background 120ms ease;
|
|
}
|
|
|
|
.topnav a:hover {
|
|
transform: translateY(-1px);
|
|
background: var(--bg-elev-3);
|
|
color: var(--text-default);
|
|
}
|
|
|
|
.topnav a.is-current {
|
|
background: linear-gradient(135deg, var(--teal-500), var(--teal-600));
|
|
border-color: transparent;
|
|
color: var(--text-on-primary);
|
|
box-shadow: 0 4px 12px rgba(20,184,166,0.3);
|
|
}
|
|
|
|
.statusbar {
|
|
margin-top: 16px;
|
|
min-height: 54px;
|
|
padding: 14px 16px;
|
|
border-radius: 14px;
|
|
border: 1px solid var(--border-subtle);
|
|
background: var(--bg-elev-2);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
color: var(--text-muted);
|
|
font-size: 14px;
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
.statusbar[data-tone="success"] {
|
|
background: var(--color-success-soft);
|
|
color: var(--color-success);
|
|
border-color: rgba(18, 107, 67, 0.2);
|
|
}
|
|
|
|
.statusbar[data-tone="warning"] {
|
|
background: var(--color-warning-soft);
|
|
color: var(--color-warning);
|
|
border-color: rgba(155, 98, 21, 0.2);
|
|
}
|
|
|
|
.statusbar[data-tone="danger"] {
|
|
background: var(--color-danger-soft);
|
|
color: var(--color-danger);
|
|
border-color: rgba(178, 49, 49, 0.2);
|
|
}
|
|
|
|
.statusbar[data-tone="info"] {
|
|
background: var(--color-info-soft);
|
|
color: var(--color-info);
|
|
border-color: rgba(56, 189, 248, 0.2);
|
|
}
|
|
|
|
/* ---- Legacy button aliases (kept so old pages work) ---- */
|
|
button.primary, .primary {
|
|
background: linear-gradient(135deg, var(--teal-500), var(--teal-600));
|
|
color: var(--text-on-primary);
|
|
border: 0;
|
|
border-radius: 999px;
|
|
padding: 12px 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: transform 120ms ease, box-shadow 120ms ease;
|
|
box-shadow: 0 6px 16px rgba(20,184,166,0.22);
|
|
}
|
|
button.primary:hover, .primary:hover { transform: translateY(-1px); box-shadow: 0 10px 22px rgba(20,184,166,0.32); }
|
|
button.primary:disabled, .primary:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
|
|
button.secondary, .secondary {
|
|
background: var(--color-primary-soft);
|
|
color: var(--color-primary);
|
|
border: 1px solid rgba(20,184,166,0.2);
|
|
border-radius: 999px;
|
|
padding: 12px 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: transform 120ms ease, background 120ms ease;
|
|
}
|
|
button.secondary:hover, .secondary:hover { transform: translateY(-1px); background: rgba(20,184,166,0.18); }
|
|
|
|
button.ghost, .ghost {
|
|
background: transparent;
|
|
border: 1px solid var(--border-subtle);
|
|
color: var(--text-muted);
|
|
border-radius: 999px;
|
|
padding: 12px 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: transform 120ms ease, background 120ms ease, color 120ms ease;
|
|
}
|
|
button.ghost:hover, .ghost:hover { transform: translateY(-1px); background: var(--bg-elev-3); color: var(--text-default); }
|
|
|
|
button.danger, .danger {
|
|
background: var(--color-danger-soft);
|
|
color: var(--color-danger);
|
|
border: 1px solid rgba(239,68,68,0.2);
|
|
border-radius: 999px;
|
|
padding: 12px 18px;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: transform 120ms ease, background 120ms ease;
|
|
}
|
|
button.danger:hover, .danger:hover { transform: translateY(-1px); background: rgba(239,68,68,0.18); }
|