Files
wled-screen-controller-mixed/server/src/wled_controller/static/css/components.css
alexei.dolgolyov ddfa7637d6 Speed up camera source modal with cached enumeration and instant open
Cache camera enumeration results for 30s and limit probe range using
WMI camera count on Windows. Open source modal instantly with a loading
spinner while dropdowns are populated asynchronously.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:35:26 +03:00

468 lines
9.4 KiB
CSS

.badge {
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: 600;
}
.badge.processing {
background: var(--primary-color);
color: var(--primary-contrast);
}
.badge.idle {
background: var(--warning-color);
color: white;
}
.badge.error {
background: var(--danger-color);
color: white;
}
.card-content {
margin-bottom: 15px;
}
.info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
color: var(--text-secondary);
}
.info-value {
font-weight: 600;
}
.card-actions {
display: flex;
gap: 8px;
margin-top: auto;
padding-top: 12px;
border-top: 1px solid var(--border-color);
align-items: center;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
transition: opacity 0.2s ease, transform 0.15s ease, box-shadow 0.2s ease;
flex: 1 1 auto;
min-width: 100px;
}
.btn:hover {
opacity: 0.9;
}
.btn:active:not(:disabled) {
transform: scale(0.97);
transition-duration: 0.06s;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary {
background: var(--primary-color);
color: var(--primary-contrast);
}
.btn-danger {
background: var(--danger-color);
color: white;
}
.btn-secondary {
background: var(--border-color);
color: var(--text-color);
}
.btn-icon {
min-width: auto;
padding: 8px 12px;
font-size: 1.2rem;
flex: 0 0 auto;
}
.btn-icon:hover {
transform: scale(1.1);
opacity: 1;
}
.btn-icon:active:not(:disabled) {
transform: scale(0.92);
transition-duration: 0.06s;
}
.form-group {
margin-bottom: 15px;
}
.settings-toggle-group {
display: flex;
flex-direction: column;
}
.settings-toggle {
position: relative;
display: inline-block;
width: 34px;
height: 18px;
cursor: pointer;
margin-top: 4px;
}
.settings-toggle input {
opacity: 0;
width: 0;
height: 0;
}
.settings-toggle-slider {
position: absolute;
inset: 0;
background: var(--border-color);
border-radius: 9px;
transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.settings-toggle-slider::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 14px;
height: 14px;
background: white;
border-radius: 50%;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.settings-toggle input:checked + .settings-toggle-slider {
background: var(--primary-color);
}
.settings-toggle input:checked + .settings-toggle-slider::after {
transform: translateX(16px);
}
label {
display: block;
margin-bottom: 5px;
color: var(--text-secondary);
font-weight: 500;
}
input[type="text"],
input[type="url"],
input[type="number"],
input[type="password"],
select {
width: 100%;
padding: 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: var(--bg-color);
color: var(--text-color);
font-size: 1rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
transition: border-color 0.25s ease, box-shadow 0.25s ease, opacity 0.2s ease;
}
input[type="number"]:disabled,
input[type="password"]:disabled,
select:disabled {
opacity: 0.4;
cursor: not-allowed;
}
input[type="range"] {
width: 100%;
margin: 8px 0;
accent-color: var(--primary-color);
}
/* Better password field appearance */
input[type="password"] {
letter-spacing: 0.15em;
}
input:focus,
select:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 3px rgba(76, 175, 80, 0.15);
}
/* Remove browser autofill styling */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus {
-webkit-box-shadow: 0 0 0 1000px var(--bg-color) inset;
-webkit-text-fill-color: var(--text-color);
transition: background-color 5000s ease-in-out 0s;
}
.loading {
text-align: center;
padding: 40px;
color: var(--text-secondary);
}
.loading-spinner {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
}
.loading-spinner::after {
content: '';
width: 28px;
height: 28px;
border: 3px solid var(--border-color);
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Modal body loading overlay — hides form while data loads */
.modal-body-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 20px;
gap: 12px;
}
.modal-body-loading .loading-spinner {
padding: 0;
}
.modal-body-loading-text {
color: var(--text-secondary);
font-size: 0.9rem;
}
/* Full-page overlay spinner */
.overlay-spinner {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
backdrop-filter: blur(4px);
}
.overlay-spinner .progress-container {
position: relative;
width: 120px;
height: 120px;
}
.overlay-spinner .progress-ring {
transform: rotate(-90deg);
}
.overlay-spinner .progress-ring-circle {
transition: stroke-dashoffset 0.1s linear;
stroke: var(--primary-color);
stroke-width: 4;
fill: transparent;
}
.overlay-spinner .progress-ring-bg {
stroke: rgba(255, 255, 255, 0.1);
stroke-width: 4;
fill: transparent;
}
.overlay-spinner .progress-content {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.overlay-spinner .progress-percentage {
color: white;
font-size: 28px;
font-weight: 600;
}
.overlay-spinner .spinner-text {
margin-top: 24px;
color: white;
font-size: 16px;
font-weight: 500;
}
.overlay-spinner-close {
position: absolute;
top: 16px;
right: 16px;
background: none;
border: none;
color: rgba(255, 255, 255, 0.6);
font-size: 32px;
cursor: pointer;
line-height: 1;
padding: 4px 8px;
transition: color 0.15s;
}
.overlay-spinner-close:hover {
color: white;
}
.overlay-preview-img {
max-width: 80vw;
max-height: 50vh;
border-radius: 8px;
margin-top: 16px;
object-fit: contain;
}
.overlay-preview-stats {
color: rgba(255, 255, 255, 0.7);
font-size: 0.85rem;
margin-top: 8px;
font-variant-numeric: tabular-nums;
}
.toast {
position: fixed;
bottom: 40px;
left: 50%;
transform: translateX(-50%) translateY(100px);
padding: 16px 24px;
border-radius: 8px;
color: white;
font-weight: 600;
font-size: 15px;
opacity: 0;
transition: opacity 0.4s cubic-bezier(0.16, 1, 0.3, 1), transform 0.4s cubic-bezier(0.16, 1, 0.3, 1);
z-index: 2500;
box-shadow: 0 4px 20px var(--shadow-color);
min-width: 300px;
text-align: center;
}
.toast.show {
opacity: 1;
transform: translateX(-50%) translateY(0);
animation: toastBounceIn 0.5s cubic-bezier(0.16, 1, 0.3, 1);
}
@keyframes toastBounceIn {
0% { transform: translateX(-50%) translateY(60px); opacity: 0; }
50% { transform: translateX(-50%) translateY(-4px); opacity: 1; }
70% { transform: translateX(-50%) translateY(2px); }
100% { transform: translateX(-50%) translateY(0); }
}
.toast.success {
background: var(--primary-color);
}
.toast.error {
background: var(--danger-color);
}
.toast.info {
background: var(--info-color);
}
/* ── Focus-visible indicators for keyboard navigation ── */
.btn:focus-visible,
.btn-icon:focus-visible,
.card-remove-btn:focus-visible,
.card-autostart-btn:focus-visible,
.card-power-btn:focus-visible,
.card-tutorial-btn:focus-visible,
.hint-toggle:focus-visible,
.modal-close-btn:focus-visible,
.modal-header-btn:focus-visible,
.tab-btn:focus-visible,
.stream-tab-btn:focus-visible,
.header-btn:focus-visible,
.tutorial-trigger-btn:focus-visible,
.tutorial-close-btn:focus-visible,
.dashboard-action-btn:focus-visible,
.btn-expand-collapse:focus-visible,
.btn-filter-action:focus-visible,
.settings-toggle:focus-visible {
outline: 2px solid var(--primary-color);
outline-offset: 2px;
}
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}
/* WS device connection URL */
.ws-url-row {
display: flex;
gap: 6px;
}
.ws-url-row input { flex: 1; }
.ws-url-row .btn { padding: 4px 10px; min-width: 0; flex: 0 0 auto; }
.endpoint-label { display: block; font-weight: 600; margin-bottom: 2px; opacity: 0.7; font-size: 0.8em; }
/* Scene target selector */
.scene-target-add-row {
display: flex;
gap: 6px;
margin-bottom: 8px;
}
.scene-target-add-row select { flex: 1; }
.scene-target-add-row .btn { padding: 4px 10px; min-width: 0; flex: 0 0 auto; font-size: 0.85rem; }
.scene-target-list {
display: flex;
flex-direction: column;
gap: 4px;
}
.scene-target-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 10px;
border: 1px solid var(--border-color);
border-radius: var(--radius);
background: var(--bg-primary);
font-size: 0.9rem;
}