From 7dbfdab40eff939d012b1d07d4be970cb4b3bd75 Mon Sep 17 00:00:00 2001 From: Gregor Klevze Date: Sun, 15 Feb 2026 16:49:15 +0100 Subject: [PATCH] gallery --- public/js/legacy-gallery-init.js | 296 +++++++++++++++--- public/legacy/js/legacy-gallery-init.js | 296 +++++++++++++++--- resources/views/blank.blade.php | 2 +- resources/views/layouts/nova.blade.php | 28 ++ .../views/legacy/_artwork_card.blade.php | 103 +++--- resources/views/legacy/browse.blade.php | 48 ++- .../views/legacy/category-slug.blade.php | 65 ++-- resources/views/legacy/content-type.blade.php | 53 +++- resources/views/legacy/photography.blade.php | 2 +- 9 files changed, 720 insertions(+), 173 deletions(-) diff --git a/public/js/legacy-gallery-init.js b/public/js/legacy-gallery-init.js index 7568f75b..fb3e761b 100644 --- a/public/js/legacy-gallery-init.js +++ b/public/js/legacy-gallery-init.js @@ -1,60 +1,280 @@ -// Modern gallery init: wait for images to decode, then init Isotope/Masonry -(function(){ +// Nova gallery progressive enhancement: +// - Masonry-like responsive layout (CSS grid row spans) +// - Infinite scroll on top of server pagination (SEO-safe fallback) +// - Skeleton placeholders while loading +// - Virtualized rendering hints for long feeds +(function () { 'use strict'; + var MAX_DOM_CARDS_FOR_VIRTUAL_HINT = 220; + var LOAD_TRIGGER_MARGIN = '900px'; + + function toArray(list) { + return Array.prototype.slice.call(list || []); + } + + function queryNextPageUrl(root) { + var pagination = root.querySelector('[data-gallery-pagination]'); + if (!pagination) return null; + var next = pagination.querySelector('a[rel="next"], a[aria-label="Next »"], a[aria-label="Next"], a[aria-label="pagination.next"]'); + return next ? next.getAttribute('href') : null; + } + + function setSkeleton(root, active, count) { + var box = root.querySelector('[data-gallery-skeleton]'); + if (!box) return; + box.innerHTML = ''; + if (!active) { + box.classList.remove('is-loading'); + return; + } + box.classList.add('is-loading'); + var total = Math.max(4, count || 8); + for (var i = 0; i < total; i += 1) { + var sk = document.createElement('div'); + sk.className = 'nova-skeleton-card'; + box.appendChild(sk); + } + } + function waitForImages(container) { - var imgs = Array.prototype.slice.call(container.querySelectorAll('img')); - var promises = imgs.map(function(img){ + var imgs = toArray(container.querySelectorAll('img')); + var promises = imgs.map(function (img) { try { - if (!img.complete) { - return img.decode().catch(function(){ /* ignore */ }); - } - // already complete: still attempt decode if supported - return img.decode ? img.decode().catch(function(){}) : Promise.resolve(); + if (img.decode) return img.decode().catch(function () { return null; }); } catch (e) { - return Promise.resolve(); + return null; } + return null; }); return Promise.all(promises); } - async function initGallery() { - var grid = document.querySelector('.gallery-grid'); + function applyMasonry(root) { + var grid = root.querySelector('[data-gallery-grid]'); + if (!grid) return; + var rowSize = 8; + var gap = 16; + + var cards = toArray(grid.querySelectorAll('.nova-card')); + cards.forEach(function (card) { + var media = card.querySelector('.nova-card-media') || card; + var height = media.getBoundingClientRect().height || 200; + // Lower the minimum forced span to avoid large empty space at the bottom. + // Previously this used a very large minimum (18) which reserved too many rows. + var minSpan = 1; + var span = Math.max(minSpan, Math.ceil((height + gap) / (rowSize + gap))); + card.style.gridRowEnd = 'span ' + span; + }); + } + + function applyVirtualizationHints(root) { + var grid = root.querySelector('[data-gallery-grid]'); + if (!grid) return; + var cards = toArray(grid.querySelectorAll('.nova-card')); + if (cards.length <= MAX_DOM_CARDS_FOR_VIRTUAL_HINT) { + cards.forEach(function (card) { + card.style.contentVisibility = ''; + card.style.containIntrinsicSize = ''; + }); + return; + } + + var viewportTop = window.scrollY; + var viewportBottom = viewportTop + window.innerHeight; + + cards.forEach(function (card) { + var rect = card.getBoundingClientRect(); + var top = rect.top + viewportTop; + var bottom = rect.bottom + viewportTop; + var farAbove = bottom < viewportTop - 1400; + var farBelow = top > viewportBottom + 2600; + + if (farAbove || farBelow) { + var h = Math.max(160, rect.height || 220); + card.style.contentVisibility = 'auto'; + card.style.containIntrinsicSize = Math.round(h) + 'px'; + } else { + card.style.contentVisibility = ''; + card.style.containIntrinsicSize = ''; + } + }); + } + + function extractAndAppendCards(root, html) { + var parser = new DOMParser(); + var doc = parser.parseFromString(html, 'text/html'); + var incomingGrid = doc.querySelector('[data-gallery-grid]'); + if (!incomingGrid) return { appended: 0, nextUrl: null }; + + var targetGrid = root.querySelector('[data-gallery-grid]'); + if (!targetGrid) return { appended: 0, nextUrl: null }; + + var cards = toArray(incomingGrid.querySelectorAll('.nova-card')); + cards.forEach(function (card) { + targetGrid.appendChild(card); + }); + + var incomingPagination = doc.querySelector('[data-gallery-pagination]'); + var currentPagination = root.querySelector('[data-gallery-pagination]'); + if (incomingPagination && currentPagination) { + currentPagination.innerHTML = incomingPagination.innerHTML; + } + + return { + appended: cards.length, + nextUrl: queryNextPageUrl(root) + }; + } + + function initOne(root) { + var grid = root.querySelector('[data-gallery-grid]'); if (!grid) return; - try { - await waitForImages(grid); - } catch (e) { - // continue even on failures + // loader overlay element (created lazily) + var loader = null; + function ensureLoader() { + if (loader) return loader; + loader = document.createElement('div'); + loader.className = 'nova-loader-overlay'; + var inner = document.createElement('div'); + inner.className = 'nova-loader-spinner'; + loader.appendChild(inner); + // place loader as child of root so it overlays grid area + loader.style.display = 'none'; + root.style.position = root.style.position || ''; + root.appendChild(loader); + return loader; } - // Prefer Isotope (legacy code included it). Fall back to Masonry. - if (window.Isotope) { - // eslint-disable-next-line no-unused-vars - var iso = new Isotope(grid, { - itemSelector: '.photo_frame', - layoutMode: 'masonry', - masonry: { columnWidth: '.photo_frame', gutter: 12 }, - percentPosition: true, - fitWidth: true - }); - } else if (window.Masonry) { - // eslint-disable-next-line no-unused-vars - var m = new Masonry(grid, { - itemSelector: '.photo_frame', - columnWidth: '.photo_frame', - percentPosition: true, - gutter: 12, - fitWidth: true + function showLoader() { var l = ensureLoader(); l.style.display = 'flex'; } + function hideLoader() { if (loader) loader.style.display = 'none'; } + + root.classList.add('is-enhanced'); + + var state = { + loading: false, + nextUrl: queryNextPageUrl(root), + done: false + }; + + function relayout() { + waitForImages(grid).then(function () { + applyMasonry(root); + applyVirtualizationHints(root); }); } + + var rafId = null; + function onScrollOrResize() { + if (rafId) return; + rafId = window.requestAnimationFrame(function () { + rafId = null; + applyVirtualizationHints(root); + }); + } + + async function loadNextPage() { + if (state.loading || state.done || !state.nextUrl) return; + state.loading = true; + + showLoader(); + + var sampleCards = toArray(grid.querySelectorAll('.nova-card')); + var skeletonCount = Math.min(12, Math.max(4, sampleCards.length ? sampleCards.slice(-4).length * 2 : 8)); + setSkeleton(root, true, skeletonCount); + + try { + var response = await window.fetch(state.nextUrl, { + credentials: 'same-origin', + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }); + if (!response.ok) throw new Error('Failed to load page'); + var html = await response.text(); + + var result = extractAndAppendCards(root, html); + state.nextUrl = result.nextUrl; + if (!state.nextUrl || result.appended === 0) { + state.done = true; + } + + // Animate appended cards + var appendedCards = toArray(grid.querySelectorAll('.nova-card')).slice(-result.appended); + appendedCards.forEach(function (c) { + c.classList.add('nova-card-enter'); + // trigger reflow then add active class + requestAnimationFrame(function () { c.classList.add('nova-card-enter-active'); }); + c.addEventListener('transitionend', function te() { c.classList.remove('nova-card-enter', 'nova-card-enter-active'); c.removeEventListener('transitionend', te); }); + }); + + relayout(); + // After new cards appended, move the trigger to remain one-row-before-last. + placeTrigger(); + } catch (e) { + state.done = true; + } finally { + state.loading = false; + setSkeleton(root, false); + hideLoader(); + } + } + + var trigger = document.createElement('div'); + trigger.setAttribute('aria-hidden', 'true'); + trigger.className = 'h-px w-full'; + + function placeTrigger() { + // Place the trigger inside the grid one row before the last row so + // loading starts earlier (when the user reaches the penultimate row). + var cards = toArray(grid.querySelectorAll('.nova-card')); + var colCount = 1; + try { + var cols = window.getComputedStyle(grid).getPropertyValue('grid-template-columns'); + if (cols) colCount = Math.max(1, cols.trim().split(/\s+/).length); + } catch (e) { + colCount = 1; + } + + var refIndex = Math.max(0, cards.length - colCount); + var ref = cards[refIndex] || null; + + if (trigger.parentNode) trigger.parentNode.removeChild(trigger); + if (ref && ref.parentNode) { + ref.parentNode.insertBefore(trigger, ref); + } else { + grid.appendChild(trigger); + } + } + + if ('IntersectionObserver' in window) { + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) loadNextPage(); + }); + }, { root: null, rootMargin: LOAD_TRIGGER_MARGIN, threshold: 0 }); + // Place and observe the trigger. It will be moved after new cards are appended. + placeTrigger(); + io.observe(trigger); + } + + window.addEventListener('resize', function () { + relayout(); + onScrollOrResize(); + placeTrigger(); + }, { passive: true }); + window.addEventListener('scroll', onScrollOrResize, { passive: true }); + + relayout(); + placeTrigger(); + } + + function init() { + toArray(document.querySelectorAll('[data-nova-gallery]')).forEach(initOne); } - // Auto-run when DOM ready if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initGallery); + document.addEventListener('DOMContentLoaded', init); } else { - initGallery(); + init(); } - })(); diff --git a/public/legacy/js/legacy-gallery-init.js b/public/legacy/js/legacy-gallery-init.js index 7568f75b..fb3e761b 100644 --- a/public/legacy/js/legacy-gallery-init.js +++ b/public/legacy/js/legacy-gallery-init.js @@ -1,60 +1,280 @@ -// Modern gallery init: wait for images to decode, then init Isotope/Masonry -(function(){ +// Nova gallery progressive enhancement: +// - Masonry-like responsive layout (CSS grid row spans) +// - Infinite scroll on top of server pagination (SEO-safe fallback) +// - Skeleton placeholders while loading +// - Virtualized rendering hints for long feeds +(function () { 'use strict'; + var MAX_DOM_CARDS_FOR_VIRTUAL_HINT = 220; + var LOAD_TRIGGER_MARGIN = '900px'; + + function toArray(list) { + return Array.prototype.slice.call(list || []); + } + + function queryNextPageUrl(root) { + var pagination = root.querySelector('[data-gallery-pagination]'); + if (!pagination) return null; + var next = pagination.querySelector('a[rel="next"], a[aria-label="Next »"], a[aria-label="Next"], a[aria-label="pagination.next"]'); + return next ? next.getAttribute('href') : null; + } + + function setSkeleton(root, active, count) { + var box = root.querySelector('[data-gallery-skeleton]'); + if (!box) return; + box.innerHTML = ''; + if (!active) { + box.classList.remove('is-loading'); + return; + } + box.classList.add('is-loading'); + var total = Math.max(4, count || 8); + for (var i = 0; i < total; i += 1) { + var sk = document.createElement('div'); + sk.className = 'nova-skeleton-card'; + box.appendChild(sk); + } + } + function waitForImages(container) { - var imgs = Array.prototype.slice.call(container.querySelectorAll('img')); - var promises = imgs.map(function(img){ + var imgs = toArray(container.querySelectorAll('img')); + var promises = imgs.map(function (img) { try { - if (!img.complete) { - return img.decode().catch(function(){ /* ignore */ }); - } - // already complete: still attempt decode if supported - return img.decode ? img.decode().catch(function(){}) : Promise.resolve(); + if (img.decode) return img.decode().catch(function () { return null; }); } catch (e) { - return Promise.resolve(); + return null; } + return null; }); return Promise.all(promises); } - async function initGallery() { - var grid = document.querySelector('.gallery-grid'); + function applyMasonry(root) { + var grid = root.querySelector('[data-gallery-grid]'); + if (!grid) return; + var rowSize = 8; + var gap = 16; + + var cards = toArray(grid.querySelectorAll('.nova-card')); + cards.forEach(function (card) { + var media = card.querySelector('.nova-card-media') || card; + var height = media.getBoundingClientRect().height || 200; + // Lower the minimum forced span to avoid large empty space at the bottom. + // Previously this used a very large minimum (18) which reserved too many rows. + var minSpan = 1; + var span = Math.max(minSpan, Math.ceil((height + gap) / (rowSize + gap))); + card.style.gridRowEnd = 'span ' + span; + }); + } + + function applyVirtualizationHints(root) { + var grid = root.querySelector('[data-gallery-grid]'); + if (!grid) return; + var cards = toArray(grid.querySelectorAll('.nova-card')); + if (cards.length <= MAX_DOM_CARDS_FOR_VIRTUAL_HINT) { + cards.forEach(function (card) { + card.style.contentVisibility = ''; + card.style.containIntrinsicSize = ''; + }); + return; + } + + var viewportTop = window.scrollY; + var viewportBottom = viewportTop + window.innerHeight; + + cards.forEach(function (card) { + var rect = card.getBoundingClientRect(); + var top = rect.top + viewportTop; + var bottom = rect.bottom + viewportTop; + var farAbove = bottom < viewportTop - 1400; + var farBelow = top > viewportBottom + 2600; + + if (farAbove || farBelow) { + var h = Math.max(160, rect.height || 220); + card.style.contentVisibility = 'auto'; + card.style.containIntrinsicSize = Math.round(h) + 'px'; + } else { + card.style.contentVisibility = ''; + card.style.containIntrinsicSize = ''; + } + }); + } + + function extractAndAppendCards(root, html) { + var parser = new DOMParser(); + var doc = parser.parseFromString(html, 'text/html'); + var incomingGrid = doc.querySelector('[data-gallery-grid]'); + if (!incomingGrid) return { appended: 0, nextUrl: null }; + + var targetGrid = root.querySelector('[data-gallery-grid]'); + if (!targetGrid) return { appended: 0, nextUrl: null }; + + var cards = toArray(incomingGrid.querySelectorAll('.nova-card')); + cards.forEach(function (card) { + targetGrid.appendChild(card); + }); + + var incomingPagination = doc.querySelector('[data-gallery-pagination]'); + var currentPagination = root.querySelector('[data-gallery-pagination]'); + if (incomingPagination && currentPagination) { + currentPagination.innerHTML = incomingPagination.innerHTML; + } + + return { + appended: cards.length, + nextUrl: queryNextPageUrl(root) + }; + } + + function initOne(root) { + var grid = root.querySelector('[data-gallery-grid]'); if (!grid) return; - try { - await waitForImages(grid); - } catch (e) { - // continue even on failures + // loader overlay element (created lazily) + var loader = null; + function ensureLoader() { + if (loader) return loader; + loader = document.createElement('div'); + loader.className = 'nova-loader-overlay'; + var inner = document.createElement('div'); + inner.className = 'nova-loader-spinner'; + loader.appendChild(inner); + // place loader as child of root so it overlays grid area + loader.style.display = 'none'; + root.style.position = root.style.position || ''; + root.appendChild(loader); + return loader; } - // Prefer Isotope (legacy code included it). Fall back to Masonry. - if (window.Isotope) { - // eslint-disable-next-line no-unused-vars - var iso = new Isotope(grid, { - itemSelector: '.photo_frame', - layoutMode: 'masonry', - masonry: { columnWidth: '.photo_frame', gutter: 12 }, - percentPosition: true, - fitWidth: true - }); - } else if (window.Masonry) { - // eslint-disable-next-line no-unused-vars - var m = new Masonry(grid, { - itemSelector: '.photo_frame', - columnWidth: '.photo_frame', - percentPosition: true, - gutter: 12, - fitWidth: true + function showLoader() { var l = ensureLoader(); l.style.display = 'flex'; } + function hideLoader() { if (loader) loader.style.display = 'none'; } + + root.classList.add('is-enhanced'); + + var state = { + loading: false, + nextUrl: queryNextPageUrl(root), + done: false + }; + + function relayout() { + waitForImages(grid).then(function () { + applyMasonry(root); + applyVirtualizationHints(root); }); } + + var rafId = null; + function onScrollOrResize() { + if (rafId) return; + rafId = window.requestAnimationFrame(function () { + rafId = null; + applyVirtualizationHints(root); + }); + } + + async function loadNextPage() { + if (state.loading || state.done || !state.nextUrl) return; + state.loading = true; + + showLoader(); + + var sampleCards = toArray(grid.querySelectorAll('.nova-card')); + var skeletonCount = Math.min(12, Math.max(4, sampleCards.length ? sampleCards.slice(-4).length * 2 : 8)); + setSkeleton(root, true, skeletonCount); + + try { + var response = await window.fetch(state.nextUrl, { + credentials: 'same-origin', + headers: { 'X-Requested-With': 'XMLHttpRequest' } + }); + if (!response.ok) throw new Error('Failed to load page'); + var html = await response.text(); + + var result = extractAndAppendCards(root, html); + state.nextUrl = result.nextUrl; + if (!state.nextUrl || result.appended === 0) { + state.done = true; + } + + // Animate appended cards + var appendedCards = toArray(grid.querySelectorAll('.nova-card')).slice(-result.appended); + appendedCards.forEach(function (c) { + c.classList.add('nova-card-enter'); + // trigger reflow then add active class + requestAnimationFrame(function () { c.classList.add('nova-card-enter-active'); }); + c.addEventListener('transitionend', function te() { c.classList.remove('nova-card-enter', 'nova-card-enter-active'); c.removeEventListener('transitionend', te); }); + }); + + relayout(); + // After new cards appended, move the trigger to remain one-row-before-last. + placeTrigger(); + } catch (e) { + state.done = true; + } finally { + state.loading = false; + setSkeleton(root, false); + hideLoader(); + } + } + + var trigger = document.createElement('div'); + trigger.setAttribute('aria-hidden', 'true'); + trigger.className = 'h-px w-full'; + + function placeTrigger() { + // Place the trigger inside the grid one row before the last row so + // loading starts earlier (when the user reaches the penultimate row). + var cards = toArray(grid.querySelectorAll('.nova-card')); + var colCount = 1; + try { + var cols = window.getComputedStyle(grid).getPropertyValue('grid-template-columns'); + if (cols) colCount = Math.max(1, cols.trim().split(/\s+/).length); + } catch (e) { + colCount = 1; + } + + var refIndex = Math.max(0, cards.length - colCount); + var ref = cards[refIndex] || null; + + if (trigger.parentNode) trigger.parentNode.removeChild(trigger); + if (ref && ref.parentNode) { + ref.parentNode.insertBefore(trigger, ref); + } else { + grid.appendChild(trigger); + } + } + + if ('IntersectionObserver' in window) { + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting) loadNextPage(); + }); + }, { root: null, rootMargin: LOAD_TRIGGER_MARGIN, threshold: 0 }); + // Place and observe the trigger. It will be moved after new cards are appended. + placeTrigger(); + io.observe(trigger); + } + + window.addEventListener('resize', function () { + relayout(); + onScrollOrResize(); + placeTrigger(); + }, { passive: true }); + window.addEventListener('scroll', onScrollOrResize, { passive: true }); + + relayout(); + placeTrigger(); + } + + function init() { + toArray(document.querySelectorAll('[data-nova-gallery]')).forEach(initOne); } - // Auto-run when DOM ready if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', initGallery); + document.addEventListener('DOMContentLoaded', init); } else { - initGallery(); + init(); } - })(); diff --git a/resources/views/blank.blade.php b/resources/views/blank.blade.php index 4f91d358..2202624d 100644 --- a/resources/views/blank.blade.php +++ b/resources/views/blank.blade.php @@ -77,7 +77,7 @@
-
+ @endif +@stack('scripts') diff --git a/resources/views/legacy/_artwork_card.blade.php b/resources/views/legacy/_artwork_card.blade.php index 8f9306fa..d0c37cf5 100644 --- a/resources/views/legacy/_artwork_card.blade.php +++ b/resources/views/legacy/_artwork_card.blade.php @@ -1,62 +1,83 @@ @php - $img_src = $art->thumb ?? ''; - $img_srcset = $art->thumb_srcset ?? ''; - $base_height = 360; - $orig_width = (int) ($art->width ?? 0); - $orig_height = (int) ($art->height ?? 0); - $img_height = $base_height; - $img_width = ($orig_width > 0 && $orig_height > 0) - ? (int) round($orig_width * ($base_height / $orig_height)) - : 600; + // If a Collection or array was passed accidentally, pick the first item. + if (isset($art) && (is_array($art) || $art instanceof Illuminate\Support\Collection || $art instanceof Illuminate\Database\Eloquent\Collection)) { + $first = null; + if (is_array($art)) { + $first = reset($art); + } elseif ($art instanceof Illuminate\Support\Collection || $art instanceof Illuminate\Database\Eloquent\Collection) { + $first = $art->first(); + } + if ($first) { + $art = $first; + } + } + + $title = trim((string) ($art->name ?? $art->title ?? 'Untitled artwork')); + $author = trim((string) ($art->uname ?? $art->author_name ?? $art->author ?? 'Skinbase')); + $category = trim((string) ($art->category_name ?? $art->category ?? 'General')); + $license = trim((string) ($art->license ?? 'Standard')); + $resolution = trim((string) ($art->resolution ?? ((isset($art->width, $art->height) && $art->width && $art->height) ? ($art->width . '×' . $art->height) : ''))); + $likes = (int) ($art->likes ?? $art->favourites ?? 0); + $downloads = (int) ($art->downloads ?? $art->downloaded ?? 0); + + $img_src = (string) ($art->thumb ?? $art->thumbnail_url ?? '/images/placeholder.jpg'); + $img_srcset = (string) ($art->thumb_srcset ?? $art->thumbnail_srcset ?? $img_src); + $img_avif_srcset = (string) ($art->thumb_avif_srcset ?? $img_srcset); + $img_webp_srcset = (string) ($art->thumb_webp_srcset ?? $img_srcset); + + $img_width = max(1, (int) ($art->width ?? 800)); + $img_height = max(1, (int) ($art->height ?? 600)); + + $contentUrl = $img_src; + $cardUrl = (string) ($art->url ?? '#'); @endphp -
-
\ No newline at end of file +
diff --git a/resources/views/legacy/browse.blade.php b/resources/views/legacy/browse.blade.php index d0722307..d1cd50a5 100644 --- a/resources/views/legacy/browse.blade.php +++ b/resources/views/legacy/browse.blade.php @@ -62,15 +62,10 @@
-
-
+
+
@forelse ($artworks as $art) - -
- {{ $art->name ?? 'Artwork' }} -
-
{{ $art->name ?? 'Artwork' }}
-
+ @include('legacy._artwork_card', ['art' => $art]) @empty
No Artworks Yet
@@ -81,9 +76,10 @@ @endforelse
-
+
{{ $artworks->withQueryString()->links() }}
+
@@ -97,5 +93,39 @@ .nb-hero-fade { background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%); } + [data-nova-gallery].is-enhanced [data-gallery-grid] { + display: grid; + grid-template-columns: repeat(1, minmax(0, 1fr)); + grid-auto-rows: 8px; + gap: 1rem; + } + @media (min-width: 768px) { + /* From 768px up to ~991px show 2 columns */ + [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(2, minmax(0, 1fr)); } + } + @media (min-width: 1024px) { + [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(4, minmax(0, 1fr)); } + } + @media (min-width: 2600px) { + /* Very large displays: 5 columns */ + [data-nova-gallery].is-enhanced [data-gallery-grid] { grid-template-columns: repeat(5, minmax(0, 1fr)); } + } + [data-nova-gallery].is-enhanced [data-gallery-grid] > .nova-card { margin: 0 !important; } + [data-nova-gallery].is-enhanced [data-gallery-pagination] { display: none; } + [data-gallery-skeleton].is-loading { display: grid !important; grid-template-columns: inherit; gap: 1rem; } + .nova-skeleton-card { + border-radius: 1rem; + min-height: 180px; + background: linear-gradient(110deg, rgba(255,255,255,.06) 8%, rgba(255,255,255,.12) 18%, rgba(255,255,255,.06) 33%); + background-size: 200% 100%; + animation: novaShimmer 1.2s linear infinite; + } + @keyframes novaShimmer { + to { background-position-x: -200%; } + } @endpush + +@push('scripts') + +@endpush diff --git a/resources/views/legacy/category-slug.blade.php b/resources/views/legacy/category-slug.blade.php index 60519e85..ff9f492d 100644 --- a/resources/views/legacy/category-slug.blade.php +++ b/resources/views/legacy/category-slug.blade.php @@ -76,15 +76,10 @@ -
-
+
+
@forelse ($artworks as $art) - -
- {{ $art->title ?? 'Artwork' }} -
-
{{ $art->title ?? 'Artwork' }}
-
+ @include('legacy._artwork_card', ['art' => $art]) @empty
No Artworks Yet
@@ -95,11 +90,12 @@ @endforelse
-
+
@if ($artworks instanceof \Illuminate\Contracts\Pagination\Paginator) {{ $artworks->links() }} @endif
+
@@ -110,32 +106,35 @@ @push('styles') @endpush + +@push('scripts') + +@endpush diff --git a/resources/views/legacy/content-type.blade.php b/resources/views/legacy/content-type.blade.php index e9e5af23..c1fc897a 100644 --- a/resources/views/legacy/content-type.blade.php +++ b/resources/views/legacy/content-type.blade.php @@ -89,15 +89,10 @@ -
-
+
+
@forelse ($artworks as $art) - -
- {{ $art->title ?? 'Artwork' }} -
-
{{ $art->title ?? 'Artwork' }}
-
+ @include('legacy._artwork_card', ['art' => $art]) @empty
No Artworks Yet
@@ -108,9 +103,10 @@ @endforelse
-
+
{{ $artworks->withQueryString()->links('pagination::tailwind') }}
+
@@ -121,13 +117,42 @@ @push('styles') @endpush + +@push('scripts') + +@endpush diff --git a/resources/views/legacy/photography.blade.php b/resources/views/legacy/photography.blade.php index 07a5a20d..6296018a 100644 --- a/resources/views/legacy/photography.blade.php +++ b/resources/views/legacy/photography.blade.php @@ -72,7 +72,7 @@
-