102 lines
5.1 KiB
Vue
102 lines
5.1 KiB
Vue
|
|
<script setup lang="ts">
|
|||
|
|
useHead({ title: 'La carte — PANZAÏ Thonon' })
|
|||
|
|
const { dishes, open, wait } = useShop()
|
|||
|
|
const { mode } = useCart()
|
|||
|
|
|
|||
|
|
const cats = [
|
|||
|
|
{ id: 'wok', label: 'Plats au wok' },
|
|||
|
|
{ id: 'baguette', label: 'Baguette' },
|
|||
|
|
{ id: 'boissons', label: 'Boissons' },
|
|||
|
|
{ id: 'desserts', label: 'Desserts' }
|
|||
|
|
] as const
|
|||
|
|
const byCat = (c: string) => dishes.value.filter(d => d.cat === c)
|
|||
|
|
const active = ref('wok')
|
|||
|
|
|
|||
|
|
// Onglet actif suivant le scroll
|
|||
|
|
onMounted(() => {
|
|||
|
|
const io = new IntersectionObserver((entries) => {
|
|||
|
|
for (const e of entries) if (e.isIntersecting) active.value = e.target.id
|
|||
|
|
}, { rootMargin: '-40% 0px -55% 0px' })
|
|||
|
|
cats.forEach(c => { const el = document.getElementById(c.id); if (el) io.observe(el) })
|
|||
|
|
onBeforeUnmount(() => io.disconnect())
|
|||
|
|
})
|
|||
|
|
|
|||
|
|
const isDesktop = ref(false)
|
|||
|
|
onMounted(() => {
|
|||
|
|
const mq = window.matchMedia('(min-width: 1024px)')
|
|||
|
|
isDesktop.value = mq.matches
|
|||
|
|
mq.addEventListener('change', e => { isDesktop.value = e.matches })
|
|||
|
|
})
|
|||
|
|
</script>
|
|||
|
|
|
|||
|
|
<template>
|
|||
|
|
<main>
|
|||
|
|
<section class="band">
|
|||
|
|
<LeafMonstera class="band-leaf" />
|
|||
|
|
<div class="container band-in">
|
|||
|
|
<div class="grow">
|
|||
|
|
<h1 class="display" style="font-size: clamp(36px, 5vw, 56px)">La carte</h1>
|
|||
|
|
<p class="muted" style="margin-top: 8px">Tout est fait minute au wok. Taille L ou XL, bœuf, crevettes ou poulet. 100 % halal.</p>
|
|||
|
|
<p v-if="!open" class="pause"><span class="dot dot-red" /> Les commandes sont en pause pour le moment. Tu peux consulter la carte.</p>
|
|||
|
|
</div>
|
|||
|
|
<div class="seg" role="group" aria-label="Mode de commande">
|
|||
|
|
<button type="button" :aria-pressed="mode === 'livraison'" @click="mode = 'livraison'">Livraison · 30–40 min</button>
|
|||
|
|
<button type="button" :aria-pressed="mode === 'retrait'" @click="mode = 'retrait'">Retrait · ~{{ wait }} min</button>
|
|||
|
|
</div>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
|
|||
|
|
<nav class="tabs" aria-label="Catégories">
|
|||
|
|
<div class="container tabs-in">
|
|||
|
|
<a v-for="c in cats" :key="c.id" :href="`#${c.id}`" :class="{ on: active === c.id }">{{ c.label }}<span class="n">{{ byCat(c.id).length || '—' }}</span></a>
|
|||
|
|
</div>
|
|||
|
|
</nav>
|
|||
|
|
|
|||
|
|
<div class="container layout">
|
|||
|
|
<div class="dishes">
|
|||
|
|
<section v-for="c in cats" :id="c.id" :key="c.id" class="cat" :aria-labelledby="`h-${c.id}`">
|
|||
|
|
<h2 :id="`h-${c.id}`" class="eyebrow">{{ c.label }}</h2>
|
|||
|
|
<div v-if="byCat(c.id).length" class="grid">
|
|||
|
|
<DishCard v-for="d in byCat(c.id)" :key="d.id" :dish="d" :layout="isDesktop ? 'grid' : 'row'" />
|
|||
|
|
</div>
|
|||
|
|
<div v-else class="soon">
|
|||
|
|
<div v-for="n in 2" :key="n" class="soon-card"><div class="soon-img" /><p class="strong">[{{ c.id === 'boissons' ? 'Boisson' : 'Dessert' }} {{ n }}]</p><p class="price small">[PRIX]</p></div>
|
|||
|
|
</div>
|
|||
|
|
</section>
|
|||
|
|
</div>
|
|||
|
|
<div class="side"><CartSummary /></div>
|
|||
|
|
</div>
|
|||
|
|
</main>
|
|||
|
|
</template>
|
|||
|
|
|
|||
|
|
<style scoped>
|
|||
|
|
.band { position: relative; overflow: hidden; border-bottom: 1px solid rgba(243,227,195,.06); }
|
|||
|
|
.band-leaf { position: absolute; right: -70px; top: -60px; width: 240px; transform: rotate(-20deg); }
|
|||
|
|
.band-in { position: relative; display: flex; flex-wrap: wrap; align-items: flex-end; gap: 20px; padding-block: 32px 24px; }
|
|||
|
|
.pause { margin-top: 12px; display: inline-flex; align-items: center; gap: 8px; padding: 10px 14px; border-radius: 12px; background: rgba(232,98,42,.12); border: 1px solid rgba(232,98,42,.4); font-weight: 700; font-size: 14px; }
|
|||
|
|
.seg { width: 100%; max-width: 440px; }
|
|||
|
|
|
|||
|
|
.tabs { position: sticky; top: 70px; z-index: 30; background: rgba(20,16,13,.92); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px); border-bottom: 1px solid rgba(243,227,195,.06); }
|
|||
|
|
.tabs-in { display: flex; gap: 8px; overflow-x: auto; padding-block: 10px; scrollbar-width: none; }
|
|||
|
|
.tabs a { flex: none; display: inline-flex; align-items: center; gap: 8px; padding: 10px 16px; border-radius: 999px; background: var(--surface); color: var(--cream); font-size: 14px; font-weight: 700; text-decoration: none; }
|
|||
|
|
.tabs a.on { background: var(--grad); color: var(--bg); font-weight: 800; }
|
|||
|
|
.tabs .n { font-size: 12px; opacity: .7; }
|
|||
|
|
|
|||
|
|
.layout { display: flex; gap: 28px; align-items: flex-start; padding-top: 24px; }
|
|||
|
|
.dishes { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 36px; }
|
|||
|
|
.cat { scroll-margin-top: 140px; }
|
|||
|
|
.cat h2 { margin-bottom: 14px; }
|
|||
|
|
.grid { display: flex; flex-direction: column; gap: 12px; }
|
|||
|
|
.side { display: none; }
|
|||
|
|
.soon { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
|
|||
|
|
.soon-card { padding: 14px; border-radius: 18px; background: var(--surface); border: 1px dashed rgba(243,227,195,.2); display: flex; flex-direction: column; gap: 8px; }
|
|||
|
|
.soon-img { height: 80px; border-radius: 12px; background: var(--surface-2); }
|
|||
|
|
|
|||
|
|
@media (min-width: 1024px) {
|
|||
|
|
.tabs { display: none; }
|
|||
|
|
.layout { padding-top: 32px; }
|
|||
|
|
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; }
|
|||
|
|
.side { display: block; flex: none; width: 350px; position: sticky; top: 92px; }
|
|||
|
|
}
|
|||
|
|
</style>
|