10 changed files with 241 additions and 43 deletions
@ -0,0 +1,90 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import { generateDarkPastelColor } from '$lib/utils/image_utils'; |
||||||
|
import { fade } from 'svelte/transition'; |
||||||
|
import { quintOut } from 'svelte/easing'; |
||||||
|
|
||||||
|
let { |
||||||
|
src, |
||||||
|
alt, |
||||||
|
eventId, |
||||||
|
className = 'w-full h-full object-cover', |
||||||
|
placeholderClassName = '', |
||||||
|
}: { |
||||||
|
src: string; |
||||||
|
alt: string; |
||||||
|
eventId: string; |
||||||
|
className?: string; |
||||||
|
placeholderClassName?: string; |
||||||
|
} = $props(); |
||||||
|
|
||||||
|
let imageLoaded = $state(false); |
||||||
|
let imageError = $state(false); |
||||||
|
let imgElement = $state<HTMLImageElement | null>(null); |
||||||
|
|
||||||
|
const placeholderColor = $derived.by(() => generateDarkPastelColor(eventId)); |
||||||
|
|
||||||
|
function loadImage() { |
||||||
|
if (!imgElement) return; |
||||||
|
|
||||||
|
imgElement.onload = () => { |
||||||
|
// Small delay to ensure smooth transition |
||||||
|
setTimeout(() => { |
||||||
|
imageLoaded = true; |
||||||
|
}, 100); |
||||||
|
}; |
||||||
|
|
||||||
|
imgElement.onerror = () => { |
||||||
|
imageError = true; |
||||||
|
}; |
||||||
|
|
||||||
|
// Set src after setting up event handlers |
||||||
|
imgElement.src = src; |
||||||
|
} |
||||||
|
|
||||||
|
function bindImg(element: HTMLImageElement) { |
||||||
|
imgElement = element; |
||||||
|
// Load image immediately when element is bound |
||||||
|
loadImage(); |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<div class="relative w-full h-full"> |
||||||
|
<!-- Placeholder --> |
||||||
|
<div |
||||||
|
class="absolute inset-0 {placeholderClassName}" |
||||||
|
style="background-color: {placeholderColor};" |
||||||
|
class:hidden={imageLoaded} |
||||||
|
> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Image --> |
||||||
|
<img |
||||||
|
bind:this={imgElement} |
||||||
|
{src} |
||||||
|
{alt} |
||||||
|
class="{className} {imageLoaded ? 'opacity-100' : 'opacity-0'}" |
||||||
|
style="transition: opacity 0.2s ease-out;" |
||||||
|
loading="lazy" |
||||||
|
decoding="async" |
||||||
|
class:hidden={imageError} |
||||||
|
onload={() => { |
||||||
|
setTimeout(() => { |
||||||
|
imageLoaded = true; |
||||||
|
}, 100); |
||||||
|
}} |
||||||
|
onerror={() => { |
||||||
|
imageError = true; |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Error state --> |
||||||
|
{#if imageError} |
||||||
|
<div |
||||||
|
class="absolute inset-0 flex items-center justify-center bg-gray-200 dark:bg-gray-700 {placeholderClassName}" |
||||||
|
> |
||||||
|
<div class="text-gray-500 dark:text-gray-400 text-xs"> |
||||||
|
Failed to load |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
</div> |
||||||
@ -0,0 +1,31 @@ |
|||||||
|
/** |
||||||
|
* Generate a dark-pastel color based on a string (like an event ID) |
||||||
|
* @param seed - The string to generate a color from |
||||||
|
* @returns A dark-pastel hex color |
||||||
|
*/ |
||||||
|
export function generateDarkPastelColor(seed: string): string { |
||||||
|
// Create a simple hash from the seed string
|
||||||
|
let hash = 0; |
||||||
|
for (let i = 0; i < seed.length; i++) { |
||||||
|
const char = seed.charCodeAt(i); |
||||||
|
hash = ((hash << 5) - hash) + char; |
||||||
|
hash = hash & hash; // Convert to 32-bit integer
|
||||||
|
} |
||||||
|
|
||||||
|
// Use the hash to generate lighter pastel colors
|
||||||
|
// Keep values in the 120-200 range for better pastel effect
|
||||||
|
const r = Math.abs(hash) % 80 + 120; // 120-200 range
|
||||||
|
const g = Math.abs(hash >> 8) % 80 + 120; // 120-200 range
|
||||||
|
const b = Math.abs(hash >> 16) % 80 + 120; // 120-200 range
|
||||||
|
|
||||||
|
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Test function to verify color generation |
||||||
|
* @param eventId - The event ID to test |
||||||
|
* @returns The generated color |
||||||
|
*/ |
||||||
|
export function testColorGeneration(eventId: string): string { |
||||||
|
return generateDarkPastelColor(eventId); |
||||||
|
}
|
||||||
Loading…
Reference in new issue