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:
@@ -16,7 +16,7 @@
|
||||
.file-icon {
|
||||
width: 100px;
|
||||
height: 70px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-lg);
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -33,12 +33,25 @@
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
z-index: 1;
|
||||
/* The thumbnail is purely decorative — it must never be a hit-test target.
|
||||
In grid view it fills the whole tile UNDER the overlay controls (checkbox,
|
||||
favorite star, kebab); a loaded full-bleed thumbnail (promoted to its own
|
||||
stacking context by the hover scale transform) was capturing clicks meant
|
||||
for those controls, so they "did nothing" (the click fell through to
|
||||
open-file). Making it transparent to pointer events routes corner clicks
|
||||
to the controls and body clicks to the .file-icon (which still opens). */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ── Item states ─────────────────────────────────────────── */
|
||||
|
||||
.file-item {
|
||||
background-color: var(--color-item);
|
||||
/* Smooth the hover/selection tint (list rows used to snap). Grid cards
|
||||
override this with their own transform/shadow transition. */
|
||||
transition:
|
||||
background-color var(--motion-fast) var(--ease-standard),
|
||||
border-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
/* Brief pulse on rows that were just optimistically inserted (e.g.
|
||||
@@ -129,7 +142,7 @@
|
||||
height: 17px;
|
||||
cursor: pointer;
|
||||
accent-color: var(--color-accent);
|
||||
border-radius: 4px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.list-header.selection-mode {
|
||||
@@ -148,9 +161,9 @@
|
||||
.list-header {
|
||||
display: grid;
|
||||
grid-template-columns: var(--files-list-columns);
|
||||
column-gap: 12px;
|
||||
column-gap: var(--space-3);
|
||||
padding: 15px;
|
||||
font-weight: 600;
|
||||
font-weight: var(--weight-semibold);
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-bottom: 1px solid var(--color-border-faint);
|
||||
@@ -198,6 +211,42 @@
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
/* ── Sortable column headers (flat Drive-style sort) ─────────── */
|
||||
.list-header-sort {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1-5);
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
/* Inherit the header row's weight + colour so it reads as a header. */
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
transition: color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.list-header-sort:hover,
|
||||
.list-header-sort.is-active {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.list-header-sort__arrow {
|
||||
font-size: var(--text-2xs);
|
||||
color: var(--color-accent);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Match the value-cell alignment so header and data line up. */
|
||||
.list-header-sort[data-sort-field="size"] {
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.list-header-sort[data-sort-field="modified_at"] {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
/* ── Owner column ────────────────────────────────────────── */
|
||||
|
||||
/* Styles applied whenever the cell is visible (hidden class absent).
|
||||
@@ -215,7 +264,7 @@
|
||||
plain-text fallback content (cells without a vignette child). */
|
||||
.owner-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 14px;
|
||||
font-size: var(--text-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
@@ -234,7 +283,7 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
border-radius: var(--radius-xl);
|
||||
overflow: hidden;
|
||||
background-color: var(--color-item);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-xs);
|
||||
@@ -243,11 +292,21 @@
|
||||
.files-list-view .file-item {
|
||||
display: grid;
|
||||
grid-template-columns: var(--files-list-columns);
|
||||
column-gap: 12px;
|
||||
padding: 12px 15px;
|
||||
column-gap: var(--space-3);
|
||||
padding: var(--space-3) 15px;
|
||||
border-bottom: 1px solid var(--color-border-xfaint);
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color var(--motion-fast) var(--ease-standard),
|
||||
box-shadow var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
/* "This row" emphasis — a left accent bar on hover/selection (inset shadow,
|
||||
so it adds no width and never shifts the grid columns). */
|
||||
.files-list-view .file-item:hover,
|
||||
.files-list-view .file-item.selected {
|
||||
box-shadow: inset 3px 0 0 var(--color-accent);
|
||||
}
|
||||
|
||||
.files-list-view .file-item.drop-target {
|
||||
@@ -258,7 +317,7 @@
|
||||
color: var(--color-text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: var(--space-3);
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -290,13 +349,16 @@
|
||||
}
|
||||
|
||||
.files-list-view .file-item .file-icon {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
border-radius: var(--radius-lg);
|
||||
/* Hairline inner ring — consistent with the grid thumbnails so light
|
||||
previews don't bleed into the row. */
|
||||
box-shadow: inset 0 0 0 1px var(--color-border);
|
||||
font-size: var(--text-md);
|
||||
margin-bottom: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -309,20 +371,20 @@
|
||||
|
||||
.files-list-view .file-item .date-cell {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 14px;
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.files-list-view .file-item .size-cell {
|
||||
color: var(--color-text-muted);
|
||||
font-size: 14px;
|
||||
font-size: var(--text-base);
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.files-list-view .file-item .type-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
font-weight: var(--weight-medium);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
|
||||
.files-list-view .file-item .action-cell {
|
||||
@@ -338,14 +400,14 @@
|
||||
display: inline;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-lg);
|
||||
border: none;
|
||||
background: transparent;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-subtle);
|
||||
font-size: 16px;
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
|
||||
.files-list-view .file-item .action-cell button:not(.btn-action):hover {
|
||||
@@ -363,12 +425,27 @@
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Reveal the kebab on hover for cleaner rows — but only on hover-capable
|
||||
devices, so touch users (no hover) keep it always tappable. Stays visible
|
||||
on keyboard focus within the row. */
|
||||
@media (hover: hover) {
|
||||
.files-list-view .file-item .action-cell button.file-actions {
|
||||
opacity: 0;
|
||||
transition: opacity var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-list-view .file-item:hover .action-cell button.file-actions,
|
||||
.files-list-view .file-item:focus-within .action-cell button.file-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Grid view ───────────────────────────────────────────── */
|
||||
|
||||
.files-grid-view {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 20px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(var(--grid-card-min), 1fr));
|
||||
gap: var(--space-5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -377,18 +454,66 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Mobile reflow (≤ --bp-sm 640px) ──────────────────────────
|
||||
The 6-column grid can't fit a phone, so each list row collapses to a
|
||||
compact flex line (icon + name … size + actions) and the column header
|
||||
is hidden (sort controls live in the actions bar). One block covers
|
||||
files / trash / favorites / recent uniformly; flex ignores the grid
|
||||
tracks, so there's no header-vs-row misalignment to maintain. */
|
||||
@media (max-width: 640px) {
|
||||
/* Keep the selection-mode header (holds select-all); hide the plain one. */
|
||||
.list-header:not(.selection-mode) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.files-list-view .file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-3);
|
||||
}
|
||||
|
||||
.files-list-view .file-item .type-cell,
|
||||
.files-list-view .file-item .date-cell,
|
||||
.files-list-view .file-item .owner-cell,
|
||||
.files-list-view .file-item .path-cell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.files-list-view .file-item .name-cell {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.files-list-view .file-item .checkbox-cell,
|
||||
.files-list-view .file-item .size-cell,
|
||||
.files-list-view .file-item .action-cell {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Tighter grid on phones (2 columns instead of 1). */
|
||||
.files-grid-view {
|
||||
--grid-card-min: 140px;
|
||||
gap: var(--space-2);
|
||||
}
|
||||
}
|
||||
|
||||
.files-grid-view .file-item {
|
||||
border-radius: 12px;
|
||||
border: 2px solid var(--color-border);
|
||||
padding: 16px;
|
||||
border-radius: var(--radius-2xl);
|
||||
/* Hairline at rest → accent on hover (gallery, not form). */
|
||||
border: 1px solid var(--color-border);
|
||||
padding: var(--space-3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
box-shadow: 0 1px 3px var(--color-shadow-xs);
|
||||
box-shadow: 0 1px 2px var(--color-shadow-xs);
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
min-height: 160px;
|
||||
position: relative;
|
||||
transition:
|
||||
transform var(--motion-base) var(--ease-standard),
|
||||
box-shadow var(--motion-base) var(--ease-standard),
|
||||
border-color var(--motion-base) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item.dragging {
|
||||
@@ -398,9 +523,19 @@
|
||||
}
|
||||
|
||||
.files-grid-view .file-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 15px var(--color-shadow-sm);
|
||||
border-color: var(--color-border-medium);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 28px -8px var(--color-shadow-md);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
/* The thumbnail subtly zooms inside its clipped tile on hover — the "alive"
|
||||
feel of a premium gallery. */
|
||||
.files-grid-view .file-item .file-icon .file-thumb {
|
||||
transition: transform var(--motion-moderate) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item:hover .file-icon .file-thumb {
|
||||
transform: scale(1.04);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item.selected {
|
||||
@@ -416,63 +551,115 @@
|
||||
0 6px 18px var(--color-accent-ring-dark);
|
||||
}
|
||||
|
||||
/* elements hidden in grid view */
|
||||
/* elements hidden in grid view — the type label ("Imagen") is redundant under
|
||||
a thumbnail, so we drop it and surface the file size instead. */
|
||||
.files-grid-view .file-item .date-cell,
|
||||
.files-grid-view .file-item .size-cell,
|
||||
.files-grid-view .file-item .type-cell,
|
||||
.files-grid-view .file-item .owner-cell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Selection checkbox */
|
||||
/* Overlay controls sit ON the thumbnail (offset by the card padding) over a
|
||||
frosted scrim, so they read as part of the media instead of floating on the
|
||||
card corner. */
|
||||
.files-grid-view .file-item .checkbox-cell {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 6px;
|
||||
background: var(--color-item);
|
||||
top: calc(var(--space-3) + 8px);
|
||||
left: calc(var(--space-3) + 8px);
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--color-scrim-control);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
transition: opacity var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item:hover .checkbox-cell {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Visible whenever the card is hovered OR already selected, so the checked
|
||||
state stays on screen after the pointer leaves. */
|
||||
.files-grid-view .file-item:hover .checkbox-cell,
|
||||
.files-grid-view .file-item.selected .checkbox-cell {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Custom-drawn checkbox: an empty white box that fills with accent and shows a
|
||||
crisp white tick when checked. The native 17px control was invisible once the
|
||||
cell turned accent on selection (accent-on-accent), and the intended tick
|
||||
targeted a `.checkbox-cell i` the markup never renders — so a click read as
|
||||
"nothing happened". This makes the checked state unmistakable. */
|
||||
.files-grid-view .file-item .checkbox-cell input[type="checkbox"] {
|
||||
appearance: none;
|
||||
-webkit-appearance: none;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
border: 2px solid var(--color-border-medium);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-bg-surface);
|
||||
display: grid;
|
||||
place-content: center;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color var(--motion-fast) var(--ease-standard),
|
||||
border-color var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .checkbox-cell input[type="checkbox"]::after {
|
||||
content: "";
|
||||
width: 5px;
|
||||
height: 9px;
|
||||
margin-top: -1px;
|
||||
border: solid var(--color-danger-text);
|
||||
border-width: 0 2px 2px 0;
|
||||
transform: rotate(45deg) scale(0);
|
||||
transform-origin: center;
|
||||
transition: transform var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .checkbox-cell input[type="checkbox"]:checked {
|
||||
background: var(--color-accent);
|
||||
border-color: var(--color-accent);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item.selected .checkbox-cell i {
|
||||
color: var(--color-danger-text);
|
||||
font-size: 11px;
|
||||
.files-grid-view .file-item .checkbox-cell input[type="checkbox"]:checked::after {
|
||||
transform: rotate(45deg) scale(1);
|
||||
}
|
||||
|
||||
/* More actions button (three dots) */
|
||||
.files-grid-view .file-item .checkbox-cell input[type="checkbox"]:focus-visible {
|
||||
outline: 2px solid var(--color-accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* More actions button (three dots) — top-right of the thumbnail on a scrim. */
|
||||
.files-grid-view .file-item .file-actions {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
top: calc(var(--space-3) + 8px);
|
||||
right: calc(var(--space-3) + 8px);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-full);
|
||||
border: none;
|
||||
background: transparent;
|
||||
background: var(--color-scrim-control);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
z-index: 10;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-subtle);
|
||||
font-size: 16px;
|
||||
color: var(--color-text);
|
||||
font-size: var(--text-md);
|
||||
transition: opacity var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item:hover .file-actions {
|
||||
@@ -480,8 +667,7 @@
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .file-actions:hover {
|
||||
background: var(--color-border-light);
|
||||
color: var(--color-text-dark);
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item.drop-target {
|
||||
@@ -489,44 +675,53 @@
|
||||
border: 2px dashed var(--color-warning-border);
|
||||
}
|
||||
|
||||
/* "Shared" indicator — top-left of the thumbnail. Sits below the checkbox
|
||||
(which only appears on hover), so the two never both compete for the eye. */
|
||||
.files-grid-view .file-item .file-badge-shared {
|
||||
position: absolute;
|
||||
top: 38px;
|
||||
right: 38px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
top: calc(var(--space-3) + 8px);
|
||||
left: calc(var(--space-3) + 8px);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--radius-full);
|
||||
border: none;
|
||||
background: transparent;
|
||||
background: var(--color-scrim-control);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 12;
|
||||
font-size: 15px;
|
||||
z-index: 9;
|
||||
font-size: var(--text-2xs);
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
line-height: var(--leading-none);
|
||||
}
|
||||
|
||||
/* Favorite star (mirrors file-actions button pattern) */
|
||||
/* Favorite star — top-right of the thumbnail, left of the kebab, on a scrim. */
|
||||
.files-grid-view .file-item button.favorite-star {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 38px;
|
||||
top: calc(var(--space-3) + 8px);
|
||||
right: calc(var(--space-3) + 8px + 34px);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-full);
|
||||
border: none;
|
||||
background: transparent;
|
||||
background: var(--color-scrim-control);
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-sm);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
z-index: 12;
|
||||
cursor: pointer;
|
||||
color: var(--color-border-medium);
|
||||
color: var(--color-text-subtle);
|
||||
font-size: 15px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
line-height: var(--leading-none);
|
||||
transition: opacity var(--motion-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item:hover button.favorite-star {
|
||||
@@ -547,45 +742,120 @@
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .name-cell {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--weight-medium);
|
||||
text-align: center;
|
||||
margin-bottom: 5px;
|
||||
margin-bottom: 2px;
|
||||
color: var(--color-text);
|
||||
/* Span the full card width. The card is a centered flex column
|
||||
(`align-items: center`), so without an explicit width the name-cell —
|
||||
which wraps BOTH the thumbnail tile and the filename — shrink-wraps to
|
||||
the filename's length. That made `.file-icon { width: 100% }` track the
|
||||
NAME width, so a long name ("CURSO TERRAFORM") rendered a big tile and a
|
||||
short one ("Idiomas") a small one. Forcing 100% makes every thumbnail
|
||||
tile identical regardless of name length. */
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: 8px;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .name-cell span {
|
||||
padding-top: 8px;
|
||||
padding-bottom: 8px;
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
/* Ellipsis must live on the span (the text node), not the flex parent,
|
||||
or long names clip with no "…". */
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-top: var(--space-2);
|
||||
padding-bottom: var(--space-2);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .name-cell svg.favorite-star-inline {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .type-cell {
|
||||
font-size: 12px;
|
||||
color: var(--color-text-muted);
|
||||
text-align: center;
|
||||
/* The grid card's secondary line is now the combined .grid-meta block
|
||||
("hace 2 días · 4,31 MB"), so the standalone size-cell is hidden here. */
|
||||
.files-grid-view .file-item .size-cell {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ── Grid metadata line — recency + size (+ owner avatar when shared) ──────
|
||||
Replaces the lone "4,31 MB": a top file app captions a thumbnail with WHEN
|
||||
it was touched (the dominant retrieval cue), not just bytes. Hidden
|
||||
everywhere except the grid — list view keeps its own date/size/owner
|
||||
columns, so a display:none here drops it cleanly out of the row grid. */
|
||||
.grid-meta {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .grid-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-1);
|
||||
max-width: 100%;
|
||||
margin-top: 2px;
|
||||
font-size: var(--text-2xs);
|
||||
color: var(--color-text-muted);
|
||||
line-height: var(--leading-snug);
|
||||
}
|
||||
|
||||
/* On a narrow card the relative date truncates first; the size never clips. */
|
||||
.files-grid-view .file-item .grid-meta__date {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .grid-meta__size {
|
||||
flex: 0 0 auto;
|
||||
color: var(--color-text-faint);
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .grid-meta__size::before {
|
||||
content: "·";
|
||||
margin-right: var(--space-1);
|
||||
color: var(--color-text-faint);
|
||||
}
|
||||
|
||||
/* Owner avatar — only rendered when the item is shared; sits at the line end. */
|
||||
.files-grid-view .file-item .grid-meta .user-vignette {
|
||||
flex: 0 0 auto;
|
||||
margin-left: var(--space-1);
|
||||
}
|
||||
|
||||
/* Full-width thumbnail tile with a fixed aspect ratio — the image
|
||||
(`.file-thumb`, object-fit:cover) fills it edge-to-edge for a uniform,
|
||||
rich grid instead of a small letterboxed preview. Non-image files show
|
||||
their type icon centred on the placeholder fill. */
|
||||
.files-grid-view .file-item .file-icon {
|
||||
margin: auto;
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
aspect-ratio: 4 / 3;
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-bg-input);
|
||||
/* Hairline inner ring so light thumbnails don't bleed into the card. */
|
||||
box-shadow: inset 0 0 0 1px var(--color-border);
|
||||
margin: 0 0 var(--space-3);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
/* Type icon for non-thumbnail files (documents, etc.) — perfectly centered in
|
||||
the tile. `inset: 0` + `margin: auto` + a fixed size is the absolute-centering
|
||||
trick; the previous `top: auto` broke the vertical half, bottom-anchoring the
|
||||
icon so it sat low in the tile. */
|
||||
.files-grid-view .file-item .file-icon > i,
|
||||
.files-grid-view .file-item .file-icon > svg {
|
||||
top: 5px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
inset: 0;
|
||||
margin: auto;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
/* ── Drag ghost ──────────────────────────────────────────── */
|
||||
@@ -595,17 +865,17 @@
|
||||
flex-direction: column;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
border-radius: var(--radius-xl);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dragged-items .file-item {
|
||||
grid-template-columns: var(--dragged-files-list-columns);
|
||||
column-gap: 12px;
|
||||
column-gap: var(--space-3);
|
||||
align-items: center;
|
||||
background-color: var(--color-item);
|
||||
display: flex;
|
||||
padding: 4px;
|
||||
padding: var(--space-1);
|
||||
width: 360px;
|
||||
height: 46px;
|
||||
color: var(--color-text);
|
||||
@@ -633,17 +903,17 @@
|
||||
top: 0;
|
||||
right: 0;
|
||||
transform: translate(50%, -50%);
|
||||
background: var(--color-content-debug-bg);
|
||||
color: var(--color-content-debug-text);
|
||||
background: var(--color-notification-badge);
|
||||
color: var(--color-danger-text);
|
||||
border-radius: 50%;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--weight-bold);
|
||||
padding: var(--space-0-5);
|
||||
}
|
||||
|
||||
/* ── Swimlane group header ───────────────────────────────── */
|
||||
@@ -652,14 +922,14 @@
|
||||
grid wraps it naturally. */
|
||||
.resource-list__swimlane-header {
|
||||
grid-column: 1 / -1;
|
||||
padding: 6px 12px 4px;
|
||||
padding: var(--space-1-5) var(--space-3) var(--space-1);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
font-weight: var(--weight-semibold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--color-text-faint);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
margin-top: 8px;
|
||||
margin-top: var(--space-2);
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
}
|
||||
@@ -675,11 +945,11 @@
|
||||
.resource-list__swimlane-header--node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
padding: var(--space-1) var(--space-3);
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
font-size: inherit;
|
||||
font-weight: normal;
|
||||
font-weight: var(--weight-normal);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
@@ -692,13 +962,13 @@
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
overflow: visible;
|
||||
gap: 10px;
|
||||
gap: var(--space-2-5);
|
||||
}
|
||||
|
||||
/* Each group is a self-contained card */
|
||||
.files-list-view .resource-list__swimlane-group {
|
||||
background-color: var(--color-item);
|
||||
border-radius: 10px;
|
||||
border-radius: var(--radius-xl);
|
||||
box-shadow: 0 1px 3px var(--color-shadow-xs);
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -717,7 +987,7 @@
|
||||
grid-column: 1 / -1;
|
||||
display: grid;
|
||||
grid-template-columns: subgrid;
|
||||
gap: 20px;
|
||||
gap: var(--space-5);
|
||||
}
|
||||
|
||||
/* ── Section item modifiers ──────────────────────────────── */
|
||||
@@ -778,7 +1048,7 @@
|
||||
*/
|
||||
.files-list-view .file-item .path-cell {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
font-size: var(--text-sm);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -798,12 +1068,12 @@
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-lg);
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--color-text-subtle);
|
||||
font-size: 16px;
|
||||
font-size: var(--text-md);
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@@ -813,5 +1083,5 @@
|
||||
}
|
||||
|
||||
.files-grid-view .file-item .btn-action {
|
||||
margin-top: 4px;
|
||||
margin-top: var(--space-1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user