diff --git a/TODO.md b/TODO.md index b6bfcf7..eda53f9 100644 --- a/TODO.md +++ b/TODO.md @@ -29,3 +29,8 @@ - [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] '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 diff --git a/app/public/assets/css/tfe.css b/app/public/assets/css/tfe.css index aca766e..bf81294 100644 --- a/app/public/assets/css/tfe.css +++ b/app/public/assets/css/tfe.css @@ -57,6 +57,8 @@ display: flex; flex-direction: column; gap: var(--space-m); + flex: 1; + min-height: 0; } .tfe-file-item { @@ -70,6 +72,30 @@ 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 { width: 100%; height: auto; @@ -95,11 +121,29 @@ .tfe-file-iframe { width: 100%; max-width: 100%; - height: clamp(300px, 80vh, 700px); + height: 100%; + min-height: 400px; display: block; 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 { font-size: var(--step--2); color: var(--text-secondary); @@ -312,8 +356,8 @@ background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); - outline: none; transition: height 0.35s ease, border-color 0.35s ease; + outline: none; } /* ── Collapsed state: thumbnail tile ────────── */ @@ -395,19 +439,30 @@ /* ── 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 { - /* Fill viewport minus the column's vertical padding */ - max-height: calc(100vh - var(--space-xl) * 2); - height: clamp(400px, 80vh, calc(100vh - var(--space-xl) * 2)); + /* Fill the right column from top to bottom (under the header). */ + height: 100%; + min-height: 400px; overflow-y: auto; + overflow-x: hidden; + overscroll-behavior-y: contain; 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 ────────────────────────────────── */ .pdf-toolbar { position: sticky; + align-self: stretch; top: 0; z-index: 2; display: flex; @@ -420,6 +475,7 @@ flex-shrink: 0; font-size: var(--step--1); user-select: none; + overscroll-behavior: none; } .pdf-toolbar-btn { @@ -505,6 +561,19 @@ .tfe-title { 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) { diff --git a/app/public/assets/js/app/pdf-viewer.js b/app/public/assets/js/app/pdf-viewer.js index b2fd6af..2e6b71f 100644 --- a/app/public/assets/js/app/pdf-viewer.js +++ b/app/public/assets/js/app/pdf-viewer.js @@ -116,6 +116,7 @@ async function expand(container, pdfDoc) { container.classList.remove("pdf-collapsed"); container.classList.add("pdf-expanded"); + document.body.classList.add("pdf-viewer-open"); container.addEventListener("contextmenu", (e) => e.preventDefault()); container.addEventListener("keydown", (e) => { @@ -132,10 +133,10 @@ if (oldThumb) oldThumb.remove(); // Build toolbar - buildToolbar(container, pdfDoc); - - // Render pages directly into the wrapper + // Render pages directly into the wrapper, then build the toolbar + // (toolbar queries canvases, so it must run after they exist). await renderAllPages(container, pdfDoc); + buildToolbar(container, pdfDoc); container.scrollIntoView({ behavior: "smooth", block: "nearest" }); } @@ -143,6 +144,7 @@ function collapse(container) { container.classList.remove("pdf-expanded"); container.classList.add("pdf-collapsed"); + document.body.classList.remove("pdf-viewer-open"); const oldToolbar = container.querySelector(".pdf-toolbar"); if (oldToolbar) oldToolbar.remove(); @@ -178,7 +180,7 @@ const rendering = new Set(); const resolveScale = () => { - const userScale = parseFloat(container.dataset.pdfScale ?? "1"); + const userScale = parseFloat(container.dataset.pdfScale ?? "0.8"); const containerWidth = container.clientWidth - 16; if (containerWidth <= 0) return userScale; return (containerWidth / firstViewport.width) * userScale; @@ -236,7 +238,7 @@ toolbar.className = "pdf-toolbar"; let currentPage = 1; - let currentScale = parseFloat(container.dataset.pdfScale ?? "1"); + let currentScale = parseFloat(container.dataset.pdfScale ?? "0.8"); const numPages = pdfDoc.numPages; const updateUI = () => { @@ -261,14 +263,14 @@ const pageLabel = mkLabel(""); 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); reRender(container, pdfDoc); updateUI(); }); 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); reRender(container, pdfDoc); updateUI(); @@ -296,9 +298,8 @@ updateUI(); - const canvases = container.querySelectorAll(".pdf-canvas"); - const onScroll = () => { + const canvases = container.querySelectorAll(".pdf-canvas"); const mid = container.scrollTop + container.clientHeight * 0.4; let best = 1; let bestDist = Infinity; @@ -352,14 +353,14 @@ function reRender(container, pdfDoc) { if (container._onScroll) { container.removeEventListener("scroll", container._onScroll); + container._onScroll = null; } container.querySelectorAll(".pdf-canvas").forEach((c) => { c.remove(); }); const oldToolbar = container.querySelector(".pdf-toolbar"); if (oldToolbar) oldToolbar.remove(); - buildToolbar(container, pdfDoc); - renderAllPages(container, pdfDoc); + renderAllPages(container, pdfDoc).then(() => buildToolbar(container, pdfDoc)); } // ── Bootstrap ────────────────────────────────────────────────── diff --git a/app/templates/public/tfe.php b/app/templates/public/tfe.php index 9b09f0f..4f7c226 100644 --- a/app/templates/public/tfe.php +++ b/app/templates/public/tfe.php @@ -386,10 +386,16 @@ ?>
+ + Ouvrir dans un nouvel onglet ↗ +