Files
offline-db/templates/home.html
google-labs-jules[bot] 77ea4bc6f9 ✨ FEATURE: Implemented Dynamic Trending and Social Comment Systems
- 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.
2026-01-16 09:12:57 +00:00

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 %}