Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@ jobs:

- name: Typecheck
run: pnpm run typecheck

- name: Build
run: pnpm run generate
4 changes: 2 additions & 2 deletions app/components/AppFooter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const { footer } = useAppConfig()
<UContainer class="flex flex-col items-center gap-3">
<div class="flex items-center gap-2">
<UButton
v-for="(link, index) of footer?.links"
:key="index"
v-for="link of footer?.links"
:key="link['aria-label'] || link.to"
v-bind="{ size: 'xs', color: 'neutral', variant: 'ghost', ...link }"
class="text-highlighted hover:text-highlighted"
/>
Expand Down
2 changes: 0 additions & 2 deletions app/components/LabCard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { getLabPath, labStatusIconMap, labStatusMap, type LabEntry } from '../utils/labs'

defineProps<{
lab: LabEntry
}>()
Expand Down
97 changes: 23 additions & 74 deletions app/components/landing/Hero.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ const avatarSrc = computed(() => colorMode.value === 'dark'
: global.picture?.light
)

const heroInitial = { scale: 1.1, opacity: 0, filter: 'blur(20px)' }
const heroAnimate = { scale: 1, opacity: 1, filter: 'blur(0px)' }
const heroTransition = (delay: number) => ({ duration: 0.6, delay })

defineProps<{
page: HeroPage
}>()
Expand All @@ -31,29 +35,18 @@ defineProps<{
>
<template #headline>
<Motion
:initial="{
scale: 1.1,
opacity: 0,
filter: 'blur(20px)'
}"
:animate="{
scale: 1,
opacity: 1,
filter: 'blur(0px)'
}"
:transition="{
duration: 0.6,
delay: 0.1
}"
:initial="heroInitial"
:animate="heroAnimate"
:transition="heroTransition(0.1)"
>
<NuxtImg
class="size-18 rounded-full object-cover ring ring-default ring-offset-3 ring-offset-(--ui-bg)"
:src="avatarSrc"
:alt="global.picture?.alt!"
:alt="global.picture?.alt ?? 'Profile picture'"
width="72"
height="72"
sizes="72px"
densities="x1 x2 x3"
densities="x1 x2"
fit="cover"
loading="eager"
/>
Expand All @@ -63,41 +56,19 @@ defineProps<{
<template #title>
<div class="space-y-4 text-center">
<Motion
:initial="{
scale: 1.1,
opacity: 0,
filter: 'blur(20px)'
}"
:animate="{
scale: 1,
opacity: 1,
filter: 'blur(0px)'
}"
:transition="{
duration: 0.6,
delay: 0.1
}"
:initial="heroInitial"
:animate="heroAnimate"
:transition="heroTransition(0.1)"
>
<p class="text-sm font-medium uppercase tracking-[0.28em] text-muted">
{{ page.hero.name }}
</p>
</Motion>

<Motion
:initial="{
scale: 1.1,
opacity: 0,
filter: 'blur(20px)'
}"
:animate="{
scale: 1,
opacity: 1,
filter: 'blur(0px)'
}"
:transition="{
duration: 0.6,
delay: 0.2
}"
:initial="heroInitial"
:animate="heroAnimate"
:transition="heroTransition(0.2)"
>
{{ page.hero.role }}
</Motion>
Expand All @@ -106,46 +77,24 @@ defineProps<{

<template #description>
<Motion
:initial="{
scale: 1.1,
opacity: 0,
filter: 'blur(20px)'
}"
:animate="{
scale: 1,
opacity: 1,
filter: 'blur(0px)'
}"
:transition="{
duration: 0.6,
delay: 0.35
}"
:initial="heroInitial"
:animate="heroAnimate"
:transition="heroTransition(0.35)"
>
{{ page.hero.intro }}
</Motion>
</template>

<template #links>
<Motion
:initial="{
scale: 1.1,
opacity: 0,
filter: 'blur(20px)'
}"
:animate="{
scale: 1,
opacity: 1,
filter: 'blur(0px)'
}"
:transition="{
duration: 0.6,
delay: 0.5
}"
:initial="heroInitial"
:animate="heroAnimate"
:transition="heroTransition(0.5)"
>
<div class="flex flex-wrap items-center justify-center gap-3">
<UButton
v-for="(link, index) of footer?.links"
:key="index"
v-for="link of footer?.links"
:key="link['aria-label'] || link.to"
v-bind="{ size: 'xs', color: 'neutral', variant: 'ghost', ...link }"
class="text-highlighted hover:text-highlighted"
/>
Expand Down
2 changes: 0 additions & 2 deletions app/components/landing/LabsTeaser.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { getLabPath, type LabEntry } from '../../utils/labs'

type LabsSection = {
title: string
description: string
Expand Down
6 changes: 1 addition & 5 deletions app/components/landing/SpeakingTeaser.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,4 @@
<script setup lang="ts">
import TalkPreviewCard from '../talks/TalkPreviewCard.vue'
import type { TalkEntry } from '../../utils/speaking'
import { getTalkPath } from '../../utils/speaking'

type SpeakingSection = {
title: string
description: string
Expand Down Expand Up @@ -45,7 +41,7 @@ defineProps<{
</Motion>

<div class="space-y-4">
<TalkPreviewCard
<TalksTalkPreviewCard
v-if="talk"
:talk="talk"
variant="featured"
Expand Down
4 changes: 1 addition & 3 deletions app/components/talks/TalkPreviewCard.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import type { TalkEntry } from '../../utils/speaking'

type PreviewAction = {
label: string
to: string
Expand Down Expand Up @@ -47,7 +45,7 @@ const subtitle = computed(() => {
class="border border-default"
:class="variant === 'list' ? 'h-full' : ''"
:ui="{
body: variant === 'featured' ? 'p-6 sm:p-8' : 'p-6 sm:p-8'
body: 'p-6 sm:p-8'
}"
>
<div
Expand Down
3 changes: 0 additions & 3 deletions app/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script setup lang="ts">
import { sortLabs } from '../utils/labs'
import { getLatestTalk } from '../utils/speaking'

const { data: page } = await useAsyncData('index', () => {
return queryCollection('index').first()
})
Expand Down
7 changes: 3 additions & 4 deletions app/pages/labs/[slug].vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { formatLabDate, getLabSlug, labStatusIconMap, labStatusMap, sortLabs } from '../../utils/labs'

const route = useRoute()
const slug = Array.isArray(route.params.slug) ? route.params.slug[0] : route.params.slug

Expand Down Expand Up @@ -97,11 +95,12 @@ const hasImage = computed(() => Boolean(lab.value?.image))
body: 'p-0'
}"
>
<img
<NuxtImg
:src="lab.image"
:alt="`${lab.title} project preview`"
class="h-full w-full object-cover"
>
loading="lazy"
/>
</UCard>

<UCard
Expand Down
2 changes: 0 additions & 2 deletions app/pages/labs/index.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { sortLabs } from '../../utils/labs'

const { data: page } = await useAsyncData('labs-page', () => {
return queryCollection('pages').path('/labs').first()
})
Expand Down
2 changes: 0 additions & 2 deletions app/pages/speaking/[slug].vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
<script setup lang="ts">
import { getTalkSlug, resolveTalkEntry, sortTalks, type ResolvedTalkResource } from '../../utils/speaking'

const route = useRoute()
const slug = Array.isArray(route.params.slug) ? route.params.slug[0] : route.params.slug

Expand Down
5 changes: 1 addition & 4 deletions app/pages/speaking/index.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script setup lang="ts">
import TalkPreviewCard from '../../components/talks/TalkPreviewCard.vue'
import { getTalkPath, sortTalks } from '../../utils/speaking'

const { data: page } = await useAsyncData('speaking', () => {
return queryCollection('speaking').first()
})
Expand Down Expand Up @@ -68,7 +65,7 @@ useSeoMeta({
:transition="{ delay: index * 0.08 }"
:in-view-options="{ once: true }"
>
<TalkPreviewCard
<TalksTalkPreviewCard
:talk="talk"
variant="list"
:show-summary="true"
Expand Down
2 changes: 2 additions & 0 deletions app/utils/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export function copyToClipboard(toCopy: string, message: string = 'Copied to cli
const toast = useToast()
navigator.clipboard.writeText(toCopy).then(() => {
toast.add({ title: message, color: 'success', icon: 'i-lucide-check-circle' })
}).catch(() => {
toast.add({ title: 'Failed to copy to clipboard', color: 'error', icon: 'i-lucide-x-circle' })
})
}
9 changes: 9 additions & 0 deletions app/utils/date.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getTimestamp = (value?: string | Date | null): number | null => {
if (!value) {
return null
}

const timestamp = new Date(value).getTime()

return Number.isNaN(timestamp) ? null : timestamp
}
8 changes: 1 addition & 7 deletions app/utils/labs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,9 @@ export const labStatusIconMap: Record<LabStatus, string> = {
paused: 'i-lucide-pause'
}

const getTimestamp = (value: string | Date) => {
const timestamp = new Date(value).getTime()

return Number.isNaN(timestamp) ? 0 : timestamp
}

export const sortLabs = <Lab extends LabEntry>(labs: Lab[]) => {
return labs.slice().sort((left, right) => {
return getTimestamp(right.date) - getTimestamp(left.date) || left.title.localeCompare(right.title)
return (getTimestamp(right.date) ?? 0) - (getTimestamp(left.date) ?? 0) || left.title.localeCompare(right.title)
})
}

Expand Down
10 changes: 0 additions & 10 deletions app/utils/speaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,6 @@ export type ResolvedTalkEntry<Talk extends TalkEntry = TalkEntry> = Omit<Talk, '
resources: ResolvedTalkResource[]
}

const getTimestamp = (value?: string | Date | null) => {
if (!value) {
return null
}

const timestamp = new Date(value).getTime()

return Number.isNaN(timestamp) ? null : timestamp
}

export const sortTalks = <Talk extends TalkEntry>(talks: Talk[]) => {
return talks.slice().sort((left, right) => {
const leftTimestamp = getTimestamp(left.date)
Expand Down