Skip to content
2 changes: 1 addition & 1 deletion app/components/Landing/IntroHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ onPrehydrate(el => {
/>
<p
id="intro-header-tagline"
class="text-fg-muted text-lg sm:text-xl max-w-xl mb-12 lg:mb-14 motion-safe:animate-slide-up motion-safe:animate-fill-both delay-100"
class="text-fg-muted text-lg ms-auto me-auto sm:text-xl max-w-xl mb-12 lg:mb-14 motion-safe:animate-slide-up motion-safe:animate-fill-both delay-100"
>
{{ $t('tagline') }}
</p>
Expand Down
37 changes: 37 additions & 0 deletions app/components/Noodle/EmojiDay/Logo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<template>
<div class="relative flex">
<ThemedLogo v-if="colorMode.value === 'dark'" :emojiSets="darkTheme" />
<ThemedLogo v-else :emojiSets="lightTheme" />
</div>
</template>

<script setup lang="ts">
import ThemedLogo from './ThemedLogo.vue'
import red from './emojisets/red.png'
import blue from './emojisets/blue.png'
import faces from './emojisets/faces.png'
import gold from './emojisets/gold.png'
import hearts from './emojisets/hearts.png'
import green from './emojisets/green.png'
import earth from './emojisets/earth.png'
import light from './emojisets/themed-light.png'
import dark from './emojisets/themed-dark.png'

const colorMode = useColorMode()

const lightTheme = {
red,
blue,
hearts,
gold,
faces,
green,
earth,
themed: light,
}

const darkTheme = {
...lightTheme,
themed: dark,
}
</script>
100 changes: 100 additions & 0 deletions app/components/Noodle/EmojiDay/ThemedLogo.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<template>
<div ref="sizer" class="sizer w-full sm:w-[540px] md:w-[640px] lg:w-[900px]"></div>
</template>

<style>
.sizer {
position: relative;
height: auto;
aspect-ratio: 900/360;
}
</style>

<script setup lang="ts">
import { init } from './emoji-thing'
import { onMounted, onUnmounted, useTemplateRef } from 'vue'

const props = defineProps<{
emojiSets: Record<string, string>
}>()

let emojiSetImagesPromise: undefined | Promise<Record<string, HTMLImageElement>>

function loadEmojiSetImages() {
if (emojiSetImagesPromise) {
return emojiSetImagesPromise
}

const promise = Promise.all(
Object.entries(props.emojiSets).map(([name, src]) => {
return new Promise<[string, HTMLImageElement]>((resolve, reject) => {
const image = new Image()
image.addEventListener('load', () => {
resolve([name, image])
})
image.addEventListener('error', () => {
if (promise === emojiSetImagesPromise) {
emojiSetImagesPromise = undefined
}
reject(new Error(`could not load image ${JSON.stringify(src)}`))
})
image.src = src
})
}),
).then(sets => Object.fromEntries(sets))

emojiSetImagesPromise = promise
return emojiSetImagesPromise
}

let running = false
let handle: undefined | ReturnType<typeof init>
let observer: undefined | IntersectionObserver

const sizerRef = useTemplateRef<HTMLDivElement>('sizer')

onMounted(() => {
const sizer = sizerRef.value
if (!sizer) {
return
}

observer = new IntersectionObserver(
([entry]) => {
if (!entry) {
return
}

if (entry.isIntersecting) {
running = true
loadEmojiSetImages().then(sets => {
if (!running) {
return
}
if (!handle) {
handle = init(sizer, sets)
}
handle.start()
})
} else {
running = false
handle?.pause()
}
},
{
rootMargin: '100px',
},
)
observer.observe(sizer)
})

onUnmounted(() => {
handle?.destroy()
handle = undefined

observer?.disconnect()
observer = undefined

running = false
})
</script>
Loading
Loading