Compare commits

...

7 Commits

Author SHA1 Message Date
Marcel
f8d888a5be fix(#103): move language switcher from header into mobile nav drawer
Some checks failed
CI / Unit & Component Tests (push) Has been cancelled
CI / Backend Unit Tests (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
On mobile the header is now cleaner — language buttons move to the
bottom of the hamburger panel. Desktop header is unchanged (sm:flex).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:41:51 +01:00
Marcel
29f0ec8a05 fix(#102): replace native file input in edit form with styled upload zone
Matches the FileSectionNew design: upload arrow icon, hidden <input>,
styled label as the click target, shows selected filename on pick.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:40:23 +01:00
Marcel
5db17880f9 fix(#101): stop bottom panel from overlapping document viewer
Replaced position:fixed on the bottom panel with shrink-0 flex child,
so the viewer (flex-1) naturally stops at the panel top instead of
extending behind it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:38:47 +01:00
Marcel
ce02c1bf39 fix(#100): hide action button labels on mobile to prevent toolbar overflow
At 320px, showing "Annotieren" + "Bearbeiten" + download pushed the
toolbar past its bounds. Icon-only at mobile, labels revealed at sm:.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:36:15 +01:00
Marcel
e1c09ddc7f fix(#99): make document detail tab bar scrollable on narrow screens
Wrap tabs in overflow-x-auto container with hidden scrollbar so all 4
German labels ("Transkription" etc.) are reachable at 320px. Close
button stays pinned outside the scroll area, always visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:33:47 +01:00
Marcel
93408c5825 fix(#98): make drop zone border and card borders visible in dark mode
- DropZone: raise border opacity from /20 to /30 for dashed drop zone
- layout.css: bump dark mode --c-line from #2e2e2e to #3d3d3d (was
  ~1.3:1 contrast on #1a1a1a surface, effectively invisible)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:31:00 +01:00
Marcel
2a2ce240e1 fix(#97): add px-4 base padding to person directory on mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 16:28:31 +01:00
8 changed files with 102 additions and 54 deletions

View File

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

View File

@@ -58,7 +58,7 @@ const compactMeta = $derived.by(() => {
</script> </script>
<div <div
class="z-20 flex shrink-0 items-center justify-between border-b border-line bg-surface px-6 py-3 shadow-sm" 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"
data-topbar data-topbar
> >
<!-- Left: back + title --> <!-- Left: back + title -->
@@ -111,13 +111,16 @@ const compactMeta = $derived.by(() => {
aria-hidden="true" aria-hidden="true"
class="h-4 w-4 {annotateMode ? 'invert' : ''}" class="h-4 w-4 {annotateMode ? 'invert' : ''}"
/> />
{annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()} <span class="hidden sm:inline"
>{annotateMode ? m.doc_panel_annotate_stop() : m.doc_panel_annotate()}</span
>
</button> </button>
{/if} {/if}
{#if canWrite} {#if canWrite}
<a <a
href="/documents/{doc.id}/edit" 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-primary-fg"
> >
<img <img
@@ -126,7 +129,7 @@ const compactMeta = $derived.by(() => {
aria-hidden="true" aria-hidden="true"
class="h-4 w-4" class="h-4 w-4"
/> />
{m.btn_edit()} <span class="hidden sm:inline">{m.btn_edit()}</span>
</a> </a>
{/if} {/if}

View File

@@ -46,8 +46,8 @@ const userInitials = $derived.by(() => {
<!-- Right Side --> <!-- Right Side -->
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<!-- Language selector --> <!-- Language selector (desktop only — mobile lives in nav drawer) -->
<div class="flex items-center gap-1 border-r border-line pr-3"> <div class="hidden items-center gap-1 border-r border-line pr-3 sm:flex">
{#each locales as locale (locale)} {#each locales as locale (locale)}
<button <button
type="button" type="button"

View File

@@ -2,9 +2,14 @@
import { page } from '$app/state'; import { page } from '$app/state';
import { untrack } from 'svelte'; import { untrack } from 'svelte';
import { m } from '$lib/paraglide/messages.js'; import { m } from '$lib/paraglide/messages.js';
import { setLocale, getLocale } from '$lib/paraglide/runtime';
let { isAdmin = false }: { isAdmin?: boolean } = $props(); 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); let mobileNavOpen = $state(false);
$effect(() => { $effect(() => {
@@ -180,6 +185,22 @@ function handleOverlayKeydown(event: KeyboardEvent) {
{m.nav_admin()} {m.nav_admin()}
</a> </a>
{/if} {/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> </nav>
</div> </div>
</div> </div>

View File

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

View File

@@ -2,14 +2,24 @@
import { m } from '$lib/paraglide/messages.js'; import { m } from '$lib/paraglide/messages.js';
let { originalFilename }: { originalFilename: string } = $props(); 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> </script>
<div class="rounded-sm border border-line bg-surface p-6 shadow-sm"> <div class="rounded-sm border border-line bg-surface shadow-sm">
<h2 class="mb-5 text-xs font-bold tracking-widest text-ink-3 uppercase"> <div class="border-b border-line px-6 py-4">
{m.doc_section_file()} <h2 class="text-xs font-bold tracking-widest text-ink-3 uppercase">
</h2> {m.doc_section_file()}
</h2>
</div>
<div class="mb-4 flex items-center gap-3 rounded bg-muted px-3 py-2 text-sm text-ink-2"> <!-- Current file -->
<div class="flex items-center gap-3 border-b border-line px-6 py-3 text-sm text-ink-2">
<img <img
src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg" src="/degruyter-icons/Simple/Medium-24px/SVG/Action/PDF-Document-MD.svg"
alt="" alt=""
@@ -22,19 +32,31 @@ let { originalFilename }: { originalFilename: string } = $props();
> >
</div> </div>
<label for="file-upload" class="mb-1 block text-sm font-medium text-ink-2"> <!-- Replace file upload zone -->
{m.doc_file_replace_label()} <label
<span class="font-normal text-ink-3">({m.doc_file_replace_note()})</span> 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> </label>
<input <input id="file-upload" type="file" name="file" onchange={handleFileChange} class="sr-only" />
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> </div>

View File

@@ -96,8 +96,8 @@
--c-overlay: #242424; --c-overlay: #242424;
--c-muted: #252525; --c-muted: #252525;
--c-line: #2e2e2e; --c-line: #3d3d3d;
--c-line-2: #222222; --c-line-2: #2e2e2e;
--c-ink: #f0efe9; --c-ink: #f0efe9;
--c-ink-2: #9ca3af; /* gray-400 — 7.5:1 on dark surface — WCAG AAA ✓ */ --c-ink-2: #9ca3af; /* gray-400 — 7.5:1 on dark surface — WCAG AAA ✓ */
@@ -124,8 +124,8 @@
--c-overlay: #242424; --c-overlay: #242424;
--c-muted: #252525; --c-muted: #252525;
--c-line: #2e2e2e; --c-line: #3d3d3d;
--c-line-2: #222222; --c-line-2: #2e2e2e;
--c-ink: #f0efe9; --c-ink: #f0efe9;
--c-ink-2: #9ca3af; --c-ink-2: #9ca3af;

View File

@@ -23,7 +23,7 @@ function handleSearch() {
} }
</script> </script>
<div class="mx-auto max-w-7xl py-12 sm:px-6 lg:px-8"> <div class="mx-auto max-w-7xl px-4 py-12 sm:px-6 lg:px-8">
<!-- Header Area --> <!-- Header Area -->
<div <div
class="mb-10 flex flex-col justify-between gap-6 border-b border-ink/10 pb-6 md:flex-row md:items-end" class="mb-10 flex flex-col justify-between gap-6 border-b border-ink/10 pb-6 md:flex-row md:items-end"