81a93a489b
Backend — Photos timeline now groups by real capture date instead of upload time. New MediaMetadataService (FileLifecycleHook) extracts EXIF DateTimeOriginal from images and container creation_time from videos (mov/mp4/mkv) via nom-exif, timezone-correct (OffsetTimeOriginal), persisting captured_at so the existing media_sort_date trigger takes over. Adds POST /admin/photos/metadata/reextract to backfill existing media. Falls back to upload date when no embedded date exists. Frontend — premium grid cards: combined metadata line (relative date · size, owner avatar when shared), custom selection checkbox with a clear checked state, uniform full-width 4:3 thumbnail tiles independent of filename length, centered file-type icons, and a hit-test fix so checkbox/star/kebab clicks reach the controls (the decorative thumbnail no longer captures pointer events). Notification messages internationalised across all 16 locales. Broader polish: design tokens, a11y/focus-visible states, brand + PWA assets. Chore — bump semver-compatible dependencies (cargo upgrade); add nom-exif 3.6.1. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
97 lines
2.4 KiB
CSS
97 lines
2.4 KiB
CSS
/* Loading spinner overlay */
|
|
.files-loading-spinner {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: var(--space-20) var(--space-5);
|
|
gap: var(--space-4);
|
|
grid-column: 1 / -1;
|
|
}
|
|
|
|
.files-loading-spinner .spinner {
|
|
width: 36px;
|
|
height: 36px;
|
|
border: 3px solid var(--color-border);
|
|
border-top-color: var(--color-accent);
|
|
border-radius: 50%;
|
|
animation: spin 0.7s linear infinite;
|
|
}
|
|
|
|
/* @keyframes spin → base/animations.css (canonical, loaded via main.css). */
|
|
|
|
.files-loading-spinner span {
|
|
font-size: var(--text-base);
|
|
color: var(--color-text-placeholder);
|
|
font-weight: var(--weight-medium);
|
|
}
|
|
|
|
/* Full-area drop overlay. Hidden by default; shown over the content region
|
|
(right of the sidebar) while files are dragged into the Files section
|
|
(toggled in ui.js). Frosted backdrop + dashed border = a clear "drop here"
|
|
affordance instead of a small inline box. */
|
|
.dropzone {
|
|
position: fixed;
|
|
top: var(--space-4);
|
|
right: var(--space-4);
|
|
bottom: var(--space-4);
|
|
left: calc(var(--sidebar-width) + var(--space-4));
|
|
z-index: var(--z-overlay);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: var(--space-4);
|
|
border: 3px dashed var(--color-border-medium);
|
|
border-radius: var(--radius-3xl);
|
|
background: var(--color-scrim-control);
|
|
backdrop-filter: blur(4px);
|
|
-webkit-backdrop-filter: blur(4px);
|
|
color: var(--color-text-secondary);
|
|
font-size: var(--text-lg);
|
|
font-weight: var(--weight-semibold);
|
|
text-align: center;
|
|
}
|
|
|
|
/* Children don't catch pointer events, so moving over the icon/text doesn't
|
|
fire dragleave on the overlay (avoids show/hide flicker). */
|
|
.dropzone > * {
|
|
pointer-events: none;
|
|
}
|
|
|
|
.dropzone-icon {
|
|
font-size: var(--text-6xl);
|
|
color: var(--color-accent);
|
|
}
|
|
|
|
.dropzone.active {
|
|
border-color: var(--color-accent);
|
|
color: var(--color-accent-text);
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.dropzone {
|
|
left: var(--space-4);
|
|
}
|
|
}
|
|
|
|
.upload-progress {
|
|
margin-top: 15px;
|
|
width: 100%;
|
|
display: block;
|
|
}
|
|
|
|
.progress-bar {
|
|
background-color: var(--color-bg-empty);
|
|
height: 6px;
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.progress-fill {
|
|
height: 100%;
|
|
background-color: var(--color-accent);
|
|
width: 0%;
|
|
transition: width 0.3s;
|
|
}
|