diff --git a/web/css/resolver-main.css b/web/css/resolver-main.css
index 6610302..6ab99be 100644
--- a/web/css/resolver-main.css
+++ b/web/css/resolver-main.css
@@ -3785,25 +3785,47 @@
}
.mr-model-details-gallery {
position: relative;
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- gap: 18px;
- align-items: stretch;
- min-height: 420px;
- height: min(640px, calc(100vh - 190px));
+ min-height: 440px;
+ height: min(660px, calc(100vh - 190px));
margin-top: 2px;
+ overflow: hidden;
}
.mr-model-details-gallery.is-single {
- grid-template-columns: minmax(280px, 520px);
- justify-content: center;
+ max-width: 560px;
+ margin-left: auto;
+ margin-right: auto;
+}
+.mr-model-details-gallery-strip {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ width: 100%;
+ height: 100%;
+ min-width: 0;
+ overflow-x: auto;
+ overflow-y: hidden;
+ scroll-behavior: smooth;
+ scrollbar-width: none;
+ cursor: grab;
+ user-select: none;
+ -webkit-user-select: none;
+ padding: 0 2px;
+}
+.mr-model-details-gallery-strip::-webkit-scrollbar {
+ display: none;
+}
+.mr-model-details-gallery-strip.is-dragging {
+ cursor: grabbing;
+ scroll-behavior: auto;
}
.mr-model-details-image {
position: relative;
display: flex;
align-items: center;
justify-content: center;
- width: 100%;
- height: 100%;
+ flex: 0 0 min(var(--mr-image-width, 330px), 82vw);
+ width: min(var(--mr-image-width, 330px), 82vw);
+ height: min(var(--mr-image-height, 430px), 100%);
min-width: 0;
min-height: 0;
margin: 0;
@@ -3813,6 +3835,10 @@
background: #101114;
overflow: hidden;
cursor: zoom-in;
+ aspect-ratio: var(--mr-image-ratio, 0.72);
+}
+.mr-model-details-gallery-strip.is-dragging .mr-model-details-image {
+ cursor: grabbing;
}
.mr-model-details-image img {
display: block;
@@ -4015,12 +4041,12 @@
font-size: 18px;
}
.mr-model-details-gallery {
- grid-template-columns: 1fr;
min-height: 360px;
height: min(560px, calc(100vh - 170px));
}
- .mr-model-details-gallery .mr-model-details-image + .mr-model-details-image {
- display: none;
+ .mr-model-details-image {
+ flex-basis: min(var(--mr-image-width, 330px), 88vw);
+ width: min(var(--mr-image-width, 330px), 88vw);
}
.mr-model-details-file {
grid-template-columns: 1fr;
diff --git a/web/resolver/views/model_info_methods.js b/web/resolver/views/model_info_methods.js
index 4e7d872..c535ae4 100644
--- a/web/resolver/views/model_info_methods.js
+++ b/web/resolver/views/model_info_methods.js
@@ -1191,13 +1191,6 @@ export const modelInfoMethods = {
const selectedVersionId = String(data.selected_version?.id || data.version_id || versions[0]?.id || '');
const selectedVersion = versions.find(version => String(version.id) === selectedVersionId) || data.selected_version || versions[0] || {};
const images = (Array.isArray(selectedVersion.images) && selectedVersion.images.length ? selectedVersion.images : data.images || []).filter(img => img?.url).slice(0, 12);
- const imageOffset = Math.max(0, Math.min(Number(data._detailsImageOffset || 0), Math.max(0, images.length - 1)));
- const visibleImages = images.length <= 2
- ? images.map((image, index) => ({ image, index }))
- : [0, 1].map(step => {
- const index = (imageOffset + step) % images.length;
- return { image: images[index], index };
- });
const stats = data.stats || {};
const versionStats = selectedVersion.stats || {};
const tags = Array.isArray(data.tags) ? data.tags.slice(0, 12) : [];
@@ -1237,15 +1230,17 @@ export const modelInfoMethods = {
`).join('')}
-
- ${images.length > 2 ? `
` : ''}
- ${images.length ? visibleImages.map(({ image, index }) => `
-
- `).join('') : '
No example images available.
'}
- ${images.length > 2 ? `
` : ''}
+
+ ${images.length > 1 ? `
` : ''}
+
+ ${images.length ? images.map((image, index) => `
+
+ `).join('') : '
No example images available.
'}
+
+ ${images.length > 1 ? `
` : ''}
About
@@ -1336,6 +1331,28 @@ export const modelInfoMethods = {
`;
},
+ getSourceModelImageSizingStyle(image = {}) {
+ const width = Number(image.width || image.metadata?.width || 0);
+ const height = Number(image.height || image.metadata?.height || 0);
+ const ratio = width > 0 && height > 0 ? width / height : 0.72;
+ const clampedRatio = Math.max(0.48, Math.min(1.85, ratio));
+ const preferredHeight = Math.max(
+ 320,
+ Math.min(
+ 560,
+ Math.round(430 + Math.max(0, 1 - clampedRatio) * 150 - Math.max(0, clampedRatio - 1) * 70)
+ )
+ );
+ let preferredWidth = Math.round(preferredHeight * clampedRatio);
+ if (preferredWidth < 220) {
+ preferredWidth = 220;
+ } else if (preferredWidth > 640) {
+ preferredWidth = 640;
+ }
+ const adjustedHeight = Math.round(preferredWidth / clampedRatio);
+ return `--mr-image-ratio:${clampedRatio.toFixed(3)}; --mr-image-width:${preferredWidth}px; --mr-image-height:${adjustedHeight}px;`;
+ },
+
bindSourceModelDetailsEvents(details) {
if (!details) return;
if (details.dataset.modelDetailsBound === 'true') return;
@@ -1358,7 +1375,6 @@ export const modelInfoMethods = {
const data = details._detailsData;
if (data) {
data.selected_version = (data.versions || []).find(version => String(version.id) === details._selectedVersionId) || data.selected_version;
- data._detailsImageOffset = 0;
details.innerHTML = this.renderSourceModelDetails(data, details._sourceModel || {});
this.bindTooltips(details);
}
@@ -1367,21 +1383,21 @@ export const modelInfoMethods = {
const galleryNav = event.target.closest('.mr-model-details-gallery-nav');
if (galleryNav && details.contains(galleryNav)) {
- const data = details._detailsData || {};
- const version = data.selected_version || {};
- const images = (Array.isArray(version.images) && version.images.length ? version.images : data.images || []).filter(img => img?.url);
- if (images.length <= 2) return;
-
+ const strip = galleryNav.closest('.mr-model-details-gallery')?.querySelector('.mr-model-details-gallery-strip');
+ if (!strip) return;
const direction = Number(galleryNav.dataset.galleryDirection || 1);
- const current = Number(data._detailsImageOffset || 0);
- data._detailsImageOffset = (current + direction + images.length) % images.length;
- details.innerHTML = this.renderSourceModelDetails(data, details._sourceModel || {});
- this.bindTooltips(details);
+ strip.scrollBy({
+ left: direction * Math.max(260, Math.round(strip.clientWidth * 0.82)),
+ behavior: 'smooth'
+ });
return;
}
const imageBtn = event.target.closest('.mr-model-details-image');
if (imageBtn && details.contains(imageBtn)) {
+ if (details._detailsGalleryDraggedUntil && Date.now() < details._detailsGalleryDraggedUntil) {
+ return;
+ }
const data = details._detailsData || {};
const version = data.selected_version || {};
const images = (Array.isArray(version.images) && version.images.length ? version.images : data.images || []).filter(img => img?.url);
@@ -1402,6 +1418,40 @@ export const modelInfoMethods = {
}
}
});
+
+ details.addEventListener('pointerdown', (event) => {
+ const strip = event.target.closest?.('.mr-model-details-gallery-strip');
+ if (!strip || !details.contains(strip) || event.button > 0) return;
+
+ const startX = event.clientX;
+ const startScrollLeft = strip.scrollLeft;
+ let moved = false;
+ strip.classList.add('is-dragging');
+ strip.setPointerCapture?.(event.pointerId);
+
+ const onPointerMove = (moveEvent) => {
+ const delta = moveEvent.clientX - startX;
+ if (Math.abs(delta) > 4) {
+ moved = true;
+ details._detailsGalleryDraggedUntil = Date.now() + 250;
+ }
+ strip.scrollLeft = startScrollLeft - delta;
+ };
+
+ const finishDrag = () => {
+ strip.classList.remove('is-dragging');
+ strip.removeEventListener('pointermove', onPointerMove);
+ strip.removeEventListener('pointerup', finishDrag);
+ strip.removeEventListener('pointercancel', finishDrag);
+ if (moved) {
+ details._detailsGalleryDraggedUntil = Date.now() + 250;
+ }
+ };
+
+ strip.addEventListener('pointermove', onPointerMove);
+ strip.addEventListener('pointerup', finishDrag, { once: true });
+ strip.addEventListener('pointercancel', finishDrag, { once: true });
+ });
},
applySourceModelDetailsSelection(selection = {}, contextModel = {}) {