mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-01 16:58:06 +00:00
* Surface plugin update availability on the plugins page
The plugin manager page already showed each installed plugin's version and
had an Update button, but nothing told users an update actually existed —
they had to guess. The store manager already compares the installed
manifest version against the registry's latest_version for its reinstall
decision; this surfaces that same signal in the UI.
- api_v3 /plugins/installed now returns `latest_version` (from the registry
cache, no extra network call) and an `update_available` flag computed by a
new semver-aware helper `_is_plugin_update_available`. A locally modified
plugin whose version is ahead of the registry is not flagged.
- The installed-plugin card shows "vX.Y.Z available" next to the installed
version, and the Update button becomes emphasized ("Update to vX.Y.Z" with
a gentle pulse) when a newer version is published — mirroring the app's own
update banner styling.
- Added tests for the helper and the endpoint fields.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EW8yDbsk8EceDpq6Hjgqj
* Catch InvalidVersion specifically in plugin update comparison
Address review feedback: the version comparison caught a blind `except
Exception` (ruff BLE001). Split the two failure modes and catch each
specifically — ImportError for a missing `packaging` (a core dependency)
and InvalidVersion for an unparseable version string — while preserving
the existing "surface the mismatch" fallback behavior. Added a test for
the unparseable-version path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EW8yDbsk8EceDpq6Hjgqj
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1415 lines
40 KiB
CSS
1415 lines
40 KiB
CSS
/* LED Matrix v3 Custom Styles */
|
|
/* Modern, clean design with utility classes */
|
|
|
|
/* CSS Custom Properties for Theme Colors */
|
|
:root {
|
|
--color-primary: #2563eb;
|
|
--color-primary-hover: #1d4ed8;
|
|
--color-secondary: #059669;
|
|
--color-secondary-hover: #047857;
|
|
--color-accent: #7c3aed;
|
|
--color-accent-hover: #6d28d9;
|
|
--color-background: #f9fafb;
|
|
--color-surface: #ffffff;
|
|
--color-text-primary: #111827;
|
|
--color-text-secondary: #374151;
|
|
--color-text-tertiary: #4b5563;
|
|
--color-border: #e5e7eb;
|
|
--color-border-light: #f3f4f6;
|
|
--color-success: #059669;
|
|
--color-success-bg: #d1fae5;
|
|
--color-error: #dc2626;
|
|
--color-error-bg: #fee2e2;
|
|
--color-warning: #d97706;
|
|
--color-warning-bg: #fef3c7;
|
|
--color-info: #2563eb;
|
|
--color-info-bg: #dbeafe;
|
|
--color-purple-bg: #f3e8ff;
|
|
--color-purple-text: #6b21a8;
|
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 5%);
|
|
--shadow: 0 1px 3px 0 rgb(0 0 0 / 10%), 0 1px 2px 0 rgb(0 0 0 / 6%);
|
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 10%), 0 2px 4px -1px rgb(0 0 0 / 6%);
|
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 10%), 0 4px 6px -2px rgb(0 0 0 / 5%);
|
|
}
|
|
|
|
/* Dark Theme Overrides */
|
|
[data-theme="dark"] {
|
|
--color-primary: #3b82f6;
|
|
--color-primary-hover: #60a5fa;
|
|
--color-secondary: #10b981;
|
|
--color-secondary-hover: #34d399;
|
|
--color-accent: #8b5cf6;
|
|
--color-accent-hover: #a78bfa;
|
|
--color-background: #111827;
|
|
--color-surface: #1f2937;
|
|
--color-text-primary: #f9fafb;
|
|
--color-text-secondary: #d1d5db;
|
|
--color-text-tertiary: #9ca3af;
|
|
--color-border: #374151;
|
|
--color-border-light: #1f2937;
|
|
--color-success: #34d399;
|
|
--color-success-bg: #064e3b;
|
|
--color-error: #f87171;
|
|
--color-error-bg: #7f1d1d;
|
|
--color-warning: #fbbf24;
|
|
--color-warning-bg: #78350f;
|
|
--color-info: #60a5fa;
|
|
--color-info-bg: #1e3a5f;
|
|
--color-purple-bg: #2e1065;
|
|
--color-purple-text: #c4b5fd;
|
|
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 30%);
|
|
--shadow: 0 1px 3px 0 rgb(0 0 0 / 40%), 0 1px 2px 0 rgb(0 0 0 / 30%);
|
|
--shadow-md: 0 4px 6px -1px rgb(0 0 0 / 40%), 0 2px 4px -1px rgb(0 0 0 / 30%);
|
|
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 40%), 0 4px 6px -2px rgb(0 0 0 / 30%);
|
|
}
|
|
|
|
/* Dark Mode - Utility Class Overrides */
|
|
[data-theme="dark"] .bg-white { background-color: var(--color-surface); }
|
|
[data-theme="dark"] .bg-gray-50 { background-color: var(--color-background); }
|
|
[data-theme="dark"] .bg-gray-100 { background-color: var(--color-surface); }
|
|
[data-theme="dark"] .bg-gray-200 { background-color: var(--color-border); }
|
|
|
|
[data-theme="dark"] .text-gray-900 { color: var(--color-text-primary); }
|
|
[data-theme="dark"] .text-gray-600 { color: var(--color-text-tertiary); }
|
|
[data-theme="dark"] .text-gray-500 { color: var(--color-text-tertiary); }
|
|
[data-theme="dark"] .text-gray-400 { color: var(--color-text-tertiary); }
|
|
[data-theme="dark"] .text-green-600 { color: var(--color-success); }
|
|
[data-theme="dark"] .text-red-600 { color: var(--color-error); }
|
|
|
|
[data-theme="dark"] .border-gray-200 { border-color: var(--color-border); }
|
|
[data-theme="dark"] .border-gray-300 { border-color: #4b5563; }
|
|
|
|
[data-theme="dark"] .hover\:bg-gray-50:hover { background-color: var(--color-border); }
|
|
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: #4b5563; }
|
|
[data-theme="dark"] .hover\:text-gray-700:hover { color: #e5e7eb; }
|
|
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: #6b7280; }
|
|
[data-theme="dark"] .bg-red-100 { background-color: #450a0a; }
|
|
[data-theme="dark"] .bg-yellow-100 { background-color: #422006; }
|
|
[data-theme="dark"] .bg-green-100 { background-color: #022c22; }
|
|
[data-theme="dark"] .text-red-700 { color: #fca5a5; }
|
|
[data-theme="dark"] .hover\:bg-red-200:hover { background-color: #7f1d1d; }
|
|
|
|
/* Base styles */
|
|
* {
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
html {
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
line-height: 1.6;
|
|
color: var(--color-text-primary);
|
|
background-color: var(--color-background);
|
|
}
|
|
|
|
/* Utility classes */
|
|
.bg-gray-50 { background-color: #f9fafb; }
|
|
.bg-gray-100 { background-color: #f3f4f6; }
|
|
.bg-white { background-color: #ffffff; }
|
|
.bg-gray-800 { background-color: #1f2937; }
|
|
.bg-gray-900 { background-color: #111827; }
|
|
.bg-green-500 { background-color: #10b981; }
|
|
.bg-red-500 { background-color: #ef4444; }
|
|
.bg-blue-500 { background-color: #3b82f6; }
|
|
.bg-yellow-500 { background-color: #f59e0b; }
|
|
.bg-green-600 { background-color: #059669; }
|
|
.bg-red-600 { background-color: #dc2626; }
|
|
.bg-blue-600 { background-color: #2563eb; }
|
|
.bg-yellow-600 { background-color: #d97706; }
|
|
.bg-gray-200 { background-color: #e5e7eb; }
|
|
|
|
/* Log level row backgrounds (subtle tints on dark log container) */
|
|
.log-level-error { background-color: rgb(127 29 29 / 10%); }
|
|
.log-level-warning { background-color: rgb(120 53 15 / 10%); }
|
|
.log-level-debug { background-color: rgb(31 41 55 / 30%); }
|
|
|
|
.text-gray-100 { color: #f3f4f6; }
|
|
.text-gray-200 { color: #e5e7eb; }
|
|
.text-gray-300 { color: #d1d5db; }
|
|
.text-gray-900 { color: #111827; }
|
|
.text-gray-600 { color: #374151; }
|
|
.text-gray-500 { color: #4b5563; }
|
|
.text-gray-400 { color: #6b7280; }
|
|
.text-red-300 { color: #fca5a5; }
|
|
.text-yellow-300 { color: #fcd34d; }
|
|
.text-white { color: #ffffff; }
|
|
.text-green-600 { color: #059669; }
|
|
.text-red-600 { color: #dc2626; }
|
|
|
|
/* Status badge chips (e.g. tools.html's dirty/clean and power-status badges) */
|
|
.bg-red-100 { background-color: #fee2e2; }
|
|
.bg-yellow-100 { background-color: #fef9c3; }
|
|
.bg-green-100 { background-color: #dcfce7; }
|
|
.text-red-800 { color: #991b1b; }
|
|
.text-yellow-800 { color: #854d0e; }
|
|
.text-green-800 { color: #166534; }
|
|
|
|
.border-gray-200 { border-color: #e5e7eb; }
|
|
.border-gray-300 { border-color: #d1d5db; }
|
|
.border-gray-700 { border-color: #374151; }
|
|
.border-transparent { border-color: transparent; }
|
|
|
|
.rounded-lg { border-radius: 0.5rem; }
|
|
.rounded-md { border-radius: 0.375rem; }
|
|
.rounded-full { border-radius: 9999px; }
|
|
.rounded { border-radius: 0.25rem; }
|
|
|
|
.shadow { box-shadow: var(--shadow); }
|
|
.shadow-sm { box-shadow: var(--shadow-sm); }
|
|
.shadow-md { box-shadow: var(--shadow-md); }
|
|
.shadow-lg { box-shadow: var(--shadow-lg); }
|
|
|
|
.p-6 { padding: 1.5rem; }
|
|
.p-4 { padding: 1rem; }
|
|
.p-2 { padding: 0.5rem; }
|
|
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
|
.py-1\.5 { padding-top: 0.375rem; padding-bottom: 0.375rem; }
|
|
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
|
|
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
|
.pb-4 { padding-bottom: 1rem; }
|
|
.mb-6 { margin-bottom: 1.5rem; }
|
|
.mb-4 { margin-bottom: 1rem; }
|
|
.mb-8 { margin-bottom: 2rem; }
|
|
.mb-2 { margin-bottom: 0.5rem; }
|
|
.mt-1 { margin-top: 0.25rem; }
|
|
.mt-4 { margin-top: 1rem; }
|
|
.mr-2 { margin-right: 0.5rem; }
|
|
.ml-3 { margin-left: 0.75rem; }
|
|
|
|
.w-full { width: 100%; }
|
|
.w-0 { width: 0; }
|
|
.w-2 { width: 0.5rem; }
|
|
.w-4 { width: 1rem; }
|
|
.h-2 { height: 0.5rem; }
|
|
.h-4 { height: 1rem; }
|
|
.h-10 { height: 2.5rem; }
|
|
.h-16 { height: 4rem; }
|
|
.h-24 { height: 6rem; }
|
|
.h-32 { height: 8rem; }
|
|
.h-96 { height: 24rem; }
|
|
|
|
.flex { display: flex; }
|
|
.inline-flex { display: inline-flex; }
|
|
.flex-wrap { flex-wrap: wrap; }
|
|
.flex-shrink-0 { flex-shrink: 0; }
|
|
.flex-1 { flex: 1; }
|
|
.items-center { align-items: center; }
|
|
.justify-center { justify-content: center; }
|
|
.justify-between { justify-content: space-between; }
|
|
.space-x-1 > * + * { margin-left: 0.25rem; }
|
|
.space-x-2 > * + * { margin-left: 0.5rem; }
|
|
.space-x-4 > * + * { margin-left: 1rem; }
|
|
.space-y-1 > * + * { margin-top: 0.25rem; }
|
|
.space-y-1\.5 > * + * { margin-top: 0.375rem; }
|
|
.space-y-2 > * + * { margin-top: 0.5rem; }
|
|
.space-y-4 > * + * { margin-top: 1rem; }
|
|
.space-y-6 > * + * { margin-top: 1.5rem; }
|
|
.space-y-8 > * + * { margin-top: 2rem; }
|
|
|
|
.grid { display: grid; }
|
|
.grid-cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
.grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
.gap-1\.5 { gap: 0.375rem; }
|
|
.gap-2 { gap: 0.5rem; }
|
|
.gap-3 { gap: 0.75rem; }
|
|
.gap-4 { gap: 1rem; }
|
|
.gap-6 { gap: 1.5rem; }
|
|
.gap-x-2 { column-gap: 0.5rem; }
|
|
.gap-x-3 { column-gap: 0.75rem; }
|
|
.gap-x-4 { column-gap: 1rem; }
|
|
.gap-x-6 { column-gap: 1.5rem; }
|
|
.gap-x-8 { column-gap: 2rem; }
|
|
.gap-y-2 { row-gap: 0.5rem; }
|
|
|
|
/* Enhanced Typography */
|
|
.text-xs { font-size: 0.75rem; line-height: 1.4; }
|
|
.text-sm { font-size: 0.875rem; line-height: 1.5; }
|
|
.text-base { font-size: 1rem; line-height: 1.5; }
|
|
.text-lg { font-size: 1.125rem; line-height: 1.75; }
|
|
.text-xl { font-size: 1.25rem; line-height: 1.75; }
|
|
.text-2xl { font-size: 1.5rem; line-height: 2; }
|
|
.text-4xl { font-size: 2.25rem; line-height: 2.5; }
|
|
.font-medium { font-weight: 500; }
|
|
.font-semibold { font-weight: 600; }
|
|
.font-bold { font-weight: 700; }
|
|
|
|
/* Headings with improved hierarchy */
|
|
h1, h2, h3, h4, h5, h6 {
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
color: var(--color-text-primary);
|
|
/* Improved line-height from 1.3 to 1.4 for better readability */
|
|
}
|
|
|
|
h1 { font-size: 1.875rem; }
|
|
h2 { font-size: 1.5rem; }
|
|
h3 { font-size: 1.25rem; }
|
|
h4 { font-size: 1.125rem; }
|
|
|
|
.border-b { border-bottom-width: 1px; }
|
|
.border-b-2 { border-bottom-width: 2px; }
|
|
|
|
.relative { position: relative; }
|
|
.fixed { position: fixed; }
|
|
.absolute { position: absolute; }
|
|
.z-50 { z-index: 50; }
|
|
|
|
.top-4 { top: 1rem; }
|
|
.right-4 { right: 1rem; }
|
|
|
|
.max-w-7xl { max-width: 56rem; }
|
|
.max-w-full { max-width: 100%; }
|
|
.mx-auto { margin-left: auto; margin-right: auto; }
|
|
.overflow-x-auto { overflow-x: auto; }
|
|
.overflow-hidden { overflow: hidden; }
|
|
|
|
.aspect-video { aspect-ratio: 16 / 9; }
|
|
|
|
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
|
|
.transition { transition-property: transform, opacity, color, border-color; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
|
/* Optimized: Replaced 'all' with specific properties to avoid animating expensive properties */
|
|
|
|
/* Removed .duration-300 - not used anywhere */
|
|
|
|
.hover\:bg-green-700:hover { background-color: #047857; }
|
|
.hover\:bg-red-700:hover { background-color: #b91c1c; }
|
|
.hover\:bg-gray-50:hover { background-color: #f9fafb; }
|
|
.hover\:bg-gray-800:hover { background-color: #1f2937; }
|
|
.hover\:bg-yellow-700:hover { background-color: #b45309; }
|
|
.hover\:text-gray-700:hover { color: #374151; }
|
|
.hover\:border-gray-300:hover { border-color: #d1d5db; }
|
|
|
|
.focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; }
|
|
.focus\:ring-2:focus {
|
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
|
|
box-shadow: var(--tw-ring-offset-shadow), var(--tw-shadow, 0 0 #0000);
|
|
}
|
|
/* Optimized: Split complex selector onto multiple lines for readability */
|
|
.focus\:ring-blue-500:focus { --tw-ring-color: #3b82f6; }
|
|
|
|
.animate-pulse {
|
|
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
|
}
|
|
|
|
@keyframes pulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: .5; }
|
|
}
|
|
|
|
/* Smooth transitions for all interactive elements */
|
|
/* Optimized: Only transition properties that don't trigger expensive repaints */
|
|
a, button, input, select, textarea {
|
|
transition: color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
|
|
/* Removed background-color transition - can trigger repaints, use opacity or border-color instead */
|
|
}
|
|
|
|
/* Custom scrollbar for webkit browsers */
|
|
::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: #f1f5f9;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: #cbd5e1;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: #94a3b8;
|
|
}
|
|
|
|
/* Logs container specific scrollbar */
|
|
#logs-container::-webkit-scrollbar {
|
|
width: 10px;
|
|
}
|
|
|
|
#logs-container::-webkit-scrollbar-track {
|
|
background: #374151;
|
|
}
|
|
|
|
#logs-container::-webkit-scrollbar-thumb {
|
|
background: #6b7280;
|
|
border-radius: 5px;
|
|
}
|
|
|
|
#logs-container::-webkit-scrollbar-thumb:hover {
|
|
background: #9ca3af;
|
|
}
|
|
|
|
/* Smooth scrolling for logs container */
|
|
#logs-container {
|
|
scroll-behavior: smooth;
|
|
position: relative;
|
|
overflow-y: auto !important;
|
|
}
|
|
|
|
/* Ensure logs content doesn't cause overflow issues */
|
|
.log-entry {
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
max-width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
/* Ensure proper containment of logs */
|
|
#logs-loading {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 1;
|
|
}
|
|
|
|
#logs-empty {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 2;
|
|
}
|
|
|
|
#logs-display {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
z-index: 3;
|
|
}
|
|
|
|
.logs-content {
|
|
height: 100%;
|
|
overflow-y: auto;
|
|
padding: 0;
|
|
}
|
|
|
|
/* Logs container responsive height - simplified for better scrolling */
|
|
@media (max-width: 768px) {
|
|
#logs-container {
|
|
height: 400px !important;
|
|
min-height: 300px !important;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
#logs-container {
|
|
height: 350px !important;
|
|
min-height: 250px !important;
|
|
}
|
|
}
|
|
|
|
/* Responsive breakpoints */
|
|
@media (min-width: 640px) {
|
|
.sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
|
.sm\:block { display: block; }
|
|
.sm\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.sm\:text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
.md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.md\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.md\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
.md\:flex { display: flex; }
|
|
.md\:hidden { display: none; }
|
|
.md\:block { display: block; }
|
|
.md\:w-auto { width: auto; }
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.lg\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.lg\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
.lg\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
.lg\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
.lg\:px-8 { padding-left: 2rem; padding-right: 2rem; }
|
|
.lg\:gap-x-3 { column-gap: 0.75rem; }
|
|
.lg\:gap-x-6 { column-gap: 1.5rem; }
|
|
.lg\:block { display: block; }
|
|
.lg\:flex { display: flex; }
|
|
.lg\:w-64 { width: 16rem; }
|
|
}
|
|
|
|
@media (min-width: 1280px) {
|
|
.xl\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
.xl\:grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
|
.xl\:px-12 { padding-left: 3rem; padding-right: 3rem; }
|
|
.xl\:space-x-6 > * + * { margin-left: 1.5rem; }
|
|
.xl\:gap-x-4 { column-gap: 1rem; }
|
|
.xl\:gap-x-8 { column-gap: 2rem; }
|
|
}
|
|
|
|
@media (min-width: 1536px) {
|
|
.2xl\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-7 { grid-template-columns: repeat(7, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-8 { grid-template-columns: repeat(8, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-9 { grid-template-columns: repeat(9, minmax(0, 1fr)); }
|
|
.2xl\:grid-cols-10 { grid-template-columns: repeat(10, minmax(0, 1fr)); }
|
|
.2xl\:px-16 { padding-left: 4rem; padding-right: 4rem; }
|
|
.2xl\:space-x-8 > * + * { margin-left: 2rem; }
|
|
}
|
|
|
|
/* ===== Mobile navigation drawer =====
|
|
Below md the #site-nav wrapper becomes an off-canvas drawer; at md and up
|
|
none of these rules apply and the nav renders exactly as before. */
|
|
@media (max-width: 767.98px) {
|
|
.site-nav {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
bottom: 0;
|
|
z-index: 60;
|
|
width: min(85vw, 320px);
|
|
background-color: var(--color-surface);
|
|
border-right: 1px solid var(--color-border);
|
|
transform: translateX(-100%);
|
|
transition: transform 0.25s ease;
|
|
overflow-y: auto;
|
|
padding: 1rem;
|
|
-webkit-overflow-scrolling: touch;
|
|
}
|
|
.site-nav.open {
|
|
transform: translateX(0);
|
|
box-shadow: 0 0 24px rgba(0, 0, 0, 0.25);
|
|
}
|
|
/* Tabs become full-width rows with >=44px touch targets */
|
|
.site-nav .nav-tab {
|
|
display: flex;
|
|
width: 100%;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
text-align: left;
|
|
padding: 0.75rem 1rem;
|
|
min-height: 44px;
|
|
}
|
|
.site-nav nav.-mb-px {
|
|
display: block;
|
|
}
|
|
.nav-backdrop {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 55;
|
|
background-color: rgba(0, 0, 0, 0.4);
|
|
}
|
|
/* Header widgets relocated into the drawer (see placeHeaderWidgets in
|
|
app.js). The originals carry `hidden`/breakpoint classes tuned for the
|
|
header, so re-enable them explicitly in the drawer context. */
|
|
#drawer-widgets #settings-search-wrap {
|
|
display: block !important;
|
|
margin-bottom: 1rem;
|
|
}
|
|
#drawer-widgets #settings-search-wrap input {
|
|
width: 100%;
|
|
}
|
|
#drawer-widgets #settings-search-results {
|
|
position: static;
|
|
width: 100%;
|
|
max-height: 50vh;
|
|
margin-top: 0.25rem;
|
|
}
|
|
#drawer-widgets #system-stats {
|
|
display: flex !important;
|
|
justify-content: space-between;
|
|
margin-bottom: 1rem;
|
|
}
|
|
/* Larger touch targets inside horizontally scrolling tables */
|
|
.overflow-x-auto table button {
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
}
|
|
.overflow-x-auto table input:not([type="checkbox"]),
|
|
.overflow-x-auto table select {
|
|
min-height: 40px;
|
|
}
|
|
.overflow-x-auto table input[type="checkbox"] {
|
|
width: 1.25rem;
|
|
height: 1.25rem;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 768px) {
|
|
/* Hard guards: even if mobileNavOpen was left true when the viewport
|
|
crossed the breakpoint, the drawer/backdrop must render as plain
|
|
in-flow nav on desktop. */
|
|
.site-nav {
|
|
position: static;
|
|
transform: none;
|
|
width: auto;
|
|
padding: 0;
|
|
border-right: none;
|
|
box-shadow: none;
|
|
background-color: transparent;
|
|
overflow-y: visible;
|
|
}
|
|
.nav-backdrop {
|
|
display: none !important;
|
|
}
|
|
#drawer-widgets {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
/* Mobile modal sizing: every .modal-content dialog fits the viewport with
|
|
internal scrolling instead of overflowing it. */
|
|
@media (max-width: 640px) {
|
|
.modal-content {
|
|
width: 95vw !important;
|
|
max-width: 95vw !important;
|
|
max-height: 90vh;
|
|
overflow-y: auto;
|
|
}
|
|
}
|
|
|
|
/* Edge-fade hint that a container scrolls horizontally (pure CSS,
|
|
Lea Verou scrolling-shadows technique — backgrounds sit behind content). */
|
|
.overflow-x-auto {
|
|
background:
|
|
linear-gradient(90deg, var(--color-surface) 30%, rgba(255, 255, 255, 0)) left / 24px 100%,
|
|
linear-gradient(270deg, var(--color-surface) 30%, rgba(255, 255, 255, 0)) right / 24px 100%,
|
|
radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0)) left / 12px 100%,
|
|
radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0)) right / 12px 100%;
|
|
background-repeat: no-repeat;
|
|
background-attachment: local, local, scroll, scroll;
|
|
}
|
|
|
|
/* HTMX loading states */
|
|
.htmx-request .loading {
|
|
display: inline-block;
|
|
}
|
|
|
|
.htmx-request .btn-text {
|
|
opacity: 0.5;
|
|
}
|
|
|
|
/* Enhanced Button styles */
|
|
.btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 0.625rem 1.25rem;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
line-height: 1.5;
|
|
text-decoration: none;
|
|
transition: transform 0.15s ease, opacity 0.15s ease;
|
|
cursor: pointer;
|
|
border: none;
|
|
/* Removed ::before pseudo-element animation for better performance */
|
|
}
|
|
|
|
.btn:hover {
|
|
transform: translateY(-1px);
|
|
opacity: 0.9;
|
|
}
|
|
|
|
.btn:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.btn:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
transform: none !important;
|
|
}
|
|
|
|
.btn:disabled:hover {
|
|
transform: none;
|
|
}
|
|
|
|
.btn:focus {
|
|
outline: 2px solid transparent;
|
|
outline-offset: 2px;
|
|
box-shadow: 0 0 0 3px rgb(37 99 235 / 30%);
|
|
}
|
|
|
|
/* Global button text contrast fix: Ensure buttons with white backgrounds have dark text */
|
|
button.bg-white {
|
|
color: #111827 !important; /* text-gray-900 equivalent - ensures good contrast on white background */
|
|
}
|
|
|
|
/* Form styles */
|
|
.form-group {
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
color: var(--color-text-secondary);
|
|
margin-bottom: 0.25rem;
|
|
}
|
|
|
|
.form-control {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 0.5rem 0.75rem;
|
|
border: 1px solid #d1d5db;
|
|
border-radius: 0.375rem;
|
|
background-color: #ffffff;
|
|
color: #111827; /* text-gray-900 - ensure dark text on white background */
|
|
font-size: 0.875rem;
|
|
line-height: 1.25rem;
|
|
transition: border-color 0.15s ease-in-out;
|
|
/* Removed box-shadow transition - using border-color only for better performance */
|
|
}
|
|
|
|
.form-control:focus {
|
|
border-color: var(--color-primary);
|
|
/* Using outline instead of box-shadow for focus state (better performance) */
|
|
outline: 2px solid rgb(37 99 235 / 20%);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.form-control:disabled {
|
|
background-color: #f9fafb;
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* Enhanced Card styles */
|
|
.card {
|
|
background-color: var(--color-surface);
|
|
border-radius: 0.5rem;
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
transition: transform 0.15s ease;
|
|
contain: layout style paint;
|
|
/* Removed box-shadow transition for better performance - using transform only (GPU accelerated) */
|
|
/* Added CSS containment for better performance isolation */
|
|
}
|
|
|
|
.card:hover {
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
/* Plugin Card Styles */
|
|
.plugin-card {
|
|
background-color: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.75rem;
|
|
padding: 1.25rem;
|
|
transition: transform 0.15s ease, border-color 0.15s ease;
|
|
cursor: pointer;
|
|
position: relative;
|
|
contain: layout style paint;
|
|
/* Simplified transitions - using only transform and border-color (cheaper than box-shadow) */
|
|
/* Added CSS containment for better performance isolation */
|
|
}
|
|
|
|
.plugin-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 3px;
|
|
background: linear-gradient(90deg, var(--color-primary), var(--color-secondary));
|
|
opacity: 0;
|
|
transition: opacity 0.15s ease;
|
|
}
|
|
|
|
.plugin-card:hover {
|
|
transform: translateY(-2px);
|
|
border-color: var(--color-primary);
|
|
/* Removed box-shadow transition for better performance */
|
|
}
|
|
|
|
.plugin-card:hover::before {
|
|
opacity: 1;
|
|
}
|
|
|
|
.plugin-card:focus {
|
|
outline: 2px solid var(--color-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.plugin-card:active {
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* Status indicators */
|
|
.status-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.25rem 0.75rem;
|
|
border-radius: 9999px;
|
|
font-size: 0.75rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.status-indicator.success {
|
|
background-color: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.status-indicator.error {
|
|
background-color: #fef2f2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.status-indicator.warning {
|
|
background-color: #fffbeb;
|
|
color: #92400e;
|
|
}
|
|
|
|
.status-indicator.info {
|
|
background-color: var(--color-info-bg);
|
|
color: var(--color-info);
|
|
}
|
|
|
|
/* Badge Styles */
|
|
.badge {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.25rem 0.625rem;
|
|
border-radius: 0.375rem;
|
|
font-size: 0.75rem;
|
|
font-weight: 600;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.badge-success {
|
|
background-color: var(--color-success-bg);
|
|
color: var(--color-success);
|
|
}
|
|
|
|
.badge-error {
|
|
background-color: var(--color-error-bg);
|
|
color: var(--color-error);
|
|
}
|
|
|
|
.badge-warning {
|
|
background-color: var(--color-warning-bg);
|
|
color: var(--color-warning);
|
|
}
|
|
|
|
.badge-info {
|
|
background-color: var(--color-info-bg);
|
|
color: var(--color-info);
|
|
}
|
|
|
|
.badge-accent {
|
|
background-color: var(--color-purple-bg);
|
|
color: var(--color-purple-text);
|
|
}
|
|
|
|
/* Filter Pill Toggle States */
|
|
.filter-pill,
|
|
.category-filter-pill {
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, opacity 0.15s ease;
|
|
}
|
|
|
|
.filter-pill[data-active="true"],
|
|
.category-filter-pill[data-active="true"] {
|
|
background-color: var(--color-info-bg);
|
|
border-color: var(--color-info);
|
|
color: var(--color-info);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.filter-pill[data-active="true"]:hover,
|
|
.category-filter-pill[data-active="true"]:hover {
|
|
opacity: 0.85;
|
|
}
|
|
|
|
.category-filter-pill[data-active="true"] {
|
|
box-shadow: 0 0 0 1px var(--color-info);
|
|
}
|
|
|
|
/* Section Headers with Subtle Gradients */
|
|
.section-header {
|
|
background: linear-gradient(135deg, rgb(255 255 255 / 90%) 0%, rgb(249 250 251 / 90%) 100%);
|
|
border-bottom: 1px solid var(--color-border);
|
|
padding: 1rem 0;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
/* Enhanced Empty States */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 3rem 1rem;
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
.empty-state-icon {
|
|
font-size: 3rem;
|
|
color: var(--color-text-tertiary);
|
|
opacity: 0.5;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
/* Enhanced Loading Skeleton */
|
|
.skeleton {
|
|
background-color: #f0f0f0;
|
|
border-radius: 0.375rem;
|
|
animation: skeletonPulse 1.5s ease-in-out infinite;
|
|
/* Simplified from gradient animation to opacity pulse for better performance */
|
|
}
|
|
|
|
@keyframes skeletonPulse {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0.6; }
|
|
}
|
|
|
|
/* Enhanced Modal Styling */
|
|
.modal-backdrop {
|
|
background-color: rgb(0 0 0 / 50%);
|
|
/* Removed backdrop-filter: blur() for better performance on Raspberry Pi */
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.modal-content {
|
|
background: var(--color-surface);
|
|
border-radius: 0.75rem;
|
|
box-shadow: var(--shadow-lg);
|
|
animation: modalSlideIn 0.2s ease;
|
|
contain: layout style paint;
|
|
/* Added CSS containment for better performance isolation */
|
|
}
|
|
|
|
@keyframes modalSlideIn {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(-10px);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
|
|
/* ============================================================================ */
|
|
/* Settings tooltips (help_tip macro + tooltips.js) */
|
|
/* ============================================================================ */
|
|
|
|
/* The (i) info trigger placed next to a setting label. */
|
|
.help-tip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 1.15rem;
|
|
height: 1.15rem;
|
|
margin-left: 0.375rem;
|
|
padding: 0;
|
|
border: none;
|
|
background: transparent;
|
|
color: var(--color-text-tertiary);
|
|
font-size: 0.8125rem;
|
|
line-height: 1;
|
|
cursor: help;
|
|
vertical-align: middle;
|
|
border-radius: 9999px;
|
|
transition: color 0.12s ease;
|
|
}
|
|
|
|
.help-tip:hover,
|
|
.help-tip:focus-visible {
|
|
color: var(--color-primary);
|
|
}
|
|
|
|
.help-tip:focus-visible {
|
|
outline: 2px solid var(--color-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* Singleton tooltip panel appended to <body> by tooltips.js. */
|
|
#ledm-tooltip {
|
|
position: fixed;
|
|
z-index: 1000;
|
|
max-width: 20rem;
|
|
padding: 0.5rem 0.75rem;
|
|
background: var(--color-surface);
|
|
color: var(--color-text-secondary);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.5rem;
|
|
box-shadow: var(--shadow-lg);
|
|
font-size: 0.8125rem;
|
|
line-height: 1.45;
|
|
white-space: pre-line; /* renders authored "\n" line breaks */
|
|
pointer-events: none; /* never steals hover/click from the page */
|
|
animation: tooltipFade 0.12s ease;
|
|
}
|
|
|
|
#ledm-tooltip[hidden] {
|
|
display: none;
|
|
}
|
|
|
|
@keyframes tooltipFade {
|
|
from { opacity: 0; }
|
|
to { opacity: 1; }
|
|
}
|
|
|
|
/* ============================================================================ */
|
|
/* Settings search — global header dropdown + per-tab filter */
|
|
/* ============================================================================ */
|
|
|
|
#settings-search-results {
|
|
background: var(--color-surface);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: 0.5rem;
|
|
box-shadow: var(--shadow-lg);
|
|
z-index: 50;
|
|
padding: 0.25rem;
|
|
max-height: min(60vh, 24rem);
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.ssr-group {
|
|
padding: 0.375rem 0.625rem 0.25rem;
|
|
font-size: 0.6875rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
color: var(--color-text-tertiary);
|
|
position: sticky;
|
|
top: 0;
|
|
background: var(--color-surface);
|
|
}
|
|
|
|
.ssr-option {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
text-align: left;
|
|
padding: 0.4rem 0.625rem;
|
|
border: none;
|
|
background: transparent;
|
|
border-radius: 0.375rem;
|
|
cursor: pointer;
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
.ssr-option:hover,
|
|
.ssr-option.active {
|
|
background: var(--color-info-bg);
|
|
}
|
|
|
|
.ssr-label {
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.ssr-help {
|
|
font-size: 0.75rem;
|
|
color: var(--color-text-tertiary);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.ssr-empty {
|
|
padding: 0.75rem 0.625rem;
|
|
font-size: 0.8125rem;
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
/* Flash highlight applied to a field after search navigation. */
|
|
@keyframes settingFlash {
|
|
0% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0); }
|
|
25% { box-shadow: 0 0 0 3px var(--color-primary); }
|
|
100% { box-shadow: 0 0 0 0 rgba(37, 99, 235, 0); }
|
|
}
|
|
|
|
.setting-flash {
|
|
animation: settingFlash 1.4s ease;
|
|
border-radius: 0.5rem;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
#ledm-tooltip { animation: none; }
|
|
.setting-flash {
|
|
animation: none;
|
|
outline: 2px solid var(--color-primary);
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
/* Removed .divider and .divider-light - not used anywhere */
|
|
|
|
/* Enhanced Spacing Utilities - Only unique classes not in main utility section */
|
|
.mt-2 { margin-top: 0.5rem; }
|
|
.mt-3 { margin-top: 0.75rem; }
|
|
.mt-6 { margin-top: 1.5rem; }
|
|
.mb-3 { margin-bottom: 0.75rem; }
|
|
.p-3 { padding: 0.75rem; }
|
|
.p-5 { padding: 1.25rem; }
|
|
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
|
|
.px-5 { padding-left: 1.25rem; padding-right: 1.25rem; }
|
|
.py-2\.5 { padding-top: 0.625rem; padding-bottom: 0.625rem; }
|
|
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
|
/* Removed duplicates: mt-4, mb-2, mb-4, mb-6, mb-8, p-4, p-6, px-4, py-2, py-3 (already defined above) */
|
|
|
|
/* Additional Utility Classes */
|
|
.min-w-0 { min-width: 0; }
|
|
.leading-relaxed { line-height: 1.625; }
|
|
|
|
/* Enhanced Navigation Tab Styles */
|
|
.nav-tab {
|
|
position: relative;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
padding: 0.5rem 0.25rem;
|
|
border-bottom-width: 2px;
|
|
border-bottom-style: solid;
|
|
border-bottom-color: transparent;
|
|
font-size: 0.875rem;
|
|
font-weight: 500;
|
|
line-height: 1.5;
|
|
color: #374151; /* text-gray-700 for better readability */
|
|
white-space: nowrap;
|
|
transition: color 0.15s ease, background-color 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
|
|
cursor: pointer;
|
|
background-color: transparent;
|
|
border-top: none;
|
|
border-left: none;
|
|
border-right: none;
|
|
}
|
|
|
|
.nav-tab i {
|
|
transition: color 0.15s ease;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
/* Inactive state - improved contrast */
|
|
.nav-tab:not(.nav-tab-active) {
|
|
color: #374151; /* text-gray-700 */
|
|
}
|
|
|
|
.nav-tab:not(.nav-tab-active) i {
|
|
color: #374151; /* text-gray-700 */
|
|
}
|
|
|
|
/* Hover state - enhanced visibility */
|
|
.nav-tab:not(.nav-tab-active):hover {
|
|
color: #111827; /* text-gray-900 */
|
|
background-color: #f3f4f6; /* bg-gray-100 */
|
|
border-bottom-color: #d1d5db; /* border-gray-300 */
|
|
}
|
|
|
|
.nav-tab:not(.nav-tab-active):hover i {
|
|
color: #111827; /* text-gray-900 */
|
|
}
|
|
|
|
/* Active state - prominent with gradient background */
|
|
.nav-tab-active {
|
|
color: #1d4ed8; /* text-blue-700 */
|
|
font-weight: 600;
|
|
border-bottom-width: 3px;
|
|
border-bottom-color: #2563eb; /* border-blue-600 */
|
|
background: linear-gradient(135deg, rgb(37 99 235 / 10%) 0%, rgb(59 130 246 / 5%) 100%);
|
|
box-shadow: 0 2px 4px rgb(37 99 235 / 10%);
|
|
}
|
|
|
|
.nav-tab-active i {
|
|
color: #1d4ed8; /* text-blue-700 */
|
|
}
|
|
|
|
/* Responsive padding adjustments */
|
|
@media (min-width: 1024px) {
|
|
.nav-tab {
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
}
|
|
}
|
|
|
|
@media (min-width: 1280px) {
|
|
.nav-tab {
|
|
padding-left: 0.75rem;
|
|
padding-right: 0.75rem;
|
|
}
|
|
}
|
|
|
|
/* ==========================================================
|
|
Dark Mode - Component Overrides
|
|
========================================================== */
|
|
|
|
/* Header */
|
|
[data-theme="dark"] header {
|
|
background-color: var(--color-surface);
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
/* Form controls */
|
|
[data-theme="dark"] .form-control {
|
|
background-color: var(--color-border);
|
|
color: var(--color-text-primary);
|
|
border-color: #4b5563;
|
|
}
|
|
|
|
[data-theme="dark"] .form-control:focus {
|
|
border-color: var(--color-primary);
|
|
outline-color: rgb(59 130 246 / 30%);
|
|
}
|
|
|
|
[data-theme="dark"] .form-control:disabled {
|
|
background-color: var(--color-surface);
|
|
}
|
|
|
|
/* Buttons with bg-white */
|
|
[data-theme="dark"] button.bg-white {
|
|
background-color: var(--color-border) !important;
|
|
color: var(--color-text-primary) !important;
|
|
}
|
|
|
|
/* Nav tabs */
|
|
[data-theme="dark"] .nav-tab:not(.nav-tab-active) {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-tab:not(.nav-tab-active) i {
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-tab:not(.nav-tab-active):hover {
|
|
color: var(--color-text-primary);
|
|
background-color: var(--color-border);
|
|
border-bottom-color: #6b7280;
|
|
}
|
|
|
|
[data-theme="dark"] .nav-tab:not(.nav-tab-active):hover i {
|
|
color: var(--color-text-primary);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-tab-active {
|
|
color: #93c5fd;
|
|
border-bottom-color: #3b82f6;
|
|
background: linear-gradient(135deg, rgb(59 130 246 / 15%) 0%, rgb(59 130 246 / 5%) 100%);
|
|
}
|
|
|
|
[data-theme="dark"] .nav-tab-active i {
|
|
color: #93c5fd;
|
|
}
|
|
|
|
/* Section header */
|
|
[data-theme="dark"] .section-header {
|
|
background: linear-gradient(135deg, rgb(31 41 55 / 90%) 0%, rgb(17 24 39 / 90%) 100%);
|
|
}
|
|
|
|
/* Status indicators */
|
|
[data-theme="dark"] .status-indicator.success {
|
|
background-color: #064e3b;
|
|
color: #6ee7b7;
|
|
}
|
|
|
|
[data-theme="dark"] .status-indicator.error {
|
|
background-color: #7f1d1d;
|
|
color: #fca5a5;
|
|
}
|
|
|
|
[data-theme="dark"] .status-indicator.warning {
|
|
background-color: #78350f;
|
|
color: #fcd34d;
|
|
}
|
|
|
|
/* Skeleton loading */
|
|
[data-theme="dark"] .skeleton {
|
|
background-color: var(--color-border);
|
|
}
|
|
|
|
/* Scrollbar */
|
|
[data-theme="dark"] ::-webkit-scrollbar-track {
|
|
background: var(--color-surface);
|
|
}
|
|
|
|
[data-theme="dark"] ::-webkit-scrollbar-thumb {
|
|
background: #4b5563;
|
|
}
|
|
|
|
[data-theme="dark"] ::-webkit-scrollbar-thumb:hover {
|
|
background: #6b7280;
|
|
}
|
|
|
|
/* Log panel - slightly deeper than surface for contrast */
|
|
[data-theme="dark"] #logs-container {
|
|
background-color: #0f1623;
|
|
border-color: var(--color-border);
|
|
}
|
|
|
|
[data-theme="dark"] #logs-display,
|
|
[data-theme="dark"] #logs-loading,
|
|
[data-theme="dark"] #logs-empty {
|
|
background-color: #0f1623;
|
|
}
|
|
|
|
/* Warning/info boxes used in various partials */
|
|
[data-theme="dark"] .bg-yellow-50 { background-color: #422006; }
|
|
[data-theme="dark"] .border-yellow-200 { border-color: #78350f; }
|
|
[data-theme="dark"] .text-yellow-800 { color: #fcd34d; }
|
|
[data-theme="dark"] .text-yellow-700 { color: #fbbf24; }
|
|
[data-theme="dark"] .text-yellow-600 { color: #fbbf24; }
|
|
[data-theme="dark"] .bg-blue-50 { background-color: #1e3a5f; }
|
|
[data-theme="dark"] .border-blue-200 { border-color: #1e40af; }
|
|
[data-theme="dark"] .text-blue-800 { color: #93c5fd; }
|
|
[data-theme="dark"] .text-blue-700 { color: #93c5fd; }
|
|
[data-theme="dark"] .bg-red-50 { background-color: #450a0a; }
|
|
[data-theme="dark"] .border-red-200 { border-color: #7f1d1d; }
|
|
[data-theme="dark"] .text-red-800 { color: #fca5a5; }
|
|
[data-theme="dark"] .text-red-700 { color: #fca5a5; }
|
|
[data-theme="dark"] .bg-green-50 { background-color: #022c22; }
|
|
[data-theme="dark"] .border-green-200 { border-color: #064e3b; }
|
|
[data-theme="dark"] .text-green-800 { color: #6ee7b7; }
|
|
[data-theme="dark"] .text-green-700 { color: #6ee7b7; }
|
|
|
|
/* Theme toggle button */
|
|
.theme-toggle-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
font-size: 1.1rem;
|
|
line-height: 1;
|
|
cursor: pointer;
|
|
border: none;
|
|
background: transparent;
|
|
color: #4b5563;
|
|
}
|
|
|
|
.theme-toggle-btn:hover {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
[data-theme="dark"] .theme-toggle-btn {
|
|
color: #fbbf24;
|
|
}
|
|
|
|
/* Update available banner */
|
|
.update-banner {
|
|
background-color: #eff6ff;
|
|
border-color: #bfdbfe;
|
|
color: #1e40af;
|
|
}
|
|
.update-banner-action {
|
|
background-color: #3b82f6;
|
|
color: #fff;
|
|
}
|
|
.update-banner-action:hover {
|
|
background-color: #2563eb;
|
|
}
|
|
.update-banner-dismiss {
|
|
color: #1e40af;
|
|
opacity: 0.6;
|
|
}
|
|
.update-banner-dismiss:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-theme="dark"] .update-banner {
|
|
background-color: #1e293b;
|
|
border-color: #334155;
|
|
color: #93c5fd;
|
|
}
|
|
[data-theme="dark"] .update-banner-action {
|
|
background-color: #2563eb;
|
|
}
|
|
[data-theme="dark"] .update-banner-action:hover {
|
|
background-color: #3b82f6;
|
|
}
|
|
[data-theme="dark"] .update-banner-dismiss {
|
|
color: #93c5fd;
|
|
}
|
|
|
|
/* Installed-plugin "Update" button when a newer version is published.
|
|
A gentle pulsing ring draws the eye without being noisy. */
|
|
.plugin-update-available {
|
|
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.6);
|
|
animation: plugin-update-pulse 2s ease-out infinite;
|
|
}
|
|
@keyframes plugin-update-pulse {
|
|
0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.55); }
|
|
70% { box-shadow: 0 0 0 6px rgba(59, 130, 246, 0); }
|
|
100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); }
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.plugin-update-available { animation: none; }
|
|
}
|
|
|
|
/* Under-voltage / throttling warning banner */
|
|
.power-warning-banner {
|
|
background-color: #fef2f2;
|
|
border-color: #fecaca;
|
|
color: #991b1b;
|
|
}
|
|
.power-warning-banner-dismiss {
|
|
color: #991b1b;
|
|
opacity: 0.6;
|
|
}
|
|
.power-warning-banner-dismiss:hover {
|
|
opacity: 1;
|
|
}
|
|
|
|
[data-theme="dark"] .power-warning-banner {
|
|
background-color: #450a0a;
|
|
border-color: #7f1d1d;
|
|
color: #fca5a5;
|
|
}
|
|
[data-theme="dark"] .power-warning-banner-dismiss {
|
|
color: #fca5a5;
|
|
}
|
|
|
|
/* ===== Floating live preview (all tabs except Overview) ===== */
|
|
.floating-preview {
|
|
position: fixed;
|
|
right: 1rem;
|
|
bottom: 1rem;
|
|
z-index: 70;
|
|
background-color: #111827;
|
|
border: 1px solid #374151;
|
|
border-radius: 0.5rem;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
overflow: hidden;
|
|
/* Desktop: draggable resize handle (bottom-left visually, since the
|
|
panel is anchored to the right). Touch devices use the size button. */
|
|
resize: both;
|
|
min-width: 160px;
|
|
max-width: 90vw;
|
|
}
|
|
.floating-preview img {
|
|
background-color: #000;
|
|
}
|
|
.floating-preview-toggle {
|
|
position: fixed;
|
|
right: 1rem;
|
|
bottom: 1rem;
|
|
z-index: 70;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 9999px;
|
|
background-color: var(--color-primary);
|
|
color: #ffffff;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
@media (max-width: 640px) {
|
|
/* Whatever size is chosen, never wider than the phone viewport */
|
|
.floating-preview {
|
|
max-width: calc(100vw - 2rem);
|
|
}
|
|
}
|