Files
MediaCollectorLibary/resources/views/games/show.twig
Lars Behrends 7a7977d8b0 yay
2025-11-01 22:00:30 +01:00

1001 lines
51 KiB
Twig

{% extends "layouts/app.twig" %}
{% block content %}
<!-- Hero Section -->
<div class="game-hero" id="gameHero" style="background: linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.7)), url('{{ platform_versions[0].banner_url ? "/images/playnite/" ~ platform_versions[0].banner_url : "/images/hero-bg.jpg" }}') no-repeat center/cover;">
<div class="container py-5">
<div class="row">
<!-- Game Cover -->
<div class="col-md-2 mb-4 mb-md-0">
<div id="gameCover">
{% if platform_versions[0].cover_image_url %}
<img src="/images/playnite/{{ platform_versions[0].cover_image_url }}" class="img-fluid rounded-3 shadow-lg game-cover" alt="{{ main_game.title }}" style="max-height: 300px; width: auto;">
{% elseif main_game.image_url %}
<img src="/images/playnite/{{ main_game.image_url }}" class="img-fluid rounded-3 shadow-lg game-cover" alt="{{ main_game.title }}" style="max-height: 300px; width: auto;">
{% else %}
<div class="bg-dark rounded-3 d-flex align-items-center justify-content-center" style="width: 200px; height: 300px;">
<i class="bi bi-controller text-muted" style="font-size: 3rem;"></i>
</div>
{% endif %}
</div>
</div>
<!-- Game Info -->
<div class="col-md-6 text-white">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-3">
<li class="breadcrumb-item"><a href="{{ path_for('games.index') }}" class="text-white-50">Games</a></li>
<li class="breadcrumb-item active text-light" aria-current="page">{{ main_game.title }}</li>
</ol>
</nav>
<h1 class="display-5 fw-bold mb-2">{{ main_game.title }}</h1>
<div class="d-flex flex-wrap gap-2 mb-3">
<span class="badge bg-primary">{{ platform_versions|length }} Platform{{ platform_versions|length > 1 ? 's' : '' }}</span>
{% if main_game.genre %}
<span class="badge bg-secondary">{{ main_game.genre }}</span>
{% endif %}
{% set playtime = platform_versions[0].playtime_minutes // 60 %}
{% if playtime > 0 %}
<span class="badge bg-success">{{ playtime }}h Played</span>
{% endif %}
</div>
<div class="d-flex gap-2 mb-4">
<button class="btn btn-primary px-4">
<i class="bi bi-play-fill me-2"></i>Play
</button>
<button class="btn btn-outline-light">
<i class="bi bi-heart me-2"></i>Favorite
</button>
<button class="btn btn-outline-light">
<i class="bi bi-share me-2"></i>Share
</button>
</div>
<!-- Platform Tabs -->
<ul class="nav nav-tabs border-0" id="platformTabs" role="tablist">
{% for version in platform_versions %}
{% set safePlatformId = version.platform|lower|replace({' ': '-', '(': '', ')': ''}) ~ '-' ~ version.source_id %}
<li class="nav-item" role="presentation">
{% set platform_icons = {
'windows': 'mdi:microsoft-windows',
'pc': 'mdi:desktop-classic',
'playstation': 'mdi:sony-playstation',
'xbox': 'mdi:microsoft-xbox',
'nintendo': 'mdi:nintendo-switch',
'switch': 'mdi:nintendo-switch',
'wii': 'mdi:nintendo-wii',
'gamecube': 'mdi:gamepad-square',
'n64': 'mdi:gamepad-square',
'snes': 'mdi:gamepad-square',
'nes': 'mdi:gamepad-square',
'android': 'mdi:android',
'ios': 'mdi:apple-ios',
'linux': 'mdi:linux',
'macos': 'mdi:apple',
'steam': 'mdi:steam',
'gog': 'mdi:gog',
'epic': 'mdi:epic-games',
'origin': 'mdi:origin',
'ubisoft': 'mdi:ubisoft',
'battlenet': 'mdi:battle-net',
'ea': 'mdi:ea',
'humble': 'mdi:humble-bundle',
'itch': 'mdi:itch-io',
'browser': 'mdi:web',
'default': 'mdi:gamepad-variant'
} %}
{% set platform_lower = version.platform|lower %}
{% set platform_icon = 'default' %}
{% for key, icon in platform_icons %}
{% if key in platform_lower %}
{% set platform_icon = icon %}
{% endif %}
{% endfor %}
<button
class="nav-link {{ loop.first ? 'active' : 'text-white-50' }} platform-tab border-0 d-flex align-items-center gap-2"
id="tab-{{ safePlatformId }}"
data-bs-toggle="tab"
data-bs-target="#content-{{ safePlatformId }}"
type="button"
role="tab"
data-platform="{{ version.platform }}"
data-source="{{ version.source_id }}"
data-last-played="{{ version.last_played_at ? version.last_played_at|date('M d, Y') : 'Never' }}"
data-playtime="{{ version.playtime_minutes|format_duration }}"
data-completion="{{ version.completion_percentage ?? 0 }}"
data-cover-image="{{ version.cover_image_url ? '/images/playnite/' ~ version.cover_image_url : (main_game.image_url ? '/images/playnite/' ~ main_game.image_url : '') }}"
data-banner-url="{{ version.hero_image_url ? '/images/playnite/' ~ version.hero_image_url : '' }}"
style="background: none;"
>
<iconify-icon icon="{{ platform_icon }}" width="20" height="20"></iconify-icon>
<span>{{ version.platform }}</span>
{% if version.source_name %}
<small class="text-white-50 ms-1">({{ version.source_name }})</small>
{% endif %}
</button>
</li>
{% endfor %}
</ul>
</div>
<!-- Stats Sidebar -->
<div class="col-md-4">
<div class="card bg-dark bg-opacity-50 text-white border-0">
<div class="card-body">
<h5 class="card-title mb-3">Game Stats</h5>
<div class="mb-3">
<div class="d-flex justify-content-between mb-2">
<span class="text-white-50">Last Played</span>
<span class="stats-last-played">{{ platform_versions[0].last_played_at ? platform_versions[0].last_played_at|date('M d, Y') : 'Never' }}</span>
</div>
<div class="d-flex justify-content-between mb-2">
<span class="text-white-50">Playtime</span>
<span class="stats-playtime">{{ platform_versions[0].playtime_minutes|format_duration }}</span>
</div>
<div class="d-flex justify-content-between mb-2 stats-completion" style="display: {{ platform_versions[0].completion_percentage > 0 ? 'flex' : 'none' }};">
<span class="text-white-50">Completion</span>
<div class="d-flex align-items-center">
<div class="progress bg-dark bg-opacity-25 me-2" style="width: 60px; height: 6px;">
<div
class="progress-bar bg-success stats-progress-bar"
role="progressbar"
style="width: {{ platform_versions[0].completion_percentage }}%"
aria-valuenow="{{ platform_versions[0].completion_percentage }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="stats-completion-percent">{{ platform_versions[0].completion_percentage }}%</span>
</div>
</div>
</div>
{% set metadata = platform_versions[0].metadata|json_decode %}
{% if metadata %}
<div class="border-top border-white-10 pt-3">
<h6 class="text-white-50 mb-2">Platform Details</h6>
{% if metadata.appid %}
<div class="d-flex justify-content-between mb-1">
<span class="text-white-50 small">App ID</span>
<code class="text-white">{{ metadata.appid }}</code>
</div>
{% endif %}
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Main Content -->
<div class="container py-4">
<div class="row">
<!-- Left Column -->
<div class="col-lg-8">
<!-- Tab Content -->
<div class="tab-content" id="platformTabsContent">
{% for version in platform_versions %}
{% set safePlatformId = version.platform|lower|replace({' ': '-', '(': '', ')': ''}) ~ '-' ~ version.source_id %}
<div
class="tab-pane fade {{ loop.first ? 'show active' }}"
id="content-{{ safePlatformId }}"
role="tabpanel"
data-platform="{{ version.platform }}"
data-source="{{ version.source_id }}"
>
<!-- Game Description -->
{% if version.description %}
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title mb-3">About</h5>
<div class="game-description">
{{ version.description|nl2br }}
</div>
<button class="btn btn-link p-0 mt-2 text-primary read-more-btn">Read more</button>
</div>
</div>
{% endif %}
<!-- Screenshots Section -->
{% if main_game.screenshots is not empty %}
<div class="card mb-4">
<div class="card-header">
<h4 class="mb-0">Screenshots</h4>
</div>
<div class="card-body">
<div id="screenshotsCarousel" class="carousel slide" data-bs-ride="carousel">
<div class="carousel-inner rounded" style="max-height: 400px; overflow: hidden;">
{% for screenshot in main_game.screenshots %}
<div class="carousel-item {{ loop.first ? 'active' : '' }}">
<img src="/images/playnite/{{ screenshot }}" class="d-block w-100" alt="Screenshot {{ loop.index }}" style="object-fit: contain; width: 100%; height: 100%;">
</div>
{% endfor %}
</div>
{% if main_game.screenshots|length > 1 %}
<button class="carousel-control-prev" type="button" data-bs-target="#screenshotsCarousel" data-bs-slide="prev">
<span class="carousel-control-prev-icon bg-dark bg-opacity-50 rounded" aria-hidden="true"></span>
<span class="visually-hidden">Previous</span>
</button>
<button class="carousel-control-next" type="button" data-bs-target="#screenshotsCarousel" data-bs-slide="next">
<span class="carousel-control-next-icon bg-dark bg-opacity-50 rounded" aria-hidden="true"></span>
<span class="visually-hidden">Next</span>
</button>
{% endif %}
</div>
{% if main_game.screenshots|length > 1 %}
<div class="d-flex justify-content-center mt-3">
<div class="d-flex flex-wrap gap-2" style="max-width: 100%; overflow-x: auto; padding-bottom: 10px;">
{% for screenshot in main_game.screenshots %}
<img
src="/images/playnite/{{ screenshot }}"
class="screenshot-thumbnail {{ loop.first ? 'active' : '' }}"
style="width: 80px; height: 45px; object-fit: cover; cursor: pointer; opacity: 0.7; transition: opacity 0.2s;"
data-bs-target="#screenshotsCarousel"
data-bs-slide-to="{{ loop.index0 }}"
onmouseover="this.style.opacity='1'"
onmouseout="this.style.opacity=this.classList.contains('active') ? '1' : '0.7'"
>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
<!-- Game Details -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title mb-3">Details</h5>
<div class="row">
{# Developer and Publisher #}
{% if version.developer or (version.metadata and version.metadata.developers is defined and version.metadata.developers|length > 0) %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Developer</h6>
<p class="mb-0">
{% if version.developer %}
{{ version.developer }}
{% elseif version.metadata and version.metadata.developers is defined and version.metadata.developers|length > 0 %}
{{ version.metadata.developers|join(', ') }}
{% endif %}
</p>
</div>
{% endif %}
{% if version.publisher or (version.metadata and version.metadata.publishers is defined and version.metadata.publishers|length > 0) %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Publisher</h6>
<p class="mb-0">
{% if version.publisher %}
{{ version.publisher }}
{% elseif version.metadata and version.metadata.publishers is defined and version.metadata.publishers|length > 0 %}
{{ version.metadata.publishers|join(', ') }}
{% endif %}
</p>
</div>
{% endif %}
{# Release Information #}
{% if version.release_date %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Release Date</h6>
<p class="mb-0">{{ version.release_date|date('M d, Y') }}</p>
</div>
{% endif %}
{# Age Rating #}
{% if version.age_rating %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Age Rating</h6>
<p class="mb-0">{{ version.age_rating }}</p>
</div>
{% endif %}
{# Region #}
{% if version.region %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Region</h6>
<p class="mb-0">{{ version.region }}</p>
</div>
{% endif %}
{# Version #}
{% if version.version %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Version</h6>
<p class="mb-0">{{ version.version }}</p>
</div>
{% endif %}
{# Play Time and Count #}
{% if version.playtime_minutes is defined and version.playtime_minutes > 0 %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Play Time</h6>
<p class="mb-0">
{% set hours = version.playtime_minutes // 60 %}
{% set minutes = version.playtime_minutes % 60 %}
{% if hours > 0 %}{{ hours }}h {% endif %}{% if minutes > 0 or hours == 0 %}{{ minutes }}m{% endif %}
</p>
</div>
{% endif %}
{% if version.play_count is not null %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Play Count</h6>
<p class="mb-0">{{ version.play_count }}</p>
</div>
{% endif %}
{# Game Saves #}
{% if version.save_count is not null %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Save Files</h6>
<p class="mb-0">{{ version.save_count }}</p>
</div>
{% endif %}
{# Installation and Size #}
{% if version.install_size %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Install Size</h6>
<p class="mb-0">
{% if version.install_size >= 1073741824 %}
{{ (version.install_size / 1073741824)|number_format(1) }} GB
{% else %}
{{ (version.install_size / 1048576)|number_format(0) }} MB
{% endif %}
</p>
</div>
{% endif %}
{# Completion Status #}
{% if version.completion_status %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Completion Status</h6>
<p class="mb-0">
{% set completionStatus = version.completion_status|lower %}
{% if completionStatus == 'completed' %}
<span class="badge bg-success">Completed</span>
{% elseif completionStatus == 'playing' %}
<span class="badge bg-primary">In Progress</span>
{% elseif completionStatus == 'notplayed' %}
<span class="badge bg-secondary">Not Played</span>
{% else %}
{{ completionStatus|capitalize }}
{% endif %}
{% if version.completion_percentage is not null %}
<span class="ms-2">{{ version.completion_percentage }}%</span>
{% endif %}
</p>
</div>
{% endif %}
{# Library Dates #}
{% if version.added_at %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Added to Library</h6>
<p class="mb-0">{{ version.added_at|date('M d, Y') }}</p>
</div>
{% endif %}
{% if version.modified_at %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Last Modified</h6>
<p class="mb-0">{{ version.modified_at|date('M d, Y') }}</p>
</div>
{% endif %}
{% if version.last_played_at %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Last Played</h6>
<p class="mb-0">
{{ version.last_played_at|date('M d, Y') }}
{% if version.last_activity %}
<small class="text-muted d-block">{{ version.last_activity }}</small>
{% endif %}
</p>
</div>
{% endif %}
{# Metadata #}
{% if version.metadata is defined and version.metadata is not empty %}
{% if version.metadata is iterable %}
{% set metadata = version.metadata %}
{% else %}
{% set metadata = version.metadata|json_decode(true) %}
{% if metadata is null or metadata is not iterable %}
{% set metadata = [] %}
{% endif %}
{% endif %}
{# Genres #}
{% if metadata.genres is defined and metadata.genres is iterable and metadata.genres|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Genres</h6>
<div class="d-flex flex-wrap gap-1">
{% for genre in metadata.genres %}
{% if genre is not empty %}
<span class="badge bg-primary bg-opacity-10 text-primary">{{ genre }}</span>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{# Tags (filtered) #}
{% if metadata.tags is defined and metadata.tags is iterable and metadata.tags|length > 0 %}
{% set filteredTags = metadata.tags|filter(tag => tag is not empty and not (tag starts with '[' and ']' in tag)) %}
{% if filteredTags|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Tags</h6>
<div class="d-flex flex-wrap gap-1">
{% for tag in filteredTags %}
<span class="badge bg-secondary bg-opacity-10 text-secondary">{{ tag }}</span>
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
{# Features #}
{% if metadata.features is defined and metadata.features is iterable and metadata.features|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Features</h6>
<div class="d-flex flex-wrap gap-1">
{% for feature in metadata.features %}
{% if feature is not empty %}
<span class="badge bg-success bg-opacity-10 text-success">{{ feature }}</span>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{# Platforms #}
{% if metadata.platforms is defined and metadata.platforms is iterable and metadata.platforms|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Available Platforms</h6>
<div class="d-flex flex-wrap gap-1">
{% for platform in metadata.platforms %}
{% if platform is not empty %}
<span class="badge bg-info bg-opacity-10 text-info">{{ platform }}</span>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{# Series #}
{% if metadata.series is defined and metadata.series is iterable and metadata.series|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Series</h6>
<div class="d-flex flex-wrap gap-1">
{% for series in metadata.series %}
{% if series is not empty %}
<span class="badge bg-purple bg-opacity-10 text-purple">{{ series }}</span>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{# Source #}
{% if metadata.source is defined and metadata.source is not empty %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Source</h6>
<p class="mb-0">{{ metadata.source }}</p>
</div>
{% endif %}
{# User Score #}
{% if metadata.user_score is defined and metadata.user_score is not empty %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">User Score</h6>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1 me-2" style="height: 6px;">
<div class="progress-bar bg-warning" role="progressbar"
style="width: {{ metadata.user_score * 10 }}%"
aria-valuenow="{{ metadata.user_score * 10 }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="small">{{ metadata.user_score }}/10</span>
</div>
</div>
{% endif %}
{# Community Score #}
{% if metadata.community_score is defined and metadata.community_score is not empty %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Community Score</h6>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1 me-2" style="height: 6px;">
<div class="progress-bar bg-info" role="progressbar"
style="width: {{ metadata.community_score }}%"
aria-valuenow="{{ metadata.community_score }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="small">{{ metadata.community_score }}%</span>
</div>
</div>
{% endif %}
{# Critic Score #}
{% if metadata.critic_score is defined and metadata.critic_score is not empty %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Critic Score</h6>
<div class="d-flex align-items-center">
<div class="progress flex-grow-1 me-2" style="height: 6px;">
<div class="progress-bar bg-success" role="progressbar"
style="width: {{ metadata.critic_score }}%"
aria-valuenow="{{ metadata.critic_score }}"
aria-valuemin="0"
aria-valuemax="100">
</div>
</div>
<span class="small">{{ metadata.critic_score }}%</span>
</div>
</div>
{% endif %}
{# Rating #}
{% if metadata.rating is defined and metadata.rating is not empty %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Rating</h6>
<p class="mb-0">
{% if metadata.rating == 'RP' %}
Rating Pending
{% elseif metadata.rating == 'EC' %}
Early Childhood
{% elseif metadata.rating == 'E' %}
Everyone
{% elseif metadata.rating == 'E10+' %}
Everyone 10+
{% elseif metadata.rating == 'T' %}
Teen
{% elseif metadata.rating == 'M' %}
Mature 17+
{% elseif metadata.rating == 'AO' %}
Adults Only 18+
{% else %}
{{ metadata.rating }}
{% endif %}
{% if metadata.rating_description is defined and metadata.rating_description is not empty %}
<small class="text-muted d-block">{{ metadata.rating_description }}</small>
{% endif %}
</p>
</div>
{% endif %}
{# Release Date #}
{% if metadata.release_date is defined and metadata.release_date is not empty %}
<div class="col-md-6 mb-3">
<h6 class="text-muted small mb-1">Original Release</h6>
<p class="mb-0">
{{ metadata.release_date }}
{% if metadata.release_year is defined and metadata.release_year is not empty %}
<small class="text-muted">({{ metadata.release_year }})</small>
{% endif %}
</p>
</div>
{% endif %}
{# Links #}
{% if metadata.links is defined and metadata.links is iterable and metadata.links|length > 0 %}
<div class="col-12 mb-3">
<h6 class="text-muted small mb-1">Links</h6>
<div class="d-flex flex-wrap gap-2">
{% for link in metadata.links %}
{% if link.name is defined and link.url is defined %}
<a href="{{ link.url }}" target="_blank" class="btn btn-sm btn-outline-secondary">
<i class="bi bi-box-arrow-up-right me-1"></i> {{ link.name }}
</a>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
<!-- Right Column -->
<div class="col-lg-4">
<!-- Platform Versions -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title mb-3">Available Platforms</h5>
<div class="list-group list-group-flush">
{% for version in platform_versions %}
<div class="list-group-item d-flex justify-content-between align-items-center">
<div>
<h6 class="mb-0">{{ version.platform }}</h6>
{% if version.source_name %}
<small class="text-muted">{{ version.source_name }}</small>
{% endif %}
</div>
{% if version.is_installed %}
<span class="badge bg-success rounded-pill">Installed</span>
{% endif %}
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Game Links -->
{% set playniteLinks = platform_versions[0].links_json|json_decode %}
{% if playniteLinks %}
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title mb-3">Links</h5>
<div class="d-grid gap-2">
{% for link in playniteLinks %}
<a href="{{ link.Url }}" target="_blank" class="btn btn-outline-secondary text-start">
<i class="bi bi-box-arrow-up-right me-2"></i> {{ link.Name }}
</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
<style>
.game-hero {
padding: 2rem 0;
position: relative;
margin-bottom: 2rem;
background-size: cover !important;
background-position: center !important;
}
.game-hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0.6) 100%);
z-index: 0;
}
.game-hero .container {
position: relative;
z-index: 1;
}
.game-description {
max-height: 150px;
overflow: hidden;
position: relative;
transition: max-height 0.3s ease;
}
.game-description.expanded {
max-height: 1000px;
}
.game-description::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 50px;
background: linear-gradient(to bottom, transparent, var(--bs-body-bg, #fff));
pointer-events: none;
transition: opacity 0.3s ease;
}
.game-description.expanded::after {
opacity: 0;
}
.nav-tabs {
--bs-nav-tabs-border-width: 0;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
padding-bottom: 4px;
align-items: center;
}
/* Only show scrollbar on mobile */
@media (max-width: 991.98px) {
.nav-tabs {
flex-wrap: nowrap;
overflow-x: auto;
scrollbar-width: thin;
}
.nav-tabs::-webkit-scrollbar {
height: 4px;
}
.nav-tabs::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 2px;
}
}
.nav-tabs .nav-link {
color: rgba(255, 255, 255, 0.7);
padding: 0.5rem 1rem;
border: none;
border-bottom: 2px solid transparent;
border-radius: 4px 4px 0 0;
transition: all 0.2s ease;
white-space: nowrap;
display: flex;
align-items: center;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.05);
margin-bottom: 0.25rem;
}
.nav-tabs .nav-link:hover:not(.active) {
color: white;
background: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.1);
}
.nav-tabs .nav-link.active {
color: white;
background: rgba(13, 110, 253, 0.15);
border-color: var(--bs-primary, #0d6efd);
font-weight: 500;
}
/* Platform icon colors */
.nav-tabs .nav-link iconify-icon[icon^="mdi:microsoft-windows"] {
color: #00a4ef;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:sony-playstation"] {
color: #003087;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:microsoft-xbox"] {
color: #107c10;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:nintendo-switch"],
.nav-tabs .nav-link iconify-icon[icon^="mdi:nintendo-wii"] {
color: #e60012;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:steam"] {
color: #00adee;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:apple"],
.nav-tabs .nav-link iconify-icon[icon^="mdi:apple-ios"] {
color: #a2aaad;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:android"] {
color: #3ddc84;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:linux"] {
color: #fcc624;
}
.nav-tabs .nav-link iconify-icon[icon^="mdi:gamepad"] {
color: #8e8e93;
}
/* Screenshot carousel styles */
.carousel-item {
height: 400px;
background-color: #1a1a1a;
display: flex;
align-items: center;
justify-content: center;
}
.screenshot-thumbnail {
border: 2px solid transparent;
transition: all 0.2s ease;
}
.screenshot-thumbnail:hover,
.screenshot-thumbnail.active {
border-color: var(--bs-primary);
opacity: 1 !important;
}
/* Custom scrollbar for thumbnails */
.d-flex.flex-wrap.gap-2::-webkit-scrollbar {
height: 6px;
}
.d-flex.flex-wrap.gap-2::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.2);
border-radius: 3px;
}
.card {
border: 1px solid rgba(0, 0, 0, 0.1);
box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.05);
}
.card-title {
font-weight: 600;
color: var(--bs-heading-color, #212529);
}
.breadcrumb {
background: transparent;
padding: 0;
margin-bottom: 1rem;
}
.breadcrumb-item a {
text-decoration: none;
}
.breadcrumb-item.active {
color: white;
}
.list-group-item {
border-left: none;
border-right: none;
padding: 0.75rem 0;
}
.list-group-item:first-child {
border-top: none;
padding-top: 0;
}
.list-group-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize screenshot thumbnails
initScreenshotThumbnails();
// Initialize all tooltips
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl);
});
// Function to update game cover image
function updateGameCover(imageUrl) {
const coverContainer = document.getElementById('gameCover');
if (!imageUrl) {
coverContainer.innerHTML = `
<div class="bg-dark rounded-3 d-flex align-items-center justify-content-center" style="width: 200px; height: 300px;">
<i class="bi bi-controller text-muted" style="font-size: 3rem;"></i>
</div>`;
return;
}
coverContainer.innerHTML = `
<img src="${imageUrl}"
class="img-fluid rounded-3 shadow-lg game-cover"
alt="${document.title}"
style="max-height: 300px; width: auto;">`;
}
// Function to update hero background
function updateHeroBackground(imageUrl) {
const hero = document.getElementById('gameHero');
if (imageUrl) {
hero.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.7)), url('${imageUrl}')`;
} else {
hero.style.backgroundImage = "linear-gradient(rgba(0,0,0,0.7), rgba(0,0,0,0.7)), url('/images/hero-bg.jpg')";
}
}
// Function to update stats in the sidebar
function updateStats(platformData) {
// Update last played
const lastPlayedEl = document.querySelector('.stats-last-played');
if (lastPlayedEl) {
lastPlayedEl.textContent = platformData.lastPlayed || 'Never';
}
// Update playtime
const playtimeEl = document.querySelector('.stats-playtime');
if (playtimeEl) {
playtimeEl.textContent = platformData.playtime || '0h 0m';
}
// Update completion
const completionEl = document.querySelector('.stats-completion');
const completionPctEl = document.querySelector('.stats-completion-percent');
const progressBar = document.querySelector('.stats-progress-bar');
if (platformData.completionPercentage > 0) {
if (completionEl) completionEl.style.display = 'flex';
if (completionPctEl) completionPctEl.textContent = `${platformData.completionPercentage}%`;
if (progressBar) progressBar.style.width = `${platformData.completionPercentage}%`;
} else {
if (completionEl) completionEl.style.display = 'none';
}
// Update cover image if available
if (platformData.coverImage) {
updateGameCover(platformData.coverImage);
} else {
// Fallback to main game image if no platform-specific image
updateGameCover('{{ main_game.image_url ? "/images/playnite/" ~ main_game.image_url : "" }}');
}
// Update hero background if available
if (platformData.heroImage) {
updateHeroBackground(platformData.heroImage);
} else {
updateHeroBackground('');
}
}
// Platform tab switching
const platformTabs = document.querySelectorAll('.platform-tab');
platformTabs.forEach(tab => {
tab.addEventListener('click', function() {
// Update active tab styling
platformTabs.forEach(t => {
t.classList.remove('active', 'border-primary', 'text-white');
t.classList.add('text-white-50');
});
this.classList.remove('text-white-50');
this.classList.add('active', 'text-white');
// Get the platform data from the tab
const platformData = {
lastPlayed: this.dataset.lastPlayed || 'Never',
playtime: this.dataset.playtime || '0h 0m',
completionPercentage: parseInt(this.dataset.completion || '0'),
coverImage: this.dataset.coverImage || '',
heroImage: this.dataset.banner_url || ''
};
// Update the UI with platform-specific data
updateStats(platformData);
});
});
// Read more/less functionality
const readMoreBtns = document.querySelectorAll('.read-more-btn');
readMoreBtns.forEach(btn => {
const description = btn.previousElementSibling;
btn.addEventListener('click', function() {
description.classList.toggle('expanded');
this.textContent = description.classList.contains('expanded') ? 'Show less' : 'Read more';
});
});
});
</script>
{% endblock %}