Files
blog/frontend/app/layouts/default.vue
T
2026-09-08 23:34:42 +02:00

306 lines
6.6 KiB
Vue

<script setup lang="ts">
const { locale, setLocale, t } = useLocale()
const otherLocale = computed(() => (locale.value === 'it' ? 'en' : 'it'))
/**
* Switches the site language. On an article/category page, jumps to the
* translated counterpart's slug if one exists; elsewhere reloads the current
* route so it refetches its content in the new language.
*/
function switchLanguage() {
setLocale(otherLocale.value)
reloadNuxtApp({ path: useRoute().fullPath })
}
</script>
<template>
<div class="page">
<a class="skip-link" href="#main">{{ t('skipToContent') }}</a>
<header class="site-header">
<div class="container">
<p class="wordmark">
<NuxtLink to="/">
<img src="/logo.png" :alt="SITE_NAME" width="400" height="400">
</NuxtLink>
</p>
<p class="tagline kicker">{{ t('tagline') }}</p>
</div>
<nav class="site-nav" :aria-label="t('navPrimary')">
<div class="container nav-inner">
<ul>
<li><NuxtLink to="/">{{ t('navHome') }}</NuxtLink></li>
<li><NuxtLink to="/blog">{{ t('navAllArticles') }}</NuxtLink></li>
<li><NuxtLink to="/come-difendersi-dal-corralito">{{ t('navCorralito') }}</NuxtLink></li>
</ul>
<button type="button" class="lang-switch" @click="switchLanguage">
{{ t('switchLanguage') }}
</button>
</div>
</nav>
</header>
<main id="main" class="container site-main">
<slot />
</main>
<footer class="site-footer">
<div class="container footer-inner">
<div class="footer-block">
<img class="footer-logo" src="/logo.png" :alt="SITE_NAME" width="400" height="400">
<p class="muted">{{ t('tagline') }}</p>
<address class="contact">
{{ t('footerContact') }}
<a :href="`mailto:${SITE_EMAIL}`">{{ SITE_EMAIL }}</a>
</address>
</div>
<nav class="footer-block" :aria-label="t('footerNavigate')">
<p class="kicker">{{ t('footerNavigate') }}</p>
<ul>
<li><NuxtLink to="/">{{ t('navHome') }}</NuxtLink></li>
<li><NuxtLink to="/blog">{{ t('navAllArticles') }}</NuxtLink></li>
<li><NuxtLink to="/come-difendersi-dal-corralito">{{ t('navCorralito') }}</NuxtLink></li>
</ul>
</nav>
<nav class="footer-block" :aria-label="t('footerFollow')">
<p class="kicker">{{ t('footerFollow') }}</p>
<ul>
<li v-for="social in SOCIAL_LINKS" :key="social.name">
<a class="social" :href="social.url" target="_blank" rel="noopener me">
<SocialIcon :name="social.name" />
<span>{{ social.name }}</span>
</a>
</li>
</ul>
</nav>
</div>
<p class="container copyright kicker">
&copy; {{ new Date().getFullYear() }} {{ SITE_NAME }}. {{ t('footerRights') }}
</p>
</footer>
</div>
</template>
<style scoped>
.page {
display: flex;
flex-direction: column;
min-height: 100vh;
}
/* Header: centred wordmark over a thin rule of links. */
.site-header {
padding-top: 1.5rem;
text-align: center;
border-bottom: 1px solid var(--color-border);
}
@media (min-width: 48rem) {
.site-header {
padding-top: 2.5rem;
}
}
.wordmark {
margin: 0;
}
.wordmark a {
display: inline-block;
text-decoration: none;
}
/* The seal carries the name itself, so it stands in for a text wordmark. */
.wordmark img {
width: clamp(6rem, 14vw, 8.5rem);
height: auto;
}
.tagline {
margin-top: 0.6rem;
}
.site-nav {
margin-top: 1.25rem;
border-top: 1px solid var(--color-border);
}
.nav-inner {
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 0.75rem 1.5rem;
padding-block: 0.6rem;
}
.site-nav ul {
display: flex;
flex-wrap: wrap;
gap: 0 1.25rem;
justify-content: center;
padding: 0;
margin: 0;
list-style: none;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.lang-switch {
font: inherit;
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.14em;
text-transform: uppercase;
padding: 0.35rem 0.75rem;
color: var(--color-body);
background: none;
border: 1px solid var(--color-border);
cursor: pointer;
}
.lang-switch:hover {
color: var(--color-ink);
border-color: var(--color-ink);
}
@media (min-width: 48rem) {
.site-nav {
margin-top: 2rem;
}
.nav-inner {
padding-block: 0.9rem;
}
.site-nav ul {
gap: 0.25rem 2rem;
}
}
.site-nav a {
display: inline-block;
text-decoration: none;
color: var(--color-body);
/* Roomy enough to tap accurately on a phone. */
padding-block: 0.6rem;
border-bottom: 1px solid transparent;
}
.site-nav a:hover,
.site-nav a.router-link-active {
color: var(--color-ink);
border-bottom-color: var(--color-ink);
}
.site-main {
flex: 1;
padding-block: 2.25rem 3.5rem;
}
@media (min-width: 48rem) {
.site-main {
padding-block: 3.5rem 5rem;
}
}
/* Footer: columns, then a centred copyright line. */
.site-footer {
padding-block: 3.5rem 2rem;
background: var(--color-surface);
border-top: 1px solid var(--color-border);
}
/* One column on phones, two on tablets, four on desktop — auto-fit would
otherwise leave the fourth block stranded on its own row on a tablet. */
.footer-inner {
display: grid;
gap: 2rem;
grid-template-columns: 1fr;
padding-bottom: 2.5rem;
}
@media (min-width: 48rem) {
.footer-inner {
grid-template-columns: repeat(2, 1fr);
gap: 2.5rem;
}
}
@media (min-width: 64rem) {
.footer-inner {
grid-template-columns: repeat(4, 1fr);
}
}
.footer-block p:last-of-type {
margin-bottom: 0;
}
.footer-logo {
width: 4.5rem;
height: auto;
margin-bottom: 1rem;
}
.contact {
margin-top: 0.75rem;
font-size: 0.95rem;
font-style: normal;
}
.contact a {
text-decoration: underline;
text-underline-offset: 0.2em;
}
.footer-block ul {
padding: 0;
margin: 0.9rem 0 0;
list-style: none;
font-size: 0.95rem;
}
.footer-block li + li {
margin-top: 0.35rem;
}
.footer-block a {
text-decoration: none;
}
.footer-block a:hover {
text-decoration: underline;
text-underline-offset: 0.2em;
}
.social {
display: inline-flex;
align-items: center;
gap: 0.55rem;
}
/* Keep the underline on the label, not stretched across the icon. */
.social:hover {
text-decoration: none;
}
.social:hover span {
text-decoration: underline;
text-underline-offset: 0.2em;
}
.copyright {
padding-top: 1.75rem;
text-align: center;
border-top: 1px solid var(--color-border);
}
</style>