- Header: sticky app-wide, active nav highlighting, data-driven theme picker with active swatch, unified SVG icons, deduped login fragment (was duplicated verbatim for desktop + mobile), removed stray console.log - LibrarySwitcher: cleaner sticky bar, SVG action icons, theme-aware - BookCard: the atomic unit now has a real surface (was floating text when wood-paneling was off due to an undefined --wood-border), with cohesive shadow + hover lift; reused across dashboard/bookshelf/series - Dashboard: calmer section headers, snap carousels with SVG chevrons - Bookshelf: recompose the filter wall into a search toolbar + collapsible Filters panel (all 9 filters + save/load/clear preserved), redesigned empty state and pagination - Book detail, series, collections, browse: cohesive cards, SVG action icons, uppercase muted labels, theme-aware badges/links
84 lines
3.3 KiB
Templ
84 lines
3.3 KiB
Templ
package templates
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
)
|
|
|
|
templ Series(user User, seriesList []SeriesCardData, libData []LibraryData, currentLibraryID string, totalPages int, currentPage int, errorMessage string) {
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8"/>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
<title>Series - Bookhoard</title>
|
|
<link href="/static/style.css" rel="stylesheet"/>
|
|
</head>
|
|
<body x-data="seriesPage" x-init="initSeriesPage()" class="theme-{ user.Theme }">
|
|
@Header(user, "/series")
|
|
@LibrarySwitcher(libData, currentLibraryID)
|
|
<div id="series-container">
|
|
<main class="w-full px-4 sm:px-6 lg:px-8 py-8">
|
|
<div class="mb-6 flex items-center gap-2">
|
|
@Icon("layers", "h-6 w-6 text-brand")
|
|
<h1 class="text-2xl font-bold tracking-tight text-content">Series</h1>
|
|
</div>
|
|
if len(seriesList) == 0 {
|
|
<div id="series-empty" class="flex flex-col items-center justify-center rounded-2xl border border-dashed border-line py-20 text-center">
|
|
<div class="mb-3 flex h-14 w-14 items-center justify-center rounded-full bg-surface-hover text-content-muted">
|
|
@Icon("layers", "h-7 w-7")
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-content">No Series Found</h3>
|
|
<p class="mt-1 text-sm text-content-muted">Books with series metadata will appear here.</p>
|
|
</div>
|
|
}
|
|
<div id="series-grid" class="grid grid-cols-2 gap-4 md:grid-cols-4 lg:grid-cols-6">
|
|
for _, series := range seriesList {
|
|
@SeriesCard(series)
|
|
}
|
|
</div>
|
|
if totalPages > 1 {
|
|
<div id="series-pagination" class="mt-8 flex items-center justify-center gap-3">
|
|
if currentPage > 1 {
|
|
<a href={ fmt.Sprintf("/series?library_id=%s&page=%d", currentLibraryID, currentPage-1) } class="btn btn-secondary">
|
|
@Icon("chevron-left", "h-4 w-4")
|
|
Previous
|
|
</a>
|
|
}
|
|
<span class="text-sm text-content-muted">Page { fmt.Sprintf("%d", currentPage) } of { fmt.Sprintf("%d", totalPages) }</span>
|
|
if currentPage < totalPages {
|
|
<a href={ fmt.Sprintf("/series?library_id=%s&page=%d", currentLibraryID, currentPage+1) } class="btn btn-secondary">
|
|
Next
|
|
@Icon("chevron-right", "h-4 w-4")
|
|
</a>
|
|
}
|
|
</div>
|
|
}
|
|
</main>
|
|
</div>
|
|
@ErrorToast(errorMessage)
|
|
</body>
|
|
</html>
|
|
}
|
|
|
|
templ SeriesCard(series SeriesCardData) {
|
|
<a href={ "/series/detail?name=" + url.QueryEscape(series.Name) } class="block">
|
|
<div class="book-card series-card overflow-hidden">
|
|
<div class={ "stacked-covers cover-count-" + fmt.Sprintf("%d", len(series.CoverPaths)) }>
|
|
for i, cover := range series.CoverPaths {
|
|
<img src={ cover } class={ "cover-img cover-" + fmt.Sprintf("%d", i) } alt={ series.Name } loading="lazy" onerror="this.src='/static/placeholder-book.svg'"/>
|
|
}
|
|
if len(series.CoverPaths) == 0 {
|
|
<div class="cover-img cover-0 flex items-center justify-center bg-surface-hover text-content-muted">
|
|
@Icon("book", "h-8 w-8")
|
|
</div>
|
|
}
|
|
</div>
|
|
<div class="book-card-meta">
|
|
<h3 class="line-clamp-2 text-sm font-semibold leading-snug text-content">{ series.Name }</h3>
|
|
<p class="mt-0.5 text-xs text-content-muted">{ fmt.Sprintf("%d", series.BookCount) } of { fmt.Sprintf("%d", series.TotalInSeries) } books</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
}
|