diff --git a/website/static/website/css/publications.css b/website/static/website/css/publications.css index a65b651c..4483421c 100644 --- a/website/static/website/css/publications.css +++ b/website/static/website/css/publications.css @@ -694,9 +694,14 @@ and style its contents. */ .artifact-preview-popover { - /* Compact card. Talk slides are landscape, so width drives the size; keep it - modest. Posters (often portrait) are bounded by the image max-height. */ - max-width: 272px; + /* The card hugs its image: thumbnailPreview.js sizes the image to a fixed box + and the card shrink-wraps it. base.css sets a global `.popover { width: 50vw; + min-width: 300px }`, so we must reset both here or the card can't shrink to + the image (that was the whitespace beside portrait posters). max-width is a + generous ceiling so a full-width landscape image (+ padding) is never clipped. */ + width: auto; + min-width: 0; + max-width: 300px; /* Bootstrap sets .popover { display: none }; the JS reveals it. Keep it above page chrome but below modals. */ z-index: 1060; @@ -713,12 +718,16 @@ .artifact-preview-image { display: block; - width: 100%; - height: auto; - /* Never let a tall poster run past the viewport. */ - max-height: 45vh; - object-fit: contain; - border-radius: var(--border-radius-sm, 3px); + /* Height-capped (not width-driven), so portrait posters stay compact instead + of running tall; landscape ones still fill the card width. width/height are + left to intrinsic aspect, bounded by the maxes. Narrower (portrait) images + are centered. */ + margin: 0 auto; + max-width: 100%; + max-height: 240px; + /* Rounded rect + subtle outline so the thumbnail reads as a framed preview. */ + border-radius: var(--border-radius-md, 8px); + border: 1px solid var(--color-border, #d0d0d0); } .artifact-preview-actions { @@ -750,9 +759,10 @@ outline-offset: var(--focus-ring-offset, 2px); } -/* File size, pushed to the trailing edge of each action row. */ +/* File size sits right after its label (not pushed to the trailing edge), so a + lone action doesn't leave an awkward gap when the card is as wide as the + poster image. */ .artifact-preview-size { - margin-left: auto; color: var(--color-text-secondary, #666); font-size: 0.85em; } diff --git a/website/static/website/js/thumbnailPreview.js b/website/static/website/js/thumbnailPreview.js index 0352e047..0210c112 100644 --- a/website/static/website/js/thumbnailPreview.js +++ b/website/static/website/js/thumbnailPreview.js @@ -59,6 +59,12 @@ const ARROW_HALF = 11; /** Delay (ms) before a hover-opened card hides, so the pointer can reach it. */ const HIDE_DELAY = 140; + /** Box the preview image is fit into (px). The image is sized explicitly in + * JS to these bounds so the card hugs it — CSS shrink-to-fit uses the image's + * intrinsic (600px-wide thumbnail) size and can't account for the height cap, + * which is what left whitespace beside portrait thumbnails. */ + const PREVIEW_MAX_W = 260; + const PREVIEW_MAX_H = 240; /* =========================================================================== STATE @@ -126,6 +132,35 @@ content.appendChild(template.content.cloneNode(true)); popover.appendChild(content); + // Size the preview image explicitly (fit within PREVIEW_MAX_W x + // PREVIEW_MAX_H, preserving aspect) so the card hugs it. Without a definite + // width the card sizes to the thumbnail's intrinsic 600px (capped), then the + // height cap shrinks the image — leaving whitespace beside portrait posters. + const img = popover.querySelector('.artifact-preview-image'); + if (img) { + const sizeToImage = function () { + const nw = img.naturalWidth; + const nh = img.naturalHeight; + if (!nw || !nh) { + return; + } + const scale = Math.min(PREVIEW_MAX_W / nw, PREVIEW_MAX_H / nh, 1); + img.style.width = Math.round(nw * scale) + 'px'; + img.style.height = Math.round(nh * scale) + 'px'; + // The card's size just changed; re-anchor it to the trigger. + if (activePopover === popover && activeTrigger === trigger) { + positionPopover(trigger, popover); + } + }; + // Cached images are already complete (no 'load' will fire); size now so the + // popover is measured correctly when open() positions it. + if (img.complete && img.naturalWidth) { + sizeToImage(); + } else { + img.addEventListener('load', sizeToImage); + } + } + return popover; }