Try to speedup movie page

This commit is contained in:
DariusIII
2025-07-01 13:59:57 +02:00
parent 178a10b3f9
commit b0b3a7803a
3 changed files with 407 additions and 334 deletions
+23
View File
@@ -47,12 +47,35 @@ class MovieController extends BasePageController
$rslt = $movie->getMovieRange($page, $catarray, $offset, config('nntmux.items_per_cover_page'), $orderby, -1, $this->userdata->categoryexclusions);
$results = $this->paginate($rslt ?? [], $rslt[0]->_totalcount ?? 0, config('nntmux.items_per_cover_page'), $page, $request->url(), $request->query());
// Optimize data processing - do heavy lifting in backend
$movies = $results->map(function ($result) {
// Pre-process exploded data to avoid template overhead
$result['genre'] = makeFieldLinks($result, 'genre', 'movies');
$result['actors'] = makeFieldLinks($result, 'actors', 'movies');
$result['director'] = makeFieldLinks($result, 'director', 'movies');
$result['languages'] = explode(', ', $result['language']);
// Pre-explode release data to reduce template processing
$result['release_ids'] = explode(',', $result['grp_release_id']);
$result['release_guids'] = explode(',', $result['grp_release_guid']);
$result['release_nfoids'] = explode(',', $result['grp_release_nfoid']);
$result['release_groups'] = explode(',', $result['grp_release_grpname']);
$result['release_names'] = explode('#', $result['grp_release_name']);
$result['release_postdates'] = explode(',', $result['grp_release_postdate']);
$result['release_sizes'] = explode(',', $result['grp_release_size']);
$result['release_totalparts'] = explode(',', $result['grp_release_totalparts']);
$result['release_comments'] = explode(',', $result['grp_release_comments']);
$result['release_grabs'] = explode(',', $result['grp_release_grabs']);
$result['release_failed'] = explode(',', $result['grp_release_failed']);
$result['release_password'] = explode(',', $result['grp_release_password']);
$result['release_innerfiles'] = explode(',', $result['grp_rarinnerfilecount']);
$result['release_haspreview'] = explode(',', $result['grp_haspreview']);
// Pre-process category data
if (isset($result['category_name'])) {
$result['category_split'] = explode('>', $result['category_name']);
}
return $result;
});
+384 -334
View File
@@ -58,24 +58,9 @@
<div class="row g-4">
{foreach $results as $result}
{if isset($result['category_name'])}
{assign var="catnamesplit" value=">"|explode:$result['category_name']}
{assign var="catnamesplit" value=$result['category_split']}
{/if}
{assign var="msplits" value=","|explode:$result['grp_release_id']}
{assign var="mguid" value=","|explode:$result['grp_release_guid']}
{assign var="mnfo" value=","|explode:$result['grp_release_nfoid']}
{assign var="mgrp" value=","|explode:$result['grp_release_grpname']}
{assign var="mname" value="#"|explode:$result['grp_release_name']}
{assign var="mpostdate" value=","|explode:$result['grp_release_postdate']}
{assign var="msize" value=","|explode:$result['grp_release_size']}
{assign var="mtotalparts" value=","|explode:$result['grp_release_totalparts']}
{assign var="mcomments" value=","|explode:$result['grp_release_comments']}
{assign var="mgrabs" value=","|explode:$result['grp_release_grabs']}
{assign var="mfailed" value=","|explode:$result['grp_release_failed']}
{assign var="mpass" value=","|explode:$result['grp_release_password']}
{assign var="minnerfiles" value=","|explode:$result['grp_rarinnerfilecount']}
{assign var="mhaspreview" value=","|explode:$result['grp_haspreview']}
<div class="col-md-6 mb-4">
<div class="card h-100 shadow-sm hover-shadow">
<div class="card-body">
@@ -88,363 +73,428 @@
{/foreach}
</div>
{foreach $msplits as $m}
{if $m@first}
<div class="position-relative mb-2">
<a href="{{url("/Movies?imdb={$result['imdbid']}")}}">
<img class="cover img-fluid rounded shadow"
src="{if isset($result['cover']) && $result['cover'] == 1}{{url("/covers/movies/{$result['imdbid']}-cover.jpg")}}{else}{{asset("/assets/images/no-cover.png")}}{/if}"
alt="{$result['title']|escape:"htmlall"}"/>
{if !empty($result['release_ids'][0])}
<div class="position-relative mb-2">
<a href="{{url("/Movies?imdb={$result['imdbid']}")}}">
<img class="cover img-fluid rounded shadow"
src="{{asset("/assets/images/placeholder.png")}}"
data-src="{if isset($result['cover']) && $result['cover'] == 1}{{url("/covers/movies/{$result['imdbid']}-cover.jpg")}}{else}{{asset("/assets/images/no-cover.png")}}{/if}"
alt="{$result['title']|escape:"htmlall"}"
loading="lazy"
onload="this.style.opacity='1'"
style="opacity:0;transition:opacity 0.3s"/>
{if !empty($mfailed[$m@index])}
<div class="position-absolute top-0 end-0 p-2">
<span class="badge bg-danger rounded-pill">
<i class="fa fa-exclamation-circle me-1"></i>Failed
</span>
</div>
{/if}
</a>
</div>
<!-- External Links Badges -->
<div class="d-flex flex-wrap gap-1 mb-2 external-links">
<a target="_blank" href="{$site->dereferrer_link}http://www.imdb.com/title/tt{$result['imdbid']}/"
title="View IMDB page" class="badge bg-secondary text-white text-decoration-none badge-link" rel="imdb">
<i class="fa fa-film me-1"></i>IMDB
</a>
<a target="_blank" href="{$site->dereferrer_link}http://trakt.tv/search/imdb/tt{$result['imdbid']}/"
title="View Trakt page" class="badge bg-secondary text-white text-decoration-none badge-link" rel="trakt">
<i class="fa fa-tv me-1"></i>TRAKT
</a>
{if (!empty($result['tmdbid']))}
<a class="badge bg-secondary text-white text-decoration-none badge-link" rel="tmdb" target="_blank"
href="{$site->dereferrer_link}http://www.themoviedb.org/movie/{$result['tmdbid']}"
title="View TheMovieDB page">
<i class="fa fa-database me-1"></i>TMDB
</a>
{if !empty($result['release_failed'][0])}
<div class="position-absolute top-0 end-0 p-2">
<span class="badge bg-danger rounded-pill">
<i class="fa fa-exclamation-circle me-1"></i>Failed
</span>
</div>
{/if}
</a>
</div>
{if $mnfo[$m@index] > 0}
<a href="{{url("/nfo/{$mguid[$m@index]}")}}" data-guid="{$mguid[$m@index]}"
title="View NFO" class="modal_nfo badge bg-secondary text-white text-decoration-none badge-link" data-bs-toggle="modal" data-bs-target="#nfoModal">
<i class="fa fa-file-text me-1"></i>NFO
</a>
{/if}
<!-- External Links Badges -->
<div class="d-flex flex-wrap gap-1 mb-2 external-links">
<a target="_blank" href="{$site->dereferrer_link}http://www.imdb.com/title/tt{$result['imdbid']}/"
title="View IMDB page" class="badge bg-secondary text-white text-decoration-none badge-link" rel="imdb">
<i class="fa fa-film me-1"></i>IMDB
</a>
<a class="badge bg-secondary text-white text-decoration-none badge-link"
href="{{url("/browse/group?g={$mgrp[$m@index]}")}}"
title="Browse releases in {$mgrp[$m@index]|replace:"alt.binaries":"a.b"}">
<i class="fa fa-users me-1"></i>Group
<a target="_blank" href="{$site->dereferrer_link}http://trakt.tv/search/imdb/tt{$result['imdbid']}/"
title="View Trakt page" class="badge bg-secondary text-white text-decoration-none badge-link" rel="trakt">
<i class="fa fa-tv me-1"></i>TRAKT
</a>
{if (!empty($result['tmdbid']))}
<a class="badge bg-secondary text-white text-decoration-none badge-link" rel="tmdb" target="_blank"
href="{$site->dereferrer_link}http://www.themoviedb.org/movie/{$result['tmdbid']}"
title="View TheMovieDB page">
<i class="fa fa-database me-1"></i>TMDB
</a>
{/if}
<a class="badge bg-success text-white text-decoration-none badge-link"
href="{{url("/mymovies?id=add&imdb={$result['imdbid']}&from={$smarty.server.REQUEST_URI|escape:"url"}")}}"
title="Add to My Movies">
<i class="fa fa-plus-circle me-1"></i>Add
{if $result['release_nfoids'][0] > 0}
<a href="{{url("/nfo/{$result['release_guids'][0]}")}}" data-guid="{$result['release_guids'][0]}"
title="View NFO" class="modal_nfo badge bg-secondary text-white text-decoration-none badge-link" data-bs-toggle="modal" data-bs-target="#nfoModal">
<i class="fa fa-file-text me-1"></i>NFO
</a>
</div>
{/if}
{/foreach}
{/if}
<a class="badge bg-secondary text-white text-decoration-none badge-link"
href="{{url("/browse/group?g={$result['release_groups'][0]}")}}"
title="Browse releases in {$result['release_groups'][0]|replace:"alt.binaries":"a.b"}">
<i class="fa fa-users me-1"></i>Group
</a>
<a class="badge bg-success text-white text-decoration-none badge-link"
href="{{url("/mymovies?id=add&imdb={$result['imdbid']}&from={$smarty.server.REQUEST_URI|escape:"url"}")}}"
title="Add to My Movies">
<i class="fa fa-plus-circle me-1"></i>Add
</a>
</div>
{/if}
</div>
<!-- Movie Info Column -->
<div class="col-md-8">
{foreach $msplits as $m}
{if $m@first}
<!-- Movie Title -->
<h5 class="mb-2">
<a class="text-decoration-none title-link" href="{{url("/Movies?imdb={$result['imdbid']}")}}">
{$result['title']|escape:"htmlall"}
<span class="year-badge badge bg-primary ms-1">{$result['year']}</span>
{if $result['rating'] != ''}
<span class="rating-badge badge bg-warning text-dark ms-1">
<i class="fa fa-star me-1"></i>{$result['rating']}/10
</span>
{/if}
</a>
</h5>
<!-- Movie Details -->
<div class="mb-3 small">
{if $result['genre'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-tags text-muted me-2"></i>
<div class="genre-tags">
{foreach explode(", ", $result['genre']) as $genre}
<span class="badge bg-light text-dark me-1">{$genre}</span>
{/foreach}
</div>
</div>
{if !empty($result['release_ids'][0])}
<!-- Movie Title -->
<h5 class="mb-2">
<a class="text-decoration-none title-link" href="{{url("/Movies?imdb={$result['imdbid']}")}}">
{$result['title']|escape:"htmlall"}
<span class="year-badge badge bg-primary ms-1">{$result['year']}</span>
{if $result['rating'] != ''}
<span class="rating-badge badge bg-warning text-dark ms-1">
<i class="fa fa-star me-1"></i>{$result['rating']}/10
</span>
{/if}
</a>
</h5>
{if $result['plot'] != ''}
<div class="mb-1 text-truncate-3 plot-text"><i class="fa fa-quote-left text-muted me-2"></i>{$result['plot']}</div>
{/if}
{if $result['director'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-video-camera text-muted me-2"></i>
<span><strong>Director:</strong> {$result['director']}</span>
<!-- Movie Details -->
<div class="mb-3 small">
{if $result['genre'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-tags text-muted me-2"></i>
<div class="genre-tags">
{foreach explode(", ", $result['genre']) as $genre}
<span class="badge bg-light text-dark me-1">{$genre}</span>
{/foreach}
</div>
{/if}
</div>
{/if}
{if $result['actors'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-users text-muted me-2"></i>
<span><strong>Starring:</strong> {$result['actors']}</span>
</div>
{/if}
</div>
{if $result['plot'] != ''}
<div class="mb-1 text-truncate-3 plot-text"><i class="fa fa-quote-left text-muted me-2"></i>{$result['plot']}</div>
{/if}
<!-- Release Info -->
<div class="card bg-light mb-3">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<label class="me-2 mb-0">
<input type="checkbox" class="form-check-input" value="{$mguid[$m@index]}" id="chksingle"/>
</label>
{if $result['director'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-video-camera text-muted me-2"></i>
<span><strong>Director:</strong> {$result['director']}</span>
</div>
{/if}
{if isset($catsplit[0])}
<span class="badge bg-primary"><i class="fa fa-folder me-1"></i>{$catsplit[0]}</span>
{/if}
{if $result['actors'] != ''}
<div class="d-flex align-items-center mb-1">
<i class="fa fa-users text-muted me-2"></i>
<span><strong>Starring:</strong> {$result['actors']}</span>
</div>
{/if}
</div>
{if isset($catsplit[1])}
<span class="badge bg-danger"><i class="fa fa-folder-open me-1"></i>{$catsplit[1]}</span>
{/if}
<!-- Release Info -->
<div class="card bg-light mb-3">
<div class="card-body p-3">
<div class="d-flex justify-content-between align-items-start mb-2">
<div>
<label class="me-2 mb-0">
<input type="checkbox" class="form-check-input" value="{$result['release_guids'][0]}" id="chksingle"/>
</label>
{if $result['rtrating'] != ''}
<span class="badge bg-info">
<i class="fa fa-percent me-1"></i>RT: {$result['rtrating']}
</span>
{/if}
</div>
{if isset($result['category_split'][0])}
<span class="badge bg-primary"><i class="fa fa-folder me-1"></i>{$result['category_split'][0]}</span>
{/if}
<div class="d-flex align-items-center">
<i class="fa fa-hdd-o text-muted me-2"></i>
<span class="badge bg-secondary">{$msize[$m@index]|filesize}</span>
</div>
</div>
{if isset($result['category_split'][1])}
<span class="badge bg-danger"><i class="fa fa-folder-open me-1"></i>{$result['category_split'][1]}</span>
{/if}
<div class="d-flex align-items-center text-muted mb-2">
<i class="fa fa-clock-o me-2"></i>
<span>Posted {$mpostdate[$m@index]|timeago} ago</span>
</div>
<!-- Release Name -->
<div class="text-truncate mb-3">
<a href="{{url("/details/{$mguid[$m@index]}")}}" class="text-muted text-decoration-none">
<i class="fa fa-file-archive-o text-muted me-2"></i>
{$mname[$m@index]|escape:"htmlall"}
</a>
</div>
<!-- Action Buttons -->
<div class="d-flex gap-2">
<a class="btn btn-sm btn-primary"
data-bs-toggle="tooltip" data-bs-placement="top" title="Download NZB"
href="{{url("/getnzb?id={$mguid[$m@index]}")}}">
<i class="fa fa-cloud-download me-1"></i>
<span class="badge bg-light text-dark">{$mgrabs[$m@index]}</span>
</a>
<a class="btn btn-sm btn-outline-secondary"
href="{{url("/details/{$mguid[$m@index]}/#comments")}}">
<i class="fa fa-comment-o me-1"></i>
<span class="badge bg-light text-dark">{$mcomments[$m@index]}</span>
</a>
<a href="#" class="btn btn-sm btn-outline-info add-to-cart"
data-bs-toggle="tooltip" data-bs-placement="top" title="Send to my download basket">
<i id="guid{$mguid[$m@index]}" class="icon_cart fa fa-shopping-basket"></i>
</a>
{if !empty($mfailed[$m@index])}
<span class="btn btn-sm btn-outline-danger">
<i class="fa fa-exclamation-triangle me-1"></i>
{$mfailed[$m@index]} Failed
{if $result['rtrating'] != ''}
<span class="badge bg-info">
<i class="fa fa-percent me-1"></i>RT: {$result['rtrating']}
</span>
{/if}
</div>
<div class="d-flex align-items-center">
<i class="fa fa-hdd-o text-muted me-2"></i>
<span class="badge bg-secondary">{$result['release_sizes'][0]|filesize}</span>
</div>
</div>
<div class="d-flex align-items-center text-muted mb-2">
<i class="fa fa-clock-o me-2"></i>
<span>Posted {$result['release_postdates'][0]|timeago} ago</span>
</div>
<!-- Release Name -->
<div class="text-truncate mb-3">
<a href="{{url("/details/{$result['release_guids'][0]}")}}" class="text-muted text-decoration-none">
<i class="fa fa-file-archive-o text-muted me-2"></i>
{$result['release_names'][0]|escape:"htmlall"}
</a>
</div>
<!-- Action Buttons -->
<div class="d-flex gap-2">
<a class="btn btn-sm btn-primary"
data-bs-toggle="tooltip" data-bs-placement="top" title="Download NZB"
href="{{url("/getnzb?id={$result['release_guids'][0]}")}}">
<i class="fa fa-cloud-download me-1"></i>
<span class="badge bg-light text-dark">{$result['release_grabs'][0]}</span>
</a>
<a class="btn btn-sm btn-outline-secondary"
href="{{url("/details/{$result['release_guids'][0]}/#comments")}}">
<i class="fa fa-comment-o me-1"></i>
<span class="badge bg-light text-dark">{$result['release_comments'][0]}</span>
</a>
<a href="#" class="btn btn-sm btn-outline-info add-to-cart"
data-bs-toggle="tooltip" data-bs-placement="top" title="Send to my download basket">
<i id="guid{$result['release_guids'][0]}" class="icon_cart fa fa-shopping-basket"></i>
</a>
{if !empty($result['release_failed'][0])}
<span class="btn btn-sm btn-outline-danger">
<i class="fa fa-exclamation-triangle me-1"></i>
{$result['release_failed'][0]} Failed
</span>
{/if}
</div>
</div>
{/if}
{/foreach}
</div>
{/if}
</div>
</div>
</div>
</div>
</div>
{/foreach}
</div>
</div>
<!-- Bottom Controls -->
<hr>
<div class="row mt-4">
<div class="col-md-4">
<div class="d-flex align-items-center">
<div class="me-3">
<span>View: <strong>Covers</strong> | <a href="{{url("/browse/Movies/{$categorytitle}")}}">List</a></span>
</div>
<div class="d-flex align-items-center">
<small class="me-2">With Selected:</small>
<div class="btn-group">
<button type="button" class="nzb_multi_operations_download btn btn-sm btn-success" data-bs-toggle="tooltip" data-bs-placement="top" title="Download NZBs">
<i class="fa fa-cloud-download"></i>
</button>
<button type="button" class="nzb_multi_operations_cart btn btn-sm btn-info" data-bs-toggle="tooltip" data-bs-placement="top" title="Send to my Download Basket">
<i class="fa fa-shopping-basket"></i>
</button>
{if isset($isadmin)}
<button type="button" class="nzb_multi_operations_edit btn btn-sm btn-warning">Edit</button>
<button type="button" class="nzb_multi_operations_delete btn btn-sm btn-danger">Delete</button>
{/if}
</div>
</div>
</div>
</div>
<div class="col-md-8 d-flex justify-content-end">
{$results->onEachSide(5)->links()}
</div>
</div>
{{Form::close()}}
</div>
</div>
</div>
<!-- Bottom Controls -->
<hr>
<div class="row mt-4">
<div class="col-md-4">
<div class="d-flex align-items-center">
<div class="me-3">
<span>View: <strong>Covers</strong> | <a href="{{url("/browse/Movies/{$categorytitle}")}}">List</a></span>
</div>
<div class="d-flex align-items-center">
<small class="me-2">With Selected:</small>
<div class="btn-group">
<button type="button" class="nzb_multi_operations_download btn btn-sm btn-success" data-bs-toggle="tooltip" data-bs-placement="top" title="Download NZBs">
<i class="fa fa-cloud-download"></i>
</button>
<button type="button" class="nzb_multi_operations_cart btn btn-sm btn-info" data-bs-toggle="tooltip" data-bs-placement="top" title="Send to my Download Basket">
<i class="fa fa-shopping-basket"></i>
</button>
{if isset($isadmin)}
<button type="button" class="nzb_multi_operations_edit btn btn-sm btn-warning">Edit</button>
<button type="button" class="nzb_multi_operations_delete btn btn-sm btn-danger">Delete</button>
{/if}
</div>
</div>
</div>
</div>
<div class="col-md-8 d-flex justify-content-end">
{$results->onEachSide(5)->links()}
</div>
</div>
{{Form::close()}}
</div>
</div>
</div>
<!-- NFO Modal -->
<div class="modal fade" id="nfoModal" tabindex="-1" aria-labelledby="nfoModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="nfoModalLabel">NFO Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="text-center mb-3 nfo-loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading NFO content...</p>
</div>
<pre id="nfoContent" class="bg-dark text-light p-3 rounded d-none" style="white-space: pre; font-family: monospace; overflow-x: auto;"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<!-- NFO Modal -->
<div class="modal fade" id="nfoModal" tabindex="-1" aria-labelledby="nfoModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="nfoModalLabel">NFO Information</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="text-center mb-3 nfo-loading">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading NFO content...</p>
</div>
<pre id="nfoContent" class="bg-dark text-light p-3 rounded d-none" style="white-space: pre; font-family: monospace; overflow-x: auto;"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<style>
.text-truncate-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.hover-shadow:hover {
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
transition: box-shadow 0.3s ease-in-out;
}
.badge-link {
transition: all 0.2s ease;
}
.badge-link:hover {
transform: translateY(-2px);
box-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.title-link:hover .year-badge,
.title-link:hover .rating-badge {
transition: all 0.2s ease;
}
.title-link:hover .year-badge {
background-color: #0d6efd !important;
}
.title-link:hover .rating-badge {
background-color: #ffca2c !important;
}
.add-to-cart:hover .icon_cart {
animation: shake 0.5s ease-in-out;
}
@keyframes shake {
0%, 100% { transform: rotate(0deg); }
25% { transform: rotate(-10deg); }
75% { transform: rotate(10deg); }
}
.genre-tags .badge {
transition: all 0.2s ease;
}
.genre-tags .badge:hover {
background-color: #e9ecef !important;
cursor: default;
}
</style>
<style>
.text-truncate-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
<script>
{literal}
// NFO Modal content loading
document.addEventListener('DOMContentLoaded', function() {
const nfoModal = document.getElementById('nfoModal');
/* Optimized hover effects - reduce GPU usage */
.hover-shadow {
transition: box-shadow 0.2s ease-out;
}
.hover-shadow:hover {
box-shadow: 0 .5rem 1rem rgba(0,0,0,.15)!important;
}
if (nfoModal) {
nfoModal.addEventListener('show.bs.modal', function(event) {
const button = event.relatedTarget;
const guid = button.getAttribute('data-guid');
const modalTitle = nfoModal.querySelector('.modal-title');
const loading = nfoModal.querySelector('.nfo-loading');
const contentElement = document.getElementById('nfoContent');
/* Simplified badge animations */
.badge-link {
transition: transform 0.15s ease-out;
}
.badge-link:hover {
transform: translateY(-1px);
}
// Reset and show loading state
loading.style.display = 'block';
contentElement.classList.add('d-none');
contentElement.textContent = '';
/* Optimized cover image loading */
.cover {
background-color: #f8f9fa;
min-height: 200px;
object-fit: cover;
}
// Fetch the NFO content via AJAX
fetch(`/nfo/${guid}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(response => response.text())
.then(html => {
// Extract just the NFO content from the response
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
/* Disable expensive animations on mobile */
@media (max-width: 768px) {
.hover-shadow:hover {
box-shadow: none !important;
}
.badge-link:hover {
transform: none;
}
.add-to-cart:hover .icon_cart {
animation: none;
}
}
// Look for the pre element that likely contains the NFO
let nfoText = '';
const preElement = doc.querySelector('pre');
/* Simplified title animations */
.title-link .year-badge,
.title-link .rating-badge {
transition: background-color 0.15s ease-out;
}
if (preElement) {
// Found a pre element, use its content
nfoText = preElement.textContent;
} else {
// Try to find the main content area
const mainContent = doc.querySelector('.card-body, .main-content, .content-area, main');
if (mainContent) {
nfoText = mainContent.textContent;
} else {
// Fallback: use the whole page but clean it up
nfoText = doc.body.textContent;
}
}
/* Remove shake animation - too expensive */
.add-to-cart {
transition: opacity 0.15s ease-out;
}
.add-to-cart:hover {
opacity: 0.8;
}
// Update the modal
loading.style.display = 'none';
contentElement.classList.remove('d-none');
contentElement.textContent = nfoText.trim();
})
.catch(error => {
console.error('Error fetching NFO content:', error);
loading.style.display = 'none';
contentElement.classList.remove('d-none');
contentElement.textContent = 'Error loading NFO content';
});
});
}
});
{/literal}
</script>
/* Optimize genre tags */
.genre-tags .badge {
transition: background-color 0.1s ease-out;
}
.genre-tags .badge:hover {
background-color: #e9ecef !important;
}
/* Loading spinner optimization */
.spinner-border {
width: 2rem;
height: 2rem;
}
</style>
<script>
{literal}
// Lazy loading implementation
document.addEventListener('DOMContentLoaded', function() {
// Intersection Observer for lazy loading
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const src = img.getAttribute('data-src');
if (src) {
img.src = src;
img.removeAttribute('data-src');
observer.unobserve(img);
}
}
});
}, {
rootMargin: '50px 0px',
threshold: 0.01
});
// Observe all images with data-src
document.querySelectorAll('img[data-src]').forEach(img => {
imageObserver.observe(img);
});
// Optimized NFO Modal loading
const nfoModal = document.getElementById('nfoModal');
if (nfoModal) {
nfoModal.addEventListener('show.bs.modal', function(event) {
const button = event.relatedTarget;
const guid = button.getAttribute('data-guid');
const loading = nfoModal.querySelector('.nfo-loading');
const contentElement = document.getElementById('nfoContent');
// Reset and show loading state
loading.style.display = 'block';
contentElement.classList.add('d-none');
contentElement.textContent = '';
// Use fetch with abort controller for better performance
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 10000); // 10s timeout
fetch(`/nfo/${guid}`, {
headers: {
'X-Requested-With': 'XMLHttpRequest'
},
signal: controller.signal
})
.then(response => {
clearTimeout(timeout);
if (!response.ok) throw new Error('Network response was not ok');
return response.text();
})
.then(html => {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
let nfoText = '';
const preElement = doc.querySelector('pre');
if (preElement) {
nfoText = preElement.textContent;
} else {
const mainContent = doc.querySelector('.card-body, .main-content, .content-area, main');
nfoText = mainContent ? mainContent.textContent : 'No NFO content found';
}
loading.style.display = 'none';
contentElement.classList.remove('d-none');
contentElement.textContent = nfoText.trim();
})
.catch(error => {
clearTimeout(timeout);
console.error('Error fetching NFO content:', error);
loading.style.display = 'none';
contentElement.classList.remove('d-none');
contentElement.textContent = 'Error loading NFO content';
});
});
}
// Debounced tooltip initialization to reduce overhead
let tooltipTimeout;
function initTooltips() {
clearTimeout(tooltipTimeout);
tooltipTimeout = setTimeout(() => {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl, {
delay: { show: 500, hide: 100 }
});
});
}, 100);
}
initTooltips();
});
{/literal}
</script>