librelad 7ba281a390 ux(backup): redesign the snapshot details panel
The expanded snapshot detail reused the shared .task-meta/.meta-item
layout, which forces each field onto one nowrap line and clips long
values (the full date string, repo paths) mid-string. Give the backup
snapshot its own scoped label-over-value grid plus full-width Tags/Paths
blocks that wrap, surface app=/host=/engine= tags as their own fields,
and show a readable date (full timestamp on hover). Applied to both the
global Snapshots tab and the per-app Backups card so they match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Signed-off-by: librelad <librelad@digitalangels.vip>
2026-05-28 18:38:28 +01:00

1473 lines
34 KiB
CSS
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Backup Page — restic-engine UI */
.backup-layout {
display: flex;
min-height: calc(100vh - var(--topbar-height, 60px));
}
.backup-layout .main {
flex: 1;
min-width: 0;
overflow-y: auto;
}
.backup-page {
color: var(--text-primary);
width: 100%;
padding-bottom: 48px;
}
/* The whole backup page is one .config-section card containing both the
.page-header and the body. Remove the card's inner padding so the
.page-header sits flush at the top and its border-bottom acts as a
full-width divider; the body gets its own padding. */
.backup-page-section {
padding: 0;
overflow: hidden;
}
.backup-page-section > .page-header {
margin-bottom: 0;
}
.backup-page-body {
padding: 22px;
}
/* Configuration tab embeds /config's renderConfig, which emits its own
.page-header. The outer backup page already has one, so suppress the
embedded one to avoid the duplicate "Backup" heading. */
.backup-embedded-config > .page-header {
display: none;
}
/* SVG icon slot inside the shared .page-header (defined in config.css). */
.page-header-icon-slot {
width: 36px;
height: 36px;
flex-shrink: 0;
color: var(--accent);
display: flex;
align-items: center;
justify-content: center;
}
.page-header-icon-slot svg {
width: 32px;
height: 32px;
}
.backup-engine-badge {
background: linear-gradient(135deg, var(--accent), var(--accent-hover));
color: var(--text-primary);
padding: 4px 10px;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.backup-primary-btn,
.backup-secondary-btn,
.backup-danger-btn,
.backup-refresh-btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 9px 16px;
border-radius: 8px;
border: none;
font-size: 0.875rem;
font-weight: 500;
cursor: pointer;
transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
}
/* .backup-primary-btn and .backup-danger-btn take their fill, text and hover
from the shared nebula button rules in themes.css (the .btn-primary and
.btn-uninstall groups), so they match the config-page buttons exactly across
themes. Only the shared layout above lives here — no local gradient/shadow. */
.backup-secondary-btn,
.backup-refresh-btn {
background: rgba(var(--text-rgb), 0.06);
color: var(--text-primary);
border: 1px solid rgba(var(--text-rgb), 0.12);
}
.backup-secondary-btn:hover,
.backup-refresh-btn:hover {
background: rgba(var(--text-rgb), 0.1);
}
.backup-tabpanel {
display: none;
}
.backup-tabpanel.active {
display: block;
animation: backupFadeIn 0.25s ease;
}
@keyframes backupFadeIn {
from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); }
}
.backup-summary-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.backup-summary-tile {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 14px;
padding: 18px 20px;
transition: transform 0.15s ease, border-color 0.15s ease;
}
.backup-summary-tile:hover {
transform: translateY(-2px);
border-color: rgba(var(--accent-rgb), 0.35);
}
.backup-summary-tile-label {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
margin-bottom: 8px;
}
.backup-summary-tile-value {
font-size: 1.6rem;
font-weight: 600;
color: var(--text-primary);
letter-spacing: -0.02em;
}
.backup-summary-tile-detail {
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
margin-top: 4px;
}
.backup-cards-row {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
@media (max-width: 980px) {
.backup-cards-row {
grid-template-columns: 1fr;
}
}
.backup-card {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 14px;
padding: 18px 22px;
}
/* Stands alone below the two-column cards row (which has no bottom margin), so
give it the same vertical rhythm as the summary -> cards gap. */
.backup-system-card {
margin-top: 20px;
}
.backup-card-header {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
margin-bottom: 16px;
flex-wrap: wrap;
}
.backup-card-header h2 {
margin: 0;
font-size: 1.05rem;
font-weight: 600;
letter-spacing: -0.01em;
}
.backup-card-hint {
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
}
.backup-app-grid {
display: grid;
/* 2 per row — clicking a tile opens the Back-up checklist modal, so we
no longer need room for inline action buttons. Wider tiles read
better and the System config tile fits one row alongside an app. */
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.backup-app-tile {
background: rgba(var(--text-rgb), 0.05);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 10px;
padding: 12px 14px;
display: flex;
align-items: center;
gap: 12px;
cursor: pointer;
transition: all 0.15s ease;
}
.backup-app-tile:hover {
border-color: rgba(var(--accent-rgb), 0.4);
transform: translateY(-1px);
}
.backup-app-tile-icon {
width: 36px;
height: 36px;
flex-shrink: 0;
border-radius: 8px;
object-fit: cover;
background: rgba(var(--text-rgb), 0.05);
}
.backup-app-tile-text {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
flex: 1;
}
.backup-app-tile-name {
font-weight: 600;
font-size: 0.95rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.backup-app-tile-meta {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
}
.backup-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.backup-status-dot.ok { background: #22c55e; box-shadow: 0 0 0 3px rgba(34, 197, 94, 0.15); }
.backup-status-dot.warn { background: #f59e0b; box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.15); }
.backup-status-dot.fail { background: #ef4444; box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.15); }
.backup-status-dot.none { background: rgba(var(--text-rgb), 0.25); }
.backup-repo-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.backup-repo-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: rgba(var(--text-rgb), 0.05);
border-radius: 10px;
gap: 12px;
}
.backup-repo-row-name {
font-weight: 600;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
}
.backup-repo-row-meta {
font-size: 0.75rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
text-align: right;
}
.backup-repo-type-pill {
background: rgba(var(--accent-rgb), 0.12);
color: var(--accent);
padding: 2px 8px;
border-radius: 999px;
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.backup-repo-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 16px;
}
/* Locations list — expandable rows mirroring the Tasks page .task-item shell */
.backup-location-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.backup-location-row.task-item {
margin-bottom: 0;
}
.backup-location-header {
padding: 18px 22px;
gap: 14px;
user-select: none;
min-height: 64px;
}
.backup-location-row-type-icon {
flex-shrink: 0;
width: 28px;
height: 28px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--accent);
}
.backup-location-row-type-icon[data-type="sftp"] { color: #818cf8; }
.backup-location-row-type-icon[data-type="rest"],
.backup-location-row-type-icon[data-type="s3"],
.backup-location-row-type-icon[data-type="b2"],
.backup-location-row-type-icon[data-type="gs"],
.backup-location-row-type-icon[data-type="azure"],
.backup-location-row-type-icon[data-type="rclone"] { color: #38bdf8; }
/* Status pill — mirrors the task-status pill on the Tasks page so the
visual language is consistent. */
.task-status.backup-loc-status {
padding: 3px 8px;
border-radius: 999px;
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border: 1px solid transparent;
line-height: 1.3;
}
.task-status.backup-loc-status.status-ready {
background: rgba(var(--status-success-rgb), 0.30);
border-color: rgba(var(--status-success-rgb), 0.65);
color: #86efac;
}
.task-status.backup-loc-status.status-init {
background: rgba(var(--status-warning-rgb), 0.22);
border-color: rgba(var(--status-warning-rgb), 0.60);
color: #fcd34d;
}
.task-status.backup-loc-status.status-disabled {
background: rgba(var(--text-rgb), 0.08);
border-color: rgba(var(--text-rgb), 0.18);
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
}
.backup-location-row-info {
display: flex;
align-items: center;
gap: 10px;
flex: 1;
min-width: 0;
overflow: hidden;
}
.backup-location-row-name {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 240px;
}
.backup-location-row-status-pill {
font-size: 0.78rem;
font-weight: 600;
color: var(--text-primary);
}
.backup-location-row-stat {
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
white-space: nowrap;
}
.backup-location-row-sep {
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.35));
}
.backup-pill-mini {
background: rgba(245, 158, 11, 0.15);
color: #f59e0b;
padding: 2px 8px;
border-radius: 999px;
font-size: 0.65rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
/* Hide separator + stat tokens on narrow screens to keep the row from
wrapping — the expanded details still show full info. */
@media (max-width: 720px) {
.backup-location-row-sep,
.backup-location-row-stat {
display: none;
}
}
.backup-location-chevron {
flex-shrink: 0;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
transition: transform 0.2s ease;
}
.backup-location-row.expanded .backup-location-chevron {
transform: rotate(180deg);
}
.backup-location-details {
padding: 16px 20px 20px;
}
.backup-location-details > .config-category:first-of-type {
margin-top: 0;
}
.backup-location-actions {
display: flex;
justify-content: flex-start;
align-items: center;
gap: 12px;
margin-top: 16px;
margin-bottom: 14px;
padding-top: 14px;
border-top: 1px solid rgba(var(--text-rgb), 0.06);
}
.backup-modal-wide .backup-modal-inner,
.backup-modal-inner.backup-modal-wide {
max-width: 640px;
}
.backup-modal-wide {
/* selector also catches when class is on inner */
}
.backup-modal-inner.backup-modal-wide,
#backup-location-modal .backup-modal-inner {
max-width: 640px;
width: min(92vw, 640px);
}
.backup-repo-card {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 14px;
padding: 18px 22px;
}
.backup-repo-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.backup-repo-card-title {
font-weight: 600;
font-size: 1.05rem;
display: flex;
align-items: center;
gap: 10px;
}
.backup-repo-disabled-pill {
background: rgba(var(--text-rgb), 0.1);
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
padding: 3px 10px;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.backup-repo-enabled-pill {
background: rgba(34, 197, 94, 0.15);
color: #16a34a;
padding: 3px 10px;
border-radius: 999px;
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
}
.backup-repo-detail {
display: grid;
grid-template-columns: 110px 1fr;
gap: 6px 12px;
font-size: 0.82rem;
margin-bottom: 12px;
}
.backup-repo-detail-key {
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
}
.backup-repo-detail-value {
color: var(--text-primary);
word-break: break-all;
}
.backup-snapshot-table-wrap {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 14px;
overflow: hidden;
}
.backup-snapshot-table {
width: 100%;
border-collapse: collapse;
}
.backup-snapshot-table th,
.backup-snapshot-table td {
padding: 12px 16px;
text-align: left;
font-size: 0.875rem;
border-bottom: 1px solid rgba(var(--text-rgb), 0.06);
}
.backup-snapshot-table th {
background: rgba(var(--text-rgb), 0.04);
font-weight: 600;
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
}
.backup-snapshot-table tbody tr:hover {
background: rgba(var(--accent-rgb), 0.04);
}
.backup-col-actions {
width: 180px;
text-align: right;
}
.backup-snapshot-id {
font-family: ui-monospace, SFMono-Regular, monospace;
font-size: 0.82rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.7));
}
.backup-row-action-btn {
background: rgba(var(--text-rgb), 0.06);
border: 1px solid rgba(var(--text-rgb), 0.1);
color: var(--text-primary);
border-radius: 6px;
padding: 5px 10px;
font-size: 0.78rem;
cursor: pointer;
margin-left: 6px;
transition: all 0.15s ease;
}
.backup-row-action-btn:hover {
background: rgba(var(--accent-rgb), 0.12);
border-color: var(--accent);
color: var(--accent);
}
.backup-row-action-btn.danger:hover {
background: rgba(239, 68, 68, 0.12);
border-color: #ef4444;
color: #ef4444;
}
.backup-filters {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
}
@media (max-width: 600px) {
.backup-filters {
grid-template-columns: 1fr;
}
}
.backup-filter-input,
.backup-filter-select {
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.1);
border-radius: 8px;
padding: 9px 12px;
color: var(--text-primary);
font-size: 0.875rem;
width: 100%;
box-sizing: border-box;
min-width: 0;
}
.backup-filter-input:focus,
.backup-filter-select:focus {
outline: none;
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}
/* Inline forms inside backup cards */
.backup-form-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 14px 20px;
margin-top: 4px;
}
.backup-form-row {
display: flex;
flex-direction: column;
gap: 6px;
font-size: 0.875rem;
}
.backup-form-row-toggle {
flex-direction: row;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 8px 0;
}
.backup-form-label {
color: var(--text-secondary, rgba(var(--text-rgb), 0.7));
font-weight: 500;
font-size: 0.82rem;
}
.backup-form-readonly {
font-family: ui-monospace, SFMono-Regular, monospace;
color: var(--text-primary);
background: rgba(var(--text-rgb), 0.04);
border: 1px solid rgba(var(--text-rgb), 0.08);
border-radius: 8px;
padding: 9px 12px;
font-size: 0.85rem;
}
.backup-form-section-title {
margin-top: 22px;
margin-bottom: 10px;
font-weight: 600;
font-size: 0.85rem;
color: var(--text-primary);
text-transform: uppercase;
letter-spacing: 0.06em;
}
.backup-form-section-title .backup-card-hint {
text-transform: none;
letter-spacing: normal;
margin-left: 8px;
font-weight: 400;
}
.backup-retention-block {
grid-template-columns: 1fr;
}
.backup-retention-preset-block {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
gap: 6px;
padding-bottom: 14px;
margin-bottom: 6px;
border-bottom: 1px solid rgba(var(--text-rgb), 0.08);
}
.backup-retention-hint {
margin-top: -4px;
font-style: italic;
}
.backup-retention-advanced[hidden] {
display: none;
}
.backup-retention-advanced {
margin-top: 12px;
padding-top: 12px;
border-top: 1px dashed rgba(var(--text-rgb), 0.08);
}
/* The location editor reuses the app-detail tab design (.tabs-wrapper /
.tab-button / .tab-panel from style.css). Round the strip's top corners so
it reads as a card (.tabs-content already rounds the bottom), and let the
panel own a comfortable, even padding around the config fields. */
.backup-location-details .tabs-list {
border-radius: 12px 12px 0 0;
}
.backup-location-details .tabs-content {
padding: 0;
}
.backup-location-details .tab-panel {
padding: 20px 22px;
}
/* SSH key card (sftp locations). LibrePortal holds the private key; only the
public key is shown — that's what goes in the remote's authorized_keys. */
.backup-ssh-key-card {
margin-top: 14px;
padding: 14px;
border: 1px solid rgba(var(--text-rgb), 0.10);
border-radius: 10px;
background: rgba(var(--text-rgb), 0.03);
}
.backup-ssh-key-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.backup-ssh-key-title {
font-weight: 600;
font-size: 0.95rem;
}
.backup-ssh-key-status {
font-size: 0.8rem;
font-weight: 600;
padding: 2px 8px;
border-radius: 999px;
}
.backup-ssh-key-status.ok {
color: var(--accent);
background: rgba(var(--accent-rgb), 0.12);
}
.backup-ssh-key-status.none {
color: rgba(var(--text-rgb), 0.6);
background: rgba(var(--text-rgb), 0.08);
}
.backup-ssh-pubkey,
.backup-ssh-keyinput {
width: 100%;
box-sizing: border-box;
font-family: var(--font-mono, monospace);
font-size: 0.8rem;
line-height: 1.4;
padding: 8px 10px;
border: 1px solid rgba(var(--text-rgb), 0.12);
border-radius: 8px;
background: var(--card-bg);
color: var(--text-primary);
resize: vertical;
}
.backup-ssh-pubkey {
word-break: break-all;
}
.backup-ssh-key-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.backup-form-footer {
display: flex;
justify-content: flex-end;
margin-top: 18px;
padding-top: 14px;
border-top: 1px solid rgba(var(--text-rgb), 0.06);
}
/* iOS-style toggle */
.backup-toggle {
position: relative;
width: 38px;
height: 22px;
flex-shrink: 0;
}
.backup-toggle input {
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
position: absolute;
inset: 0;
margin: 0;
z-index: 1;
}
.backup-toggle-slider {
position: absolute;
inset: 0;
background: rgba(var(--text-rgb), 0.15);
border-radius: 999px;
transition: background 0.18s ease;
}
.backup-toggle-slider::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
background: #fff;
border-radius: 50%;
transition: transform 0.18s ease;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}
.backup-toggle input:checked + .backup-toggle-slider {
background: var(--accent);
}
.backup-toggle input:checked + .backup-toggle-slider::after {
transform: translateX(16px);
}
/* Enable/disable toggle in a location row header — sits between the row
info and the expand chevron, controlling the location without expanding it. */
.backup-loc-enable-toggle {
margin-right: 6px;
}
/* Repo card extras */
.backup-repo-stats {
display: flex;
gap: 18px;
flex-wrap: wrap;
padding: 10px 14px;
background: rgba(var(--text-rgb), 0.04);
border-radius: 10px;
margin-bottom: 16px;
font-size: 0.82rem;
}
.backup-repo-stat-label {
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
margin-right: 4px;
text-transform: uppercase;
letter-spacing: 0.05em;
font-size: 0.7rem;
}
.backup-warning-banner {
margin-top: 18px;
padding: 14px 16px;
background: rgba(245, 158, 11, 0.08);
border: 1px solid rgba(245, 158, 11, 0.25);
border-radius: 10px;
font-size: 0.85rem;
display: flex;
align-items: center;
gap: 16px;
}
.backup-warning-banner-text {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
min-width: 0;
}
/* Big amber alert icon so the warning reads at a glance. */
.backup-warning-banner-icon {
flex-shrink: 0;
color: #f59e0b;
}
/* Dismiss (×) in the banner's top-right corner. */
.backup-warning-banner-close {
flex-shrink: 0;
align-self: flex-start;
background: transparent;
border: none;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
cursor: pointer;
padding: 2px;
border-radius: 6px;
line-height: 0;
transition: background 0.15s ease, color 0.15s ease;
}
.backup-warning-banner-close:hover {
background: rgba(var(--text-rgb), 0.1);
color: var(--text-primary);
}
/* Export dropdown in the backup page header (Configuration tab). */
#backup-page-header .page-header-actions {
position: relative;
}
.backup-export-menu {
position: absolute;
top: calc(100% + 6px);
right: 0;
z-index: 50;
min-width: 210px;
padding: 6px;
background: var(--card-bg);
border: 1px solid var(--card-border, var(--border-color));
border-radius: 10px;
box-shadow: var(--card-shadow, 0 8px 24px rgba(0, 0, 0, 0.25));
display: flex;
flex-direction: column;
gap: 2px;
}
.backup-export-menu[hidden] {
display: none;
}
.backup-export-menu-item {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
text-align: left;
padding: 8px 10px;
background: transparent;
border: none;
border-radius: 6px;
color: var(--text-primary);
font-size: 0.85rem;
cursor: pointer;
}
.backup-export-menu-item:hover {
background: rgba(var(--text-rgb), 0.08);
}
.backup-warning-banner [data-action="export-passwords"] {
flex-shrink: 0;
white-space: nowrap;
}
.backup-warning-banner [data-action="export-passwords"][data-busy="1"] {
opacity: 0.6;
cursor: progress;
}
.backup-warning-banner strong {
color: #f59e0b;
}
.backup-warning-banner code {
background: rgba(var(--text-rgb), 0.06);
padding: 4px 8px;
border-radius: 6px;
font-size: 0.8rem;
color: var(--text-primary);
display: inline-block;
margin-top: 4px;
}
.backup-modal {
display: none;
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(6px);
z-index: 1000;
align-items: center;
justify-content: center;
}
.backup-modal.open {
display: flex;
}
/* Slightly translucent — backdrop blur still shows through, but enough
alpha to keep the modal legible on busy gradients. */
.backup-modal-inner {
background: color-mix(in srgb, var(--surface-bg-solid, #1a1d24) 78%, transparent);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(var(--text-rgb), 0.14);
border-radius: 14px;
width: 90%;
max-width: 460px;
overflow: hidden;
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}
.backup-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid rgba(var(--text-rgb), 0.08);
}
.backup-modal-header h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
.backup-modal-close {
background: none;
border: none;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
font-size: 1.4rem;
cursor: pointer;
line-height: 1;
padding: 0 6px;
}
.backup-modal-body {
padding: 18px 20px;
font-size: 0.9rem;
color: var(--text-primary);
max-height: 60vh;
overflow-y: auto;
}
.backup-modal-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
padding: 14px 20px;
border-top: 1px solid rgba(var(--text-rgb), 0.08);
}
.backup-empty-state {
padding: 40px 20px;
text-align: center;
color: var(--text-secondary, rgba(var(--text-rgb), 0.55));
font-size: 0.9rem;
}
.backup-engine-input-row {
display: flex;
flex-wrap: nowrap;
align-items: stretch;
gap: 10px;
width: 100%;
}
.backup-engine-input-row > *:not(.backup-engine-details-btn) {
flex: 1 1 0%;
width: auto;
min-width: 0;
max-width: none;
}
.backup-engine-input-row > .custom-select {
display: block;
}
.backup-engine-details-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
flex: 0 0 auto;
padding: 0 16px;
min-height: 44px;
white-space: nowrap;
line-height: 1;
}
/* Engine name pill next to the type pill on each location row. */
.backup-engine-pill {
background: rgba(var(--accent-rgb), 0.16);
color: var(--accent);
padding: 2px 8px;
border-radius: 999px;
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.backup-engine-pill[data-engine="borg"] {
background: rgba(245, 158, 11, 0.18);
color: #f59e0b;
}
.backup-engine-pill[data-engine="kopia"] {
background: rgba(99, 102, 241, 0.18);
color: #818cf8;
}
/* Engine details modal. */
.backup-engine-modal-head {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 16px;
}
.backup-engine-logo {
width: 36px;
height: 36px;
flex-shrink: 0;
color: var(--accent);
}
.backup-engine-modal-head h4 {
margin: 0 0 4px 0;
font-size: 1.1rem;
font-weight: 600;
}
.backup-engine-props {
width: 100%;
border-collapse: collapse;
margin-bottom: 18px;
}
.backup-engine-props th,
.backup-engine-props td {
padding: 8px 10px;
text-align: left;
font-size: 0.85rem;
border-bottom: 1px solid rgba(var(--text-rgb), 0.06);
}
.backup-engine-props th {
width: 35%;
color: var(--text-secondary, rgba(var(--text-rgb), 0.6));
font-weight: 500;
}
.backup-engine-features {
margin: 6px 0 18px;
padding-left: 22px;
font-size: 0.88rem;
line-height: 1.55;
}
.backup-engine-features li {
margin-bottom: 4px;
}
.backup-engine-docs-link {
color: var(--accent);
text-decoration: none;
font-family: ui-monospace, SFMono-Regular, monospace;
font-size: 0.85rem;
}
.backup-engine-docs-link:hover {
text-decoration: underline;
}
/* Per-app backup status badge (used on app detail page) */
.backup-app-badge {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 10px;
background: rgba(var(--text-rgb), 0.05);
border-radius: 999px;
font-size: 0.78rem;
color: var(--text-secondary, rgba(var(--text-rgb), 0.65));
}
/* ============================================================
Per-app Backups tab — Services-style snapshot rows.
Each row is a .task-item + .task-header + .task-details so it
inherits the global task-list visual (shared with services). The
only backup-specific styling is the location pill colour, the
ID chip, the deep-link highlight flash, and the inline tags.
============================================================ */
/* Tab shell — mirrors .services-section / .tasks-section so the
three app-detail tabs share one visual idiom: padding 0 here,
the title block provides its own 20px inset, the snapshots get
a recessed dark container of their own. */
.backup-section {
display: flex;
flex-direction: column;
padding: 0;
}
/* Header row — title left, action buttons right (Backup now /
Open backup center). Pinned at the top of the pane regardless of
scroll so the primary actions stay reachable when the snapshot
list grows. Same shape services-title uses for its Advanced
toggle. */
.backup-title {
padding: 20px;
background: transparent;
border-bottom: 1px solid var(--border-color);
margin-bottom: 0;
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
}
.backup-title-main { flex: 1; min-width: 0; }
.backup-title h3 {
margin: 0 0 8px 0;
color: var(--text-primary, #fff);
font-size: 18px;
font-weight: 600;
}
.backup-title p {
margin: 0;
color: var(--text-secondary, #ccc);
font-size: 13px;
}
.backup-title-actions {
display: flex;
gap: 10px;
align-items: center;
flex-shrink: 0;
}
/* Recessed dark panel wrapping the snapshot list — same recipe as
.tasks-container / .services-rows so all three tabs match. */
.backup-snapshots-container {
padding: 16px;
margin: 16px;
background: rgba(var(--bg-rgb), 0.2);
border-radius: 8px;
}
/* Status line above the rows reading "Latest backup 5m ago · N total
across M locations". Set in JS via #backup-app-card-status. */
.backup-app-card-status {
color: var(--text-secondary, #ccc);
font-size: 13px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.backup-app-card-status .backup-card-hint {
color: rgba(var(--text-rgb), 0.5);
}
.backup-snapshot-rows {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: 14px;
}
.backup-snapshot-loc-pill {
background: rgba(var(--accent-rgb), 0.15);
color: var(--accent);
border-radius: 999px;
padding: 2px 9px;
font-size: 0.72rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.backup-snapshot-id-chip {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.72rem;
color: rgba(var(--text-rgb), 0.55);
padding: 1px 6px;
background: rgba(var(--text-rgb), 0.05);
border-radius: 4px;
letter-spacing: 0.02em;
}
.backup-snapshot-tag {
display: inline-block;
margin: 0 4px 2px 0;
padding: 1px 6px;
background: rgba(var(--text-rgb), 0.06);
border-radius: 4px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.72rem;
color: rgba(var(--text-rgb), 0.7);
}
/* Snapshot detail panel. The shared .task-meta/.meta-item layout forces
one nowrap line per item and clips long values (the full date, repo
paths) mid-string, so the backup row gets its own label-over-value grid
plus full-width blocks for tags and paths that wrap cleanly. */
.backup-snapshot-meta {
display: flex;
flex-direction: column;
gap: 14px;
}
.backup-snapshot-meta .bsm-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 14px 18px;
}
.backup-snapshot-meta .bsm-field {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.backup-snapshot-meta .bsm-label {
font-size: 0.68rem;
text-transform: uppercase;
letter-spacing: 0.05em;
font-weight: 600;
color: rgba(var(--text-rgb), 0.45);
}
.backup-snapshot-meta .bsm-value {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
font-size: 0.85rem;
color: var(--text-primary);
}
.backup-snapshot-meta .bsm-value code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.78rem;
background: rgba(var(--text-rgb), 0.06);
color: rgba(var(--text-rgb), 0.82);
padding: 1px 6px;
border-radius: 4px;
word-break: break-all;
}
.backup-snapshot-meta .bsm-block {
display: flex;
flex-direction: column;
gap: 8px;
padding-top: 13px;
border-top: 1px solid rgba(var(--text-rgb), 0.08);
}
.backup-snapshot-meta .bsm-tags {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.backup-snapshot-meta .bsm-paths {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.backup-snapshot-meta .bsm-paths code {
display: inline-block;
max-width: 100%;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 0.78rem;
background: rgba(var(--text-rgb), 0.06);
color: rgba(var(--text-rgb), 0.82);
padding: 4px 9px;
border-radius: 5px;
word-break: break-all;
}
.backup-snapshot-overflow {
margin-top: 10px;
font-size: 0.78rem;
color: rgba(var(--text-rgb), 0.5);
text-align: center;
}
.backup-snapshot-overflow a { color: var(--accent); }
/* Deep-link arrival: ?snapshot=<id> flashes the row briefly so the
user's eye lands on the right thing after the SPA jump. */
.backup-snapshot-flash {
animation: backup-snapshot-flash 2.2s ease-out;
}
@keyframes backup-snapshot-flash {
0% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.55); }
20% { box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.55); }
100% { box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.0); }
}
/* ============================================================
Global Backup dashboard — app tile + "Back up" action pill.
The whole tile navigates to the per-app Backups tab; the pill
is the explicit affordance for the old "open the pick modal"
behaviour. Hidden by default; visible on hover/focus so the
tile stays calm at rest.
============================================================ */
.backup-app-tile {
position: relative;
}
.backup-app-tile-action {
position: absolute;
top: 10px;
right: 10px;
padding: 4px 10px;
font-size: 0.72rem;
font-weight: 700;
color: var(--accent);
background: rgba(var(--accent-rgb), 0.14);
border: 1px solid rgba(var(--accent-rgb), 0.4);
border-radius: 999px;
cursor: pointer;
opacity: 0;
transform: translateY(-2px);
transition: opacity .15s ease, transform .15s ease, background .15s ease;
}
.backup-app-tile:hover .backup-app-tile-action,
.backup-app-tile:focus-within .backup-app-tile-action {
opacity: 1;
transform: translateY(0);
}
.backup-app-tile-action:hover {
background: rgba(var(--accent-rgb), 0.28);
}
/* ============================================================
Global Snapshots table — App + ID cells link to the per-app
page deep-linked to that snapshot.
============================================================ */
.backup-snapshot-link {
color: var(--text-primary);
text-decoration: none;
border-bottom: 1px dashed rgba(var(--accent-rgb), 0.4);
transition: color .15s ease, border-color .15s ease;
}
.backup-snapshot-link:hover {
color: var(--accent);
border-bottom-color: var(--accent);
}
a.backup-snapshot-link.backup-snapshot-id {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}