Files
ComfyUI-Lora-Manager/static/css/components/modal/_base.css
T
Will Miao 3d165f92e0 fix(theme): use opaque surface tokens for modals and sticky layers
The elevated surface tokens (--bg-elevated / --surface-elevated) are
translucent, so sticky modal footers and table headers double-composited
over the modal surface and showed a visible lighter seam in light themes.

Add derived opaque tokens (--bg-elevated-solid / --surface-elevated-solid,
aliased as --lora-surface-solid) and use them for .modal-content, the
download/import modal sticky action footers, and the refresh result
table's sticky header, so the modal surface and its sticky layers always
share one opaque color across every theme and preset.
2026-10-10 19:58:54 +08:00

740 lines
17 KiB
CSS

/* Modal base styles */
.modal {
display: none;
position: fixed;
top: var(--header-height, 48px); /* Start below the header */
left: 0;
width: 100%;
height: calc(100% - var(--header-height, 48px)); /* Adjust height to exclude header */
background: var(--modal-backdrop-bg, rgba(0, 0, 0, 0.5));
backdrop-filter: blur(var(--modal-backdrop-blur, 6px));
-webkit-backdrop-filter: blur(var(--modal-backdrop-blur, 6px));
z-index: var(--z-modal);
overflow: auto; /* Change from hidden to auto to allow scrolling */
}
/* Software-rendering fallback (set by applyModalBackdropBlurPolicy): a
full-viewport backdrop-filter forces per-frame CPU rasterization of
everything behind the modal and freezes the browser (issue #1092) */
html.no-modal-backdrop-blur .modal,
html.no-modal-backdrop-blur .delete-modal,
html.no-modal-backdrop-blur .batch-preview-select-all {
backdrop-filter: none;
-webkit-backdrop-filter: none;
}
/* Prevent body scroll when modal is open */
body.modal-open {
position: fixed;
width: 100%;
padding-right: var(--scrollbar-width, 0px);
}
/* Modal content styles */
.modal-content {
position: relative;
max-width: 800px;
height: auto;
max-height: calc(100vh - var(--header-height, 48px) - 5.5rem); /* Subtract header height and modal margins */
margin: 1rem auto; /* Keep reduced top margin */
/* Opaque surface: the modal stacks above the backdrop, and sticky layers
inside (action footers, table headers) share this same solid token so
they cannot double-composite into a visible seam (light themes). */
background: var(--lora-surface-solid);
border-radius: var(--border-radius-base);
padding: var(--space-3);
border: 1px solid var(--lora-border);
box-shadow: var(--shadow-md);
overflow-y: auto;
overflow-x: hidden;
scrollbar-gutter: stable both-edges; /* Reserve space to prevent layout shift when scrollbar toggles */
}
.modal-content-large {
min-height: 480px;
}
/* Lock body when modal is open */
body.modal-open {
overflow: hidden !important;
padding-right: var(--scrollbar-width, 8px);
}
@keyframes modalFadeIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-actions {
display: flex;
gap: var(--space-2);
justify-content: center;
margin-top: var(--space-3);
}
.cancel-btn, .delete-btn, .exclude-btn, .confirm-btn {
display: flex;
align-items: center;
justify-content: center;
gap: var(--space-1);
padding: var(--space-1) var(--space-2);
border-radius: var(--border-radius-sm);
border: none;
cursor: pointer;
font-weight: 500;
font-size: 0.95em;
min-width: 100px;
transition: background-color var(--transition-base), opacity var(--transition-base), transform var(--transition-fast);
}
.cancel-btn:active,
.delete-btn:active,
.exclude-btn:active,
.confirm-btn:active {
transform: scale(0.98);
}
.cancel-btn {
background: var(--lora-surface);
border: 1px solid var(--lora-border);
color: var(--text-color);
}
.delete-btn {
background: var(--lora-error);
color: white;
}
/* Style for exclude button - different from delete button */
.exclude-btn, .confirm-btn {
background: var(--lora-accent, #4f46e5);
color: white;
}
.cancel-btn:hover,
.cancel-btn:focus-visible {
background: var(--lora-border);
}
.delete-btn:hover,
.delete-btn:focus-visible {
background: oklch(from var(--lora-error) l c h / 85%);
}
.exclude-btn:hover,
.exclude-btn:focus-visible,
.confirm-btn:hover,
.confirm-btn:focus-visible {
background: oklch(from var(--lora-accent, #4f46e5) l c h / 85%);
}
.modal-content h2 {
color: var(--text-color);
margin-bottom: var(--space-1);
font-size: 1.5em;
}
.close {
position: absolute;
top: var(--space-2);
right: var(--space-2);
background: transparent;
border: none;
color: var(--text-color);
font-size: 1.5em;
cursor: pointer;
opacity: 0.7;
transition: opacity var(--transition-base);
z-index: 10;
}
.close:hover,
.close:focus-visible {
opacity: 1;
outline: 2px solid var(--lora-accent);
outline-offset: 2px;
border-radius: var(--border-radius-xs);
}
/* Unified section styles */
.support-section,
.changelog-section,
.update-info,
.update-channels,
.info-item,
.path-preview {
background: var(--surface-subtle);
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
padding: var(--space-2);
}
/* Dark theme unified styles */
[data-theme="dark"] .modal-content {
background: var(--lora-surface-solid);
border: 1px solid var(--lora-border);
}
.primary-btn {
display: flex;
align-items: center;
gap: 8px;
padding: var(--space-1) var(--space-2);
background-color: var(--lora-accent);
color: var(--lora-text);
border: none;
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: background-color 0.2s;
font-size: 0.95em;
}
.primary-btn:hover,
.primary-btn:focus-visible {
background-color: oklch(from var(--lora-accent) l c h / 85%);
color: var(--lora-text);
outline: none;
}
/* Secondary button styles */
.secondary-btn {
display: flex;
align-items: center;
gap: 8px;
padding: var(--space-1) var(--space-2);
background-color: var(--card-bg);
color: var(--text-color);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: var(--transition-base);
font-size: 0.95em;
}
.secondary-btn:hover,
.secondary-btn:focus-visible {
background-color: var(--border-color);
color: var(--text-color);
outline: none;
}
/* Disabled button styles */
.primary-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
background-color: var(--lora-accent);
color: var(--lora-text);
pointer-events: none;
}
.secondary-btn.disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
button:disabled,
.primary-btn:disabled,
.danger-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.restart-required-icon {
color: var(--lora-warning);
margin-left: 5px;
font-size: 0.85em;
vertical-align: text-bottom;
}
/* Dark theme specific button adjustments */
[data-theme="dark"] .primary-btn:hover {
background-color: oklch(from var(--lora-accent) l c h / 75%);
}
[data-theme="dark"] .secondary-btn {
background-color: var(--lora-surface);
}
[data-theme="dark"] .secondary-btn:hover {
background-color: oklch(35% 0.02 256 / 0.98);
}
/* Danger button styles */
.danger-btn {
display: flex;
align-items: center;
gap: 8px;
padding: var(--space-1) var(--space-2);
background-color: var(--lora-error);
color: white;
border: none;
border-radius: var(--border-radius-sm);
cursor: pointer;
transition: background-color 0.2s;
font-size: 0.95em;
}
.danger-btn:hover,
.danger-btn:focus-visible {
background-color: oklch(from var(--lora-error) l c h / 85%);
color: white;
outline: none;
}
/* Metadata archive status styles */
.metadata-archive-status {
background: var(--surface-subtle);
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
padding: var(--space-2);
margin-bottom: var(--space-2);
}
.archive-status-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 0.95em;
}
.archive-status-item:last-child {
margin-bottom: 0;
}
.archive-status-label {
font-weight: 500;
color: var(--text-color);
opacity: 0.8;
}
.archive-status-value {
color: var(--text-color);
}
.archive-status-value.status-available {
color: var(--lora-success, #10b981);
}
.archive-status-value.status-unavailable {
color: var(--lora-warning, #f59e0b);
}
.archive-status-value.status-enabled {
color: var(--lora-success, #10b981);
}
.archive-status-value.status-disabled {
color: var(--lora-error, #ef4444);
}
.backup-status {
background: var(--surface-subtle);
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
padding: var(--space-3);
}
.backup-summary-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: var(--space-2);
margin-bottom: var(--space-3);
}
.backup-summary-card {
background: var(--lora-surface);
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
padding: var(--space-2);
}
.backup-summary-label {
color: var(--text-color);
font-size: 0.85rem;
opacity: 0.7;
margin-bottom: 6px;
}
.backup-summary-value {
color: var(--text-color);
font-size: 1.1rem;
font-weight: 600;
line-height: 1.3;
word-break: break-word;
}
.backup-summary-value.status-enabled {
color: var(--lora-success, #10b981);
}
.backup-summary-value.status-disabled {
color: var(--lora-error, #ef4444);
}
.backup-status-list {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.backup-status-row {
display: grid;
grid-template-columns: minmax(140px, 180px) 1fr;
gap: var(--space-2);
align-items: start;
}
.backup-status-label {
color: var(--text-color);
font-weight: 500;
opacity: 0.8;
}
.backup-status-content {
min-width: 0;
}
.backup-status-primary {
color: var(--text-color);
font-weight: 600;
line-height: 1.4;
}
.backup-status-secondary {
color: var(--text-color);
opacity: 0.72;
font-size: 0.88rem;
line-height: 1.4;
word-break: break-word;
margin-top: 2px;
}
.backup-location-details {
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
background: var(--surface-subtle);
}
.backup-location-details summary {
cursor: pointer;
padding: var(--space-2) var(--space-3);
color: var(--text-color);
font-weight: 500;
}
.backup-location-panel {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: var(--space-2);
align-items: center;
width: 100%;
max-width: 100%;
box-sizing: border-box;
padding: 0 var(--space-3) var(--space-3);
}
.backup-location-panel .text-btn {
justify-self: end;
}
.backup-location-path {
display: block;
min-width: 0;
max-width: 100%;
padding: 6px 8px;
border-radius: var(--border-radius-sm);
background: var(--surface-subtle);
color: var(--text-color);
overflow-wrap: anywhere;
word-break: break-word;
}
@media (max-width: 768px) {
.backup-status-row {
grid-template-columns: 1fr;
}
.backup-location-panel {
grid-template-columns: 1fr;
}
.backup-location-panel .text-btn {
justify-self: start;
}
}
/* Add styles for delete preview image */
.delete-preview {
max-width: 150px;
margin: 0 auto var(--space-2);
overflow: hidden;
}
.delete-preview img,
.delete-preview video {
width: 100%;
height: auto;
max-height: 150px;
object-fit: contain;
border-radius: var(--border-radius-sm);
}
.delete-info {
text-align: center;
}
.delete-info h3 {
margin-bottom: var(--space-1);
word-break: break-word;
}
.delete-info p {
margin: var(--space-1) 0;
font-size: 0.9em;
opacity: 0.8;
}
.delete-note {
font-size: 0.85em;
color: var(--text-color);
opacity: 0.7;
font-style: italic;
margin-top: var(--space-1);
text-align: center;
}
/* Bulk Download Missing LoRAs Modal */
#bulkDownloadMissingLorasModal .modal-body {
padding: var(--space-3);
}
#bulkDownloadMissingLorasModal .confirmation-message {
color: var(--text-color);
margin-bottom: var(--space-3);
font-size: 1em;
line-height: 1.5;
}
#bulkDownloadMissingLorasModal .bulk-download-loras-preview {
background: var(--surface-subtle);
border: 1px solid var(--lora-border);
border-radius: var(--border-radius-sm);
padding: var(--space-3);
margin-bottom: var(--space-3);
}
#bulkDownloadMissingLorasModal .preview-title {
font-weight: 600;
margin-bottom: var(--space-2);
color: var(--text-color);
font-size: 0.95em;
}
#bulkDownloadMissingLorasModal .bulk-download-loras-list {
list-style: none;
padding: 0;
margin: 0;
}
#bulkDownloadMissingLorasModal .bulk-download-loras-list li {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-1) 0;
border-bottom: 1px solid var(--border-color);
font-size: 0.9em;
}
#bulkDownloadMissingLorasModal .bulk-download-loras-list li:last-child {
border-bottom: none;
}
#bulkDownloadMissingLorasModal .bulk-download-loras-list li.more-items {
font-style: italic;
opacity: 0.7;
text-align: center;
justify-content: center;
padding: var(--space-2) 0;
}
#bulkDownloadMissingLorasModal .lora-name {
font-weight: 500;
color: var(--text-color);
flex: 1;
}
#bulkDownloadMissingLorasModal .lora-version {
font-size: 0.85em;
opacity: 0.7;
margin-left: var(--space-1);
color: var(--text-muted);
}
#bulkDownloadMissingLorasModal .confirmation-note {
display: flex;
align-items: flex-start;
gap: var(--space-2);
padding: var(--space-2);
background: oklch(from var(--lora-accent) l c h / 0.1);
border-radius: var(--border-radius-sm);
font-size: 0.9em;
color: var(--text-color);
}
#bulkDownloadMissingLorasModal .confirmation-note i {
color: var(--lora-accent);
margin-top: 2px;
flex-shrink: 0;
}
/* Recipe Rematch Options Modal */
#rematchOptionsModal .modal-body {
padding: var(--space-3);
}
#rematchOptionsModal .confirmation-message {
color: var(--text-color);
margin-bottom: var(--space-3);
font-size: 1em;
line-height: 1.5;
}
/* Selectable option card — click anywhere toggles the checkbox (label wrap).
Checkmark follows the batch-import modal's custom checkbox pattern. */
#rematchOptionsModal .rematch-option-card {
position: relative;
display: flex;
align-items: flex-start;
gap: var(--space-2);
padding: var(--space-3);
background: var(--surface-subtle);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-sm);
cursor: pointer;
user-select: none;
transition: var(--transition-base);
}
#rematchOptionsModal .rematch-option-card:hover {
border-color: var(--lora-accent);
}
#rematchOptionsModal .rematch-option-card:has(input[type="checkbox"]:checked) {
border-color: var(--lora-accent);
background: oklch(from var(--lora-accent) l c h / 0.08);
}
/* Visually hidden but keyboard-focusable (focus ring lands on the card). */
#rematchOptionsModal .rematch-option-card input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
#rematchOptionsModal .rematch-option-card:has(input[type="checkbox"]:focus-visible) {
box-shadow: 0 0 0 2px oklch(from var(--lora-accent) l c h / 0.2);
}
#rematchOptionsModal .rematch-option-checkmark {
width: 18px;
height: 18px;
margin-top: 1px;
flex-shrink: 0;
border: 2px solid var(--border-color);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
transition: var(--transition-base);
background: var(--bg-color);
}
#rematchOptionsModal .rematch-option-card input[type="checkbox"]:checked + .rematch-option-checkmark {
background: var(--lora-accent);
border-color: var(--lora-accent);
}
#rematchOptionsModal .rematch-option-card input[type="checkbox"]:checked + .rematch-option-checkmark::after {
content: '\f00c';
font-family: 'Font Awesome 6 Free', sans-serif;
font-weight: 900;
color: var(--lora-text);
font-size: 12px;
}
#rematchOptionsModal .rematch-option-text {
display: flex;
flex-direction: column;
gap: var(--space-1);
color: var(--text-color);
min-width: 0;
}
#rematchOptionsModal .rematch-option-title {
font-weight: 600;
font-size: 0.95em;
}
#rematchOptionsModal .rematch-option-caveat {
display: flex;
align-items: flex-start;
gap: var(--space-2);
font-size: 0.85em;
line-height: 1.4;
color: var(--text-muted);
}
#rematchOptionsModal .rematch-option-caveat i {
color: var(--lora-accent);
margin-top: 2px;
flex-shrink: 0;
}
/* Recipe Rematch Summary Modal (dynamically built by RematchSummaryModal.js;
stat cards / failure table / summary header come from
metadata-refresh-result.css and download-batch-summary.css). */
.rematch-summary-modal {
max-width: 700px;
}
.rematch-cancelled-note {
display: flex;
align-items: flex-start;
gap: var(--space-2);
margin: 0 0 var(--space-2) 0;
font-size: var(--text-sm);
color: var(--color-warning);
}
.rematch-cancelled-note i {
margin-top: 2px;
flex-shrink: 0;
}
/* Review section heading uses the accent (review, not failure) instead of
the failure-section error color. */
.rematch-review-section h4 {
color: var(--lora-accent);
}
#rematchSummaryModal .rematch-undo-btn {
padding: var(--space-1) var(--space-2);
font-size: var(--text-xs);
white-space: nowrap;
}
#rematchSummaryModal tr.undone td:not(.rematch-undo-cell) {
text-decoration: line-through;
opacity: 0.6;
}