mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:41:09 +02:00
i18n(comic+quiz): translate picker/character-detail/play-view via $_()
- comic/components/CharacterPicker: route through comic.picker.* with HTML interpolation for the no-face/empty-garment alerts - comic/views/DetailCharacterView: route through comic.character_detail.* + dynamic comic.styles.<id>; drops unused STYLE_LABELS import - quiz/PlayView: route through quiz.play_view.* (back/empty/result/play all consolidated) Baseline 869 → 851 (-18).
This commit is contained in:
parent
5d9dc80662
commit
a842537191
5 changed files with 111 additions and 78 deletions
|
|
@ -18,6 +18,7 @@
|
||||||
import { useAllGarments } from '$lib/modules/wardrobe/queries';
|
import { useAllGarments } from '$lib/modules/wardrobe/queries';
|
||||||
import { garmentPhotoUrl } from '$lib/modules/wardrobe/api/media-url';
|
import { garmentPhotoUrl } from '$lib/modules/wardrobe/api/media-url';
|
||||||
import type { Garment } from '$lib/modules/wardrobe/types';
|
import type { Garment } from '$lib/modules/wardrobe/types';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string[];
|
value: string[];
|
||||||
|
|
@ -108,33 +109,30 @@
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div>
|
<div>
|
||||||
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
<h3 class="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
Protagonist
|
{$_('comic.picker.section_title')}
|
||||||
</h3>
|
</h3>
|
||||||
<p class="mt-0.5 text-xs text-muted-foreground">
|
<p class="mt-0.5 text-xs text-muted-foreground">
|
||||||
Dein Gesicht ist Pflicht. Body-Ref und bis zu {MAX_GARMENTS} Kostüm-Fotos sind optional — klicke
|
{$_('comic.picker.section_hint', { values: { max: MAX_GARMENTS } })}
|
||||||
ein Bild oder das ✕, um es wieder zu entfernen.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap items-start gap-2">
|
<div class="flex flex-wrap items-start gap-2">
|
||||||
<!-- Face ref tile — mandatory, not deselectable. Small "Pflicht"-
|
<!-- Face ref tile — mandatory, not deselectable. -->
|
||||||
badge makes the locked state explicit so the user doesn't
|
|
||||||
hunt for a remove button that doesn't exist. -->
|
|
||||||
<div class="flex flex-col items-center gap-1">
|
<div class="flex flex-col items-center gap-1">
|
||||||
{#if face?.publicUrl}
|
{#if face?.publicUrl}
|
||||||
<div
|
<div
|
||||||
class="relative h-20 w-20 overflow-hidden rounded-md border-2 border-primary/40"
|
class="relative h-20 w-20 overflow-hidden rounded-md border-2 border-primary/40"
|
||||||
title="Face-Ref ist Pflicht — kann nicht entfernt werden"
|
title={$_('comic.picker.face_required_title')}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={face.thumbnailUrl ?? face.publicUrl}
|
src={face.thumbnailUrl ?? face.publicUrl}
|
||||||
alt="Face-Ref"
|
alt={$_('comic.picker.face_alt')}
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
<span
|
<span
|
||||||
class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent px-1 py-0.5 text-center text-[9px] font-semibold uppercase tracking-wider text-white"
|
class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/70 to-transparent px-1 py-0.5 text-center text-[9px] font-semibold uppercase tracking-wider text-white"
|
||||||
>
|
>
|
||||||
Pflicht
|
{$_('comic.picker.face_required_badge')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -142,10 +140,12 @@
|
||||||
class="flex h-20 w-20 flex-col items-center justify-center gap-1 rounded-md border border-dashed border-border bg-muted/50 text-[10px] text-muted-foreground"
|
class="flex h-20 w-20 flex-col items-center justify-center gap-1 rounded-md border border-dashed border-border bg-muted/50 text-[10px] text-muted-foreground"
|
||||||
>
|
>
|
||||||
<UserCircle size={20} />
|
<UserCircle size={20} />
|
||||||
<span>Face fehlt</span>
|
<span>{$_('comic.picker.face_missing')}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-[10px] font-medium text-muted-foreground">Face</span>
|
<span class="text-[10px] font-medium text-muted-foreground"
|
||||||
|
>{$_('comic.picker.face_label')}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Body ref tile — optional toggle. Two states need clear visual
|
<!-- Body ref tile — optional toggle. Two states need clear visual
|
||||||
|
|
@ -165,11 +165,11 @@
|
||||||
? 'border-primary shadow-sm shadow-primary/20'
|
? 'border-primary shadow-sm shadow-primary/20'
|
||||||
: 'border-border opacity-60 hover:border-primary/50 hover:opacity-100 hover:shadow-sm'}"
|
: 'border-border opacity-60 hover:border-primary/50 hover:opacity-100 hover:shadow-sm'}"
|
||||||
aria-pressed={bodyInValue}
|
aria-pressed={bodyInValue}
|
||||||
title={bodyInValue ? 'Klick zum Entfernen' : 'Klick zum Hinzufügen'}
|
title={bodyInValue ? $_('comic.picker.toggle_remove') : $_('comic.picker.toggle_add')}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={body.thumbnailUrl ?? body.publicUrl}
|
src={body.thumbnailUrl ?? body.publicUrl}
|
||||||
alt="Body-Ref"
|
alt={$_('comic.picker.body_alt')}
|
||||||
class="h-full w-full object-cover"
|
class="h-full w-full object-cover"
|
||||||
/>
|
/>
|
||||||
{#if !bodyInValue}
|
{#if !bodyInValue}
|
||||||
|
|
@ -189,13 +189,15 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div
|
<div
|
||||||
class="flex h-20 w-20 flex-col items-center justify-center gap-1 rounded-md border border-dashed border-border bg-muted/30 text-[10px] text-muted-foreground"
|
class="flex h-20 w-20 flex-col items-center justify-center gap-1 rounded-md border border-dashed border-border bg-muted/30 text-[10px] text-muted-foreground"
|
||||||
title="Kein Body-Ref im aktiven Space"
|
title={$_('comic.picker.body_no_in_space')}
|
||||||
>
|
>
|
||||||
<UserCircle size={18} />
|
<UserCircle size={18} />
|
||||||
<span>Body fehlt</span>
|
<span>{$_('comic.picker.body_missing')}</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<span class="text-[10px] font-medium text-muted-foreground">Body</span>
|
<span class="text-[10px] font-medium text-muted-foreground"
|
||||||
|
>{$_('comic.picker.body_label')}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Garment tiles (picked). Whole tile is also clickable to
|
<!-- Garment tiles (picked). Whole tile is also clickable to
|
||||||
|
|
@ -210,8 +212,8 @@
|
||||||
{disabled}
|
{disabled}
|
||||||
onclick={() => mediaId && removeGarment(mediaId)}
|
onclick={() => mediaId && removeGarment(mediaId)}
|
||||||
class="group relative h-20 w-20 overflow-hidden rounded-md border-2 border-primary/40 shadow-sm transition-all active:translate-y-px hover:border-error/60"
|
class="group relative h-20 w-20 overflow-hidden rounded-md border-2 border-primary/40 shadow-sm transition-all active:translate-y-px hover:border-error/60"
|
||||||
aria-label={`${g.name} entfernen`}
|
aria-label={$_('comic.picker.garment_remove_aria', { values: { name: g.name } })}
|
||||||
title="Klick zum Entfernen"
|
title={$_('comic.picker.toggle_remove')}
|
||||||
>
|
>
|
||||||
{#if mediaId}
|
{#if mediaId}
|
||||||
<img
|
<img
|
||||||
|
|
@ -247,7 +249,7 @@
|
||||||
aria-expanded={showGarmentPicker}
|
aria-expanded={showGarmentPicker}
|
||||||
>
|
>
|
||||||
<Plus size={16} />
|
<Plus size={16} />
|
||||||
<span class="text-[10px] font-medium">Kostüm</span>
|
<span class="text-[10px] font-medium">{$_('comic.picker.garment_label')}</span>
|
||||||
</button>
|
</button>
|
||||||
<span class="text-[10px] text-muted-foreground">
|
<span class="text-[10px] text-muted-foreground">
|
||||||
{garmentIdsInValue.length}/{MAX_GARMENTS}
|
{garmentIdsInValue.length}/{MAX_GARMENTS}
|
||||||
|
|
@ -260,21 +262,21 @@
|
||||||
{#if showGarmentPicker}
|
{#if showGarmentPicker}
|
||||||
<div class="rounded-lg border border-border bg-muted/30 p-3">
|
<div class="rounded-lg border border-border bg-muted/30 p-3">
|
||||||
<div class="mb-2 flex items-center justify-between">
|
<div class="mb-2 flex items-center justify-between">
|
||||||
<h4 class="text-xs font-semibold text-foreground">Kostüm aus dem Schrank wählen</h4>
|
<h4 class="text-xs font-semibold text-foreground">
|
||||||
|
{$_('comic.picker.garment_picker_title')}
|
||||||
|
</h4>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => (showGarmentPicker = false)}
|
onclick={() => (showGarmentPicker = false)}
|
||||||
class="text-xs text-muted-foreground hover:text-foreground"
|
class="text-xs text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
Schließen
|
{$_('comic.picker.garment_picker_close')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{#if availableGarments.length === 0}
|
{#if availableGarments.length === 0}
|
||||||
<p class="text-xs text-muted-foreground">
|
<p class="text-xs text-muted-foreground">
|
||||||
Keine weiteren Kleidungsstücke verfügbar — lade welche in <a
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
href="/wardrobe"
|
{@html $_('comic.picker.garment_picker_empty_html')}
|
||||||
class="text-primary hover:underline">/wardrobe</a
|
|
||||||
> hoch.
|
|
||||||
</p>
|
</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="grid max-h-48 grid-cols-4 gap-2 overflow-y-auto sm:grid-cols-6">
|
<div class="grid max-h-48 grid-cols-4 gap-2 overflow-y-auto sm:grid-cols-6">
|
||||||
|
|
@ -307,14 +309,13 @@
|
||||||
|
|
||||||
{#if !hasFace}
|
{#if !hasFace}
|
||||||
<div class="rounded-md border border-error/30 bg-error/5 p-3 text-xs text-error" role="alert">
|
<div class="rounded-md border border-error/30 bg-error/5 p-3 text-xs text-error" role="alert">
|
||||||
Kein Gesichtsbild in diesem Space. Lade eins in
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<a href="/profile/me-images" class="underline hover:no-underline">Profil → Bilder</a>
|
{@html $_('comic.picker.no_face_alert_html')}
|
||||||
hoch — ohne Face-Ref kein Comic.
|
|
||||||
</div>
|
</div>
|
||||||
{:else if !hasBody}
|
{:else if !hasBody}
|
||||||
<p class="text-xs text-muted-foreground">
|
<p class="text-xs text-muted-foreground">
|
||||||
<TShirt size={12} class="inline" /> Tipp: Ein Body-Ref hilft, wenn der Comic Ganzkörper-Panels zeigen
|
<TShirt size={12} class="inline" />
|
||||||
soll.
|
{$_('comic.picker.body_tip')}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@
|
||||||
import { ArrowLeft, Archive, Heart, Plus, Sparkle, Trash } from '@mana/shared-icons';
|
import { ArrowLeft, Archive, Heart, Plus, Sparkle, Trash } from '@mana/shared-icons';
|
||||||
import { comicCharactersStore } from '../stores/characters.svelte';
|
import { comicCharactersStore } from '../stores/characters.svelte';
|
||||||
import { useCharacter } from '../queries';
|
import { useCharacter } from '../queries';
|
||||||
import { STYLE_LABELS } from '../constants';
|
|
||||||
import VariantTile from '../components/VariantTile.svelte';
|
import VariantTile from '../components/VariantTile.svelte';
|
||||||
import CharacterBuilder from '../components/CharacterBuilder.svelte';
|
import CharacterBuilder from '../components/CharacterBuilder.svelte';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
@ -36,7 +36,14 @@
|
||||||
|
|
||||||
async function handleDelete() {
|
async function handleDelete() {
|
||||||
if (!character) return;
|
if (!character) return;
|
||||||
if (!confirm(`Character "${character.name}" wirklich löschen?`)) return;
|
if (
|
||||||
|
!confirm(
|
||||||
|
$_('comic.character_detail.confirm_delete_character', {
|
||||||
|
values: { name: character.name },
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return;
|
||||||
await comicCharactersStore.deleteCharacter(character.id);
|
await comicCharactersStore.deleteCharacter(character.id);
|
||||||
await goto('/comic/character');
|
await goto('/comic/character');
|
||||||
}
|
}
|
||||||
|
|
@ -48,12 +55,7 @@
|
||||||
|
|
||||||
async function handleRemove(variantId: string) {
|
async function handleRemove(variantId: string) {
|
||||||
if (!character) return;
|
if (!character) return;
|
||||||
if (
|
if (!confirm($_('comic.character_detail.confirm_remove_variant'))) return;
|
||||||
!confirm(
|
|
||||||
'Variante aus dem Character entfernen? Das Bild bleibt in deiner Picture-Galerie und kann dort gelöscht werden.'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
await comicCharactersStore.removeVariant(character.id, variantId);
|
await comicCharactersStore.removeVariant(character.id, variantId);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -63,20 +65,24 @@
|
||||||
<a
|
<a
|
||||||
href="/comic/character"
|
href="/comic/character"
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted"
|
class="flex h-8 w-8 items-center justify-center rounded-lg text-muted-foreground hover:bg-muted"
|
||||||
aria-label="Zurück zu Characters"
|
aria-label={$_('comic.character_detail.back_aria')}
|
||||||
>
|
>
|
||||||
<ArrowLeft size={16} />
|
<ArrowLeft size={16} />
|
||||||
</a>
|
</a>
|
||||||
<span class="text-muted-foreground">Comic · Characters</span>
|
<span class="text-muted-foreground">{$_('comic.character_detail.breadcrumb')}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{#if !character}
|
{#if !character}
|
||||||
{#if character$.loading}
|
{#if character$.loading}
|
||||||
<p class="text-sm text-muted-foreground">Lädt…</p>
|
<p class="text-sm text-muted-foreground">{$_('comic.character_detail.loading')}</p>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="rounded-2xl border border-dashed border-border bg-background/50 p-8 text-center">
|
<div class="rounded-2xl border border-dashed border-border bg-background/50 p-8 text-center">
|
||||||
<p class="text-sm font-medium text-foreground">Character nicht gefunden.</p>
|
<p class="text-sm font-medium text-foreground">
|
||||||
<p class="mt-1 text-sm text-muted-foreground">Gelöscht oder in einem anderen Space.</p>
|
{$_('comic.character_detail.not_found')}
|
||||||
|
</p>
|
||||||
|
<p class="mt-1 text-sm text-muted-foreground">
|
||||||
|
{$_('comic.character_detail.not_found_hint')}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -87,15 +93,20 @@
|
||||||
<h1 class="truncate text-lg font-semibold text-foreground">{character.name}</h1>
|
<h1 class="truncate text-lg font-semibold text-foreground">{character.name}</h1>
|
||||||
<div class="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
<div class="mt-1 flex flex-wrap items-center gap-2 text-xs text-muted-foreground">
|
||||||
<span class="rounded-full bg-primary/10 px-2 py-0.5 font-medium text-primary">
|
<span class="rounded-full bg-primary/10 px-2 py-0.5 font-medium text-primary">
|
||||||
{STYLE_LABELS[character.style].de}
|
{$_('comic.styles.' + character.style)}
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
{character.variantMediaIds.length}
|
{character.variantMediaIds.length === 1
|
||||||
{character.variantMediaIds.length === 1 ? 'Variante' : 'Varianten'}
|
? $_('comic.character_detail.variant_one', {
|
||||||
|
values: { n: character.variantMediaIds.length },
|
||||||
|
})
|
||||||
|
: $_('comic.character_detail.variant_other', {
|
||||||
|
values: { n: character.variantMediaIds.length },
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
{#if !character.pinnedVariantId && character.variantMediaIds.length > 0}
|
{#if !character.pinnedVariantId && character.variantMediaIds.length > 0}
|
||||||
<span class="rounded-full bg-amber-500/15 px-2 py-0.5 font-medium text-amber-700"
|
<span class="rounded-full bg-amber-500/15 px-2 py-0.5 font-medium text-amber-700"
|
||||||
>Pin offen</span
|
>{$_('comic.character_detail.pin_open')}</span
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -103,8 +114,12 @@
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={handleToggleFavorite}
|
onclick={handleToggleFavorite}
|
||||||
aria-label={character.isFavorite ? 'Favorit entfernen' : 'Als Favorit markieren'}
|
aria-label={character.isFavorite
|
||||||
title={character.isFavorite ? 'Favorit entfernen' : 'Als Favorit markieren'}
|
? $_('comic.character_detail.favorite_remove')
|
||||||
|
: $_('comic.character_detail.favorite_set')}
|
||||||
|
title={character.isFavorite
|
||||||
|
? $_('comic.character_detail.favorite_remove')
|
||||||
|
: $_('comic.character_detail.favorite_set')}
|
||||||
class="flex h-8 w-8 items-center justify-center rounded-md transition-colors {character.isFavorite
|
class="flex h-8 w-8 items-center justify-center rounded-md transition-colors {character.isFavorite
|
||||||
? 'text-rose-500 hover:bg-rose-500/10'
|
? 'text-rose-500 hover:bg-rose-500/10'
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground'}"
|
||||||
|
|
@ -119,7 +134,7 @@
|
||||||
|
|
||||||
{#if character.addPrompt}
|
{#if character.addPrompt}
|
||||||
<div class="rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground">
|
<div class="rounded-md bg-muted/50 px-3 py-2 text-xs text-muted-foreground">
|
||||||
<strong class="text-foreground">Prompt-Add:</strong>
|
<strong class="text-foreground">{$_('comic.character_detail.prompt_add_label')}</strong>
|
||||||
{character.addPrompt}
|
{character.addPrompt}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -129,7 +144,7 @@
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
<h2 class="text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
Varianten
|
{$_('comic.character_detail.section_variants')}
|
||||||
</h2>
|
</h2>
|
||||||
{#if !showBuilder && !character.isArchived}
|
{#if !showBuilder && !character.isArchived}
|
||||||
<button
|
<button
|
||||||
|
|
@ -138,7 +153,7 @@
|
||||||
class="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
class="inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
|
||||||
>
|
>
|
||||||
<Plus size={12} />
|
<Plus size={12} />
|
||||||
Mehr Varianten
|
{$_('comic.character_detail.action_more_variants')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -147,10 +162,12 @@
|
||||||
<div
|
<div
|
||||||
class="rounded-2xl border border-dashed border-border bg-background/50 p-6 text-center"
|
class="rounded-2xl border border-dashed border-border bg-background/50 p-6 text-center"
|
||||||
>
|
>
|
||||||
<p class="text-sm font-medium text-foreground">Noch keine Varianten.</p>
|
<p class="text-sm font-medium text-foreground">
|
||||||
|
{$_('comic.character_detail.empty_variants_title')}
|
||||||
|
</p>
|
||||||
<p class="mt-1 text-sm text-muted-foreground">
|
<p class="mt-1 text-sm text-muted-foreground">
|
||||||
Klick oben rechts auf <strong class="text-foreground">+ Mehr Varianten</strong>, um die
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
ersten 4 zu generieren.
|
{@html $_('comic.character_detail.empty_variants_hint_html')}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -190,7 +207,9 @@
|
||||||
class="flex flex-1 items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted"
|
class="flex flex-1 items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground transition-colors hover:bg-muted"
|
||||||
>
|
>
|
||||||
<Archive size={14} />
|
<Archive size={14} />
|
||||||
{character.isArchived ? 'Wieder aktiv' : 'Archivieren'}
|
{character.isArchived
|
||||||
|
? $_('comic.character_detail.unarchive')
|
||||||
|
: $_('comic.character_detail.archive')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|
@ -198,7 +217,7 @@
|
||||||
class="flex flex-1 items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm text-error transition-colors hover:bg-error/10"
|
class="flex flex-1 items-center justify-center gap-2 rounded-md border border-border bg-background px-3 py-2 text-sm text-error transition-colors hover:bg-error/10"
|
||||||
>
|
>
|
||||||
<Trash size={14} />
|
<Trash size={14} />
|
||||||
Löschen
|
{$_('comic.character_detail.delete')}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -206,8 +225,8 @@
|
||||||
<p
|
<p
|
||||||
class="rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground"
|
class="rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground"
|
||||||
>
|
>
|
||||||
<Sparkle size={12} class="inline" /> Archivierter Character — keine Variant-Generierung möglich,
|
<Sparkle size={12} class="inline" />
|
||||||
bis wieder aktiviert.
|
{$_('comic.character_detail.archived_hint')}
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import { attemptsStore } from './stores/attempts.svelte';
|
import { attemptsStore } from './stores/attempts.svelte';
|
||||||
import type { AttemptAnswer } from './types';
|
import type { AttemptAnswer } from './types';
|
||||||
import { ArrowLeft, Check, X } from '@mana/shared-icons';
|
import { ArrowLeft, Check, X } from '@mana/shared-icons';
|
||||||
|
import { _ } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
quizId: string;
|
quizId: string;
|
||||||
|
|
@ -108,8 +109,9 @@
|
||||||
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<header class="header">
|
<header class="header">
|
||||||
<button class="back" onclick={() => goto('/quiz')} aria-label="Zurück">
|
<button class="back" onclick={() => goto('/quiz')} aria-label={$_('quiz.play_view.back_aria')}>
|
||||||
<ArrowLeft size={18} /> Quiz
|
<ArrowLeft size={18} />
|
||||||
|
{$_('quiz.play_view.back_label')}
|
||||||
</button>
|
</button>
|
||||||
{#if quiz}
|
{#if quiz}
|
||||||
<span class="title">{quiz.title}</span>
|
<span class="title">{quiz.title}</span>
|
||||||
|
|
@ -120,14 +122,18 @@
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{#if !quiz}
|
{#if !quiz}
|
||||||
<p class="empty">Quiz nicht gefunden.</p>
|
<p class="empty">{$_('quiz.play_view.empty_quiz')}</p>
|
||||||
{:else if total === 0}
|
{:else if total === 0}
|
||||||
<p class="empty">Dieses Quiz hat noch keine Fragen.</p>
|
<p class="empty">{$_('quiz.play_view.empty_no_questions')}</p>
|
||||||
{:else if finished}
|
{:else if finished}
|
||||||
<section class="result">
|
<section class="result">
|
||||||
<div class="score">
|
<div class="score">
|
||||||
<span class="score-num">{scorePct}%</span>
|
<span class="score-num">{scorePct}%</span>
|
||||||
<span class="score-sub">{correctCount} von {total} richtig</span>
|
<span class="score-sub"
|
||||||
|
>{$_('quiz.play_view.score_summary', {
|
||||||
|
values: { correct: correctCount, total },
|
||||||
|
})}</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<ol class="review">
|
<ol class="review">
|
||||||
{#each questions as q, i (q.id)}
|
{#each questions as q, i (q.id)}
|
||||||
|
|
@ -142,15 +148,18 @@
|
||||||
</div>
|
</div>
|
||||||
{#if q.type === 'text'}
|
{#if q.type === 'text'}
|
||||||
<p class="review-line">
|
<p class="review-line">
|
||||||
Deine Antwort: <strong>{ans?.textAnswer || '—'}</strong>
|
{$_('quiz.play_view.review_your_answer')}<strong
|
||||||
|
>{ans?.textAnswer || $_('quiz.play_view.placeholder_review_dash')}</strong
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
{#if !ans?.correct}
|
{#if !ans?.correct}
|
||||||
<p class="review-line">Richtig: <strong>{q.options[0]?.text}</strong></p>
|
<p class="review-line">
|
||||||
|
{$_('quiz.play_view.review_correct')}<strong>{q.options[0]?.text}</strong>
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
<p class="review-line">
|
<p class="review-line">
|
||||||
Richtig:
|
{$_('quiz.play_view.review_correct')}<strong>
|
||||||
<strong>
|
|
||||||
{q.options
|
{q.options
|
||||||
.filter((o) => o.isCorrect)
|
.filter((o) => o.isCorrect)
|
||||||
.map((o) => o.text)
|
.map((o) => o.text)
|
||||||
|
|
@ -162,8 +171,10 @@
|
||||||
{/each}
|
{/each}
|
||||||
</ol>
|
</ol>
|
||||||
<div class="result-actions">
|
<div class="result-actions">
|
||||||
<button class="secondary-btn" onclick={() => goto('/quiz')}>Zurück zur Liste</button>
|
<button class="secondary-btn" onclick={() => goto('/quiz')}
|
||||||
<button class="primary-btn" onclick={restart}>Nochmal spielen</button>
|
>{$_('quiz.play_view.action_back_to_list')}</button
|
||||||
|
>
|
||||||
|
<button class="primary-btn" onclick={restart}>{$_('quiz.play_view.action_replay')}</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{:else if current}
|
{:else if current}
|
||||||
|
|
@ -176,14 +187,16 @@
|
||||||
type="text"
|
type="text"
|
||||||
bind:value={textInput}
|
bind:value={textInput}
|
||||||
disabled={revealed}
|
disabled={revealed}
|
||||||
placeholder="Deine Antwort"
|
placeholder={$_('quiz.play_view.placeholder_text_answer')}
|
||||||
/>
|
/>
|
||||||
{#if revealed}
|
{#if revealed}
|
||||||
<p class="feedback" class:ok={answers.at(-1)?.correct}>
|
<p class="feedback" class:ok={answers.at(-1)?.correct}>
|
||||||
{#if answers.at(-1)?.correct}
|
{#if answers.at(-1)?.correct}
|
||||||
<Check size={14} weight="bold" /> Richtig!
|
<Check size={14} weight="bold" />
|
||||||
|
{$_('quiz.play_view.feedback_correct')}
|
||||||
{:else}
|
{:else}
|
||||||
<X size={14} weight="bold" /> Richtige Antwort:
|
<X size={14} weight="bold" />
|
||||||
|
{$_('quiz.play_view.feedback_incorrect_label')}
|
||||||
<strong>{current.options[0]?.text}</strong>
|
<strong>{current.options[0]?.text}</strong>
|
||||||
{/if}
|
{/if}
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -221,11 +234,13 @@
|
||||||
<div class="play-actions">
|
<div class="play-actions">
|
||||||
{#if !revealed}
|
{#if !revealed}
|
||||||
<button class="primary-btn" disabled={!canAnswer} onclick={reveal}>
|
<button class="primary-btn" disabled={!canAnswer} onclick={reveal}>
|
||||||
Antwort prüfen
|
{$_('quiz.play_view.action_check')}
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="primary-btn" onclick={next}>
|
<button class="primary-btn" onclick={next}>
|
||||||
{currentIndex + 1 >= total ? 'Ergebnis ansehen' : 'Weiter'}
|
{currentIndex + 1 >= total
|
||||||
|
? $_('quiz.play_view.action_view_result')
|
||||||
|
: $_('quiz.play_view.action_next')}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,6 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/BatchPanelEditor.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/comic/components/BatchPanelEditor.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte": 4,
|
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterBuilder.svelte": 4,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterCard.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterPicker.svelte": 6,
|
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterRefPicker.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/comic/components/CharacterRefPicker.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/PanelEditor.svelte": 3,
|
"apps/mana/apps/web/src/lib/modules/comic/components/PanelEditor.svelte": 3,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/PanelModelPicker.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/components/PanelModelPicker.svelte": 1,
|
||||||
|
|
@ -86,7 +85,6 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/components/VariantTile.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/views/CharactersView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte": 6,
|
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/views/ListView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/comic/views/ListView.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/community/components/ItemCard.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/community/views/DetailView.svelte": 1,
|
||||||
|
|
@ -150,7 +148,6 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/questions/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6,
|
"apps/mana/apps/web/src/lib/modules/questions/views/DetailView.svelte": 6,
|
||||||
"apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5,
|
"apps/mana/apps/web/src/lib/modules/quiz/ListView.svelte": 5,
|
||||||
"apps/mana/apps/web/src/lib/modules/quiz/PlayView.svelte": 6,
|
|
||||||
"apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/quotes/views/DetailView.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/research-lab/components/CompareColumn.svelte": 2,
|
"apps/mana/apps/web/src/lib/modules/research-lab/components/CompareColumn.svelte": 2,
|
||||||
"apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte": 4,
|
"apps/mana/apps/web/src/lib/modules/research-lab/ListView.svelte": 4,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
"apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/ai-workbench/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/broadcast/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/broadcast/views/DetailView.svelte": 1,
|
||||||
|
"apps/mana/apps/web/src/lib/modules/comic/views/DetailCharacterView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/comic/views/DetailView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/credits/ListView.svelte": 1,
|
||||||
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 1,
|
"apps/mana/apps/web/src/lib/modules/dreams/ListView.svelte": 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue