Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 w-full">
<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