Mobile-first layout with breakpoints at 48rem (tablet) and 64rem (desktop): stacked nav on phones, progressive column counts for the article grid and footer, scrollable markdown tables, larger tap targets on nav links.
93 lines
2.0 KiB
Vue
93 lines
2.0 KiB
Vue
<script setup lang="ts">
|
|
import type { ArticleSummary } from '#shared/types/blog'
|
|
|
|
const props = defineProps<{ article: ArticleSummary }>()
|
|
|
|
const { locale } = useI18n()
|
|
const mediaUrl = useMediaUrl()
|
|
const cover = computed(() => mediaUrl(props.article.cover))
|
|
</script>
|
|
|
|
<template>
|
|
<article class="card">
|
|
<!-- No placeholder box when there is no cover: an empty grey rectangle
|
|
reads as a broken image rather than as a deliberate choice. -->
|
|
<NuxtLinkLocale
|
|
v-if="cover"
|
|
class="cover-link"
|
|
:to="`/blog/${article.slug}`"
|
|
tabindex="-1"
|
|
aria-hidden="true"
|
|
>
|
|
<img
|
|
class="cover"
|
|
:src="cover"
|
|
:alt="article.cover?.alternativeText ?? ''"
|
|
:width="article.cover?.width ?? undefined"
|
|
:height="article.cover?.height ?? undefined"
|
|
loading="lazy"
|
|
>
|
|
</NuxtLinkLocale>
|
|
|
|
<p v-if="article.category" class="kicker">{{ article.category.name }}</p>
|
|
|
|
<h2>
|
|
<NuxtLinkLocale :to="`/blog/${article.slug}`">{{ article.title }}</NuxtLinkLocale>
|
|
</h2>
|
|
|
|
<p class="kicker date">
|
|
<time :datetime="isoDate(article.publishedAt)">
|
|
{{ formatDate(article.publishedAt, locale) }}
|
|
</time>
|
|
</p>
|
|
</article>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.cover-link {
|
|
display: block;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.cover {
|
|
width: 100%;
|
|
aspect-ratio: 4 / 5;
|
|
object-fit: cover;
|
|
background: var(--color-surface);
|
|
transition: transform 0.4s ease;
|
|
}
|
|
|
|
.cover-link:hover .cover {
|
|
transform: scale(1.03);
|
|
}
|
|
|
|
/* Without a cover the kicker leads the card, so it needs no offset. */
|
|
.cover-link + .kicker,
|
|
.cover-link + h2 {
|
|
margin-top: 1.1rem;
|
|
}
|
|
|
|
h2 {
|
|
margin-top: 0.5rem;
|
|
font-size: 1.35rem;
|
|
}
|
|
|
|
h2 a {
|
|
text-decoration: none;
|
|
background-image: linear-gradient(var(--color-ink), var(--color-ink));
|
|
background-size: 0 1px;
|
|
background-position: 0 100%;
|
|
background-repeat: no-repeat;
|
|
transition: background-size 0.3s ease;
|
|
}
|
|
|
|
h2 a:hover {
|
|
background-size: 100% 1px;
|
|
}
|
|
|
|
.date {
|
|
margin-top: 0.6rem;
|
|
font-weight: 400;
|
|
}
|
|
</style>
|