pdf-viewer: various visual and usability changes

- fix page nav buttons, smaller default pages, violet bg

  - Build toolbar after renderAllPages so it queries existing canvases;
    query canvases fresh in the scroll handler so page tracking works
    after re-render (zoom in/out).
  - Default data-pdf-scale 0.85 (JS fallback 0.8) so pages are a bit
    smaller by default.
  - Add dark transparent violet background (accent-secondary 22%) to
    .pdf-expanded, behind the pages.

  - expand to fill column top-to-bottom via CSS flex

    Use a flex chain (.tfe-files flex:1, .tfe-file-item:has(.pdf-expanded)
    flex:1) so the expanded PDF wrapper fills the right column from under
    the header to the bottom of the viewport, instead of viewport-based
    clamp math that didn't account for the header height.

  - fill right column, center pages, freeze column on expand

  The expanded viewer now stays inside the right column instead of
  growing the page: the column switches to overflow:hidden while the
  PDF panel scrolls internally. Pages are centered horizontally via
  flexbox align-items: center.

  - fix toolbar not full-width, hide sibling file items

  The toolbar was constrained by the parent's align-items:center; fixed
  with align-self:stretch. Hiding sibling .tfe-file-item elements when
  a PDF is expanded prevents them from rendering above the overlay.

  - disable overscroll on expanded view

  - use overscroll-behavior-y on container, none on toolbar

  - disable body overscroll via .pdf-viewer-open class

  - mobile full-viewport, iframe matches pdf height

  - mobile overlay above everything via position:fixed

  - opaque dark-violet bg on mobile overlay

  - tfe: add 'Open in new tab' link above website iframes

  - trim column padding when expanded for more vertical space

  - 5% zoom increments, subtle padding on expanded column

  - prevent horizontal overflow/overscroll
This commit is contained in:
Pontoporeia
2026-07-08 22:41:52 +02:00
parent 82d8eff512
commit 20873f94af
4 changed files with 99 additions and 18 deletions
+5
View File
@@ -29,3 +29,8 @@
- [x] Improve h1, h2, h3 size difference in base.css heading scale - [x] Improve h1, h2, h3 size difference in base.css heading scale
- [x] Rework TFE page desktop layout: two columns 35vw/1fr, left = author/title/meta/synopsis, right = files, media 100% width; columns scroll independently, content fits 100vw; no bottom padding on main; add &scrollbar=0 to PDF iframe URLs - [x] Rework TFE page desktop layout: two columns 35vw/1fr, left = author/title/meta/synopsis, right = files, media 100% width; columns scroll independently, content fits 100vw; no bottom padding on main; add &scrollbar=0 to PDF iframe URLs
- [x] 'Promoteur·ice ULB' remplacé par 'Promoteur·ice université' (changement juste l'étiquette) - [x] 'Promoteur·ice ULB' remplacé par 'Promoteur·ice université' (changement juste l'étiquette)
- [x] PDF viewer: fix page navigation buttons (prev/next) — build toolbar after canvases exist & query canvases fresh in scroll handler
- [x] PDF viewer: render pages smaller by default (0.85× scale)
- [x] PDF viewer: add dark transparent violet background behind pages when expanded
- [x] pdf-viewer: expand to fill right column (not whole page), center pages, freeze column on expand
- [x] pdf-viewer: fix toolbar width (align-self:stretch), hide sibling file items when expanded
+75 -6
View File
@@ -57,6 +57,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-m); gap: var(--space-m);
flex: 1;
min-height: 0;
} }
.tfe-file-item { .tfe-file-item {
@@ -70,6 +72,30 @@
overflow: visible; overflow: visible;
} }
.tfe-file-item:has(.pdf-expanded) {
flex: 1;
min-height: 0;
overflow: visible;
}
.tfe-file-item:has(.tfe-file-iframe) {
flex: 1;
min-height: 0;
overflow: visible;
}
/* When a PDF is expanded, freeze the right column and hide
sibling file items so they can't render above the expanded view.
Also trim column padding so the PDF fills more vertical space. */
.tfe-right-column:has(.pdf-expanded) {
overflow: hidden;
padding-top: var(--space-xs);
padding-bottom: var(--space-xs);
}
.tfe-right-column:has(.pdf-expanded) .tfe-file-item:not(:has(.pdf-expanded)) {
display: none;
}
.tfe-file-item img { .tfe-file-item img {
width: 100%; width: 100%;
height: auto; height: auto;
@@ -95,11 +121,29 @@
.tfe-file-iframe { .tfe-file-iframe {
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
height: clamp(300px, 80vh, 700px); height: 100%;
min-height: 400px;
display: block; display: block;
border: none; border: none;
} }
.tfe-website-link {
display: inline-flex;
align-items: center;
gap: var(--space-3xs);
padding: var(--space-2xs) var(--space-xs);
font-size: var(--step--1);
color: var(--accent-primary);
text-decoration: none;
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: var(--space-2xs);
}
.tfe-website-link:hover {
background: color-mix(in srgb, var(--accent-primary) 10%, var(--bg-primary));
}
.tfe-file-item figcaption { .tfe-file-item figcaption {
font-size: var(--step--2); font-size: var(--step--2);
color: var(--text-secondary); color: var(--text-secondary);
@@ -312,8 +356,8 @@
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
outline: none;
transition: height 0.35s ease, border-color 0.35s ease; transition: height 0.35s ease, border-color 0.35s ease;
outline: none;
} }
/* ── Collapsed state: thumbnail tile ────────── */ /* ── Collapsed state: thumbnail tile ────────── */
@@ -395,19 +439,30 @@
/* ── Expanded state: wrapper becomes scroll panel ── */ /* ── Expanded state: wrapper becomes scroll panel ── */
/* Disable root-level overscroll (rubberbanding) while a PDF is open. */
body.pdf-viewer-open {
overscroll-behavior: none;
}
.pdf-expanded { .pdf-expanded {
/* Fill viewport minus the column's vertical padding */ /* Fill the right column from top to bottom (under the header). */
max-height: calc(100vh - var(--space-xl) * 2); height: 100%;
height: clamp(400px, 80vh, calc(100vh - var(--space-xl) * 2)); min-height: 400px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;
overscroll-behavior-y: contain;
border: 2px solid var(--accent-primary); border: 2px solid var(--accent-primary);
display: block; display: flex;
flex-direction: column;
align-items: center;
background: color-mix(in srgb, var(--accent-secondary) 22%, transparent);
} }
/* ── Toolbar ────────────────────────────────── */ /* ── Toolbar ────────────────────────────────── */
.pdf-toolbar { .pdf-toolbar {
position: sticky; position: sticky;
align-self: stretch;
top: 0; top: 0;
z-index: 2; z-index: 2;
display: flex; display: flex;
@@ -420,6 +475,7 @@
flex-shrink: 0; flex-shrink: 0;
font-size: var(--step--1); font-size: var(--step--1);
user-select: none; user-select: none;
overscroll-behavior: none;
} }
.pdf-toolbar-btn { .pdf-toolbar-btn {
@@ -505,6 +561,19 @@
.tfe-title { .tfe-title {
font-size: var(--step-2); font-size: var(--step-2);
} }
/* PDF expands to full viewport height on mobile, above everything */
.pdf-expanded {
position: fixed;
inset: 0;
z-index: 100;
height: auto;
min-height: unset;
background: color-mix(in srgb, var(--accent-secondary) 92%, #000 8%);
}
.tfe-file-iframe {
height: 100dvh;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
+12 -11
View File
@@ -116,6 +116,7 @@
async function expand(container, pdfDoc) { async function expand(container, pdfDoc) {
container.classList.remove("pdf-collapsed"); container.classList.remove("pdf-collapsed");
container.classList.add("pdf-expanded"); container.classList.add("pdf-expanded");
document.body.classList.add("pdf-viewer-open");
container.addEventListener("contextmenu", (e) => e.preventDefault()); container.addEventListener("contextmenu", (e) => e.preventDefault());
container.addEventListener("keydown", (e) => { container.addEventListener("keydown", (e) => {
@@ -132,10 +133,10 @@
if (oldThumb) oldThumb.remove(); if (oldThumb) oldThumb.remove();
// Build toolbar // Build toolbar
buildToolbar(container, pdfDoc); // Render pages directly into the wrapper, then build the toolbar
// (toolbar queries canvases, so it must run after they exist).
// Render pages directly into the wrapper
await renderAllPages(container, pdfDoc); await renderAllPages(container, pdfDoc);
buildToolbar(container, pdfDoc);
container.scrollIntoView({ behavior: "smooth", block: "nearest" }); container.scrollIntoView({ behavior: "smooth", block: "nearest" });
} }
@@ -143,6 +144,7 @@
function collapse(container) { function collapse(container) {
container.classList.remove("pdf-expanded"); container.classList.remove("pdf-expanded");
container.classList.add("pdf-collapsed"); container.classList.add("pdf-collapsed");
document.body.classList.remove("pdf-viewer-open");
const oldToolbar = container.querySelector(".pdf-toolbar"); const oldToolbar = container.querySelector(".pdf-toolbar");
if (oldToolbar) oldToolbar.remove(); if (oldToolbar) oldToolbar.remove();
@@ -178,7 +180,7 @@
const rendering = new Set(); const rendering = new Set();
const resolveScale = () => { const resolveScale = () => {
const userScale = parseFloat(container.dataset.pdfScale ?? "1"); const userScale = parseFloat(container.dataset.pdfScale ?? "0.8");
const containerWidth = container.clientWidth - 16; const containerWidth = container.clientWidth - 16;
if (containerWidth <= 0) return userScale; if (containerWidth <= 0) return userScale;
return (containerWidth / firstViewport.width) * userScale; return (containerWidth / firstViewport.width) * userScale;
@@ -236,7 +238,7 @@
toolbar.className = "pdf-toolbar"; toolbar.className = "pdf-toolbar";
let currentPage = 1; let currentPage = 1;
let currentScale = parseFloat(container.dataset.pdfScale ?? "1"); let currentScale = parseFloat(container.dataset.pdfScale ?? "0.8");
const numPages = pdfDoc.numPages; const numPages = pdfDoc.numPages;
const updateUI = () => { const updateUI = () => {
@@ -261,14 +263,14 @@
const pageLabel = mkLabel(""); const pageLabel = mkLabel("");
const zoomOutBtn = mkBtn("", "Zoom arrière", () => { const zoomOutBtn = mkBtn("", "Zoom arrière", () => {
currentScale = Math.max(0.5, currentScale - 0.2); currentScale = Math.max(0.5, currentScale - 0.05);
container.dataset.pdfScale = String(currentScale); container.dataset.pdfScale = String(currentScale);
reRender(container, pdfDoc); reRender(container, pdfDoc);
updateUI(); updateUI();
}); });
const zoomInBtn = mkBtn("+", "Zoom avant", () => { const zoomInBtn = mkBtn("+", "Zoom avant", () => {
currentScale = Math.min(3.0, currentScale + 0.2); currentScale = Math.min(3.0, currentScale + 0.05);
container.dataset.pdfScale = String(currentScale); container.dataset.pdfScale = String(currentScale);
reRender(container, pdfDoc); reRender(container, pdfDoc);
updateUI(); updateUI();
@@ -296,9 +298,8 @@
updateUI(); updateUI();
const canvases = container.querySelectorAll(".pdf-canvas");
const onScroll = () => { const onScroll = () => {
const canvases = container.querySelectorAll(".pdf-canvas");
const mid = container.scrollTop + container.clientHeight * 0.4; const mid = container.scrollTop + container.clientHeight * 0.4;
let best = 1; let best = 1;
let bestDist = Infinity; let bestDist = Infinity;
@@ -352,14 +353,14 @@
function reRender(container, pdfDoc) { function reRender(container, pdfDoc) {
if (container._onScroll) { if (container._onScroll) {
container.removeEventListener("scroll", container._onScroll); container.removeEventListener("scroll", container._onScroll);
container._onScroll = null;
} }
container.querySelectorAll(".pdf-canvas").forEach((c) => { container.querySelectorAll(".pdf-canvas").forEach((c) => {
c.remove(); c.remove();
}); });
const oldToolbar = container.querySelector(".pdf-toolbar"); const oldToolbar = container.querySelector(".pdf-toolbar");
if (oldToolbar) oldToolbar.remove(); if (oldToolbar) oldToolbar.remove();
buildToolbar(container, pdfDoc); renderAllPages(container, pdfDoc).then(() => buildToolbar(container, pdfDoc));
renderAllPages(container, pdfDoc);
} }
// ── Bootstrap ────────────────────────────────────────────────── // ── Bootstrap ──────────────────────────────────────────────────
+7 -1
View File
@@ -386,10 +386,16 @@
?> ?>
<div class="pdf-embed-wrapper" <div class="pdf-embed-wrapper"
data-pdf-url="<?= $mediaUrl ?>" data-pdf-url="<?= $mediaUrl ?>"
data-pdf-scale="1" data-pdf-scale="0.85"
data-pdf-label="<?= htmlspecialchars($_pdfLabel) ?>" data-pdf-label="<?= htmlspecialchars($_pdfLabel) ?>"
tabindex="-1"></div> tabindex="-1"></div>
<?php elseif ($isWebsite): ?> <?php elseif ($isWebsite): ?>
<a href="<?= $mediaUrl ?>"
class="tfe-website-link"
target="_blank"
rel="noopener noreferrer">
Ouvrir dans un nouvel onglet ↗
</a>
<iframe src="<?= $mediaUrl ?>" <iframe src="<?= $mediaUrl ?>"
class="tfe-file-iframe" class="tfe-file-iframe"
title="<?= $fileName ?>" title="<?= $fileName ?>"