aboutsummaryrefslogtreecommitdiff
path: root/src/lib/FallbackBadge.svelte
blob: f78d6e03720a6b9d071dac68e57d06e5822bc7ff (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
<script lang="ts">
	import locale from '$stores/locale';
	import { tweened } from 'svelte/motion';
	import type { Badge } from './Database/userBadges';
	import Tooltip from './Tooltip/LinkedTooltip.svelte';
	import { databaseTimeToDate } from './Utility/time';
	import { cubicOut } from 'svelte/easing';
	import { dev } from '$app/environment';

	export let source: string | null | undefined;
	export let alternative: string | null | undefined;
	export let fallback: string | null | undefined;
	export let maxReplaceCount = 1;
	export let replaceDelay = 1000;
	export let error = 'https://i2.kym-cdn.com/photos/images/newsfeed/000/290/992/0aa.jpg';
	export let hideOnError = false;
	export let badge: Badge;
	export let style = '';
	export let selectedBadge: Badge | null = null;

	let replaceCount = 0;
	let badgeReference: HTMLImageElement;
	const mouse = tweened(
		{ x: 0, y: 0 },
		{
			duration: 75,
			easing: cubicOut
		}
	);

	const delayedReplace = (event: Event, image: string | undefined | null) => {
		if (replaceCount >= maxReplaceCount) return;

		setTimeout(() => {
			(event.target as HTMLImageElement).src = image || '';

			replaceCount += 1;
		}, replaceDelay);
	};

	const handleMouseMove = (event: MouseEvent) => {
		const boundingRectangle = badgeReference.getBoundingClientRect();
		const factor = 1.25;
		const limit = 50;

		if ($mouse.x === 0 && $mouse.y === 0) $mouse = { x: event.clientX, y: event.clientY };

		$mouse.x +=
			(-(event.clientX - boundingRectangle.left - boundingRectangle.width / 2) - $mouse.x) * factor;
		$mouse.y +=
			(-(event.clientY - boundingRectangle.top - boundingRectangle.height / 2) - $mouse.y) * factor;
		$mouse.x = Math.max(Math.min($mouse.x, limit), -limit);
		$mouse.y = Math.max(Math.min($mouse.y, limit), -limit);
	};

	const handleMouseLeave = () => {
		$mouse = { x: 0, y: 0 };
	};
</script>

{#if replaceCount < maxReplaceCount}
	<Tooltip
		content={`${dev ? `${badge.id}\n` : ''}${
			badge.time ? $locale().dateFormatter(databaseTimeToDate(badge.time)) : ''
		}${badge.description ? `\n${badge.description}` : ''}${
			badge.designer ? `\nDesigner: ${badge.designer}` : ''
		}${badge.source ? `\nSource: ${badge.source}` : ''}`}
		id={`badge-${badge.id}`}
		pin={`badge-${badge.id}`}
		pinPosition="top"
	>
		<a
			href={'#'}
			target="_blank"
			class="badge-container badge"
			on:mousemove={handleMouseMove}
			on:mouseleave={handleMouseLeave}
			on:click={(e) => {
				e.preventDefault();

				selectedBadge = badge;
			}}
		>
			<img
				src={source}
				alt={alternative}
				loading="lazy"
				class="badge"
				bind:this={badgeReference}
				style="transform: perspective(1000px) rotateX({$mouse.y / 10}deg) rotateY({-$mouse.x /
					10}deg); ${style}"
				on:error={(e) => delayedReplace(e, fallback)}
			/>
		</a>
	</Tooltip>
{:else if !hideOnError}
	<img src={error} alt="Not found" loading="lazy" class="badge" />
{/if}

<style lang="scss">
	$transition: transform 0.325s ease;

	.badge {
		display: flex;
		flex-direction: column;
		justify-content: center;
		align-items: center;
		transition: $transition;
		box-sizing: border-box;
		border-radius: 8px;
	}

	.badge:hover {
		transform: scale(1.075);
		position: relative;
		z-index: 2;
		transition: $transition;
	}
</style>