mirror of
https://github.com/ld3z/new-manga.git
synced 2026-11-09 21:03:45 -07:00
180 lines
No EOL
6.4 KiB
Text
180 lines
No EOL
6.4 KiB
Text
---
|
|
import type { Comic, Genre } from '../lib/types';
|
|
import { Image } from 'astro:assets';
|
|
import { getGenreNames } from '../lib/api';
|
|
|
|
interface Props {
|
|
comic: Comic;
|
|
genres: Genre[];
|
|
}
|
|
|
|
const { comic, genres } = Astro.props;
|
|
const genreNames = getGenreNames(comic.md_comics.genres, genres);
|
|
const isAvif = comic.md_comics.md_covers[0]?.b2key.endsWith('.avif');
|
|
const coverUrl = comic.md_comics.md_covers[0]
|
|
? `https://meo.comick.pictures/${comic.md_comics.md_covers[0].b2key}`
|
|
: '/placeholder.png';
|
|
const isNSFW = comic.md_comics.content_rating === 'erotica';
|
|
---
|
|
|
|
<div class="group relative bg-secondary rounded-xl overflow-visible shadow-lg transition-all duration-300 hover:-translate-y-2 hover:shadow-xl h-full flex flex-col max-w-[280px] mx-auto border border-hover/30">
|
|
<a href={`https://comick.io/comic/${comic.md_comics.slug}`} target="_blank" rel="noopener noreferrer" class="flex flex-col h-full">
|
|
<div class="relative overflow-hidden aspect-[2/3] rounded-t-xl">
|
|
{!isAvif ? (
|
|
<Image
|
|
src={coverUrl}
|
|
alt={comic.md_comics.title}
|
|
inferSize={true}
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
/>
|
|
) : (
|
|
<img
|
|
src={coverUrl}
|
|
alt={comic.md_comics.title}
|
|
width="280"
|
|
height="420"
|
|
class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
|
/>
|
|
)}
|
|
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
|
|
|
|
{isNSFW && (
|
|
<div class="absolute top-2 right-2 bg-red-500/90 text-white px-2 py-1 rounded text-xs font-bold shadow-lg backdrop-blur-[2px]">
|
|
NSFW
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div class="p-4 md:p-5 flex-grow flex flex-col bg-secondary relative rounded-b-xl">
|
|
<div class="absolute -top-8 left-3 right-3 bg-secondary/95 backdrop-blur-[12px] p-3 rounded-lg shadow-lg border border-hover/30">
|
|
<h3 class="text-lg font-bold line-clamp-1 text-center text-primary drop-shadow-[0_1px_1px_rgba(0,0,0,0.5)]">
|
|
{comic.md_comics.title}
|
|
</h3>
|
|
</div>
|
|
|
|
<div class="mt-6">
|
|
<p class="inline-block bg-accent/20 text-accent px-4 py-1 rounded-full text-sm font-semibold">
|
|
Chapter {comic.chap}
|
|
</p>
|
|
|
|
{genreNames.length > 0 && (
|
|
<div class="relative mt-3" aria-label="Comic genres">
|
|
<div class="flex flex-wrap gap-2">
|
|
{genreNames.slice(0, 3).map(genre => (
|
|
<span class="bg-hover text-primary/80 px-3 py-1 rounded-full text-xs font-medium truncate transition-all hover:bg-hover/80" title={genre}>
|
|
{genre}
|
|
</span>
|
|
))}
|
|
{genreNames.length > 3 && (
|
|
<button
|
|
type="button"
|
|
class="expand-tags-btn bg-hover text-primary/80 px-3 py-1 rounded-full text-xs font-medium hover:bg-hover/80 cursor-pointer transition-colors"
|
|
aria-expanded="false"
|
|
data-comic-id={comic.id}
|
|
>
|
|
+{genreNames.length - 3} more
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{genreNames.length > 3 && (
|
|
<div
|
|
id={`tags-${comic.id}`}
|
|
class="expanded-tags bg-secondary/95 backdrop-blur-sm p-3 rounded-lg shadow-lg border border-hover/30 hidden"
|
|
role="tooltip"
|
|
>
|
|
<div class="flex flex-wrap gap-2">
|
|
{genreNames.slice(3).map(genre => (
|
|
<span class="bg-hover text-primary/80 px-3 py-1 rounded-full text-xs font-medium truncate transition-all hover:bg-hover/80" title={genre}>
|
|
{genre}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<div class="mt-auto pt-4 flex items-center justify-between text-sm text-primary/60 border-t border-hover/30">
|
|
<time datetime={comic.updated_at}>
|
|
{new Date(comic.updated_at).toLocaleString(undefined, {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
})}
|
|
</time>
|
|
<span class="group-hover:text-accent transition-colors duration-300">Read Now →</span>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
|
|
<script>
|
|
import { computePosition, flip, shift, offset } from '@floating-ui/dom';
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const buttons = document.querySelectorAll('.expand-tags-btn');
|
|
|
|
buttons.forEach(button => {
|
|
const comicId = button.getAttribute('data-comic-id');
|
|
const tooltip = document.getElementById(`tags-${comicId}`);
|
|
|
|
if (!tooltip) return;
|
|
|
|
function updatePosition() {
|
|
computePosition(button, tooltip, {
|
|
placement: 'bottom',
|
|
middleware: [
|
|
offset(8),
|
|
flip(),
|
|
shift({ padding: 8 })
|
|
],
|
|
}).then(({x, y}) => {
|
|
Object.assign(tooltip.style, {
|
|
left: `${x}px`,
|
|
top: `${y}px`,
|
|
});
|
|
});
|
|
}
|
|
|
|
function showTooltip() {
|
|
tooltip.classList.remove('hidden');
|
|
updatePosition();
|
|
}
|
|
|
|
function hideTooltip() {
|
|
tooltip.classList.add('hidden');
|
|
}
|
|
|
|
button.addEventListener('click', (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
|
|
const isHidden = tooltip.classList.contains('hidden');
|
|
|
|
if (isHidden) {
|
|
showTooltip();
|
|
button.textContent = 'Show less';
|
|
|
|
// Close when clicking outside
|
|
const closeHandler = (event: MouseEvent) => {
|
|
if (!tooltip.contains(event.target as Node) && !button.contains(event.target as Node)) {
|
|
hideTooltip();
|
|
button.textContent = `+${tooltip.querySelectorAll('span').length} more`;
|
|
document.removeEventListener('click', closeHandler);
|
|
}
|
|
};
|
|
|
|
setTimeout(() => {
|
|
document.addEventListener('click', closeHandler);
|
|
}, 0);
|
|
} else {
|
|
hideTooltip();
|
|
button.textContent = `+${tooltip.querySelectorAll('span').length} more`;
|
|
}
|
|
});
|
|
});
|
|
});
|
|
</script> |