fix: replace absolute positioning with grid stacking in anime card

This commit is contained in:
2026-04-21 01:30:09 +02:00
parent 28cacdd7c5
commit c7906ba324

View File

@@ -20,58 +20,28 @@ type AnimeCardProps struct {
Synopsis string // optional synopsis for hover detail Synopsis string // optional synopsis for hover detail
PlayHref string // optional play button href (anchored to poster) PlayHref string // optional play button href (anchored to poster)
// Watchlist integration // Watchlist integration
TitleEnglish string TitleEnglish string
TitleJapanese string TitleJapanese string
Airing bool Airing bool
WatchlistStatus string // empty if not in watchlist WatchlistStatus string // empty if not in watchlist
DisableWatchlist bool // if true, don't render the watchlist button DisableWatchlist bool // if true, don't render the watchlist button
} }
templ AnimeCard(props AnimeCardProps) { templ AnimeCard(props AnimeCardProps) {
<div class={ defaultString(props.Class, "group relative min-w-0") }> <div class={ defaultString(props.Class, "group min-w-0") }>
if props.CurrentNode { @animeCardPoster(props)
@animeCardPoster(props) if !props.HideTitle {
if !props.HideTitle { if props.CurrentNode {
<div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-sm leading-snug text-(--text)") }> <div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-sm leading-snug text-(--text)") }>
{ props.Title } { props.Title }
</div> </div>
} } else {
} else { <a href={ templ.URL(cardHref(props)) } class="block">
<a href={ templ.URL(cardHref(props)) } class="block">
@animeCardPoster(props)
if !props.HideTitle {
<div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-sm leading-snug text-(--text)") }> <div class={ defaultString(props.TitleClass, "mt-2 line-clamp-2 text-sm leading-snug text-(--text)") }>
{ props.Title } { props.Title }
</div> </div>
} </a>
</a> }
}
if props.PlayHref != "" || !props.CurrentNode && !props.DisableWatchlist {
<div class="absolute bottom-2 left-2 z-20 flex gap-2 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
if props.PlayHref != "" {
<a
href={ templ.URL(props.PlayHref) }
class="text-white"
aria-label="Play"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Play</title>
<path d="M8 5V19L19 12L8 5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
</svg>
</a>
}
if !props.CurrentNode && !props.DisableWatchlist {
@watchlist.CardButton(
props.ID,
props.Title,
props.TitleEnglish,
props.TitleJapanese,
props.ImageURL,
props.Airing,
props.WatchlistStatus != "",
)
}
</div>
} }
{ children... } { children... }
</div> </div>
@@ -81,31 +51,73 @@ func cardHref(props AnimeCardProps) string {
if props.Href != "" { if props.Href != "" {
return props.Href return props.Href
} }
return fmt.Sprintf("/anime/%d", props.ID) return fmt.Sprintf("/anime/%d", props.ID)
} }
templ animeCardPoster(props AnimeCardProps) { templ animeCardPoster(props AnimeCardProps) {
<div class="relative w-full aspect-2/3 overflow-hidden"> <div class="grid w-full aspect-2/3 overflow-hidden">
<div class="block h-full w-full"> <!-- Base image layer -->
if props.ImageURL != "" { if !props.CurrentNode {
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "block h-full w-full object-cover object-center") } loading="lazy"/> <a href={ templ.URL(cardHref(props)) } class="col-start-1 row-start-1 block h-full w-full">
} else { @animeCardImage(props)
<div class="flex h-full w-full justify-center overflow-hidden text-transparent">No image</div> </a>
} } else {
</div> <div class="col-start-1 row-start-1 h-full w-full">
<div class="absolute inset-0 bg-black/0 transition-colors duration-200 group-hover:bg-black/40"></div> @animeCardImage(props)
</div>
}
<!-- Hover darken overlay -->
<div class="pointer-events-none col-start-1 row-start-1 bg-black/0 transition-colors duration-200 group-hover:bg-black/40"></div>
<!-- Synopsis overlay -->
if props.Synopsis != "" { if props.Synopsis != "" {
<div class="absolute inset-0 flex flex-col justify-between p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100"> <div class="pointer-events-none col-start-1 row-start-1 flex flex-col justify-between p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<div> <div>
<div class="mb-1 text-[11px] font-semibold text-white/90">{ props.Title }</div> <div class="mb-1 text-[11px] font-semibold text-white/90">{ props.Title }</div>
<p class="line-clamp-3 text-[11px] leading-relaxed text-white/90">{ props.Synopsis }</p> <p class="line-clamp-3 text-[11px] leading-relaxed text-white/90">{ props.Synopsis }</p>
</div> </div>
</div> </div>
} }
<!-- Actions layer -->
if props.PlayHref != "" || !props.CurrentNode && !props.DisableWatchlist {
<div class="col-start-1 row-start-1 flex items-end justify-start p-3 opacity-0 transition-opacity duration-200 group-hover:opacity-100">
<div class="flex gap-2">
if props.PlayHref != "" {
<a
href={ templ.URL(props.PlayHref) }
class="text-white"
aria-label="Play"
>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<title>Play</title>
<path d="M8 5V19L19 12L8 5Z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
</svg>
</a>
}
if !props.CurrentNode && !props.DisableWatchlist {
@watchlist.CardButton(
props.ID,
props.Title,
props.TitleEnglish,
props.TitleJapanese,
props.ImageURL,
props.Airing,
props.WatchlistStatus != "",
)
}
</div>
</div>
}
</div> </div>
} }
templ animeCardImage(props AnimeCardProps) {
if props.ImageURL != "" {
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "block h-full w-full object-cover object-center") } loading="lazy"/>
} else {
<div class="flex h-full w-full justify-center overflow-hidden text-transparent">No image</div>
}
}
func defaultString(val, fallback string) string { func defaultString(val, fallback string) string {
if val == "" { if val == "" {
return fallback return fallback