ESP32/frontend-preview/styles.css
2026-06-23 20:38:23 +08:00

829 lines
16 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.

/* ============================================================
ESP32-S3-RLCD Workbench — macOS 柔和风
Canvas 像素级预览 + 浅色毛玻璃工具面板
============================================================ */
:root {
/* 背景层 */
--bg-gradient-start: #f5f5f7;
--bg-gradient-end: #e8e8ed;
--noise-opacity: 0.04;
/* 表面层(毛玻璃卡片) */
--surface: rgba(255, 255, 255, 0.72);
--surface-strong: rgba(255, 255, 255, 0.92);
--surface-sunken: rgba(0, 0, 0, 0.03);
/* 文字 */
--ink: #1d1d1f;
--ink-soft: #424245;
--ink-mute: #86868b;
--ink-faint: #aeaeb2;
/* 线条 */
--hairline: rgba(0, 0, 0, 0.08);
--hairline-strong: rgba(0, 0, 0, 0.14);
/* 强调色(iOS 系统色) */
--accent: #007aff;
--accent-soft: rgba(0, 122, 255, 0.12);
--accent-strong: #0051d5;
--success: #34c759;
--warning: #ff9f0a;
--danger: #ff3b30;
--danger-soft: rgba(255, 59, 48, 0.12);
/* 设备外壳 */
--device-shell: #2c2c2e;
--device-bezel: #1c1c1e;
/* 屏幕底色(反射屏灰白) */
--screen-bg: #d8d8d0;
/* 阴影 */
--shadow-card: 0 8px 24px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-device: 0 18px 50px rgba(0, 0, 0, 0.18), 0 4px 12px rgba(0, 0, 0, 0.08);
/* 圆角 */
--r-card: 12px;
--r-button: 8px;
--r-pill: 999px;
--r-device: 18px;
--r-bezel: 10px;
color-scheme: light;
font-family:
-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
"Segoe UI", "Helvetica Neue", Arial, "PingFang SC", "Microsoft YaHei",
sans-serif;
font-size: 13px;
line-height: 1.45;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
}
[hidden] {
display: none !important;
}
body {
margin: 0;
min-height: 100vh;
color: var(--ink);
background:
radial-gradient(circle at 20% 0%, rgba(0, 122, 255, 0.04), transparent 50%),
radial-gradient(circle at 80% 100%, rgba(255, 55, 95, 0.03), transparent 50%),
linear-gradient(180deg, var(--bg-gradient-start), var(--bg-gradient-end));
background-attachment: fixed;
}
button,
input,
select,
textarea {
font: inherit;
color: inherit;
}
button {
cursor: pointer;
-webkit-appearance: none;
appearance: none;
}
/* ============================================================
主布局
============================================================ */
.workbench {
min-height: 100vh;
display: grid;
grid-template-columns: minmax(0, 1fr) 360px;
gap: 16px;
padding: 16px;
}
.stage {
display: grid;
grid-template-rows: auto 1fr;
gap: 14px;
min-width: 0;
}
.panel {
display: flex;
flex-direction: column;
gap: 12px;
min-width: 0;
max-height: calc(100vh - 32px);
overflow-y: auto;
padding-right: 4px;
}
/* ============================================================
顶部 Header
============================================================ */
.header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 10px 16px;
background: var(--surface);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--hairline);
border-radius: var(--r-card);
box-shadow: var(--shadow-card);
}
.header-title {
display: flex;
align-items: baseline;
gap: 10px;
min-width: 0;
}
.header-title .product {
font-size: 14px;
font-weight: 600;
letter-spacing: -0.01em;
}
.header-title .sub {
font-size: 11px;
color: var(--ink-mute);
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.bridge-pill {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 4px 10px 4px 8px;
background: var(--surface-sunken);
border: 1px solid var(--hairline);
border-radius: var(--r-pill);
font-size: 11px;
color: var(--ink-mute);
white-space: nowrap;
transition: all 160ms ease;
}
.bridge-pill::before {
content: "";
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--ink-faint);
transition: all 160ms ease;
}
.bridge-pill.ok {
color: var(--success);
border-color: rgba(52, 199, 89, 0.3);
background: rgba(52, 199, 89, 0.08);
}
.bridge-pill.ok::before {
background: var(--success);
box-shadow: 0 0 0 3px rgba(52, 199, 89, 0.18);
}
.bridge-pill.bad {
color: var(--danger);
border-color: rgba(255, 59, 48, 0.24);
background: var(--danger-soft);
}
.bridge-pill.bad::before {
background: var(--danger);
}
/* ============================================================
舞台 - 设备预览
============================================================ */
.stage-area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
padding: 24px 16px;
background: var(--surface);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--hairline);
border-radius: var(--r-card);
box-shadow: var(--shadow-card);
min-height: 0;
}
/* 设备外壳 - 模拟开发板外观 */
.device-shell {
position: relative;
padding: 14px 14px 10px;
background: linear-gradient(160deg, #3a3a3c, var(--device-shell));
border-radius: var(--r-device);
box-shadow: var(--shadow-device);
border: 1px solid #000;
}
/* 屏幕外框(黑色 bezel) */
.screen-bezel {
padding: 14px;
background: var(--device-bezel);
border-radius: var(--r-bezel);
border: 1px solid #000;
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.04);
}
/* Canvas 容器 - 关键:固定 400×300, scale 放大整个 device-shell */
.canvas-frame {
position: relative;
width: 400px;
height: 300px;
background: var(--screen-bg);
border: 1px solid #000;
overflow: hidden;
/* 反射屏的微妙纹理 */
background-image:
repeating-linear-gradient(
45deg,
transparent 0 2px,
rgba(0, 0, 0, 0.015) 2px 4px
);
}
/* Canvas 本身 - 物理像素 400×300, 不单独缩放 */
.canvas-frame canvas {
display: block;
width: 400px;
height: 300px;
image-rendering: pixelated;
image-rendering: -moz-crisp-edges;
image-rendering: crisp-edges;
}
/* 关键:scale 应用到整个 device-shell, 让外框+bezel+canvas 一起放大 */
.device-shell {
transform-origin: center center;
transition: transform 120ms ease-out;
}
/* 像素网格叠加(可选) */
.canvas-frame.show-grid::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(0, 122, 255, 0.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 122, 255, 0.08) 1px, transparent 1px);
background-size: 8px 8px;
}
/* 设备底部信息条 */
.device-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
padding: 0 4px;
color: rgba(255, 255, 255, 0.42);
font-size: 10px;
font-weight: 500;
letter-spacing: 0.02em;
}
.device-meta .dots {
display: flex;
gap: 4px;
}
.device-meta .dots span {
width: 4px;
height: 4px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
}
/* ============================================================
预览控件
============================================================ */
.preview-controls {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
justify-content: center;
}
.zoom-group {
display: inline-flex;
align-items: center;
gap: 0;
background: var(--surface-sunken);
border: 1px solid var(--hairline);
border-radius: var(--r-button);
overflow: hidden;
}
.zoom-btn {
width: 28px;
height: 28px;
border: 0;
background: transparent;
color: var(--ink-soft);
font-size: 16px;
font-weight: 500;
display: grid;
place-items: center;
transition: background 120ms ease;
}
.zoom-btn:hover {
background: rgba(0, 0, 0, 0.05);
}
.zoom-btn:active {
background: rgba(0, 0, 0, 0.08);
}
.zoom-label {
padding: 0 10px;
font-size: 11px;
color: var(--ink-mute);
font-variant-numeric: tabular-nums;
min-width: 48px;
text-align: center;
}
.toggle-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: var(--surface-sunken);
border: 1px solid var(--hairline);
border-radius: var(--r-button);
font-size: 11px;
color: var(--ink-mute);
cursor: pointer;
user-select: none;
transition: all 120ms ease;
}
.toggle-chip:hover {
background: rgba(0, 0, 0, 0.05);
}
.toggle-chip.on {
background: var(--accent-soft);
border-color: rgba(0, 122, 255, 0.24);
color: var(--accent-strong);
}
.toggle-chip .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ink-faint);
transition: all 120ms ease;
}
.toggle-chip.on .dot {
background: var(--accent);
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.2);
}
/* ============================================================
右侧控制面板
============================================================ */
.card {
background: var(--surface);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--hairline);
border-radius: var(--r-card);
box-shadow: var(--shadow-card);
overflow: hidden;
}
.card-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid var(--hairline);
background: rgba(255, 255, 255, 0.4);
cursor: pointer;
user-select: none;
transition: background 120ms ease;
}
.card-head:hover {
background: rgba(255, 255, 255, 0.6);
}
.card-head h2 {
margin: 0;
font-size: 11px;
font-weight: 600;
color: var(--ink-soft);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.card-head .chevron {
color: var(--ink-faint);
font-size: 10px;
transition: transform 160ms ease;
}
.card.collapsed .chevron {
transform: rotate(-90deg);
}
.card-body {
padding: 12px 14px;
display: flex;
flex-direction: column;
gap: 10px;
}
.card.collapsed .card-body {
display: none;
}
/* 数据字段分组 */
.data-group {
padding-top: 8px;
border-top: 1px solid var(--hairline);
}
.data-group:first-child {
padding-top: 0;
border-top: 0;
}
.data-group-title {
font-size: 10px;
font-weight: 600;
color: var(--ink-mute);
letter-spacing: 0.06em;
text-transform: uppercase;
margin-bottom: 8px;
}
/* 字段输入 */
.field {
display: grid;
grid-template-columns: 88px 1fr;
align-items: center;
gap: 8px;
margin-bottom: 6px;
}
.field label {
font-size: 11px;
color: var(--ink-soft);
font-weight: 400;
}
.field input[type="text"],
.field input[type="number"],
.field select {
width: 100%;
height: 26px;
padding: 0 8px;
background: var(--surface-strong);
border: 1px solid var(--hairline);
border-radius: 6px;
font-size: 11px;
font-variant-numeric: tabular-nums;
color: var(--ink);
transition: all 120ms ease;
}
.field input:focus,
.field select:focus {
outline: 0;
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
}
.field input[type="range"] {
width: 100%;
height: 4px;
-webkit-appearance: none;
appearance: none;
background: var(--hairline);
border-radius: var(--r-pill);
outline: 0;
}
.field input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
.field input[type="range"]::-moz-range-thumb {
width: 14px;
height: 14px;
border: 0;
border-radius: 50%;
background: var(--accent);
cursor: pointer;
}
/* 按钮 */
.btn-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.btn-row.three {
grid-template-columns: 1fr 1fr 1fr;
}
.btn {
height: 32px;
border-radius: var(--r-button);
border: 1px solid transparent;
font-size: 12px;
font-weight: 500;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 4px;
transition: all 120ms ease;
white-space: nowrap;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
.btn-primary:hover:not(:disabled) {
background: var(--accent-strong);
border-color: var(--accent-strong);
}
.btn-secondary {
background: var(--surface-strong);
color: var(--ink);
border-color: var(--hairline-strong);
}
.btn-secondary:hover:not(:disabled) {
background: #fff;
border-color: rgba(0, 0, 0, 0.2);
}
.btn-danger {
background: var(--danger);
color: #fff;
border-color: var(--danger);
}
.btn-danger:hover:not(:disabled) {
background: #d70015;
border-color: #d70015;
}
.btn-text {
background: transparent;
color: var(--accent);
border: 0;
padding: 2px 4px;
height: auto;
font-size: 11px;
}
.btn-text:hover:not(:disabled) {
color: var(--accent-strong);
text-decoration: underline;
}
/* 固件列表 */
.fw-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.fw-item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 8px 10px;
background: var(--surface-strong);
border: 1px solid var(--hairline);
border-radius: 6px;
cursor: pointer;
transition: all 120ms ease;
}
.fw-item:hover {
background: #fff;
}
.fw-item.active {
border-color: var(--accent);
background: var(--accent-soft);
}
.fw-name {
font-size: 11px;
font-weight: 500;
color: var(--ink);
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.fw-meta {
font-size: 10px;
color: var(--ink-mute);
}
.badge {
flex-shrink: 0;
padding: 1px 6px;
border-radius: var(--r-pill);
font-size: 9px;
font-weight: 600;
letter-spacing: 0.02em;
background: rgba(0, 122, 255, 0.12);
color: var(--accent);
}
.badge.warn {
background: rgba(255, 159, 10, 0.16);
color: var(--warning);
}
.badge.muted {
background: var(--surface-sunken);
color: var(--ink-mute);
}
/* 日志框 */
.log-box {
margin-top: 8px;
padding: 8px 10px;
background: var(--surface-sunken);
border: 1px solid var(--hairline);
border-radius: 6px;
font-family: "SF Mono", ui-monospace, Menlo, Consolas, monospace;
font-size: 10px;
line-height: 1.5;
color: var(--ink-soft);
min-height: 60px;
max-height: 140px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
.log-box:empty::before {
content: "等待操作…";
color: var(--ink-faint);
font-style: italic;
}
/* ============================================================
模态框
============================================================ */
.modal-backdrop {
position: fixed;
inset: 0;
z-index: 100;
display: grid;
place-items: center;
background: rgba(0, 0, 0, 0.32);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
padding: 20px;
animation: backdrop-fade 160ms ease;
}
@keyframes backdrop-fade {
from { opacity: 0; }
to { opacity: 1; }
}
.modal {
width: min(380px, 100%);
background: var(--surface-strong);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 1px solid var(--hairline);
border-radius: 14px;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.24);
padding: 18px;
animation: modal-rise 200ms ease;
}
@keyframes modal-rise {
from { transform: translateY(8px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.modal h2 {
margin: 0 0 6px;
font-size: 15px;
font-weight: 600;
}
.modal p {
margin: 0;
color: var(--ink-soft);
font-size: 12px;
line-height: 1.5;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 16px;
}
/* ============================================================
响应式
============================================================ */
@media (max-width: 1100px) {
.workbench {
grid-template-columns: 1fr;
}
.panel {
max-height: none;
}
}
@media (max-width: 640px) {
.workbench {
padding: 10px;
}
.device-shell {
transform: scale(0.85);
transform-origin: top center;
}
}
/* 滚动条(macOS 风) */
.panel::-webkit-scrollbar,
.log-box::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.panel::-webkit-scrollbar-thumb,
.log-box::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.18);
border-radius: 3px;
}
.panel::-webkit-scrollbar-thumb:hover,
.log-box::-webkit-scrollbar-thumb:hover {
background: rgba(0, 0, 0, 0.3);
}
.panel::-webkit-scrollbar-track,
.log-box::-webkit-scrollbar-track {
background: transparent;
}