Files
WechatOnCloud/panel/web/src/styles.css
T
Gloridust 0ccbaa3a35 feat(admin): 实例「管理」菜单改为悬浮图层展开
绝对定位悬浮层(从按钮下方浮出),不再撑高卡片/顶走下方内容;展开时卡片 overflow:visible
+ z-index:5(盖住下方/同列卡片,仍低于弹窗);加点击外部 / 点击菜单项自动关闭。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 15:15:05 +08:00

2044 lines
41 KiB
CSS
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.
/* WechatOnCloud 面板 —— 牛奶布艺(soft neumorphism+ 微信绿 + iOS26 圆角极简
* 比喻:整个界面是盖在桌面上的一整块白布,组件是被布顶起的物体。
* - 高光在物体顶面(radial,中心偏上)
* - 阴影是布料折痕(小半径、紧贴边缘、冷灰蓝),不是悬浮投影
* - 组件亮度 ≥ 背景;层级靠明度,不靠边框/分割线 */
:root {
--base: #ebedf1; /* 整块布底色(立体档,91% 区间) */
--surface: #ffffff; /* 浮起表面:纯白 */
--trough: #e0e3ea; /* 凹槽:比 base 更暗(输入框/进度槽) */
--shadow: 51 66 102; /* 冷灰蓝阴影 RGB(配合 rgba() */
--wx-green: #07c160;
--wx-green-dark: #06ad56;
--green-rgb: 7 193 96;
--text: #1a1d24;
--muted: #8a9099;
--danger: #fa5151;
--danger-rgb: 250 81 81;
--r-card: 28px;
--r-blob: 22px;
--r-small: 16px;
/* 布料折痕:两层,紧贴边缘 */
--crease: 0 1px 3px rgba(var(--shadow) / 0.4), 0 2px 8px rgba(var(--shadow) / 0.22);
--crease-press: 0 1px 2px rgba(var(--shadow) / 0.28), 0 1px 3px rgba(var(--shadow) / 0.16);
--crease-accent: 0 1px 3px rgba(var(--green-rgb) / 0.45), 0 4px 14px rgba(var(--green-rgb) / 0.32);
/* 顶面高光(中心偏上的 radial),所有浮起组件共用 */
--sheen: radial-gradient(
ellipse at 50% 28%,
rgba(255, 255, 255, 0.55) 0%,
rgba(255, 255, 255, 0.16) 38%,
transparent 74%
);
}
* {
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
html,
body,
#root {
height: 100%;
margin: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', 'Segoe UI', Roboto, sans-serif;
background: var(--base);
color: var(--text);
-webkit-font-smoothing: antialiased;
}
input,
button {
font-family: inherit;
}
.center-screen {
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
padding-top: max(24px, env(safe-area-inset-top));
padding-bottom: max(24px, env(safe-area-inset-bottom));
}
/* ── 浮起原子:surface + 折痕 + 顶面高光 ──────────────────── */
.mf-raised,
.card,
.enter-card,
.list,
.wx-state {
position: relative;
background: var(--surface);
box-shadow: var(--crease);
}
.mf-raised::before,
.card::before,
.enter-card::before,
.list::before,
.wx-state::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--sheen);
pointer-events: none;
}
.card > *,
.enter-card > *,
.list > *,
.wx-state > * {
position: relative;
z-index: 1;
}
.card {
border-radius: var(--r-card);
padding: 24px;
width: 100%;
max-width: 360px;
}
/* 登录 */
.login-screen {
background:
radial-gradient(60% 50% at 50% 0%, rgba(var(--green-rgb) / 0.1), transparent 70%),
var(--base);
}
.login-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
width: 100%;
max-width: 360px;
}
.login-card {
display: flex;
flex-direction: column;
gap: 14px;
padding: 28px 24px;
}
.login-foot {
font-size: 12px;
color: var(--muted);
text-align: center;
padding: 0 12px;
}
.brand {
text-align: center;
margin-bottom: 8px;
}
.brand-logo {
position: relative;
width: 60px;
height: 60px;
border-radius: 18px;
background: var(--wx-green);
color: #fff;
font-size: 32px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 14px;
box-shadow: var(--crease-accent);
overflow: hidden;
}
.brand-logo img {
width: 100%;
height: 100%;
display: block;
}
.brand-logo::before {
content: '';
position: absolute;
inset: 0;
background: var(--sheen);
pointer-events: none;
}
.brand h1 {
font-size: 22px;
font-weight: 700;
margin: 0 0 4px;
}
.muted {
color: var(--muted);
}
.small {
font-size: 12px;
}
/* 输入框:凹陷(trough fill + 顶部内阴影),聚焦叠绿色半透环 */
.input {
width: 100%;
height: 48px;
padding: 0 16px;
border: none;
border-radius: var(--r-small);
font-size: 16px;
outline: none;
color: var(--text);
background: var(--trough);
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.18), inset 0 2px 6px rgba(var(--shadow) / 0.1);
transition: box-shadow 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.input::placeholder {
color: var(--muted);
}
.input:focus {
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.16), 0 0 0 3px rgba(var(--green-rgb) / 0.22);
}
/* 密码框 + 显示/隐藏切换 */
.pw-field {
position: relative;
}
.pw-field .input {
padding-right: 46px;
}
.pw-toggle {
position: absolute;
right: 6px;
top: 50%;
transform: translateY(-50%);
width: 36px;
height: 36px;
border: none;
background: none;
color: var(--muted);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 10px;
}
.pw-toggle:active {
background: rgba(var(--shadow) / 0.06);
}
/* 按钮:浮起 blob,按下缩小 + 折痕收紧 */
.btn {
position: relative;
height: 48px;
border: none;
border-radius: var(--r-small);
font-size: 16px;
font-weight: 500;
background: var(--surface);
color: var(--text);
cursor: pointer;
box-shadow: var(--crease);
overflow: hidden;
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.btn::before {
content: '';
position: absolute;
inset: 0;
background: var(--sheen);
pointer-events: none;
}
.btn:active:not(:disabled) {
transform: scale(0.97);
box-shadow: var(--crease-press);
}
.btn-primary {
background: var(--wx-green);
color: #fff;
box-shadow: var(--crease-accent);
}
/* 绿底上用更克制的顶面高光,避免白色 sheen 把绿冲淡 */
.btn-primary::before,
.wx-btn.btn-primary::before {
background: radial-gradient(
ellipse at 50% 22%,
rgba(255, 255, 255, 0.28) 0%,
rgba(255, 255, 255, 0.08) 42%,
transparent 72%
);
}
.btn-primary:active:not(:disabled) {
background: var(--wx-green-dark);
}
/* 危险动作按钮:红底,折痕用 danger 色 */
.btn-danger {
background: var(--danger);
color: #fff;
box-shadow: 0 1px 3px rgba(var(--danger-rgb) / 0.45), 0 4px 14px rgba(var(--danger-rgb) / 0.3);
}
.btn-danger::before {
background: radial-gradient(ellipse at 50% 22%, rgba(255, 255, 255, 0.26) 0%, rgba(255, 255, 255, 0.07) 42%, transparent 72%);
}
.btn-danger:active:not(:disabled) {
background: #e84444;
}
.btn:disabled {
opacity: 0.6;
cursor: default;
}
/* 文本按钮:无浮起,绿字,按下淡绿底 */
.btn-text {
background: none;
border: none;
color: var(--wx-green);
font-size: 15px;
cursor: pointer;
padding: 6px 10px;
border-radius: 10px;
transition: background 0.18s;
}
.btn-text:active {
background: rgba(var(--green-rgb) / 0.12);
}
.btn-text.danger {
color: var(--danger);
}
.btn-text.danger:active {
background: rgba(var(--danger-rgb) / 0.12);
}
/* 实例卡片操作:「管理」分类折叠菜单(默认收起,点开按运维/设置/危险分组展开文字操作) */
.inst-menu-toggle {
width: 100%;
height: 38px;
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
border: none;
border-radius: 12px;
background: var(--trough);
color: var(--text);
font-size: 14px;
font-weight: 600;
cursor: pointer;
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.1);
transition: background 0.15s;
}
.inst-menu-toggle:hover {
background: var(--base);
}
.inst-menu-caret {
display: inline-flex;
color: var(--muted);
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.inst-menu-toggle.open .inst-menu-caret {
transform: rotate(180deg);
}
/* 「管理」菜单:绝对定位悬浮层,浮在下方内容之上,不把卡片撑高、不顶走后面的内容 */
.inst-menu-wrap {
position: relative;
margin-top: 10px;
}
.inst-menu {
position: absolute;
top: calc(100% + 6px);
left: 0;
right: 0;
z-index: 2;
display: flex;
flex-direction: column;
gap: 6px;
padding: 10px;
background: var(--surface);
border-radius: var(--r-small);
box-shadow: 0 6px 22px rgba(var(--shadow) / 0.26), 0 2px 6px rgba(var(--shadow) / 0.18);
animation: inst-menu-in 0.16s ease;
}
@keyframes inst-menu-in {
from {
opacity: 0;
transform: translateY(-4px);
}
}
.inst-menu-label {
font-size: 11px;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.04em;
margin: 2px 4px 2px;
}
.inst-menu-items {
display: flex;
flex-wrap: wrap;
gap: 2px;
}
.inst-menu-danger {
margin-top: 2px;
padding-top: 8px;
border-top: 1px solid rgba(var(--shadow) / 0.08);
}
.error {
color: var(--danger);
font-size: 14px;
}
.ok {
color: var(--wx-green);
font-size: 14px;
}
/* ── 页面骨架 ──────────────────────────────────────────── */
.page {
min-height: 100%;
display: flex;
flex-direction: column;
}
/* 顶栏走浅色布面(不再整条绿),标题深色、操作绿字 —— iOS26 + 牛奶布艺 */
.topbar {
position: relative;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 8px;
padding-top: env(safe-area-inset-top);
height: calc(52px + env(safe-area-inset-top));
background: var(--base);
}
.topbar-title {
font-size: 17px;
font-weight: 600;
color: var(--text);
position: absolute;
left: 50%;
transform: translateX(-50%);
}
.content {
flex: 1;
padding: 16px;
max-width: 560px;
width: 100%;
margin: 0 auto;
}
/* PC 大屏:加宽容器,实例网格自动多列卡片 */
@media (min-width: 880px) {
.content {
max-width: 940px;
padding: 20px 28px 40px;
}
.topbar {
padding-left: 16px;
padding-right: 16px;
}
}
.hello {
font-size: 22px;
font-weight: 700;
margin: 8px 4px 18px;
}
.tag {
display: inline-block;
font-size: 11px;
background: rgba(var(--green-rgb) / 0.14);
color: var(--wx-green-dark);
border-radius: 999px;
padding: 2px 9px;
margin-left: 8px;
vertical-align: middle;
font-weight: 600;
}
.tag-off {
background: rgba(var(--danger-rgb) / 0.14);
color: var(--danger);
}
/* 进入微信入口卡 */
.enter-card {
width: 100%;
border: none;
border-radius: var(--r-card);
padding: 20px;
display: flex;
align-items: center;
gap: 14px;
cursor: pointer;
text-align: left;
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.enter-card:active {
transform: scale(0.985);
box-shadow: var(--crease-press);
}
.enter-icon {
position: relative;
width: 46px;
height: 46px;
flex: none;
border-radius: 14px;
background: var(--wx-green);
color: #fff;
font-size: 24px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--crease-accent);
overflow: hidden;
}
.enter-icon::after {
content: '';
position: absolute;
inset: 0;
background: var(--sheen);
pointer-events: none;
}
.enter-text {
flex: 1;
}
.enter-title {
font-size: 17px;
font-weight: 600;
}
.enter-sub {
font-size: 13px;
color: var(--muted);
margin-top: 3px;
}
.enter-arrow {
color: #c2c7d0;
font-size: 22px;
}
/* 列表卡:行间用折痕细缝分隔,不用 border */
.list {
margin-top: 16px;
border-radius: var(--r-card);
overflow: hidden;
}
.list-item {
position: relative;
z-index: 1;
width: 100%;
background: none;
border: none;
box-shadow: inset 0 -1px 0 rgba(var(--shadow) / 0.08);
padding: 16px 18px;
font-size: 16px;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
transition: background 0.18s;
}
.list-item:active {
background: rgba(var(--shadow) / 0.04);
}
.list-item:last-child {
box-shadow: none;
}
/* 子账号列表 */
.user-row {
position: relative;
z-index: 1;
padding: 14px 18px;
box-shadow: inset 0 -1px 0 rgba(var(--shadow) / 0.08);
}
.user-row:last-child {
box-shadow: none;
}
.user-main {
display: flex;
justify-content: space-between;
align-items: center;
}
.user-name {
font-size: 16px;
font-weight: 500;
}
.user-actions {
display: flex;
gap: 4px;
margin-top: 6px;
}
/* ── 微信安装/更新状态卡(启动下载 + 更新到最新版) ──────── */
.wx-state {
margin-top: 16px;
border-radius: var(--r-card);
padding: 20px;
}
.wx-state-row {
position: relative;
z-index: 1;
display: flex;
align-items: center;
gap: 14px;
}
.wx-state-text {
flex: 1;
min-width: 0;
}
.wx-state-title {
font-size: 16px;
font-weight: 600;
}
.wx-state-sub {
font-size: 13px;
color: var(--muted);
margin-top: 3px;
}
.wx-btn {
height: 40px;
flex: none;
padding: 0 18px;
border-radius: 999px;
font-size: 14px;
font-weight: 600;
}
/* 进度条:凹槽轨 + 绿色带光晕填充 */
.wx-progress {
position: relative;
z-index: 1;
margin-top: 16px;
height: 8px;
border-radius: 999px;
background: var(--trough);
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.2);
overflow: hidden;
}
.wx-progress-bar {
height: 100%;
border-radius: 999px;
background: linear-gradient(90deg, var(--wx-green-dark), var(--wx-green));
box-shadow: 0 0 8px rgba(var(--green-rgb) / 0.5);
transition: width 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* 进度不确定(拿不到总大小)时来回滑动 */
.wx-progress-bar.indeterminate {
width: 40%;
transition: none;
animation: wx-slide 1.1s ease-in-out infinite;
}
@keyframes wx-slide {
0% {
margin-left: -40%;
}
100% {
margin-left: 100%;
}
}
.wx-progress-text {
position: relative;
z-index: 1;
margin-top: 8px;
font-size: 12px;
color: var(--muted);
text-align: right;
}
/* ── 弹窗 ──────────────────────────────────────────────── */
.modal-mask {
position: fixed;
inset: 0;
background: rgba(26, 29, 36, 0.4);
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
display: flex;
align-items: center;
justify-content: center;
padding: 24px;
z-index: 20;
}
.modal {
display: flex;
flex-direction: column;
gap: 12px;
border-radius: var(--r-card);
}
.modal h2 {
margin: 0 0 4px;
font-size: 18px;
font-weight: 700;
}
.modal-actions {
display: flex;
gap: 10px;
}
.modal-actions .btn {
flex: 1;
}
/* ── 桌面 ──────────────────────────────────────────────── */
.desktop-wrap {
position: fixed;
inset: 0;
background: #000;
}
.desktop-frame {
width: 100%;
height: 100%;
border: none;
display: block;
}
/* 返回钮:浮起白色圆 blob */
.desktop-back {
position: fixed;
top: max(12px, env(safe-area-inset-top));
left: 12px;
width: 42px;
height: 42px;
border-radius: 50%;
border: none;
background: var(--surface);
color: var(--text);
font-size: 24px;
line-height: 1;
cursor: pointer;
z-index: 10;
box-shadow: var(--crease);
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1), box-shadow 0.18s;
}
.desktop-back:active {
transform: scale(0.94);
box-shadow: var(--crease-press);
}
/* 文件按钮:返回钮下方 */
.desktop-files-btn {
position: fixed;
top: max(12px, env(safe-area-inset-top));
left: 64px;
width: 42px;
height: 42px;
border-radius: 50%;
border: none;
background: var(--surface);
color: var(--text);
font-size: 20px;
line-height: 1;
cursor: pointer;
z-index: 10;
box-shadow: var(--crease);
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.desktop-files-btn:active {
transform: scale(0.94);
}
/* 拖拽落区 */
.drop-zone {
position: fixed;
inset: 0;
z-index: 30;
background: rgba(7, 193, 96, 0.16);
-webkit-backdrop-filter: blur(3px);
backdrop-filter: blur(3px);
display: flex;
align-items: center;
justify-content: center;
}
.drop-card {
background: var(--surface);
border-radius: var(--r-card);
box-shadow: var(--crease);
padding: 28px 36px;
text-align: center;
border: 2px dashed rgba(var(--green-rgb) / 0.5);
}
.drop-icon {
font-size: 36px;
color: var(--wx-green);
}
.drop-title {
margin-top: 8px;
font-size: 17px;
font-weight: 700;
}
.drop-sub {
margin-top: 4px;
font-size: 13px;
color: var(--muted);
}
/* 文件传输面板 */
.files-panel {
position: fixed;
top: max(64px, calc(env(safe-area-inset-top) + 52px));
left: 12px;
width: min(330px, calc(100vw - 24px));
max-height: 70vh;
display: flex;
flex-direction: column;
background: var(--surface);
border-radius: var(--r-card);
box-shadow: var(--crease);
z-index: 11;
padding: 16px;
gap: 10px;
}
.files-head {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 16px;
font-weight: 700;
}
.files-upload {
height: 42px;
font-size: 15px;
}
.clip-area {
width: 100%;
resize: vertical;
min-height: 96px;
padding: 10px 12px;
border-radius: 14px;
border: none;
background: var(--mf-trough, #edeef1);
color: var(--text, #1a1d24);
font-size: 14px;
line-height: 1.5;
font-family: inherit;
box-shadow: inset 0 1px 3px rgba(51, 66, 102, 0.16);
outline: none;
box-sizing: border-box;
}
.clip-area:focus {
box-shadow: inset 0 1px 3px rgba(51, 66, 102, 0.16), 0 0 0 2px rgba(7, 193, 96, 0.35);
}
.security-status {
margin-top: 12px;
padding: 10px 14px;
border-radius: 14px;
background: var(--mf-trough, #edeef1);
box-shadow: inset 0 1px 3px rgba(51, 66, 102, 0.12);
display: flex;
flex-direction: column;
gap: 6px;
}
.security-row {
display: flex;
justify-content: space-between;
font-size: 13px;
line-height: 1.4;
}
.iv-stuck-actions {
display: flex;
gap: 10px;
margin-top: 14px;
}
.files-hint {
font-size: 12px;
color: var(--muted);
line-height: 1.5;
}
.files-list {
overflow-y: auto;
display: flex;
flex-direction: column;
}
.files-item {
display: flex;
align-items: center;
gap: 6px;
box-shadow: inset 0 -1px 0 rgba(var(--shadow) / 0.08);
}
.files-item:last-child {
box-shadow: none;
}
.files-dl {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding: 11px 4px;
text-decoration: none;
color: var(--text);
font-size: 14px;
}
.files-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.files-size {
flex: none;
color: var(--wx-green-dark);
font-size: 12px;
font-weight: 600;
}
.files-del {
flex: none;
width: 30px;
height: 30px;
border: none;
background: none;
color: var(--muted);
font-size: 14px;
cursor: pointer;
border-radius: 8px;
}
.files-del:active {
background: rgba(var(--danger-rgb) / 0.12);
color: var(--danger);
}
/* ── loading ───────────────────────────────────────────── */
.spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(var(--green-rgb) / 0.2);
border-top-color: var(--wx-green);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ---------- 多实例:分区标题 ---------- */
.section-row {
display: flex;
align-items: center;
justify-content: space-between;
margin: 6px 6px 12px;
}
.section-title {
font-size: 15px;
font-weight: 700;
color: var(--text);
}
/* ---------- 实例网格 ---------- */
.inst-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 14px;
margin-bottom: 22px;
/* 各卡片按自身内容高度,避免某张展开「管理」菜单时同行其它卡片被拉等高(显得也展开了) */
align-items: start;
}
.inst-card {
position: relative;
background: var(--surface);
border-radius: var(--r-card);
box-shadow: var(--crease);
padding: 18px;
overflow: hidden;
}
.inst-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--sheen);
pointer-events: none;
}
.inst-card > * {
position: relative;
}
/* 菜单展开时:放开裁剪让悬浮层露出,并抬升层级盖住下方/同列卡片(仍低于弹窗 z-index 20 */
.inst-card.open-menu {
overflow: visible;
z-index: 5;
}
.inst-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.inst-name {
font-size: 16px;
font-weight: 700;
color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.inst-sub {
margin-top: 6px;
font-size: 13px;
color: var(--muted);
min-height: 18px;
}
.inst-actions {
display: flex;
gap: 8px;
margin-top: 14px;
}
.inst-enter {
flex: 1;
}
.inst-act {
flex: none;
}
.inst-act-wide {
flex: 1;
height: 42px;
font-size: 15px;
}
/* 管理卡片底部的文字操作(重命名/分配/删除) */
.inst-admin-links {
display: flex;
flex-wrap: wrap;
gap: 2px;
margin-top: 10px;
padding-top: 8px;
box-shadow: inset 0 1px 0 rgba(var(--shadow) / 0.08);
}
.inst-admin-links .btn-text {
padding: 6px 8px;
font-size: 14px;
}
/* 状态徽章配色 */
.tag-on {
background: rgba(var(--green-rgb) / 0.16);
color: var(--wx-green-dark);
}
.tag-busy {
background: rgba(51 102 204 / 0.16);
color: #2f5fd0;
}
.tag-warn {
background: rgba(245 158 11 / 0.18);
color: #b9770a;
}
/* ---------- chip 多选 / 展示 ---------- */
.field-label {
font-size: 13px;
font-weight: 600;
color: var(--muted);
margin: 14px 2px 8px;
}
.chip-row {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 4px;
}
.chip-row-pick {
display: flex;
}
.chip {
font-size: 12px;
border-radius: 999px;
padding: 5px 12px;
font-weight: 600;
line-height: 1.4;
}
.chip-static {
background: rgba(var(--green-rgb) / 0.12);
color: var(--wx-green-dark);
}
.chip-toggle {
border: none;
cursor: pointer;
background: var(--trough);
color: var(--muted);
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.16);
transition: transform 0.15s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.chip-toggle:active {
transform: scale(0.95);
}
.chip-toggle.on {
background: var(--wx-green);
color: #fff;
box-shadow: var(--crease-accent);
}
/* ---------- 空状态 ---------- */
.empty-state {
text-align: center;
padding: 30px 16px 36px;
}
.empty-action {
margin-top: 16px;
}
.empty-blob {
width: 96px;
height: 96px;
margin: 0 auto 16px;
border-radius: 50%;
background: var(--surface);
box-shadow: var(--crease);
display: flex;
align-items: center;
justify-content: center;
font-size: 40px;
position: relative;
overflow: hidden;
}
.empty-blob::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--sheen);
}
.empty-blob img {
width: 52px;
height: 52px;
position: relative;
}
.empty-title {
font-size: 16px;
font-weight: 700;
color: var(--text);
}
.empty-sub {
margin-top: 6px;
font-size: 13px;
color: var(--muted);
}
/* ── 默认密码安全告警条 ──────────────────────────────────── */
.warn-banner {
position: relative;
z-index: 1;
width: 100%;
text-align: left;
display: flex;
align-items: center;
gap: 12px;
border: none;
cursor: pointer;
background: rgba(var(--danger-rgb) / 0.1);
border-radius: var(--r-blob);
padding: 14px 16px;
margin: 4px 0 14px;
box-shadow: inset 0 0 0 1.5px rgba(var(--danger-rgb) / 0.3);
transition: transform 0.18s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.warn-banner:active {
transform: scale(0.99);
}
.warn-icon {
flex: none;
width: 26px;
height: 26px;
border-radius: 50%;
background: var(--danger);
color: #fff;
font-weight: 800;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.warn-text {
display: flex;
flex-direction: column;
gap: 2px;
font-size: 13px;
color: var(--danger);
}
.warn-text b {
font-size: 14px;
}
/* ── 删除实例:勾选是否连数据卷一起删 ──────────────────────── */
.purge-opt {
display: flex;
align-items: flex-start;
gap: 10px;
cursor: pointer;
padding: 12px 14px;
border-radius: var(--r-small);
background: var(--trough);
font-size: 14px;
box-shadow: inset 0 1px 2px rgba(var(--shadow) / 0.14);
transition: box-shadow 0.18s;
}
.purge-opt.on {
box-shadow: inset 0 0 0 1.5px rgba(var(--danger-rgb) / 0.45);
}
.purge-check {
flex: none;
width: 20px;
height: 20px;
border-radius: 6px;
background: var(--surface);
box-shadow: var(--crease-press);
color: var(--danger);
font-weight: 800;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
margin-top: 1px;
}
.purge-opt.on .purge-check {
background: var(--danger);
color: #fff;
}
/* ── Toast ──────────────────────────────────────────────── */
.toast-stack {
position: fixed;
left: 50%;
bottom: max(24px, env(safe-area-inset-bottom));
transform: translateX(-50%);
display: flex;
flex-direction: column;
gap: 8px;
align-items: center;
z-index: 50;
pointer-events: none;
}
.toast {
pointer-events: auto;
max-width: 80vw;
background: var(--surface);
color: var(--text);
font-size: 14px;
font-weight: 500;
padding: 11px 18px;
border-radius: 999px;
box-shadow: var(--crease);
animation: toast-in 0.32s cubic-bezier(0.2, 0.9, 0.2, 1);
}
.toast-ok {
color: var(--wx-green-dark);
}
.toast-error {
color: var(--danger);
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateY(10px) scale(0.96);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
/* ── 桌面连接 loading 遮罩 ──────────────────────────────── */
.desktop-loading {
position: fixed;
inset: 0;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 14px;
background: var(--base);
}
.desktop-loading-text {
font-size: 16px;
font-weight: 600;
color: var(--text);
}
.desktop-loading-sub {
font-size: 13px;
color: var(--muted);
text-align: center;
padding: 0 24px;
}
.desktop-loading-warn {
margin-top: 6px;
font-size: 12px;
color: var(--danger);
text-align: center;
padding: 0 24px;
}
/* ============================================================
* 微信 PC 式布局:左侧 tab 栏 + 右侧工作区
* ============================================================ */
.shell {
display: flex;
height: 100%;
min-height: 0;
background: var(--base);
}
/* —— 左侧栏 —— */
.sidebar {
flex: none;
width: 264px;
display: flex;
flex-direction: column;
background: var(--surface);
box-shadow: 1px 0 0 rgba(var(--shadow) / 0.08), 4px 0 16px rgba(var(--shadow) / 0.05);
z-index: 20;
transition: width 0.2s cubic-bezier(0.2, 0.8, 0.2, 1);
padding-top: env(safe-area-inset-top);
}
.shell.collapsed .sidebar {
width: 64px;
}
.sb-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 14px 14px 10px;
}
.sb-brand {
display: flex;
align-items: center;
gap: 10px;
min-width: 0;
}
.sb-logo {
width: 30px;
height: 30px;
flex: none;
border-radius: 9px;
}
.sb-name {
font-size: 17px;
font-weight: 700;
white-space: nowrap;
}
.sb-collapse {
flex: none;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: none;
background: none;
color: var(--muted);
cursor: pointer;
border-radius: 9px;
}
.sb-collapse:active {
background: rgba(var(--shadow) / 0.06);
}
.collapsed .sb-top {
flex-direction: column;
}
.sb-nav {
padding: 4px 10px;
}
.sb-section {
padding: 12px 16px 6px;
font-size: 12px;
font-weight: 700;
color: var(--muted);
}
.sb-list {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0 10px;
display: flex;
flex-direction: column;
gap: 2px;
}
.sb-empty {
padding: 10px 16px;
font-size: 13px;
color: var(--muted);
}
.sb-item {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
border: none;
background: none;
cursor: pointer;
padding: 9px 12px;
border-radius: 12px;
font-size: 15px;
color: var(--text);
text-align: left;
transition: background 0.15s;
}
.sb-item:hover {
background: rgba(var(--shadow) / 0.05);
}
.sb-item.on {
background: rgba(var(--green-rgb) / 0.12);
color: var(--wx-green-dark);
}
.sb-item.on .sb-ic {
color: var(--wx-green-dark);
}
.sb-ic {
flex: none;
width: 24px;
display: flex;
align-items: center;
justify-content: center;
color: var(--muted);
}
.sb-label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-weight: 500;
}
.collapsed .sb-item {
justify-content: center;
padding: 9px 0;
}
/* 实例头像 + 状态点 */
.sb-avatar {
position: relative;
flex: none;
width: 34px;
height: 34px;
border-radius: 10px;
background: linear-gradient(150deg, var(--wx-green), var(--wx-green-dark));
color: #fff;
font-weight: 700;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
.sb-dot {
position: absolute;
right: -2px;
bottom: -2px;
width: 11px;
height: 11px;
border-radius: 50%;
border: 2px solid var(--surface);
}
.sb-stxt {
flex: none;
font-size: 11px;
font-weight: 600;
color: var(--muted);
}
.st-on {
background: #16c060;
color: var(--wx-green-dark);
}
.st-off {
background: #c2c7d0;
color: var(--muted);
}
.st-busy {
background: #2f7ad0;
color: #2f5fd0;
}
.st-warn {
background: #e6a23c;
color: #b9770a;
}
.sb-footer {
padding: 8px 10px calc(10px + env(safe-area-inset-bottom));
box-shadow: inset 0 1px 0 rgba(var(--shadow) / 0.08);
}
.sb-user {
padding: 8px 12px 2px;
font-size: 12px;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* —— 右侧工作区 —— */
.workspace {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.ws-page {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.ws-head {
flex: none;
display: flex;
align-items: center;
gap: 10px;
height: calc(52px + env(safe-area-inset-top));
padding: env(safe-area-inset-top) 16px 0;
background: var(--base);
box-shadow: inset 0 -1px 0 rgba(var(--shadow) / 0.07);
}
.ws-title {
font-size: 16px;
font-weight: 700;
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ws-menu {
display: none;
flex: none;
width: 36px;
height: 36px;
align-items: center;
justify-content: center;
border: none;
background: none;
color: var(--text);
cursor: pointer;
border-radius: 9px;
margin-left: -6px;
}
.ws-action {
flex: none;
border: none;
background: var(--surface);
color: var(--wx-green-dark);
font-size: 13px;
font-weight: 600;
padding: 6px 11px;
border-radius: 999px;
cursor: pointer;
box-shadow: var(--crease);
}
.ws-action:active {
transform: scale(0.96);
}
.ws-action.on {
background: var(--wx-green, #07c160);
color: #fff;
}
.ws-page .content {
flex: 1;
min-height: 0;
overflow-y: auto;
}
/* 主页实例快捷卡 */
.home-card {
display: flex;
align-items: center;
gap: 12px;
width: 100%;
text-align: left;
border: none;
cursor: pointer;
background: var(--surface);
border-radius: var(--r-card);
padding: 16px;
box-shadow: var(--crease);
position: relative;
overflow: hidden;
transition: transform 0.16s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.home-card::before {
content: '';
position: absolute;
inset: 0;
border-radius: inherit;
background: var(--sheen);
pointer-events: none;
}
.home-card:hover {
transform: translateY(-2px);
box-shadow: var(--crease-accent);
}
.home-card:active {
transform: scale(0.985);
}
.home-card-av {
position: relative;
flex: none;
width: 42px;
height: 42px;
border-radius: 12px;
background: linear-gradient(150deg, var(--wx-green), var(--wx-green-dark));
color: #fff;
font-weight: 700;
font-size: 20px;
display: flex;
align-items: center;
justify-content: center;
text-transform: uppercase;
}
.home-card-main {
position: relative;
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 3px;
}
.home-card-name {
font-size: 16px;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.home-card-meta {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.home-card-st {
font-size: 12px;
font-weight: 600;
background: none !important;
flex: none;
}
.home-card-ver {
font-size: 12px;
color: var(--muted);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.home-card-ver::before {
content: '·';
margin-right: 8px;
color: var(--muted);
}
/* —— 内嵌实例视图 —— */
.iv-stage {
position: relative;
flex: 1;
min-height: 0;
background: #000;
}
.iv-center {
display: flex;
align-items: center;
justify-content: center;
background: var(--base);
}
/* VNC 分支:纵向 flex —— 画面区(.iv-canvas, flex:1) 在上,中文输入条在下,互不重叠。
输入条占用布局空间 → iframe 区域变小 → resize=remote 让远端桌面同步缩小,微信画面完整可见、不被遮。 */
.iv-stage--vnc {
display: flex;
flex-direction: column;
}
.iv-canvas {
position: relative;
flex: 1;
min-height: 0;
background: #000;
}
.iv-frame {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
border: none;
display: block;
}
.iv-imebar {
flex: 0 0 auto;
display: flex;
gap: 8px;
align-items: center;
padding: 8px 12px;
/* 与顶部 nav 栏同款牛奶布艺浅色主题 + 顶部细折痕分隔 */
background: var(--base);
box-shadow: inset 0 1px 0 rgba(var(--shadow) / 0.07);
box-sizing: border-box;
}
.iv-imebar-input {
flex: 1;
height: 38px;
max-height: 38px;
resize: none;
padding: 9px 12px;
border-radius: 10px;
border: none;
outline: none;
font-size: 14px;
font-family: inherit;
line-height: 1.3;
/* 牛奶布艺凹槽输入框:与「剪贴板」文本框一致 */
background: var(--trough);
color: var(--text);
box-shadow: inset 0 1px 3px rgba(var(--shadow) / 0.16);
box-sizing: border-box;
}
.iv-imebar-input:focus {
box-shadow: inset 0 1px 3px rgba(var(--shadow) / 0.16), 0 0 0 2px rgba(7, 193, 96, 0.35);
}
.iv-imebar-send {
height: 38px;
flex: 0 0 auto;
padding: 0 18px;
}
.iv-loading,
.iv-drop {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
z-index: 5;
}
.iv-loading {
background: var(--base);
}
.iv-loading-text {
font-size: 16px;
font-weight: 600;
}
.iv-loading-sub {
font-size: 13px;
color: var(--muted);
text-align: center;
padding: 0 24px;
}
.iv-loading-warn {
margin-top: 4px;
font-size: 12px;
color: var(--danger);
text-align: center;
padding: 0 24px;
}
.iv-drop {
z-index: 30;
background: rgba(7, 193, 96, 0.16);
-webkit-backdrop-filter: blur(3px);
backdrop-filter: blur(3px);
}
.iv-files {
position: absolute;
top: 14px;
right: 14px;
width: min(330px, calc(100% - 28px));
max-height: calc(100% - 28px);
display: flex;
flex-direction: column;
gap: 10px;
background: var(--surface);
border-radius: var(--r-card);
box-shadow: var(--crease), 0 8px 30px rgba(var(--shadow) / 0.18);
z-index: 12;
padding: 16px;
}
/* 只读遮罩:他人正在操作时盖住 VNC,拦截键鼠(半透,仍能看到画面) */
.iv-lock {
position: absolute;
inset: 0;
z-index: 8;
display: flex;
align-items: center;
justify-content: center;
background: rgba(26, 29, 36, 0.28);
-webkit-backdrop-filter: blur(1.5px);
backdrop-filter: blur(1.5px);
}
.iv-lock-card {
background: var(--surface);
border-radius: var(--r-card);
box-shadow: var(--crease), 0 8px 30px rgba(var(--shadow) / 0.25);
padding: 22px 26px;
text-align: center;
max-width: 320px;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
}
.iv-lock-title {
font-size: 16px;
font-weight: 700;
}
.iv-lock-sub {
font-size: 13px;
color: var(--muted);
line-height: 1.5;
}
.iv-notice {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 14px;
padding: 24px;
}
.iv-notice-title {
font-size: 17px;
font-weight: 700;
}
.iv-notice-sub {
font-size: 13px;
color: var(--muted);
}
.iv-notice-btn {
padding: 0 22px;
height: 44px;
}
/* —— 移动端:侧栏变抽屉 —— */
.shell-backdrop {
display: none;
}
@media (max-width: 767px) {
.sidebar {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: 82vw;
max-width: 320px;
transform: translateX(-100%);
transition: transform 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
box-shadow: 0 0 40px rgba(var(--shadow) / 0.35);
}
.shell.drawer-open .sidebar {
transform: none;
}
.shell.drawer-open .shell-backdrop {
display: block;
position: fixed;
inset: 0;
background: rgba(26, 29, 36, 0.4);
z-index: 15;
}
.ws-menu {
display: flex;
}
.ws-action {
padding: 7px 12px;
}
}
/* 宽屏:工作区内容容器更宽(实例网格多列) */
@media (min-width: 880px) {
.ws-page .content {
max-width: 940px;
}
}
/* ── 数据卷管理(管理员)──────────────────────────────────── */
.vol-modal {
width: 640px;
max-width: 92vw;
max-height: 86vh;
}
/* 运行中警示条:柔和琥珀,inset 凹槽质感(不是红色危险) */
.vol-warn {
background: rgba(245, 166, 35, 0.14);
color: #9a6400;
font-size: 13px;
line-height: 1.5;
padding: 9px 12px;
border-radius: var(--r-small);
}
/* 分区容器:内部元素紧凑成组(label 紧贴其内容),分区之间靠 .modal 的 12px gap 拉开 */
.vol-sec {
display: flex;
flex-direction: column;
gap: 8px;
}
.vol-section-label {
font-size: 12px;
font-weight: 600;
color: var(--muted);
}
.vol-hint {
font-size: 12px;
color: var(--muted);
line-height: 1.5;
}
.vol-topbar {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 10px;
}
/* 主操作:中号浮起胶囊(比默认 48px 更克制,和弹窗比例协调) */
.vol-topbar .btn {
height: 40px;
padding: 0 18px;
font-size: 14px;
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
text-decoration: none;
}
.vol-crumbs {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 2px;
font-size: 13px;
}
.vol-crumb {
border: none;
background: none;
color: var(--wx-green);
font-weight: 600;
cursor: pointer;
padding: 2px 4px;
border-radius: 8px;
}
.vol-crumb:active {
background: rgba(var(--green-rgb) / 0.12);
}
.vol-crumb:disabled {
color: var(--muted);
cursor: default;
}
.vol-sep {
color: var(--muted);
}
/* 工具栏:浮起小胶囊(绿字),和上面「扁平绿色面包屑」明显区分,读起来是"按钮"而非散落文字 */
.vol-tools {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.vol-tools .btn-text {
background: var(--surface);
box-shadow: var(--crease-press);
font-size: 14px;
padding: 8px 14px;
}
.vol-tools .btn-text:active {
transform: scale(0.96);
}
.vol-tools .btn-text:disabled {
opacity: 0.5;
}
.vol-mkdir {
display: flex;
gap: 8px;
}
.vol-mkdir .input {
flex: 1;
}
.vol-busy {
font-size: 13px;
color: var(--wx-green-dark);
font-weight: 600;
}
/* 列表容器:凹槽(inset),内部条目为浮起 bumphover) */
.vol-list {
background: var(--trough);
border-radius: var(--r-small);
padding: 6px;
max-height: 44vh;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 2px;
box-shadow: inset 0 1px 3px rgba(var(--shadow) / 0.22);
}
.vol-row {
display: flex;
align-items: center;
gap: 6px;
border-radius: 12px;
padding: 2px 4px 2px 6px;
}
.vol-row:hover {
background: var(--surface);
box-shadow: var(--crease-press);
}
.vol-main {
flex: 1;
min-width: 0;
display: flex;
align-items: center;
gap: 9px;
border: none;
background: none;
text-align: left;
padding: 8px 4px;
color: var(--text);
font-size: 14px;
}
.vol-main:disabled {
opacity: 0.55;
}
.vol-up {
color: var(--muted);
}
.vol-ic {
flex: 0 0 auto;
width: 20px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--muted);
}
.vol-ic.dir {
color: var(--wx-green);
}
.vol-ic svg {
display: block;
}
.vol-nm {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.vol-meta {
flex: 0 0 auto;
color: var(--muted);
font-size: 12px;
padding-left: 8px;
}
.vol-rename {
flex: 1;
margin: 2px 0;
}
.vol-acts {
flex: 0 0 auto;
display: flex;
gap: 3px;
}
.vol-act {
border: none;
background: var(--trough);
color: var(--muted);
width: 30px;
height: 30px;
border-radius: 9px;
cursor: pointer;
text-decoration: none;
display: inline-flex;
align-items: center;
justify-content: center;
box-shadow: var(--crease-press);
}
.vol-row:hover .vol-act {
background: var(--base);
color: var(--text);
}
.vol-act:active {
transform: scale(0.92);
}
.vol-act.danger:hover {
color: var(--danger);
background: rgba(var(--danger-rgb) / 0.12);
}
.vol-act:disabled {
opacity: 0.4;
cursor: default;
}