phase 3: depth and rhythm polish
This commit is contained in:
parent
745c846a6e
commit
c78b134d4d
46 changed files with 440 additions and 440 deletions
|
|
@ -173,9 +173,9 @@ export default function EPUBPage() {
|
||||||
<p className="text-danger mb-4">{error}</p>
|
<p className="text-danger mb-4">{error}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center px-3 py-1 bg-surface text-foreground rounded-lg hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2 text-soft" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
</svg>
|
</svg>
|
||||||
Back to Documents
|
Back to Documents
|
||||||
|
|
@ -190,7 +190,7 @@ export default function EPUBPage() {
|
||||||
left={
|
left={
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
aria-label="Back to documents"
|
aria-label="Back to documents"
|
||||||
>
|
>
|
||||||
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -242,7 +242,7 @@ export default function EPUBPage() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAtLimit ? (
|
{isAtLimit ? (
|
||||||
<div className="sticky bottom-0 z-30 w-full border-t border-offbase bg-base" data-app-ttsbar>
|
<div className="sticky bottom-0 z-30 w-full border-t border-line-soft bg-surface" data-app-ttsbar>
|
||||||
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
||||||
<RateLimitPauseButton />
|
<RateLimitPauseButton />
|
||||||
<RateLimitBanner />
|
<RateLimitBanner />
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,7 @@ export default function HTMLPage() {
|
||||||
<p className="text-danger mb-4">{error}</p>
|
<p className="text-danger mb-4">{error}</p>
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center px-3 py-1 bg-surface text-foreground rounded-lg hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
|
|
@ -177,7 +177,7 @@ export default function HTMLPage() {
|
||||||
left={
|
left={
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
aria-label="Back to documents"
|
aria-label="Back to documents"
|
||||||
>
|
>
|
||||||
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -233,7 +233,7 @@ export default function HTMLPage() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAtLimit ? (
|
{isAtLimit ? (
|
||||||
<div className="sticky bottom-0 z-30 w-full border-t border-offbase bg-base" data-app-ttsbar>
|
<div className="sticky bottom-0 z-30 w-full border-t border-line-soft bg-surface" data-app-ttsbar>
|
||||||
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
||||||
<RateLimitPauseButton />
|
<RateLimitPauseButton />
|
||||||
<RateLimitBanner />
|
<RateLimitBanner />
|
||||||
|
|
|
||||||
|
|
@ -255,9 +255,9 @@ export default function PDFViewerPage() {
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
onClick={handleBackToDocuments}
|
onClick={handleBackToDocuments}
|
||||||
className="inline-flex items-center px-3 py-1 bg-base text-foreground rounded-lg hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center px-3 py-1 bg-surface text-foreground rounded-lg hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 mr-2 text-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4 mr-2 text-soft" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
</svg>
|
</svg>
|
||||||
Back to Documents
|
Back to Documents
|
||||||
|
|
@ -308,34 +308,34 @@ export default function PDFViewerPage() {
|
||||||
: (isMerging ? 'Stage: merge' : 'Stage: infer'));
|
: (isMerging ? 'Stage: merge' : 'Stage: infer'));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full w-full bg-base">
|
<div className="h-full w-full bg-surface">
|
||||||
<div className={`mx-auto flex h-full items-center px-4 py-6 transition duration-slow ease-standard ${showDetailedParseLoader ? 'max-w-lg' : 'max-w-md'}`}>
|
<div className={`mx-auto flex h-full items-center px-4 py-6 transition duration-slow ease-standard ${showDetailedParseLoader ? 'max-w-lg' : 'max-w-md'}`}>
|
||||||
{showDetailedParseLoader ? (
|
{showDetailedParseLoader ? (
|
||||||
<div className="w-full rounded-xl border border-offbase bg-surface-sunken shadow-sm overflow-hidden">
|
<div className="w-full rounded-lg border border-line bg-surface-sunken shadow-elev-1 overflow-hidden">
|
||||||
<div className="h-1 bg-[linear-gradient(90deg,var(--accent),transparent_80%)]" />
|
<div className="h-1 bg-[linear-gradient(90deg,var(--accent),transparent_80%)]" />
|
||||||
<div className="p-3.5 sm:p-4">
|
<div className="p-3.5 sm:p-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="inline-flex items-center gap-2 rounded-md border border-offbase bg-surface-solid px-2.5 py-1">
|
<div className="inline-flex items-center gap-2 rounded-md border border-line bg-surface-solid px-2.5 py-1">
|
||||||
<LoadingSpinner className="h-3.5 w-3.5 text-accent" />
|
<LoadingSpinner className="h-3.5 w-3.5 text-accent" />
|
||||||
<span className="text-[10px] font-semibold uppercase tracking-[0.08em] text-muted">PDF Layout Parse</span>
|
<span className="text-[10px] font-semibold uppercase tracking-[0.08em] text-soft">PDF Layout Parse</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm font-semibold text-foreground">{statusText}</p>
|
<p className="text-sm font-semibold text-foreground">{statusText}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-3 rounded-lg border border-offbase bg-surface-solid p-2.5">
|
<div className="mt-3 rounded-lg border border-line bg-surface-solid p-2.5">
|
||||||
<div className="mb-1.5 flex items-end justify-between gap-2">
|
<div className="mb-1.5 flex items-end justify-between gap-2">
|
||||||
<p className="text-[11px] font-semibold text-foreground">
|
<p className="text-[11px] font-semibold text-foreground">
|
||||||
{hasMeasuredProgress ? `Page ${pagesParsed} / ${totalPages}` : 'Awaiting first page'}
|
{hasMeasuredProgress ? `Page ${pagesParsed} / ${totalPages}` : 'Awaiting first page'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[10px] text-muted">{stageLabel}</p>
|
<p className="text-[10px] text-soft">{stageLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-2 w-full rounded-full bg-offbase overflow-hidden">
|
<div className="h-2 w-full rounded-full bg-surface-sunken overflow-hidden">
|
||||||
<div
|
<div
|
||||||
className="h-full bg-accent transition duration-slow ease-standard"
|
className="h-full bg-accent transition duration-slow ease-standard"
|
||||||
style={{ width: `${hasMeasuredProgress ? progressPercent : 6}%` }}
|
style={{ width: `${hasMeasuredProgress ? progressPercent : 6}%` }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1.5 text-[10px] text-muted">
|
<p className="mt-1.5 text-[10px] text-soft">
|
||||||
{hasMeasuredProgress ? `${Math.round(progressPercent)}% complete` : statusSubText}
|
{hasMeasuredProgress ? `${Math.round(progressPercent)}% complete` : statusSubText}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -345,7 +345,7 @@ export default function PDFViewerPage() {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={requestForceReparse}
|
onClick={requestForceReparse}
|
||||||
className="inline-flex items-center rounded-md border border-offbase bg-base px-3 py-1.5 text-xs font-medium text-foreground hover:text-accent transition-colors"
|
className="inline-flex items-center rounded-md border border-line bg-surface px-3 py-1.5 text-xs font-medium text-foreground hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
Retry Parse
|
Retry Parse
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -354,14 +354,14 @@ export default function PDFViewerPage() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="w-full rounded-xl border border-offbase bg-surface-sunken p-4 shadow-sm transition duration-slow ease-standard overflow-hidden">
|
<div className="w-full rounded-lg border border-line bg-surface-sunken p-4 shadow-elev-1 transition duration-slow ease-standard overflow-hidden">
|
||||||
<div className="h-0.5 -mx-4 -mt-4 mb-3 bg-[linear-gradient(90deg,var(--accent),transparent_75%)]" />
|
<div className="h-0.5 -mx-4 -mt-4 mb-3 bg-[linear-gradient(90deg,var(--accent),transparent_75%)]" />
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<p className="text-sm font-semibold text-foreground">{compactLabel}</p>
|
<p className="text-sm font-semibold text-foreground">{compactLabel}</p>
|
||||||
<p className="mt-1 text-xs text-muted">{compactSubLabel}</p>
|
<p className="mt-1 text-xs text-soft">{compactSubLabel}</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="inline-flex items-center justify-center rounded-md border border-offbase bg-base p-1.5">
|
<span className="inline-flex items-center justify-center rounded-md border border-line bg-surface p-1.5">
|
||||||
<LoadingSpinner className="h-3.5 w-3.5 text-accent" />
|
<LoadingSpinner className="h-3.5 w-3.5 text-accent" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -384,7 +384,7 @@ export default function PDFViewerPage() {
|
||||||
<Link
|
<Link
|
||||||
href="/app"
|
href="/app"
|
||||||
onClick={handleBackToDocuments}
|
onClick={handleBackToDocuments}
|
||||||
className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard hover:text-accent"
|
className="inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard hover:text-accent"
|
||||||
aria-label="Back to documents"
|
aria-label="Back to documents"
|
||||||
>
|
>
|
||||||
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3 h-3 mr-2" fill="currentColor" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -440,7 +440,7 @@ export default function PDFViewerPage() {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{isAtLimit ? (
|
{isAtLimit ? (
|
||||||
<div className="sticky bottom-0 z-30 w-full border-t border-offbase bg-base" data-app-ttsbar>
|
<div className="sticky bottom-0 z-30 w-full border-t border-line-soft bg-surface" data-app-ttsbar>
|
||||||
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
||||||
<RateLimitPauseButton />
|
<RateLimitPauseButton />
|
||||||
<RateLimitBanner />
|
<RateLimitBanner />
|
||||||
|
|
|
||||||
|
|
@ -120,11 +120,11 @@ function SignInContent() {
|
||||||
<SessionExpiredLoader setSessionExpired={setSessionExpired} />
|
<SessionExpiredLoader setSessionExpired={setSessionExpired} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<div className="w-full max-w-md bg-base rounded-2xl shadow-xl p-6">
|
<div className="w-full max-w-md bg-surface rounded-lg shadow-elev-3 p-6">
|
||||||
<h1 className="text-xl font-semibold text-foreground">
|
<h1 className="text-xl font-semibold text-foreground">
|
||||||
{sessionExpired ? 'Session Expired' : 'Connect Account'}
|
{sessionExpired ? 'Session Expired' : 'Connect Account'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-muted mt-1">
|
<p className="text-sm text-soft mt-1">
|
||||||
{sessionExpired
|
{sessionExpired
|
||||||
? 'Please sign in again to continue'
|
? 'Please sign in again to continue'
|
||||||
: 'Connect an email account to sync your data across devices'}
|
: 'Connect an email account to sync your data across devices'}
|
||||||
|
|
@ -154,8 +154,8 @@ function SignInContent() {
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => { setEmail(e.target.value); setError(null); }}
|
onChange={(e) => { setEmail(e.target.value); setError(null); }}
|
||||||
placeholder="me@example.com"
|
placeholder="me@example.com"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm
|
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-elev-1
|
||||||
focus:outline-none focus:ring-2 focus:ring-accent"
|
focus:outline-none focus:ring-2 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -167,8 +167,8 @@ function SignInContent() {
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => { setPassword(e.target.value); setError(null); }}
|
onChange={(e) => { setPassword(e.target.value); setError(null); }}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm
|
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-elev-1
|
||||||
focus:outline-none focus:ring-2 focus:ring-accent"
|
focus:outline-none focus:ring-2 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ function SignInContent() {
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={rememberMe}
|
checked={rememberMe}
|
||||||
onChange={(e) => setRememberMe(e.target.checked)}
|
onChange={(e) => setRememberMe(e.target.checked)}
|
||||||
className="rounded border-muted text-accent focus:ring-accent"
|
className="rounded border-muted text-accent focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
<span className="text-sm text-foreground">Remember me</span>
|
<span className="text-sm text-foreground">Remember me</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -230,16 +230,16 @@ function SignInContent() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="mt-6 pt-4 border-t border-offbase text-center space-y-2">
|
<div className="mt-6 pt-4 border-t border-line-soft text-center space-y-2">
|
||||||
{enableUserSignups && (
|
{enableUserSignups && (
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
Don't have an account?{' '}
|
Don't have an account?{' '}
|
||||||
<Link href="/signup" className="underline hover:text-foreground">
|
<Link href="/signup" className="underline hover:text-foreground">
|
||||||
Sign up
|
Sign up
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
By signing in, you agree to our{' '}
|
By signing in, you agree to our{' '}
|
||||||
<button
|
<button
|
||||||
onClick={() => showPrivacyModal()}
|
onClick={() => showPrivacyModal()}
|
||||||
|
|
|
||||||
|
|
@ -105,13 +105,13 @@ export default function SignUpPage() {
|
||||||
if (!enableUserSignups) {
|
if (!enableUserSignups) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
|
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
|
||||||
<div className="w-full max-w-md bg-base rounded-2xl shadow-xl p-6">
|
<div className="w-full max-w-md bg-surface rounded-lg shadow-elev-3 p-6">
|
||||||
<h1 className="text-xl font-semibold text-foreground">Sign-ups unavailable</h1>
|
<h1 className="text-xl font-semibold text-foreground">Sign-ups unavailable</h1>
|
||||||
<p className="text-sm text-muted mt-1">
|
<p className="text-sm text-soft mt-1">
|
||||||
New account sign-ups are currently disabled by the site administrator.
|
New account sign-ups are currently disabled by the site administrator.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-6 pt-4 border-t border-offbase text-center">
|
<div className="mt-6 pt-4 border-t border-line-soft text-center">
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link href="/signin" className="underline hover:text-foreground">
|
<Link href="/signin" className="underline hover:text-foreground">
|
||||||
Sign in
|
Sign in
|
||||||
|
|
@ -129,9 +129,9 @@ export default function SignUpPage() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
|
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
|
||||||
<div className="w-full max-w-md bg-base rounded-2xl shadow-xl p-6">
|
<div className="w-full max-w-md bg-surface rounded-lg shadow-elev-3 p-6">
|
||||||
<h1 className="text-xl font-semibold text-foreground">Sign Up</h1>
|
<h1 className="text-xl font-semibold text-foreground">Sign Up</h1>
|
||||||
<p className="text-sm text-muted mt-1">Create your account to get started</p>
|
<p className="text-sm text-soft mt-1">Create your account to get started</p>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="mt-4 p-3 bg-danger-wash border border-danger rounded-lg">
|
<div className="mt-4 p-3 bg-danger-wash border border-danger rounded-lg">
|
||||||
|
|
@ -148,8 +148,8 @@ export default function SignUpPage() {
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
placeholder="me@example.com"
|
placeholder="me@example.com"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm
|
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-elev-1
|
||||||
focus:outline-none focus:ring-2 focus:ring-accent"
|
focus:outline-none focus:ring-2 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -162,8 +162,8 @@ export default function SignUpPage() {
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 pr-10 text-foreground shadow-sm
|
className="w-full rounded-lg bg-background py-2 px-3 pr-10 text-foreground shadow-elev-1
|
||||||
focus:outline-none focus:ring-2 focus:ring-accent"
|
focus:outline-none focus:ring-2 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -171,7 +171,7 @@ export default function SignUpPage() {
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
size: 'icon',
|
size: 'icon',
|
||||||
className: 'absolute right-2 top-1/2 -translate-y-1/2 h-7 w-7 text-muted',
|
className: 'absolute right-2 top-1/2 -translate-y-1/2 h-7 w-7 text-soft',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{showPassword ? '👁️' : '👁️🗨️'}
|
{showPassword ? '👁️' : '👁️🗨️'}
|
||||||
|
|
@ -185,7 +185,7 @@ export default function SignUpPage() {
|
||||||
{[0, 1, 2, 3, 4].map((i) => (
|
{[0, 1, 2, 3, 4].map((i) => (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={`h-1 flex-1 rounded ${i < strength ? strengthColors[strength - 1] : 'bg-offbase'
|
className={`h-1 flex-1 rounded ${i < strength ? strengthColors[strength - 1] : 'bg-surface-sunken'
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
@ -193,7 +193,7 @@ export default function SignUpPage() {
|
||||||
<p className={`text-xs ${strength >= 3 ? 'text-accent' : 'text-danger'}`}>
|
<p className={`text-xs ${strength >= 3 ? 'text-accent' : 'text-danger'}`}>
|
||||||
{strengthLabels[strength - 1] || 'Very Weak'}
|
{strengthLabels[strength - 1] || 'Very Weak'}
|
||||||
</p>
|
</p>
|
||||||
<div className="text-xs space-y-0.5 text-muted">
|
<div className="text-xs space-y-0.5 text-soft">
|
||||||
{Object.entries(checks).map(([key, passed]) => (
|
{Object.entries(checks).map(([key, passed]) => (
|
||||||
<div key={key} className={`flex items-center gap-1 ${passed ? 'text-accent' : ''}`}>
|
<div key={key} className={`flex items-center gap-1 ${passed ? 'text-accent' : ''}`}>
|
||||||
<span>{passed ? '✓' : '○'}</span>
|
<span>{passed ? '✓' : '○'}</span>
|
||||||
|
|
@ -219,8 +219,8 @@ export default function SignUpPage() {
|
||||||
value={passwordConfirmation}
|
value={passwordConfirmation}
|
||||||
onChange={(e) => setPasswordConfirmation(e.target.value)}
|
onChange={(e) => setPasswordConfirmation(e.target.value)}
|
||||||
placeholder="Confirm Password"
|
placeholder="Confirm Password"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm
|
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-elev-1
|
||||||
focus:outline-none focus:ring-2 focus:ring-accent"
|
focus:outline-none focus:ring-2 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
{passwordConfirmation && password && (
|
{passwordConfirmation && password && (
|
||||||
<p className={`text-xs mt-1 ${password === passwordConfirmation ? 'text-accent' : 'text-danger'}`}>
|
<p className={`text-xs mt-1 ${password === passwordConfirmation ? 'text-accent' : 'text-danger'}`}>
|
||||||
|
|
@ -241,14 +241,14 @@ export default function SignUpPage() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="mt-6 pt-4 border-t border-offbase text-center space-y-2">
|
<div className="mt-6 pt-4 border-t border-line-soft text-center space-y-2">
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
Already have an account?{' '}
|
Already have an account?{' '}
|
||||||
<Link href="/signin" className="underline hover:text-foreground">
|
<Link href="/signin" className="underline hover:text-foreground">
|
||||||
Sign in
|
Sign in
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
By creating an account, you agree to our{' '}
|
By creating an account, you agree to our{' '}
|
||||||
<button
|
<button
|
||||||
onClick={() => showPrivacyModal()}
|
onClick={() => showPrivacyModal()}
|
||||||
|
|
|
||||||
|
|
@ -476,12 +476,12 @@ export function AudiobookExportModal({
|
||||||
<>
|
<>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{!isGenerating && (
|
{!isGenerating && (
|
||||||
<div className="w-full rounded-xl border border-offbase bg-background">
|
<div className="w-full rounded-lg border border-line bg-background">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-offbase bg-base rounded-t-xl">
|
<div className="flex items-center justify-between px-4 py-3 border-b border-line-soft bg-surface rounded-t-xl">
|
||||||
<h4 className="text-sm font-medium text-foreground tracking-tight">Generation settings</h4>
|
<h4 className="text-sm font-medium text-foreground tracking-tight">Generation settings</h4>
|
||||||
{settingsLocked && (
|
{settingsLocked && (
|
||||||
<span className="inline-flex items-center gap-1 rounded-md bg-offbase px-2 py-0.5 text-[11px] font-medium text-muted uppercase tracking-wider">
|
<span className="inline-flex items-center gap-1 rounded-md bg-surface-sunken px-2 py-0.5 text-[11px] font-medium text-soft uppercase tracking-wider">
|
||||||
<svg className="h-3 w-3" viewBox="0 0 16 16" fill="currentColor"><path fillRule="evenodd" d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z" clipRule="evenodd" /></svg>
|
<svg className="h-3 w-3" viewBox="0 0 16 16" fill="currentColor"><path fillRule="evenodd" d="M8 1a3.5 3.5 0 0 0-3.5 3.5V7A1.5 1.5 0 0 0 3 8.5v5A1.5 1.5 0 0 0 4.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 11.5 7V4.5A3.5 3.5 0 0 0 8 1Zm2 6V4.5a2 2 0 1 0-4 0V7h4Z" clipRule="evenodd" /></svg>
|
||||||
Locked
|
Locked
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -492,7 +492,7 @@ export function AudiobookExportModal({
|
||||||
{isLegacyAudiobookMissingSettings && (
|
{isLegacyAudiobookMissingSettings && (
|
||||||
<div className="mb-4 rounded-lg border border-accent-line bg-accent-wash p-3 text-xs text-foreground">
|
<div className="mb-4 rounded-lg border border-accent-line bg-accent-wash p-3 text-xs text-foreground">
|
||||||
<div className="font-medium">Saved generation settings not found</div>
|
<div className="font-medium">Saved generation settings not found</div>
|
||||||
<div className="mt-1 text-muted">
|
<div className="mt-1 text-soft">
|
||||||
This audiobook was likely created before v1 metadata was introduced, so OpenReader can't know
|
This audiobook was likely created before v1 metadata was introduced, so OpenReader can't know
|
||||||
which voice/speeds/format were used. Consider resetting this audiobook to regenerate it with
|
which voice/speeds/format were used. Consider resetting this audiobook to regenerate it with
|
||||||
v1 metadata (so settings are saved for resumes across devices).
|
v1 metadata (so settings are saved for resumes across devices).
|
||||||
|
|
@ -503,18 +503,18 @@ export function AudiobookExportModal({
|
||||||
{settingsLocked && savedSettings ? (
|
{settingsLocked && savedSettings ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="rounded-lg bg-base p-3">
|
<div className="rounded-lg bg-surface p-3">
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted mb-1">Voice</div>
|
<div className="text-[11px] uppercase tracking-wider text-soft mb-1">Voice</div>
|
||||||
<div className="text-sm font-medium text-foreground truncate">{savedSettings.voice}</div>
|
<div className="text-sm font-medium text-foreground truncate">{savedSettings.voice}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-base p-3">
|
<div className="rounded-lg bg-surface p-3">
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted mb-1">Format</div>
|
<div className="text-[11px] uppercase tracking-wider text-soft mb-1">Format</div>
|
||||||
<div className="text-sm font-medium text-foreground">{savedSettings.format.toUpperCase()}</div>
|
<div className="text-sm font-medium text-foreground">{savedSettings.format.toUpperCase()}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<div className="rounded-lg bg-base p-3">
|
<div className="rounded-lg bg-surface p-3">
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted mb-1">Native speed</div>
|
<div className="text-[11px] uppercase tracking-wider text-soft mb-1">Native speed</div>
|
||||||
<div className="text-sm font-medium text-foreground">
|
<div className="text-sm font-medium text-foreground">
|
||||||
{resolveTtsProviderModelPolicy({
|
{resolveTtsProviderModelPolicy({
|
||||||
providerRef: savedSettings.providerRef,
|
providerRef: savedSettings.providerRef,
|
||||||
|
|
@ -525,12 +525,12 @@ export function AudiobookExportModal({
|
||||||
: 'Not supported'}
|
: 'Not supported'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-lg bg-base p-3">
|
<div className="rounded-lg bg-surface p-3">
|
||||||
<div className="text-[11px] uppercase tracking-wider text-muted mb-1">Post speed</div>
|
<div className="text-[11px] uppercase tracking-wider text-soft mb-1">Post speed</div>
|
||||||
<div className="text-sm font-medium text-foreground">{formatSpeed(savedSettings.postSpeed)}x</div>
|
<div className="text-sm font-medium text-foreground">{formatSpeed(savedSettings.postSpeed)}x</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
Reset the audiobook to change generation settings.
|
Reset the audiobook to change generation settings.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -539,7 +539,7 @@ export function AudiobookExportModal({
|
||||||
{/* Voice & Format row */}
|
{/* Voice & Format row */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[11px] uppercase tracking-wider font-medium text-muted">Voice</label>
|
<label className="text-[11px] uppercase tracking-wider font-medium text-soft">Voice</label>
|
||||||
<VoicesControlBase
|
<VoicesControlBase
|
||||||
availableVoices={availableVoices}
|
availableVoices={availableVoices}
|
||||||
voice={audiobookVoice}
|
voice={audiobookVoice}
|
||||||
|
|
@ -552,7 +552,7 @@ export function AudiobookExportModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="text-[11px] uppercase tracking-wider font-medium text-muted">Format</label>
|
<label className="text-[11px] uppercase tracking-wider font-medium text-soft">Format</label>
|
||||||
{chapters.length === 0 ? (
|
{chapters.length === 0 ? (
|
||||||
<Listbox
|
<Listbox
|
||||||
value={format}
|
value={format}
|
||||||
|
|
@ -560,10 +560,10 @@ export function AudiobookExportModal({
|
||||||
disabled={chapters.length > 0 || settingsLocked}
|
disabled={chapters.length > 0 || settingsLocked}
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<ListboxButton className="relative cursor-pointer rounded-lg bg-base py-1.5 pl-3 pr-10 text-left text-foreground focus:outline-none focus:ring-2 focus:ring-accent transform transition-transform duration-base ease-standard hover:text-accent w-full">
|
<ListboxButton className="relative cursor-pointer rounded-lg bg-surface py-1.5 pl-3 pr-10 text-left text-foreground focus:outline-none focus:ring-2 focus:ring-accent-line transform transition-transform duration-base ease-standard hover:text-accent w-full">
|
||||||
<span className="block truncate text-sm font-medium">{format.toUpperCase()}</span>
|
<span className="block truncate text-sm font-medium">{format.toUpperCase()}</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon className="h-4 w-4 text-muted" />
|
<ChevronUpDownIcon className="h-4 w-4 text-soft" />
|
||||||
</span>
|
</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<Transition
|
<Transition
|
||||||
|
|
@ -572,11 +572,11 @@ export function AudiobookExportModal({
|
||||||
leaveFrom="opacity-100"
|
leaveFrom="opacity-100"
|
||||||
leaveTo="opacity-0"
|
leaveTo="opacity-0"
|
||||||
>
|
>
|
||||||
<ListboxOptions className="absolute left-0 mt-1 max-h-60 w-full overflow-auto rounded-lg bg-base py-1 shadow-lg ring-1 ring-black/5 focus:outline-none z-10">
|
<ListboxOptions className="absolute left-0 mt-1 max-h-60 w-full overflow-auto rounded-lg bg-surface py-1 shadow-elev-2 ring-1 ring-line-soft focus:outline-none z-10">
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
value="m4b"
|
value="m4b"
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-offbase text-accent' : 'text-foreground'}`
|
`relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-surface-sunken text-accent' : 'text-foreground'}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -588,7 +588,7 @@ export function AudiobookExportModal({
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
value="mp3"
|
value="mp3"
|
||||||
className={({ active }) =>
|
className={({ active }) =>
|
||||||
`relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-offbase text-accent' : 'text-foreground'}`
|
`relative cursor-pointer select-none py-2 pl-3 pr-4 ${active ? 'bg-surface-sunken text-accent' : 'text-foreground'}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -608,9 +608,9 @@ export function AudiobookExportModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Speed controls */}
|
{/* Speed controls */}
|
||||||
<div className="rounded-lg bg-base p-3 space-y-3">
|
<div className="rounded-lg bg-surface p-3 space-y-3">
|
||||||
{!nativeSpeedSupported && (
|
{!nativeSpeedSupported && (
|
||||||
<div className="rounded-md border border-offbase bg-background px-2 py-1.5 text-[11px] text-muted">
|
<div className="rounded-md border border-line bg-background px-2 py-1.5 text-[11px] text-soft">
|
||||||
Native model speed is not available for this model.
|
Native model speed is not available for this model.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -619,7 +619,7 @@ export function AudiobookExportModal({
|
||||||
<>
|
<>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-[11px] uppercase tracking-wider font-medium text-muted">Native model speed</label>
|
<label className="text-[11px] uppercase tracking-wider font-medium text-soft">Native model speed</label>
|
||||||
<span className="text-xs font-medium text-accent tabular-nums">{formatSpeed(nativeSpeed)}x</span>
|
<span className="text-xs font-medium text-accent tabular-nums">{formatSpeed(nativeSpeed)}x</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|
@ -629,21 +629,21 @@ export function AudiobookExportModal({
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={nativeSpeed}
|
value={nativeSpeed}
|
||||||
onChange={(e) => setNativeSpeed(parseFloat(e.target.value))}
|
onChange={(e) => setNativeSpeed(parseFloat(e.target.value))}
|
||||||
className="w-full h-1.5 bg-offbase rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-sm [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
|
className="w-full h-1.5 bg-surface-sunken rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-elev-1 [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between text-[10px] text-muted">
|
<div className="flex justify-between text-[10px] text-soft">
|
||||||
<span>0.5x</span>
|
<span>0.5x</span>
|
||||||
<span>3x</span>
|
<span>3x</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-offbase" />
|
<div className="border-t border-line-soft" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<label className="text-[11px] uppercase tracking-wider font-medium text-muted">Post-generation speed</label>
|
<label className="text-[11px] uppercase tracking-wider font-medium text-soft">Post-generation speed</label>
|
||||||
<span className="text-xs font-medium text-accent tabular-nums">{formatSpeed(postSpeed)}x</span>
|
<span className="text-xs font-medium text-accent tabular-nums">{formatSpeed(postSpeed)}x</span>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
|
|
@ -653,9 +653,9 @@ export function AudiobookExportModal({
|
||||||
step="0.1"
|
step="0.1"
|
||||||
value={postSpeed}
|
value={postSpeed}
|
||||||
onChange={(e) => setPostSpeed(parseFloat(e.target.value))}
|
onChange={(e) => setPostSpeed(parseFloat(e.target.value))}
|
||||||
className="w-full h-1.5 bg-offbase rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-sm [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
|
className="w-full h-1.5 bg-surface-sunken rounded-full appearance-none cursor-pointer [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-full [&::-webkit-slider-runnable-track]:h-1.5 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-webkit-slider-thumb]:-mt-[5px] [&::-webkit-slider-thumb]:shadow-elev-1 [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-full [&::-moz-range-track]:h-1.5 [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent [&::-moz-range-thumb]:border-0"
|
||||||
/>
|
/>
|
||||||
<div className="flex justify-between text-[10px] text-muted">
|
<div className="flex justify-between text-[10px] text-soft">
|
||||||
<span>0.5x</span>
|
<span>0.5x</span>
|
||||||
<span>3x</span>
|
<span>3x</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -708,7 +708,7 @@ export function AudiobookExportModal({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{showRegenerateHint && (
|
{showRegenerateHint && (
|
||||||
<div className="flex items-start justify-between bg-offbase border border-offbase rounded-md px-3 py-2 text-xs sm:text-sm">
|
<div className="flex items-start justify-between bg-surface-sunken border border-line rounded-md px-3 py-2 text-xs sm:text-sm">
|
||||||
<p className="text-xs sm:text-sm text-foreground">
|
<p className="text-xs sm:text-sm text-foreground">
|
||||||
TTS audio for this chapter may be cached
|
TTS audio for this chapter may be cached
|
||||||
<br />
|
<br />
|
||||||
|
|
@ -716,7 +716,7 @@ export function AudiobookExportModal({
|
||||||
</p>
|
</p>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowRegenerateHint(false)}
|
onClick={() => setShowRegenerateHint(false)}
|
||||||
className="ml-3 p-1 rounded-md hover:bg-base hover:text-accent transition-colors"
|
className="ml-3 p-1 rounded-md hover:bg-surface hover:text-accent transition-colors"
|
||||||
aria-label="Dismiss regenerate hint"
|
aria-label="Dismiss regenerate hint"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -746,12 +746,12 @@ export function AudiobookExportModal({
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<h4 className="text-sm font-medium text-foreground">Chapters</h4>
|
<h4 className="text-sm font-medium text-foreground">Chapters</h4>
|
||||||
{isRefreshingChapters && <ClockIcon className="h-4 w-4 text-muted animate-spin" />}
|
{isRefreshingChapters && <ClockIcon className="h-4 w-4 text-soft animate-spin" />}
|
||||||
</div>
|
</div>
|
||||||
{displayChapters.map((chapter) => (
|
{displayChapters.map((chapter) => (
|
||||||
<div
|
<div
|
||||||
key={chapter.index}
|
key={chapter.index}
|
||||||
className={`flex items-center justify-between px-2 sm:px-3 py-1 sm:py-1.5 rounded-lg bg-offbase ${(regeneratingChapter === chapter.index || chapter.status === 'generating') ? 'prism-outline' : ''}`}
|
className={`flex items-center justify-between px-2 sm:px-3 py-1 sm:py-1.5 rounded-lg bg-surface-sunken ${(regeneratingChapter === chapter.index || chapter.status === 'generating') ? 'prism-outline' : ''}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-3 flex-1">
|
<div className="flex items-center space-x-3 flex-1">
|
||||||
{chapter.status === 'completed' ? (
|
{chapter.status === 'completed' ? (
|
||||||
|
|
@ -760,20 +760,20 @@ export function AudiobookExportModal({
|
||||||
<Button
|
<Button
|
||||||
onClick={() => handleRegenerateChapter(chapter)}
|
onClick={() => handleRegenerateChapter(chapter)}
|
||||||
disabled={regeneratingChapter !== null || chapter.status === 'generating' || isGenerating}
|
disabled={regeneratingChapter !== null || chapter.status === 'generating' || isGenerating}
|
||||||
className="inline-flex items-center justify-center rounded-full bg-offbase text-accent hover:bg-accent-wash p-1.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent transform transition-transform duration-base ease-standard disabled:opacity-50 disabled:cursor-not-allowed"
|
className="inline-flex items-center justify-center rounded-full bg-surface-sunken text-accent hover:bg-accent-wash p-1.5 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent transform transition-transform duration-base ease-standard disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
title={chapter.status === 'generating' ? 'Generating...' : 'Regenerate this chapter'}
|
title={chapter.status === 'generating' ? 'Generating...' : 'Regenerate this chapter'}
|
||||||
>
|
>
|
||||||
<RefreshIcon className={`h-4 w-4 ${regeneratingChapter === chapter.index || chapter.status === 'generating' ? 'animate-spin' : ''}`} />
|
<RefreshIcon className={`h-4 w-4 ${regeneratingChapter === chapter.index || chapter.status === 'generating' ? 'animate-spin' : ''}`} />
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<ClockIcon className="h-5 w-5 text-muted" />
|
<ClockIcon className="h-5 w-5 text-soft" />
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row flex-wrap items-center gap-1">
|
<div className="flex flex-row flex-wrap items-center gap-1">
|
||||||
<p className="text-sm font-medium text-foreground">
|
<p className="text-sm font-medium text-foreground">
|
||||||
{chapter.title}
|
{chapter.title}
|
||||||
</p>
|
</p>
|
||||||
<p>•</p>
|
<p>•</p>
|
||||||
<p className="text-xs text-muted mt-0.5">
|
<p className="text-xs text-soft mt-0.5">
|
||||||
{chapter.status !== 'completed' && <span className="text-warning">Missing • </span>}{formatDuration(chapter.duration)}
|
{chapter.status !== 'completed' && <span className="text-warning">Missing • </span>}{formatDuration(chapter.duration)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -782,7 +782,7 @@ export function AudiobookExportModal({
|
||||||
{((onRegenerateChapter && !isGenerating) || chapter.status === 'completed') && (
|
{((onRegenerateChapter && !isGenerating) || chapter.status === 'completed') && (
|
||||||
<Menu as="div" className="relative inline-block text-left">
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="inline-flex items-center justify-center rounded-md p-1.5 hover:bg-background focus:outline-none focus-visible:ring-2 focus-visible:ring-accent text-muted hover:text-foreground transform transition-transform duration-base ease-standard"
|
className="inline-flex items-center justify-center rounded-md p-1.5 hover:bg-background focus:outline-none focus-visible:ring-2 focus-visible:ring-accent text-soft hover:text-foreground transform transition-transform duration-base ease-standard"
|
||||||
title="Chapter actions"
|
title="Chapter actions"
|
||||||
>
|
>
|
||||||
<DotsVerticalIcon className="h-5 w-5" />
|
<DotsVerticalIcon className="h-5 w-5" />
|
||||||
|
|
@ -799,7 +799,7 @@ export function AudiobookExportModal({
|
||||||
<MenuItems
|
<MenuItems
|
||||||
anchor={{ to: 'bottom end', gap: '8px', padding: '12px' }}
|
anchor={{ to: 'bottom end', gap: '8px', padding: '12px' }}
|
||||||
portal
|
portal
|
||||||
className="w-44 rounded-md bg-background shadow-lg ring-1 ring-black/5 focus:outline-none z-[70] p-1 origin-top-right"
|
className="w-44 rounded-md bg-background shadow-elev-2 ring-1 ring-line-soft focus:outline-none z-[70] p-1 origin-top-right"
|
||||||
>
|
>
|
||||||
{chapter.status === 'completed' && (
|
{chapter.status === 'completed' && (
|
||||||
<>
|
<>
|
||||||
|
|
@ -807,7 +807,7 @@ export function AudiobookExportModal({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => setPendingDeleteChapter(chapter)}
|
onClick={() => setPendingDeleteChapter(chapter)}
|
||||||
className={`${active ? 'bg-offbase' : ''} text-danger group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
className={`${active ? 'bg-surface-sunken' : ''} text-danger group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
||||||
title="Delete this chapter"
|
title="Delete this chapter"
|
||||||
>
|
>
|
||||||
<XCircleIcon className="h-4 w-4" />
|
<XCircleIcon className="h-4 w-4" />
|
||||||
|
|
@ -819,7 +819,7 @@ export function AudiobookExportModal({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDownloadChapter(chapter)}
|
onClick={() => handleDownloadChapter(chapter)}
|
||||||
className={`${active ? 'bg-offbase text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
className={`${active ? 'bg-surface-sunken text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
||||||
>
|
>
|
||||||
<DownloadIcon className="h-4 w-4" />
|
<DownloadIcon className="h-4 w-4" />
|
||||||
<span>Download</span>
|
<span>Download</span>
|
||||||
|
|
@ -833,7 +833,7 @@ export function AudiobookExportModal({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={handleCancel}
|
onClick={handleCancel}
|
||||||
className={`${active ? 'bg-offbase text-danger' : 'text-danger'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
className={`${active ? 'bg-surface-sunken text-danger' : 'text-danger'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm`}
|
||||||
title="Cancel this chapter regeneration"
|
title="Cancel this chapter regeneration"
|
||||||
>
|
>
|
||||||
<XCircleIcon className="h-4 w-4" />
|
<XCircleIcon className="h-4 w-4" />
|
||||||
|
|
@ -848,7 +848,7 @@ export function AudiobookExportModal({
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRegenerateChapter(chapter)}
|
onClick={() => handleRegenerateChapter(chapter)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`${active ? 'bg-offbase text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm disabled:opacity-50 disabled:cursor-not-allowed`}
|
className={`${active ? 'bg-surface-sunken text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded px-2 py-2 text-sm disabled:opacity-50 disabled:cursor-not-allowed`}
|
||||||
title="Regenerate this chapter"
|
title="Regenerate this chapter"
|
||||||
>
|
>
|
||||||
<RefreshIcon className={`h-4 w-4 ${regeneratingChapter === chapter.index ? 'animate-spin' : ''}`} />
|
<RefreshIcon className={`h-4 w-4 ${regeneratingChapter === chapter.index ? 'animate-spin' : ''}`} />
|
||||||
|
|
@ -868,7 +868,7 @@ export function AudiobookExportModal({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{bookId && !isGenerating && (
|
{bookId && !isGenerating && (
|
||||||
<div className="pt-4 border-t border-offbase">
|
<div className="pt-4 border-t border-line-soft">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDownloadComplete}
|
onClick={handleDownloadComplete}
|
||||||
disabled={isCombining}
|
disabled={isCombining}
|
||||||
|
|
@ -888,7 +888,7 @@ export function AudiobookExportModal({
|
||||||
|
|
||||||
{chapters.length === 0 && !isGenerating && !isLoadingExisting && (
|
{chapters.length === 0 && !isGenerating && !isLoadingExisting && (
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="text-sm text-muted">
|
<p className="text-sm text-soft">
|
||||||
Audiobook settings are fixed after generation. Chapters will appear here as they are ready.
|
Audiobook settings are fixed after generation. Chapters will appear here as they are ready.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -938,43 +938,43 @@ export function AudiobookExportModal({
|
||||||
function AudiobookSettingsSkeleton() {
|
function AudiobookSettingsSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 animate-pulse" aria-label="Loading audiobook settings" aria-busy="true">
|
<div className="space-y-4 animate-pulse" aria-label="Loading audiobook settings" aria-busy="true">
|
||||||
<div className="w-full rounded-xl border border-offbase bg-background overflow-hidden">
|
<div className="w-full rounded-lg border border-line bg-background overflow-hidden">
|
||||||
<div className="flex items-center justify-between px-4 py-3 border-b border-offbase bg-base">
|
<div className="flex items-center justify-between px-4 py-3 border-b border-line-soft bg-surface">
|
||||||
<div className="h-4 w-40 rounded bg-offbase" />
|
<div className="h-4 w-40 rounded bg-surface-sunken" />
|
||||||
<div className="h-5 w-14 rounded bg-offbase" />
|
<div className="h-5 w-14 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 space-y-4">
|
<div className="p-4 space-y-4">
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="h-3 w-16 rounded bg-offbase" />
|
<div className="h-3 w-16 rounded bg-surface-sunken" />
|
||||||
<div className="h-9 w-full rounded-md bg-offbase" />
|
<div className="h-9 w-full rounded-md bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="h-3 w-16 rounded bg-offbase" />
|
<div className="h-3 w-16 rounded bg-surface-sunken" />
|
||||||
<div className="h-9 w-full rounded-md bg-offbase" />
|
<div className="h-9 w-full rounded-md bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="h-3 w-24 rounded bg-offbase" />
|
<div className="h-3 w-24 rounded bg-surface-sunken" />
|
||||||
<div className="h-2 w-full rounded bg-offbase" />
|
<div className="h-2 w-full rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<div className="h-3 w-20 rounded bg-offbase" />
|
<div className="h-3 w-20 rounded bg-surface-sunken" />
|
||||||
<div className="h-2 w-full rounded bg-offbase" />
|
<div className="h-2 w-full rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-9 w-full rounded-md bg-offbase" />
|
<div className="h-9 w-full rounded-md bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full rounded-xl border border-offbase bg-background overflow-hidden">
|
<div className="w-full rounded-lg border border-line bg-background overflow-hidden">
|
||||||
<div className="px-4 py-3 border-b border-offbase bg-base">
|
<div className="px-4 py-3 border-b border-line-soft bg-surface">
|
||||||
<div className="h-4 w-28 rounded bg-offbase" />
|
<div className="h-4 w-28 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="p-4 space-y-2">
|
<div className="p-4 space-y-2">
|
||||||
{Array.from({ length: 4 }).map((_, index) => (
|
{Array.from({ length: 4 }).map((_, index) => (
|
||||||
<div key={index} className="h-16 rounded-lg border border-offbase bg-base" />
|
<div key={index} className="h-16 rounded-lg border border-line bg-surface" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function CodeBlock({ children }: { children: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative group my-2">
|
<div className="relative group my-2">
|
||||||
<pre className="bg-background p-3 rounded-md overflow-x-auto text-xs text-left
|
<pre className="bg-background p-3 rounded-md overflow-x-auto text-xs text-left
|
||||||
font-mono text-foreground border border-offbase">
|
font-mono text-foreground border border-line">
|
||||||
<code>{children}</code>
|
<code>{children}</code>
|
||||||
</pre>
|
</pre>
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,7 @@ export function ColorPicker({ value, field, label, onChange }: ColorPickerProps)
|
||||||
<PopoverPanel
|
<PopoverPanel
|
||||||
anchor="bottom start"
|
anchor="bottom start"
|
||||||
transition
|
transition
|
||||||
className="z-[60] mt-2 w-56 rounded-xl shadow-xl border border-offbase bg-background p-3 space-y-3
|
className="z-[60] mt-2 w-56 rounded-lg shadow-elev-3 border border-line bg-background p-3 space-y-3
|
||||||
transition duration-fast ease-standard data-[closed]:opacity-0 data-[closed]:scale-95"
|
transition duration-fast ease-standard data-[closed]:opacity-0 data-[closed]:scale-95"
|
||||||
>
|
>
|
||||||
{/* Label */}
|
{/* Label */}
|
||||||
|
|
@ -107,7 +107,7 @@ export function ColorPicker({ value, field, label, onChange }: ColorPickerProps)
|
||||||
className="p-1"
|
className="p-1"
|
||||||
aria-label="Open system color picker"
|
aria-label="Open system color picker"
|
||||||
>
|
>
|
||||||
<PaletteIcon className="w-4 h-4 text-muted transform transition-transform duration-base ease-standard hover:text-accent" />
|
<PaletteIcon className="w-4 h-4 text-soft transform transition-transform duration-base ease-standard hover:text-accent" />
|
||||||
</button>
|
</button>
|
||||||
<input
|
<input
|
||||||
ref={nativeRef}
|
ref={nativeRef}
|
||||||
|
|
@ -166,7 +166,7 @@ export function ColorPicker({ value, field, label, onChange }: ColorPickerProps)
|
||||||
}}
|
}}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
maxLength={7}
|
maxLength={7}
|
||||||
className="flex-1 rounded-lg px-2 py-1 text-xs font-mono border border-offbase bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-accent"
|
className="flex-1 rounded-lg px-2 py-1 text-xs font-mono border border-line bg-background text-foreground focus:outline-none focus:ring-1 focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ export function ConfirmDialog({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel role='dialog' className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel role='dialog' className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-semibold leading-6 text-foreground"
|
className="text-lg font-semibold leading-6 text-foreground"
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export function CookieConsentBanner() {
|
||||||
leaveTo="translate-y-full opacity-0"
|
leaveTo="translate-y-full opacity-0"
|
||||||
className="fixed bottom-0 left-0 right-0 z-[60] px-4 pt-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:px-6 md:pt-6 md:pb-[max(1.5rem,env(safe-area-inset-bottom))]"
|
className="fixed bottom-0 left-0 right-0 z-[60] px-4 pt-4 pb-[max(1rem,env(safe-area-inset-bottom))] md:px-6 md:pt-6 md:pb-[max(1.5rem,env(safe-area-inset-bottom))]"
|
||||||
>
|
>
|
||||||
<div className="mx-auto max-w-5xl rounded-xl border border-offbase bg-base p-5 shadow-2xl md:flex md:items-center md:justify-between md:gap-8">
|
<div className="mx-auto max-w-5xl rounded-lg border border-line bg-surface p-5 shadow-elev-3 md:flex md:items-center md:justify-between md:gap-8">
|
||||||
<div className="mb-4 md:mb-0">
|
<div className="mb-4 md:mb-0">
|
||||||
<h3 className="mb-2 text-lg font-bold">
|
<h3 className="mb-2 text-lg font-bold">
|
||||||
🍪 We use cookies
|
🍪 We use cookies
|
||||||
|
|
@ -70,7 +70,7 @@ export function CookieConsentBanner() {
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'primary',
|
variant: 'primary',
|
||||||
size: 'md',
|
size: 'md',
|
||||||
className: 'whitespace-nowrap font-bold shadow-sm',
|
className: 'whitespace-nowrap font-bold shadow-elev-1',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
Accept All
|
Accept All
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ export function Header({
|
||||||
right?: ReactNode;
|
right?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-0 z-40 w-full border-b border-offbase bg-base" data-app-header>
|
<div className="sticky top-0 z-40 w-full border-b border-line-soft bg-surface" data-app-header>
|
||||||
<div className="px-2 sm:px-3 py-1 flex items-center justify-between gap-2 min-h-10">
|
<div className="px-2 sm:px-3 py-1 flex items-center justify-between gap-2 min-h-10">
|
||||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||||
{left}
|
{left}
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ interface PrivacyModalProps {
|
||||||
function PrivacyModalBody({ origin }: { origin: string }) {
|
function PrivacyModalBody({ origin }: { origin: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="mt-4 space-y-4 text-sm text-soft">
|
<div className="mt-4 space-y-4 text-sm text-soft">
|
||||||
<div className="rounded-lg border border-offbase bg-surface-sunken p-3">
|
<div className="rounded-lg border border-line bg-surface-sunken p-3">
|
||||||
<div className="text-xs font-semibold uppercase tracking-wide text-muted">Service Operator</div>
|
<div className="text-xs font-semibold uppercase tracking-wide text-soft">Service Operator</div>
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
This instance is hosted at <span className="font-bold">{origin || 'this server'}</span>.
|
This instance is hosted at <span className="font-bold">{origin || 'this server'}</span>.
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -102,7 +102,7 @@ export function PrivacyModal({ isOpen, onAccept, onDismiss }: PrivacyModalProps)
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel data-testid="privacy-modal" className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel data-testid="privacy-modal" className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-semibold leading-6 text-foreground"
|
className="text-lg font-semibold leading-6 text-foreground"
|
||||||
|
|
@ -113,7 +113,7 @@ export function PrivacyModal({ isOpen, onAccept, onDismiss }: PrivacyModalProps)
|
||||||
<PrivacyModalBody origin={origin} />
|
<PrivacyModalBody origin={origin} />
|
||||||
|
|
||||||
<div className="mt-6 space-y-4">
|
<div className="mt-6 space-y-4">
|
||||||
<div className="flex items-start gap-3 rounded-lg border border-offbase p-3 bg-surface-sunken">
|
<div className="flex items-start gap-3 rounded-lg border border-line p-3 bg-surface-sunken">
|
||||||
<div className="flex h-6 items-center">
|
<div className="flex h-6 items-center">
|
||||||
<input
|
<input
|
||||||
data-testid="privacy-agree-checkbox"
|
data-testid="privacy-agree-checkbox"
|
||||||
|
|
@ -121,7 +121,7 @@ export function PrivacyModal({ isOpen, onAccept, onDismiss }: PrivacyModalProps)
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={agreed}
|
checked={agreed}
|
||||||
onChange={(e) => setAgreed(e.target.checked)}
|
onChange={(e) => setAgreed(e.target.checked)}
|
||||||
className="h-4 w-4 rounded border-line text-accent focus:ring-accent bg-base"
|
className="h-4 w-4 rounded border-line text-accent focus:ring-accent-line bg-surface"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-sm leading-6">
|
<div className="text-sm leading-6">
|
||||||
|
|
@ -216,7 +216,7 @@ export function showPrivacyModal(): void {
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-semibold leading-6 text-foreground"
|
className="text-lg font-semibold leading-6 text-foreground"
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export function ProgressCard({
|
||||||
const operationLabel = getOperationLabel();
|
const operationLabel = getOperationLabel();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-offbase rounded-lg p-3 space-y-2">
|
<div className="bg-surface-sunken rounded-lg p-3 space-y-2">
|
||||||
{/* Header with operation type and cancel button */}
|
{/* Header with operation type and cancel button */}
|
||||||
<div className="flex items-start justify-between gap-3">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex-1 min-w-0 space-y-1">
|
<div className="flex-1 min-w-0 space-y-1">
|
||||||
|
|
@ -57,7 +57,7 @@ export function ProgressCard({
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'ghost',
|
variant: 'ghost',
|
||||||
size: 'xs',
|
size: 'xs',
|
||||||
className: 'shrink-0 hover:bg-surface-sunken',
|
className: 'shrink-0 hover:bg-accent-wash',
|
||||||
})}
|
})}
|
||||||
onClick={(e) => onCancel(e)}
|
onClick={(e) => onCancel(e)}
|
||||||
>
|
>
|
||||||
|
|
@ -74,7 +74,7 @@ export function ProgressCard({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats row */}
|
{/* Stats row */}
|
||||||
<div className="flex items-center gap-2 text-xs text-muted">
|
<div className="flex items-center gap-2 text-xs text-soft">
|
||||||
{completedChapters !== undefined && (
|
{completedChapters !== undefined && (
|
||||||
<>
|
<>
|
||||||
<span className="font-medium">{completedChapters} chapters</span>
|
<span className="font-medium">{completedChapters} chapters</span>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function ProgressPopup({
|
||||||
<div className="fixed inset-x-0 top-2 z-[60] pointer-events-none px-4">
|
<div className="fixed inset-x-0 top-2 z-[60] pointer-events-none px-4">
|
||||||
<div className="w-full max-w-md mx-auto">
|
<div className="w-full max-w-md mx-auto">
|
||||||
<div
|
<div
|
||||||
className={`pointer-events-auto shadow-xl ${
|
className={`pointer-events-auto shadow-elev-3 ${
|
||||||
onClick ? 'cursor-pointer' : ''
|
onClick ? 'cursor-pointer' : ''
|
||||||
}`}
|
}`}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
|
|
||||||
|
|
@ -142,7 +142,7 @@ export function SettingsTrigger({
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
onClick={onOpen}
|
onClick={onOpen}
|
||||||
className={`inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase hover:text-accent transition-transform transition-colors duration-base ease-standard ${className}`}
|
className={`inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash hover:text-accent transition-transform transition-colors duration-base ease-standard ${className}`}
|
||||||
aria-label="Settings"
|
aria-label="Settings"
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
|
|
@ -483,8 +483,8 @@ export function SettingsModal({
|
||||||
setActiveSection(visibleSections[0]?.id ?? 'theme');
|
setActiveSection(visibleSections[0]?.id ?? 'theme');
|
||||||
}, [activeSection, visibleSections]);
|
}, [activeSection, visibleSections]);
|
||||||
|
|
||||||
const fieldLabelClass = 'block text-[11px] font-semibold uppercase tracking-wide text-muted';
|
const fieldLabelClass = 'block text-[11px] font-semibold uppercase tracking-wide text-soft';
|
||||||
const sectionShellClass = 'space-y-2 pb-3 border-b border-offbase px-0.5';
|
const sectionShellClass = 'space-y-2 pb-3 border-b border-line-soft px-0.5';
|
||||||
const sectionHeadingClass = 'text-sm font-semibold text-foreground';
|
const sectionHeadingClass = 'text-sm font-semibold text-foreground';
|
||||||
const effectiveProviderType = resolveEffectiveProviderType({
|
const effectiveProviderType = resolveEffectiveProviderType({
|
||||||
providerRef: selectedProviderRef,
|
providerRef: selectedProviderRef,
|
||||||
|
|
@ -540,16 +540,16 @@ export function SettingsModal({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel data-testid="settings-modal" className="relative w-full max-w-4xl transform rounded-xl bg-base text-left align-middle shadow-xl transition overflow-hidden border border-offbase">
|
<DialogPanel data-testid="settings-modal" className="relative w-full max-w-4xl transform rounded-lg bg-surface text-left align-middle shadow-elev-3 transition overflow-hidden border border-line">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between px-5 py-3 border-b border-offbase">
|
<div className="flex items-center justify-between px-5 py-3 border-b border-line-soft">
|
||||||
<div className="flex items-baseline gap-4">
|
<div className="flex items-baseline gap-4">
|
||||||
<DialogTitle as="h3" className="text-lg font-semibold leading-6 text-foreground">
|
<DialogTitle as="h3" className="text-lg font-semibold leading-6 text-foreground">
|
||||||
Settings
|
Settings
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsChangelogOpen(true)}
|
onClick={() => setIsChangelogOpen(true)}
|
||||||
className="text-sm font-medium leading-6 text-muted hover:text-accent transition-colors"
|
className="text-sm font-medium leading-6 text-soft hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
{displayVersion ? `v${displayVersion} · Changelog` : 'Changelog'}
|
{displayVersion ? `v${displayVersion} · Changelog` : 'Changelog'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -557,7 +557,7 @@ export function SettingsModal({
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => showPrivacyModal()}
|
onClick={() => showPrivacyModal()}
|
||||||
className="text-sm font-medium text-muted hover:text-accent transition-colors"
|
className="text-sm font-medium text-soft hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
Privacy
|
Privacy
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -573,7 +573,7 @@ export function SettingsModal({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{/* Mobile: 2x2 grid nav */}
|
{/* Mobile: 2x2 grid nav */}
|
||||||
<div className="grid grid-cols-2 gap-1 sm:hidden border-b border-offbase bg-background p-2">
|
<div className="grid grid-cols-2 gap-1 sm:hidden border-b border-line-soft bg-background p-2">
|
||||||
{visibleSections.map((section) => {
|
{visibleSections.map((section) => {
|
||||||
const Icon = section.icon;
|
const Icon = section.icon;
|
||||||
return (
|
return (
|
||||||
|
|
@ -583,7 +583,7 @@ export function SettingsModal({
|
||||||
className={`flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-lg text-xs font-medium transition-colors ${
|
className={`flex items-center justify-center gap-1.5 px-2 py-1.5 rounded-lg text-xs font-medium transition-colors ${
|
||||||
activeSection === section.id
|
activeSection === section.id
|
||||||
? 'bg-accent text-background'
|
? 'bg-accent text-background'
|
||||||
: 'text-foreground hover:bg-offbase hover:text-accent'
|
: 'text-foreground hover:bg-accent-wash hover:text-accent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className="w-3.5 h-3.5" />
|
<Icon className="w-3.5 h-3.5" />
|
||||||
|
|
@ -595,7 +595,7 @@ export function SettingsModal({
|
||||||
|
|
||||||
<div className="flex flex-row h-[490px]">
|
<div className="flex flex-row h-[490px]">
|
||||||
{/* Desktop: vertical sidebar */}
|
{/* Desktop: vertical sidebar */}
|
||||||
<nav className="hidden sm:block w-44 shrink-0 border-r border-offbase bg-background p-2">
|
<nav className="hidden sm:block w-44 shrink-0 border-r border-line-soft bg-background p-2">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
{visibleSections.map((section) => {
|
{visibleSections.map((section) => {
|
||||||
const Icon = section.icon;
|
const Icon = section.icon;
|
||||||
|
|
@ -607,7 +607,7 @@ export function SettingsModal({
|
||||||
className={`w-full flex items-center gap-2.5 text-left px-2.5 py-1.5 rounded-md text-sm font-medium transition-colors whitespace-nowrap ${
|
className={`w-full flex items-center gap-2.5 text-left px-2.5 py-1.5 rounded-md text-sm font-medium transition-colors whitespace-nowrap ${
|
||||||
active
|
active
|
||||||
? 'bg-accent text-background'
|
? 'bg-accent text-background'
|
||||||
: 'text-foreground hover:bg-base hover:text-accent'
|
: 'text-foreground hover:bg-surface hover:text-accent'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon className="w-4 h-4 shrink-0" />
|
<Icon className="w-4 h-4 shrink-0" />
|
||||||
|
|
@ -661,7 +661,7 @@ export function SettingsModal({
|
||||||
{selectedProviderOption?.name || 'Select Provider'}
|
{selectedProviderOption?.name || 'Select Provider'}
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon className="h-5 w-5 text-muted" />
|
<ChevronUpDownIcon className="h-5 w-5 text-soft" />
|
||||||
</span>
|
</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<Transition
|
<Transition
|
||||||
|
|
@ -700,7 +700,7 @@ export function SettingsModal({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{restrictUserApiKeys && (
|
{restrictUserApiKeys && (
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
This instance restricts user API keys. TTS runs through admin-configured shared providers only.
|
This instance restricts user API keys. TTS runs through admin-configured shared providers only.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -737,7 +737,7 @@ export function SettingsModal({
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isSharedSelected && (
|
{isSharedSelected && (
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
This is a shared provider configured by an admin. API key and base URL are managed server-side.
|
This is a shared provider configured by an admin. API key and base URL are managed server-side.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -745,7 +745,7 @@ export function SettingsModal({
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className={fieldLabelClass}>TTS Model</label>
|
<label className={fieldLabelClass}>TTS Model</label>
|
||||||
{!showAllProviderModels && (
|
{!showAllProviderModels && (
|
||||||
<p className="text-xs text-muted">
|
<p className="text-xs text-soft">
|
||||||
This instance restricts model selection to each provider's default model.
|
This instance restricts model selection to each provider's default model.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -768,7 +768,7 @@ export function SettingsModal({
|
||||||
{selectedModel.name}
|
{selectedModel.name}
|
||||||
</span>
|
</span>
|
||||||
{selectedModelVersion && (
|
{selectedModelVersion && (
|
||||||
<span className="block truncate text-xs text-muted">
|
<span className="block truncate text-xs text-soft">
|
||||||
{selectedModelVersion}
|
{selectedModelVersion}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -777,7 +777,7 @@ export function SettingsModal({
|
||||||
<span className="block truncate">Select Model</span>
|
<span className="block truncate">Select Model</span>
|
||||||
)}
|
)}
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
<ChevronUpDownIcon className="h-5 w-5 text-muted" />
|
<ChevronUpDownIcon className="h-5 w-5 text-soft" />
|
||||||
</span>
|
</span>
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<Transition
|
<Transition
|
||||||
|
|
@ -801,7 +801,7 @@ export function SettingsModal({
|
||||||
<span className={`block ${selected ? 'font-medium' : 'font-normal'}`}>
|
<span className={`block ${selected ? 'font-medium' : 'font-normal'}`}>
|
||||||
<span className="block truncate">{model.name}</span>
|
<span className="block truncate">{model.name}</span>
|
||||||
{model.id.includes(':') && (
|
{model.id.includes(':') && (
|
||||||
<span className="block truncate text-xs text-muted">
|
<span className="block truncate text-xs text-soft">
|
||||||
{model.id.slice(model.id.indexOf(':'))}
|
{model.id.slice(model.id.indexOf(':'))}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -911,7 +911,7 @@ export function SettingsModal({
|
||||||
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 w-full text-left transition duration-base ease-standard transform border
|
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 w-full text-left transition duration-base ease-standard transform border
|
||||||
${isActive
|
${isActive
|
||||||
? 'border-accent'
|
? 'border-accent'
|
||||||
: 'border-offbase hover:border-muted'
|
: 'border-line hover:border-accent-line'
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: colors.base }}
|
style={{ backgroundColor: colors.base }}
|
||||||
>
|
>
|
||||||
|
|
@ -927,8 +927,8 @@ export function SettingsModal({
|
||||||
{systemTheme.name}
|
{systemTheme.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-1 ml-auto">
|
<div className="flex gap-1 ml-auto">
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.background }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.background }} />
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.offbase }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.offbase }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
||||||
|
|
@ -940,7 +940,7 @@ export function SettingsModal({
|
||||||
|
|
||||||
{/* Custom theme */}
|
{/* Custom theme */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="block text-xs font-medium text-muted uppercase tracking-wide">Custom</label>
|
<label className="block text-xs font-medium text-soft uppercase tracking-wide">Custom</label>
|
||||||
{(() => {
|
{(() => {
|
||||||
const colors = getThemeColors('custom');
|
const colors = getThemeColors('custom');
|
||||||
const isActive = theme === 'custom';
|
const isActive = theme === 'custom';
|
||||||
|
|
@ -955,7 +955,7 @@ export function SettingsModal({
|
||||||
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 flex-1 text-left transition duration-base ease-standard transform border
|
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 flex-1 text-left transition duration-base ease-standard transform border
|
||||||
${isActive
|
${isActive
|
||||||
? 'border-accent'
|
? 'border-accent'
|
||||||
: 'border-offbase hover:border-muted'
|
: 'border-line hover:border-accent-line'
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: colors.base }}
|
style={{ backgroundColor: colors.base }}
|
||||||
>
|
>
|
||||||
|
|
@ -971,8 +971,8 @@ export function SettingsModal({
|
||||||
Custom
|
Custom
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-1 ml-auto">
|
<div className="flex gap-1 ml-auto">
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.background }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.background }} />
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.offbase }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.offbase }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
||||||
|
|
@ -980,7 +980,7 @@ export function SettingsModal({
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsCustomExpanded(!isCustomExpanded)}
|
onClick={() => setIsCustomExpanded(!isCustomExpanded)}
|
||||||
className="shrink-0 p-1.5 rounded-lg border border-offbase hover:border-muted transition-colors"
|
className="shrink-0 p-1.5 rounded-lg border border-line hover:border-accent-line transition-colors"
|
||||||
style={{ color: colors.muted, backgroundColor: colors.base }}
|
style={{ color: colors.muted, backgroundColor: colors.base }}
|
||||||
aria-label={isCustomExpanded ? 'Collapse color picker' : 'Expand color picker'}
|
aria-label={isCustomExpanded ? 'Collapse color picker' : 'Expand color picker'}
|
||||||
>
|
>
|
||||||
|
|
@ -1043,7 +1043,7 @@ export function SettingsModal({
|
||||||
|
|
||||||
{/* Light themes */}
|
{/* Light themes */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="block text-xs font-medium text-muted uppercase tracking-wide">Light</label>
|
<label className="block text-xs font-medium text-soft uppercase tracking-wide">Light</label>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
|
||||||
{lightThemes.map((t) => {
|
{lightThemes.map((t) => {
|
||||||
const colors = getThemeColors(t.id);
|
const colors = getThemeColors(t.id);
|
||||||
|
|
@ -1055,7 +1055,7 @@ export function SettingsModal({
|
||||||
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 text-left transition duration-base ease-standard transform border
|
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 text-left transition duration-base ease-standard transform border
|
||||||
${isActive
|
${isActive
|
||||||
? 'border-accent'
|
? 'border-accent'
|
||||||
: 'border-offbase hover:border-muted'
|
: 'border-line hover:border-accent-line'
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: colors.base }}
|
style={{ backgroundColor: colors.base }}
|
||||||
>
|
>
|
||||||
|
|
@ -1071,8 +1071,8 @@ export function SettingsModal({
|
||||||
{t.name}
|
{t.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-1 ml-auto">
|
<div className="flex gap-1 ml-auto">
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.background }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.background }} />
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.offbase }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.offbase }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
||||||
|
|
@ -1085,7 +1085,7 @@ export function SettingsModal({
|
||||||
|
|
||||||
{/* Dark themes */}
|
{/* Dark themes */}
|
||||||
<div className="space-y-1.5">
|
<div className="space-y-1.5">
|
||||||
<label className="block text-xs font-medium text-muted uppercase tracking-wide">Dark</label>
|
<label className="block text-xs font-medium text-soft uppercase tracking-wide">Dark</label>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-1.5">
|
||||||
{darkThemes.map((t) => {
|
{darkThemes.map((t) => {
|
||||||
const colors = getThemeColors(t.id);
|
const colors = getThemeColors(t.id);
|
||||||
|
|
@ -1097,7 +1097,7 @@ export function SettingsModal({
|
||||||
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 text-left transition duration-base ease-standard transform border
|
className={`flex items-center gap-2 rounded-lg px-2 py-1.5 text-left transition duration-base ease-standard transform border
|
||||||
${isActive
|
${isActive
|
||||||
? 'border-accent'
|
? 'border-accent'
|
||||||
: 'border-offbase hover:border-muted'
|
: 'border-line hover:border-accent-line'
|
||||||
}`}
|
}`}
|
||||||
style={{ backgroundColor: colors.base }}
|
style={{ backgroundColor: colors.base }}
|
||||||
>
|
>
|
||||||
|
|
@ -1113,8 +1113,8 @@ export function SettingsModal({
|
||||||
{t.name}
|
{t.name}
|
||||||
</span>
|
</span>
|
||||||
<div className="flex gap-1 ml-auto">
|
<div className="flex gap-1 ml-auto">
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.background }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.background }} />
|
||||||
<div className="w-4 h-4 rounded-full border border-offbase" style={{ backgroundColor: colors.offbase }} />
|
<div className="w-4 h-4 rounded-full border border-line" style={{ backgroundColor: colors.offbase }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.accent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.secondaryAccent }} />
|
||||||
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
<div className="w-4 h-4 rounded-full" style={{ backgroundColor: colors.muted }} />
|
||||||
|
|
@ -1199,10 +1199,10 @@ export function SettingsModal({
|
||||||
{activeSection === 'account' && (
|
{activeSection === 'account' && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{/* Session info */}
|
{/* Session info */}
|
||||||
<div className="rounded-lg bg-background border border-offbase p-4 space-y-2">
|
<div className="rounded-lg bg-background border border-line p-4 space-y-2">
|
||||||
<h4 className="text-sm font-medium text-foreground">Current Session</h4>
|
<h4 className="text-sm font-medium text-foreground">Current Session</h4>
|
||||||
<div className="text-sm space-y-1">
|
<div className="text-sm space-y-1">
|
||||||
<p className="text-muted">Logged in as:</p>
|
<p className="text-soft">Logged in as:</p>
|
||||||
{session?.user ? (
|
{session?.user ? (
|
||||||
<>
|
<>
|
||||||
<p className="font-medium text-foreground">
|
<p className="font-medium text-foreground">
|
||||||
|
|
@ -1211,7 +1211,7 @@ export function SettingsModal({
|
||||||
: (session.user.name || session.user.email || 'Account')}
|
: (session.user.name || session.user.email || 'Account')}
|
||||||
</p>
|
</p>
|
||||||
{!session.user.isAnonymous && (
|
{!session.user.isAnonymous && (
|
||||||
<p className="text-xs text-muted font-mono">{session.user.email}</p>
|
<p className="text-xs text-soft font-mono">{session.user.email}</p>
|
||||||
)}
|
)}
|
||||||
{session.user.isAnonymous && (
|
{session.user.isAnonymous && (
|
||||||
<p className="text-xs text-accent mt-1">Anonymous session</p>
|
<p className="text-xs text-accent mt-1">Anonymous session</p>
|
||||||
|
|
@ -1229,14 +1229,14 @@ export function SettingsModal({
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open('/api/user/export', '_blank');
|
window.open('/api/user/export', '_blank');
|
||||||
}}
|
}}
|
||||||
className="w-full rounded-lg border border-offbase bg-background p-4 flex items-center gap-4 hover:bg-offbase transition-colors text-left group"
|
className="w-full rounded-lg border border-line bg-background p-4 flex items-center gap-4 hover:bg-accent-wash transition-colors text-left group"
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-offbase flex items-center justify-center group-hover:bg-background transition-colors">
|
<div className="flex-shrink-0 w-10 h-10 rounded-lg bg-surface-sunken flex items-center justify-center group-hover:bg-background transition-colors">
|
||||||
<DownloadIcon className="w-5 h-5 text-accent" />
|
<DownloadIcon className="w-5 h-5 text-accent" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-foreground">Export My Data</p>
|
<p className="text-sm font-medium text-foreground">Export My Data</p>
|
||||||
<p className="text-xs text-muted">Download all your data as a ZIP file</p>
|
<p className="text-xs text-soft">Download all your data as a ZIP file</p>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1252,7 +1252,7 @@ export function SettingsModal({
|
||||||
Disconnect account
|
Disconnect account
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="pt-4 mt-4 border-t border-offbase">
|
<div className="pt-4 mt-4 border-t border-line-soft">
|
||||||
<label className="block text-sm font-medium text-danger mb-2">Danger Zone</label>
|
<label className="block text-sm font-medium text-danger mb-2">Danger Zone</label>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowDeleteAccountConfirm(true)}
|
onClick={() => setShowDeleteAccountConfirm(true)}
|
||||||
|
|
@ -1260,14 +1260,14 @@ export function SettingsModal({
|
||||||
>
|
>
|
||||||
Delete Account
|
Delete Account
|
||||||
</Button>
|
</Button>
|
||||||
<p className="text-xs text-muted mt-2">
|
<p className="text-xs text-soft mt-2">
|
||||||
Permanently deletes your account and all data.
|
Permanently deletes your account and all data.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="pt-2 border-t border-offbase">
|
<div className="pt-2 border-t border-line-soft">
|
||||||
<p className="text-sm text-muted mb-3">
|
<p className="text-sm text-soft mb-3">
|
||||||
{session?.user?.isAnonymous
|
{session?.user?.isAnonymous
|
||||||
? (runtimeConfig.enableUserSignups
|
? (runtimeConfig.enableUserSignups
|
||||||
? 'You are using an anonymous session. Sign up to save your progress permanently, your current data is automatically transferred.'
|
? 'You are using an anonymous session. Sign up to save your progress permanently, your current data is automatically transferred.'
|
||||||
|
|
@ -1435,11 +1435,11 @@ function SettingsChangelogPanel({
|
||||||
}, [expanded, manifest, manifestUrl, bodies]);
|
}, [expanded, manifest, manifestUrl, bodies]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-[490px] flex flex-col bg-base">
|
<div className="h-[490px] flex flex-col bg-surface">
|
||||||
<div className="flex items-center gap-3 px-4 py-3 border-b border-offbase bg-background">
|
<div className="flex items-center gap-3 px-4 py-3 border-b border-line-soft bg-background">
|
||||||
<Button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="inline-flex items-center justify-center rounded-md text-muted hover:text-accent hover:bg-base transition duration-base ease-standard transform"
|
className="inline-flex items-center justify-center rounded-md text-soft hover:text-accent hover:bg-surface transition duration-base ease-standard transform"
|
||||||
aria-label="Back to settings"
|
aria-label="Back to settings"
|
||||||
title="Back"
|
title="Back"
|
||||||
>
|
>
|
||||||
|
|
@ -1449,7 +1449,7 @@ function SettingsChangelogPanel({
|
||||||
</Button>
|
</Button>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h4 className="text-sm font-semibold text-foreground">Changelog</h4>
|
<h4 className="text-sm font-semibold text-foreground">Changelog</h4>
|
||||||
<p className="text-xs text-muted truncate">
|
<p className="text-xs text-soft truncate">
|
||||||
{normalizedAppVersion
|
{normalizedAppVersion
|
||||||
? `Current version: v${normalizedAppVersion}`
|
? `Current version: v${normalizedAppVersion}`
|
||||||
: 'Release history from GitHub'}
|
: 'Release history from GitHub'}
|
||||||
|
|
@ -1459,15 +1459,15 @@ function SettingsChangelogPanel({
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto px-3 pb-3">
|
<div className="flex-1 overflow-y-auto px-3 pb-3">
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="py-3 text-sm text-muted">
|
<div className="py-3 text-sm text-soft">
|
||||||
Loading changelog…
|
Loading changelog…
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && error && (
|
{!loading && error && (
|
||||||
<div className="py-3 space-y-2 border-b border-offbase">
|
<div className="py-3 space-y-2 border-b border-line-soft">
|
||||||
<p className="text-sm text-foreground">Could not load changelog right now.</p>
|
<p className="text-sm text-foreground">Could not load changelog right now.</p>
|
||||||
<p className="text-xs text-muted break-words">{error}</p>
|
<p className="text-xs text-soft break-words">{error}</p>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/richardr1126/openreader/releases"
|
href="https://github.com/richardr1126/openreader/releases"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|
@ -1480,7 +1480,7 @@ function SettingsChangelogPanel({
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!loading && !error && manifest.length === 0 && (
|
{!loading && !error && manifest.length === 0 && (
|
||||||
<div className="py-3 text-sm text-muted">
|
<div className="py-3 text-sm text-soft">
|
||||||
No releases found.
|
No releases found.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -1493,35 +1493,35 @@ function SettingsChangelogPanel({
|
||||||
const normalizedName = normalizeVersion(entry.name || '');
|
const normalizedName = normalizeVersion(entry.name || '');
|
||||||
const showName = Boolean(entry.name) && normalizedName !== normalizedTag;
|
const showName = Boolean(entry.name) && normalizedName !== normalizedTag;
|
||||||
return (
|
return (
|
||||||
<div key={entry.tag_name} className="border-b border-offbase">
|
<div key={entry.tag_name} className="border-b border-line-soft">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setExpanded((prev) => ({ ...prev, [entry.tag_name]: !isExpanded }))}
|
onClick={() => setExpanded((prev) => ({ ...prev, [entry.tag_name]: !isExpanded }))}
|
||||||
className="w-full text-left py-2 flex items-center gap-2 hover:bg-base transition duration-base ease-standard transform"
|
className="w-full text-left py-2 flex items-center gap-2 hover:bg-surface transition duration-base ease-standard transform"
|
||||||
>
|
>
|
||||||
<ChevronRightIcon
|
<ChevronRightIcon
|
||||||
className={`w-3.5 h-3.5 shrink-0 text-muted transition-transform ${
|
className={`w-3.5 h-3.5 shrink-0 text-soft transition-transform ${
|
||||||
isExpanded ? 'rotate-90 text-foreground' : ''
|
isExpanded ? 'rotate-90 text-foreground' : ''
|
||||||
}`}
|
}`}
|
||||||
/>
|
/>
|
||||||
<div className="min-w-0 flex items-center gap-2 text-sm w-full">
|
<div className="min-w-0 flex items-center gap-2 text-sm w-full">
|
||||||
<span className="font-semibold text-foreground shrink-0">{entry.tag_name}</span>
|
<span className="font-semibold text-foreground shrink-0">{entry.tag_name}</span>
|
||||||
{entry.prerelease && (
|
{entry.prerelease && (
|
||||||
<span className="text-[10px] uppercase tracking-wide font-semibold rounded px-1.5 py-0.5 bg-offbase text-muted shrink-0">
|
<span className="text-[10px] uppercase tracking-wide font-semibold rounded px-1.5 py-0.5 bg-surface-sunken text-soft shrink-0">
|
||||||
prerelease
|
prerelease
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{isCurrent && (
|
{isCurrent && (
|
||||||
<span className="text-[10px] uppercase tracking-wide font-semibold rounded px-1.5 py-0.5 bg-offbase text-accent shrink-0">
|
<span className="text-[10px] uppercase tracking-wide font-semibold rounded px-1.5 py-0.5 bg-surface-sunken text-accent shrink-0">
|
||||||
current
|
current
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{showName && (
|
{showName && (
|
||||||
<span className="text-xs text-muted truncate">
|
<span className="text-xs text-soft truncate">
|
||||||
{entry.name}
|
{entry.name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span className="text-[11px] text-muted shrink-0">
|
<span className="text-[11px] text-soft shrink-0">
|
||||||
{new Date(entry.published_at).toLocaleDateString()}
|
{new Date(entry.published_at).toLocaleDateString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1530,13 +1530,13 @@ function SettingsChangelogPanel({
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<div className="pl-6 pr-1 pb-3 pt-1 space-y-2">
|
<div className="pl-6 pr-1 pb-3 pt-1 space-y-2">
|
||||||
{body ? (
|
{body ? (
|
||||||
<div className="text-sm text-foreground leading-6 space-y-2 [&_h1]:text-base [&_h1]:font-semibold [&_h2]:text-sm [&_h2]:font-semibold [&_ul]:pl-5 [&_ol]:pl-5 [&_code]:bg-offbase [&_code]:rounded [&_code]:px-1 [&_pre]:bg-offbase [&_pre]:rounded [&_pre]:p-2 [&_pre]:overflow-x-auto">
|
<div className="text-sm text-foreground leading-6 space-y-2 [&_h1]:text-base [&_h1]:font-semibold [&_h2]:text-sm [&_h2]:font-semibold [&_ul]:pl-5 [&_ol]:pl-5 [&_code]:bg-surface-sunken [&_code]:rounded [&_code]:px-1 [&_pre]:bg-surface-sunken [&_pre]:rounded [&_pre]:p-2 [&_pre]:overflow-x-auto">
|
||||||
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
<ReactMarkdown remarkPlugins={[remarkGfm]}>
|
||||||
{body.body || '_No release notes provided._'}
|
{body.body || '_No release notes provided._'}
|
||||||
</ReactMarkdown>
|
</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-muted">Loading release notes…</p>
|
<p className="text-xs text-soft">Loading release notes…</p>
|
||||||
)}
|
)}
|
||||||
<a
|
<a
|
||||||
href={entry.html_url}
|
href={entry.html_url}
|
||||||
|
|
|
||||||
|
|
@ -252,11 +252,11 @@ export function AuthLoader({ children }: { children: ReactNode }) {
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 bg-base z-50 flex flex-col items-center justify-center gap-4">
|
<div className="fixed inset-0 bg-surface z-50 flex flex-col items-center justify-center gap-4">
|
||||||
<LoadingSpinner className="w-8 h-8 text-accent" />
|
<LoadingSpinner className="w-8 h-8 text-accent" />
|
||||||
{bootstrapError ? (
|
{bootstrapError ? (
|
||||||
<div className="flex flex-col items-center gap-3">
|
<div className="flex flex-col items-center gap-3">
|
||||||
<p className="text-sm text-muted text-center">{bootstrapError}</p>
|
<p className="text-sm text-soft text-center">{bootstrapError}</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
@ -270,7 +270,7 @@ export function AuthLoader({ children }: { children: ReactNode }) {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted animate-pulse">
|
<p className="text-sm text-soft animate-pulse">
|
||||||
{isAutoLoggingIn ? 'Starting anonymous session...' : 'Loading...'}
|
{isAutoLoggingIn ? 'Starting anonymous session...' : 'Loading...'}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,7 @@ export default function ClaimDataModal({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel data-testid="claim-modal" className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel data-testid="claim-modal" className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-semibold leading-6 text-foreground mb-4"
|
className="text-lg font-semibold leading-6 text-foreground mb-4"
|
||||||
|
|
@ -107,13 +107,13 @@ export default function ClaimDataModal({
|
||||||
Existing Data Found
|
Existing Data Found
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<p className="text-sm text-muted mb-2">
|
<p className="text-sm text-soft mb-2">
|
||||||
We found existing anonymous data from before auth was enabled.
|
We found existing anonymous data from before auth was enabled.
|
||||||
Claim it now to attach it to your account.
|
Claim it now to attach it to your account.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div className="mb-4 rounded-lg border border-offbase bg-surface-sunken p-3">
|
<div className="mb-4 rounded-lg border border-line bg-surface-sunken p-3">
|
||||||
<div className="text-xs font-semibold uppercase tracking-wide text-muted">Claimable data</div>
|
<div className="text-xs font-semibold uppercase tracking-wide text-soft">Claimable data</div>
|
||||||
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-soft">
|
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-soft">
|
||||||
<li>{claimableCounts.documents} document(s)</li>
|
<li>{claimableCounts.documents} document(s)</li>
|
||||||
<li>{claimableCounts.audiobooks} audiobook(s)</li>
|
<li>{claimableCounts.audiobooks} audiobook(s)</li>
|
||||||
|
|
@ -132,8 +132,8 @@ export default function ClaimDataModal({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onDismiss}
|
onClick={onDismiss}
|
||||||
disabled={isClaiming}
|
disabled={isClaiming}
|
||||||
className="inline-flex justify-center rounded-lg bg-background px-3 py-1.5 text-sm
|
className="inline-flex justify-center rounded-lg bg-background px-3 py-1.5 text-sm
|
||||||
font-medium text-foreground hover:bg-offbase focus:outline-none
|
font-medium text-foreground hover:bg-accent-wash focus:outline-none
|
||||||
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
||||||
transform transition-transform duration-base ease-standard hover:text-accent
|
transform transition-transform duration-base ease-standard hover:text-accent
|
||||||
disabled:opacity-50"
|
disabled:opacity-50"
|
||||||
|
|
@ -145,8 +145,8 @@ export default function ClaimDataModal({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleClaim}
|
onClick={handleClaim}
|
||||||
disabled={isClaiming}
|
disabled={isClaiming}
|
||||||
className="inline-flex justify-center rounded-lg bg-accent px-3 py-1.5 text-sm
|
className="inline-flex justify-center rounded-lg bg-accent px-3 py-1.5 text-sm
|
||||||
font-medium text-background hover:bg-secondary-accent focus:outline-none
|
font-medium text-background hover:bg-secondary-accent focus:outline-none
|
||||||
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
||||||
transform transition-transform duration-base ease-standard hover:text-background
|
transform transition-transform duration-base ease-standard hover:text-background
|
||||||
disabled:opacity-50"
|
disabled:opacity-50"
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ export function RateLimitIndicator({ className = '' }: RateLimitBannerProps) {
|
||||||
|
|
||||||
if (isWarning) {
|
if (isWarning) {
|
||||||
return (
|
return (
|
||||||
<span className={`text-xs text-muted ${className}`}>
|
<span className={`text-xs text-soft ${className}`}>
|
||||||
{formatCharCount(status.remainingChars)} chars left
|
{formatCharCount(status.remainingChars)} chars left
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -32,19 +32,19 @@ export function UserMenu({
|
||||||
};
|
};
|
||||||
|
|
||||||
const rowClass =
|
const rowClass =
|
||||||
'w-full inline-flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border border-transparent transition duration-base ease-standard text-left hover:border-accent hover:bg-offbase hover:text-accent';
|
'w-full inline-flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border border-transparent transition duration-base ease-standard text-left hover:border-accent hover:bg-accent-wash hover:text-accent';
|
||||||
|
|
||||||
if (!session || session.user.isAnonymous) {
|
if (!session || session.user.isAnonymous) {
|
||||||
if (variant === 'sidebar') {
|
if (variant === 'sidebar') {
|
||||||
return (
|
return (
|
||||||
<div className={`flex w-full flex-col gap-0.5 ${className}`}>
|
<div className={`flex w-full flex-col gap-0.5 ${className}`}>
|
||||||
<Link href="/signin" className={rowClass}>
|
<Link href="/signin" className={rowClass}>
|
||||||
<UserIcon className="h-3.5 w-3.5 text-muted" />
|
<UserIcon className="h-3.5 w-3.5 text-soft" />
|
||||||
<span className="truncate">Connect</span>
|
<span className="truncate">Connect</span>
|
||||||
</Link>
|
</Link>
|
||||||
{enableUserSignups && (
|
{enableUserSignups && (
|
||||||
<Link href="/signup" className={rowClass}>
|
<Link href="/signup" className={rowClass}>
|
||||||
<UserIcon className="h-3.5 w-3.5 text-muted" />
|
<UserIcon className="h-3.5 w-3.5 text-soft" />
|
||||||
<span className="truncate">Create account</span>
|
<span className="truncate">Create account</span>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
@ -55,13 +55,13 @@ export function UserMenu({
|
||||||
return (
|
return (
|
||||||
<div className={`flex gap-2 ${className}`}>
|
<div className={`flex gap-2 ${className}`}>
|
||||||
<Link href="/signin">
|
<Link href="/signin">
|
||||||
<Button className="inline-flex items-center rounded-md bg-base border border-offbase px-2 py-1 text-xs font-medium text-foreground hover:bg-offbase focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition duration-base ease-standard hover:text-accent">
|
<Button className="inline-flex items-center rounded-md bg-surface border border-line px-2 py-1 text-xs font-medium text-foreground hover:bg-accent-wash focus:outline-none focus:ring-2 focus:ring-accent-line focus:ring-offset-2 transform transition duration-base ease-standard hover:text-accent">
|
||||||
Connect
|
Connect
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
{enableUserSignups && (
|
{enableUserSignups && (
|
||||||
<Link href="/signup">
|
<Link href="/signup">
|
||||||
<Button className="inline-flex items-center rounded-md bg-accent px-2 py-1 text-xs font-medium text-background hover:bg-secondary-accent focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 transform transition duration-base ease-standard">
|
<Button className="inline-flex items-center rounded-md bg-accent px-2 py-1 text-xs font-medium text-background hover:bg-secondary-accent focus:outline-none focus:ring-2 focus:ring-accent-line focus:ring-offset-2 transform transition duration-base ease-standard">
|
||||||
Create account
|
Create account
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -78,7 +78,7 @@ export function UserMenu({
|
||||||
title="Disconnect account"
|
title="Disconnect account"
|
||||||
aria-label="Disconnect account"
|
aria-label="Disconnect account"
|
||||||
>
|
>
|
||||||
<UserIcon className="h-3.5 w-3.5 text-muted" />
|
<UserIcon className="h-3.5 w-3.5 text-soft" />
|
||||||
<span className="truncate flex-1">{session.user.email || 'Account'}</span>
|
<span className="truncate flex-1">{session.user.email || 'Account'}</span>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||||
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path>
|
||||||
|
|
@ -90,7 +90,7 @@ export function UserMenu({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`flex items-center gap-2 px-2 py-1 rounded-md border border-offbase bg-base ${className}`}>
|
<div className={`flex items-center gap-2 px-2 py-1 rounded-md border border-line bg-surface ${className}`}>
|
||||||
<span className="hidden sm:block text-xs font-medium text-foreground truncate max-w-[160px]">
|
<span className="hidden sm:block text-xs font-medium text-foreground truncate max-w-[160px]">
|
||||||
{session.user.email || 'Account'}
|
{session.user.email || 'Account'}
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ export function CreateFolderDialog({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle as="h3" className="text-lg font-semibold text-foreground">
|
<DialogTitle as="h3" className="text-lg font-semibold text-foreground">
|
||||||
Create New Folder
|
Create New Folder
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
@ -53,10 +53,10 @@ export function CreateFolderDialog({
|
||||||
onChange={(e) => onFolderNameChange(e.target.value)}
|
onChange={(e) => onFolderNameChange(e.target.value)}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
placeholder="Enter folder name"
|
placeholder="Enter folder name"
|
||||||
className="w-full rounded-lg bg-background py-2 px-3 text-foreground shadow-sm focus:outline-none focus:ring-1 focus:ring-accent"
|
className="w-full rounded-lg bg-surface-sunken py-2 px-3 text-foreground shadow-elev-1 focus:outline-none focus:ring-1 focus:ring-accent-line"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<p className="mt-2 text-xs text-muted">Press Enter to create or Escape to cancel</p>
|
<p className="mt-2 text-xs text-soft">Press Enter to create or Escape to cancel</p>
|
||||||
</div>
|
</div>
|
||||||
</DialogPanel>
|
</DialogPanel>
|
||||||
</TransitionChild>
|
</TransitionChild>
|
||||||
|
|
|
||||||
|
|
@ -139,14 +139,14 @@ function SidebarUploadLoader({
|
||||||
const dashOffset = circumference - (progress / 100) * circumference;
|
const dashOffset = circumference - (progress / 100) * circumference;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-offbase bg-surface-sunken px-2 py-1.5">
|
<div className="rounded-md border border-line bg-surface-sunken px-2 py-1.5">
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="min-w-0 flex items-center gap-1.5 text-[11px] leading-tight">
|
<div className="min-w-0 flex items-center gap-1.5 text-[11px] leading-tight">
|
||||||
<span className="font-medium text-foreground">{label}</span>
|
<span className="font-medium text-foreground">{label}</span>
|
||||||
<span className="shrink-0 tabular-nums text-muted">{completedFiles}/{totalFiles}</span>
|
<span className="shrink-0 tabular-nums text-soft">{completedFiles}/{totalFiles}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 flex items-center gap-1 text-accent" aria-label={`Upload progress ${progress}%`}>
|
<div className="shrink-0 flex items-center gap-1 text-accent" aria-label={`Upload progress ${progress}%`}>
|
||||||
<span className="text-[10px] tabular-nums text-muted">{progress}%</span>
|
<span className="text-[10px] tabular-nums text-soft">{progress}%</span>
|
||||||
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className={phase === 'converting' ? 'animate-spin' : ''}>
|
<svg width={size} height={size} viewBox={`0 0 ${size} ${size}`} className={phase === 'converting' ? 'animate-spin' : ''}>
|
||||||
<circle
|
<circle
|
||||||
cx={size / 2}
|
cx={size / 2}
|
||||||
|
|
@ -174,7 +174,7 @@ function SidebarUploadLoader({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{currentFileName && (
|
{currentFileName && (
|
||||||
<p className="mt-0.5 truncate text-[10px] text-muted" title={currentFileName}>
|
<p className="mt-0.5 truncate text-[10px] text-soft" title={currentFileName}>
|
||||||
{currentFileName}
|
{currentFileName}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
@ -649,15 +649,15 @@ function DocumentListInner({ brand, appActions }: DocumentListInnerProps) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{!isLoading && showHint && allDocuments.length > 1 && (
|
{!isLoading && showHint && allDocuments.length > 1 && (
|
||||||
<div className="px-3 pt-3 shrink-0 bg-background">
|
<div className="px-3 pt-3 shrink-0 bg-surface-sunken">
|
||||||
<div className="flex items-center justify-between bg-base border border-offbase rounded-md px-3 py-1 text-[12px]">
|
<div className="flex items-center justify-between bg-surface border border-line rounded-md px-3 py-1 text-[12px]">
|
||||||
<p className="text-foreground">
|
<p className="text-foreground">
|
||||||
Drag files onto each other to make folders. Drop into the sidebar to move.
|
Drag files onto each other to make folders. Drop into the sidebar to move.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowHint(false)}
|
onClick={() => setShowHint(false)}
|
||||||
className="h-6 w-6 inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base rounded transition duration-base ease-standard"
|
className="h-6 w-6 inline-flex items-center justify-center text-soft hover:text-accent hover:bg-surface rounded transition duration-base ease-standard"
|
||||||
aria-label="Dismiss hint"
|
aria-label="Dismiss hint"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
|
||||||
|
|
@ -15,11 +15,11 @@ function IconsSkeleton({ iconSize }: { iconSize: IconSize }) {
|
||||||
<div className="h-full min-h-0 overflow-y-auto p-3">
|
<div className="h-full min-h-0 overflow-y-auto p-3">
|
||||||
<div className="grid" style={iconsGridStyle(iconSize, ICON_SKELETON_ITEM_COUNT)}>
|
<div className="grid" style={iconsGridStyle(iconSize, ICON_SKELETON_ITEM_COUNT)}>
|
||||||
{Array.from({ length: ICON_SKELETON_ITEM_COUNT }).map((_, index) => (
|
{Array.from({ length: ICON_SKELETON_ITEM_COUNT }).map((_, index) => (
|
||||||
<div key={index} className="overflow-hidden rounded-md border border-offbase bg-base">
|
<div key={index} className="overflow-hidden rounded-md border border-line bg-surface">
|
||||||
<div className="aspect-[3/4] w-full bg-offbase" />
|
<div className="aspect-[3/4] w-full bg-surface-sunken" />
|
||||||
<div className="flex items-center gap-2 px-2 py-2">
|
<div className="flex items-center gap-2 px-2 py-2">
|
||||||
<div className="h-3.5 w-3.5 rounded bg-offbase" />
|
<div className="h-3.5 w-3.5 rounded bg-surface-sunken" />
|
||||||
<div className="h-2.5 w-4/5 rounded bg-offbase" />
|
<div className="h-2.5 w-4/5 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -31,18 +31,18 @@ function IconsSkeleton({ iconSize }: { iconSize: IconSize }) {
|
||||||
function ListSkeleton() {
|
function ListSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full min-h-0 overflow-y-auto">
|
<div className="h-full min-h-0 overflow-y-auto">
|
||||||
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
<div className="sticky top-0 z-10 bg-surface border-b border-line-soft grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
||||||
<div className="h-8 flex items-center px-2">
|
<div className="h-8 flex items-center px-2">
|
||||||
<div className="h-2.5 w-12 rounded bg-offbase" />
|
<div className="h-2.5 w-12 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 flex items-center px-2">
|
<div className="h-8 flex items-center px-2">
|
||||||
<div className="h-2.5 w-10 rounded bg-offbase" />
|
<div className="h-2.5 w-10 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 flex items-center justify-end px-2">
|
<div className="h-8 flex items-center justify-end px-2">
|
||||||
<div className="h-2.5 w-10 rounded bg-offbase" />
|
<div className="h-2.5 w-10 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="h-8 flex items-center px-2">
|
<div className="h-8 flex items-center px-2">
|
||||||
<div className="h-2.5 w-14 rounded bg-offbase" />
|
<div className="h-2.5 w-14 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div />
|
<div />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -50,23 +50,23 @@ function ListSkeleton() {
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
{Array.from({ length: 10 }).map((_, index) => (
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center border-b border-offbase h-[35px]"
|
className="grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center border-b border-line-soft h-[35px]"
|
||||||
>
|
>
|
||||||
<div className="px-2 flex items-center gap-2">
|
<div className="px-2 flex items-center gap-2">
|
||||||
<div className="h-3.5 w-3.5 rounded bg-offbase" />
|
<div className="h-3.5 w-3.5 rounded bg-surface-sunken" />
|
||||||
<div className="h-2.5 w-2/3 rounded bg-offbase" />
|
<div className="h-2.5 w-2/3 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<div className="h-2.5 w-8 rounded bg-offbase" />
|
<div className="h-2.5 w-8 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 flex justify-end">
|
<div className="px-2 flex justify-end">
|
||||||
<div className="h-2.5 w-12 rounded bg-offbase" />
|
<div className="h-2.5 w-12 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2">
|
<div className="px-2">
|
||||||
<div className="h-2.5 w-16 rounded bg-offbase" />
|
<div className="h-2.5 w-16 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
<div className="px-2 flex justify-center">
|
<div className="px-2 flex justify-center">
|
||||||
<div className="h-4 w-4 rounded bg-offbase" />
|
<div className="h-4 w-4 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -78,24 +78,24 @@ function ListSkeleton() {
|
||||||
function GallerySkeleton() {
|
function GallerySkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="h-full min-h-0 flex flex-col">
|
<div className="h-full min-h-0 flex flex-col">
|
||||||
<div className="flex-1 min-h-0 flex items-center justify-center p-6 bg-background">
|
<div className="flex-1 min-h-0 flex items-center justify-center p-6 bg-surface-sunken">
|
||||||
<div className="flex flex-col items-center gap-3 max-w-[420px]">
|
<div className="flex flex-col items-center gap-3 max-w-[420px]">
|
||||||
<div className="w-[260px] sm:w-[320px] aspect-[3/4] rounded-lg border border-offbase bg-offbase" />
|
<div className="w-[260px] sm:w-[320px] aspect-[3/4] rounded-lg border border-line bg-surface-sunken" />
|
||||||
<div className="h-3 w-40 rounded bg-offbase" />
|
<div className="h-3 w-40 rounded bg-surface-sunken" />
|
||||||
<div className="h-2.5 w-28 rounded bg-offbase" />
|
<div className="h-2.5 w-28 rounded bg-surface-sunken" />
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="h-8 w-20 rounded-md bg-offbase" />
|
<div className="h-8 w-20 rounded-md bg-surface-sunken" />
|
||||||
<div className="h-8 w-20 rounded-md bg-offbase" />
|
<div className="h-8 w-20 rounded-md bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="shrink-0 border-t border-offbase bg-base">
|
<div className="shrink-0 border-t border-line-soft bg-surface">
|
||||||
<div className="flex gap-2 overflow-x-auto p-2">
|
<div className="flex gap-2 overflow-x-auto p-2">
|
||||||
{Array.from({ length: 10 }).map((_, index) => (
|
{Array.from({ length: 10 }).map((_, index) => (
|
||||||
<div key={index} className="shrink-0 w-[88px] rounded-md overflow-hidden border border-offbase bg-base">
|
<div key={index} className="shrink-0 w-[88px] rounded-md overflow-hidden border border-line bg-surface">
|
||||||
<div className="aspect-[3/4] bg-offbase" />
|
<div className="aspect-[3/4] bg-surface-sunken" />
|
||||||
<div className="p-1.5">
|
<div className="p-1.5">
|
||||||
<div className="h-2.5 w-5/6 rounded bg-offbase" />
|
<div className="h-2.5 w-5/6 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ export function DocumentPreview({ doc }: DocumentPreviewProps) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className="relative w-full aspect-[3/4] overflow-hidden rounded-t-md bg-base"
|
className="relative w-full aspect-[3/4] overflow-hidden rounded-t-md bg-surface"
|
||||||
>
|
>
|
||||||
{imagePreview ? (
|
{imagePreview ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
||||||
|
|
@ -140,9 +140,9 @@ export function DocumentTile({
|
||||||
className={
|
className={
|
||||||
'group relative flex flex-col rounded-md overflow-hidden border transition duration-base ease-standard ' +
|
'group relative flex flex-col rounded-md overflow-hidden border transition duration-base ease-standard ' +
|
||||||
(isSelected
|
(isSelected
|
||||||
? 'border-accent bg-offbase'
|
? 'border-accent-line bg-surface-sunken'
|
||||||
: 'border-offbase bg-base hover:bg-offbase hover:border-accent') +
|
: 'border-line bg-surface hover:bg-accent-wash hover:border-accent-line') +
|
||||||
(isDropTarget ? ' ring-1 ring-accent' : '') +
|
(isDropTarget ? ' ring-1 ring-accent-line' : '') +
|
||||||
(isDragging ? ' opacity-50' : '')
|
(isDragging ? ' opacity-50' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -170,7 +170,7 @@ export function DocumentTile({
|
||||||
) : doc.type === 'epub' ? (
|
) : doc.type === 'epub' ? (
|
||||||
<EPUBIcon className={`${FILE_ICON_CLASSES[iconSize]} text-accent`} />
|
<EPUBIcon className={`${FILE_ICON_CLASSES[iconSize]} text-accent`} />
|
||||||
) : (
|
) : (
|
||||||
<FileIcon className={`${FILE_ICON_CLASSES[iconSize]} text-muted`} />
|
<FileIcon className={`${FILE_ICON_CLASSES[iconSize]} text-soft`} />
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
|
@ -187,7 +187,7 @@ export function DocumentTile({
|
||||||
{showDeleteButton && (
|
{showDeleteButton && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => onDelete(doc)}
|
onClick={() => onDelete(doc)}
|
||||||
className={`inline-flex items-center justify-center text-muted hover:text-accent hover:bg-base focus:outline-none transition-colors duration-base ${TRASH_BTN_CLASSES[iconSize]}`}
|
className={`inline-flex items-center justify-center text-soft hover:text-accent hover:bg-surface focus:outline-none transition-colors duration-base ${TRASH_BTN_CLASSES[iconSize]}`}
|
||||||
aria-label={`Delete ${doc.name}`}
|
aria-label={`Delete ${doc.name}`}
|
||||||
>
|
>
|
||||||
<svg className={TRASH_ICON_CLASSES[iconSize]} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className={TRASH_ICON_CLASSES[iconSize]} fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@ function formatParseStatus(status: DocumentListDocument['parseStatus']): string
|
||||||
function KindIcon({ doc, className }: { doc: DocumentListDocument; className?: string }) {
|
function KindIcon({ doc, className }: { doc: DocumentListDocument; className?: string }) {
|
||||||
if (doc.type === 'pdf') return <PDFIcon className={className ?? 'w-4 h-4 text-danger'} />;
|
if (doc.type === 'pdf') return <PDFIcon className={className ?? 'w-4 h-4 text-danger'} />;
|
||||||
if (doc.type === 'epub') return <EPUBIcon className={className ?? 'w-4 h-4 text-accent'} />;
|
if (doc.type === 'epub') return <EPUBIcon className={className ?? 'w-4 h-4 text-accent'} />;
|
||||||
return <FileIcon className={className ?? 'w-4 h-4 text-muted'} />;
|
return <FileIcon className={className ?? 'w-4 h-4 text-soft'} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function GalleryThumb({
|
function GalleryThumb({
|
||||||
|
|
@ -92,29 +92,29 @@ function GalleryThumb({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
aria-current={active ? 'true' : undefined}
|
aria-current={active ? 'true' : undefined}
|
||||||
className={
|
className={
|
||||||
'group relative w-[98px] sm:w-[110px] shrink-0 cursor-pointer rounded-lg overflow-hidden border bg-base snap-start transition duration-base ease-standard ' +
|
'group relative w-[98px] sm:w-[110px] shrink-0 cursor-pointer rounded-lg overflow-hidden border bg-surface snap-start transition duration-base ease-standard ' +
|
||||||
(active
|
(active
|
||||||
? 'border-accent shadow-[0_10px_24px_-18px_rgba(0,0,0,0.85)] -translate-y-0.5'
|
? 'border-accent-line shadow-elev-2 -translate-y-px'
|
||||||
: 'border-offbase hover:border-accent hover:-translate-y-0.5') +
|
: 'border-line hover:border-accent-line hover:-translate-y-px hover:shadow-elev-2') +
|
||||||
(isOver && canDrop ? ' border-accent' : '') +
|
(isOver && canDrop ? ' border-accent-line' : '') +
|
||||||
(isDragging ? ' opacity-50' : '')
|
(isDragging ? ' opacity-50' : '')
|
||||||
}
|
}
|
||||||
title={doc.name}
|
title={doc.name}
|
||||||
>
|
>
|
||||||
<div className="aspect-[3/4] bg-base">
|
<div className="aspect-[3/4] bg-surface">
|
||||||
<DocumentPreview doc={doc} />
|
<DocumentPreview doc={doc} />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'px-2 py-1.5 flex items-center gap-1.5 border-t transition-colors duration-base ' +
|
'px-2 py-1.5 flex items-center gap-1.5 border-t transition-colors duration-base ' +
|
||||||
(active ? 'bg-offbase border-accent' : 'bg-base border-offbase')
|
(active ? 'bg-surface-sunken border-accent-line' : 'bg-surface border-line')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<KindIcon
|
<KindIcon
|
||||||
doc={doc}
|
doc={doc}
|
||||||
className={
|
className={
|
||||||
'w-3 h-3 shrink-0 transition-colors duration-base ' +
|
'w-3 h-3 shrink-0 transition-colors duration-base ' +
|
||||||
(active ? 'text-accent' : 'text-muted')
|
(active ? 'text-accent' : 'text-soft')
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
|
|
@ -181,19 +181,19 @@ export function GalleryView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 min-h-0 flex flex-col">
|
<div className="flex-1 min-h-0 flex flex-col">
|
||||||
<div className="flex-1 min-h-0 overflow-y-auto bg-background">
|
<div className="flex-1 min-h-0 overflow-y-auto bg-surface-sunken">
|
||||||
<div className="min-h-full flex items-center justify-center p-3 sm:p-6">
|
<div className="min-h-full flex items-center justify-center p-3 sm:p-6">
|
||||||
{activeDoc ? (
|
{activeDoc ? (
|
||||||
<div className="w-full max-w-[920px] flex flex-col md:flex-row items-center md:items-start justify-center gap-4 md:gap-6">
|
<div className="w-full max-w-[920px] flex flex-col md:flex-row items-center md:items-start justify-center gap-4 md:gap-6">
|
||||||
<div className="flex flex-col items-center gap-3 w-[180px] sm:w-[260px] md:w-[320px] shrink-0">
|
<div className="flex flex-col items-center gap-3 w-[180px] sm:w-[260px] md:w-[320px] shrink-0">
|
||||||
<div className="w-full aspect-[3/4] rounded-lg overflow-hidden border border-offbase shadow-lg">
|
<div className="w-full aspect-[3/4] rounded-lg overflow-hidden border border-line shadow-elev-2">
|
||||||
<DocumentPreview doc={activeDoc} />
|
<DocumentPreview doc={activeDoc} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h2 className="text-[14px] font-semibold text-foreground truncate max-w-[320px]">
|
<h2 className="text-[14px] font-semibold text-foreground truncate max-w-[320px]">
|
||||||
{activeDoc.name}
|
{activeDoc.name}
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-[11px] text-muted">
|
<p className="text-[11px] text-soft">
|
||||||
{activeDoc.type.toUpperCase()} • {formatDocumentSize(activeDoc.size)}
|
{activeDoc.type.toUpperCase()} • {formatDocumentSize(activeDoc.size)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -214,18 +214,18 @@ export function GalleryView({
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dl className="w-full max-w-[280px] sm:max-w-[360px] md:max-w-[340px] grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 rounded-md border border-offbase bg-base px-3 py-2 text-[11px] md:self-center">
|
<dl className="w-full max-w-[280px] sm:max-w-[360px] md:max-w-[340px] grid grid-cols-[auto_1fr] gap-x-3 gap-y-1 rounded-md border border-line bg-surface px-3 py-2 text-[11px] md:self-center">
|
||||||
<dt className="text-muted">Type</dt>
|
<dt className="text-soft">Type</dt>
|
||||||
<dd className="text-foreground text-right uppercase tracking-wide">{activeDoc.type}</dd>
|
<dd className="text-foreground text-right uppercase tracking-wide">{activeDoc.type}</dd>
|
||||||
<dt className="text-muted">Size</dt>
|
<dt className="text-soft">Size</dt>
|
||||||
<dd className="text-foreground text-right tabular-nums">{formatDocumentSize(activeDoc.size)}</dd>
|
<dd className="text-foreground text-right tabular-nums">{formatDocumentSize(activeDoc.size)}</dd>
|
||||||
<dt className="text-muted">Last opened</dt>
|
<dt className="text-soft">Last opened</dt>
|
||||||
<dd className="text-foreground text-right">{formatDateTime(activeDoc.recentlyOpenedAt)}</dd>
|
<dd className="text-foreground text-right">{formatDateTime(activeDoc.recentlyOpenedAt)}</dd>
|
||||||
<dt className="text-muted">Last modified</dt>
|
<dt className="text-soft">Last modified</dt>
|
||||||
<dd className="text-foreground text-right">{formatDateTime(activeDoc.lastModified)}</dd>
|
<dd className="text-foreground text-right">{formatDateTime(activeDoc.lastModified)}</dd>
|
||||||
{activeDoc.folderId && (
|
{activeDoc.folderId && (
|
||||||
<>
|
<>
|
||||||
<dt className="text-muted">Folder</dt>
|
<dt className="text-soft">Folder</dt>
|
||||||
<dd className="text-foreground text-right truncate" title={folderNameById?.[activeDoc.folderId] ?? activeDoc.folderId}>
|
<dd className="text-foreground text-right truncate" title={folderNameById?.[activeDoc.folderId] ?? activeDoc.folderId}>
|
||||||
{folderNameById?.[activeDoc.folderId] ?? activeDoc.folderId}
|
{folderNameById?.[activeDoc.folderId] ?? activeDoc.folderId}
|
||||||
</dd>
|
</dd>
|
||||||
|
|
@ -233,19 +233,19 @@ export function GalleryView({
|
||||||
)}
|
)}
|
||||||
{activeDoc.type === 'pdf' && (
|
{activeDoc.type === 'pdf' && (
|
||||||
<>
|
<>
|
||||||
<dt className="text-muted">Parse status</dt>
|
<dt className="text-soft">Parse status</dt>
|
||||||
<dd className="text-foreground text-right">{formatParseStatus(activeDoc.parseStatus)}</dd>
|
<dd className="text-foreground text-right">{formatParseStatus(activeDoc.parseStatus)}</dd>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-[12px] text-muted">No documents to show</p>
|
<p className="text-[12px] text-soft">No documents to show</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="shrink-0 border-t border-offbase bg-gradient-to-b from-base to-offbase/30">
|
<div className="shrink-0 border-t border-line-soft bg-gradient-to-b from-surface to-surface-sunken">
|
||||||
<div
|
<div
|
||||||
ref={railRef}
|
ref={railRef}
|
||||||
className="flex gap-2.5 overflow-x-auto pl-4 pr-3 pt-2.5 pb-1.5 snap-x snap-proximity scroll-pl-4 sm:scroll-pl-5 scroll-pr-3 sm:scroll-pr-4"
|
className="flex gap-2.5 overflow-x-auto pl-4 pr-3 pt-2.5 pb-1.5 snap-x snap-proximity scroll-pl-4 sm:scroll-pl-5 scroll-pr-3 sm:scroll-pr-4"
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ function formatDate(ms: number): string {
|
||||||
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
function KindIcon({ doc }: { doc: DocumentListDocument }) {
|
||||||
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 shrink-0 text-danger" />;
|
if (doc.type === 'pdf') return <PDFIcon className="w-4 h-4 shrink-0 text-danger" />;
|
||||||
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 shrink-0 text-accent" />;
|
if (doc.type === 'epub') return <EPUBIcon className="w-4 h-4 shrink-0 text-accent" />;
|
||||||
return <FileIcon className="w-4 h-4 shrink-0 text-muted" />;
|
return <FileIcon className="w-4 h-4 shrink-0 text-soft" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HeaderCell({
|
function HeaderCell({
|
||||||
|
|
@ -63,7 +63,7 @@ function HeaderCell({
|
||||||
}}
|
}}
|
||||||
className={
|
className={
|
||||||
'flex items-center gap-1 px-2 py-1.5 text-[10px] uppercase tracking-wide font-semibold transition-colors duration-base ease-standard hover:text-accent ' +
|
'flex items-center gap-1 px-2 py-1.5 text-[10px] uppercase tracking-wide font-semibold transition-colors duration-base ease-standard hover:text-accent ' +
|
||||||
(active ? 'text-accent' : 'text-muted') +
|
(active ? 'text-accent' : 'text-soft') +
|
||||||
(align === 'right' ? ' justify-end' : '') +
|
(align === 'right' ? ' justify-end' : '') +
|
||||||
' ' +
|
' ' +
|
||||||
(className ?? '')
|
(className ?? '')
|
||||||
|
|
@ -131,11 +131,11 @@ function DocRow({
|
||||||
data-doc-tile
|
data-doc-tile
|
||||||
aria-selected={isSelected}
|
aria-selected={isSelected}
|
||||||
className={
|
className={
|
||||||
'grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center text-[12px] border-b border-offbase transition-colors duration-base ease-standard ' +
|
'grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px] items-center text-[12px] border-b border-line-soft transition-colors duration-base ease-standard ' +
|
||||||
(isSelected
|
(isSelected
|
||||||
? 'bg-offbase text-accent'
|
? 'bg-surface-sunken text-accent'
|
||||||
: 'text-foreground hover:bg-offbase') +
|
: 'text-foreground hover:bg-accent-wash') +
|
||||||
(isTarget ? ' ring-1 ring-accent ring-inset' : '') +
|
(isTarget ? ' ring-1 ring-accent-line ring-inset' : '') +
|
||||||
(isDragging ? ' opacity-50' : '')
|
(isDragging ? ' opacity-50' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -149,11 +149,11 @@ function DocRow({
|
||||||
<KindIcon doc={doc} />
|
<KindIcon doc={doc} />
|
||||||
<span className="truncate">{doc.name}</span>
|
<span className="truncate">{doc.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span className="px-2 text-[11px] text-muted uppercase tracking-wide">{doc.type}</span>
|
<span className="px-2 text-[11px] text-soft uppercase tracking-wide">{doc.type}</span>
|
||||||
<span className="px-2 text-[11px] text-muted text-right tabular-nums">
|
<span className="px-2 text-[11px] text-soft text-right tabular-nums">
|
||||||
{formatDocumentSize(doc.size)}
|
{formatDocumentSize(doc.size)}
|
||||||
</span>
|
</span>
|
||||||
<span className="px-2 text-[11px] text-muted tabular-nums">
|
<span className="px-2 text-[11px] text-soft tabular-nums">
|
||||||
{formatDate(doc.lastModified)}
|
{formatDate(doc.lastModified)}
|
||||||
</span>
|
</span>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -161,7 +161,7 @@ function DocRow({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDeleteDoc(doc);
|
onDeleteDoc(doc);
|
||||||
}}
|
}}
|
||||||
className="h-7 w-7 flex items-center justify-center text-muted hover:text-accent hover:bg-offbase rounded transition duration-base ease-standard"
|
className="h-7 w-7 flex items-center justify-center text-soft hover:text-accent hover:bg-accent-wash rounded transition duration-base ease-standard"
|
||||||
aria-label={`Delete ${doc.name}`}
|
aria-label={`Delete ${doc.name}`}
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
|
@ -198,7 +198,7 @@ export function ListView({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onClick={handleBackgroundClick} className="flex-1 min-h-0 overflow-y-auto">
|
<div onClick={handleBackgroundClick} className="flex-1 min-h-0 overflow-y-auto">
|
||||||
<div className="sticky top-0 z-10 bg-base border-b border-offbase grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
<div className="sticky top-0 z-10 bg-surface border-b border-line-soft grid grid-cols-[minmax(0,1fr)_44px_72px_104px_28px] sm:grid-cols-[minmax(0,1fr)_56px_96px_140px_32px]">
|
||||||
<HeaderCell label="Name" field="name" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
<HeaderCell label="Name" field="name" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
||||||
<HeaderCell label="Kind" field="type" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
<HeaderCell label="Kind" field="type" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} />
|
||||||
<HeaderCell label="Size" field="size" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} align="right" />
|
<HeaderCell label="Size" field="size" sortBy={sortBy} sortDirection={sortDirection} onSortChange={onSortChange} align="right" />
|
||||||
|
|
|
||||||
|
|
@ -58,15 +58,15 @@ function SidebarRow({
|
||||||
className={
|
className={
|
||||||
'group w-full flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition duration-base ease-standard text-left ' +
|
'group w-full flex items-center gap-2 px-2 py-1 rounded-md text-[12px] border transform transition duration-base ease-standard text-left ' +
|
||||||
(active
|
(active
|
||||||
? 'border-accent bg-offbase text-accent'
|
? 'border-accent-line bg-surface-sunken text-accent'
|
||||||
: 'border-transparent bg-transparent text-foreground hover:border-accent hover:text-accent') +
|
: 'border-transparent bg-transparent text-foreground hover:border-accent-line hover:text-accent') +
|
||||||
(isDropTarget ? ' ring-1 ring-accent' : '')
|
(isDropTarget ? ' ring-1 ring-accent-line' : '')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
className={
|
className={
|
||||||
'w-4 h-4 shrink-0 flex items-center justify-center transition-colors duration-base ' +
|
'w-4 h-4 shrink-0 flex items-center justify-center transition-colors duration-base ' +
|
||||||
(active ? 'text-accent' : 'text-muted group-hover:text-accent')
|
(active ? 'text-accent' : 'text-soft group-hover:text-accent')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{icon}
|
{icon}
|
||||||
|
|
@ -74,7 +74,7 @@ function SidebarRow({
|
||||||
<span className="truncate flex-1">{label}</span>
|
<span className="truncate flex-1">{label}</span>
|
||||||
{typeof count === 'number' && count > 0 && (
|
{typeof count === 'number' && count > 0 && (
|
||||||
<span
|
<span
|
||||||
className={`text-[10px] text-muted tabular-nums transition-transform duration-base ease-standard ${countClassName ?? ''}`}
|
className={`text-[10px] text-soft tabular-nums transition-transform duration-base ease-standard ${countClassName ?? ''}`}
|
||||||
>
|
>
|
||||||
{count}
|
{count}
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -137,7 +137,7 @@ function FolderRow({
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
onDelete();
|
onDelete();
|
||||||
}}
|
}}
|
||||||
className="absolute right-1 top-1/2 -translate-y-1/2 h-5 w-5 inline-flex items-center justify-center rounded text-muted opacity-0 group-hover/folder:opacity-100 group-focus-within/folder:opacity-100 hover:text-accent hover:bg-offbase transition"
|
className="absolute right-1 top-1/2 -translate-y-1/2 h-5 w-5 inline-flex items-center justify-center rounded text-soft opacity-0 group-hover/folder:opacity-100 group-focus-within/folder:opacity-100 hover:text-accent hover:bg-accent-wash transition"
|
||||||
aria-label={`Delete ${folder.name}`}
|
aria-label={`Delete ${folder.name}`}
|
||||||
title={`Delete ${folder.name}`}
|
title={`Delete ${folder.name}`}
|
||||||
>
|
>
|
||||||
|
|
@ -161,7 +161,7 @@ function SectionHeader({
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
'px-2 pb-1 text-[10px] uppercase tracking-[0.08em] text-muted font-semibold leading-none flex items-center justify-between ' +
|
'px-2 pb-1 text-[10px] uppercase tracking-[0.08em] text-soft font-semibold leading-none flex items-center justify-between ' +
|
||||||
(isFirst ? 'pt-1.5' : 'pt-3')
|
(isFirst ? 'pt-1.5' : 'pt-3')
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -206,7 +206,7 @@ export function FinderSidebar({
|
||||||
return (
|
return (
|
||||||
<aside
|
<aside
|
||||||
style={{ '--sidebar-width': `${width}px` } as CSSProperties}
|
style={{ '--sidebar-width': `${width}px` } as CSSProperties}
|
||||||
className="relative h-full w-full md:[width:var(--sidebar-width)] bg-base border-r border-offbase shrink-0 flex flex-col"
|
className="relative h-full w-full md:[width:var(--sidebar-width)] bg-surface border-r border-line-soft shrink-0 flex flex-col"
|
||||||
>
|
>
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto">
|
<div className="min-h-0 flex-1 overflow-y-auto">
|
||||||
<div className="p-2 flex flex-col gap-0.5">
|
<div className="p-2 flex flex-col gap-0.5">
|
||||||
|
|
@ -272,7 +272,7 @@ export function FinderSidebar({
|
||||||
rightSlot={(
|
rightSlot={(
|
||||||
<Menu as="div" className="relative inline-flex items-center leading-none text-left shrink-0 normal-case tracking-normal font-normal">
|
<Menu as="div" className="relative inline-flex items-center leading-none text-left shrink-0 normal-case tracking-normal font-normal">
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="inline-flex items-center justify-center h-3.5 w-5 rounded-sm text-muted hover:text-accent transition-colors duration-base ease-standard focus:outline-none"
|
className="inline-flex items-center justify-center h-3.5 w-5 rounded-sm text-soft hover:text-accent transition-colors duration-base ease-standard focus:outline-none"
|
||||||
title="Folder actions"
|
title="Folder actions"
|
||||||
aria-label="Folder actions"
|
aria-label="Folder actions"
|
||||||
>
|
>
|
||||||
|
|
@ -289,7 +289,7 @@ export function FinderSidebar({
|
||||||
>
|
>
|
||||||
<MenuItems
|
<MenuItems
|
||||||
anchor="bottom start"
|
anchor="bottom start"
|
||||||
className="z-50 mt-2 min-w-[180px] rounded-md bg-base shadow-lg ring-1 ring-black/5 focus:outline-none p-1 normal-case tracking-normal font-normal"
|
className="z-50 mt-2 min-w-[180px] rounded-md bg-surface shadow-elev-2 ring-1 ring-line-soft focus:outline-none p-1 normal-case tracking-normal font-normal"
|
||||||
>
|
>
|
||||||
<MenuItem>
|
<MenuItem>
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
|
|
@ -299,7 +299,7 @@ export function FinderSidebar({
|
||||||
onNewFolder();
|
onNewFolder();
|
||||||
onRowAction?.();
|
onRowAction?.();
|
||||||
}}
|
}}
|
||||||
className={`${active ? 'bg-offbase text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
className={`${active ? 'bg-surface-sunken text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
||||||
>
|
>
|
||||||
<FolderPlusIcon className="h-4 w-4" />
|
<FolderPlusIcon className="h-4 w-4" />
|
||||||
New Folder
|
New Folder
|
||||||
|
|
@ -315,7 +315,7 @@ export function FinderSidebar({
|
||||||
onRowAction?.();
|
onRowAction?.();
|
||||||
}}
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={`${disabled ? 'text-faint cursor-not-allowed' : active ? 'bg-offbase text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
className={`${disabled ? 'text-faint cursor-not-allowed' : active ? 'bg-surface-sunken text-accent' : 'text-foreground'} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
||||||
>
|
>
|
||||||
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
|
@ -332,7 +332,7 @@ export function FinderSidebar({
|
||||||
Folders
|
Folders
|
||||||
</SectionHeader>
|
</SectionHeader>
|
||||||
{folders.length === 0 ? (
|
{folders.length === 0 ? (
|
||||||
<p className="px-2 py-1 text-[11px] text-muted">No folders yet</p>
|
<p className="px-2 py-1 text-[11px] text-soft">No folders yet</p>
|
||||||
) : (
|
) : (
|
||||||
folders.map((folder) => (
|
folders.map((folder) => (
|
||||||
<FolderRow
|
<FolderRow
|
||||||
|
|
@ -355,7 +355,7 @@ export function FinderSidebar({
|
||||||
</div>
|
</div>
|
||||||
{bottomSlot && (
|
{bottomSlot && (
|
||||||
<div
|
<div
|
||||||
className="shrink-0 border-t border-offbase px-2 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] md:pb-2"
|
className="shrink-0 border-t border-line-soft px-2 pt-2 pb-[max(0.5rem,env(safe-area-inset-bottom))] md:pb-2"
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{bottomSlot}
|
{bottomSlot}
|
||||||
|
|
@ -370,7 +370,7 @@ export function FinderSidebar({
|
||||||
onPointerMove={onResizeMove}
|
onPointerMove={onResizeMove}
|
||||||
onPointerUp={onResizeEnd}
|
onPointerUp={onResizeEnd}
|
||||||
onPointerCancel={onResizeEnd}
|
onPointerCancel={onResizeEnd}
|
||||||
className="hidden md:block absolute top-0 right-0 h-full w-1 cursor-col-resize hover:bg-offbase active:bg-accent transition-colors duration-base ease-standard"
|
className="hidden md:block absolute top-0 right-0 h-full w-1 cursor-col-resize hover:bg-accent-wash active:bg-accent transition-colors duration-base ease-standard"
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,14 @@ export function FinderStatusBar({
|
||||||
<div
|
<div
|
||||||
role="status"
|
role="status"
|
||||||
aria-live="polite"
|
aria-live="polite"
|
||||||
className="min-h-6 px-3 pb-[env(safe-area-inset-bottom)] flex items-center justify-between gap-3 text-[11px] text-muted bg-base border-t border-offbase select-none"
|
className="min-h-6 px-3 pb-[env(safe-area-inset-bottom)] flex items-center justify-between gap-3 text-[11px] text-soft bg-surface border-t border-line-soft select-none"
|
||||||
>
|
>
|
||||||
<span className="truncate">{summary}</span>
|
<span className="truncate">{summary}</span>
|
||||||
<span className="shrink-0">
|
<span className="shrink-0">
|
||||||
{selectedCount > 0
|
{selectedCount > 0
|
||||||
? `${selectedCount} of ${itemCount} selected`
|
? `${selectedCount} of ${itemCount} selected`
|
||||||
: `${itemCount} item${itemCount === 1 ? '' : 's'}`}
|
: `${itemCount} item${itemCount === 1 ? '' : 's'}`}
|
||||||
<span className="mx-1.5 text-muted">•</span>
|
<span className="mx-1.5 text-soft">•</span>
|
||||||
{formatDocumentSize(totalSize)}
|
{formatDocumentSize(totalSize)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -54,19 +54,19 @@ const ICON_SIZES: Array<{ value: IconSize; label: string }> = [
|
||||||
|
|
||||||
// Match SettingsModal / UserMenu trigger sizing exactly so all bar buttons share one rhythm.
|
// Match SettingsModal / UserMenu trigger sizing exactly so all bar buttons share one rhythm.
|
||||||
const TOOLBAR_BTN =
|
const TOOLBAR_BTN =
|
||||||
'inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition duration-base ease-standard';
|
'inline-flex items-center py-1 px-2 rounded-md border bg-surface text-xs transition duration-base ease-standard';
|
||||||
const TOOLBAR_BTN_INACTIVE =
|
const TOOLBAR_BTN_INACTIVE =
|
||||||
'border-offbase text-foreground hover:text-accent hover:border-accent hover:bg-offbase';
|
'border-line text-foreground hover:text-accent hover:border-accent-line hover:bg-accent-wash';
|
||||||
const TOOLBAR_BTN_ACTIVE = 'border-accent bg-offbase text-accent';
|
const TOOLBAR_BTN_ACTIVE = 'border-accent-line bg-surface-sunken text-accent';
|
||||||
|
|
||||||
// Pill-grouped segmented control. Outer pill carries the border; inner segments are
|
// Pill-grouped segmented control. Outer pill carries the border; inner segments are
|
||||||
// borderless and rely on bg/text color to show active/hover. Sized so the whole pill
|
// borderless and rely on bg/text color to show active/hover. Sized so the whole pill
|
||||||
// matches the height of a standalone TOOLBAR_BTN.
|
// matches the height of a standalone TOOLBAR_BTN.
|
||||||
const PILL = 'inline-flex items-center rounded-md border border-offbase bg-base p-0.5 gap-0.5 shrink-0';
|
const PILL = 'inline-flex items-center rounded-md border border-line bg-surface p-0.5 gap-0.5 shrink-0';
|
||||||
const PILL_SEGMENT =
|
const PILL_SEGMENT =
|
||||||
'inline-flex items-center justify-center rounded-[5px] text-xs transition-colors duration-base ease-standard';
|
'inline-flex items-center justify-center rounded-sm text-xs transition-colors duration-base ease-standard';
|
||||||
const PILL_SEGMENT_INACTIVE = 'text-muted hover:bg-offbase hover:text-accent';
|
const PILL_SEGMENT_INACTIVE = 'text-soft hover:bg-accent-wash hover:text-accent';
|
||||||
const PILL_SEGMENT_ACTIVE = 'bg-offbase text-accent';
|
const PILL_SEGMENT_ACTIVE = 'bg-surface-sunken text-accent';
|
||||||
|
|
||||||
export function FinderToolbar({
|
export function FinderToolbar({
|
||||||
viewMode,
|
viewMode,
|
||||||
|
|
@ -89,10 +89,10 @@ export function FinderToolbar({
|
||||||
const directionLabel = sortDirection === 'asc' ? currentSort.asc : currentSort.desc;
|
const directionLabel = sortDirection === 'asc' ? currentSort.asc : currentSort.desc;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sticky top-0 z-40 w-full border-b border-offbase bg-base">
|
<div className="sticky top-0 z-40 w-full border-b border-line-soft bg-surface">
|
||||||
<div className="px-2 sm:px-3 py-1 min-h-10 flex items-center gap-1.5 sm:gap-2">
|
<div className="px-2 sm:px-3 py-1 min-h-10 flex items-center gap-1.5 sm:gap-2">
|
||||||
{leftSlot && (
|
{leftSlot && (
|
||||||
<div className="shrink-0 flex items-center gap-2 pr-1 sm:pr-2 sm:border-r sm:border-offbase">
|
<div className="shrink-0 flex items-center gap-2 pr-1 sm:pr-2 sm:border-r sm:border-line">
|
||||||
{leftSlot}
|
{leftSlot}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -135,7 +135,7 @@ export function FinderToolbar({
|
||||||
<div
|
<div
|
||||||
className="absolute top-full left-1/2 z-30 -translate-x-1/2 pt-1 opacity-0 pointer-events-none transition-opacity duration-fast group-hover/icons:opacity-100 group-hover/icons:pointer-events-auto group-focus-within/icons:opacity-100 group-focus-within/icons:pointer-events-auto"
|
className="absolute top-full left-1/2 z-30 -translate-x-1/2 pt-1 opacity-0 pointer-events-none transition-opacity duration-fast group-hover/icons:opacity-100 group-hover/icons:pointer-events-auto group-focus-within/icons:opacity-100 group-focus-within/icons:pointer-events-auto"
|
||||||
>
|
>
|
||||||
<div className={`${PILL} shadow-lg`}>
|
<div className={`${PILL} shadow-elev-2`}>
|
||||||
{ICON_SIZES.map(({ value: sizeValue, label: sizeLabel }) => {
|
{ICON_SIZES.map(({ value: sizeValue, label: sizeLabel }) => {
|
||||||
const sizeActive = iconSize === sizeValue;
|
const sizeActive = iconSize === sizeValue;
|
||||||
return (
|
return (
|
||||||
|
|
@ -182,7 +182,7 @@ export function FinderToolbar({
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<ListboxOptions
|
<ListboxOptions
|
||||||
anchor="bottom end"
|
anchor="bottom end"
|
||||||
className="z-50 mt-1 rounded-md bg-background border border-offbase shadow-lg p-1 focus:outline-none"
|
className="z-50 mt-1 rounded-md bg-surface-sunken border border-line shadow-elev-2 p-1 focus:outline-none"
|
||||||
>
|
>
|
||||||
{SORT_OPTIONS.map((opt) => (
|
{SORT_OPTIONS.map((opt) => (
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
|
|
@ -190,7 +190,7 @@ export function FinderToolbar({
|
||||||
value={opt.value}
|
value={opt.value}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`cursor-pointer select-none rounded-sm py-1.5 px-2.5 text-xs ${
|
`cursor-pointer select-none rounded-sm py-1.5 px-2.5 text-xs ${
|
||||||
active ? 'bg-offbase text-accent' : 'text-foreground'
|
active ? 'bg-surface-sunken text-accent' : 'text-foreground'
|
||||||
} ${selected ? 'font-semibold' : ''}`
|
} ${selected ? 'font-semibold' : ''}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
@ -204,19 +204,19 @@ export function FinderToolbar({
|
||||||
|
|
||||||
<div className="flex-1 min-w-0" />
|
<div className="flex-1 min-w-0" />
|
||||||
|
|
||||||
<div className="hidden sm:flex items-center gap-1.5 px-2 py-1 rounded-md bg-background border border-offbase hover:border-accent focus-within:ring-1 focus-within:ring-accent focus-within:border-accent transition-colors duration-base ease-standard w-[160px] md:w-[200px]">
|
<div className="hidden sm:flex items-center gap-1.5 px-2 py-1 rounded-md bg-surface-sunken border border-line hover:border-accent-line focus-within:ring-1 focus-within:ring-accent-line focus-within:border-accent-line transition-colors duration-base ease-standard w-[160px] md:w-[200px]">
|
||||||
<SearchIcon className="w-3.5 h-3.5 text-muted shrink-0" />
|
<SearchIcon className="w-3.5 h-3.5 text-soft shrink-0" />
|
||||||
<input
|
<input
|
||||||
type="search"
|
type="search"
|
||||||
value={query}
|
value={query}
|
||||||
onChange={(e) => onQueryChange(e.target.value)}
|
onChange={(e) => onQueryChange(e.target.value)}
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
className="flex-1 min-w-0 bg-transparent outline-none text-xs text-foreground placeholder:text-muted"
|
className="flex-1 min-w-0 bg-transparent outline-none text-xs text-foreground placeholder:text-soft"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{rightSlot && (
|
{rightSlot && (
|
||||||
<div className="shrink-0 flex items-center gap-2 pl-1 sm:pl-2 sm:border-l sm:border-offbase ml-0.5">
|
<div className="shrink-0 flex items-center gap-2 pl-1 sm:pl-2 sm:border-l sm:border-line ml-0.5">
|
||||||
{rightSlot}
|
{rightSlot}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ export function FinderWindow({
|
||||||
const isNarrow = useIsNarrow();
|
const isNarrow = useIsNarrow();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full w-full bg-background overflow-hidden">
|
<div className="flex flex-col h-full w-full bg-surface-sunken overflow-hidden">
|
||||||
{toolbar}
|
{toolbar}
|
||||||
|
|
||||||
<div className="flex flex-1 min-h-0">
|
<div className="flex flex-1 min-h-0">
|
||||||
|
|
@ -89,7 +89,7 @@ export function FinderWindow({
|
||||||
leaveTo="-translate-x-full"
|
leaveTo="-translate-x-full"
|
||||||
>
|
>
|
||||||
<DialogPanel
|
<DialogPanel
|
||||||
className="w-[80vw] max-w-[280px] h-full bg-base shadow-xl"
|
className="w-[80vw] max-w-[280px] h-full bg-surface shadow-elev-3"
|
||||||
>
|
>
|
||||||
{sidebar}
|
{sidebar}
|
||||||
</DialogPanel>
|
</DialogPanel>
|
||||||
|
|
|
||||||
|
|
@ -161,12 +161,12 @@ export function DexieMigrationModal({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel data-testid="migration-modal" className="w-full max-w-md transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition">
|
<DialogPanel data-testid="migration-modal" className="w-full max-w-md transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition">
|
||||||
<DialogTitle as="h3" className="text-lg font-semibold leading-6 text-foreground mb-4">
|
<DialogTitle as="h3" className="text-lg font-semibold leading-6 text-foreground mb-4">
|
||||||
{title}
|
{title}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-sm text-muted mb-2">
|
<p className="text-sm text-soft mb-2">
|
||||||
Found {localCount} document{localCount === 1 ? '' : 's'} stored locally from an older version.
|
Found {localCount} document{localCount === 1 ? '' : 's'} stored locally from an older version.
|
||||||
{displayMissingCount > 0 ? (
|
{displayMissingCount > 0 ? (
|
||||||
<> {displayMissingCount} {displayMissingCount === 1 ? 'is' : 'are'} not here yet.</>
|
<> {displayMissingCount} {displayMissingCount === 1 ? 'is' : 'are'} not here yet.</>
|
||||||
|
|
@ -175,8 +175,8 @@ export function DexieMigrationModal({
|
||||||
</p>
|
</p>
|
||||||
{isUploading && (
|
{isUploading && (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<p className="text-xs text-muted">{status}</p>
|
<p className="text-xs text-soft">{status}</p>
|
||||||
<div className="h-2 w-full rounded bg-offbase">
|
<div className="h-2 w-full rounded bg-surface-sunken">
|
||||||
<div className="h-2 rounded bg-accent" style={{ width: `${Math.max(1, Math.round(progress))}%` }} />
|
<div className="h-2 rounded bg-accent" style={{ width: `${Math.max(1, Math.round(progress))}%` }} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -189,8 +189,8 @@ export function DexieMigrationModal({
|
||||||
data-testid="migration-skip-button"
|
data-testid="migration-skip-button"
|
||||||
onClick={handleSkip}
|
onClick={handleSkip}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
className="inline-flex justify-center rounded-lg bg-background px-3 py-1.5 text-sm
|
className="inline-flex justify-center rounded-lg bg-background px-3 py-1.5 text-sm
|
||||||
font-medium text-foreground hover:bg-offbase focus:outline-none
|
font-medium text-foreground hover:bg-accent-wash focus:outline-none
|
||||||
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
||||||
transform transition-transform duration-base ease-standard hover:text-accent
|
transform transition-transform duration-base ease-standard hover:text-accent
|
||||||
disabled:opacity-50"
|
disabled:opacity-50"
|
||||||
|
|
@ -200,8 +200,8 @@ export function DexieMigrationModal({
|
||||||
<Button
|
<Button
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={isUploading}
|
disabled={isUploading}
|
||||||
className="inline-flex justify-center rounded-lg bg-accent px-3 py-1.5 text-sm
|
className="inline-flex justify-center rounded-lg bg-accent px-3 py-1.5 text-sm
|
||||||
font-medium text-background hover:bg-secondary-accent focus:outline-none
|
font-medium text-background hover:bg-secondary-accent focus:outline-none
|
||||||
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2
|
||||||
transform transition-transform duration-base ease-standard hover:text-background
|
transform transition-transform duration-base ease-standard hover:text-background
|
||||||
disabled:opacity-50"
|
disabled:opacity-50"
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,10 @@ export function DocumentHeaderMenu({
|
||||||
{onOpenSegments && (
|
{onOpenSegments && (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenSegments}
|
onClick={onOpenSegments}
|
||||||
className={`inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition duration-base ease-standard ${
|
className={`inline-flex items-center py-1 px-2 rounded-md border bg-surface text-xs transition duration-base ease-standard ${
|
||||||
isSegmentsOpen
|
isSegmentsOpen
|
||||||
? 'border-accent text-accent bg-offbase'
|
? 'border-accent text-accent bg-surface-sunken'
|
||||||
: 'border-offbase text-foreground hover:bg-offbase hover:text-accent'
|
: 'border-line text-foreground hover:bg-accent-wash hover:text-accent'
|
||||||
}`}
|
}`}
|
||||||
aria-label={isSegmentsOpen ? 'Hide segments sidebar' : 'Open segments sidebar'}
|
aria-label={isSegmentsOpen ? 'Hide segments sidebar' : 'Open segments sidebar'}
|
||||||
title={isSegmentsOpen ? 'Hide Segments' : 'Segments'}
|
title={isSegmentsOpen ? 'Hide Segments' : 'Segments'}
|
||||||
|
|
@ -63,10 +63,10 @@ export function DocumentHeaderMenu({
|
||||||
{showAudiobookExport && onOpenAudiobook && (
|
{showAudiobookExport && onOpenAudiobook && (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenAudiobook}
|
onClick={onOpenAudiobook}
|
||||||
className={`inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition duration-base ease-standard ${
|
className={`inline-flex items-center py-1 px-2 rounded-md border bg-surface text-xs transition duration-base ease-standard ${
|
||||||
isAudiobookOpen
|
isAudiobookOpen
|
||||||
? 'border-accent text-accent bg-offbase'
|
? 'border-accent text-accent bg-surface-sunken'
|
||||||
: 'border-offbase text-foreground hover:bg-offbase hover:text-accent'
|
: 'border-line text-foreground hover:bg-accent-wash hover:text-accent'
|
||||||
}`}
|
}`}
|
||||||
aria-label={isAudiobookOpen ? 'Hide audiobook export' : 'Open audiobook export'}
|
aria-label={isAudiobookOpen ? 'Hide audiobook export' : 'Open audiobook export'}
|
||||||
title={isAudiobookOpen ? 'Hide Export Audiobook' : 'Export Audiobook'}
|
title={isAudiobookOpen ? 'Hide Export Audiobook' : 'Export Audiobook'}
|
||||||
|
|
@ -76,10 +76,10 @@ export function DocumentHeaderMenu({
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
className={`inline-flex items-center py-1 px-2 rounded-md border bg-base text-xs transition duration-base ease-standard ${
|
className={`inline-flex items-center py-1 px-2 rounded-md border bg-surface text-xs transition duration-base ease-standard ${
|
||||||
isSettingsOpen
|
isSettingsOpen
|
||||||
? 'border-accent text-accent bg-offbase'
|
? 'border-accent text-accent bg-surface-sunken'
|
||||||
: 'border-offbase text-foreground hover:bg-offbase hover:text-accent'
|
: 'border-line text-foreground hover:bg-accent-wash hover:text-accent'
|
||||||
}`}
|
}`}
|
||||||
aria-label={isSettingsOpen ? 'Hide settings' : 'Open settings'}
|
aria-label={isSettingsOpen ? 'Hide settings' : 'Open settings'}
|
||||||
title={isSettingsOpen ? 'Hide Settings' : 'Settings'}
|
title={isSettingsOpen ? 'Hide Settings' : 'Settings'}
|
||||||
|
|
@ -95,7 +95,7 @@ export function DocumentHeaderMenu({
|
||||||
<div className="sm:hidden flex items-center">
|
<div className="sm:hidden flex items-center">
|
||||||
<Menu as="div" className="relative inline-block text-left">
|
<Menu as="div" className="relative inline-block text-left">
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="inline-flex items-center justify-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard hover:text-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
className="inline-flex items-center justify-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard hover:text-accent focus:outline-none focus-visible:ring-2 focus-visible:ring-accent"
|
||||||
title="Menu"
|
title="Menu"
|
||||||
>
|
>
|
||||||
<DotsVerticalIcon className="w-4 h-4 transform transition-transform duration-base ease-standard hover:text-accent" />
|
<DotsVerticalIcon className="w-4 h-4 transform transition-transform duration-base ease-standard hover:text-accent" />
|
||||||
|
|
@ -109,10 +109,10 @@ export function DocumentHeaderMenu({
|
||||||
leaveFrom="transform opacity-100 scale-100"
|
leaveFrom="transform opacity-100 scale-100"
|
||||||
leaveTo="transform opacity-0 scale-95"
|
leaveTo="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<MenuItems className="absolute right-0 mt-2 min-w-max origin-top-right divide-y divide-offbase rounded-md bg-base shadow-lg ring-1 ring-black/5 focus:outline-none z-50">
|
<MenuItems className="absolute right-0 mt-2 min-w-max origin-top-right divide-y divide-line-soft rounded-md bg-surface shadow-elev-2 ring-1 ring-line-soft focus:outline-none z-50">
|
||||||
{/* Zoom Controls Section */}
|
{/* Zoom Controls Section */}
|
||||||
<div className="px-4 py-3">
|
<div className="px-4 py-3">
|
||||||
<p className="text-xs font-medium text-muted mb-2">Zoom / Padding</p>
|
<p className="text-xs font-medium text-soft mb-2">Zoom / Padding</p>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<ZoomControl
|
<ZoomControl
|
||||||
value={zoomLevel}
|
value={zoomLevel}
|
||||||
|
|
@ -138,7 +138,7 @@ export function DocumentHeaderMenu({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenSegments}
|
onClick={onOpenSegments}
|
||||||
className={`${active || isSegmentsOpen ? 'bg-offbase text-accent' : 'text-foreground'
|
className={`${active || isSegmentsOpen ? 'bg-surface-sunken text-accent' : 'text-foreground'
|
||||||
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
||||||
>
|
>
|
||||||
<ListIcon className="h-4 w-4" />
|
<ListIcon className="h-4 w-4" />
|
||||||
|
|
@ -152,7 +152,7 @@ export function DocumentHeaderMenu({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenAudiobook}
|
onClick={onOpenAudiobook}
|
||||||
className={`${active || isAudiobookOpen ? 'bg-offbase text-accent' : 'text-foreground'
|
className={`${active || isAudiobookOpen ? 'bg-surface-sunken text-accent' : 'text-foreground'
|
||||||
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
||||||
>
|
>
|
||||||
<DownloadIcon className="h-4 w-4" />
|
<DownloadIcon className="h-4 w-4" />
|
||||||
|
|
@ -165,7 +165,7 @@ export function DocumentHeaderMenu({
|
||||||
{({ active }) => (
|
{({ active }) => (
|
||||||
<button
|
<button
|
||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
className={`${active || isSettingsOpen ? 'bg-offbase text-accent' : 'text-foreground'
|
className={`${active || isSettingsOpen ? 'bg-surface-sunken text-accent' : 'text-foreground'
|
||||||
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
} group flex w-full items-center gap-2 rounded-md px-2 py-2 text-xs`}
|
||||||
>
|
>
|
||||||
<FileSettingsIcon className="h-4 w-4" />
|
<FileSettingsIcon className="h-4 w-4" />
|
||||||
|
|
@ -176,7 +176,7 @@ export function DocumentHeaderMenu({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Auth Section */}
|
{/* Auth Section */}
|
||||||
<div className="p-2 border-t border-offbase flex justify-center">
|
<div className="p-2 border-t border-line-soft flex justify-center">
|
||||||
<UserMenu />
|
<UserMenu />
|
||||||
</div>
|
</div>
|
||||||
</MenuItems>
|
</MenuItems>
|
||||||
|
|
|
||||||
|
|
@ -139,7 +139,7 @@ export function DocumentSelectionModal({
|
||||||
leaveFrom="opacity-100 scale-100"
|
leaveFrom="opacity-100 scale-100"
|
||||||
leaveTo="opacity-0 scale-95"
|
leaveTo="opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<DialogPanel className="w-full max-w-2xl transform rounded-2xl bg-base p-6 text-left align-middle shadow-xl transition flex flex-col h-[80vh]">
|
<DialogPanel className="w-full max-w-2xl transform rounded-lg bg-surface p-6 text-left align-middle shadow-elev-3 transition flex flex-col h-[80vh]">
|
||||||
<DialogTitle
|
<DialogTitle
|
||||||
as="h3"
|
as="h3"
|
||||||
className="text-lg font-semibold leading-6 text-foreground mb-4 flex-shrink-0 flex justify-between items-center"
|
className="text-lg font-semibold leading-6 text-foreground mb-4 flex-shrink-0 flex justify-between items-center"
|
||||||
|
|
@ -147,10 +147,10 @@ export function DocumentSelectionModal({
|
||||||
{title}
|
{title}
|
||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<div className="flex items-center text-sm font-normal">
|
<div className="flex items-center text-sm font-normal">
|
||||||
<label className="flex items-center gap-2 cursor-pointer select-none text-muted hover:text-foreground transition-colors">
|
<label className="flex items-center gap-2 cursor-pointer select-none text-soft hover:text-foreground transition-colors">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
className="rounded border-muted text-accent focus:ring-accent"
|
className="rounded border-muted text-accent focus:ring-accent-line"
|
||||||
checked={allSelected}
|
checked={allSelected}
|
||||||
ref={input => {
|
ref={input => {
|
||||||
if (input) input.indeterminate = isIndeterminate;
|
if (input) input.indeterminate = isIndeterminate;
|
||||||
|
|
@ -163,13 +163,13 @@ export function DocumentSelectionModal({
|
||||||
)}
|
)}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
|
|
||||||
<div className="flex-1 overflow-auto border border-offbase rounded-lg bg-background p-2 min-h-0">
|
<div className="flex-1 overflow-auto border border-line rounded-lg bg-background p-2 min-h-0">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<DocumentSelectionSkeleton />
|
<DocumentSelectionSkeleton />
|
||||||
) : errorMessage ? (
|
) : errorMessage ? (
|
||||||
<div className="flex items-center justify-center h-full text-danger">{errorMessage}</div>
|
<div className="flex items-center justify-center h-full text-danger">{errorMessage}</div>
|
||||||
) : files.length === 0 ? (
|
) : files.length === 0 ? (
|
||||||
<div className="flex items-center justify-center h-full text-muted">No documents found.</div>
|
<div className="flex items-center justify-center h-full text-soft">No documents found.</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
{files.map((file) => {
|
{files.map((file) => {
|
||||||
|
|
@ -179,7 +179,7 @@ export function DocumentSelectionModal({
|
||||||
key={file.id}
|
key={file.id}
|
||||||
onClick={(e) => handleRowClick(e, file.id)}
|
onClick={(e) => handleRowClick(e, file.id)}
|
||||||
className={`flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer text-sm select-none
|
className={`flex items-center gap-3 px-3 py-2 rounded-md cursor-pointer text-sm select-none
|
||||||
${isSelected ? 'bg-accent-wash' : 'hover:bg-offbase'}
|
${isSelected ? 'bg-accent-wash' : 'hover:bg-accent-wash'}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="flex-shrink-0" onClick={(e) => e.stopPropagation()}>
|
<div className="flex-shrink-0" onClick={(e) => e.stopPropagation()}>
|
||||||
|
|
@ -187,7 +187,7 @@ export function DocumentSelectionModal({
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={isSelected}
|
checked={isSelected}
|
||||||
onChange={(e) => handleCheckboxChange(file.id, e.target.checked)}
|
onChange={(e) => handleCheckboxChange(file.id, e.target.checked)}
|
||||||
className="rounded border-muted text-accent focus:ring-accent"
|
className="rounded border-muted text-accent focus:ring-accent-line"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|
@ -197,7 +197,7 @@ export function DocumentSelectionModal({
|
||||||
>
|
>
|
||||||
{file.name}
|
{file.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-muted text-xs whitespace-nowrap">{formatSize(file.size)}</div>
|
<div className="text-soft text-xs whitespace-nowrap">{formatSize(file.size)}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
@ -237,9 +237,9 @@ function DocumentSelectionSkeleton() {
|
||||||
<div className="h-full animate-pulse space-y-0.5" aria-label="Loading documents" aria-busy="true">
|
<div className="h-full animate-pulse space-y-0.5" aria-label="Loading documents" aria-busy="true">
|
||||||
{rows.map((_, index) => (
|
{rows.map((_, index) => (
|
||||||
<div key={index} className="flex items-center gap-3 px-3 py-2 rounded-md">
|
<div key={index} className="flex items-center gap-3 px-3 py-2 rounded-md">
|
||||||
<div className="h-4 w-4 rounded-sm bg-offbase border border-offbase" />
|
<div className="h-4 w-4 rounded-sm bg-surface-sunken border border-line" />
|
||||||
<div className="h-3.5 flex-1 rounded bg-offbase" />
|
<div className="h-3.5 flex-1 rounded bg-surface-sunken" />
|
||||||
<div className="h-3 w-14 rounded bg-offbase" />
|
<div className="h-3 w-14 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -163,13 +163,13 @@ export function DocumentUploader({
|
||||||
variant === 'compact'
|
variant === 'compact'
|
||||||
? `${
|
? `${
|
||||||
isDragActive
|
isDragActive
|
||||||
? 'border border-accent bg-offbase text-accent'
|
? 'border border-accent bg-surface-sunken text-accent'
|
||||||
: 'border border-dashed border-offbase text-foreground hover:border-accent hover:bg-offbase hover:text-accent'
|
: 'border border-dashed border-line text-foreground hover:border-accent hover:bg-accent-wash hover:text-accent'
|
||||||
}`
|
}`
|
||||||
: `${
|
: `${
|
||||||
isDragActive
|
isDragActive
|
||||||
? 'border-2 border-dashed border-accent bg-base text-foreground'
|
? 'border-2 border-dashed border-accent bg-surface text-foreground'
|
||||||
: 'border-2 border-dashed border-muted bg-transparent text-foreground hover:border-accent hover:bg-base'
|
: 'border-2 border-dashed border-muted bg-transparent text-foreground hover:border-accent hover:bg-surface'
|
||||||
}`;
|
}`;
|
||||||
|
|
||||||
const paddingClass = variant === 'compact' ? 'py-1 px-2 rounded-md' : 'py-5 px-3 rounded-lg';
|
const paddingClass = variant === 'compact' ? 'py-1 px-2 rounded-md' : 'py-5 px-3 rounded-lg';
|
||||||
|
|
@ -182,7 +182,7 @@ export function DocumentUploader({
|
||||||
{children}
|
{children}
|
||||||
{isDragActive && (
|
{isDragActive && (
|
||||||
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center bg-surface backdrop-blur-md pointer-events-none p-6">
|
<div className="absolute inset-0 z-50 flex flex-col items-center justify-center bg-surface backdrop-blur-md pointer-events-none p-6">
|
||||||
<div className="w-full h-full border-2 border-dashed border-accent rounded-xl flex flex-col items-center justify-center bg-surface-solid text-center p-4">
|
<div className="w-full h-full border-2 border-dashed border-accent rounded-lg flex flex-col items-center justify-center bg-surface-solid text-center p-4">
|
||||||
<UploadIcon className="w-14 h-14 text-accent mb-4 animate-bounce" />
|
<UploadIcon className="w-14 h-14 text-accent mb-4 animate-bounce" />
|
||||||
<p className="text-xl font-bold text-foreground mb-1.5">
|
<p className="text-xl font-bold text-foreground mb-1.5">
|
||||||
Drop files here to upload
|
Drop files here to upload
|
||||||
|
|
@ -217,7 +217,7 @@ export function DocumentUploader({
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
{variant === 'compact' ? (
|
{variant === 'compact' ? (
|
||||||
<div className="flex items-center gap-2 text-left w-full min-w-0">
|
<div className="flex items-center gap-2 text-left w-full min-w-0">
|
||||||
<UploadIcon className="w-3.5 h-3.5 text-muted group-hover:text-accent shrink-0 transition-colors duration-base" />
|
<UploadIcon className="w-3.5 h-3.5 text-soft group-hover:text-accent shrink-0 transition-colors duration-base" />
|
||||||
{isUploading ? (
|
{isUploading ? (
|
||||||
<p className="text-[12px] font-medium truncate flex-1">Uploading…</p>
|
<p className="text-[12px] font-medium truncate flex-1">Uploading…</p>
|
||||||
) : isConverting ? (
|
) : isConverting ? (
|
||||||
|
|
@ -233,7 +233,7 @@ export function DocumentUploader({
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col items-center justify-center text-center">
|
<div className="flex flex-col items-center justify-center text-center">
|
||||||
<UploadIcon className="w-7 h-7 sm:w-10 sm:h-10 mb-2 text-muted" />
|
<UploadIcon className="w-7 h-7 sm:w-10 sm:h-10 mb-2 text-soft" />
|
||||||
{isUploading ? (
|
{isUploading ? (
|
||||||
<p className="text-sm sm:text-lg font-semibold text-foreground">Uploading file...</p>
|
<p className="text-sm sm:text-lg font-semibold text-foreground">Uploading file...</p>
|
||||||
) : isConverting ? (
|
) : isConverting ? (
|
||||||
|
|
@ -243,7 +243,7 @@ export function DocumentUploader({
|
||||||
<p className="mb-2 text-sm sm:text-lg font-semibold text-foreground">
|
<p className="mb-2 text-sm sm:text-lg font-semibold text-foreground">
|
||||||
{isDragActive ? 'Drop your file(s) here' : 'Drop your file(s) here, or click to select'}
|
{isDragActive ? 'Drop your file(s) here' : 'Drop your file(s) here, or click to select'}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs sm:text-sm text-muted">
|
<p className="text-xs sm:text-sm text-soft">
|
||||||
{enableDocx ? 'PDF, EPUB, TXT, MD, or DOCX files are accepted' : 'PDF, EPUB, TXT, or MD files are accepted'}
|
{enableDocx ? 'PDF, EPUB, TXT, MD, or DOCX files are accepted' : 'PDF, EPUB, TXT, or MD files are accepted'}
|
||||||
</p>
|
</p>
|
||||||
{error && <p className="mt-2 text-sm text-danger">{error}</p>}
|
{error && <p className="mt-2 text-sm text-danger">{error}</p>}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ export function ZoomControl({
|
||||||
>
|
>
|
||||||
−
|
−
|
||||||
</button>
|
</button>
|
||||||
<span className="text-xs tabular-nums w-12 text-center text-muted">{value}%</span>
|
<span className="text-xs tabular-nums w-12 text-center text-soft">{value}%</span>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onIncrease}
|
onClick={onIncrease}
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,16 @@ export {
|
||||||
type ButtonVariant,
|
type ButtonVariant,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Inputs use `bg-base` so they remain visible regardless of whether the
|
// Inputs use `bg-surface` so they remain visible regardless of whether the
|
||||||
// surrounding container is `bg-background` (Card) or `bg-base` (Section).
|
// surrounding container is `bg-background` (Card) or `bg-surface` (Section).
|
||||||
// Using the same `bg-background` as the Card would make the input blend in.
|
// Using the same `bg-background` as the Card would make the input blend in.
|
||||||
// (Note: never use Tailwind alpha modifiers on these theme variables — they
|
// (Note: never use Tailwind alpha modifiers on these theme variables — they
|
||||||
// resolve to CSS custom properties that don't accept opacity suffixes.)
|
// resolve to CSS custom properties that don't accept opacity suffixes.)
|
||||||
export const inputClass =
|
export const inputClass =
|
||||||
'w-full rounded-md bg-surface-sunken border border-line px-2.5 py-1.5 text-sm text-foreground transition-colors duration-fast ease-standard focus:outline-none focus:ring-2 focus:ring-accent focus:border-accent-line';
|
'w-full rounded-md bg-surface-sunken border border-line px-2.5 py-1.5 text-sm text-foreground transition-colors duration-fast ease-standard focus:outline-none focus:ring-2 focus:ring-accent-line focus:border-accent-line';
|
||||||
|
|
||||||
export const listboxButtonClass =
|
export const listboxButtonClass =
|
||||||
'relative w-full cursor-pointer rounded-md bg-surface-sunken border border-line py-1.5 pl-2.5 pr-9 text-left text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent hover:bg-accent-wash transition-colors duration-fast ease-standard';
|
'relative w-full cursor-pointer rounded-md bg-surface-sunken border border-line py-1.5 pl-2.5 pr-9 text-left text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-accent-line hover:bg-accent-wash transition-colors duration-fast ease-standard';
|
||||||
export const listboxOptionsClass =
|
export const listboxOptionsClass =
|
||||||
'z-50 w-[var(--button-width)] max-h-60 overflow-y-auto overscroll-contain rounded-md bg-surface p-1 shadow-elev-2 ring-1 ring-line focus:outline-none [--anchor-gap:0.25rem]';
|
'z-50 w-[var(--button-width)] max-h-60 overflow-y-auto overscroll-contain rounded-md bg-surface p-1 shadow-elev-2 ring-1 ring-line focus:outline-none [--anchor-gap:0.25rem]';
|
||||||
export const listboxOptionClass = (active: boolean) =>
|
export const listboxOptionClass = (active: boolean) =>
|
||||||
|
|
|
||||||
|
|
@ -283,7 +283,7 @@ export function FileIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className={props.className || "w-8 h-8 text-muted"}
|
className={props.className || "w-8 h-8 text-soft"}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
|
||||||
<Button
|
<Button
|
||||||
onClick={() => skipToLocation(currentPage - 1, true)}
|
onClick={() => skipToLocation(currentPage - 1, true)}
|
||||||
disabled={currentPage <= 1}
|
disabled={currentPage <= 1}
|
||||||
className="relative p-2 rounded-full text-foreground hover:bg-offbase data-[hover]:bg-offbase data-[active]:bg-offbase transition duration-base focus:outline-none disabled:opacity-50 transform ease-standard hover:text-accent"
|
className="relative p-2 rounded-full text-foreground hover:bg-accent-wash data-[hover]:bg-accent-wash data-[active]:bg-accent-wash transition duration-base focus:outline-none disabled:opacity-50 transform ease-standard hover:text-accent"
|
||||||
aria-label="Previous page"
|
aria-label="Previous page"
|
||||||
>
|
>
|
||||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
|
|
@ -67,14 +67,14 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
|
||||||
{/* Page number popup */}
|
{/* Page number popup */}
|
||||||
<Popover className="relative mb-1">
|
<Popover className="relative mb-1">
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
className="bg-offbase px-2 py-0.5 rounded-full focus:outline-none cursor-pointer hover:bg-offbase transform transition-transform duration-base ease-standard hover:text-accent"
|
className="bg-surface-sunken px-2 py-0.5 rounded-full focus:outline-none cursor-pointer hover:bg-accent-wash transform transition-transform duration-base ease-standard hover:text-accent"
|
||||||
onClick={handlePopoverOpen}
|
onClick={handlePopoverOpen}
|
||||||
>
|
>
|
||||||
<p className="text-xs whitespace-nowrap">
|
<p className="text-xs whitespace-nowrap">
|
||||||
{currentPage} / {numPages || 1}
|
{currentPage} / {numPages || 1}
|
||||||
</p>
|
</p>
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<PopoverPanel anchor="top" className="absolute z-50 bg-base p-3 rounded-md shadow-lg border border-offbase">
|
<PopoverPanel anchor="top" className="absolute z-50 bg-surface p-3 rounded-md shadow-elev-2 border border-line">
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<div className="text-xs font-medium text-foreground">Go to page</div>
|
<div className="text-xs font-medium text-foreground">Go to page</div>
|
||||||
<input
|
<input
|
||||||
|
|
@ -82,7 +82,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
|
||||||
type="text"
|
type="text"
|
||||||
inputMode="numeric"
|
inputMode="numeric"
|
||||||
pattern="[0-9]*"
|
pattern="[0-9]*"
|
||||||
className="w-20 px-2 py-1 text-xs text-accent bg-offbase rounded border-none outline-none appearance-none text-center"
|
className="w-20 px-2 py-1 text-xs text-accent bg-surface-sunken rounded border-none outline-none appearance-none text-center"
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={handleInputChange}
|
onChange={handleInputChange}
|
||||||
onBlur={handleInputConfirm}
|
onBlur={handleInputConfirm}
|
||||||
|
|
@ -90,7 +90,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
|
||||||
placeholder={currentPage.toString()}
|
placeholder={currentPage.toString()}
|
||||||
aria-label="Page number"
|
aria-label="Page number"
|
||||||
/>
|
/>
|
||||||
<div className="text-xs text-muted text-center">of {numPages || 1}</div>
|
<div className="text-xs text-soft text-center">of {numPages || 1}</div>
|
||||||
</div>
|
</div>
|
||||||
</PopoverPanel>
|
</PopoverPanel>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
|
@ -99,7 +99,7 @@ export const Navigator = ({ currentPage, numPages, skipToLocation }: {
|
||||||
<Button
|
<Button
|
||||||
onClick={() => skipToLocation(currentPage + 1, true)}
|
onClick={() => skipToLocation(currentPage + 1, true)}
|
||||||
disabled={currentPage >= (numPages || 1)}
|
disabled={currentPage >= (numPages || 1)}
|
||||||
className="relative p-2 rounded-full text-foreground hover:bg-offbase data-[hover]:bg-offbase data-[active]:bg-offbase transition duration-base focus:outline-none disabled:opacity-50 transform ease-standard hover:text-accent"
|
className="relative p-2 rounded-full text-foreground hover:bg-accent-wash data-[hover]:bg-accent-wash data-[active]:bg-accent-wash transition duration-base focus:outline-none disabled:opacity-50 transform ease-standard hover:text-accent"
|
||||||
aria-label="Next page"
|
aria-label="Next page"
|
||||||
>
|
>
|
||||||
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
<svg className="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ export function RateLimitPauseButton() {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (isPlaying) togglePlay();
|
if (isPlaying) togglePlay();
|
||||||
}}
|
}}
|
||||||
className="relative p-1.5 rounded-md text-foreground hover:bg-offbase transition duration-base focus:outline-none h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
className="relative p-1.5 rounded-md text-foreground hover:bg-accent-wash transition duration-base focus:outline-none h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
||||||
aria-label="Pause"
|
aria-label="Pause"
|
||||||
>
|
>
|
||||||
<PauseIcon className="w-5 h-5" />
|
<PauseIcon className="w-5 h-5" />
|
||||||
|
|
|
||||||
|
|
@ -88,16 +88,16 @@ export const SpeedControl = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className="relative">
|
<Popover className="relative">
|
||||||
<PopoverButton className="flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1 transform transition-transform duration-base ease-standard hover:text-accent">
|
<PopoverButton className="flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-accent-wash rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1 transform transition-transform duration-base ease-standard hover:text-accent">
|
||||||
<SpeedometerIcon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
<SpeedometerIcon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||||
<span className="sm:hidden">{compactTriggerLabel}</span>
|
<span className="sm:hidden">{compactTriggerLabel}</span>
|
||||||
<span className="hidden sm:inline">{triggerLabel}</span>
|
<span className="hidden sm:inline">{triggerLabel}</span>
|
||||||
<ChevronUpDownIcon className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
<ChevronUpDownIcon className="h-2.5 w-2.5 sm:h-3 sm:w-3" />
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
<PopoverPanel anchor="top" className="absolute z-50 bg-base p-3 rounded-md shadow-lg border border-offbase">
|
<PopoverPanel anchor="top" className="absolute z-50 bg-surface p-3 rounded-md shadow-elev-2 border border-line">
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{!nativeSpeedSupported && (
|
{!nativeSpeedSupported && (
|
||||||
<div className="rounded-md border border-offbase bg-background px-2 py-1.5 text-[11px] text-muted">
|
<div className="rounded-md border border-line bg-background px-2 py-1.5 text-[11px] text-soft">
|
||||||
Native model speed is not available for this model.
|
Native model speed is not available for this model.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -122,7 +122,7 @@ export const SpeedControl = ({
|
||||||
onMouseUp={handleVoiceSpeedChangeComplete}
|
onMouseUp={handleVoiceSpeedChangeComplete}
|
||||||
onKeyUp={handleVoiceSpeedChangeComplete}
|
onKeyUp={handleVoiceSpeedChangeComplete}
|
||||||
onTouchEnd={handleVoiceSpeedChangeComplete}
|
onTouchEnd={handleVoiceSpeedChangeComplete}
|
||||||
className="w-full bg-offbase rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent"
|
className="w-full bg-surface-sunken rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -146,7 +146,7 @@ export const SpeedControl = ({
|
||||||
onMouseUp={handleAudioSpeedChangeComplete}
|
onMouseUp={handleAudioSpeedChangeComplete}
|
||||||
onKeyUp={handleAudioSpeedChangeComplete}
|
onKeyUp={handleAudioSpeedChangeComplete}
|
||||||
onTouchEnd={handleAudioSpeedChangeComplete}
|
onTouchEnd={handleAudioSpeedChangeComplete}
|
||||||
className="w-full bg-offbase rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-offbase [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-offbase [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent"
|
className="w-full bg-surface-sunken rounded-lg appearance-none cursor-pointer accent-accent [&::-webkit-slider-runnable-track]:bg-surface-sunken [&::-webkit-slider-runnable-track]:rounded-lg [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:h-4 [&::-webkit-slider-thumb]:w-4 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-accent [&::-moz-range-track]:bg-surface-sunken [&::-moz-range-track]:rounded-lg [&::-moz-range-thumb]:appearance-none [&::-moz-range-thumb]:h-4 [&::-moz-range-thumb]:w-4 [&::-moz-range-thumb]:rounded-full [&::-moz-range-thumb]:bg-accent"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ export default function TTSPlayer({ currentPage, numPages }: {
|
||||||
} = useTTS();
|
} = useTTS();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sticky bottom-0 z-30 w-full border-t border-offbase bg-base" data-app-ttsbar>
|
<div className="sticky bottom-0 z-30 w-full border-t border-line-soft bg-surface" data-app-ttsbar>
|
||||||
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
<div className="px-2 md:px-3 pt-1 pb-[max(0.375rem,env(safe-area-inset-bottom))] flex items-center justify-center gap-1 min-h-10">
|
||||||
{/* Speed control */}
|
{/* Speed control */}
|
||||||
<SpeedControl
|
<SpeedControl
|
||||||
|
|
@ -51,7 +51,7 @@ export default function TTSPlayer({ currentPage, numPages }: {
|
||||||
{/* Playback Controls */}
|
{/* Playback Controls */}
|
||||||
<Button
|
<Button
|
||||||
onClick={skipBackward}
|
onClick={skipBackward}
|
||||||
className="relative p-1.5 rounded-md text-foreground hover:bg-offbase transition duration-base focus:outline-none disabled:opacity-50 h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
className="relative p-1.5 rounded-md text-foreground hover:bg-accent-wash transition duration-base focus:outline-none disabled:opacity-50 h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
||||||
aria-label="Skip backward"
|
aria-label="Skip backward"
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
>
|
>
|
||||||
|
|
@ -60,7 +60,7 @@ export default function TTSPlayer({ currentPage, numPages }: {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={togglePlay}
|
onClick={togglePlay}
|
||||||
className="relative p-1.5 rounded-md text-foreground hover:bg-offbase transition duration-base focus:outline-none h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
className="relative p-1.5 rounded-md text-foreground hover:bg-accent-wash transition duration-base focus:outline-none h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
||||||
aria-label={isPlaying ? 'Pause' : 'Play'}
|
aria-label={isPlaying ? 'Pause' : 'Play'}
|
||||||
disabled={isProcessing && !isPlaying}
|
disabled={isProcessing && !isPlaying}
|
||||||
>
|
>
|
||||||
|
|
@ -69,7 +69,7 @@ export default function TTSPlayer({ currentPage, numPages }: {
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={skipForward}
|
onClick={skipForward}
|
||||||
className="relative p-1.5 rounded-md text-foreground hover:bg-offbase transition duration-base focus:outline-none disabled:opacity-50 h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
className="relative p-1.5 rounded-md text-foreground hover:bg-accent-wash transition duration-base focus:outline-none disabled:opacity-50 h-8 w-8 flex items-center justify-center transform ease-standard hover:text-accent"
|
||||||
aria-label="Skip forward"
|
aria-label="Skip forward"
|
||||||
disabled={isProcessing}
|
disabled={isProcessing}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,15 @@ export function VoicesControlBase({
|
||||||
: 'bottom-full right-0 mb-1';
|
: 'bottom-full right-0 mb-1';
|
||||||
|
|
||||||
const buttonClass = variant === 'field'
|
const buttonClass = variant === 'field'
|
||||||
? 'relative cursor-pointer rounded-lg bg-base py-1.5 pl-3 pr-10 text-left text-foreground focus:outline-none focus:ring-2 focus:ring-accent transform transition-transform duration-base ease-standard hover:text-accent w-full'
|
? 'relative cursor-pointer rounded-lg bg-surface py-1.5 pl-3 pr-10 text-left text-foreground focus:outline-none focus:ring-2 focus:ring-accent-line transform transition-transform duration-base ease-standard hover:text-accent w-full'
|
||||||
: 'flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-offbase rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1 transform transition-transform duration-base ease-standard hover:text-accent';
|
: 'flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-foreground text-xs sm:text-sm focus:outline-none cursor-pointer hover:bg-accent-wash rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1 transform transition-transform duration-base ease-standard hover:text-accent';
|
||||||
|
|
||||||
const iconClass = variant === 'field'
|
const iconClass = variant === 'field'
|
||||||
? 'h-3.5 w-3.5 shrink-0'
|
? 'h-3.5 w-3.5 shrink-0'
|
||||||
: 'h-3 w-3 sm:h-3.5 sm:w-3.5';
|
: 'h-3 w-3 sm:h-3.5 sm:w-3.5';
|
||||||
|
|
||||||
const chevronClass = variant === 'field'
|
const chevronClass = variant === 'field'
|
||||||
? 'h-4 w-4 text-muted'
|
? 'h-4 w-4 text-soft'
|
||||||
: 'h-2.5 w-2.5 sm:h-3 sm:w-3';
|
: 'h-2.5 w-2.5 sm:h-3 sm:w-3';
|
||||||
|
|
||||||
const providerModelPolicy = resolveTtsProviderModelPolicy({
|
const providerModelPolicy = resolveTtsProviderModelPolicy({
|
||||||
|
|
@ -85,7 +85,7 @@ export function VoicesControlBase({
|
||||||
if (availableVoices.length === 0) {
|
if (availableVoices.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-muted text-xs sm:text-sm rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1">
|
<div className="flex items-center space-x-0.5 sm:space-x-1 bg-transparent text-soft text-xs sm:text-sm rounded pl-1.5 sm:pl-2 pr-0.5 sm:pr-1 py-0.5 sm:py-1">
|
||||||
<AudioWaveIcon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
<AudioWaveIcon className="h-3 w-3 sm:h-3.5 sm:w-3.5" />
|
||||||
<span>No voices</span>
|
<span>No voices</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -140,13 +140,13 @@ export function VoicesControlBase({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<ListboxOptions className={`absolute ${dropdownPosition} z-50 ${dropdownWidth} !h-auto !min-h-0 !max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}>
|
<ListboxOptions className={`absolute ${dropdownPosition} z-50 ${dropdownWidth} !h-auto !min-h-0 !max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg bg-surface shadow-elev-2 ring-1 ring-line-soft focus:outline-none`}>
|
||||||
{availableVoices.map((voiceId) => (
|
{availableVoices.map((voiceId) => (
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
key={voiceId}
|
key={voiceId}
|
||||||
value={voiceId}
|
value={voiceId}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`relative cursor-pointer select-none py-1 px-2 sm:py-2 sm:px-3 flex items-center gap-2 ${active ? 'bg-offbase' : ''} ${selected ? 'font-medium bg-accent text-background' : ''} ${selected && active ? 'text-foreground' : ''}`
|
`relative cursor-pointer select-none py-1 px-2 sm:py-2 sm:px-3 flex items-center gap-2 ${active ? 'bg-surface-sunken' : ''} ${selected ? 'font-medium bg-accent text-background' : ''} ${selected && active ? 'text-foreground' : ''}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{({ selected }) => (
|
{({ selected }) => (
|
||||||
|
|
@ -184,13 +184,13 @@ export function VoicesControlBase({
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</ListboxButton>
|
</ListboxButton>
|
||||||
<ListboxOptions className={`absolute ${dropdownPosition} z-50 ${dropdownWidth} !h-auto !min-h-0 !max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg bg-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none`}>
|
<ListboxOptions className={`absolute ${dropdownPosition} z-50 ${dropdownWidth} !h-auto !min-h-0 !max-h-[50vh] overflow-y-auto overscroll-contain rounded-lg bg-surface shadow-elev-2 ring-1 ring-line-soft focus:outline-none`}>
|
||||||
{availableVoices.map((voiceId) => (
|
{availableVoices.map((voiceId) => (
|
||||||
<ListboxOption
|
<ListboxOption
|
||||||
key={voiceId}
|
key={voiceId}
|
||||||
value={voiceId}
|
value={voiceId}
|
||||||
className={({ active, selected }) =>
|
className={({ active, selected }) =>
|
||||||
`relative cursor-pointer select-none py-1 px-2 sm:py-2 sm:px-3 ${active ? 'bg-offbase' : ''} ${selected ? 'font-medium bg-accent text-background' : ''} ${selected && active ? 'text-foreground' : ''}`
|
`relative cursor-pointer select-none py-1 px-2 sm:py-2 sm:px-3 ${active ? 'bg-surface-sunken' : ''} ${selected ? 'font-medium bg-accent text-background' : ''} ${selected && active ? 'text-foreground' : ''}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="text-xs sm:text-sm">{voiceId}</span>
|
<span className="text-xs sm:text-sm">{voiceId}</span>
|
||||||
|
|
|
||||||
|
|
@ -68,12 +68,12 @@ export function ReaderSidebarShell({
|
||||||
<aside
|
<aside
|
||||||
role="dialog"
|
role="dialog"
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
className={`reader-sidebar-panel absolute inset-y-0 right-0 sm:right-3 sm:top-3 sm:bottom-3 pointer-events-auto bg-base border-l sm:border border-offbase shadow-xl sm:rounded-xl flex flex-col ${panelClassName}`}
|
className={`reader-sidebar-panel absolute inset-y-0 right-0 sm:right-3 sm:top-3 sm:bottom-3 pointer-events-auto bg-surface border-l sm:border border-line shadow-elev-3 sm:rounded-lg flex flex-col ${panelClassName}`}
|
||||||
>
|
>
|
||||||
<div className="border-b border-offbase px-4 py-3 flex items-start justify-between gap-3">
|
<div className="border-b border-line-soft px-4 py-3 flex items-start justify-between gap-3">
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h2 className="text-sm font-medium text-foreground">{title}</h2>
|
<h2 className="text-sm font-medium text-foreground">{title}</h2>
|
||||||
{subtitle ? <p className="mt-0.5 text-xs text-muted">{subtitle}</p> : null}
|
{subtitle ? <p className="mt-0.5 text-xs text-soft">{subtitle}</p> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
{headerActions}
|
{headerActions}
|
||||||
|
|
@ -85,7 +85,7 @@ export function ReaderSidebarShell({
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
size: 'icon',
|
size: 'icon',
|
||||||
className: 'h-8 w-8 text-muted',
|
className: 'h-8 w-8 text-soft',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<XCircleIcon className="w-4 h-4" />
|
<XCircleIcon className="w-4 h-4" />
|
||||||
|
|
|
||||||
|
|
@ -641,7 +641,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
size: 'xs',
|
size: 'xs',
|
||||||
className: 'h-8 px-2 text-muted',
|
className: 'h-8 px-2 text-soft',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{isClearingSegments ? 'Clearing…' : 'Clear'}
|
{isClearingSegments ? 'Clearing…' : 'Clear'}
|
||||||
|
|
@ -654,7 +654,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
className={buttonClass({
|
className={buttonClass({
|
||||||
variant: 'secondary',
|
variant: 'secondary',
|
||||||
size: 'icon',
|
size: 'icon',
|
||||||
className: 'h-8 w-8 text-muted',
|
className: 'h-8 w-8 text-soft',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<RefreshIcon className="w-3.5 h-3.5" />
|
<RefreshIcon className="w-3.5 h-3.5" />
|
||||||
|
|
@ -672,8 +672,8 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
headerActions={headerActions}
|
headerActions={headerActions}
|
||||||
bodyClassName="flex-1 overflow-y-auto px-0 py-0"
|
bodyClassName="flex-1 overflow-y-auto px-0 py-0"
|
||||||
>
|
>
|
||||||
<div className="px-4 py-2 border-b border-offbase">
|
<div className="px-4 py-2 border-b border-line-soft">
|
||||||
<div className="text-xs text-muted">
|
<div className="text-xs text-soft">
|
||||||
{hasLoadedManifest ? (
|
{hasLoadedManifest ? (
|
||||||
<>
|
<>
|
||||||
{rowsToRender.length} indexed
|
{rowsToRender.length} indexed
|
||||||
|
|
@ -687,7 +687,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
) : isManifestLoading ? (
|
) : isManifestLoading ? (
|
||||||
<div className="animate-pulse h-3 w-36 rounded bg-offbase" aria-label="Loading segment summary" aria-busy="true" />
|
<div className="animate-pulse h-3 w-36 rounded bg-surface-sunken" aria-label="Loading segment summary" aria-busy="true" />
|
||||||
) : hasManifestError ? (
|
) : hasManifestError ? (
|
||||||
<span className="text-danger">error</span>
|
<span className="text-danger">error</span>
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -705,16 +705,16 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
)}
|
)}
|
||||||
{hasLoadedManifest && rowsToRender.length === 0 && (
|
{hasLoadedManifest && rowsToRender.length === 0 && (
|
||||||
<div className="px-4 py-10 flex flex-col items-center text-center gap-2">
|
<div className="px-4 py-10 flex flex-col items-center text-center gap-2">
|
||||||
<div className="text-sm font-medium text-muted">
|
<div className="text-sm font-medium text-soft">
|
||||||
No segments
|
No segments
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-muted leading-relaxed max-w-[24ch]">
|
<p className="text-sm text-soft leading-relaxed max-w-[24ch]">
|
||||||
Press play in the reader to generate audio segments.
|
Press play in the reader to generate audio segments.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hasLoadedManifest && rowsToRender.length > 0 && (
|
{hasLoadedManifest && rowsToRender.length > 0 && (
|
||||||
<ul className="divide-y divide-offbase">
|
<ul className="divide-y divide-line-soft">
|
||||||
{rowsToRender.map(({ segmentIndex, sentenceText, row, isCurrentLocation, groupKey, groupLabel, isSynthesized }, rowIndex) => {
|
{rowsToRender.map(({ segmentIndex, sentenceText, row, isCurrentLocation, groupKey, groupLabel, isSynthesized }, rowIndex) => {
|
||||||
const previousGroupKey = rowIndex > 0 ? rowsToRender[rowIndex - 1]?.groupKey : null;
|
const previousGroupKey = rowIndex > 0 ? rowsToRender[rowIndex - 1]?.groupKey : null;
|
||||||
const showGroupHeader = previousGroupKey !== groupKey;
|
const showGroupHeader = previousGroupKey !== groupKey;
|
||||||
|
|
@ -744,8 +744,8 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
className={`relative px-4 py-3 ${isCurrent ? 'bg-surface-sunken' : ''}`}
|
className={`relative px-4 py-3 ${isCurrent ? 'bg-surface-sunken' : ''}`}
|
||||||
>
|
>
|
||||||
{showGroupHeader && (
|
{showGroupHeader && (
|
||||||
<div className="mb-2 -mx-4 px-4 py-1.5 bg-surface-sunken border-y border-offbase">
|
<div className="mb-2 -mx-4 px-4 py-1.5 bg-surface-sunken border-y border-line">
|
||||||
<span className="text-[10px] uppercase tracking-[0.14em] text-muted">
|
<span className="text-[10px] uppercase tracking-[0.14em] text-soft">
|
||||||
{groupLabel}
|
{groupLabel}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -758,7 +758,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => { if (canJump) handleJump(segmentIndex, row.locator); }}
|
onClick={() => { if (canJump) handleJump(segmentIndex, row.locator); }}
|
||||||
disabled={!canJump}
|
disabled={!canJump}
|
||||||
className={`text-xs font-medium shrink-0 pt-0.5 ${canJump ? 'text-muted hover:text-accent' : 'text-faint cursor-not-allowed'}`}
|
className={`text-xs font-medium shrink-0 pt-0.5 ${canJump ? 'text-soft hover:text-accent' : 'text-faint cursor-not-allowed'}`}
|
||||||
title={canJump ? (playable ? 'Play this segment' : 'Jump to this segment') : 'Text not loaded yet'}
|
title={canJump ? (playable ? 'Play this segment' : 'Jump to this segment') : 'Text not loaded yet'}
|
||||||
aria-label={`Segment ${segmentIndex + 1}`}
|
aria-label={`Segment ${segmentIndex + 1}`}
|
||||||
>
|
>
|
||||||
|
|
@ -774,7 +774,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
>
|
>
|
||||||
<p className={`text-sm leading-snug ${isCurrent ? 'text-foreground' : 'text-soft'} line-clamp-2`}>
|
<p className={`text-sm leading-snug ${isCurrent ? 'text-foreground' : 'text-soft'} line-clamp-2`}>
|
||||||
{sentenceText || (
|
{sentenceText || (
|
||||||
<span className="text-muted italic text-xs">
|
<span className="text-soft italic text-xs">
|
||||||
[text not loaded — press play to fetch]
|
[text not loaded — press play to fetch]
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -787,7 +787,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
aria-label={`Status ${status}`}
|
aria-label={`Status ${status}`}
|
||||||
title={status}
|
title={status}
|
||||||
/>
|
/>
|
||||||
<span className="text-xs text-muted">
|
<span className="text-xs text-soft">
|
||||||
{formatDuration(activeVariant?.durationMs)}
|
{formatDuration(activeVariant?.durationMs)}
|
||||||
</span>
|
</span>
|
||||||
{isCurrent && isPlaying && (
|
{isCurrent && isPlaying && (
|
||||||
|
|
@ -796,7 +796,7 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{!canJump && (
|
{!canJump && (
|
||||||
<span className="text-[10px] text-faint border border-offbase rounded px-1 py-0.5">
|
<span className="text-[10px] text-faint border border-line rounded px-1 py-0.5">
|
||||||
not loaded
|
not loaded
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -822,8 +822,8 @@ export function SegmentsSidebar({ isOpen, setIsOpen, documentId, epubBookRef }:
|
||||||
isActive
|
isActive
|
||||||
? 'border-accent text-accent bg-surface-sunken'
|
? 'border-accent text-accent bg-surface-sunken'
|
||||||
: known
|
: known
|
||||||
? 'border-offbase text-muted hover:border-accent hover:text-accent'
|
? 'border-line text-soft hover:border-accent hover:text-accent'
|
||||||
: 'border-offbase text-muted opacity-60 cursor-not-allowed',
|
: 'border-line text-soft opacity-60 cursor-not-allowed',
|
||||||
].join(' ')}
|
].join(' ')}
|
||||||
>
|
>
|
||||||
{formatVoiceLabel(variant.settings)}
|
{formatVoiceLabel(variant.settings)}
|
||||||
|
|
@ -854,21 +854,21 @@ function SegmentsListSkeleton() {
|
||||||
return (
|
return (
|
||||||
<div className="px-4 py-3">
|
<div className="px-4 py-3">
|
||||||
<div className="animate-pulse space-y-3" aria-label="Loading segments" aria-busy="true">
|
<div className="animate-pulse space-y-3" aria-label="Loading segments" aria-busy="true">
|
||||||
<div className="h-3 w-40 rounded bg-offbase" />
|
<div className="h-3 w-40 rounded bg-surface-sunken" />
|
||||||
{Array.from({ length: 8 }).map((_, index) => (
|
{Array.from({ length: 8 }).map((_, index) => (
|
||||||
<div key={index} className="rounded-md border border-offbase bg-base px-3 py-2.5">
|
<div key={index} className="rounded-md border border-line bg-surface px-3 py-2.5">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-start gap-3">
|
||||||
<div className="h-3.5 w-8 rounded bg-offbase mt-0.5 shrink-0" />
|
<div className="h-3.5 w-8 rounded bg-surface-sunken mt-0.5 shrink-0" />
|
||||||
<div className="min-w-0 flex-1 space-y-2">
|
<div className="min-w-0 flex-1 space-y-2">
|
||||||
<div className="h-3.5 w-11/12 rounded bg-offbase" />
|
<div className="h-3.5 w-11/12 rounded bg-surface-sunken" />
|
||||||
<div className="h-3.5 w-3/4 rounded bg-offbase" />
|
<div className="h-3.5 w-3/4 rounded bg-surface-sunken" />
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="h-2 w-2 rounded-full bg-offbase" />
|
<div className="h-2 w-2 rounded-full bg-surface-sunken" />
|
||||||
<div className="h-3 w-12 rounded bg-offbase" />
|
<div className="h-3 w-12 rounded bg-surface-sunken" />
|
||||||
<div className="h-4 w-20 rounded bg-offbase" />
|
<div className="h-4 w-20 rounded bg-surface-sunken" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="h-6 w-6 rounded bg-offbase shrink-0" />
|
<div className="h-6 w-6 rounded bg-surface-sunken shrink-0" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
@ -882,7 +882,7 @@ function SegmentsListSkeletonRows() {
|
||||||
<div className="px-4 py-3 animate-pulse" aria-label="Loading more segments" aria-busy="true">
|
<div className="px-4 py-3 animate-pulse" aria-label="Loading more segments" aria-busy="true">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{Array.from({ length: 3 }).map((_, index) => (
|
{Array.from({ length: 3 }).map((_, index) => (
|
||||||
<div key={index} className="h-12 rounded-md border border-offbase bg-base" />
|
<div key={index} className="h-12 rounded-md border border-line bg-surface" />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -895,7 +895,7 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) {
|
||||||
<PopoverButton
|
<PopoverButton
|
||||||
aria-label="Segment metadata"
|
aria-label="Segment metadata"
|
||||||
title="Metadata"
|
title="Metadata"
|
||||||
className="inline-flex items-center justify-center w-7 h-7 rounded-md border border-transparent text-muted hover:bg-offbase hover:border-offbase hover:text-accent transition-colors"
|
className="inline-flex items-center justify-center w-7 h-7 rounded-md border border-transparent text-soft hover:bg-accent-wash hover:border-line hover:text-accent transition-colors"
|
||||||
>
|
>
|
||||||
<InfoIcon className="w-3.5 h-3.5" />
|
<InfoIcon className="w-3.5 h-3.5" />
|
||||||
</PopoverButton>
|
</PopoverButton>
|
||||||
|
|
@ -910,7 +910,7 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) {
|
||||||
>
|
>
|
||||||
<PopoverPanel
|
<PopoverPanel
|
||||||
anchor="bottom end"
|
anchor="bottom end"
|
||||||
className="z-[60] w-[300px] mt-1 rounded-lg border border-offbase bg-base shadow-xl p-3"
|
className="z-[60] w-[300px] mt-1 rounded-lg border border-line bg-surface shadow-elev-3 p-3"
|
||||||
>
|
>
|
||||||
<dl className="space-y-2">
|
<dl className="space-y-2">
|
||||||
<Row label="locator">
|
<Row label="locator">
|
||||||
|
|
@ -925,7 +925,7 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) {
|
||||||
: `${row.locator.readerType || '?'} (legacy)`}
|
: `${row.locator.readerType || '?'} (legacy)`}
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-muted text-[11px]">none</span>
|
<span className="text-soft text-[11px]">none</span>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
<Row label="variants">
|
<Row label="variants">
|
||||||
|
|
@ -934,9 +934,9 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) {
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
{row.variants.map((v) => (
|
{row.variants.map((v) => (
|
||||||
<div key={v.segmentId} className="border-t border-offbase pt-2">
|
<div key={v.segmentId} className="border-t border-line-soft pt-2">
|
||||||
<Row label="segment_id">
|
<Row label="segment_id">
|
||||||
<span className="font-mono text-[10px] text-muted break-all">
|
<span className="font-mono text-[10px] text-soft break-all">
|
||||||
{v.segmentId.slice(0, 16)}…
|
{v.segmentId.slice(0, 16)}…
|
||||||
</span>
|
</span>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
@ -972,7 +972,7 @@ function SegmentMetadataPopover({ row }: { row: TTSSegmentRow }) {
|
||||||
function Row({ label, children }: { label: string; children: ReactNode }) {
|
function Row({ label, children }: { label: string; children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-[80px_1fr] gap-2 items-baseline">
|
<div className="grid grid-cols-[80px_1fr] gap-2 items-baseline">
|
||||||
<dt className="font-mono uppercase tracking-[0.16em] text-[9px] text-muted">{label}</dt>
|
<dt className="font-mono uppercase tracking-[0.16em] text-[9px] text-soft">{label}</dt>
|
||||||
<dd className="min-w-0">{children}</dd>
|
<dd className="min-w-0">{children}</dd>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -158,12 +158,12 @@ export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`h-full flex flex-col relative z-0 ${className}`} ref={containerRef}>
|
<div className={`h-full flex flex-col relative z-0 ${className}`} ref={containerRef}>
|
||||||
<div className="flex items-center justify-between px-2 py-1 border-b border-offbase bg-base text-xs text-muted">
|
<div className="flex items-center justify-between px-2 py-1 border-b border-line-soft bg-surface text-xs text-soft">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setIsTocOpen(open => !open)}
|
onClick={() => setIsTocOpen(open => !open)}
|
||||||
className="inline-flex items-center py-1 px-1 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard transform hover:text-accent"
|
className="inline-flex items-center py-1 px-1 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard transform hover:text-accent"
|
||||||
aria-label={isTocOpen ? 'Hide chapters' : 'Show chapters'}
|
aria-label={isTocOpen ? 'Hide chapters' : 'Show chapters'}
|
||||||
>
|
>
|
||||||
<DotsVerticalIcon className="w-4 h-4" />
|
<DotsVerticalIcon className="w-4 h-4" />
|
||||||
|
|
@ -171,7 +171,7 @@ export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleLocationChanged('prev')}
|
onClick={() => handleLocationChanged('prev')}
|
||||||
className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard transform hover:text-accent"
|
className="inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard transform hover:text-accent"
|
||||||
aria-label="Previous section"
|
aria-label="Previous section"
|
||||||
>
|
>
|
||||||
<ChevronLeftIcon className="w-4 h-4" />
|
<ChevronLeftIcon className="w-4 h-4" />
|
||||||
|
|
@ -185,15 +185,15 @@ export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => handleLocationChanged('next')}
|
onClick={() => handleLocationChanged('next')}
|
||||||
className="inline-flex items-center py-1 px-2 rounded-md border border-offbase bg-base text-foreground text-xs hover:bg-offbase transition duration-base ease-standard transform hover:text-accent"
|
className="inline-flex items-center py-1 px-2 rounded-md border border-line bg-surface text-foreground text-xs hover:bg-accent-wash transition duration-base ease-standard transform hover:text-accent"
|
||||||
aria-label="Next section"
|
aria-label="Next section"
|
||||||
>
|
>
|
||||||
<ChevronRightIcon className="w-4 h-4" />
|
<ChevronRightIcon className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{isTocOpen && tocRef.current && tocRef.current.length > 0 && (
|
{isTocOpen && tocRef.current && tocRef.current.length > 0 && (
|
||||||
<div className="border-b border-offbase bg-background text-xs overflow-y-auto max-h-64 p-2">
|
<div className="border-b border-line-soft bg-background text-xs overflow-y-auto max-h-64 p-2">
|
||||||
<div className="font-semibold text-muted pb-1">Skip to chapters</div>
|
<div className="font-semibold text-soft pb-1">Skip to chapters</div>
|
||||||
<div className="flex flex-wrap gap-1 w-full">
|
<div className="flex flex-wrap gap-1 w-full">
|
||||||
{tocRef.current.map((item, index) => (
|
{tocRef.current.map((item, index) => (
|
||||||
<button
|
<button
|
||||||
|
|
@ -204,9 +204,9 @@ export function EPUBViewer({ className = '', epubState }: EPUBViewerProps) {
|
||||||
setIsTocOpen(false);
|
setIsTocOpen(false);
|
||||||
}}
|
}}
|
||||||
className="
|
className="
|
||||||
px-2 py-1 rounded-md font-medium text-foreground text-center bg-base
|
px-2 py-1 rounded-md font-medium text-foreground text-center bg-surface
|
||||||
hover:bg-offbase hover:text-accent transition-colors duration-fast
|
hover:bg-accent-wash hover:text-accent transition-colors duration-fast
|
||||||
whitespace-nowrap
|
whitespace-nowrap
|
||||||
flex-1 min-w-[140px]
|
flex-1 min-w-[140px]
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -480,7 +480,7 @@ export function PDFViewer({ zoomLevel, onDocumentReady, pdfState }: PDFViewerPro
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={`${block.id}:${fragment.page}:${fragment.readingOrder}`}
|
key={`${block.id}:${fragment.page}:${fragment.readingOrder}`}
|
||||||
className="absolute border border-accent-line rounded-[2px]"
|
className="absolute border border-accent-line rounded-sm"
|
||||||
style={{
|
style={{
|
||||||
left: `${leftPct}%`,
|
left: `${leftPct}%`,
|
||||||
top: `${topPct}%`,
|
top: `${topPct}%`,
|
||||||
|
|
@ -536,7 +536,7 @@ export function PDFViewer({ zoomLevel, onDocumentReady, pdfState }: PDFViewerPro
|
||||||
pageNumber={i + 1}
|
pageNumber={i + 1}
|
||||||
renderAnnotationLayer={true}
|
renderAnnotationLayer={true}
|
||||||
renderTextLayer={i + 1 === currDocPage}
|
renderTextLayer={i + 1 === currDocPage}
|
||||||
className="shadow-lg"
|
className="shadow-elev-2"
|
||||||
scale={currentScale()}
|
scale={currentScale()}
|
||||||
onRenderSuccess={() => {
|
onRenderSuccess={() => {
|
||||||
lastRenderedLayoutKeyRef.current = layoutKey;
|
lastRenderedLayoutKeyRef.current = layoutKey;
|
||||||
|
|
@ -562,7 +562,7 @@ export function PDFViewer({ zoomLevel, onDocumentReady, pdfState }: PDFViewerPro
|
||||||
pageNumber={leftPage}
|
pageNumber={leftPage}
|
||||||
renderAnnotationLayer={true}
|
renderAnnotationLayer={true}
|
||||||
renderTextLayer={leftPage === currDocPage}
|
renderTextLayer={leftPage === currDocPage}
|
||||||
className="shadow-lg"
|
className="shadow-elev-2"
|
||||||
scale={currentScale()}
|
scale={currentScale()}
|
||||||
onRenderSuccess={() => {
|
onRenderSuccess={() => {
|
||||||
lastRenderedLayoutKeyRef.current = layoutKey;
|
lastRenderedLayoutKeyRef.current = layoutKey;
|
||||||
|
|
@ -584,7 +584,7 @@ export function PDFViewer({ zoomLevel, onDocumentReady, pdfState }: PDFViewerPro
|
||||||
pageNumber={rightPage}
|
pageNumber={rightPage}
|
||||||
renderAnnotationLayer={true}
|
renderAnnotationLayer={true}
|
||||||
renderTextLayer={rightPage === currDocPage}
|
renderTextLayer={rightPage === currDocPage}
|
||||||
className="shadow-lg"
|
className="shadow-elev-2"
|
||||||
scale={currentScale()}
|
scale={currentScale()}
|
||||||
onRenderSuccess={() => {
|
onRenderSuccess={() => {
|
||||||
lastRenderedLayoutKeyRef.current = layoutKey;
|
lastRenderedLayoutKeyRef.current = layoutKey;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue