feat: photo/video capture-date pipeline + premium UI/UX overhaul

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>
This commit is contained in:
DioCrafts
2026-06-15 00:17:17 +02:00
parent dac299fea6
commit 81a93a489b
129 changed files with 8194 additions and 2473 deletions
+82
View File
@@ -0,0 +1,82 @@
/* ============================================================
* Typography utilities — Fase 0 foundations.
*
* Semantic heading roles DECOUPLED from element level, so any
* h1–h6 can carry the correct visual weight while the document
* keeps a correct, accessible heading order. All values route
* through the type/leading/weight/tracking tokens in variables.css.
*
* Views are migrated onto these classes in Fase 1 (replacing the
* per-page raw font-size/weight headings the audit flagged).
* ============================================================ */
/* Page title — one per page (the h1 role). */
.heading-page {
font-size: var(--text-2xl);
line-height: var(--leading-tight);
font-weight: var(--weight-bold);
letter-spacing: var(--tracking-tight);
color: var(--color-text-heading);
}
/* Section heading. */
.heading-section {
font-size: var(--text-xl);
line-height: var(--leading-snug);
font-weight: var(--weight-semibold);
letter-spacing: var(--tracking-tight);
color: var(--color-text-heading);
}
/* Card / panel heading. */
.heading-card {
font-size: var(--text-md);
line-height: var(--leading-snug);
font-weight: var(--weight-semibold);
color: var(--color-text-heading);
}
/* Eyebrow / overline label (uppercase caps with tracking). */
.heading-eyebrow {
font-size: var(--text-xs);
line-height: var(--leading-normal);
font-weight: var(--weight-semibold);
letter-spacing: var(--tracking-widest);
text-transform: uppercase;
color: var(--color-text-muted);
}
/* Constrain running text to a comfortable measure (~65ch). */
.prose {
max-width: var(--measure-prose);
}
/* Headings wrap with balanced line lengths (no single orphan word). */
h1,
h2,
h3,
.heading-page,
.heading-section,
.heading-card,
.page-title {
text-wrap: balance;
}
/* Running prose wraps "pretty" (avoids orphans and short last lines). */
.prose,
.empty-state p,
.about-description,
.auth-subtitle,
.auth-hint,
.language-subtitle {
text-wrap: pretty;
}
/* Tabular figures for numeric UI so digits align and don't jitter as they
change (storage readouts, badges, stat counters). */
.storage-info,
.user-menu-storage-text,
.notif-badge,
.stat-value {
font-variant-numeric: tabular-nums;
}