feat: visionneuse d'images - navigation fluide, image ajustee au cadre, fleches laterales et pellicule persistante #111
CI / lint (push) Successful in 2m2s
CI / security (push) Successful in 1m23s
CI / test (push) Successful in 4m23s
CI / build (push) Successful in 1m19s
CI / e2e (push) Successful in 13m27s

This commit is contained in:
2026-09-23 14:37:41 -04:00
parent 8264e7ffae
commit b926f01b85
16 changed files with 460 additions and 70 deletions
+79
View File
@@ -9640,10 +9640,17 @@ body.desktop-mode .editor-container {
}
/* #108-D — dedicated image viewer: zoom, pan, filmstrip, metadata, lightbox */
/* #111 — the viewer owns the whole content box (no page scrolling): the image
is always resized to the available frame and the toolbar/filmstrip stay put. */
.content-area:has(> .image-viewer-container) {
padding: 0;
overflow: hidden;
}
.image-viewer-container {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
outline: none;
}
.image-toolbar {
@@ -9673,10 +9680,25 @@ body.desktop-mode .editor-container {
min-width: 46px;
text-align: center;
}
.image-counter {
font-variant-numeric: tabular-nums;
font-size: 0.75rem;
color: var(--text-secondary);
padding: 0 6px;
white-space: nowrap;
}
.image-counter[hidden] {
display: none;
}
.image-viewer-body {
flex: 1;
display: flex;
/* #111 — stretch (not center) so .image-stage is capped to the body height and
the image can actually resolve max-height:100% against a definite frame. */
align-items: stretch;
padding: 0;
min-height: 0;
overflow: hidden;
}
.image-stage {
flex: 1;
@@ -9686,6 +9708,9 @@ body.desktop-mode .editor-container {
align-items: center;
justify-content: center;
min-width: 0;
min-height: 0;
padding: 16px;
box-sizing: border-box;
background: var(--surface2);
cursor: grab;
touch-action: none;
@@ -9694,6 +9719,8 @@ body.desktop-mode .editor-container {
cursor: grabbing;
}
.image-main {
width: auto;
height: auto;
max-width: 100%;
max-height: 100%;
object-fit: contain;
@@ -9706,6 +9733,58 @@ body.desktop-mode .editor-container {
color: var(--text-secondary);
font-size: 0.9rem;
}
/* #111 — translucent overlay arrows along the frame edges (revealed on hover) */
.image-nav-arrow {
position: absolute;
top: 0;
bottom: 0;
width: clamp(44px, 6vw, 84px);
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
background: transparent;
color: #fff;
opacity: 0;
cursor: pointer;
z-index: 6;
transition: opacity 160ms ease, background 160ms ease;
}
.image-nav-arrow[hidden] {
display: none;
}
.image-nav-arrow-prev {
left: 0;
}
.image-nav-arrow-next {
right: 0;
}
.image-nav-arrow-prev:hover,
.image-nav-arrow-prev:focus-visible {
background: linear-gradient(to right, rgba(0, 0, 0, 0.42), transparent);
}
.image-nav-arrow-next:hover,
.image-nav-arrow-next:focus-visible {
background: linear-gradient(to left, rgba(0, 0, 0, 0.42), transparent);
}
.image-stage:hover .image-nav-arrow {
opacity: 0.4;
}
.image-stage .image-nav-arrow:hover,
.image-stage .image-nav-arrow:focus-visible {
opacity: 1;
outline: none;
}
.image-nav-arrow .icon {
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.85));
}
@media (hover: none) {
.image-nav-arrow {
opacity: 0.55;
}
}
.image-nav-strip {
display: flex;
gap: 6px;