Feat: Always Display Library Stats & Library Stats Overhaul (#47)

This commit is contained in:
Youwes09
2026-04-23 21:44:11 -05:00
parent 371b4af73f
commit 72a88b10c8
3 changed files with 27 additions and 26 deletions
@@ -573,6 +573,7 @@
{remainingCount} {remainingCount}
renderLimit={store.settings.renderLimit ?? 48} renderLimit={store.settings.renderLimit ?? 48}
cropCovers={store.settings.libraryCropCovers} cropCovers={store.settings.libraryCropCovers}
statsAlways={store.settings.libraryStatsAlways ?? false}
libraryFilter={tab} libraryFilter={tab}
onCardClick={onCardClick} onCardClick={onCardClick}
onCardContextMenu={openCtx} onCardContextMenu={openCtx}
@@ -15,6 +15,7 @@
remainingCount: number; remainingCount: number;
renderLimit: number; renderLimit: number;
cropCovers: boolean; cropCovers: boolean;
statsAlways: boolean;
libraryFilter: string; libraryFilter: string;
bulkWorking: boolean; bulkWorking: boolean;
visibleCategories: Category[]; visibleCategories: Category[];
@@ -34,7 +35,7 @@
let { let {
visibleManga, filtered, loading, cols, anims, selectMode, selectedIds, visibleManga, filtered, loading, cols, anims, selectMode, selectedIds,
hasMore, remainingCount, renderLimit, cropCovers, libraryFilter, hasMore, remainingCount, renderLimit, cropCovers, statsAlways, libraryFilter,
bulkWorking, visibleCategories, bulkWorking, visibleCategories,
onCardClick, onCardContextMenu, onCardPointerDown, onCardPointerUp, onCardPointerLeave, onCardClick, onCardContextMenu, onCardPointerDown, onCardPointerUp, onCardPointerLeave,
onLoadMore, onRetry, onExitSelectMode, onSelectAll, onBulkMove, onBulkRemove, onBulkAutomate, onLoadMore, onRetry, onExitSelectMode, onSelectAll, onBulkMove, onBulkRemove, onBulkAutomate,
@@ -128,23 +129,17 @@
> >
<div class="cover-wrap" class:completed={isCompleted}> <div class="cover-wrap" class:completed={isCompleted}>
<Thumbnail src={m.thumbnailUrl} alt={m.title} class="cover" style="object-fit:{cropCovers ? 'cover' : 'contain'}" draggable="false" /> <Thumbnail src={m.thumbnailUrl} alt={m.title} class="cover" style="object-fit:{cropCovers ? 'cover' : 'contain'}" draggable="false" />
<div class="card-info-overlay" class:anim={anims} class:instant={!anims}> <div class="card-info-overlay" class:anim={anims} class:instant={!anims} class:always={statsAlways}>
{#if isCompleted} <div class="overlay-badges">
<span class="info-chip info-chip-done">✓ complete</span> {#if isCompleted}
{:else if m.unreadCount} <span class="badge badge-done">✓ Done</span>
<span class="info-chip info-chip-unread"> {:else if m.unreadCount}
<span class="info-chip-dot"></span> <span class="badge badge-unread">{m.unreadCount} new</span>
{m.unreadCount} unread {/if}
</span> {#if m.downloadCount}
{:else} <span class="badge badge-dl">{m.downloadCount}</span>
<span></span> {/if}
{/if} </div>
{#if m.downloadCount}
<span class="info-chip info-chip-dl">
<span class="info-chip-dot"></span>
{m.downloadCount}
</span>
{/if}
</div> </div>
{#if selectMode} {#if selectMode}
<div class="select-overlay" aria-hidden="true"> <div class="select-overlay" aria-hidden="true">
@@ -200,15 +195,16 @@
.card.anims .cover-wrap { transition: transform 0.18s cubic-bezier(0.16,1,0.3,1), border-color var(--t-base), box-shadow 0.18s cubic-bezier(0.16,1,0.3,1); } .card.anims .cover-wrap { transition: transform 0.18s cubic-bezier(0.16,1,0.3,1), border-color var(--t-base), box-shadow 0.18s cubic-bezier(0.16,1,0.3,1); }
.cover-wrap.completed { box-shadow: inset 0 -2px 0 0 var(--accent); } .cover-wrap.completed { box-shadow: inset 0 -2px 0 0 var(--accent); }
.card.anims .cover { transition: filter var(--t-base); } .card.anims .cover { transition: filter var(--t-base); }
.card-info-overlay { position: absolute; bottom: 0; left: 0; right: 0; display: flex; align-items: flex-end; justify-content: space-between; padding: 20px 5px 5px; background: linear-gradient(to top, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.3) 55%, transparent 100%); opacity: 0; transform: translateY(3px); pointer-events: none; } .card-info-overlay { position: absolute; bottom: -4px; left: 0; right: 0; padding: 32px 6px 10px; background: linear-gradient(to top, rgba(0,0,0,0.88) 0%, rgba(0,0,0,0.5) 50%, transparent 100%); opacity: 0; pointer-events: none; }
.card-info-overlay.anim { transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.16,1,0.3,1); } .card-info-overlay.anim { transition: opacity 0.18s ease; }
.card-info-overlay.instant { transition: none; } .card-info-overlay.instant { transition: none; }
.card:not(.select-mode):hover .card-info-overlay { opacity: 1; transform: translateY(0); } .card-info-overlay.always { opacity: 1; }
.info-chip { display: flex; align-items: center; gap: 4px; font-size: 10px; font-weight: 700; letter-spacing: 0.03em; line-height: 1; padding: 3px 6px; border-radius: 4px; background: rgba(0,0,0,0.52); backdrop-filter: blur(6px); } .card:not(.select-mode):hover .card-info-overlay { opacity: 1; }
.info-chip-unread { color: #fff; } .overlay-badges { display: flex; align-items: flex-end; justify-content: space-between; gap: 4px; flex-wrap: wrap; }
.info-chip-done { color: var(--accent-fg); font-size: 9px; letter-spacing: 0.06em; text-transform: uppercase; } .badge { font-family: var(--font-ui); font-size: 9.5px; font-weight: 700; letter-spacing: 0.04em; line-height: 1; padding: 3px 7px; border-radius: 20px; white-space: nowrap; }
.info-chip-dl { color: var(--accent-fg); } .badge-unread { background: var(--accent); color: #fff; box-shadow: 0 1px 8px rgba(0,0,0,0.5); }
.info-chip-dot { width: 4px; height: 4px; border-radius: 50%; background: currentColor; flex-shrink: 0; } .badge-done { background: rgba(255,255,255,0.18); color: rgba(255,255,255,0.9); border: 1px solid rgba(255,255,255,0.25); }
.badge-dl { background: rgba(0,0,0,0.55); color: rgba(255,255,255,0.8); border: 1px solid rgba(255,255,255,0.18); margin-left: auto; }
.select-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.18); display: flex; align-items: flex-start; justify-content: flex-end; padding: 6px; pointer-events: none; } .select-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.18); display: flex; align-items: flex-start; justify-content: flex-end; padding: 6px; pointer-events: none; }
.select-check { color: var(--text-faint); opacity: 0.7; transition: color var(--t-base), opacity var(--t-base); } .select-check { color: var(--text-faint); opacity: 0.7; transition: color var(--t-base), opacity var(--t-base); }
.select-check.checked { color: var(--accent-fg); opacity: 1; } .select-check.checked { color: var(--accent-fg); opacity: 1; }
@@ -19,6 +19,10 @@
<div class="s-section"> <div class="s-section">
<p class="s-section-title">Display</p> <p class="s-section-title">Display</p>
<div class="s-section-body"> <div class="s-section-body">
<label class="s-row">
<div class="s-row-info"><span class="s-label">Always show card stats</span><span class="s-desc">Show unread and download counts without needing to hover</span></div>
<button role="switch" aria-checked={store.settings.libraryStatsAlways ?? false} aria-label="Always show card stats" class="s-toggle" class:on={store.settings.libraryStatsAlways ?? false} onclick={() => updateSettings({ libraryStatsAlways: !(store.settings.libraryStatsAlways ?? false) })}><span class="s-toggle-thumb"></span></button>
</label>
<label class="s-row"> <label class="s-row">
<div class="s-row-info"><span class="s-label">Crop cover images</span><span class="s-desc">Fills the card with the cover art instead of letterboxing</span></div> <div class="s-row-info"><span class="s-label">Crop cover images</span><span class="s-desc">Fills the card with the cover art instead of letterboxing</span></div>
<button role="switch" aria-checked={store.settings.libraryCropCovers} aria-label="Crop cover images" class="s-toggle" class:on={store.settings.libraryCropCovers} onclick={() => updateSettings({ libraryCropCovers: !store.settings.libraryCropCovers })}><span class="s-toggle-thumb"></span></button> <button role="switch" aria-checked={store.settings.libraryCropCovers} aria-label="Crop cover images" class="s-toggle" class:on={store.settings.libraryCropCovers} onclick={() => updateSettings({ libraryCropCovers: !store.settings.libraryCropCovers })}><span class="s-toggle-thumb"></span></button>