mirror of
https://github.com/ceratic/MediaCollectorLibary.git
synced 2026-05-13 23:56:46 +02:00
1001 lines
51 KiB
Twig
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 %} |