22a3078af9
Implements credentialed admin panel and multi-step setup wizard so new
users can configure location, receivers, and preferences without editing
JSON files. Adds bcrypt password auth with session cookies, setup lockout
after first run, and expanded config schema with backward compatibility.
New files: admin/{html,css,js}, setup/{html,css,js}
Modified: server.py (auth, setup, admin APIs), index.html (dynamic title),
pixel-view.js (site/display config), requirements.txt (bcrypt),
config.json.example (full schema)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
393 lines
6.5 KiB
CSS
393 lines
6.5 KiB
CSS
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
background: linear-gradient(180deg, #2c3e50 0%, #1a252f 100%);
|
|
color: #fcfcfc;
|
|
font-family: 'Courier New', monospace;
|
|
min-height: 100vh;
|
|
}
|
|
|
|
.hidden { display: none !important; }
|
|
|
|
/* Login Screen */
|
|
#login-screen {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.login-box {
|
|
background: rgba(52, 73, 94, 0.9);
|
|
border: 3px solid #5c94fc;
|
|
border-radius: 12px;
|
|
padding: 40px;
|
|
text-align: center;
|
|
max-width: 400px;
|
|
width: 100%;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
|
}
|
|
|
|
.retro-title {
|
|
font-family: 'Press Start 2P', monospace;
|
|
color: #fcd444;
|
|
text-shadow: 2px 2px 0px #000;
|
|
font-size: 20px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.login-subtitle {
|
|
color: #b4d4ec;
|
|
font-size: 14px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
#login-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
input[type="password"],
|
|
input[type="text"],
|
|
input[type="number"],
|
|
textarea,
|
|
select {
|
|
background: rgba(0, 0, 0, 0.5);
|
|
border: 2px solid #5c94fc;
|
|
border-radius: 6px;
|
|
color: #fcfcfc;
|
|
font-family: 'Courier New', monospace;
|
|
font-size: 14px;
|
|
padding: 10px 12px;
|
|
width: 100%;
|
|
outline: none;
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
input:focus, textarea:focus, select:focus {
|
|
border-color: #fcd444;
|
|
}
|
|
|
|
select {
|
|
cursor: pointer;
|
|
}
|
|
|
|
select option {
|
|
background: #2c3e50;
|
|
color: #fcfcfc;
|
|
}
|
|
|
|
textarea {
|
|
resize: vertical;
|
|
min-height: 80px;
|
|
}
|
|
|
|
.btn {
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 11px;
|
|
padding: 10px 20px;
|
|
border: 2px solid #5c94fc;
|
|
border-radius: 6px;
|
|
background: rgba(92, 148, 252, 0.2);
|
|
color: #fcfcfc;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
text-decoration: none;
|
|
display: inline-block;
|
|
text-align: center;
|
|
}
|
|
|
|
.btn:hover {
|
|
background: rgba(92, 148, 252, 0.4);
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.btn:active {
|
|
transform: translateY(1px);
|
|
}
|
|
|
|
.btn-primary {
|
|
background: rgba(252, 212, 68, 0.3);
|
|
border-color: #fcd444;
|
|
color: #fcd444;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: rgba(252, 212, 68, 0.5);
|
|
}
|
|
|
|
.btn-danger {
|
|
border-color: #fc5454;
|
|
color: #fc5454;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background: rgba(252, 84, 84, 0.3);
|
|
}
|
|
|
|
.btn-small {
|
|
font-size: 9px;
|
|
padding: 6px 12px;
|
|
}
|
|
|
|
.error-msg {
|
|
color: #fc5454;
|
|
font-size: 12px;
|
|
margin-top: 10px;
|
|
}
|
|
|
|
.back-link {
|
|
display: inline-block;
|
|
margin-top: 16px;
|
|
color: #b4d4ec;
|
|
text-decoration: none;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.back-link:hover {
|
|
color: #fcd444;
|
|
}
|
|
|
|
/* Admin Panel */
|
|
.admin-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 12px 20px;
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border-bottom: 3px solid #5c94fc;
|
|
}
|
|
|
|
.admin-header .retro-title {
|
|
font-size: 16px;
|
|
margin: 0;
|
|
}
|
|
|
|
.header-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
/* Tab Bar */
|
|
.tab-bar {
|
|
display: flex;
|
|
gap: 2px;
|
|
padding: 10px 20px 0;
|
|
overflow-x: auto;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
|
|
.tab {
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 9px;
|
|
padding: 8px 14px;
|
|
background: rgba(52, 73, 94, 0.6);
|
|
border: 2px solid #5c94fc;
|
|
border-bottom: none;
|
|
border-radius: 6px 6px 0 0;
|
|
color: #b4d4ec;
|
|
cursor: pointer;
|
|
transition: all 0.15s;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.tab:hover {
|
|
background: rgba(92, 148, 252, 0.3);
|
|
color: #fcfcfc;
|
|
}
|
|
|
|
.tab.active {
|
|
background: rgba(52, 73, 94, 0.9);
|
|
color: #fcd444;
|
|
border-color: #fcd444;
|
|
}
|
|
|
|
/* Tab Content */
|
|
.tab-content {
|
|
padding: 20px;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.tab-pane {
|
|
background: rgba(52, 73, 94, 0.6);
|
|
border: 2px solid #5c94fc;
|
|
border-radius: 0 8px 8px 8px;
|
|
padding: 24px;
|
|
}
|
|
|
|
.tab-pane h2 {
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 14px;
|
|
color: #fcd444;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
/* Status Grid */
|
|
.status-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 12px;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.status-card {
|
|
background: rgba(0, 0, 0, 0.4);
|
|
border: 2px solid #5c94fc;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
text-align: center;
|
|
}
|
|
|
|
.status-label {
|
|
display: block;
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 8px;
|
|
color: #b4d4ec;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.status-value {
|
|
display: block;
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 18px;
|
|
color: #54fc54;
|
|
}
|
|
|
|
/* Forms */
|
|
.form-group {
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 9px;
|
|
color: #b4d4ec;
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.form-row {
|
|
display: flex;
|
|
gap: 16px;
|
|
}
|
|
|
|
.form-row .form-group {
|
|
flex: 1;
|
|
}
|
|
|
|
.hint {
|
|
display: block;
|
|
font-size: 11px;
|
|
color: #888;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.checkbox-label {
|
|
display: flex !important;
|
|
align-items: center;
|
|
gap: 8px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.checkbox-label input[type="checkbox"] {
|
|
width: 18px;
|
|
height: 18px;
|
|
cursor: pointer;
|
|
accent-color: #fcd444;
|
|
}
|
|
|
|
.button-row {
|
|
display: flex;
|
|
gap: 10px;
|
|
margin-top: 20px;
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.info-box {
|
|
background: rgba(0, 0, 0, 0.3);
|
|
border: 2px solid #5c94fc;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.info-box h3 {
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 10px;
|
|
color: #fcd444;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
/* Toast Notification */
|
|
.toast {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
background: rgba(84, 252, 84, 0.9);
|
|
color: #000;
|
|
font-family: 'Press Start 2P', monospace;
|
|
font-size: 10px;
|
|
padding: 12px 24px;
|
|
border-radius: 8px;
|
|
border: 2px solid #54fc54;
|
|
z-index: 1000;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
.toast.error {
|
|
background: rgba(252, 84, 84, 0.9);
|
|
border-color: #fc5454;
|
|
color: #fff;
|
|
}
|
|
|
|
/* Mobile */
|
|
@media (max-width: 768px) {
|
|
.admin-header {
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
text-align: center;
|
|
}
|
|
|
|
.tab-bar {
|
|
padding: 8px 10px 0;
|
|
}
|
|
|
|
.tab {
|
|
font-size: 8px;
|
|
padding: 6px 10px;
|
|
}
|
|
|
|
.tab-content {
|
|
padding: 10px;
|
|
}
|
|
|
|
.tab-pane {
|
|
padding: 16px;
|
|
}
|
|
|
|
.form-row {
|
|
flex-direction: column;
|
|
gap: 0;
|
|
}
|
|
|
|
.status-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.button-row {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.button-row .btn {
|
|
width: 100%;
|
|
}
|
|
}
|