Compare commits

...

2 Commits

Author SHA1 Message Date
root
c91dd20c8c refactor: clean up profile page UI — remove decorative icons
All checks were successful
Release / Test backend (push) Successful in 48s
Release / Check ui (push) Successful in 1m51s
Release / Docker (push) Successful in 6m21s
Release / Gitea Release (push) Successful in 36s
Remove all decorative SVG icons (checkmarks, chevrons, stars, fire,
external-link arrows, empty-state illustrations). Replace icon-only
interactive elements with text (avatar hover shows 'Edit', voice sample
buttons show 'Play'/'Stop', danger zone toggle shows 'Open'/'Close').
Replace SVG avatar placeholder with the user's initial. Strip emoji
from stats cards and genre chips. Tighten playback toggle descriptions.
2026-04-11 10:21:14 +05:00
root
3b24f4560f feat: add OG/Twitter meta tags on book and chapter pages
All checks were successful
Release / Test backend (push) Successful in 45s
Release / Check ui (push) Successful in 1m53s
Release / Docker (push) Successful in 6m13s
Release / Gitea Release (push) Successful in 37s
Add og:title, og:description, og:image (book cover), og:url, og:type,
og:site_name, twitter:card, twitter:image, and rel=canonical to the
book detail and chapter reader pages so link previews in Telegram,
WhatsApp, Twitter/X, Discord etc. show the cover image instead of
the site logo.
2026-04-11 09:35:21 +05:00
3 changed files with 84 additions and 104 deletions

View File

@@ -1,6 +1,7 @@
<script lang="ts">
import { onMount, untrack } from 'svelte';
import { goto, invalidateAll } from '$app/navigation';
import { page } from '$app/state';
import type { PageData } from './$types';
import CommentsSection from '$lib/components/CommentsSection.svelte';
import StarRating from '$lib/components/StarRating.svelte';
@@ -660,6 +661,28 @@
<svelte:head>
<title>{data.scraping ? m.book_detail_scraping() : (data.book?.title ?? 'Book')} — libnovel</title>
{#if data.book && !data.scraping}
{@const ogTitle = `${data.book.title} libnovel`}
{@const ogDesc = data.book.summary ? (data.book.summary.length > 200 ? data.book.summary.slice(0, 200) + '…' : data.book.summary) : `${data.book.total_chapters} chapters · ${data.book.author}`}
{@const ogUrl = `${page.url.origin}/books/${data.book.slug}`}
<link rel="canonical" href={ogUrl} />
<meta property="og:type" content="book" />
<meta property="og:site_name" content="libnovel" />
<meta property="og:url" content={ogUrl} />
<meta property="og:title" content={ogTitle} />
<meta property="og:description" content={ogDesc} />
{#if data.book.cover}
<meta property="og:image" content={data.book.cover} />
<meta property="og:image:width" content="300" />
<meta property="og:image:height" content="450" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={data.book.cover} />
{:else}
<meta name="twitter:card" content="summary" />
{/if}
<meta name="twitter:title" content={ogTitle} />
<meta name="twitter:description" content={ogDesc} />
{/if}
</svelte:head>
{#if data.scraping}

View File

@@ -332,6 +332,23 @@
<svelte:head>
<title>{cleanTitle}{data.book.title} — libnovel</title>
<link rel="canonical" href="{page.url.origin}/books/{data.book.slug}/chapters/{data.chapter.number}" />
<meta property="og:type" content="book" />
<meta property="og:site_name" content="libnovel" />
<meta property="og:url" content="{page.url.origin}/books/{data.book.slug}/chapters/{data.chapter.number}" />
<meta property="og:title" content="{cleanTitle}{data.book.title} — libnovel" />
<meta property="og:description" content="Chapter {data.chapter.number} of {data.book.title}" />
{#if data.book.cover}
<meta property="og:image" content={data.book.cover} />
<meta property="og:image:width" content="300" />
<meta property="og:image:height" content="450" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={data.book.cover} />
{:else}
<meta name="twitter:card" content="summary" />
{/if}
<meta name="twitter:title" content="{cleanTitle}{data.book.title} — libnovel" />
<meta name="twitter:description" content="Chapter {data.chapter.number} of {data.book.title}" />
</svelte:head>
<!-- Reading progress bar (scroll mode, fixed at top of viewport) -->

View File

@@ -192,7 +192,6 @@
function markSaved() {
saveStatus = 'saving';
clearTimeout(savedTimer);
// Give a tick for layout's effect to fire, then show ✓ Saved
savedTimer = setTimeout(() => {
saveStatus = 'saved';
savedTimer = setTimeout(() => (saveStatus = 'idle'), 2000) as unknown as number;
@@ -218,11 +217,10 @@
}
if (!initialized) { initialized = true; return; }
void v; void sp; void an; void ac; void am; // keep subscriptions live
void v; void sp; void an; void ac; void am;
markSaved();
});
// Keep theme/font writes flowing into layout context when changed from selectors
$effect(() => { if (settingsCtx) settingsCtx.current = selectedTheme; });
$effect(() => { if (settingsCtx) settingsCtx.fontFamily = selectedFontFamily; });
$effect(() => { if (settingsCtx) settingsCtx.fontSize = selectedFontSize; });
@@ -283,7 +281,6 @@
deleteError = body.message ?? `Delete failed (${res.status}). Please try again.`;
return;
}
// Server deleted account — submit logout form to clear session cookie
const logoutForm = document.getElementById('logout-form') as HTMLFormElement | null;
if (logoutForm) logoutForm.submit();
} catch {
@@ -331,12 +328,9 @@
<!-- ── Post-checkout success banner ──────────────────────────────────────── -->
{#if justSubscribed}
<div class="rounded-xl bg-(--color-brand)/10 border border-(--color-brand)/40 px-5 py-4 flex items-start gap-3">
<svg class="w-5 h-5 text-(--color-brand) shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
<div>
<p class="text-sm font-semibold text-(--color-brand)">Welcome to Pro!</p>
<p class="text-sm text-(--color-muted) mt-0.5">Your subscription is being activated. Refresh the page in a moment if the Pro badge doesn't appear yet.</p>
</div>
<div class="rounded-xl bg-(--color-brand)/10 border border-(--color-brand)/40 px-5 py-4">
<p class="text-sm font-semibold text-(--color-brand)">Welcome to Pro!</p>
<p class="text-sm text-(--color-muted) mt-0.5">Your subscription is being activated. Refresh the page in a moment if the Pro badge doesn't appear yet.</p>
</div>
{/if}
@@ -353,9 +347,9 @@
<img src={avatarUrl} alt="Profile" class="w-full h-full object-cover" />
{:else}
<div class="w-full h-full bg-(--color-surface-3) flex items-center justify-center">
<svg class="w-10 h-10 text-(--color-muted)" fill="currentColor" viewBox="0 0 24 24">
<path d="M12 12c2.7 0 4.8-2.1 4.8-4.8S14.7 2.4 12 2.4 7.2 4.5 7.2 7.2 9.3 12 12 12zm0 2.4c-3.2 0-9.6 1.6-9.6 4.8v2.4h19.2v-2.4c0-3.2-6.4-4.8-9.6-4.8z"/>
</svg>
<span class="text-3xl font-bold text-(--color-muted) select-none">
{data.user.username.slice(0, 1).toUpperCase()}
</span>
</div>
{/if}
<div class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
@@ -365,10 +359,7 @@
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"></path>
</svg>
{:else}
<svg class="w-5 h-5 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"/>
</svg>
<span class="text-xs font-semibold text-white tracking-wide">Edit</span>
{/if}
</div>
</button>
@@ -380,8 +371,7 @@
<div class="flex items-center gap-2 mt-1 flex-wrap">
<span class="text-xs font-semibold px-2 py-0.5 rounded-full bg-(--color-surface-3) text-(--color-muted) capitalize border border-(--color-border)">{data.user.role}</span>
{#if data.isPro}
<span class="inline-flex items-center gap-1 text-xs font-bold px-2 py-0.5 rounded-full bg-(--color-brand)/15 text-(--color-brand) border border-(--color-brand)/30 uppercase tracking-wide">
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
<span class="text-xs font-bold px-2 py-0.5 rounded-full bg-(--color-brand)/15 text-(--color-brand) border border-(--color-brand)/30 uppercase tracking-wide">
{m.profile_plan_pro()}
</span>
{/if}
@@ -440,8 +430,6 @@
class="inline-flex items-center gap-2 px-4 py-2 rounded-lg bg-(--color-brand) text-(--color-surface) font-semibold text-sm hover:bg-(--color-brand-dim) transition-colors disabled:opacity-60 disabled:cursor-wait">
{#if checkoutLoading === 'monthly'}
<svg class="w-4 h-4 shrink-0 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/></svg>
{:else}
<svg class="w-4 h-4 shrink-0" fill="currentColor" viewBox="0 0 24 24"><path d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/></svg>
{/if}
{m.profile_upgrade_monthly()}
</button>
@@ -467,9 +455,8 @@
<p class="text-sm text-(--color-muted) mt-0.5">{m.profile_pro_perks()}</p>
</div>
<a href={manageUrl} target="_blank" rel="noopener noreferrer"
class="shrink-0 inline-flex items-center gap-1.5 text-sm font-medium text-(--color-brand) hover:underline">
{m.profile_manage_subscription()}
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"/></svg>
class="shrink-0 text-sm font-medium text-(--color-brand) hover:underline">
{m.profile_manage_subscription()}
</a>
</section>
{/if}
@@ -562,7 +549,7 @@
</div>
</div>
<!-- TTS voice — visual card picker grouped by engine -->
<!-- TTS voice -->
<div class="px-6 py-5 space-y-3">
<p class="text-sm font-medium text-(--color-text)">{m.profile_tts_voice()}</p>
{#if !voicesLoaded}
@@ -574,7 +561,6 @@
{:else if voices.length === 0}
<p class="text-sm text-(--color-muted) italic">No voices available.</p>
{:else}
<!-- Engine groups -->
{#each [
{ label: 'Kokoro (GPU)', voices: kokoroVoices },
{ label: 'Pocket TTS (CPU)', voices: pocketVoices },
@@ -586,7 +572,6 @@
{#each group.voices as v (v.id)}
{@const isSelected = audioStore.voice === v.id}
{@const isPlaying = samplePlayingVoice === v.id}
<!-- Use role=option div to avoid nested <button> inside <button> -->
<div
role="option"
aria-selected={isSelected}
@@ -594,43 +579,25 @@
onclick={() => { audioStore.voice = v.id; }}
onkeydown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); audioStore.voice = v.id; } }}
class={cn(
'flex items-center justify-between gap-2 px-3 py-2 rounded-lg border text-sm transition-colors cursor-pointer select-none',
'flex items-center justify-between gap-2 px-3 py-2.5 rounded-lg border text-sm transition-colors cursor-pointer select-none',
isSelected
? 'border-(--color-brand) bg-(--color-brand)/10 text-(--color-brand)'
: 'border-(--color-border) bg-(--color-surface-3) text-(--color-text) hover:border-(--color-brand)/40'
)}
>
<div class="flex items-center gap-2 min-w-0">
{#if isSelected}
<svg class="w-3.5 h-3.5 shrink-0 text-(--color-brand)" fill="currentColor" viewBox="0 0 24 24">
<path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/>
</svg>
{:else}
<span class="w-3.5 h-3.5 shrink-0"></span>
{/if}
<span class="truncate font-medium">{voiceLabel(v)}</span>
</div>
<!-- Sample play button -->
<span class="truncate font-medium">{voiceLabel(v)}</span>
<button
type="button"
onclick={(e) => { e.stopPropagation(); toggleSample(v.id); }}
class={cn(
'shrink-0 w-6 h-6 rounded-full flex items-center justify-center transition-colors',
'shrink-0 px-2 py-0.5 rounded text-xs font-medium transition-colors',
isPlaying
? 'bg-(--color-brand) text-(--color-surface)'
: 'bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text) hover:bg-(--color-surface-3)'
: 'bg-(--color-surface-2) text-(--color-muted) hover:text-(--color-text)'
)}
title={isPlaying ? 'Stop sample' : 'Play sample'}
>
{#if isPlaying}
<svg class="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
<path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/>
</svg>
{:else}
<svg class="w-3 h-3 ml-0.5" fill="currentColor" viewBox="0 0 24 24">
<path d="M8 5v14l11-7z"/>
</svg>
{/if}
{isPlaying ? 'Stop' : 'Play'}
</button>
</div>
{/each}
@@ -654,15 +621,15 @@
</div>
</div>
<!-- Playback toggles row -->
<div class="px-6 py-5 space-y-4">
<!-- Playback toggles -->
<div class="px-6 py-5 space-y-5">
<p class="text-sm font-medium text-(--color-text)">Playback</p>
<!-- Auto-advance -->
<div class="flex items-center justify-between">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-sm text-(--color-text)">{m.profile_auto_advance()}</p>
<p class="text-xs text-(--color-muted) mt-0.5">Automatically load the next chapter when audio finishes</p>
<p class="text-xs text-(--color-muted) mt-0.5">Load the next chapter automatically when audio ends</p>
</div>
<button
type="button"
@@ -680,10 +647,10 @@
</div>
<!-- Announce chapter -->
<div class="flex items-center justify-between">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-sm text-(--color-text)">Announce chapter</p>
<p class="text-xs text-(--color-muted) mt-0.5">Read the chapter title aloud before auto-advancing</p>
<p class="text-xs text-(--color-muted) mt-0.5">Read the chapter title aloud before advancing</p>
</div>
<button
type="button"
@@ -701,22 +668,18 @@
</div>
<!-- Audio mode -->
<div class="flex items-center justify-between">
<div class="flex items-center justify-between gap-4">
<div>
<p class="text-sm text-(--color-text)">Audio mode</p>
<p class="text-xs text-(--color-muted) mt-0.5">
{#if audioStore.audioMode === 'stream'}
<strong class="text-(--color-text)">Stream</strong> — audio starts within seconds, saved in background
{:else}
<strong class="text-(--color-text)">Generate</strong> — wait for full audio before playing
{/if}
{audioStore.audioMode === 'stream' ? 'Stream — starts within seconds' : 'Generate — waits for full audio'}
{#if audioStore.voice.startsWith('cfai:')} <span class="text-(--color-border)">(not available for CF AI)</span>{/if}
</p>
</div>
<button
type="button"
onclick={() => {
audioStore.audioMode = audioStore.audioMode === 'stream' ? 'generate' : 'stream';
}}
aria-label="Toggle audio mode"
onclick={() => { audioStore.audioMode = audioStore.audioMode === 'stream' ? 'generate' : 'stream'; }}
disabled={audioStore.voice.startsWith('cfai:')}
class={cn(
'shrink-0 relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-(--color-brand) focus:ring-offset-2 focus:ring-offset-(--color-surface)',
@@ -726,7 +689,6 @@
? 'bg-(--color-brand)'
: 'bg-(--color-surface-3) border border-(--color-border)'
)}
title={audioStore.voice.startsWith('cfai:') ? 'CF AI voices always use generate mode' : undefined}
>
<span class={cn(
'inline-block h-4 w-4 transform rounded-full bg-white shadow transition-transform',
@@ -806,12 +768,7 @@
<p class="text-sm font-semibold text-red-400">Danger zone</p>
<p class="text-xs text-(--color-muted) mt-0.5">Irreversible actions — proceed with care</p>
</div>
<svg
class={cn('w-4 h-4 text-(--color-muted) transition-transform', deleteConfirmOpen && 'rotate-180')}
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
<span class="text-xs text-(--color-muted)">{deleteConfirmOpen ? 'Close' : 'Open'}</span>
</button>
{#if deleteConfirmOpen}
@@ -851,9 +808,6 @@
<svg class="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v8H4z"/></svg>
Deleting…
{:else}
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
</svg>
Delete my account
{/if}
</button>
@@ -871,10 +825,10 @@
<h2 class="text-sm font-semibold text-(--color-muted) uppercase tracking-wider mb-4">Reading Overview</h2>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-3">
{#each [
{ label: 'Chapters Read', value: data.stats.totalChaptersRead, icon: '📖' },
{ label: 'Completed', value: data.stats.booksCompleted, icon: '✅' },
{ label: 'Reading', value: data.stats.booksReading, icon: '📚' },
{ label: 'Plan to Read', value: data.stats.booksPlanToRead, icon: '🔖' },
{ label: 'Chapters Read', value: data.stats.totalChaptersRead },
{ label: 'Completed', value: data.stats.booksCompleted },
{ label: 'Reading', value: data.stats.booksReading },
{ label: 'Plan to Read', value: data.stats.booksPlanToRead },
] as stat}
<div class="bg-(--color-surface-3) rounded-lg p-3 text-center">
<p class="text-2xl font-bold text-(--color-text) tabular-nums">{stat.value}</p>
@@ -888,21 +842,15 @@
<section class="bg-(--color-surface-2) rounded-xl border border-(--color-border) p-5">
<h2 class="text-sm font-semibold text-(--color-muted) uppercase tracking-wider mb-4">Activity</h2>
<div class="grid grid-cols-2 gap-3">
<div class="flex items-center gap-3 bg-(--color-surface-3) rounded-lg p-3">
<div class="w-9 h-9 rounded-full bg-orange-500/15 flex items-center justify-center text-lg flex-shrink-0">🔥</div>
<div>
<p class="text-xl font-bold text-(--color-text) tabular-nums">{data.stats.streak}</p>
<p class="text-xs text-(--color-muted)">day streak</p>
</div>
<div class="bg-(--color-surface-3) rounded-lg p-4">
<p class="text-2xl font-bold text-(--color-text) tabular-nums">{data.stats.streak}</p>
<p class="text-xs text-(--color-muted) mt-1">day streak</p>
</div>
<div class="flex items-center gap-3 bg-(--color-surface-3) rounded-lg p-3">
<div class="w-9 h-9 rounded-full bg-yellow-500/15 flex items-center justify-center text-lg flex-shrink-0"></div>
<div>
<p class="text-xl font-bold text-(--color-text) tabular-nums">
{data.stats.avgRatingGiven > 0 ? data.stats.avgRatingGiven.toFixed(1) : '—'}
</p>
<p class="text-xs text-(--color-muted)">avg rating given</p>
</div>
<div class="bg-(--color-surface-3) rounded-lg p-4">
<p class="text-2xl font-bold text-(--color-text) tabular-nums">
{data.stats.avgRatingGiven > 0 ? data.stats.avgRatingGiven.toFixed(1) : '—'}
</p>
<p class="text-xs text-(--color-muted) mt-1">avg rating given</p>
</div>
</div>
</section>
@@ -914,12 +862,11 @@
<div class="flex flex-wrap gap-2">
{#each data.stats.topGenres as genre, i}
<span class={cn(
'flex items-center gap-1.5 px-3 py-1.5 rounded-full text-sm font-medium',
'px-3 py-1.5 rounded-full text-sm font-medium',
i === 0
? 'bg-(--color-brand)/20 text-(--color-brand) border border-(--color-brand)/30'
: 'bg-(--color-surface-3) text-(--color-text) border border-(--color-border)'
)}>
{#if i === 0}<span class="text-xs">🏆</span>{/if}
{genre}
</span>
{/each}
@@ -940,10 +887,7 @@
{#if activeTab === 'history'}
<div class="space-y-2">
{#if data.history.length === 0}
<div class="py-12 text-center text-(--color-muted)">
<svg class="w-10 h-10 mx-auto mb-3 text-(--color-border)" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<div class="py-16 text-center text-(--color-muted)">
<p class="text-sm">No reading history yet.</p>
</div>
{:else}
@@ -956,11 +900,7 @@
{#if item.cover}
<img src={item.cover} alt={item.title} class="w-full h-full object-cover" loading="lazy" />
{:else}
<div class="w-full h-full flex items-center justify-center text-(--color-muted)">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" />
</svg>
</div>
<div class="w-full h-full bg-(--color-surface-3)"></div>
{/if}
</div>
<div class="flex-1 min-w-0">