- Add ZoneCard component for zone display with status indicators - Add CheminLibre drawer with vertical zone cards and path decoration - Add BottomBar with Map, Progress, and Settings buttons - Add ProgressDetail modal showing visited sections - Add SettingsDrawer with language, consent, and reset options - Add i18n translations for zone, cheminLibre, bottomBar, settings - Add --bottom-bar-height CSS variable for spacing - Modify layouts to include BottomBar on mobile (< 768px) - Support safe-area-inset for iOS devices - Touch targets minimum 48x48px for WCAG compliance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
195 lines
6.5 KiB
Vue
195 lines
6.5 KiB
Vue
<script setup lang="ts">
|
|
const { t } = useI18n()
|
|
const showCheminLibre = ref(false)
|
|
const showProgress = ref(false)
|
|
const showSettings = ref(false)
|
|
|
|
const progressionStore = useProgressionStore()
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bottom-bar-container md:hidden">
|
|
<!-- Bottom Bar fixe -->
|
|
<nav
|
|
class="bottom-bar fixed bottom-0 inset-x-0 z-40 bg-sky-dark-50 border-t border-sky-dark-100 safe-bottom"
|
|
>
|
|
<div class="flex items-center justify-around h-16">
|
|
<!-- Bouton Carte -->
|
|
<button
|
|
type="button"
|
|
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
|
|
:class="{ 'text-sky-accent': showCheminLibre }"
|
|
:aria-label="t('bottomBar.map')"
|
|
@click="showCheminLibre = true"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M9 20l-5.447-2.724A1 1 0 013 16.382V5.618a1 1 0 011.447-.894L9 7m0 13l6-3m-6 3V7m6 10l4.553 2.276A1 1 0 0021 18.382V7.618a1 1 0 00-.553-.894L15 4m0 13V4m0 0L9 7"
|
|
/>
|
|
</svg>
|
|
<span class="text-xs font-ui mt-1">{{ t('bottomBar.map') }}</span>
|
|
</button>
|
|
|
|
<!-- Bouton Progression -->
|
|
<button
|
|
type="button"
|
|
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
|
|
:class="{ 'text-sky-accent': showProgress }"
|
|
:aria-label="t('bottomBar.progress')"
|
|
@click="showProgress = true"
|
|
>
|
|
<!-- Cercle de progression -->
|
|
<div class="relative w-6 h-6">
|
|
<svg class="w-full h-full -rotate-90" viewBox="0 0 36 36">
|
|
<circle
|
|
cx="18"
|
|
cy="18"
|
|
r="14"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="3"
|
|
class="text-sky-dark-100"
|
|
/>
|
|
<circle
|
|
cx="18"
|
|
cy="18"
|
|
r="14"
|
|
fill="none"
|
|
stroke="currentColor"
|
|
stroke-width="3"
|
|
stroke-linecap="round"
|
|
class="text-sky-accent"
|
|
:stroke-dasharray="`${progressionStore.completionPercent * 0.88}, 100`"
|
|
/>
|
|
</svg>
|
|
<span
|
|
class="absolute inset-0 flex items-center justify-center text-[8px] font-ui font-bold"
|
|
>
|
|
{{ progressionStore.completionPercent }}
|
|
</span>
|
|
</div>
|
|
<span class="text-xs font-ui mt-1">{{ t('bottomBar.progress') }}</span>
|
|
</button>
|
|
|
|
<!-- Bouton Paramètres -->
|
|
<button
|
|
type="button"
|
|
class="bottom-bar-btn flex flex-col items-center justify-center min-w-12 min-h-12 p-2 text-sky-text-muted hover:text-sky-accent transition-colors"
|
|
:class="{ 'text-sky-accent': showSettings }"
|
|
:aria-label="t('bottomBar.settings')"
|
|
@click="showSettings = true"
|
|
>
|
|
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
|
|
/>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
/>
|
|
</svg>
|
|
<span class="text-xs font-ui mt-1">{{ t('bottomBar.settings') }}</span>
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Drawer Chemin Libre -->
|
|
<Teleport to="body">
|
|
<Transition name="slide-up">
|
|
<div v-if="showCheminLibre" class="fixed inset-x-0 bottom-16 top-0 z-50">
|
|
<!-- Overlay -->
|
|
<div class="absolute inset-0 bg-black/50" @click="showCheminLibre = false" />
|
|
|
|
<!-- Drawer content -->
|
|
<div class="absolute inset-x-0 bottom-0 max-h-[80vh] bg-sky-dark-50 rounded-t-2xl">
|
|
<CheminLibre @close="showCheminLibre = false" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Modal Progression -->
|
|
<Transition name="fade">
|
|
<div
|
|
v-if="showProgress"
|
|
class="fixed inset-0 z-50 flex items-end justify-center md:items-center"
|
|
>
|
|
<div class="absolute inset-0 bg-black/50" @click="showProgress = false" />
|
|
<div
|
|
class="relative w-full max-w-sm bg-sky-dark-50 rounded-t-2xl md:rounded-2xl p-6 safe-bottom"
|
|
>
|
|
<ProgressDetail @close="showProgress = false" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
|
|
<!-- Drawer Paramètres -->
|
|
<Transition name="slide-up">
|
|
<div v-if="showSettings" class="fixed inset-x-0 bottom-16 top-0 z-50">
|
|
<div class="absolute inset-0 bg-black/50" @click="showSettings = false" />
|
|
<div
|
|
class="absolute inset-x-0 bottom-0 max-h-[60vh] bg-sky-dark-50 rounded-t-2xl p-6 safe-bottom overflow-y-auto"
|
|
>
|
|
<SettingsDrawer @close="showSettings = false" />
|
|
</div>
|
|
</div>
|
|
</Transition>
|
|
</Teleport>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.safe-bottom {
|
|
padding-bottom: max(env(safe-area-inset-bottom), 1rem);
|
|
}
|
|
|
|
.bottom-bar {
|
|
height: var(--bottom-bar-height, 64px);
|
|
padding-bottom: env(safe-area-inset-bottom);
|
|
}
|
|
|
|
.bottom-bar-btn {
|
|
min-width: 48px;
|
|
min-height: 48px;
|
|
}
|
|
|
|
.slide-up-enter-active,
|
|
.slide-up-leave-active {
|
|
transition:
|
|
transform 0.3s ease,
|
|
opacity 0.3s ease;
|
|
}
|
|
|
|
.slide-up-enter-from,
|
|
.slide-up-leave-to {
|
|
transform: translateY(100%);
|
|
opacity: 0;
|
|
}
|
|
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.2s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.slide-up-enter-active,
|
|
.slide-up-leave-active,
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: none;
|
|
}
|
|
}
|
|
</style>
|