Compare commits

..

1 Commits

Author SHA1 Message Date
Marcel
53b482c5f2 fix(e2e): fix admin tag test (use existing tag) and annotation locator
Some checks failed
CI / Unit & Component Tests (push) Successful in 2m37s
CI / Backend Unit Tests (push) Successful in 2m18s
CI / E2E Tests (push) Failing after 30m45s
- Admin tag test: "Familie" never existed in the database; use "Fest"
  which is a real seeded tag, with a matching rename-back to restore state
- Annotation hash test: the broad `[data-testid^="annotation-"]` locator
  also matched `annotation-side-panel` (always in DOM, even when
  off-screen); extend the :not() exclusion list to cover it

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 11:34:42 +01:00
37 changed files with 219 additions and 389 deletions

View File

@@ -180,19 +180,19 @@ test.describe('Admin — tag management', () => {
// Wait for the tags list to render after the tab switch
await page.waitForSelector('ul > li');
// Hover over the "Familie" row to reveal the opacity-0 action buttons
const familieRow = page
// Hover over the "Fest" row to reveal the opacity-0 action buttons
const festRow = page
.locator('ul > li')
.filter({ has: page.locator('span', { hasText: /^Familie$/ }) });
await familieRow.hover();
await familieRow.getByRole('button', { name: 'Schlagwort bearbeiten' }).click();
.filter({ has: page.locator('span', { hasText: /^Fest$/ }) });
await festRow.hover();
await festRow.getByRole('button', { name: 'Schlagwort bearbeiten' }).click();
// After clicking edit, {#if editingTagId} replaces the span with a form —
// the familieRow filter no longer matches, so we find the input directly.
await page.locator('input[name="name"]').fill('Familie (E2E)');
// the festRow filter no longer matches, so we find the input directly.
await page.locator('input[name="name"]').fill('Fest (E2E)');
await page.getByRole('button', { name: 'Speichern' }).click();
await expect(page.getByText('Familie (E2E)')).toBeVisible();
await expect(page.getByText('Fest (E2E)')).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/admin-tag-renamed.png' });
});
@@ -205,14 +205,14 @@ test.describe('Admin — tag management', () => {
const renamedRow = page
.locator('ul > li')
.filter({ has: page.locator('span', { hasText: /^Familie \(E2E\)$/ }) });
.filter({ has: page.locator('span', { hasText: /^Fest \(E2E\)$/ }) });
await renamedRow.hover();
await renamedRow.getByRole('button', { name: 'Schlagwort bearbeiten' }).click();
await page.locator('input[name="name"]').fill('Familie');
await page.locator('input[name="name"]').fill('Fest');
await page.getByRole('button', { name: 'Speichern' }).click();
await expect(page.getByText('Familie')).toBeVisible();
await expect(page.getByText('Fest')).toBeVisible();
await page.screenshot({ path: 'test-results/e2e/admin-tag-restored.png' });
});
});

View File

@@ -461,9 +461,11 @@ test.describe('PDF annotations — file hash versioning', () => {
await page.waitForSelector('[data-hydrated]');
await page.locator('canvas').first().waitFor({ state: 'visible', timeout: 20000 });
// Use :not() to exclude the outdated-notice element whose testid also starts with "annotation-"
// Use :not() to exclude the outdated-notice and side-panel elements whose testid also starts with "annotation-"
await expect(
page.locator('[data-testid^="annotation-"]:not([data-testid="annotation-outdated-notice"])')
page.locator(
'[data-testid^="annotation-"]:not([data-testid="annotation-outdated-notice"]):not([data-testid="annotation-side-panel"])'
)
).toHaveCount(0, { timeout: 8000 });
await expect(page.locator('[data-testid="annotation-outdated-notice"]')).toBeVisible({
timeout: 5000

View File

@@ -188,7 +188,7 @@ onMount(() => {
></textarea>
<div class="flex items-center gap-3">
<button
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-white hover:bg-primary/80 disabled:opacity-40"
disabled={posting}
onclick={() => saveEdit(comment.id)}
>
@@ -291,7 +291,7 @@ onMount(() => {
></textarea>
<div class="flex items-center gap-3">
<button
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-white hover:bg-primary/80 disabled:opacity-40"
disabled={posting}
onclick={() => postReply(thread.id)}
>
@@ -321,7 +321,7 @@ onMount(() => {
></textarea>
<div>
<button
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg hover:bg-primary/80 disabled:opacity-40"
class="rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-white hover:bg-primary/80 disabled:opacity-40"
disabled={posting || !newText.trim()}
onclick={postComment}
>

View File

@@ -107,7 +107,7 @@ function handleCountChange(count: number) {
</script>
<div
class="z-30 flex shrink-0 flex-col border-t border-line bg-surface shadow-[0_-4px_16px_rgba(0,0,0,0.08)]"
class="fixed right-0 bottom-0 left-0 z-30 flex flex-col border-t border-line bg-surface shadow-[0_-4px_16px_rgba(0,0,0,0.08)]"
style="height: {panelHeight}px"
data-testid="bottom-panel"
>
@@ -127,37 +127,35 @@ function handleCountChange(count: number) {
</div>
<!-- Tab bar -->
<div class="flex shrink-0 items-center border-b border-line bg-surface">
<!-- Scrollable tabs area — hides scrollbar visually -->
<div
class="flex flex-1 items-center overflow-x-auto px-2 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
>
{#each tabs as tab (tab.id)}
<button
onclick={() => openTab(tab.id)}
class="mr-1 shrink-0 px-3 py-2.5 font-sans text-xs font-medium transition-colors {activeTab === tab.id && open
? 'border-b-2 border-primary text-ink'
: 'text-ink-3 hover:text-ink'}"
aria-pressed={activeTab === tab.id && open}
>
{tab.label()}
{#if tab.id === 'discussion'}
<span
data-testid="discussion-count-badge"
class="ml-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 font-sans text-[10px] font-bold text-primary-fg"
>{discussionCount}</span
>
{/if}
</button>
{/each}
</div>
<div class="flex shrink-0 items-center border-b border-line bg-surface px-4">
{#each tabs as tab (tab.id)}
<button
onclick={() => openTab(tab.id)}
class="mr-1 px-3 py-2.5 font-sans text-xs font-medium transition-colors {activeTab === tab.id && open
? 'border-b-2 border-primary text-ink'
: 'text-ink-3 hover:text-ink'}"
aria-pressed={activeTab === tab.id && open}
>
{tab.label()}
{#if tab.id === 'discussion'}
<span
data-testid="discussion-count-badge"
class="ml-1.5 inline-flex h-4 min-w-4 items-center justify-center rounded-full bg-primary px-1 font-sans text-[10px] font-bold text-primary-fg"
>{discussionCount}</span
>
{/if}
</button>
{/each}
<!-- spacer -->
<div class="flex-1"></div>
{#if open}
<button
onclick={closePanel}
data-testid="panel-close-btn"
aria-label="Panel schließen"
class="mr-2 shrink-0 rounded p-1.5 text-ink-3 transition-colors hover:bg-muted hover:text-ink"
class="rounded p-1.5 text-ink-3 transition-colors hover:bg-muted hover:text-ink"
>
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />

View File

@@ -58,7 +58,7 @@ const compactMeta = $derived.by(() => {
</script>
<div
class="z-20 flex shrink-0 items-center justify-between border-b border-line bg-surface px-3 py-3 shadow-sm sm:px-6"
class="z-20 flex shrink-0 items-center justify-between border-b border-line bg-surface px-6 py-3 shadow-sm"
data-topbar
>
<!-- Left: back + title -->
@@ -102,8 +102,8 @@ const compactMeta = $derived.by(() => {
onclick={() => (annotateMode = !annotateMode)}
aria-label={annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}
class="flex items-center gap-1.5 rounded px-3 py-1.5 font-sans text-xs font-medium transition {annotateMode
? 'bg-primary text-primary-fg'
: 'border border-primary text-ink hover:bg-primary hover:text-primary-fg'}"
? 'bg-primary text-white'
: 'border border-primary text-ink hover:bg-primary hover:text-white'}"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Note/Note-Add-MD.svg"
@@ -111,17 +111,14 @@ const compactMeta = $derived.by(() => {
aria-hidden="true"
class="h-4 w-4 {annotateMode ? 'invert' : ''}"
/>
<span class="hidden sm:inline"
>{annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}</span
>
{annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}
</button>
{/if}
{#if canWrite}
<a
href="/documents/{doc.id}/edit"
aria-label={m.btn_edit()}
class="flex items-center gap-2 rounded border border-primary bg-transparent px-3 py-1.5 text-xs font-medium text-ink transition hover:bg-primary hover:text-primary-fg"
class="flex items-center gap-2 rounded border border-primary bg-transparent px-3 py-1.5 text-xs font-medium text-ink transition hover:bg-primary hover:text-white"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Edit-Content-MD.svg"
@@ -129,7 +126,7 @@ const compactMeta = $derived.by(() => {
aria-hidden="true"
class="h-4 w-4"
/>
<span class="hidden sm:inline">{m.btn_edit()}</span>
{m.btn_edit()}
</a>
{/if}

View File

@@ -328,7 +328,7 @@ $effect(() => {
<button
onclick={applyCompare}
disabled={!compareA || !compareB || compareA === compareB}
class="w-full rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-primary-fg transition hover:bg-primary/80 disabled:cursor-not-allowed disabled:opacity-40"
class="w-full rounded bg-primary px-3 py-1.5 font-sans text-xs font-medium text-white transition hover:bg-primary/80 disabled:cursor-not-allowed disabled:opacity-40"
>
{m.history_compare_apply()}
</button>

View File

@@ -100,7 +100,7 @@ let { doc }: { doc: Doc } = $props();
{#each doc.tags as tag (tag.id)}
<a
href="/?tag={encodeURIComponent(tag.name)}"
class="inline-flex items-center rounded bg-muted px-2 py-0.5 text-xs font-bold tracking-wide text-ink uppercase transition-colors hover:bg-primary hover:text-primary-fg"
class="inline-flex items-center rounded bg-muted px-2 py-0.5 text-xs font-bold tracking-wide text-ink uppercase transition-colors hover:bg-primary hover:text-white"
title={m.doc_tag_filter_title({ name: tag.name })}
>
{tag.name}
@@ -131,7 +131,7 @@ let { doc }: { doc: Doc } = $props();
>
<div class="flex items-center gap-3">
<div
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary font-serif text-sm text-primary-fg"
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary font-serif text-sm text-white"
>
{doc.sender.firstName[0]}{doc.sender.lastName[0]}
</div>

View File

@@ -39,6 +39,9 @@ const userInitials = $derived.by(() => {
<div class="min-h-screen bg-canvas" data-hydrated={hydrated || undefined}>
{#if !isAuthPage}
<header class="sticky top-0 z-50 border-b border-line-2 bg-surface">
<!-- De Gruyter Brill purple accent strip -->
<div class="h-1 bg-brand-purple"></div>
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div class="flex h-16 justify-between">
<!-- Logo & Nav -->
@@ -46,8 +49,8 @@ const userInitials = $derived.by(() => {
<!-- Right Side -->
<div class="flex items-center gap-3">
<!-- Language selector (desktop only — mobile lives in nav drawer) -->
<div class="hidden items-center gap-1 border-r border-line pr-3 sm:flex">
<!-- Language selector -->
<div class="flex items-center gap-1 border-r border-line pr-3">
{#each locales as locale (locale)}
<button
type="button"

View File

@@ -67,7 +67,7 @@ $effect(() => {
});
</script>
<main class="mx-auto max-w-7xl px-4 py-8 font-sans sm:px-6 lg:px-8">
<main class="mx-auto max-w-7xl py-8 font-sans sm:px-6 lg:px-8">
<SearchFilterBar
bind:q={q}
bind:from={from}

View File

@@ -1,47 +1,19 @@
<script lang="ts">
import { page } from '$app/state';
import { untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js';
import { setLocale, getLocale } from '$lib/paraglide/runtime';
let { isAdmin = false }: { isAdmin?: boolean } = $props();
const locales = ['DE', 'EN', 'ES'] as const;
const localeMap = { DE: 'de', EN: 'en', ES: 'es' } as const;
const activeLocale = $derived(getLocale().toUpperCase());
let mobileNavOpen = $state(false);
$effect(() => {
// Read pathname to establish the reactive dependency.
// Write via untrack so the effect doesn't re-run on its own write.
void page.url.pathname;
untrack(() => {
mobileNavOpen = false;
});
});
function closeMobileNav() {
mobileNavOpen = false;
}
function handleOverlayKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
mobileNavOpen = false;
}
}
</script>
<div class="flex items-center">
<div class="flex">
<div class="mr-10 flex flex-shrink-0 items-center">
<a href="/" class="flex items-center" aria-label="Familienarchiv">
<span class="hidden font-sans text-xl font-bold tracking-widest text-ink uppercase sm:inline"
<span class="font-sans text-xl font-bold tracking-widest text-ink uppercase"
>Familienarchiv</span
>
</a>
</div>
<!-- Desktop nav -->
<nav class="hidden items-center sm:flex sm:space-x-1">
<a
href="/"
@@ -84,124 +56,4 @@ function handleOverlayKeydown(event: KeyboardEvent) {
</a>
{/if}
</nav>
<!-- Hamburger toggle (mobile only) -->
<button
class="ml-auto flex h-11 w-11 items-center justify-center rounded text-ink-2 transition-colors hover:bg-muted hover:text-ink sm:hidden"
aria-label={mobileNavOpen ? 'Menü schließen' : 'Menü öffnen'}
aria-expanded={mobileNavOpen}
aria-controls="mobile-nav"
onclick={() => (mobileNavOpen = !mobileNavOpen)}
>
{#if mobileNavOpen}
<!-- X icon -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="18" y1="6" x2="6" y2="18" />
<line x1="6" y1="6" x2="18" y2="18" />
</svg>
{:else}
<!-- Hamburger icon -->
<svg
xmlns="http://www.w3.org/2000/svg"
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<line x1="3" y1="6" x2="21" y2="6" />
<line x1="3" y1="12" x2="21" y2="12" />
<line x1="3" y1="18" x2="21" y2="18" />
</svg>
{/if}
</button>
</div>
<!-- Mobile nav overlay -->
{#if mobileNavOpen}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="fixed inset-0 top-[68px] z-40 sm:hidden" onkeydown={handleOverlayKeydown}>
<!-- Backdrop -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="absolute inset-0 bg-black/20" onclick={closeMobileNav}></div>
<!-- Panel -->
<div class="relative border-b border-line bg-surface shadow-md">
<nav id="mobile-nav">
<a
href="/"
class="block flex min-h-[44px] w-full items-center px-4 py-3 font-sans text-sm font-bold tracking-widest uppercase transition-colors
{page.url.pathname === '/' || page.url.pathname.startsWith('/documents')
? 'bg-nav-active text-ink'
: 'text-ink-2 hover:bg-muted hover:text-ink'}"
>
{m.nav_documents()}
</a>
<a
href="/persons"
class="block flex min-h-[44px] w-full items-center px-4 py-3 font-sans text-sm font-bold tracking-widest uppercase transition-colors
{page.url.pathname.startsWith('/persons')
? 'bg-nav-active text-ink'
: 'text-ink-2 hover:bg-muted hover:text-ink'}"
>
{m.nav_persons()}
</a>
<a
href="/conversations"
class="block flex min-h-[44px] w-full items-center px-4 py-3 font-sans text-sm font-bold tracking-widest uppercase transition-colors
{page.url.pathname.startsWith('/conversations')
? 'bg-nav-active text-ink'
: 'text-ink-2 hover:bg-muted hover:text-ink'}"
>
{m.nav_conversations()}
</a>
{#if isAdmin}
<a
href="/admin"
class="block flex min-h-[44px] w-full items-center px-4 py-3 font-sans text-sm font-bold tracking-widest uppercase transition-colors
{page.url.pathname.startsWith('/admin')
? 'bg-nav-active text-ink'
: 'text-ink-2 hover:bg-muted hover:text-ink'}"
>
{m.nav_admin()}
</a>
{/if}
<!-- Language switcher -->
<div class="flex items-center gap-2 border-t border-line px-4 py-3">
{#each locales as locale (locale)}
<button
type="button"
onclick={() => setLocale(localeMap[locale])}
class="min-h-[44px] px-3 font-sans text-sm tracking-widest transition-colors
{activeLocale === locale
? 'font-bold text-ink'
: 'font-normal text-ink-3 hover:text-ink'}"
>
{locale}
</button>
{/each}
</div>
</nav>
</div>
</div>
{/if}

View File

@@ -121,7 +121,7 @@ let {
{#each doc.tags as tag (tag.id)}
<button
type="button"
class="relative z-10 inline-flex cursor-pointer items-center rounded bg-muted px-2 py-1 text-[10px] font-bold tracking-widest text-ink uppercase transition-colors hover:bg-primary hover:text-primary-fg"
class="relative z-10 inline-flex cursor-pointer items-center rounded bg-muted px-2 py-1 text-[10px] font-bold tracking-widest text-ink uppercase transition-colors hover:bg-primary hover:text-white"
onclick={(e) => {
e.preventDefault();
e.stopPropagation();

View File

@@ -146,7 +146,7 @@ $effect(() => {
? 'border-primary bg-accent-bg py-10 text-primary'
: windowDragging
? 'border-primary/60 bg-accent-bg/50 py-10 text-primary/80'
: 'border-ink/30 py-6 text-ink-3 hover:border-primary hover:text-primary'}"
: 'border-ink/20 py-6 text-ink-3 hover:border-primary hover:text-primary'}"
ondragover={handleDragOver}
ondragleave={handleDragLeave}
ondrop={handleDrop}

View File

@@ -33,7 +33,7 @@ function clickOutside(node: HTMLElement) {
aria-expanded={userMenuOpen}
aria-haspopup="true"
onclick={() => (userMenuOpen = !userMenuOpen)}
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary font-sans text-xs font-bold text-primary-fg transition-opacity hover:opacity-80"
class="flex h-8 w-8 items-center justify-center rounded-full bg-primary font-sans text-xs font-bold text-white transition-opacity hover:opacity-80"
>
{userInitials}
</button>

View File

@@ -11,37 +11,37 @@ let { data, form } = $props();
let activeTab = $state('users');
</script>
<div class="mx-auto max-w-7xl px-4 py-8 font-sans sm:px-6 lg:px-8">
<div class="mb-8 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="mx-auto max-w-7xl py-8 font-sans sm:px-6 lg:px-8">
<div class="mb-8 flex items-center justify-between">
<h1 class="font-serif text-3xl text-ink">{m.admin_heading()}</h1>
<!-- Tabs -->
<div class="flex rounded-lg border border-line bg-surface p-1 shadow-sm">
<button
class="rounded-md px-2 py-2 text-sm font-bold tracking-wide uppercase transition sm:px-4 {activeTab ===
class="rounded-md px-4 py-2 text-sm font-bold tracking-wide uppercase transition {activeTab ===
'users'
? 'bg-primary text-primary-fg'
? 'bg-primary text-white'
: 'text-ink-2 hover:text-ink'}"
onclick={() => (activeTab = 'users')}>{m.admin_tab_users()}</button
>
<button
class="rounded-md px-2 py-2 text-sm font-bold tracking-wide uppercase transition sm:px-4 {activeTab ===
class="rounded-md px-4 py-2 text-sm font-bold tracking-wide uppercase transition {activeTab ===
'groups'
? 'bg-primary text-primary-fg'
? 'bg-primary text-white'
: 'text-ink-2 hover:text-ink'}"
onclick={() => (activeTab = 'groups')}>{m.admin_tab_groups()}</button
>
<button
class="rounded-md px-2 py-2 text-sm font-bold tracking-wide uppercase transition sm:px-4 {activeTab ===
class="rounded-md px-4 py-2 text-sm font-bold tracking-wide uppercase transition {activeTab ===
'tags'
? 'bg-primary text-primary-fg'
? 'bg-primary text-white'
: 'text-ink-2 hover:text-ink'}"
onclick={() => (activeTab = 'tags')}>{m.admin_tab_tags()}</button
>
<button
class="rounded-md px-2 py-2 text-sm font-bold tracking-wide uppercase transition sm:px-4 {activeTab ===
class="rounded-md px-4 py-2 text-sm font-bold tracking-wide uppercase transition {activeTab ===
'system'
? 'bg-primary text-primary-fg'
? 'bg-primary text-white'
: 'text-ink-2 hover:text-ink'}"
onclick={() => (activeTab = 'system')}>{m.admin_tab_system()}</button
>

View File

@@ -212,7 +212,7 @@ function cancelEditGroup() {
<button
type="submit"
class="w-full rounded bg-primary px-6 py-2 text-sm font-bold text-primary-fg uppercase hover:bg-accent hover:text-ink md:w-auto"
class="w-full rounded bg-primary px-6 py-2 text-sm font-bold text-white uppercase hover:bg-accent hover:text-ink md:w-auto"
>
{m.btn_create()}
</button>

View File

@@ -41,7 +41,7 @@ async function backfillFileHashes() {
<button
onclick={backfillVersions}
disabled={backfillLoading}
class="rounded bg-primary px-6 py-2 text-sm font-bold text-primary-fg uppercase transition hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50"
class="rounded bg-primary px-6 py-2 text-sm font-bold text-white uppercase transition hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50"
>
{backfillLoading ? '…' : m.admin_system_backfill_btn()}
</button>
@@ -60,7 +60,7 @@ async function backfillFileHashes() {
<button
onclick={backfillFileHashes}
disabled={backfillHashesLoading}
class="rounded bg-primary px-6 py-2 text-sm font-bold text-primary-fg uppercase transition hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50"
class="rounded bg-primary px-6 py-2 text-sm font-bold text-white uppercase transition hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50"
>
{backfillHashesLoading ? '…' : m.admin_system_backfill_hashes_btn()}
</button>

View File

@@ -20,7 +20,7 @@ let {
<h2 class="text-lg font-bold text-ink-2">{m.admin_section_users()}</h2>
<a
href="/admin/users/new"
class="inline-flex items-center gap-1 rounded-sm bg-primary px-4 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
class="inline-flex items-center gap-1 rounded-sm bg-primary px-4 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
>
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />

View File

@@ -85,7 +85,7 @@ const selectedGroupIds = $derived(data.editUser.groups?.map((g: { id: string })
</a>
<button
type="submit"
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
>
{m.btn_save()}
</button>

View File

@@ -61,7 +61,7 @@ let { data, form } = $props();
</a>
<button
type="submit"
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
>
{m.btn_create()}
</button>

View File

@@ -48,7 +48,7 @@ let {
<button
data-testid="conv-swap-btn"
onclick={onswapPersons}
class="flex w-full items-center justify-center gap-2 border border-line px-3 py-2.5 text-xs font-bold tracking-widest text-ink uppercase transition-colors hover:bg-primary hover:text-primary-fg md:w-auto {senderId &&
class="flex w-full items-center justify-center gap-2 border border-line px-3 py-2.5 text-xs font-bold tracking-widest text-ink uppercase transition-colors hover:bg-primary hover:text-white md:w-auto {senderId &&
receiverId
? ''
: 'invisible'}"
@@ -121,7 +121,7 @@ let {
<div>
<button
onclick={ontoggleSort}
class="flex h-[42px] w-full items-center justify-center border border-line text-xs font-bold tracking-wide text-ink uppercase transition-colors hover:bg-primary hover:text-primary-fg"
class="flex h-[42px] w-full items-center justify-center border border-line text-xs font-bold tracking-wide text-ink uppercase transition-colors hover:bg-primary hover:text-white"
>
<span class="mr-2">{m.conv_sort_label()}</span>
<span>{sortDir === 'DESC' ? m.conv_sort_newest() : m.conv_sort_oldest()}</span>

View File

@@ -2,24 +2,14 @@
import { m } from '$lib/paraglide/messages.js';
let { originalFilename }: { originalFilename: string } = $props();
let selectedFilename = $state<string | null>(null);
function handleFileChange(e: Event) {
const file = (e.target as HTMLInputElement).files?.[0];
selectedFilename = file?.name ?? null;
}
</script>
<div class="rounded-sm border border-line bg-surface shadow-sm">
<div class="border-b border-line px-6 py-4">
<h2 class="text-xs font-bold tracking-widest text-ink-3 uppercase">
{m.doc_section_file()}
</h2>
</div>
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm">
<h2 class="mb-5 text-xs font-bold tracking-widest text-ink-3 uppercase">
{m.doc_section_file()}
</h2>
<!-- Current file -->
<div class="flex items-center gap-3 border-b border-line px-6 py-3 text-sm text-ink-2">
<div class="mb-4 flex items-center gap-3 rounded bg-muted px-3 py-2 text-sm text-ink-2">
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
alt=""
@@ -32,31 +22,19 @@ function handleFileChange(e: Event) {
>
</div>
<!-- Replace file upload zone -->
<label
for="file-upload"
class="flex cursor-pointer flex-col items-center gap-3 px-6 py-8 transition-colors hover:bg-muted/40"
>
<svg
class="h-8 w-8 text-ink-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="1.5"
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12"
/>
</svg>
{#if selectedFilename}
<span class="text-sm font-medium text-ink">{selectedFilename}</span>
{:else}
<span class="text-sm font-medium text-ink-2">{m.doc_file_replace_label()}</span>
<span class="text-xs text-ink-3">{m.doc_file_replace_note()}</span>
{/if}
<label for="file-upload" class="mb-1 block text-sm font-medium text-ink-2">
{m.doc_file_replace_label()}
<span class="font-normal text-ink-3">({m.doc_file_replace_note()})</span>
</label>
<input id="file-upload" type="file" name="file" onchange={handleFileChange} class="sr-only" />
<input
id="file-upload"
type="file"
name="file"
class="block w-full cursor-pointer text-sm
text-ink-2 file:mr-4 file:rounded
file:border-0 file:bg-muted
file:px-4 file:py-2
file:text-sm file:font-semibold
file:text-ink hover:file:bg-muted"
/>
</div>

View File

@@ -7,79 +7,73 @@ let confirmDelete = $state(false);
</script>
<div
class="sticky bottom-0 z-10 -mx-4 border-t border-line bg-surface px-4 py-3 shadow-[0_-2px_8px_rgba(0,0,0,0.06)] sm:px-6 sm:py-4"
class="sticky bottom-0 z-10 -mx-4 flex items-center justify-between border-t border-line bg-surface px-6 py-4 shadow-[0_-2px_8px_rgba(0,0,0,0.06)]"
>
<!-- Desktop: delete left, cancel+buttons right -->
<!-- Mobile: action buttons stacked full-width, delete+cancel row at bottom -->
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<!-- Primary actions first (top on mobile, right on desktop) -->
<div class="flex flex-col gap-2 sm:order-last sm:flex-row sm:items-center sm:gap-4">
<!-- Left: delete -->
<div class="flex items-center gap-3">
{#if confirmDelete}
<span class="font-sans text-sm text-red-700">{m.doc_delete_confirm()}</span>
<button
type="submit"
form="mark-for-review-form"
class="w-full rounded-sm border border-line px-4 py-2.5 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:bg-muted sm:w-auto sm:py-2"
form="delete-form"
class="rounded bg-red-600 px-4 py-1.5 text-sm font-bold text-white transition-colors hover:bg-red-700"
>
{m.btn_mark_for_review()}
{m.btn_delete()}
</button>
<button
type="submit"
class="w-full rounded bg-primary px-6 py-2.5 font-sans text-sm font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/80 sm:w-auto sm:py-2"
type="button"
onclick={() => (confirmDelete = false)}
class="text-sm text-ink-2 transition-colors hover:text-ink"
>
{m.btn_save()}
{m.btn_cancel()}
</button>
</div>
{:else}
<button
type="button"
onclick={() => (confirmDelete = true)}
class="flex items-center gap-1.5 rounded border border-red-300 px-4 py-1.5 text-sm font-bold text-red-600 transition-colors hover:border-red-600 hover:bg-red-50"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
<path d="M10 11v6M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
{m.btn_delete()}
</button>
{/if}
</div>
<!-- Secondary: delete + cancel (row on both mobile and desktop) -->
<div class="flex items-center justify-between sm:justify-start sm:gap-4">
{#if confirmDelete}
<span class="font-sans text-sm text-red-700">{m.doc_delete_confirm()}</span>
<div class="flex items-center gap-3">
<button
type="submit"
form="delete-form"
class="rounded bg-red-600 px-4 py-1.5 text-sm font-bold text-white transition-colors hover:bg-red-700"
>
{m.btn_delete()}
</button>
<button
type="button"
onclick={() => (confirmDelete = false)}
class="text-sm text-ink-2 transition-colors hover:text-ink"
>
{m.btn_cancel()}
</button>
</div>
{:else}
<button
type="button"
onclick={() => (confirmDelete = true)}
class="flex items-center gap-1.5 rounded border border-red-300 px-4 py-1.5 text-sm font-bold text-red-600 transition-colors hover:border-red-600 hover:bg-red-50"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<polyline points="3 6 5 6 21 6" />
<path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6" />
<path d="M10 11v6M14 11v6" />
<path d="M9 6V4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v2" />
</svg>
{m.btn_delete()}
</button>
<a
href="/documents/{docId}"
class="text-sm font-medium text-ink-2 transition-colors hover:text-ink"
>
{m.btn_cancel()}
</a>
{/if}
</div>
<!-- Right: cancel + mark for review + save -->
<div class="flex items-center gap-4">
<a
href="/documents/{docId}"
class="text-sm font-medium text-ink-2 transition-colors hover:text-ink"
>
{m.btn_cancel()}
</a>
<button
type="submit"
form="mark-for-review-form"
class="rounded-sm border border-gray-300 px-4 py-2 font-sans text-xs font-bold tracking-widest text-gray-600 uppercase transition-colors hover:bg-gray-50"
>
{m.btn_mark_for_review()}
</button>
<button
type="submit"
class="rounded bg-primary px-6 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/80"
>
{m.btn_save()}
</button>
</div>
</div>

View File

@@ -120,35 +120,30 @@ $effect(() => {
<!-- Sticky Save Bar -->
<div
class="sticky bottom-0 z-10 -mx-4 border-t border-line bg-surface px-4 py-3 shadow-[0_-2px_8px_rgba(0,0,0,0.06)] sm:px-6 sm:py-4"
class="sticky bottom-0 z-10 -mx-4 flex items-center justify-between border-t border-line bg-surface px-6 py-4 shadow-[0_-2px_8px_rgba(0,0,0,0.06)]"
>
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
<a
href="/"
class="order-last text-center text-sm font-medium text-ink-2 transition-colors hover:text-ink sm:order-first sm:text-left"
<a href="/" class="text-sm font-medium text-ink-2 transition-colors hover:text-ink">
{m.btn_cancel()}
</a>
<div class="flex items-center gap-3">
<button
type="submit"
name="metadataComplete"
value="false"
formaction="?/save"
class="rounded-sm border border-gray-300 px-5 py-2 font-sans text-xs font-bold tracking-widest text-gray-600 uppercase transition-colors hover:bg-gray-50"
>
{m.btn_cancel()}
</a>
<div class="flex flex-col gap-2 sm:flex-row sm:items-center sm:gap-3">
<button
type="submit"
name="metadataComplete"
value="false"
formaction="?/save"
class="w-full rounded-sm border border-line px-5 py-2.5 font-sans text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:bg-muted sm:w-auto sm:py-2"
>
{m.btn_save()}
</button>
<button
type="submit"
name="metadataComplete"
value="true"
formaction="?/saveReviewed"
class="w-full rounded-sm bg-primary px-5 py-2.5 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90 sm:w-auto sm:py-2"
>
{m.btn_save_and_mark_reviewed()}
</button>
</div>
{m.btn_save()}
</button>
<button
type="submit"
name="metadataComplete"
value="true"
formaction="?/saveReviewed"
class="rounded-sm bg-brand-navy px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-brand-navy/90"
>
{m.btn_save_and_mark_reviewed()}
</button>
</div>
</div>
</form>

View File

@@ -19,7 +19,7 @@ function formatUploadDate(createdAt: string): string {
<!-- Back Link -->
<a
href="/"
class="group mb-4 inline-flex items-center font-sans text-xs font-bold tracking-widest text-ink-2 uppercase transition-colors hover:text-ink"
class="group mb-4 inline-flex items-center font-sans text-xs font-bold tracking-widest text-gray-500 uppercase transition-colors hover:text-brand-navy"
>
<img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/Arrow/Arrow-Left-MD.svg"
@@ -47,7 +47,7 @@ function formatUploadDate(createdAt: string): string {
{#if count > 0}
<a
href="/enrich/{documents[0].id}"
class="bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="bg-brand-navy px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-brand-navy/90"
>
{m.enrich_list_start()}
</a>
@@ -76,8 +76,8 @@ function formatUploadDate(createdAt: string): string {
</div>
{:else}
<!-- Document Rows -->
<div class="border border-line bg-white shadow-sm">
<ul class="divide-y divide-line-2">
<div class="border-brand-sand border bg-white shadow-sm">
<ul class="divide-brand-sand divide-y">
{#each documents as doc (doc.id)}
<li class="group hover:bg-brand-sand/30 transition-colors duration-200">
<a href="/enrich/{doc.id}" class="flex items-center justify-between p-6">

View File

@@ -156,7 +156,7 @@ let selectedReceivers = $state(untrack(() => doc.receivers ?? []));
type="submit"
form="save-form"
formaction="?/saveAndReview"
class="rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="rounded-sm bg-brand-navy px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-brand-navy/90"
>
{m.btn_save_and_mark_reviewed()}
</button>

View File

@@ -17,21 +17,21 @@ import { m } from '$lib/paraglide/messages.js';
{m.enrich_done_heading()}
</h1>
<p class="mt-2 max-w-xs font-sans text-sm text-ink-2">
<p class="mt-2 max-w-xs font-sans text-sm text-gray-500">
{m.enrich_done_body()}
</p>
<div class="mt-8 flex flex-col items-center gap-4">
<a
href="/"
class="bg-primary px-6 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="bg-brand-navy px-6 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-brand-navy/90"
>
{m.btn_back_to_overview()}
</a>
<a
href="/enrich"
class="font-sans text-xs text-ink-2 underline-offset-4 transition-colors hover:text-ink hover:underline"
class="font-sans text-xs text-gray-400 underline-offset-4 transition-colors hover:text-brand-navy hover:underline"
>
{m.enrich_back_to_list()}
</a>

View File

@@ -5,6 +5,9 @@ let { form }: { form?: { error?: string; success?: boolean } } = $props();
</script>
<div class="relative flex min-h-screen flex-col bg-surface">
<!-- Accent strip -->
<div class="h-1 bg-brand-purple"></div>
<div class="flex flex-1 items-center justify-center px-4">
<div class="w-full max-w-sm">
<!-- Logo -->
@@ -54,7 +57,7 @@ let { form }: { form?: { error?: string; success?: boolean } } = $props();
<button
type="submit"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/90"
>
{m.forgot_password_submit()}
</button>

View File

@@ -10,6 +10,7 @@
--palette-mint: #a1dcd8;
--palette-turquoise: #00c7b1;
--palette-sand: #f0efe9;
--palette-purple: #b4b9ff;
/* Typography */
--font-sans: 'Montserrat', ui-sans-serif, system-ui, sans-serif;
@@ -57,6 +58,7 @@
--color-pdf-text: var(--c-pdf-text);
/* Static brand tokens (not themed) */
--color-brand-purple: var(--palette-purple);
--color-brand-navy: var(--palette-navy);
--color-brand-mint: var(--palette-mint);
}
@@ -72,8 +74,8 @@
--c-line-2: #eeede8;
--c-ink: #012851;
--c-ink-2: #4b5563; /* gray-600 — 7.6:1 on white, 6.6:1 on canvas — WCAG AA ✓ */
--c-ink-3: #6b7280; /* gray-500 — 4.8:1 on white — WCAG AA ✓; use only on surface, not canvas */
--c-ink-2: #6b7280;
--c-ink-3: #9ca3af;
--c-accent: #a1dcd8;
--c-accent-bg: rgba(161, 220, 216, 0.15);
@@ -96,12 +98,12 @@
--c-overlay: #242424;
--c-muted: #252525;
--c-line: #3d3d3d;
--c-line-2: #2e2e2e;
--c-line: #2e2e2e;
--c-line-2: #222222;
--c-ink: #f0efe9;
--c-ink-2: #9ca3af; /* gray-400 — 7.5:1 on dark surface — WCAG AAA ✓ */
--c-ink-3: #8b97a5; /* gray-450 — 6.5:1 on dark surface — WCAG AA ✓ */
--c-ink-2: #9ca3af;
--c-ink-3: #6b7280;
--c-accent: #00c7b1;
--c-accent-bg: rgba(0, 199, 177, 0.12);
@@ -124,8 +126,8 @@
--c-overlay: #242424;
--c-muted: #252525;
--c-line: #3d3d3d;
--c-line-2: #2e2e2e;
--c-line: #2e2e2e;
--c-line-2: #222222;
--c-ink: #f0efe9;
--c-ink-2: #9ca3af;

View File

@@ -10,6 +10,9 @@ const activeLocale = $derived(getLocale().toUpperCase());
</script>
<div class="relative flex min-h-screen flex-col bg-canvas">
<!-- DGB purple accent strip -->
<div class="h-1 bg-brand-purple"></div>
<!-- Language switcher -->
<div class="absolute top-4 right-4 flex items-center gap-1">
{#each locales as locale (locale)}
@@ -82,7 +85,7 @@ const activeLocale = $derived(getLocale().toUpperCase());
<button
type="submit"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/90"
>
{m.login_btn_submit()}
</button>

View File

@@ -23,7 +23,7 @@ function handleSearch() {
}
</script>
<div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<div class="mx-auto max-w-7xl py-12 sm:px-6 lg:px-8">
<!-- Header Area -->
<div
class="mb-10 flex flex-col justify-between gap-6 border-b border-ink/10 pb-6 md:flex-row md:items-end"
@@ -107,7 +107,7 @@ function handleSearch() {
<!-- Avatar -->
<div class="flex-shrink-0">
<div
class="flex h-12 w-12 items-center justify-center rounded-full bg-primary font-serif text-lg text-primary-fg transition-colors group-hover:bg-accent group-hover:text-ink"
class="flex h-12 w-12 items-center justify-center rounded-full bg-primary font-serif text-lg text-white transition-colors group-hover:bg-accent group-hover:text-ink"
>
{person.firstName[0]}{person.lastName[0]}
</div>

View File

@@ -143,7 +143,7 @@ $effect(() => {
<div class="flex gap-3">
<button
type="submit"
class="rounded bg-primary px-5 py-2 text-sm font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/80"
class="rounded bg-primary px-5 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/80"
>
{m.btn_save()}
</button>

View File

@@ -44,7 +44,7 @@ const visibleDocuments = $derived(
<div class="mb-10">
<div class="mb-6 flex items-center gap-3 border-b border-ink/10 pb-2">
<h2 class="font-serif text-xl text-ink">{heading}</h2>
<span class="rounded-full bg-primary px-2 py-1 text-xs font-bold text-primary-fg">
<span class="rounded-full bg-primary px-2 py-1 text-xs font-bold text-white">
{documents.length}
</span>
{#if yearRange}

View File

@@ -89,7 +89,7 @@ let { form } = $props();
</a>
<button
type="submit"
class="rounded bg-primary px-6 py-2 text-sm font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/80"
class="rounded bg-primary px-6 py-2 text-sm font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/80"
>
{m.btn_create()}
</button>

View File

@@ -70,7 +70,7 @@ let {
<button
type="submit"
class="mt-5 rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
class="mt-5 rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
>
{m.btn_save()}
</button>

View File

@@ -115,7 +115,7 @@ function handleBirthDateInput(e: Event) {
<button
type="submit"
class="mt-5 rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-opacity hover:opacity-80"
class="mt-5 rounded-sm bg-primary px-5 py-2 font-sans text-xs font-bold tracking-widest text-white uppercase transition-opacity hover:opacity-80"
>
{m.btn_save()}
</button>

View File

@@ -12,6 +12,9 @@ let {
</script>
<div class="relative flex min-h-screen flex-col bg-surface">
<!-- Accent strip -->
<div class="h-1 bg-brand-purple"></div>
<div class="flex flex-1 items-center justify-center px-4">
<div class="w-full max-w-sm">
<!-- Logo -->
@@ -83,7 +86,7 @@ let {
<button
type="submit"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-primary-fg uppercase transition-colors hover:bg-primary/90"
class="mt-2 w-full bg-primary py-2.5 font-sans text-xs font-bold tracking-widest text-white uppercase transition-colors hover:bg-primary/90"
>
{m.reset_password_submit()}
</button>

View File

@@ -45,13 +45,13 @@ const initials = $derived.by(() => {
<div class="mb-5 flex justify-center">
{#if initials}
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-primary text-primary-fg"
class="flex h-16 w-16 items-center justify-center rounded-full bg-primary text-white"
>
<span class="font-serif text-xl font-bold">{initials}</span>
</div>
{:else}
<div
class="flex h-16 w-16 items-center justify-center rounded-full bg-primary text-primary-fg"
class="flex h-16 w-16 items-center justify-center rounded-full bg-primary text-white"
>
<svg
class="h-8 w-8"