mirror of
https://github.com/barkeser2002/offline-db.git
synced 2026-09-25 06:39:51 +03:00
- Added 'Trending Now' engine based on 7-day watch history. - Built a secure comment section with spoiler support and XSS prevention. - Created new 'social' blueprint and expanded database schema. - Updated Home and Player UI for dynamic content integration.
192 lines
9.4 KiB
HTML
192 lines
9.4 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block content %}
|
|
<!-- Hero Section / Database Stats -->
|
|
<section class="bg-gray-800 p-6 rounded-lg mb-6 shadow-lg border border-gray-700">
|
|
<h2 class="text-2xl font-semibold mb-4 text-blue-400">Database Stats</h2>
|
|
<div class="grid grid-cols-3 gap-4 text-center">
|
|
<div class="p-4 bg-gray-900 rounded-lg">
|
|
<p class="text-4xl font-bold text-blue-500">{{ stats.anime_count }}</p>
|
|
<p class="text-gray-400 uppercase text-xs tracking-widest mt-1">Animes</p>
|
|
</div>
|
|
<div class="p-4 bg-gray-900 rounded-lg">
|
|
<p class="text-4xl font-bold text-green-500">{{ stats.episode_count }}</p>
|
|
<p class="text-gray-400 uppercase text-xs tracking-widest mt-1">Episodes</p>
|
|
</div>
|
|
<div class="p-4 bg-gray-900 rounded-lg">
|
|
<p class="text-4xl font-bold text-purple-500">{{ stats.video_count }}</p>
|
|
<p class="text-gray-400 uppercase text-xs tracking-widest mt-1">Video Links</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Trending Section -->
|
|
<section class="mb-8" id="trending-section" style="display: none;">
|
|
<h2 class="text-2xl font-semibold mb-4 border-l-4 border-orange-500 pl-4 flex items-center">
|
|
<svg class="w-6 h-6 mr-2 text-orange-500" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M12.395 2.553a1 1 0 00-1.45-.385c-.345.23-.614.558-.822.88-.214.33-.403.713-.57 1.116-.334.804-.614 1.768-.84 2.734a31.365 31.365 0 00-.613 3.58 2.64 2.64 0 01-.945-1.067c-.328-.68-.398-1.534-.398-2.654A1 1 0 005.05 6.05 6.981 6.981 0 003 11a7 7 0 1014 0c0-1.187-.249-2.315-.702-3.333a1 1 0 00-1.558-.285l-1.457 1.27a9.188 9.188 0 01-.312-3.67c.198-.81.498-1.582.76-2.108a1 1 0 00-.337-1.301z" clip-rule="evenodd"></path></svg>
|
|
Trending Now
|
|
</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4" id="trending-container">
|
|
<!-- Loaded via JS -->
|
|
</div>
|
|
</section>
|
|
|
|
{% if session.user_id %}
|
|
<!-- Continue Watching Section -->
|
|
{% if user_history %}
|
|
<section class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4 border-l-4 border-yellow-500 pl-4">Continue Watching</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
{% for entry in user_history %}
|
|
<a href="/player?mal_id={{ entry.mal_id }}&ep={{ entry.episode_number }}" class="anime-card bg-gray-800 rounded-lg overflow-hidden shadow-lg border border-gray-700">
|
|
<div class="relative">
|
|
<img src="{{ entry.cover_local or entry.cover_url }}" alt="{{ entry.title }}" class="w-full h-48 object-cover">
|
|
<div class="absolute bottom-0 left-0 w-full bg-blue-600 h-1" style="width: {{ entry.progress_percent }}%"></div>
|
|
</div>
|
|
<div class="p-3">
|
|
<h3 class="font-bold text-sm truncate">{{ entry.title }}</h3>
|
|
<p class="text-gray-400 text-xs">Episode {{ entry.episode_number }}</p>
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
|
|
<!-- My Watchlist Section -->
|
|
{% if user_watchlist %}
|
|
<section class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4 border-l-4 border-blue-500 pl-4">My Watchlist</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
{% for entry in user_watchlist %}
|
|
<a href="/player?mal_id={{ entry.mal_id }}" class="anime-card bg-gray-800 rounded-lg overflow-hidden shadow-lg border border-gray-700">
|
|
<img src="{{ entry.cover_local or entry.cover_url }}" alt="{{ entry.title }}" class="w-full h-48 object-cover">
|
|
<div class="p-3">
|
|
<h3 class="font-bold text-sm truncate">{{ entry.title }}</h3>
|
|
<div class="flex justify-between items-center mt-1">
|
|
<span class="text-xs text-blue-400 px-2 py-0.5 bg-blue-900/50 rounded">{{ entry.status.replace('-', ' ').title() }}</span>
|
|
<span class="text-xs text-gray-400">⭐ {{ entry.score }}</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endif %}
|
|
{% endif %}
|
|
|
|
<section class="bg-gray-800 p-6 rounded-lg mb-8 shadow-lg border border-gray-700">
|
|
<h2 class="text-2xl font-semibold mb-4 text-green-400">Browse by Season</h2>
|
|
<div class="flex flex-wrap gap-2">
|
|
{% for s in seasons[:15] %}
|
|
<a href="/season/{{ s.year }}/{{ s.season }}" class="bg-gray-700 hover:bg-blue-600 text-white text-sm font-medium py-1.5 px-3 rounded transition">
|
|
{{ s.year }} {{ s.season.title() }}
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Top Anime Section -->
|
|
<section class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4 border-l-4 border-red-500 pl-4">Top Anime</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
{% for anime in top_anime %}
|
|
<a href="/player?mal_id={{ anime.mal_id }}" class="anime-card bg-gray-800 rounded-lg overflow-hidden shadow-lg border border-gray-700">
|
|
<img src="{{ anime.images.jpg.large_image_url }}" alt="{{ anime.title }}" class="w-full h-64 object-cover">
|
|
<div class="p-4">
|
|
<h3 class="font-bold text-lg truncate">{{ anime.title }}</h3>
|
|
<p class="text-gray-400 text-sm">Score: {{ anime.score }}</p>
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Recent Recommendations Section -->
|
|
<section class="mb-8">
|
|
<h2 class="text-2xl font-semibold mb-4 border-l-4 border-green-500 pl-4">Recent Recommendations</h2>
|
|
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
{% for entry in recommendations %}
|
|
{% for anime in entry.entry %}
|
|
<a href="/player?mal_id={{ anime.mal_id }}" class="anime-card bg-gray-800 rounded-lg overflow-hidden shadow-lg border border-gray-700">
|
|
<img src="{{ anime.images.jpg.large_image_url }}" alt="{{ anime.title }}" class="w-full h-64 object-cover">
|
|
<div class="p-4">
|
|
<h3 class="font-bold text-lg truncate">{{ anime.title }}</h3>
|
|
</div>
|
|
</a>
|
|
{% endfor %}
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
async function loadTrending() {
|
|
const section = document.getElementById('trending-section');
|
|
const container = document.getElementById('trending-container');
|
|
|
|
try {
|
|
const resp = await fetch('/api/trending?limit=5');
|
|
const data = await resp.json();
|
|
|
|
if (data.length > 0) {
|
|
section.style.display = 'block';
|
|
container.innerHTML = '';
|
|
data.forEach(anime => {
|
|
const card = document.createElement('a');
|
|
card.href = `/player?mal_id=${anime.mal_id}`;
|
|
card.className = 'anime-card bg-gray-800 rounded-lg overflow-hidden shadow-lg border border-gray-700 group';
|
|
|
|
const imgContainer = document.createElement('div');
|
|
imgContainer.className = 'relative overflow-hidden';
|
|
|
|
const img = document.createElement('img');
|
|
img.src = anime.cover || '';
|
|
img.alt = anime.title;
|
|
img.className = 'w-full h-64 object-cover transform group-hover:scale-110 transition duration-500';
|
|
|
|
const badge = document.createElement('div');
|
|
badge.className = 'absolute top-2 right-2 bg-orange-600 text-white text-[10px] font-bold px-2 py-1 rounded shadow-lg';
|
|
badge.textContent = 'TRENDING';
|
|
|
|
imgContainer.appendChild(img);
|
|
imgContainer.appendChild(badge);
|
|
|
|
const info = document.createElement('div');
|
|
info.className = 'p-4';
|
|
|
|
const title = document.createElement('h3');
|
|
title.className = 'font-bold text-lg truncate group-hover:text-orange-400 transition';
|
|
title.textContent = anime.title;
|
|
|
|
const meta = document.createElement('div');
|
|
meta.className = 'flex justify-between items-center mt-1';
|
|
|
|
const type = document.createElement('span');
|
|
type.className = 'text-xs text-gray-400';
|
|
type.textContent = anime.type || 'TV';
|
|
|
|
const score = document.createElement('span');
|
|
score.className = 'text-xs text-orange-400 font-bold';
|
|
score.textContent = `⭐ ${anime.score || 'N/A'}`;
|
|
|
|
meta.appendChild(type);
|
|
meta.appendChild(score);
|
|
info.appendChild(title);
|
|
info.appendChild(meta);
|
|
|
|
card.appendChild(imgContainer);
|
|
card.appendChild(info);
|
|
container.appendChild(card);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
console.error("Failed to load trending:", e);
|
|
}
|
|
}
|
|
|
|
loadTrending();
|
|
</script>
|
|
{% endblock %}
|