Files
blog/frontend/app/pages/index.vue
T
davide 4e8d1a892e Use the CorralitoComing seal as the site logo and favicon
The seal already carries the name, so it replaces the text wordmark in the
header and opens the footer. Small icon sizes use the central emblem only: the
circular lettering is unreadable below 48px.
2026-08-25 13:25:13 +02:00

170 lines
3.9 KiB
Vue

<script setup lang="ts">
import type { ArticleSummary, Paginated } from '#shared/types/blog'
const { data, error } = await useFetch<Paginated<ArticleSummary>>('/api/articles', {
key: 'home-articles',
query: { page: 1 },
})
/** The newest article leads the page; the next six fill the grid below it. */
const featured = computed(() => data.value?.items[0] ?? null)
const rest = computed(() => data.value?.items.slice(1, 7) ?? [])
const mediaUrl = useMediaUrl()
const featuredCover = computed(() => mediaUrl(featured.value?.cover))
const { public: config } = useRuntimeConfig()
// The home page is the site itself, so it carries the bare name as its title.
useHead({ titleTemplate: '%s', link: [{ rel: 'canonical', href: config.siteUrl }] })
useSeoMeta({
title: SITE_NAME,
description: SITE_DESCRIPTION,
ogTitle: SITE_NAME,
ogDescription: SITE_DESCRIPTION,
ogType: 'website',
ogUrl: config.siteUrl,
ogImage: `${config.siteUrl}/logo.png`,
})
</script>
<template>
<div>
<p v-if="error">Articles could not be loaded. Please try again later.</p>
<p v-else-if="!featured">Nothing has been published yet.</p>
<template v-else>
<article class="featured">
<NuxtLink class="featured-cover" :to="`/blog/${featured.slug}`" tabindex="-1" aria-hidden="true">
<img
v-if="featuredCover"
:src="featuredCover"
:alt="featured.cover?.alternativeText ?? ''"
:width="featured.cover?.width ?? undefined"
:height="featured.cover?.height ?? undefined"
>
<span v-else class="placeholder" />
</NuxtLink>
<div class="featured-text">
<p class="kicker">
{{ featured.category ? featured.category.name : 'Latest' }}
</p>
<h1>
<NuxtLink :to="`/blog/${featured.slug}`">{{ featured.title }}</NuxtLink>
</h1>
<p class="kicker date">
<time :datetime="isoDate(featured.publishedAt)">
{{ formatDate(featured.publishedAt) }}
</time>
</p>
<p class="read-more">
<NuxtLink :to="`/blog/${featured.slug}`">Read the article</NuxtLink>
</p>
</div>
</article>
<template v-if="rest.length">
<h2 class="section-heading kicker">More stories</h2>
<ul class="card-grid">
<li v-for="article in rest" :key="article.slug">
<ArticleCard :article="article" />
</li>
</ul>
</template>
<p class="more">
<NuxtLink to="/blog">View all articles</NuxtLink>
</p>
</template>
</div>
</template>
<style scoped>
.featured {
display: grid;
gap: 2.5rem;
align-items: center;
grid-template-columns: 1fr;
}
@media (min-width: 52rem) {
.featured {
grid-template-columns: 1.35fr 1fr;
gap: 3.5rem;
}
}
.featured-cover {
display: block;
overflow: hidden;
}
.featured-cover img,
.placeholder {
width: 100%;
aspect-ratio: 3 / 2;
object-fit: cover;
background: var(--color-surface);
}
.featured h1 {
margin-top: 0.6rem;
font-size: clamp(2rem, 4.5vw, 3.1rem);
}
.featured h1 a {
text-decoration: none;
}
.featured h1 a:hover {
text-decoration: underline;
text-underline-offset: 0.12em;
}
.date {
margin-top: 1rem;
font-weight: 400;
}
.read-more {
margin-top: 1.75rem;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.read-more a {
padding-bottom: 0.35rem;
text-decoration: none;
border-bottom: 1px solid var(--color-ink);
}
.more {
margin-top: 4rem;
text-align: center;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.more a {
padding: 0.85rem 2.5rem;
text-decoration: none;
border: 1px solid var(--color-ink);
color: var(--color-ink);
display: inline-block;
transition: background 0.2s ease, color 0.2s ease;
}
.more a:hover {
background: var(--color-ink);
color: var(--color-bg);
}
</style>