This commit is contained in:
team3
2026-09-16 17:55:23 +02:00
parent cb78225e86
commit b42cf64782
13 changed files with 387 additions and 57 deletions

View File

@@ -1,5 +1,5 @@
<script setup>
import { computed, onMounted, ref } from 'vue'
import { computed, nextTick, onMounted, ref, watch } from 'vue'
import MovieCard from './components/MovieCard.vue'
import FilterSidebar from './components/FilterSidebar.vue'
import AehnlichTab from './components/AehnlichTab.vue'
@@ -88,12 +88,23 @@ const tabs = computed(() => [
])
// Auf dem Handy passt die Filterleiste nicht neben die Karten — dort
// klappt sie über einen Knopf auf
// legt sie sich über einen Knopf über die Seite
const filterOffen = ref(false)
const mitFilter = computed(() =>
['netflix', 'canon', 'search', 'aehnlich'].includes(activeTab.value)
)
// Passen die Reiter nicht in eine Zeile, soll der offene sichtbar sein
const reiter = ref(null)
watch(activeTab, async () => {
filterOffen.value = false
await nextTick()
const leiste = reiter.value
if (!leiste || leiste.scrollWidth <= leiste.clientWidth) return
leiste.querySelector('.active')?.scrollIntoView({ inline: 'center', block: 'nearest' })
})
const selected = ref(null)
// Der Katalog liefert nur sechs Bildpfade je Film. Beim Öffnen holen wir
@@ -154,29 +165,33 @@ onMounted(load)
<template>
<div class="page" :class="{ breit: !mitFilter }">
<nav class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
type="button"
:class="{ active: tab.id === activeTab }"
@click="activeTab = tab.id"
>
{{ tab.label }}
<span v-if="tab.count !== null" class="count">{{ tab.count }}</span>
</button>
<button
v-if="mitFilter"
type="button"
class="filter-knopf"
:class="{ an: filterOffen }"
@click="filterOffen = !filterOffen"
>
Filter
</button>
<NamensWahl v-if="!host" :items="runde" :mine="ich" @waehlen="michSetzen" />
<span v-else class="host">Host</span>
</nav>
<header class="kopf">
<nav ref="reiter" class="tabs">
<button
v-for="tab in tabs"
:key="tab.id"
type="button"
:class="{ active: tab.id === activeTab }"
@click="activeTab = tab.id"
>
{{ tab.label }}
<span v-if="tab.count !== null" class="count">{{ tab.count }}</span>
</button>
</nav>
<div class="rechts">
<button
v-if="mitFilter"
type="button"
class="filter-knopf"
:class="{ an: filterOffen }"
@click="filterOffen = !filterOffen"
>
Filter
</button>
<NamensWahl v-if="!host" :items="runde" :mine="ich" @waehlen="michSetzen" />
<span v-else class="host">Host</span>
</div>
</header>
<div v-if="activeTab === 'search' || activeTab === 'aehnlich'" class="layout">
<SearchTab
@@ -317,6 +332,15 @@ onMounted(load)
↑
</button>
<button
v-if="filterOffen && mitFilter"
type="button"
class="filter-fertig"
@click="filterOffen = false"
>
Fertig
</button>
<MovieDetail
v-if="selected"
:movie="selected"
@@ -347,12 +371,11 @@ onMounted(load)
padding-right: 1.25rem;
}
.tabs {
.kopf {
position: sticky;
top: 0;
z-index: 10;
display: flex;
gap: 0.35rem;
align-items: stretch;
/* Der negative Rand deckt das Polster der Seite ab, sonst scrollen
Karten oberhalb der Leiste durchs Bild */
@@ -362,6 +385,24 @@ onMounted(load)
border-bottom: 1px solid var(--line);
}
.tabs {
display: flex;
gap: 0.35rem;
align-items: stretch;
min-width: 0;
}
.rechts {
display: flex;
gap: 0.4rem;
align-items: center;
margin-left: auto;
}
.filter-fertig {
display: none;
}
.nach-oben {
position: fixed;
right: calc(var(--sidebar) + 1.25rem);
@@ -402,15 +443,22 @@ onMounted(load)
border-bottom-color: var(--accent);
}
/* Den Filterknopf gibt es nur auf schmalen Schirmen — mit .tabs davor,
sonst gewinnt die Regel für alle Reiterknöpfe */
.tabs .filter-knopf {
/* Den Filterknopf gibt es nur auf schmalen Schirmen */
.filter-knopf {
display: none;
padding: 0.35rem 0.8rem;
color: var(--text);
font-size: 0.85rem;
border: 1px solid var(--line);
border-radius: 6px;
}
.filter-knopf.an {
color: var(--accent);
border-color: var(--accent);
}
.host {
align-self: center;
margin-left: 0.4rem;
padding: 0.15rem 0.5rem;
color: var(--accent);
font-size: 0.7rem;
@@ -491,53 +539,83 @@ onMounted(load)
bottom: 1rem;
}
/* Zu schmal für eine feste Leiste — die Filter klappen über den Karten auf */
.layout {
display: flex;
flex-direction: column;
}
/* Zu schmal für eine feste Leiste — die Filter legen sich als ganze
Seite darüber, ein Balken unten schließt sie wieder */
.layout > :last-child {
display: none;
position: static;
order: -1;
width: auto;
margin-bottom: 1.25rem;
border: 1px solid var(--line);
border-radius: 12px;
}
.layout > :last-child.offen {
position: fixed;
inset: 0;
z-index: 20;
display: flex;
width: auto;
padding-bottom: 5rem;
overflow-y: auto;
border: 0;
}
.tabs .filter-knopf {
display: flex;
margin-left: auto;
.filter-fertig {
position: fixed;
right: 1rem;
bottom: 1rem;
left: 1rem;
z-index: 21;
display: block;
padding: 0.9rem;
color: #fff;
font-size: 1rem;
font-weight: 600;
background: var(--accent);
border-radius: 10px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
}
.tabs .filter-knopf.an {
color: var(--accent);
}
/* Die Namenswahl rückt neben den Filterknopf */
.filter-knopf + * {
margin-left: 0;
.filter-knopf {
display: block;
}
}
@media (max-width: 640px) {
.page {
padding: 1rem 0.75rem 2.5rem;
padding: 0.75rem 0.75rem 2.5rem;
}
/* Zwei Zeilen: oben rechts Filter und Name, darunter die Reiter, die
seitlich scrollen. Der Verlauf rechts zeigt, dass da noch mehr kommt. */
.kopf {
flex-wrap: wrap;
margin: -0.75rem -0.75rem 0.9rem;
padding: 0 0.75rem;
}
.rechts {
order: -1;
justify-content: flex-end;
width: 100%;
padding: 0.55rem 0 0.25rem;
}
/* Die Namenswahl schiebt sich sonst allein nach rechts und lässt den
Filterknopf links stehen */
.rechts > .wahl {
margin-left: 0;
}
/* Die Reiter passen nicht in eine Zeile — sie scrollen seitlich */
.tabs {
gap: 0;
margin: -1rem -0.75rem 1rem;
width: 100%;
margin: 0 -0.75rem;
padding: 0 0.75rem;
overflow-x: auto;
scrollbar-width: none;
mask-image: linear-gradient(to right, #000 88%, transparent);
}
.tabs::-webkit-scrollbar {
@@ -545,13 +623,15 @@ onMounted(load)
}
.tabs button {
padding: 0.7rem 0.6rem 0.5rem;
font-size: 0.88rem;
padding: 0.6rem 0.65rem 0.55rem;
font-size: 0.9rem;
}
/* Drei Poster nebeneinander, ohne Teaser — mehr Übersicht als zwei
riesige Karten */
.grid {
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
gap: 0.7rem;
grid-template-columns: repeat(auto-fill, minmax(105px, 1fr));
gap: 0.6rem;
}
.grid.details {