Linear design system: new admin UI + display design switcher (8 designs)
Admin UI: Linear.app dark theme — near-black canvas, indigo-violet accent, Inter font Display: Minimal, Minimal Color, Bold, Analog Clock, Magazine, Cards, Kiosk, Classic
This commit is contained in:
+600
-74
@@ -4,40 +4,552 @@
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>epaper-dashboard</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;510;590&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Crect width='100' height='100' fill='%230a0a0a'/%3E%3Crect x='10' y='10' width='80' height='80' fill='%23fafafa'/%3E%3Crect x='15' y='15' width='35' height='35' fill='%230080ff'/%3E%3Crect x='50' y='15' width='35' height='35' fill='%23ff0080'/%3E%3Crect x='15' y='50' width='35' height='35' fill='%2300cc00'/%3E%3Crect x='50' y='50' width='35' height='35' fill='%23ff8000'/%3E%3C/svg%3E">
|
||||
<style>
|
||||
/* ================================================================
|
||||
ADMIN UI THEME VARIABLES — switch via data-theme="A|B|C" on <body>
|
||||
LINEAR DESIGN SYSTEM — epaper-dashboard Admin UI
|
||||
Based on Linear.app design system
|
||||
================================================================ */
|
||||
:root,
|
||||
:root[data-theme="A"] {
|
||||
/* A: Dark Minimal (original/default) */
|
||||
--bg: #0a0a0f; --surface: #15151c; --surface-2: #1f1f28; --surface-3: #2a2a35;
|
||||
--border: #2e2e3a; --border-light: #3a3a48;
|
||||
--fg: #f0f0f5; --fg-muted: #9090a0; --fg-dim: #6a6a78;
|
||||
--accent: #6d8eff; --accent-glow: rgba(109,142,255,0.15);
|
||||
--ok: #4ade80; --warn: #fbbf24; --alert: #f87171; --info: #60a5fa; --success: #22c55e;
|
||||
}
|
||||
:root[data-theme="B"] {
|
||||
/* B: Retro Terminal — CRT green phosphor */
|
||||
--bg: #0d0d00; --surface: #141400; --surface-2: #1a1a00; --surface-3: #222200;
|
||||
--border: #2a2a00; --border-light: #3a3a00;
|
||||
--fg: #e8c840; --fg-muted: #a09020; --fg-dim: #605800;
|
||||
--accent: #ffcc00; --accent-glow: rgba(255,204,0,0.12);
|
||||
--ok: #88ff44; --warn: #ffaa00; --alert: #ff4444; --info: #88ccff; --success: #44ff88;
|
||||
}
|
||||
:root[data-theme="C"] {
|
||||
/* C: Warm Editorial — newspaper / cream paper */
|
||||
--bg: #f4f1eb; --surface: #faf8f4; --surface-2: #f0ede6; --surface-3: #e8e4dc;
|
||||
--border: #d8d3c8; --border-light: #e6e1d8;
|
||||
--fg: #1a1816; --fg-muted: #6b6560; --fg-dim: #9a9490;
|
||||
--accent: #c0392b; --accent-glow: rgba(192,57,43,0.10);
|
||||
--ok: #27ae60; --warn: #e67e22; --alert: #c0392b; --info: #2980b9; --success: #27ae60;
|
||||
:root {
|
||||
/* Background Surfaces — near-black dark mode */
|
||||
--bg: #08090a; /* marketing black */
|
||||
--surface: #0f1011; /* panel dark */
|
||||
--surface-2: #191a1b; /* elevated surface */
|
||||
--surface-3: #28282c; /* hover/lighter surface */
|
||||
|
||||
/* Text */
|
||||
--fg: #f7f8f8; /* primary white (not pure) */
|
||||
--fg-muted: #d0d6e0; /* silver-gray body */
|
||||
--fg-dim: #8a8f98; /* tertiary/muted */
|
||||
--fg-subtle: #62666d; /* quaternary/timestamps */
|
||||
|
||||
/* Brand Accent — Linear indigo-violet */
|
||||
--accent: #5e6ad2; /* brand indigo */
|
||||
--accent-bright: #7170ff; /* interactive accent */
|
||||
--accent-hover: #828fff; /* hover state */
|
||||
|
||||
/* Status */
|
||||
--ok: #27a644; /* success green */
|
||||
--success: #10b981; /* emerald */
|
||||
--warn: #fbbf24; /* amber */
|
||||
--alert: #f87171; /* red */
|
||||
--info: #60a5fa; /* blue */
|
||||
|
||||
/* Borders — semi-transparent white */
|
||||
--border: rgba(255,255,255,0.08); /* standard */
|
||||
--border-subtle: rgba(255,255,255,0.05); /* subtle/default */
|
||||
--border-light: rgba(255,255,255,0.12); /* lighter */
|
||||
|
||||
/* Glow */
|
||||
--accent-glow: rgba(113,112,255,0.15);
|
||||
--ok-glow: rgba(39,166,68,0.25);
|
||||
--warn-glow: rgba(251,191,36,0.25);
|
||||
}
|
||||
|
||||
:root[data-theme="B"] body { background-image: repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,0.06) 2px,rgba(0,0,0,0.06) 4px); }
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
font-feature-settings: "cv01", "ss03";
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
padding: 24px 32px 80px;
|
||||
}
|
||||
|
||||
/* ============ Header ============ */
|
||||
.app-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 0;
|
||||
margin-bottom: 32px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
}
|
||||
.brand-logo {
|
||||
width: 34px; height: 34px;
|
||||
background: linear-gradient(135deg, var(--accent) 0%, var(--accent-bright) 100%);
|
||||
border-radius: 8px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
.brand-logo svg { color: white; }
|
||||
.brand-text h1 {
|
||||
font-size: 1.1em; font-weight: 510;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--fg);
|
||||
}
|
||||
.brand-text .sub {
|
||||
font-size: 0.78em; color: var(--fg-dim);
|
||||
font-family: ui-monospace, 'SF Mono', monospace;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex; gap: 10px; align-items: center;
|
||||
}
|
||||
|
||||
/* ============ Net-Status ============ */
|
||||
.net-status {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px; padding: 8px 14px;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.net-status .mode-dot {
|
||||
width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--fg-subtle);
|
||||
}
|
||||
.net-status.online .mode-dot {
|
||||
background: var(--ok);
|
||||
box-shadow: 0 0 6px var(--ok-glow);
|
||||
}
|
||||
.net-status.ap .mode-dot {
|
||||
background: var(--warn);
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
.net-status.offline .mode-dot { background: var(--alert); }
|
||||
.net-status.connecting .mode-dot {
|
||||
background: var(--accent-bright);
|
||||
animation: pulse 1s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.35; }
|
||||
}
|
||||
.net-status .info-block { display: flex; flex-direction: column; line-height: 1.3; }
|
||||
.net-status .mode-label { font-weight: 510; font-size: 0.88em; }
|
||||
.net-status .mode-meta { color: var(--fg-dim); font-size: 0.85em; }
|
||||
.net-status .mode-error { color: var(--alert); font-size: 0.82em; margin-top: 2px; }
|
||||
|
||||
/* ============ Buttons — Linear ghost style ============ */
|
||||
button, .btn {
|
||||
display: inline-flex; align-items: center; gap: 6px;
|
||||
padding: 7px 14px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid rgba(36,40,44,1);
|
||||
color: var(--fg-muted);
|
||||
border-radius: 6px; cursor: pointer; font-size: 0.88em;
|
||||
font-family: inherit; font-weight: 400;
|
||||
transition: all 0.12s ease;
|
||||
}
|
||||
button:hover, .btn:hover {
|
||||
background: rgba(255,255,255,0.04);
|
||||
color: var(--fg);
|
||||
border-color: rgba(255,255,255,0.08);
|
||||
}
|
||||
button:active { transform: scale(0.98); }
|
||||
button:disabled { opacity: 0.4; cursor: not-allowed; }
|
||||
button.primary {
|
||||
background: var(--accent); color: #fff; border-color: var(--accent);
|
||||
}
|
||||
button.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
||||
button.danger { color: var(--alert); border-color: rgba(248,113,113,0.3); }
|
||||
button.danger:hover { background: rgba(248,113,113,0.08); }
|
||||
button.warn { color: var(--warn); border-color: rgba(251,191,36,0.3); }
|
||||
button.ghost { background: transparent; border-color: transparent; }
|
||||
button.ghost:hover { background: rgba(255,255,255,0.04); border-color: rgba(255,255,255,0.08); }
|
||||
button.icon { padding: 6px 8px; }
|
||||
button.refresh {
|
||||
background: var(--accent); color: #fff; border-color: var(--accent);
|
||||
}
|
||||
button.refresh:hover {
|
||||
background: var(--accent-hover);
|
||||
box-shadow: 0 2px 8px var(--accent-glow);
|
||||
}
|
||||
|
||||
/* ============ Tabs ============ */
|
||||
.tabs {
|
||||
display: flex; gap: 2px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.tab {
|
||||
padding: 10px 16px;
|
||||
background: transparent; border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--fg-dim); cursor: pointer;
|
||||
font-size: 0.9em; font-weight: 400;
|
||||
font-family: inherit;
|
||||
transition: all 0.12s ease;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.tab:hover { color: var(--fg-muted); }
|
||||
.tab.active {
|
||||
color: var(--fg); border-bottom-color: var(--accent-bright);
|
||||
font-weight: 510;
|
||||
}
|
||||
.tab-content { display: none; }
|
||||
.tab-content.active { display: block; }
|
||||
|
||||
/* ============ Cards — Linear elevated surface ============ */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 10px;
|
||||
padding: 20px 22px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.card-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 16px; padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
.card-header h2, .card-header h3 {
|
||||
font-size: 0.9em; font-weight: 510;
|
||||
letter-spacing: -0.01em;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
}
|
||||
.card-header .badge {
|
||||
font-size: 0.7em; padding: 2px 8px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
color: var(--fg-dim);
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, 'SF Mono', monospace;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* ============ Layout Editor ============ */
|
||||
.layout-toolbar {
|
||||
display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.add-form {
|
||||
display: flex; gap: 8px; align-items: center;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 4px 4px 4px 14px;
|
||||
}
|
||||
.add-form select, .add-form input {
|
||||
border: none; background: transparent; color: var(--fg);
|
||||
padding: 6px 8px; font-family: inherit; font-size: 0.88em;
|
||||
}
|
||||
.add-form select { padding-right: 20px; }
|
||||
.add-form select:focus, .add-form input:focus { outline: none; }
|
||||
.add-form button { padding: 6px 12px; font-size: 0.85em; }
|
||||
|
||||
.grid-preview {
|
||||
display: grid;
|
||||
grid-template-columns: repeat({{ grid.cols }}, 1fr);
|
||||
grid-template-rows: repeat({{ grid.rows }}, 120px);
|
||||
gap: 6px;
|
||||
background: var(--border-subtle);
|
||||
padding: 6px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.grid-cell {
|
||||
background: var(--surface-2);
|
||||
border-radius: 5px;
|
||||
}
|
||||
.grid-item {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
display: flex; flex-direction: column;
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
transition: all 0.12s ease;
|
||||
cursor: grab;
|
||||
}
|
||||
.grid-item:hover {
|
||||
border-color: var(--accent-bright);
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.grid-item.dragging { opacity: 0.4; }
|
||||
.grid-item.oob {
|
||||
border-color: rgba(248,113,113,0.5);
|
||||
background: rgba(248,113,113,0.04);
|
||||
}
|
||||
.grid-item .item-label {
|
||||
font-weight: 510; font-size: 0.85em; line-height: 1.2;
|
||||
color: var(--fg);
|
||||
}
|
||||
.grid-item .item-meta {
|
||||
font-size: 0.7em; color: var(--fg-dim);
|
||||
font-family: ui-monospace, 'SF Mono', monospace;
|
||||
}
|
||||
.grid-item .item-category {
|
||||
display: inline-block;
|
||||
font-size: 0.62em; padding: 2px 6px;
|
||||
background: rgba(113,112,255,0.1);
|
||||
color: var(--accent-bright);
|
||||
border-radius: 3px;
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
font-weight: 510;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.grid-item .item-controls {
|
||||
position: absolute; top: 6px; right: 6px;
|
||||
display: flex; gap: 2px; opacity: 0;
|
||||
transition: opacity 0.12s;
|
||||
}
|
||||
.grid-item:hover .item-controls { opacity: 1; }
|
||||
.grid-item .item-controls button {
|
||||
padding: 2px 5px; font-size: 0.68em; line-height: 1.3;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-subtle);
|
||||
}
|
||||
|
||||
.layout-status {
|
||||
font-size: 0.82em; color: var(--fg-dim);
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.layout-status.has-warnings { color: var(--warn); }
|
||||
.layout-status::before {
|
||||
content: ''; width: 6px; height: 6px; border-radius: 50%;
|
||||
background: currentColor;
|
||||
}
|
||||
|
||||
/* ============ Plugin-Config Cards ============ */
|
||||
.plugin-config-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 10px;
|
||||
padding: 16px 20px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.plugin-config-card h3 {
|
||||
font-size: 0.9em; font-weight: 510;
|
||||
letter-spacing: -0.01em;
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.plugin-config-card h3 .badge {
|
||||
font-size: 0.7em; padding: 2px 8px;
|
||||
background: rgba(255,255,255,0.05);
|
||||
color: var(--fg-dim);
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, 'SF Mono', monospace;
|
||||
font-weight: 400;
|
||||
}
|
||||
.plugin-config-card .desc {
|
||||
font-size: 0.82em; color: var(--fg-dim); margin-bottom: 14px;
|
||||
}
|
||||
.plugin-config-card label {
|
||||
display: block; margin: 12px 0 4px;
|
||||
font-size: 0.76em; color: var(--fg-dim);
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
font-weight: 500;
|
||||
}
|
||||
.plugin-config-card input[type=text],
|
||||
.plugin-config-card input[type=number],
|
||||
.plugin-config-card input[type=password],
|
||||
.plugin-config-card select {
|
||||
width: 100%; padding: 9px 12px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px;
|
||||
color: var(--fg);
|
||||
font-size: 0.88em; font-family: inherit;
|
||||
transition: all 0.12s;
|
||||
}
|
||||
.plugin-config-card input:focus, .plugin-config-card select:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-bright);
|
||||
box-shadow: 0 0 0 3px rgba(113,112,255,0.1);
|
||||
}
|
||||
.plugin-config-card input[type=checkbox] {
|
||||
width: 16px; height: 16px;
|
||||
accent-color: var(--accent-bright);
|
||||
}
|
||||
.plugin-config-card .checkbox-row {
|
||||
display: flex; align-items: center; gap: 8px; margin: 12px 0;
|
||||
}
|
||||
.plugin-config-card .checkbox-row label {
|
||||
margin: 0; text-transform: none; letter-spacing: 0; font-size: 0.88em;
|
||||
color: var(--fg-muted);
|
||||
}
|
||||
.plugin-config-card .help {
|
||||
font-size: 0.76em; color: var(--fg-subtle);
|
||||
margin-top: 4px; font-style: italic;
|
||||
}
|
||||
.plugin-config-card .secret-row {
|
||||
display: flex; gap: 6px; align-items: stretch;
|
||||
}
|
||||
.plugin-config-card .secret-row input {
|
||||
flex: 1;
|
||||
font-family: ui-monospace, 'SF Mono', monospace;
|
||||
}
|
||||
.plugin-config-card .secret-row button {
|
||||
padding: 0 12px;
|
||||
color: var(--alert);
|
||||
border-color: rgba(248,113,113,0.3);
|
||||
}
|
||||
.plugin-config-card .actions {
|
||||
display: flex; gap: 8px; margin-top: 16px;
|
||||
padding-top: 12px; border-top: 1px solid var(--border-subtle);
|
||||
}
|
||||
.plugin-config-card .save-status {
|
||||
font-size: 0.78em; color: var(--success);
|
||||
display: none; align-items: center; gap: 4px;
|
||||
}
|
||||
.plugin-config-card .save-status.visible { display: inline-flex; }
|
||||
|
||||
/* ============ Preview ============ */
|
||||
.preview-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 10px;
|
||||
padding: 20px 22px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.preview-card .preview-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.preview-card .preview-header h2 {
|
||||
font-size: 0.9em; font-weight: 510;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.preview-card .meta { color: var(--fg-dim); font-size: 0.82em; }
|
||||
.preview-card img {
|
||||
max-width: 100%; height: auto; display: block;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--border-subtle);
|
||||
image-rendering: pixelated;
|
||||
}
|
||||
|
||||
/* ============ Network panel ============ */
|
||||
.network-grid {
|
||||
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
|
||||
}
|
||||
.network-grid > .card { margin-bottom: 0; }
|
||||
.network-grid .full-width { grid-column: 1 / -1; }
|
||||
.network-grid h3 {
|
||||
font-size: 0.9em; font-weight: 510;
|
||||
letter-spacing: -0.01em;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.network-grid label {
|
||||
display: block; margin: 12px 0 4px;
|
||||
font-size: 0.76em; color: var(--fg-dim);
|
||||
text-transform: uppercase; letter-spacing: 0.06em;
|
||||
}
|
||||
.network-grid input, .network-grid select {
|
||||
width: 100%; padding: 9px 12px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px; color: var(--fg);
|
||||
font-family: inherit; font-size: 0.88em;
|
||||
}
|
||||
.network-grid input:focus, .network-grid select:focus {
|
||||
outline: none; border-color: var(--accent-bright);
|
||||
}
|
||||
.network-grid .checkbox-row {
|
||||
display: flex; align-items: center; gap: 8px; margin: 12px 0;
|
||||
}
|
||||
.network-grid .checkbox-row label { margin: 0; text-transform: none; letter-spacing: 0; }
|
||||
.network-grid ul#savedList { list-style: none; padding: 0; margin: 0; }
|
||||
.network-grid ul#savedList li {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 8px 0; border-bottom: 1px solid var(--border-subtle);
|
||||
}
|
||||
.network-grid ul#savedList li:last-child { border-bottom: 0; }
|
||||
.network-grid ul#savedList code {
|
||||
font-family: ui-monospace, 'SF Mono', monospace; font-size: 0.85em;
|
||||
}
|
||||
.network-grid details {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px; padding: 12px 16px;
|
||||
}
|
||||
.network-grid details summary {
|
||||
cursor: pointer; font-weight: 510; color: var(--fg-dim); font-size: 0.88em;
|
||||
}
|
||||
.network-grid details code {
|
||||
background: var(--bg); padding: 2px 6px; border-radius: 3px;
|
||||
font-size: 0.88em;
|
||||
}
|
||||
|
||||
/* ============ Empty state ============ */
|
||||
.empty-state {
|
||||
text-align: center; padding: 48px 20px; color: var(--fg-dim);
|
||||
}
|
||||
.empty-state .icon { font-size: 2em; margin-bottom: 10px; opacity: 0.4; }
|
||||
.empty-state .hint { font-size: 0.85em; margin-top: 4px; color: var(--fg-subtle); }
|
||||
|
||||
/* ============ Admin Theme Switcher ============ */
|
||||
.admin-theme-switcher {
|
||||
display: flex; gap: 2px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 6px; padding: 3px;
|
||||
}
|
||||
.theme-btn {
|
||||
width: 26px; height: 26px;
|
||||
border: none; border-radius: 4px;
|
||||
background: transparent; color: var(--fg-dim);
|
||||
font-size: 0.75em; font-weight: 510; font-family: inherit;
|
||||
cursor: pointer; transition: all 0.12s;
|
||||
}
|
||||
.theme-btn:hover { background: rgba(255,255,255,0.05); color: var(--fg); }
|
||||
.theme-btn.active {
|
||||
background: var(--accent-bright); color: #fff;
|
||||
}
|
||||
|
||||
/* ============ Display Design Picker ============ */
|
||||
.design-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.design-option {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
text-align: center;
|
||||
}
|
||||
.design-option:hover {
|
||||
border-color: var(--accent-bright);
|
||||
background: var(--surface-3);
|
||||
}
|
||||
.design-option.selected {
|
||||
border-color: var(--accent-bright);
|
||||
background: rgba(113,112,255,0.06);
|
||||
box-shadow: 0 0 0 1px var(--accent-bright);
|
||||
}
|
||||
.design-option .design-name {
|
||||
font-size: 0.82em; font-weight: 510; margin-bottom: 4px;
|
||||
}
|
||||
.design-option .design-desc {
|
||||
font-size: 0.72em; color: var(--fg-dim); line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ============ Responsive ============ */
|
||||
@media (max-width: 900px) {
|
||||
.container { padding: 16px; }
|
||||
.network-grid { grid-template-columns: 1fr; }
|
||||
.app-header { flex-direction: column; align-items: flex-start; gap: 16px; }
|
||||
.header-actions { width: 100%; flex-wrap: wrap; }
|
||||
}
|
||||
|
||||
/* ============ Scrollbar ============ */
|
||||
::-webkit-scrollbar { width: 6px; height: 6px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg); }
|
||||
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.12); }
|
||||
</style>
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
||||
@@ -628,31 +1140,45 @@
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Display Theme</h2>
|
||||
<h2>Display Design</h2>
|
||||
<span class="badge" id="currentDesignBadge">—</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 16px; align-items: flex-start; flex-wrap: wrap;">
|
||||
<div style="flex: 1; min-width: 200px;">
|
||||
<label style="display: block; margin-bottom: 8px; font-size: 0.82em; color: var(--fg-muted);">
|
||||
Theme für das e-Paper-Display
|
||||
</label>
|
||||
<select id="displayThemeSelect" onchange="setDisplayTheme(this.value)"
|
||||
style="width: 100%; padding: 8px 12px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; color: var(--fg); font-size: 0.9em;">
|
||||
<option value="default" id="themeOpt_default">Classic White</option>
|
||||
<option value="dark" id="themeOpt_dark">Dark Mode</option>
|
||||
<option value="sepia" id="themeOpt_sepia">Sepia</option>
|
||||
<option value="nord" id="themeOpt_nord">Nord</option>
|
||||
<option value="terminal" id="themeOpt_terminal">Terminal (Green)</option>
|
||||
</select>
|
||||
<p style="font-size: 0.82em; color: var(--fg-dim); margin-bottom: 16px;">
|
||||
bestimmt das Layout auf dem e-Paper-Display.
|
||||
</p>
|
||||
<div class="design-grid" id="designGrid">
|
||||
<div class="design-option" data-design="minimal" onclick="setDisplayDesign('minimal')">
|
||||
<div class="design-name">Minimal</div>
|
||||
<div class="design-desc">Nur Content, kein UI-Chrome, maximal clean</div>
|
||||
</div>
|
||||
<div id="themePreviewBox" style="display: flex; gap: 10px; align-items: center; padding-top: 22px;">
|
||||
<div style="text-align: center;">
|
||||
<div id="themePreview" style="width: 80px; height: 48px; border-radius: 4px; border: 1px solid var(--border); background: #fff; display: flex; align-items: center; justify-content: center; font-size: 0.65em; color: #000;">Preview</div>
|
||||
<div style="font-size: 0.65em; color: var(--fg-dim); margin-top: 4px;" id="themePreviewLabel">Classic</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="minimal_color" onclick="setDisplayDesign('minimal_color')">
|
||||
<div class="design-name">Minimal Color</div>
|
||||
<div class="design-desc">WinZiger Akzent-Strich pro Widget</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="bold" onclick="setDisplayDesign('bold')">
|
||||
<div class="design-name">Bold</div>
|
||||
<div class="design-desc">Riesen-Typography, 1px-Rahmen, vollflächig</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="analog" onclick="setDisplayDesign('analog')">
|
||||
<div class="design-name">Analog Clock</div>
|
||||
<div class="design-desc">2×2-Uhr als Ziffernblatt, Rest clean Panels</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="magazine" onclick="setDisplayDesign('magazine')">
|
||||
<div class="design-name">Magazine</div>
|
||||
<div class="design-desc">Bold Typography, farbige Akzentstreifen links</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="cards" onclick="setDisplayDesign('cards')">
|
||||
<div class="design-name">Cards</div>
|
||||
<div class="design-desc">iOS-Widget-Style, Emoji-Icons, warme Paneele</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="kiosk" onclick="setDisplayDesign('kiosk')">
|
||||
<div class="design-name">Kiosk</div>
|
||||
<div class="design-desc">Dunkel, Daten-first, kompakte Info-Zellen</div>
|
||||
</div>
|
||||
<div class="design-option" data-design="classic" onclick="setDisplayDesign('classic')">
|
||||
<div class="design-name">Classic</div>
|
||||
<div class="design-desc">Original flaches Design, weißer Hintergrund</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta" style="margin-top: 12px;">
|
||||
Theme wird beim nächsten Refresh auf dem Display angezeigt. Nur für Designs mit weißem Hintergrund geeignet (Widget-Farben bleiben gleich).
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1068,36 +1594,36 @@
|
||||
const savedTheme = localStorage.getItem('adminTheme');
|
||||
if (savedTheme) setAdminTheme(savedTheme);
|
||||
|
||||
// ============ Display Theme Switcher ============
|
||||
const THEME_PREVIEWS = {
|
||||
default: { bg: '#ffffff', fg: '#000000', label: 'Classic' },
|
||||
dark: { bg: '#111111', fg: '#f0f0f0', label: 'Dark' },
|
||||
sepia: { bg: '#f4ecd8', fg: '#5b4636', label: 'Sepia' },
|
||||
nord: { bg: '#eceff4', fg: '#2e3440', label: 'Nord' },
|
||||
terminal: { bg: '#0d0d00', fg: '#e8c840', label: 'Terminal' },
|
||||
// ============ Display Design Switcher ============
|
||||
const DESIGN_LABELS = {
|
||||
minimal: "Minimal", minimal_color: "Minimal Color",
|
||||
bold: "Bold", analog: "Analog Clock", magazine: "Magazine",
|
||||
cards: "Cards", kiosk: "Kiosk", classic: "Classic",
|
||||
};
|
||||
function updateThemePreview(theme) {
|
||||
const p = THEME_PREVIEWS[theme] || THEME_PREVIEWS.default;
|
||||
const el = document.getElementById('themePreview');
|
||||
const lbl = document.getElementById('themePreviewLabel');
|
||||
if (el) { el.style.background = p.bg; el.style.color = p.fg; }
|
||||
if (lbl) lbl.textContent = p.label;
|
||||
}
|
||||
async function loadDisplayTheme() {
|
||||
const r = await fetch('/api/display_theme');
|
||||
|
||||
async function loadDisplayDesign() {
|
||||
const r = await fetch('/api/config');
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
document.getElementById('displayThemeSelect').value = d.current;
|
||||
updateThemePreview(d.current);
|
||||
const cfg = await r.json();
|
||||
const current = cfg.display_design || 'minimal';
|
||||
document.querySelectorAll('.design-option').forEach(el => {
|
||||
el.classList.toggle('selected', el.dataset.design === current);
|
||||
});
|
||||
const badge = document.getElementById('currentDesignBadge');
|
||||
if (badge) badge.textContent = DESIGN_LABELS[current] || current;
|
||||
}
|
||||
async function setDisplayTheme(theme) {
|
||||
updateThemePreview(theme);
|
||||
const r = await fetch('/api/display_theme', {
|
||||
|
||||
async function setDisplayDesign(design) {
|
||||
await fetch('/api/display_design', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({theme})
|
||||
body: JSON.stringify({design}),
|
||||
});
|
||||
if (r.ok) triggerRefresh();
|
||||
document.querySelectorAll('.design-option').forEach(el => {
|
||||
el.classList.toggle('selected', el.dataset.design === design);
|
||||
});
|
||||
const badge = document.getElementById('currentDesignBadge');
|
||||
if (badge) badge.textContent = DESIGN_LABELS[design] || design;
|
||||
}
|
||||
|
||||
// ============ Init ============
|
||||
@@ -1105,7 +1631,7 @@
|
||||
refreshNetStatus();
|
||||
refreshScan();
|
||||
refreshSaved();
|
||||
loadDisplayTheme();
|
||||
loadDisplayDesign();
|
||||
setInterval(refreshNetStatus, 10000);
|
||||
setInterval(refreshScan, 60000);
|
||||
setInterval(refreshPreview, 10000);
|
||||
|
||||
Reference in New Issue
Block a user