feat: tap-to-preview stickers in gallery with glow effect
All checks were successful
CI / update (push) Successful in 3m33s

Add full rarity glow to gallery stickers matching the reward popup style.
Tapping an owned sticker opens a large preview card. Allow calendar
stickers to overdraw their cell on hover.
This commit is contained in:
2026-04-07 20:38:05 +02:00
parent e43bc9b067
commit 48beb50466
4 changed files with 23 additions and 8 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "homepage", "name": "homepage",
"version": "1.5.3", "version": "1.6.0",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -155,7 +155,6 @@
border-radius: 8px; border-radius: 8px;
border: 1px solid transparent; border: 1px solid transparent;
transition: background 120ms; transition: background 120ms;
overflow: hidden;
} }
.cal-day.outside { .cal-day.outside {
opacity: 0.25; opacity: 0.25;

View File

@@ -3,7 +3,7 @@
import { elasticOut } from 'svelte/easing'; import { elasticOut } from 'svelte/easing';
import { getRarityColor } from '$lib/utils/stickers'; import { getRarityColor } from '$lib/utils/stickers';
let { sticker, onclose } = $props(); let { sticker, onclose, title = 'Sticker erhalten!', buttonText = 'Toll!', bounce = true } = $props();
const rarityLabels = /** @type {Record<string, string>} */ ({ const rarityLabels = /** @type {Record<string, string>} */ ({
common: 'Gewöhnlich', common: 'Gewöhnlich',
@@ -17,19 +17,21 @@
<div class="popup-backdrop" transition:fade={{ duration: 200 }} onclick={onclose} onkeydown={e => e.key === 'Escape' && onclose?.()}> <div class="popup-backdrop" transition:fade={{ duration: 200 }} onclick={onclose} onkeydown={e => e.key === 'Escape' && onclose?.()}>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="popup-card" transition:scale={{ start: 0.5, duration: 500, easing: elasticOut }} onclick={e => e.stopPropagation()}> <div class="popup-card" transition:scale={bounce ? { start: 0.5, duration: 500, easing: elasticOut } : { start: 0.85, duration: 200 }} onclick={e => e.stopPropagation()}>
<div class="sticker-display" style="--rarity-color: {getRarityColor(sticker.rarity)}"> <div class="sticker-display" style="--rarity-color: {getRarityColor(sticker.rarity)}">
<img class="sticker-img" src="/stickers/{sticker.image}" alt={sticker.name} /> <img class="sticker-img" src="/stickers/{sticker.image}" alt={sticker.name} />
</div> </div>
<div class="popup-text"> <div class="popup-text">
<h3>Sticker erhalten!</h3> <h3>{title}</h3>
<p class="sticker-name">{sticker.name}</p> {#if title !== sticker.name}
<p class="sticker-name">{sticker.name}</p>
{/if}
<p class="sticker-desc">{sticker.description}</p> <p class="sticker-desc">{sticker.description}</p>
<span class="rarity-badge" style="color: {getRarityColor(sticker.rarity)}"> <span class="rarity-badge" style="color: {getRarityColor(sticker.rarity)}">
{rarityLabels[sticker.rarity] || sticker.rarity} {rarityLabels[sticker.rarity] || sticker.rarity}
</span> </span>
</div> </div>
<button class="btn-close" onclick={onclose}>Toll!</button> <button class="btn-close" onclick={onclose}>{buttonText}</button>
</div> </div>
</div> </div>

View File

@@ -6,9 +6,13 @@
import { flip } from 'svelte/animate'; import { flip } from 'svelte/animate';
import { Trash2 } from '@lucide/svelte'; import { Trash2 } from '@lucide/svelte';
import StickerCalendar from '$lib/components/tasks/StickerCalendar.svelte'; import StickerCalendar from '$lib/components/tasks/StickerCalendar.svelte';
import StickerPopup from '$lib/components/tasks/StickerPopup.svelte';
let { data } = $props(); let { data } = $props();
/** @type {import('$lib/utils/stickers').Sticker | null} */
let selectedSticker = $state(null);
let stats = $state(data.stats || { userStats: [], userStickers: [], recentCompletions: [] }); let stats = $state(data.stats || { userStats: [], userStickers: [], recentCompletions: [] });
let currentUser = $derived(data.session?.user?.nickname || ''); let currentUser = $derived(data.session?.user?.nickname || '');
@@ -99,12 +103,15 @@
{#each sortedStickers as sticker (sticker.id)} {#each sortedStickers as sticker (sticker.id)}
{@const count = displayedStickers.get(sticker.id) || 0} {@const count = displayedStickers.get(sticker.id) || 0}
{@const owned = count > 0} {@const owned = count > 0}
<!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<div <div
class="sticker-card" class="sticker-card"
class:owned class:owned
class:locked={!owned} class:locked={!owned}
animate:flip={{ duration: 300 }} animate:flip={{ duration: 300 }}
style="--rarity-color: {getRarityColor(sticker.rarity)}" style="--rarity-color: {getRarityColor(sticker.rarity)}"
onclick={() => owned && (selectedSticker = sticker)}
> >
<div class="sticker-visual"> <div class="sticker-visual">
{#if owned} {#if owned}
@@ -156,6 +163,10 @@
</section> </section>
{/if} {/if}
{#if selectedSticker}
<StickerPopup sticker={selectedSticker} title={selectedSticker.name} buttonText="Schließen" bounce={false} onclose={() => selectedSticker = null} />
{/if}
<div class="danger-zone"> <div class="danger-zone">
<button class="btn-clear" onclick={clearHistory}> <button class="btn-clear" onclick={clearHistory}>
<Trash2 size={14} /> <Trash2 size={14} />
@@ -229,6 +240,7 @@
.sticker-card.owned { .sticker-card.owned {
border-color: var(--rarity-color); border-color: var(--rarity-color);
border-width: 1.5px; border-width: 1.5px;
cursor: pointer;
} }
.sticker-card.owned:hover { .sticker-card.owned:hover {
transform: translateY(-2px); transform: translateY(-2px);
@@ -249,13 +261,15 @@
margin-bottom: 0.4rem; margin-bottom: 0.4rem;
} }
.owned .sticker-visual { .owned .sticker-visual {
background: radial-gradient(circle, color-mix(in srgb, var(--rarity-color) 20%, transparent) 0%, transparent 70%); background: radial-gradient(circle, var(--rarity-color) 0%, transparent 70%);
border-radius: 50%; border-radius: 50%;
opacity: 0.95;
} }
.sticker-img { .sticker-img {
width: 52px; width: 52px;
height: 52px; height: 52px;
object-fit: contain; object-fit: contain;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.15));
} }
.sticker-unknown { .sticker-unknown {
font-size: 1.6rem; font-size: 1.6rem;