fd715eac2b
新增: - services/web_panel/routes/files.py — 全功能文件管理后端API (11个端点) - pages/files.html + files.js — 文件管理前端 (浏览/编辑/上传/删除/右键菜单) - 插件Picker API: window.open+postMessage唤出文件选择器 - 符号链接目录双层面包屑 (逻辑路径+物理路径) - 文件系统全访问+跨平台 (Linux/Windows/macOS) - permission_rules.yaml新增4个filemanager.*权限 修复: - SVG fill=currentColor 日夜模式自适应 - 特殊目录容错 (/dev/fd损坏符号链接/proc) - 面包屑每层级独立可点击+可编辑路径跳转 - ..行返回上级+data-is-dir补全 - rmlint→lstat回退 损坏符号链接不炸页面 文档: - 插件开发指南新增第八章(文件管理器集成) - 开发踩坑记录新增4条(特殊目录/双面包屑/SVG颜色/Windows) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
483 lines
26 KiB
CSS
483 lines
26 KiB
CSS
/* ═══════════════════════════════════════════
|
|
SenSu Web Panel — Material Design 3 2026
|
|
═══════════════════════════════════════════ */
|
|
|
|
/* ── MD3 Color Tokens (Dark Theme) ── */
|
|
:root {
|
|
--md-sys-color-primary: #D0BCFF;
|
|
--md-sys-color-on-primary: #381E72;
|
|
--md-sys-color-primary-container: #4F378B;
|
|
--md-sys-color-on-primary-container: #EADDFF;
|
|
--md-sys-color-secondary: #CCC2DC;
|
|
--md-sys-color-on-secondary: #332D41;
|
|
--md-sys-color-secondary-container: #4A4458;
|
|
--md-sys-color-on-secondary-container: #E8DEF8;
|
|
--md-sys-color-tertiary: #EFB8C8;
|
|
--md-sys-color-on-tertiary: #492532;
|
|
--md-sys-color-tertiary-container: #633B48;
|
|
--md-sys-color-on-tertiary-container: #FFD8E4;
|
|
--md-sys-color-error: #F2B8B5;
|
|
--md-sys-color-on-error: #601410;
|
|
--md-sys-color-error-container: #8C1D18;
|
|
--md-sys-color-on-error-container: #F9DEDC;
|
|
|
|
--md-sys-color-surface: #141218;
|
|
--md-sys-color-on-surface: #E6E1E5;
|
|
--md-sys-color-surface-variant: #49454F;
|
|
--md-sys-color-on-surface-variant: #CAC4D0;
|
|
--md-sys-color-outline: #938F99;
|
|
--md-sys-color-outline-variant: #49454F;
|
|
|
|
--md-sys-color-surface-container-lowest: #0F0D13;
|
|
--md-sys-color-surface-container-low: #1D1B20;
|
|
--md-sys-color-surface-container: #211F26;
|
|
--md-sys-color-surface-container-high: #2B2930;
|
|
--md-sys-color-surface-container-highest: #36343B;
|
|
|
|
/* Aliases for quick refactoring */
|
|
--bg: var(--md-sys-color-surface);
|
|
--bg-card: var(--md-sys-color-surface-container);
|
|
--bg-elevated: var(--md-sys-color-surface-container-high);
|
|
--bg-hover: var(--md-sys-color-surface-container-highest);
|
|
--text: var(--md-sys-color-on-surface);
|
|
--text-dim: var(--md-sys-color-on-surface-variant);
|
|
--primary: var(--md-sys-color-primary);
|
|
--on-primary: var(--md-sys-color-on-primary);
|
|
--primary-container: var(--md-sys-color-primary-container);
|
|
--outline: var(--md-sys-color-outline-variant);
|
|
--error: var(--md-sys-color-error);
|
|
|
|
/* MD3 Elevation */
|
|
--md-sys-elevation-0: none;
|
|
--md-sys-elevation-1: 0 1px 2px 0 rgba(0,0,0,.3), 0 1px 3px 1px rgba(0,0,0,.15);
|
|
--md-sys-elevation-2: 0 1px 2px 0 rgba(0,0,0,.3), 0 2px 6px 2px rgba(0,0,0,.15);
|
|
--md-sys-elevation-3: 0 4px 8px 3px rgba(0,0,0,.15), 0 1px 3px 0 rgba(0,0,0,.3);
|
|
--md-sys-elevation-4: 0 6px 10px 4px rgba(0,0,0,.15), 0 2px 3px 0 rgba(0,0,0,.3);
|
|
--md-sys-elevation-5: 0 8px 12px 6px rgba(0,0,0,.15), 0 4px 4px 0 rgba(0,0,0,.3);
|
|
|
|
/* MD3 Shape */
|
|
--shape-xs: 8px; --shape-sm: 12px; --shape-md: 16px; --shape-lg: 24px; --shape-xl: 28px; --shape-full: 9999px;
|
|
|
|
/* Layout */
|
|
--sidebar-w: 260px; --sidebar-collapsed: 64px; --topbar-h: 64px;
|
|
}
|
|
|
|
/* ── Reset ── */
|
|
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
svg{fill:currentColor}
|
|
body{
|
|
background:var(--bg);color:var(--text);
|
|
font-family:"Google Sans",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
|
|
font-size:14px;line-height:1.5;height:100vh;overflow:hidden;
|
|
-webkit-font-smoothing:antialiased;
|
|
}
|
|
|
|
/* ── Typography Scale ── */
|
|
h2{font-size:1.5rem;font-weight:500;letter-spacing:0;color:var(--text)}
|
|
h3{font-size:1rem;font-weight:500;letter-spacing:.15px}
|
|
.display{font-size:2.25rem;font-weight:400;line-height:1.1}
|
|
.headline{font-size:1.75rem;font-weight:400}
|
|
.title{font-size:1.25rem;font-weight:500}
|
|
.label{font-size:.75rem;font-weight:500;letter-spacing:.5px;text-transform:uppercase}
|
|
.mono{font-family:"JetBrains Mono","Fira Code","Cascadia Code",monospace}
|
|
|
|
/* ── MD3 Surface Tint ── */
|
|
.surface{background:var(--bg-card);border-radius:var(--shape-md)}
|
|
.surface-high{background:var(--bg-elevated);border-radius:var(--shape-md)}
|
|
.surface-highest{background:var(--bg-hover);border-radius:var(--shape-md)}
|
|
|
|
/* ── MD3 Card ── */
|
|
.card{
|
|
background:var(--bg-card);border-radius:var(--shape-sm);
|
|
padding:16px;box-shadow:var(--md-sys-elevation-1);
|
|
transition:box-shadow .2s,background .2s;
|
|
}
|
|
.card:hover{box-shadow:var(--md-sys-elevation-2);background:var(--bg-elevated)}
|
|
.card.outlined{box-shadow:none;border:1px solid var(--outline)}
|
|
.card.outlined:hover{border-color:var(--primary);box-shadow:none}
|
|
|
|
/* ── MD3 Button Variants ── */
|
|
.btn{
|
|
display:inline-flex;align-items:center;justify-content:center;gap:8px;
|
|
padding:10px 24px;border:none;border-radius:var(--shape-full);font-size:14px;
|
|
font-weight:500;letter-spacing:.1px;cursor:pointer;transition:.2s;text-decoration:none;
|
|
line-height:1.25;white-space:nowrap;position:relative;overflow:hidden;
|
|
}
|
|
.btn-filled{background:var(--primary);color:var(--on-primary)}
|
|
.btn-filled:hover{box-shadow:var(--md-sys-elevation-1);filter:brightness(1.08)}
|
|
.btn-tonal{background:var(--primary-container);color:var(--md-sys-color-on-primary-container)}
|
|
.btn-tonal:hover{filter:brightness(1.1)}
|
|
.btn-outlined{border:1px solid var(--outline);background:transparent;color:var(--primary)}
|
|
.btn-outlined:hover{border-color:var(--primary);background:rgba(208,188,255,.08)}
|
|
.btn-text{background:transparent;color:var(--primary)}
|
|
.btn-text:hover{background:rgba(208,188,255,.08)}
|
|
.btn-danger{color:var(--error)}
|
|
.btn-danger:hover{background:rgba(242,184,181,.08)}
|
|
.btn-sm{padding:6px 16px;font-size:12px}
|
|
.btn-lg{padding:14px 32px;font-size:16px}
|
|
.btn-icon{width:40px;height:40px;padding:0;border-radius:var(--shape-full)}
|
|
.btn:disabled{opacity:.38;pointer-events:none}
|
|
|
|
/* ── MD3 Input ── */
|
|
.input-group{margin-bottom:16px;text-align:left}
|
|
.input-group label{display:block;margin-bottom:6px;color:var(--text-dim);font-size:.8rem;font-weight:500;letter-spacing:.3px}
|
|
.input{
|
|
width:100%;padding:12px 16px;background:var(--md-sys-color-surface-container-lowest);
|
|
border:1px solid var(--outline);border-radius:var(--shape-xs);color:var(--text);
|
|
font-size:14px;transition:border-color .2s,box-shadow .2s;outline:none;
|
|
}
|
|
.input:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(208,188,255,.2)}
|
|
.input::placeholder{color:var(--text-dim);opacity:.6}
|
|
|
|
/* ── MD3 FAB ── */
|
|
.fab{
|
|
width:56px;height:56px;border-radius:var(--shape-md);background:var(--primary-container);
|
|
color:var(--md-sys-color-on-primary-container);border:none;cursor:pointer;
|
|
display:flex;align-items:center;justify-content:center;box-shadow:var(--md-sys-elevation-3);
|
|
position:fixed;bottom:24px;right:24px;font-size:24px;transition:.2s;z-index:100;
|
|
}
|
|
.fab:hover{box-shadow:var(--md-sys-elevation-4);filter:brightness(1.1)}
|
|
|
|
/* ── MD3 Chip ── */
|
|
.chip{
|
|
display:inline-flex;align-items:center;padding:4px 12px;border-radius:var(--shape-xs);
|
|
border:1px solid var(--outline);font-size:12px;color:var(--text-dim);gap:6px;
|
|
background:transparent;transition:.2s;
|
|
}
|
|
.chip.active{background:var(--primary-container);color:var(--md-sys-color-on-primary-container);border-color:transparent}
|
|
|
|
/* ── Login Page ── */
|
|
.login-wrapper{
|
|
display:flex;align-items:center;justify-content:center;height:100vh;
|
|
background:radial-gradient(circle at 50% 0%,var(--md-sys-color-primary-container) 0%,var(--bg) 70%);
|
|
}
|
|
.login-card{
|
|
background:var(--md-sys-color-surface-container-high);border-radius:var(--shape-xl);
|
|
padding:40px 32px;width:360px;text-align:center;box-shadow:var(--md-sys-elevation-4);
|
|
}
|
|
.login-card h2{color:var(--primary);margin-bottom:24px}
|
|
.err-msg{color:var(--error);font-size:.8rem;margin-top:12px;min-height:1.2rem}
|
|
|
|
/* ── App Shell ── */
|
|
.app-frame{
|
|
display:grid;grid-template-columns:var(--sidebar-w) 1fr;
|
|
grid-template-rows:var(--topbar-h) 1fr;height:100vh;transition:grid-template-columns .3s cubic-bezier(.4,0,.2,1);
|
|
}
|
|
.app-frame.collapsed{grid-template-columns:var(--sidebar-collapsed) 1fr}
|
|
|
|
/* ── Top Bar ── */
|
|
.top-bar{
|
|
grid-column:1/-1;display:flex;align-items:center;justify-content:space-between;
|
|
padding:0 24px;background:var(--md-sys-color-surface-container);z-index:10;
|
|
border-bottom:1px solid var(--outline);
|
|
}
|
|
.top-bar .title{font-size:1.15rem;font-weight:500;color:var(--primary);display:flex;align-items:center;gap:8px}
|
|
.top-bar .user-info{display:flex;align-items:center;gap:12px;color:var(--text-dim);font-size:.85rem}
|
|
.top-bar .logout-btn{background:transparent;border:1px solid var(--outline);color:var(--text-dim);padding:6px 14px;border-radius:var(--shape-full);cursor:pointer;transition:.2s}
|
|
.top-bar .logout-btn:hover{border-color:var(--error);color:var(--error)}
|
|
|
|
/* ── Navigation Rail / Sidebar ── */
|
|
.sidebar{
|
|
background:var(--md-sys-color-surface-container-low);display:flex;flex-direction:column;
|
|
padding:8px 0;overflow:hidden;transition:.3s;gap:2px;
|
|
}
|
|
.nav-item{
|
|
display:flex;align-items:center;padding:14px 16px;color:var(--text-dim);
|
|
text-decoration:none;cursor:pointer;transition:.15s;white-space:nowrap;
|
|
gap:12px;margin:0 8px;border-radius:var(--shape-full);font-weight:500;font-size:14px;
|
|
position:relative;
|
|
}
|
|
.nav-item:hover{background:rgba(208,188,255,.08);color:var(--text)}
|
|
.nav-item.active{background:var(--primary-container);color:var(--md-sys-color-on-primary-container)}
|
|
.nav-item .nav-icon{width:24px;height:24px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
|
|
.nav-item svg{width:20px;height:20px;flex-shrink:0;fill:currentColor}
|
|
.nav-item .nav-label{overflow:hidden;text-overflow:ellipsis}
|
|
.toggle-sidebar{
|
|
margin-top:auto;padding:16px;text-align:center;cursor:pointer;
|
|
color:var(--text-dim);border-top:1px solid var(--outline);
|
|
transition:.2s;font-size:12px;
|
|
}
|
|
.toggle-sidebar:hover{color:var(--primary)}
|
|
|
|
/* ── Main Content ── */
|
|
.content-area{overflow:hidden;display:flex;flex-direction:column;background:var(--bg)}
|
|
.progress-bar{
|
|
position:absolute;top:0;left:0;height:3px;background:var(--primary);
|
|
width:0;transition:width .3s,opacity .3s;opacity:0;z-index:100;border-radius:0 2px 2px 0;
|
|
}
|
|
.progress-bar.active{opacity:1}
|
|
.page-container{flex:1;padding:24px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--outline) transparent}
|
|
|
|
/* ── Dashboard ── */
|
|
.dash-layout{display:flex;gap:24px;height:calc(100vh - var(--topbar-h) - 48px);overflow:hidden}
|
|
.dash-main{flex:1;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--outline) transparent}
|
|
.dash-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}
|
|
.stat-card{background:var(--bg-card);border-radius:var(--shape-sm);padding:20px;box-shadow:var(--md-sys-elevation-1);display:flex;flex-direction:column;gap:8px;transition:.2s}
|
|
.stat-card:hover{box-shadow:var(--md-sys-elevation-2)}
|
|
.stat-card h3{font-size:.8rem;color:var(--text-dim);text-transform:uppercase;letter-spacing:.5px;font-weight:500}
|
|
.stat-value{font-size:2rem;font-weight:400;color:var(--text)}
|
|
.stat-sub{font-size:.75rem;color:var(--text-dim)}
|
|
.mini-chart{width:100%;height:64px;background:rgba(208,188,255,.05);border-radius:var(--shape-xs);margin-top:8px}
|
|
.dash-sidebar{width:340px;flex-shrink:0;display:flex;flex-direction:column;gap:16px;overflow-y:auto}
|
|
.side-card{background:var(--bg-card);border-radius:var(--shape-sm);padding:20px;box-shadow:var(--md-sys-elevation-1)}
|
|
.side-card h3{font-size:.85rem;color:var(--primary);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--outline)}
|
|
.info-row{display:flex;justify-content:space-between;font-size:.82rem;padding:6px 0;color:var(--text-dim)}
|
|
.info-value{color:var(--text);font-family:monospace;font-weight:500;text-align:right;max-width:60%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
|
|
/* ── Terminal / Log ── */
|
|
.terminal{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--shape-sm);overflow:hidden;height:75vh;display:flex;flex-direction:column;box-shadow:var(--md-sys-elevation-1)}
|
|
.term-header{background:var(--bg-card);padding:8px 16px;font-size:.8rem;color:var(--text-dim);display:flex;justify-content:space-between;align-items:center}
|
|
.term-body{flex:1;padding:12px;overflow-y:auto;font-family:"JetBrains Mono",monospace;font-size:.8rem;color:#ccc;line-height:1.6;scrollbar-width:thin}
|
|
.term-input-area{display:flex;border-top:1px solid var(--outline)}
|
|
.term-input{flex:1;background:transparent;border:none;padding:12px 16px;color:var(--text);font-family:monospace;outline:none;font-size:.85rem}
|
|
|
|
/* ── Log entries ── */
|
|
.log-entry{padding:3px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:.8rem}
|
|
.log-INFO{color:var(--primary)}.log-WARNING{color:#E0AF68}.log-ERROR{color:var(--error)}
|
|
|
|
/* ── Plugin Cards ── */
|
|
.plugin-list{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}
|
|
.plugin-card{background:var(--bg-card);border-radius:var(--shape-sm);padding:20px;box-shadow:var(--md-sys-elevation-1);display:flex;justify-content:space-between;align-items:center;transition:.2s}
|
|
.plugin-card:hover{box-shadow:var(--md-sys-elevation-2)}
|
|
.plugin-info h4{color:var(--primary);font-weight:500;margin-bottom:4px}
|
|
.plugin-info p{font-size:.8rem;color:var(--text-dim)}
|
|
.badge{padding:4px 12px;border-radius:var(--shape-full);font-size:.7rem;font-weight:600;letter-spacing:.3px}
|
|
.badge-run{background:rgba(158,206,106,.15);color:#9ece6a}
|
|
.badge-stop{background:rgba(242,184,181,.15);color:var(--error)}
|
|
.plugin-act{display:flex;gap:8px}
|
|
|
|
/* ── Project / Proxy page cards (shared) ── */
|
|
.mgmt-card{background:var(--bg-card);border-radius:var(--shape-sm);padding:16px;box-shadow:var(--md-sys-elevation-1);margin-bottom:12px;display:flex;align-items:center;gap:16px;transition:.2s}
|
|
.mgmt-card:hover{box-shadow:var(--md-sys-elevation-2)}
|
|
.mgmt-card .info{flex:1}
|
|
.mgmt-card .info b{color:var(--primary);font-weight:500}
|
|
.mgmt-card .info small{color:var(--text-dim);font-size:.8rem}
|
|
.mgmt-card .actions{display:flex;gap:8px;flex-shrink:0}
|
|
.status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
|
.status-running{background:#9ece6a;box-shadow:0 0 8px rgba(158,206,106,.4)}
|
|
.status-stopped{background:var(--error);box-shadow:0 0 8px rgba(242,184,181,.4)}
|
|
.status-error{background:#E0AF68;box-shadow:0 0 8px rgba(224,175,104,.4)}
|
|
.log-box{background:var(--md-sys-color-surface-container-lowest);border-radius:var(--shape-xs);padding:8px 12px;max-height:300px;overflow-y:auto;font-family:monospace;font-size:11px;color:#0f0;margin-top:8px;display:none}
|
|
|
|
/* ── Tabs ── */
|
|
.tabs{display:flex;gap:0;margin-bottom:20px;background:var(--bg-card);border-radius:var(--shape-full);padding:4px;width:fit-content}
|
|
.tab{padding:8px 20px;border-radius:var(--shape-full);cursor:pointer;font-size:13px;font-weight:500;color:var(--text-dim);transition:.2s}
|
|
.tab.active{background:var(--primary-container);color:var(--md-sys-color-on-primary-container)}
|
|
.tab:hover:not(.active){color:var(--text)}
|
|
|
|
/* ── Responsive ── */
|
|
@media(max-width:1100px){.dash-layout{flex-direction:column;height:auto;overflow:visible}.dash-sidebar{width:100%}}
|
|
@media(max-width:768px){.app-frame{grid-template-columns:var(--sidebar-collapsed) 1fr}.nav-label{display:none}.toggle-sidebar{display:none}}
|
|
|
|
/* ═══════════════════════════════════════════
|
|
MD3 Light Theme
|
|
═══════════════════════════════════════════ */
|
|
[data-theme="light"] {
|
|
--md-sys-color-primary: #6750A4;
|
|
--md-sys-color-on-primary: #FFFFFF;
|
|
--md-sys-color-primary-container: #EADDFF;
|
|
--md-sys-color-on-primary-container: #21005D;
|
|
|
|
--md-sys-color-secondary: #625B71;
|
|
--md-sys-color-on-secondary: #FFFFFF;
|
|
--md-sys-color-secondary-container: #E8DEF8;
|
|
--md-sys-color-on-secondary-container: #1D192B;
|
|
|
|
--md-sys-color-tertiary: #7D5260;
|
|
--md-sys-color-on-tertiary: #FFFFFF;
|
|
--md-sys-color-tertiary-container: #FFD8E4;
|
|
--md-sys-color-on-tertiary-container: #31111D;
|
|
|
|
--md-sys-color-error: #B3261E;
|
|
--md-sys-color-on-error: #FFFFFF;
|
|
--md-sys-color-error-container: #F9DEDC;
|
|
--md-sys-color-on-error-container: #410E0B;
|
|
|
|
--md-sys-color-surface: #FFFBFE;
|
|
--md-sys-color-on-surface: #1C1B1F;
|
|
--md-sys-color-surface-variant: #E7E0EC;
|
|
--md-sys-color-on-surface-variant: #49454F;
|
|
--md-sys-color-outline: #79747E;
|
|
--md-sys-color-outline-variant: #CAC4D0;
|
|
|
|
--md-sys-color-surface-container-lowest: #FFFFFF;
|
|
--md-sys-color-surface-container-low: #F7F2FA;
|
|
--md-sys-color-surface-container: #F3EDF7;
|
|
--md-sys-color-surface-container-high: #ECE6F0;
|
|
--md-sys-color-surface-container-highest: #E6E0E9;
|
|
|
|
--md-sys-elevation-1: 0 1px 2px 0 rgba(0,0,0,.05), 0 1px 3px 1px rgba(0,0,0,.08);
|
|
--md-sys-elevation-2: 0 1px 2px 0 rgba(0,0,0,.08), 0 2px 6px 2px rgba(0,0,0,.06);
|
|
--md-sys-elevation-3: 0 4px 8px 3px rgba(0,0,0,.06), 0 1px 3px 0 rgba(0,0,0,.08);
|
|
--md-sys-elevation-4: 0 6px 10px 4px rgba(0,0,0,.05), 0 2px 3px 0 rgba(0,0,0,.08);
|
|
--md-sys-elevation-5: 0 8px 12px 6px rgba(0,0,0,.04), 0 4px 4px 0 rgba(0,0,0,.08);
|
|
|
|
--bg: var(--md-sys-color-surface);
|
|
--bg-card: var(--md-sys-color-surface-container);
|
|
--bg-elevated: var(--md-sys-color-surface-container-high);
|
|
--bg-hover: var(--md-sys-color-surface-container-highest);
|
|
--text: var(--md-sys-color-on-surface);
|
|
--text-dim: var(--md-sys-color-on-surface-variant);
|
|
--primary: var(--md-sys-color-primary);
|
|
--on-primary: var(--md-sys-color-on-primary);
|
|
--primary-container: var(--md-sys-color-primary-container);
|
|
--outline: var(--md-sys-color-outline-variant);
|
|
--error: var(--md-sys-color-error);
|
|
}
|
|
|
|
/* ── Theme Toggle Button ── */
|
|
.theme-toggle{
|
|
display:flex;align-items:center;justify-content:center;
|
|
width:40px;height:40px;border-radius:var(--shape-full);
|
|
background:transparent;border:none;color:var(--text-dim);cursor:pointer;
|
|
font-size:20px;transition:.2s;
|
|
}
|
|
.theme-toggle:hover{background:rgba(208,188,255,.08)}
|
|
|
|
/* ═══════════════════════════════════════════
|
|
MD3 Animations & Micro-interactions
|
|
═══════════════════════════════════════════ */
|
|
|
|
/* ── Button Ripple ── */
|
|
.btn{position:relative;overflow:hidden;transform:translateZ(0)}
|
|
.btn::after{
|
|
content:"";position:absolute;inset:0;background:radial-gradient(circle at center,currentColor 10%,transparent 10%);
|
|
background-size:0 0;background-repeat:no-repeat;opacity:0;transition:background-size .4s,opacity .3s;
|
|
}
|
|
.btn:active::after{background-size:300% 300%;opacity:.12;transition:0s}
|
|
|
|
/* ── Card Elevation Transition ── */
|
|
.card,.stat-card,.mgmt-card,.side-card{
|
|
transition:box-shadow .3s cubic-bezier(.4,0,.2,1),transform .2s cubic-bezier(.4,0,.2,1),background .3s;
|
|
}
|
|
.card:hover,.stat-card:hover,.mgmt-card:hover{transform:translateY(-1px)}
|
|
.card:active{transform:translateY(0)}
|
|
|
|
/* ── Page Transition Fade ── */
|
|
.page-container{animation:pageIn .25s cubic-bezier(.4,0,.2,1)}
|
|
@keyframes pageIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}
|
|
|
|
/* ── Tab Indicator Slide ── */
|
|
.tab{position:relative;transition:color .2s,background .25s}
|
|
.tab.active::after{
|
|
content:"";position:absolute;bottom:0;left:25%;right:25%;height:2px;
|
|
background:var(--primary);border-radius:2px 2px 0 0;
|
|
animation:tabSlide .3s cubic-bezier(.4,0,.2,1)
|
|
}
|
|
@keyframes tabSlide{from{transform:scaleX(0)}to{transform:scaleX(1)}}
|
|
|
|
/* ── Input Focus Glow ── */
|
|
.input{transition:border-color .25s,box-shadow .25s,background .25s}
|
|
.input:focus{animation:inputGlow .6s ease-out}
|
|
@keyframes inputGlow{0%{box-shadow:0 0 0 0 rgba(208,188,255,.4)}100%{box-shadow:0 0 0 4px rgba(208,188,255,0)}}
|
|
|
|
/* ── Sidebar Collapse ── */
|
|
.sidebar{transition:width .3s cubic-bezier(.4,0,.2,1)}
|
|
.nav-item .nav-label{transition:opacity .25s,width .3s;opacity:1}
|
|
.app-frame.collapsed .nav-label{opacity:0;width:0}
|
|
|
|
/* ── Nav Item Active Dot ── */
|
|
.nav-item.active::before{
|
|
content:"";position:absolute;left:0;top:50%;transform:translateY(-50%);
|
|
width:3px;height:20px;background:var(--primary);border-radius:0 3px 3px 0;
|
|
animation:navDotIn .3s cubic-bezier(.4,0,.2,1)
|
|
}
|
|
@keyframes navDotIn{from{height:0;opacity:0}to{height:20px;opacity:1}}
|
|
|
|
/* ── Status Dot Pulse ── */
|
|
.status-running{animation:statusPulse 2s ease-in-out infinite}
|
|
@keyframes statusPulse{0%,100%{box-shadow:0 0 4px rgba(158,206,106,.4)}50%{box-shadow:0 0 12px rgba(158,206,106,.7)}}
|
|
|
|
/* ── Loading Skeleton Shimmer ── */
|
|
.skeleton{background:linear-gradient(90deg,var(--bg-card) 25%,var(--bg-elevated) 50%,var(--bg-card) 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:var(--shape-xs);height:1rem}
|
|
@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
|
|
|
|
/* ── Progress Bar ── */
|
|
.progress-bar{transition:width .3s cubic-bezier(.4,0,.2,1),opacity .2s}
|
|
|
|
/* ── Badge Bounce ── */
|
|
@keyframes badgeIn{0%{transform:scale(0);opacity:0}60%{transform:scale(1.2)}100%{transform:scale(1);opacity:1}}
|
|
.badge{animation:badgeIn .3s cubic-bezier(.4,0,.2,1)}
|
|
|
|
/* ── FAB Float ── */
|
|
.fab{transition:box-shadow .3s,transform .2s cubic-bezier(.4,0,.2,1),filter .2s}
|
|
.fab:hover{transform:scale(1.05)}
|
|
|
|
/* ── Log Entry Fade ── */
|
|
.log-entry{animation:logIn .2s ease-out}
|
|
@keyframes logIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:translateX(0)}}
|
|
|
|
/* ── Toggle Theme Button Spin ── */
|
|
.theme-toggle{transition:transform .3s cubic-bezier(.4,0,.2,1)}
|
|
.theme-toggle:hover{transform:rotate(30deg)}
|
|
|
|
/* ── Scrollbar MD3 Style ── */
|
|
::-webkit-scrollbar{width:6px}
|
|
::-webkit-scrollbar-track{background:transparent}
|
|
::-webkit-scrollbar-thumb{background:var(--outline);border-radius:3px}
|
|
::-webkit-scrollbar-thumb:hover{background:var(--text-dim)}
|
|
|
|
/* ── Ripple on Management Cards ── */
|
|
.mgmt-card,.plugin-card{position:relative;overflow:hidden}
|
|
.mgmt-card::before,.plugin-card::before{
|
|
content:"";position:absolute;inset:0;background:radial-gradient(circle at var(--mouse-x,50%) var(--mouse-y,50%),rgba(208,188,255,.1) 0%,transparent 60%);
|
|
opacity:0;transition:opacity .3s
|
|
}
|
|
.mgmt-card:hover::before,.plugin-card:hover::before{opacity:1}
|
|
|
|
/* ── Menu reveal animation for login error ── */
|
|
.err-msg{transition:opacity .3s}
|
|
|
|
/* ═══════════════════════════════════════════
|
|
File Manager
|
|
═══════════════════════════════════════════ */
|
|
.fm-container{display:flex;flex-direction:column;height:calc(100vh - var(--topbar-h) - 48px);gap:12px}
|
|
.fm-toolbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px}
|
|
.fm-path-bar{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
|
|
.fm-path-input{width:100%;padding:8px 12px;background:var(--md-sys-color-surface-container-lowest);border:1px solid var(--outline);border-radius:var(--shape-xs);color:var(--text);font-family:"JetBrains Mono",monospace;font-size:12px;outline:none;transition:border-color .2s}
|
|
.fm-path-input:focus{border-color:var(--primary);box-shadow:0 0 0 2px rgba(208,188,255,.15)}
|
|
.fm-breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:4px;overflow-x:auto;scrollbar-width:none;flex:1;min-width:0}
|
|
.fm-breadcrumb::-webkit-scrollbar{display:none}
|
|
.fm-crumb{white-space:nowrap;padding:4px 10px;border-radius:var(--shape-full);cursor:pointer;font-size:.82rem;color:var(--text-dim);transition:.15s}
|
|
.fm-crumb:hover{background:rgba(208,188,255,.1);color:var(--primary)}
|
|
.fm-crumb-sep{color:var(--outline);font-size:.7rem;flex-shrink:0}
|
|
.fm-resolved-crumbs{display:flex;align-items:center;gap:4px;font-size:.75rem;opacity:.8;flex-basis:100%}
|
|
.fm-actions{display:flex;gap:6px;flex-shrink:0;align-items:center}
|
|
.fm-toggle{display:flex;align-items:center;gap:4px;font-size:.75rem;color:var(--text-dim);cursor:pointer}
|
|
.fm-toggle input{margin:0}
|
|
.fm-list{flex:1;overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--outline) transparent}
|
|
.fm-row{display:grid;grid-template-columns:32px 1fr 100px 140px;align-items:center;padding:10px 12px;cursor:pointer;border-radius:var(--shape-xs);transition:background .12s;gap:8px;font-size:.88rem}
|
|
.fm-row:hover{background:rgba(208,188,255,.06)}
|
|
.fm-row.fm-dir{font-weight:500}
|
|
.fm-row.fm-parent{border-bottom:1px solid var(--outline);margin-bottom:2px;font-weight:600}
|
|
.fm-icon{font-size:1.2rem;text-align:center}
|
|
.fm-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
.fm-size{text-align:right;color:var(--text-dim);font-size:.8rem;font-variant-numeric:tabular-nums}
|
|
.fm-date{text-align:right;color:var(--text-dim);font-size:.78rem}
|
|
|
|
/* Editor overlay */
|
|
.fm-editor-overlay{position:fixed;inset:0;z-index:200;display:flex;flex-direction:column;background:var(--md-sys-color-surface-container-high)}
|
|
.fm-editor-header{display:flex;justify-content:space-between;align-items:center;padding:12px 20px;border-bottom:1px solid var(--outline);background:var(--bg-card)}
|
|
.fm-editor-header span{font-weight:500;color:var(--primary)}
|
|
.fm-editor-header div{display:flex;gap:8px}
|
|
.fm-editor-overlay textarea{flex:1;padding:16px 20px;background:var(--md-sys-color-surface-container-lowest);color:var(--text);border:none;outline:none;resize:none;font-family:"JetBrains Mono","Fira Code",monospace;font-size:13px;line-height:1.6;tab-size:4}
|
|
|
|
/* Dialog overlay */
|
|
.fm-dialog-overlay{position:fixed;inset:0;z-index:300;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.5)}
|
|
.fm-dialog{background:var(--md-sys-color-surface-container-high);border-radius:var(--shape-md);padding:24px;min-width:320px;max-width:450px;box-shadow:var(--md-sys-elevation-4)}
|
|
|
|
/* Context menu */
|
|
.fm-context{position:fixed;z-index:250;background:var(--md-sys-color-surface-container-high);border-radius:var(--shape-xs);box-shadow:var(--md-sys-elevation-3);min-width:160px;padding:4px 0;overflow:hidden}
|
|
.fm-context-item{padding:8px 16px;cursor:pointer;font-size:.82rem;color:var(--text);transition:background .12s}
|
|
.fm-context-item:hover{background:rgba(208,188,255,.1)}
|
|
.fm-context-item.danger{color:var(--error)}
|
|
.fm-context-item.danger:hover{background:rgba(242,184,181,.1)}
|
|
.fm-context-sep{height:1px;background:var(--outline);margin:4px 0}
|
|
|
|
/* Responsive */
|
|
@media(max-width:768px){
|
|
.fm-row{grid-template-columns:28px 1fr 70px}
|
|
.fm-date{display:none}
|
|
.fm-actions{flex-wrap:wrap}
|
|
}
|