Les huit liens de la barre ne tiennent qu'au-delà de ~1180px. En dessous, la barre flex les compressait au lieu de les masquer : « Start project », « Git services » et « How it works » se cassaient sur deux ou trois lignes, les libellés recouvraient le logo et le bouton Gitea sortait du cadre. Sous 900px, il n'y avait par ailleurs AUCUNE navigation : les sections n'étaient atteignables qu'en scrollant. - Aucun libellé ne peut plus se casser (`whitespace-nowrap` + `shrink-0`), et le logo ne se rogne plus. - Les liens apparaissent par palier de largeur : 4 dès 900px, 6 à partir de 1024, les 8 à partir de 1180. Pur CSS, aucun JS, aucune mesure au runtime. - Sous 900px, un bouton menu ouvre un panneau listant TOUS les liens (plus Gitea), refermé au choix d'un lien ou par Échap. Le bouton Gitea quitte la barre à ces largeurs, sinon le bouton menu se retrouvait tronqué au bord de l'écran sur un mobile de 390px. - Le libellé « Gitea » n'apparaît qu'au-delà de 1180px ; en dessous l'icône suffit et l'aria-label reste. Ajout d'un bouton « remonter en haut » en bas à droite : mêmes tokens que le reste du site, visible seulement après 600px de défilement, cible de 44px, respecte prefers-reduced-motion. Vérifié au rendu à 390, 760, 900, 1024, 1180 et 1440px, dans les deux thèmes, menu ouvert compris.
66 lines
2.2 KiB
Vue
66 lines
2.2 KiB
Vue
<script setup lang="ts">
|
|
// Bouton « remonter en haut », en bas à droite. Reprend les tokens existants (bordure `border`, fond
|
|
// `surface-1`, accent au survol, radius 12px, ombre `shadow-card`) : rien de neuf visuellement.
|
|
// Il n'apparaît qu'après un vrai défilement et disparaît en haut de page, pour ne jamais recouvrir le
|
|
// contenu sans raison.
|
|
import { onBeforeUnmount, onMounted, ref } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
|
|
const { t } = useI18n();
|
|
|
|
/** Au-delà d'un écran de défilement, remonter rend un vrai service. */
|
|
const SHOW_AFTER = 600;
|
|
|
|
const visible = ref(false);
|
|
const onScroll = (): void => {
|
|
visible.value = window.scrollY > SHOW_AFTER;
|
|
};
|
|
|
|
function toTop(): void {
|
|
// Respecte la préférence système : pas de défilement animé si l'utilisateur les a réduites.
|
|
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
window.scrollTo({ top: 0, behavior: reduce ? 'auto' : 'smooth' });
|
|
}
|
|
|
|
onMounted(() => {
|
|
onScroll();
|
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
});
|
|
onBeforeUnmount(() => window.removeEventListener('scroll', onScroll));
|
|
</script>
|
|
|
|
<template>
|
|
<Transition name="btt">
|
|
<button
|
|
v-if="visible"
|
|
type="button"
|
|
class="fixed right-5 bottom-5 z-40 inline-flex h-11 w-11 items-center justify-center rounded-[12px] border border-border bg-surface-1/90 text-fg-muted shadow-card backdrop-blur-[10px] transition-colors hover:border-accent hover:text-accent focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent/70 sm:right-7 sm:bottom-7"
|
|
:aria-label="t('backToTop')"
|
|
:title="t('backToTop')"
|
|
@click="toTop"
|
|
>
|
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
<path d="m18 15-6-6-6 6" />
|
|
</svg>
|
|
</button>
|
|
</Transition>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.btt-enter-active,
|
|
.btt-leave-active {
|
|
transition: opacity 0.18s ease, transform 0.18s ease;
|
|
}
|
|
.btt-enter-from,
|
|
.btt-leave-to {
|
|
opacity: 0;
|
|
transform: translateY(6px);
|
|
}
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.btt-enter-active,
|
|
.btt-leave-active {
|
|
transition: none;
|
|
}
|
|
}
|
|
</style>
|