mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
Feat: History Page Revamp
This commit is contained in:
@@ -321,7 +321,7 @@
|
|||||||
.root { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
.root { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
||||||
.header { position: relative; z-index: 100; display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); flex-shrink: 0; border-bottom: 1px solid var(--border-dim); }
|
.header { position: relative; z-index: 100; display: flex; align-items: center; gap: var(--sp-4); padding: var(--sp-4) var(--sp-6); flex-shrink: 0; border-bottom: 1px solid var(--border-dim); }
|
||||||
.heading { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; flex-shrink: 0; }
|
.heading { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; flex-shrink: 0; }
|
||||||
.tabs { display: flex; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; }
|
.tabs { margin-left: auto; display: flex; gap: 2px; background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 2px; position: relative; }
|
||||||
.tab-slide-indicator { position: absolute; top: 2px; bottom: 2px; border-radius: var(--radius-sm); background: var(--accent-muted); border: 1px solid var(--accent-dim); pointer-events: none; z-index: 0; transition: left 0.22s cubic-bezier(0.16,1,0.3,1), width 0.22s cubic-bezier(0.16,1,0.3,1); }
|
.tab-slide-indicator { position: absolute; top: 2px; bottom: 2px; border-radius: var(--radius-sm); background: var(--accent-muted); border: 1px solid var(--accent-dim); pointer-events: none; z-index: 0; transition: left 0.22s cubic-bezier(0.16,1,0.3,1), width 0.22s cubic-bezier(0.16,1,0.3,1); }
|
||||||
.tab { position: relative; z-index: 1; display: flex; align-items: center; gap: 5px; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 4px 10px; border-radius: var(--radius-sm); color: var(--text-faint); white-space: nowrap; transition: background var(--t-base), color var(--t-base); cursor: pointer; border: 1px solid transparent; }
|
.tab { position: relative; z-index: 1; display: flex; align-items: center; gap: 5px; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); text-transform: uppercase; padding: 4px 10px; border-radius: var(--radius-sm); color: var(--text-faint); white-space: nowrap; transition: background var(--t-base), color var(--t-base); cursor: pointer; border: 1px solid transparent; }
|
||||||
.tab:hover { color: var(--text-muted); }
|
.tab:hover { color: var(--text-muted); }
|
||||||
|
|||||||
@@ -57,11 +57,13 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="hero-stage">
|
<div class="hero-stage">
|
||||||
{#if heroThumb}
|
{#key heroThumb}
|
||||||
<div class="hero-backdrop" style="background-image:url({heroThumb})"></div>
|
{#if heroThumb}
|
||||||
{:else}
|
<div class="hero-backdrop" style="background-image:url({heroThumb})"></div>
|
||||||
<div class="hero-backdrop hero-bd-empty"></div>
|
{:else}
|
||||||
{/if}
|
<div class="hero-backdrop hero-bd-empty"></div>
|
||||||
|
{/if}
|
||||||
|
{/key}
|
||||||
<div class="hero-scrim"></div>
|
<div class="hero-scrim"></div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
@@ -230,11 +232,9 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
height: 374px;
|
height: 374px;
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-raised);
|
background: var(--bg-raised);
|
||||||
border: 1px solid var(--border-dim);
|
border-bottom: 1px solid var(--border-dim);
|
||||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.32);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-backdrop {
|
.hero-backdrop {
|
||||||
@@ -246,7 +246,7 @@
|
|||||||
transform: scale(1.07);
|
transform: scale(1.07);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
transition: background-image 0.3s ease;
|
animation: backdropIn 0.5s ease both;
|
||||||
}
|
}
|
||||||
.hero-bd-empty { background: var(--bg-void); filter: none; }
|
.hero-bd-empty { background: var(--bg-void); filter: none; }
|
||||||
|
|
||||||
@@ -266,12 +266,10 @@
|
|||||||
height: 374px;
|
height: 374px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-right: 1px solid rgba(255, 255, 255, 0.07);
|
|
||||||
background: var(--bg-raised);
|
background: var(--bg-raised);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border-top: none;
|
border: none;
|
||||||
border-bottom: none;
|
border-right: 1px solid rgba(255, 255, 255, 0.07);
|
||||||
border-left: none;
|
|
||||||
}
|
}
|
||||||
.hero-cover-col:hover .hero-cover { filter: brightness(1.1) saturate(1.05); }
|
.hero-cover-col:hover .hero-cover { filter: brightness(1.1) saturate(1.05); }
|
||||||
.hero-cover-col:hover .cover-resume-hint { opacity: 1; }
|
.hero-cover-col:hover .cover-resume-hint { opacity: 1; }
|
||||||
@@ -577,5 +575,9 @@
|
|||||||
}
|
}
|
||||||
.ch-view-all:hover { color: var(--accent-fg); }
|
.ch-view-all:hover { color: var(--accent-fg); }
|
||||||
|
|
||||||
|
@keyframes backdropIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
@keyframes pulse { 0%, 100% { opacity: 0.4 } 50% { opacity: 0.7 } }
|
@keyframes pulse { 0%, 100% { opacity: 0.4 } 50% { opacity: 0.7 } }
|
||||||
</style>
|
</style>
|
||||||
@@ -223,28 +223,26 @@
|
|||||||
<div class="root">
|
<div class="root">
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
|
||||||
<div class="hero-section">
|
<HeroStage
|
||||||
<HeroStage
|
{resolvedSlots}
|
||||||
{resolvedSlots}
|
bind:activeIdx
|
||||||
bind:activeIdx
|
{heroThumb}
|
||||||
{heroThumb}
|
{heroTitle}
|
||||||
{heroTitle}
|
{heroManga}
|
||||||
{heroManga}
|
{heroEntry}
|
||||||
{heroEntry}
|
{heroMangaId}
|
||||||
{heroMangaId}
|
{heroChapters}
|
||||||
{heroChapters}
|
{loadingHeroChapters}
|
||||||
{loadingHeroChapters}
|
{resuming}
|
||||||
{resuming}
|
onresume={resumeActive}
|
||||||
onresume={resumeActive}
|
onopenchapter={openChapter}
|
||||||
onopenchapter={openChapter}
|
oncyclenext={cycleNext}
|
||||||
oncyclenext={cycleNext}
|
oncycleprev={cyclePrev}
|
||||||
oncycleprev={cyclePrev}
|
ongotoslot={goToSlot}
|
||||||
ongotoslot={goToSlot}
|
onopenpicker={openPicker}
|
||||||
onopenpicker={openPicker}
|
onunpin={unpinSlot}
|
||||||
onunpin={unpinSlot}
|
onviewall={() => { if (heroManga) store.activeManga = heroManga; }}
|
||||||
onviewall={() => { if (heroManga) store.activeManga = heroManga; }}
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ActivityFeed
|
<ActivityFeed
|
||||||
entries={recentHistory}
|
entries={recentHistory}
|
||||||
@@ -284,7 +282,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
animation: fadeIn 0.14s ease both;
|
animation: fadeIn 0.4s ease both;
|
||||||
}
|
}
|
||||||
.body {
|
.body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -294,10 +292,6 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.hero-section {
|
|
||||||
padding: var(--sp-3) var(--sp-4) var(--sp-2);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
.bottom-row {
|
.bottom-row {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1px 1fr;
|
grid-template-columns: 1fr 1px 1fr;
|
||||||
@@ -308,5 +302,8 @@
|
|||||||
}
|
}
|
||||||
.bottom-divider { background: var(--border-dim); align-self: stretch; }
|
.bottom-divider { background: var(--border-dim); align-self: stretch; }
|
||||||
|
|
||||||
@keyframes fadeIn { from { opacity: 0 } to { opacity: 1 } }
|
@keyframes fadeIn {
|
||||||
</style>
|
from { opacity: 0; transform: translateY(6px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ClockCounterClockwise, Trash, MagnifyingGlass, Play, Books, Fire, BookOpen, Clock, TrendUp } from "phosphor-svelte";
|
import { ClockCounterClockwise, Trash, MagnifyingGlass, Books, Fire, BookOpen, Clock, TrendUp } from "phosphor-svelte";
|
||||||
import Thumbnail from "@shared/manga/Thumbnail.svelte";
|
import Thumbnail from "@shared/manga/Thumbnail.svelte";
|
||||||
import { store, clearHistory, setActiveManga } from "@store/state.svelte";
|
import { store, clearHistory, setPreviewManga } from "@store/state.svelte";
|
||||||
import type { HistoryEntry } from "@store/state.svelte";
|
import type { HistoryEntry } from "@store/state.svelte";
|
||||||
import { timeAgo, dayLabel, formatReadTime } from "@core/util";
|
import { timeAgo, dayLabel, formatReadTime } from "@core/util";
|
||||||
|
|
||||||
@@ -72,15 +72,6 @@
|
|||||||
return Array.from(map.entries()).map(([label, items]) => ({ label, items }));
|
return Array.from(map.entries()).map(([label, items]) => ({ label, items }));
|
||||||
});
|
});
|
||||||
|
|
||||||
function resume(session: Session) {
|
|
||||||
setActiveManga({
|
|
||||||
id: session.mangaId,
|
|
||||||
title: session.mangaTitle,
|
|
||||||
thumbnailUrl: session.thumbnailUrl,
|
|
||||||
inLibrary: false,
|
|
||||||
} as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleClear() {
|
function handleClear() {
|
||||||
if (!confirmClear) { confirmClear = true; setTimeout(() => confirmClear = false, 3000); return; }
|
if (!confirmClear) { confirmClear = true; setTimeout(() => confirmClear = false, 3000); return; }
|
||||||
clearHistory(); confirmClear = false;
|
clearHistory(); confirmClear = false;
|
||||||
@@ -88,18 +79,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="root anim-fade-in">
|
<div class="root anim-fade-in">
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<span class="heading">History</span>
|
<div class="heading-group">
|
||||||
|
<ClockCounterClockwise size={13} weight="light" class="heading-icon" />
|
||||||
|
<span class="heading">History</span>
|
||||||
|
</div>
|
||||||
<div class="header-right">
|
<div class="header-right">
|
||||||
<div class="search-wrap">
|
<div class="search-wrap">
|
||||||
<MagnifyingGlass size={12} class="search-icon" weight="light" />
|
<MagnifyingGlass size={11} class="search-icon" weight="light" />
|
||||||
<input class="search" placeholder="Search history…" bind:value={search} />
|
<input class="search" placeholder="Search…" bind:value={search} />
|
||||||
{#if search}<button class="search-clear" onclick={() => search = ""}>×</button>{/if}
|
{#if search}
|
||||||
|
<button class="search-clear" onclick={() => search = ""}>×</button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if store.history.length > 0}
|
{#if store.history.length > 0}
|
||||||
<button class="clear-btn" class:confirm={confirmClear} onclick={handleClear}
|
<button
|
||||||
title={confirmClear ? "Click again to confirm" : "Clear history"}>
|
class="clear-btn"
|
||||||
<Trash size={14} weight="light" />
|
class:confirm={confirmClear}
|
||||||
|
onclick={handleClear}
|
||||||
|
title={confirmClear ? "Click again to confirm" : "Clear history"}
|
||||||
|
>
|
||||||
|
<Trash size={12} weight="light" />
|
||||||
{#if confirmClear}<span class="clear-label">Confirm?</span>{/if}
|
{#if confirmClear}<span class="clear-label">Confirm?</span>{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -107,61 +108,72 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if store.readingStats.totalChaptersRead > 0}
|
{#if store.readingStats.totalChaptersRead > 0}
|
||||||
<div class="stats-bar">
|
<div class="stats-grid">
|
||||||
<div class="stat-group">
|
<div class="stat-card streak">
|
||||||
<Fire size={13} weight="fill" class="stat-fire" />
|
<div class="stat-icon-wrap fire">
|
||||||
<span class="stat-val accent">{store.readingStats.currentStreakDays}</span>
|
<Fire size={12} weight="fill" />
|
||||||
<span class="stat-label">day streak</span>
|
</div>
|
||||||
|
<div class="stat-body">
|
||||||
|
<span class="stat-val">{store.readingStats.currentStreakDays}</span>
|
||||||
|
<span class="stat-unit">day streak</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-sep"></div>
|
<div class="stat-card">
|
||||||
<div class="stat-group">
|
<div class="stat-icon-wrap">
|
||||||
<BookOpen size={13} weight="light" class="stat-icon-neutral" />
|
<BookOpen size={12} weight="light" />
|
||||||
<span class="stat-val">{store.readingStats.totalChaptersRead}</span>
|
</div>
|
||||||
<span class="stat-label">chapters</span>
|
<div class="stat-body">
|
||||||
|
<span class="stat-val">{store.readingStats.totalChaptersRead}</span>
|
||||||
|
<span class="stat-unit">chapters</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-sep"></div>
|
<div class="stat-card">
|
||||||
<div class="stat-group">
|
<div class="stat-icon-wrap">
|
||||||
<Clock size={13} weight="light" class="stat-icon-neutral" />
|
<Clock size={12} weight="light" />
|
||||||
<span class="stat-val">{formatReadTime(store.readingStats.totalMinutesRead)}</span>
|
</div>
|
||||||
<span class="stat-label">read time</span>
|
<div class="stat-body">
|
||||||
|
<span class="stat-val">{formatReadTime(store.readingStats.totalMinutesRead)}</span>
|
||||||
|
<span class="stat-unit">read time</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-sep"></div>
|
<div class="stat-card">
|
||||||
<div class="stat-group">
|
<div class="stat-icon-wrap">
|
||||||
<TrendUp size={13} weight="light" class="stat-icon-neutral" />
|
<TrendUp size={12} weight="light" />
|
||||||
<span class="stat-val">{store.readingStats.totalMangaRead}</span>
|
</div>
|
||||||
<span class="stat-label">series</span>
|
<div class="stat-body">
|
||||||
|
<span class="stat-val">{store.readingStats.totalMangaRead}</span>
|
||||||
|
<span class="stat-unit">series</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stat-sep"></div>
|
|
||||||
<div class="stat-group">
|
|
||||||
<span class="stat-val muted">{store.readingStats.longestStreakDays}d</span>
|
|
||||||
<span class="stat-label">best streak</span>
|
|
||||||
</div>
|
|
||||||
<span class="stats-note">Stats are preserved when you clear the feed</span>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if store.history.length === 0}
|
{#if store.history.length === 0}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<ClockCounterClockwise size={32} weight="light" class="empty-icon" />
|
<div class="empty-icon-wrap">
|
||||||
|
<ClockCounterClockwise size={24} weight="light" />
|
||||||
|
</div>
|
||||||
<p class="empty-text">No reading history yet</p>
|
<p class="empty-text">No reading history yet</p>
|
||||||
<p class="empty-hint">Chapters you read will appear here</p>
|
<p class="empty-hint">Chapters you read will appear here</p>
|
||||||
</div>
|
</div>
|
||||||
{:else if sessions.length === 0}
|
{:else if sessions.length === 0}
|
||||||
<div class="empty">
|
<div class="empty">
|
||||||
<Books size={28} weight="light" class="empty-icon" />
|
<div class="empty-icon-wrap">
|
||||||
|
<Books size={20} weight="light" />
|
||||||
|
</div>
|
||||||
<p class="empty-text">No results for "{search}"</p>
|
<p class="empty-text">No results for "{search}"</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="timeline">
|
<div class="timeline">
|
||||||
{#each groups as { label, items }}
|
{#each groups as { label, items }}
|
||||||
<div class="day-group">
|
<div class="day-group">
|
||||||
<div class="day-label-row">
|
<div class="day-header">
|
||||||
<span class="day-label">{label}</span>
|
<span class="day-label">{label}</span>
|
||||||
<div class="day-line"></div>
|
<div class="day-rule"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="session-list">
|
<div class="session-list">
|
||||||
{#each items as session (session.latestChapterId)}
|
{#each items as session (session.latestChapterId)}
|
||||||
<button class="session-row" onclick={() => resume(session)}>
|
<button class="session-row" onclick={() => setPreviewManga({ id: session.mangaId, title: session.mangaTitle, thumbnailUrl: session.thumbnailUrl } as any)}>
|
||||||
<div class="thumb-wrap">
|
<div class="thumb-wrap">
|
||||||
<Thumbnail src={session.thumbnailUrl} alt={session.mangaTitle} class="thumb" />
|
<Thumbnail src={session.thumbnailUrl} alt={session.mangaTitle} class="thumb" />
|
||||||
{#if session.chapterCount > 1}
|
{#if session.chapterCount > 1}
|
||||||
@@ -172,21 +184,16 @@
|
|||||||
<span class="session-title">{session.mangaTitle}</span>
|
<span class="session-title">{session.mangaTitle}</span>
|
||||||
<span class="session-chapter">
|
<span class="session-chapter">
|
||||||
{#if session.chapterCount > 1}
|
{#if session.chapterCount > 1}
|
||||||
{session.firstChapterName}
|
{session.firstChapterName}<span class="ch-arrow">→</span>{session.latestChapterName}
|
||||||
<span class="ch-arrow">→</span>
|
|
||||||
{session.latestChapterName}
|
|
||||||
{:else}
|
{:else}
|
||||||
{session.latestChapterName}
|
{session.latestChapterName}
|
||||||
{#if session.latestPageNumber > 1}
|
{#if session.latestPageNumber > 1}
|
||||||
<span class="ch-page">p.{session.latestPageNumber}</span>
|
<span class="ch-page">· p.{session.latestPageNumber}</span>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="session-time">{timeAgo(session.readAt)}</span>
|
<span class="session-time">{timeAgo(session.readAt)}</span>
|
||||||
<div class="play-pill">
|
|
||||||
<Play size={10} weight="fill" /> Resume
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -197,63 +204,356 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root { display: flex; flex-direction: column; height: 100%; overflow: hidden; }
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.header { display: flex; align-items: center; justify-content: space-between; padding: var(--sp-4) var(--sp-6); border-bottom: 1px solid var(--border-dim); flex-shrink: 0; }
|
.header {
|
||||||
.heading { font-family: var(--font-ui); font-size: var(--text-xs); font-weight: var(--weight-normal); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; }
|
display: flex;
|
||||||
.header-right { display: flex; align-items: center; gap: var(--sp-2); }
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--sp-3) var(--sp-5);
|
||||||
|
border-bottom: 1px solid var(--border-dim);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading-group {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.heading-icon) { color: var(--text-faint); }
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
color: var(--text-muted);
|
||||||
|
letter-spacing: var(--tracking-wider);
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-wrap {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-wrap :global(.search-icon) {
|
||||||
|
position: absolute;
|
||||||
|
left: 8px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search {
|
||||||
|
background: var(--bg-raised);
|
||||||
|
border: 1px solid var(--border-dim);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 4px 26px;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
width: 148px;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color var(--t-base), width var(--t-base), background var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
.search-wrap { position: relative; display: flex; align-items: center; }
|
|
||||||
.search-wrap :global(.search-icon) { position: absolute; left: 9px; color: var(--text-faint); pointer-events: none; }
|
|
||||||
.search { background: var(--bg-raised); border: 1px solid var(--border-dim); border-radius: var(--radius-md); padding: 5px 26px; color: var(--text-primary); font-size: var(--text-sm); width: 180px; outline: none; transition: border-color var(--t-base); }
|
|
||||||
.search::placeholder { color: var(--text-faint); }
|
.search::placeholder { color: var(--text-faint); }
|
||||||
.search:focus { border-color: var(--border-strong); }
|
|
||||||
.search-clear { position: absolute; right: 7px; color: var(--text-faint); font-size: 14px; line-height: 1; background: none; border: none; cursor: pointer; padding: 2px; transition: color var(--t-base); }
|
.search:focus {
|
||||||
|
border-color: var(--border-strong);
|
||||||
|
background: var(--bg-elevated);
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-clear {
|
||||||
|
position: absolute;
|
||||||
|
right: 8px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
transition: color var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
.search-clear:hover { color: var(--text-muted); }
|
.search-clear:hover { color: var(--text-muted); }
|
||||||
|
|
||||||
.clear-btn { display: flex; align-items: center; gap: 5px; height: 28px; padding: 0 var(--sp-2); border-radius: var(--radius-md); color: var(--text-faint); background: none; border: 1px solid transparent; cursor: pointer; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); transition: color var(--t-base), background var(--t-base), border-color var(--t-base); }
|
.clear-btn {
|
||||||
.clear-btn:hover { color: var(--color-error); background: var(--color-error-bg); }
|
display: flex;
|
||||||
.clear-btn.confirm { color: var(--color-error); background: var(--color-error-bg); border-color: var(--color-error); }
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
height: 26px;
|
||||||
|
padding: 0 var(--sp-2);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
color: var(--text-faint);
|
||||||
|
background: none;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-2xs);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
transition: color var(--t-base), background var(--t-base), border-color var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn:hover {
|
||||||
|
color: var(--color-error);
|
||||||
|
background: var(--color-error-bg);
|
||||||
|
border-color: color-mix(in srgb, var(--color-error) 20%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.clear-btn.confirm {
|
||||||
|
color: var(--color-error);
|
||||||
|
background: var(--color-error-bg);
|
||||||
|
border-color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
.clear-label { font-size: var(--text-2xs); }
|
.clear-label { font-size: var(--text-2xs); }
|
||||||
|
|
||||||
.stats-bar { display: flex; align-items: center; gap: var(--sp-3); flex-wrap: wrap; padding: var(--sp-3) var(--sp-6); border-bottom: 1px solid var(--border-dim); background: var(--bg-raised); flex-shrink: 0; }
|
.stats-grid {
|
||||||
.stat-group { display: flex; align-items: center; gap: 5px; }
|
display: grid;
|
||||||
.stat-sep { width: 1px; height: 14px; background: var(--border-dim); flex-shrink: 0; }
|
grid-template-columns: repeat(4, 1fr);
|
||||||
:global(.stat-fire) { color: #f97316; }
|
gap: 1px;
|
||||||
:global(.stat-icon-neutral) { color: var(--text-faint); }
|
background: var(--border-dim);
|
||||||
.stat-val { font-family: var(--font-ui); font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-secondary); }
|
border-bottom: 1px solid var(--border-dim);
|
||||||
.stat-val.accent { color: var(--accent-fg); }
|
flex-shrink: 0;
|
||||||
.stat-val.muted { color: var(--text-faint); }
|
}
|
||||||
.stat-label { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); }
|
|
||||||
.stats-note { margin-left: auto; font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); opacity: 0.5; letter-spacing: var(--tracking-wide); font-style: italic; }
|
.stat-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
padding: var(--sp-3) var(--sp-4);
|
||||||
|
background: var(--bg-base);
|
||||||
|
transition: background var(--t-base);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.streak .stat-icon-wrap { background: color-mix(in srgb, #f97316 12%, transparent); }
|
||||||
|
.stat-card.streak .stat-val { color: #f97316; }
|
||||||
|
|
||||||
|
.stat-icon-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
color: var(--text-faint);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-icon-wrap.fire { color: #f97316; }
|
||||||
|
|
||||||
|
.stat-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-val {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--weight-semibold);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-unit {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 9px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
text-transform: uppercase;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: var(--sp-4) var(--sp-5) var(--sp-6);
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--border-dim) transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.timeline { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-6); }
|
|
||||||
.day-group { margin-bottom: var(--sp-5); }
|
.day-group { margin-bottom: var(--sp-5); }
|
||||||
.day-label-row { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); }
|
|
||||||
.day-label { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wider); text-transform: uppercase; white-space: nowrap; flex-shrink: 0; }
|
|
||||||
.day-line { flex: 1; height: 1px; background: var(--border-dim); }
|
|
||||||
|
|
||||||
.session-list { display: flex; flex-direction: column; gap: 2px; }
|
.day-header {
|
||||||
.session-row { display: flex; align-items: center; gap: var(--sp-3); width: 100%; padding: 8px var(--sp-3); border-radius: var(--radius-md); border: 1px solid transparent; background: none; text-align: left; cursor: pointer; transition: background var(--t-fast), border-color var(--t-fast); }
|
display: flex;
|
||||||
.session-row:hover { background: var(--bg-raised); border-color: var(--border-dim); }
|
align-items: center;
|
||||||
.session-row:hover .play-pill { opacity: 1; transform: translateX(0); }
|
gap: var(--sp-3);
|
||||||
|
padding-bottom: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
.thumb-wrap { position: relative; flex-shrink: 0; }
|
.day-label {
|
||||||
:global(.thumb) { width: 38px; height: 54px; border-radius: var(--radius-sm); object-fit: cover; display: block; background: var(--bg-raised); border: 1px solid var(--border-dim); }
|
font-family: var(--font-ui);
|
||||||
.session-count { position: absolute; bottom: -4px; right: -6px; background: var(--accent-muted); border: 1px solid var(--accent-dim); color: var(--accent-fg); font-family: var(--font-ui); font-size: 9px; font-weight: 600; padding: 1px 4px; border-radius: 6px; line-height: 1.4; pointer-events: none; }
|
font-size: 9px;
|
||||||
|
color: var(--text-faint);
|
||||||
|
letter-spacing: var(--tracking-wider);
|
||||||
|
text-transform: uppercase;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.session-info { flex: 1; display: flex; flex-direction: column; gap: 3px; overflow: hidden; min-width: 0; }
|
.day-rule {
|
||||||
.session-title { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-secondary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
flex: 1;
|
||||||
.session-chapter { font-size: var(--text-xs); color: var(--text-muted); display: flex; align-items: center; gap: var(--sp-1); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
height: 1px;
|
||||||
.ch-arrow { color: var(--text-faint); font-size: 10px; flex-shrink: 0; }
|
background: var(--border-dim);
|
||||||
.ch-page { font-family: var(--font-ui); font-size: var(--text-2xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); flex-shrink: 0; }
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
.session-time { font-family: var(--font-ui); font-size: var(--text-xs); color: var(--text-faint); letter-spacing: var(--tracking-wide); flex-shrink: 0; white-space: nowrap; }
|
.session-list {
|
||||||
.play-pill { display: flex; align-items: center; gap: 4px; flex-shrink: 0; font-family: var(--font-ui); font-size: var(--text-2xs); letter-spacing: var(--tracking-wide); color: var(--accent-fg); background: var(--accent-muted); border: 1px solid var(--accent-dim); padding: 3px 8px; border-radius: var(--radius-full); opacity: 0; transform: translateX(4px); transition: opacity var(--t-base), transform var(--t-base); }
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.empty { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: var(--sp-2); }
|
.session-row {
|
||||||
:global(.empty-icon) { color: var(--text-faint); }
|
display: flex;
|
||||||
.empty-text { font-size: var(--text-base); color: var(--text-muted); }
|
align-items: center;
|
||||||
.empty-hint { font-size: var(--text-sm); color: var(--text-faint); }
|
gap: var(--sp-3);
|
||||||
|
width: 100%;
|
||||||
|
padding: var(--sp-2) var(--sp-2);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
text-align: left;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background var(--t-fast);
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
.session-row:hover { background: var(--bg-raised); }
|
||||||
|
.session-row:active { background: var(--bg-elevated); }
|
||||||
|
|
||||||
|
.thumb-wrap {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(.thumb) {
|
||||||
|
width: 38px;
|
||||||
|
height: 54px;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
border: 1px solid var(--border-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-count {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -4px;
|
||||||
|
right: -6px;
|
||||||
|
background: var(--accent-muted);
|
||||||
|
border: 1px solid var(--accent-dim);
|
||||||
|
color: var(--accent-fg);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 8px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 1px 3px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
line-height: 1.3;
|
||||||
|
pointer-events: none;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-title {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
color: var(--text-primary);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-chapter {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-2xs);
|
||||||
|
color: var(--text-faint);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-arrow {
|
||||||
|
color: var(--text-faint);
|
||||||
|
opacity: 0.35;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ch-page {
|
||||||
|
color: var(--text-faint);
|
||||||
|
opacity: 0.5;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.session-time {
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: var(--text-2xs);
|
||||||
|
color: var(--text-faint);
|
||||||
|
letter-spacing: var(--tracking-wide);
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: var(--sp-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-icon-wrap {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
background: var(--bg-raised);
|
||||||
|
border: 1px solid var(--border-dim);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--text-faint);
|
||||||
|
opacity: 0.5;
|
||||||
|
margin-bottom: var(--sp-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-text {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: var(--weight-medium);
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-hint {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--text-faint);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user