467f9a4e71
CI / update (push) Has been cancelled
Replace the pokedex grid on /tasks/rewards with a scrapbook "sticker album": category pages on warm paper, die-cut glossy vinyl stickers (debossed silhouettes for missing ones), rarity-scaled holo shine/foil/glow, and a large sticker-themed detail popup on click. Pages sort by rarity (rarest category + sticker first; "Allerlei" catch-all last); each category has an info popover explaining how its stickers drop, with the /tasks tag icons. Restyle the calendar as a cozy fridge wall-calendar (paper, washi tape, Fredoka month, cats stuck on dates as tilted die-cut stickers, weekend tint). Shows only the current user by default; tap a name in the monthly tally to fold in the other household member (per-person colour dots). Export ALWAYS_CATEGORIES + getTagsForCategory from stickers util.
182 lines
5.0 KiB
Svelte
182 lines
5.0 KiB
Svelte
<script>
|
||
import { scale, fade } from 'svelte/transition';
|
||
import { elasticOut } from 'svelte/easing';
|
||
import { getRarityColor } from '$lib/utils/stickers';
|
||
|
||
let { sticker, count = 0, firstEarnedLabel = '', sourceTask = '', onclose } = $props();
|
||
|
||
const rarityLabels = /** @type {Record<string, string>} */ ({
|
||
common: 'Gewöhnlich',
|
||
uncommon: 'Ungewöhnlich',
|
||
rare: 'Selten',
|
||
legendary: 'Legendär'
|
||
});
|
||
const foilByRarity = /** @type {Record<string, number>} */ ({
|
||
common: 0,
|
||
uncommon: 0.25,
|
||
rare: 0.65,
|
||
legendary: 1
|
||
});
|
||
</script>
|
||
|
||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||
<div class="backdrop" transition:fade={{ duration: 180 }} onclick={onclose} onkeydown={(e) => e.key === 'Escape' && onclose?.()}>
|
||
<div
|
||
class="card"
|
||
transition:scale={{ start: 0.85, duration: 320, easing: elasticOut }}
|
||
style="--rarity: {getRarityColor(sticker.rarity)}; --foil: {foilByRarity[sticker.rarity]};"
|
||
onclick={(e) => e.stopPropagation()}
|
||
>
|
||
<div class="stage">
|
||
<div class="vinyl">
|
||
<img src="/stickers/{sticker.image}" alt={sticker.name} />
|
||
<span class="foil" style="--m: url('/stickers/{sticker.image}');" aria-hidden="true"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="title">{sticker.name}</h2>
|
||
<span class="rarity-badge">{rarityLabels[sticker.rarity]}</span>
|
||
<p class="desc">{sticker.description}</p>
|
||
|
||
<dl class="stats">
|
||
<div><dt>Anzahl</dt><dd>×{count}</dd></div>
|
||
<div><dt>Zuerst erhalten</dt><dd>{firstEarnedLabel || '—'}</dd></div>
|
||
<div><dt>Quelle</dt><dd>{sourceTask || '—'}</dd></div>
|
||
</dl>
|
||
|
||
<button class="close" onclick={onclose}>Schließen</button>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.backdrop {
|
||
position: fixed;
|
||
inset: 0;
|
||
z-index: 1000;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: rgba(0, 0, 0, 0.5);
|
||
backdrop-filter: blur(4px);
|
||
padding: 1rem;
|
||
}
|
||
.card {
|
||
position: relative;
|
||
width: 100%;
|
||
max-width: 340px;
|
||
padding: 1.5rem 1.5rem 1.25rem;
|
||
text-align: center;
|
||
border-radius: var(--radius-card);
|
||
background:
|
||
radial-gradient(120% 70% at 50% 0%, color-mix(in srgb, var(--rarity) 22%, var(--color-surface)), var(--color-surface));
|
||
border: 2px solid color-mix(in srgb, var(--rarity) 60%, transparent);
|
||
box-shadow: 0 24px 60px rgba(0, 0, 0, 0.35);
|
||
}
|
||
|
||
.stage {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 170px;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
.stage::before {
|
||
content: '';
|
||
position: absolute;
|
||
width: 180px;
|
||
height: 180px;
|
||
border-radius: 50%;
|
||
background: radial-gradient(circle, var(--rarity), transparent 65%);
|
||
opacity: 0.4;
|
||
}
|
||
.vinyl { position: relative; width: 150px; height: 150px; }
|
||
.vinyl img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
/* die-cut white border + drop shadow */
|
||
filter:
|
||
drop-shadow(2px 0 0 #fff) drop-shadow(-2px 0 0 #fff)
|
||
drop-shadow(0 2px 0 #fff) drop-shadow(0 -2px 0 #fff)
|
||
drop-shadow(0 6px 7px rgba(0, 0, 0, 0.3));
|
||
}
|
||
.foil {
|
||
position: absolute;
|
||
inset: 0;
|
||
pointer-events: none;
|
||
-webkit-mask: var(--m) center / contain no-repeat;
|
||
mask: var(--m) center / contain no-repeat;
|
||
background: repeating-linear-gradient(
|
||
115deg,
|
||
rgba(0, 231, 255, 0.55) 0%,
|
||
rgba(255, 0, 231, 0.55) 7%,
|
||
rgba(255, 245, 0, 0.55) 14%,
|
||
rgba(0, 231, 255, 0.55) 21%
|
||
);
|
||
background-size: 250% 250%;
|
||
mix-blend-mode: color-dodge;
|
||
opacity: var(--foil);
|
||
animation: shift 6s linear infinite;
|
||
}
|
||
@keyframes shift {
|
||
to { background-position: 250% 0; }
|
||
}
|
||
|
||
.title {
|
||
margin: 0;
|
||
font-family: 'Fredoka', Helvetica, sans-serif;
|
||
font-weight: 700;
|
||
font-size: 1.85rem;
|
||
line-height: 1.1;
|
||
color: var(--color-text-primary);
|
||
}
|
||
.rarity-badge {
|
||
display: inline-block;
|
||
margin-top: 0.3rem;
|
||
font-size: 0.7rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
color: var(--rarity);
|
||
}
|
||
.desc {
|
||
margin: 0.5rem 0 1rem;
|
||
font-size: 0.88rem;
|
||
font-style: italic;
|
||
color: var(--color-text-secondary);
|
||
}
|
||
|
||
.stats {
|
||
margin: 0 0 1.25rem;
|
||
text-align: left;
|
||
font-size: 0.82rem;
|
||
}
|
||
.stats div {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
gap: 0.5rem;
|
||
padding: 0.4rem 0.2rem;
|
||
border-bottom: 1px solid var(--color-border);
|
||
}
|
||
.stats dt { color: var(--color-text-secondary); }
|
||
.stats dd { margin: 0; font-weight: 600; color: var(--color-text-primary); text-align: right; }
|
||
|
||
.close {
|
||
padding: 0.55rem 2rem;
|
||
border: none;
|
||
border-radius: var(--radius-pill);
|
||
background: var(--color-primary);
|
||
color: var(--color-text-on-primary);
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: background var(--transition-fast);
|
||
}
|
||
.close:hover { background: var(--color-primary-hover); }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.foil { animation: none; }
|
||
}
|
||
</style>
|