feat(ui): migrate remaining pages to the new shell

Apply the sidebar shell and bold primitives across the rest of the app so
the whole experience shares one visual language:

- Browse/organize: series (keeps stacked-covers/series-card CSS),
  collections (keeps wood-paneling + carousel classes), browse_detail.
- Account/stats: profile + form + modal, progress, analytics (stat-card
  tiles), devices, conflicts (status semantics), queue (status/priority
  badges).
- Admin: admin sidebar restyled with icons, dashboard/users/library/
  processing-issues/settings migrated to .card/.btn/.input primitives.
- Docs/setup/misc: docs (keeps prose/highlight.js), api_explorer, setup
  wizard, unlinked_books, custom_section builder.
- Modals/fragments: collection_modal, collection_rules,
  restore_system_collection_modal, filter_item, book_detail_modals — all
  overlays use --surface-overlay + --shadow-pop.

Every Alpine handler, HTMX attribute, id, name, and data-* is preserved;
only presentation changes.
This commit is contained in:
2026-08-05 16:45:02 -04:00
parent 07578e0206
commit 8ea70ea7f8
62 changed files with 5887 additions and 3267 deletions
+26 -18
View File
@@ -17,15 +17,24 @@ templ Series(user User, seriesList []SeriesCardData, libData []LibraryData, curr
<body x-data="seriesPage" x-init="initSeriesPage()" class="theme-{ user.Theme }">
@Header(user, "/series")
@LibrarySwitcher(libData, currentLibraryID)
<!-- Series Grid -->
<div id="series-container">
<main class="w-full px-4 py-8">
<h1 class="text-3xl font-bold mb-6" style="color: var(--text-primary)">Series</h1>
<main class="mx-auto container px-4 sm:px-6 lg:px-8 py-8">
<div class="mb-6 flex items-center gap-3">
<span class="grid place-items-center h-10 w-10 rounded-xl" style="background-color: var(--accent-muted); color: var(--accent);">
@Icon("layers", "h-5 w-5")
</span>
<div>
<h1 class="text-2xl font-bold tracking-tight" style="color: var(--text-primary)">Series</h1>
<p class="text-xs font-semibold uppercase tracking-wide" style="color: var(--text-secondary)">Grouped by series metadata</p>
</div>
</div>
if len(seriesList) == 0 {
<div id="series-empty" class="text-center py-16" style="color: var(--text-secondary)">
<div class="text-6xl mb-4">📚</div>
<div id="series-empty" class="card text-center py-16 px-6">
<div class="grid place-items-center h-14 w-14 rounded-2xl mx-auto mb-4" style="background-color: var(--accent-muted); color: var(--accent);">
@Icon("layers", "h-7 w-7")
</div>
<h3 class="text-xl font-semibold mb-2" style="color: var(--text-primary)">No Series Found</h3>
<p>Books with series metadata will appear here</p>
<p style="color: var(--text-secondary)">Books with series metadata will appear here</p>
</div>
}
<div id="series-grid" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-6 gap-6">
@@ -33,28 +42,27 @@ templ Series(user User, seriesList []SeriesCardData, libData []LibraryData, curr
@SeriesCard(series)
}
</div>
<!-- Pagination -->
if totalPages > 1 {
<div id="series-pagination" class="flex justify-center items-center gap-4 mt-8">
<div id="series-pagination" class="flex justify-center items-center gap-3 mt-10">
if currentPage > 1 {
<a
href={ fmt.Sprintf("/series?library_id=%s&page=%d", currentLibraryID, currentPage-1) }
class="px-4 py-2 rounded-lg border"
style="border-color: var(--border); color: var(--text-primary);"
class="btn btn-secondary"
>
Previous
@Icon("chevron-left", "h-4 w-4")
<span>Previous</span>
</a>
}
<span class="text-sm" style="color: var(--text-secondary)">
<span class="chip">
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="px-4 py-2 rounded-lg border"
style="border-color: var(--border); color: var(--text-primary);"
class="btn btn-secondary"
>
Next
<span>Next</span>
@Icon("chevron-right", "h-4 w-4")
</a>
}
</div>
@@ -68,7 +76,7 @@ templ Series(user User, seriesList []SeriesCardData, libData []LibraryData, curr
templ SeriesCard(series SeriesCardData) {
<a href={ "/series/detail?name=" + url.QueryEscape(series.Name) } class="block">
<div class="series-card rounded-lg overflow-hidden cursor-pointer hover:shadow-lg transition-shadow" style="background-color: var(--bg-secondary);">
<div class="series-card card rounded-2xl overflow-hidden cursor-pointer" style="box-shadow: var(--shadow-card);">
<div class={ "stacked-covers cover-count-" + fmt.Sprintf("%d", len(series.CoverPaths)) }>
for i, cover := range series.CoverPaths {
<img
@@ -80,8 +88,8 @@ templ SeriesCard(series SeriesCardData) {
/>
}
if len(series.CoverPaths) == 0 {
<div class="cover-img cover-0 flex items-center justify-center" style="background: var(--bg-primary);">
<span class="text-3xl">📚</span>
<div class="cover-img cover-0 flex items-center justify-center" style="background: var(--bg-primary); color: var(--text-secondary);">
@Icon("book", "h-8 w-8 opacity-60")
</div>
}
</div>