skoutz-relaunch/src/components/Fieberkurve.vue
2025-08-29 22:06:40 +02:00

336 lines
6.9 KiB
Vue

<template>
<div>
<div class="grid-container">
<!-- Linker Text mit roten Dots -->
<div v-if="!hideText" class="item1">
<div v-for="(label, index) in leftLabels" :key="index" class="text-pair">
<span v-if="sliders[index].clicked" class="dot" />
{{ label }}
</div>
</div>
<!-- Fieberkurve (Slider & Punkte) -->
<div class="item2">
<div class="FieberkurveGesamt">
<div
v-for="(slider, index) in sliders"
:key="index"
class="Fieberkurve"
:class="{ active: slider.clicked }"
>
<div
v-for="i in 7"
:key="i"
class="kreis"
:class="{ 'kreis-active': slider.clicked }"
/>
<input
v-model="slider.value"
type="range"
min="1"
max="7"
class="slider-bg"
:class="{ 'bg-clicked': slider.clicked }"
:disabled="isStatic"
/>
<input
v-model="slider.value"
type="range"
min="1"
max="7"
class="slider"
:class="{ clicked: slider.clicked }"
:disabled="isStatic"
@input="updateLine"
@click="handleClick(index)"
/>
</div>
<!-- Linie -->
<svg class="line-svg">
<polyline
:points="linePoints"
:class="{ clicked: sliders.some(slider => slider.clicked) }"
fill="none"
stroke-width="3"
/>
</svg>
</div>
</div>
<!-- Rechter Text -->
<div v-if="!hideText" class="item3">
<div v-for="label in rightLabels" :key="label" class="text-pair">
{{ label }}
</div>
</div>
</div>
<!-- Reset Button -->
<!-- <div v-if="!isStatic" class="reset-container">
<button @click="resetSliders">Fieberkurve zurücksetzen</button>
</div>-->
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted, defineProps, defineEmits } from "vue"
const props = defineProps({
isStatic: { type: Boolean, default: false },
defaultValues: {
type: Array,
default: () => [
{ value: 4, clicked: false, lastClickedValue: null },
{ value: 4, clicked: false, lastClickedValue: null },
{ value: 4, clicked: false, lastClickedValue: null },
{ value: 4, clicked: false, lastClickedValue: null },
{ value: 4, clicked: false, lastClickedValue: null },
],
},
hideText: { type: Boolean, default: false },
})
const emit = defineEmits(["update:values", "filter-by-row"])
defineExpose({ resetSliders })
const sliders = ref([...props.defaultValues])
const leftLabels = ["anspruchsvoll", "lustig", "spannend", "brutal", "sittsam"]
const rightLabels = ["leseleicht", "traurig", "entspannend", "gewaltfrei", "erotisch"]
const linePoints = computed(() => {
const Breite = 240
const Hoehe = 130
const Kreis = 20
return sliders.value
.map((slider, index) => {
const x = (slider.value - 1) * ((Breite - Kreis) / 6) + Kreis / 2
const y = index * ((Hoehe - Kreis) / 4) + Kreis / 2
return `${x},${y}`
})
.join(" ")
})
watch(
() => props.defaultValues,
newValues => {
sliders.value = newValues.map(val => ({ ...val }))
},
{ deep: true },
)
watch(
sliders,
() => {
emit("update:values", sliders.value.map(slider => slider.value))
},
{ deep: true },
)
function handleClick(index) {
const clickedSlider = sliders.value[index]
const currentValue = Number(clickedSlider.value)
if (clickedSlider.clicked && clickedSlider.lastClickedValue === currentValue) {
// Deaktivieren
clickedSlider.clicked = false
clickedSlider.value = 4
clickedSlider.lastClickedValue = null
} else {
clickedSlider.clicked = true
clickedSlider.lastClickedValue = currentValue
}
const activeFilters = sliders.value
.map((slider, idx) =>
slider.clicked ? { rowIndex: idx, value: Number(slider.value) } : null
)
.filter(Boolean)
emit("filter-by-row", activeFilters)
}
function resetSliders() {
sliders.value.forEach(slider => {
slider.value = 4
slider.clicked = false
slider.lastClickedValue = null
})
emit("filter-by-row", [])
}
function updateLine() {
// Optional: Linienanimation
}
onMounted(() => {
// Init
})
</script>
<style scoped>
.grid-container {
display: grid;
grid-template-areas: "txtLinks Fieberkurve txtRechts";
gap: 10px;
justify-content: center;
align-items: center;
}
.item1{
width: 105px;
}
.item1,
.item3 {
background: transparent;
}
.text-pair {
display: flex;
align-items: center;
height: 26px;
font-size: 14px;
}
/* 🔴 Dot neben Text */
.dot {
width: 8px;
height: 8px;
background-color: #f45246;
border-radius: 50%;
display: inline-block;
margin-right: 6px;
animation: pop 0.3s ease;
}
@keyframes pop {
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
}
.FieberkurveGesamt {
height: 130px;
width: 240px;
position: relative;
display: flex;
flex-wrap: wrap;
align-content: space-between;
}
.Fieberkurve {
position: relative;
width: 240px;
display: flex;
justify-content: space-between;
background-color: transparent;
}
.kreis {
z-index: 2;
width: 20px;
height: 20px;
border-radius: 100%;
background: #ceccccdb;
box-shadow: 2px 12px 27px -13px rgba(0, 0, 0, 0.48);
transition: transform 0.2s ease;
}
/* 🔁 Wackel-Animation */
.kreis-active {
animation: wobble 0.3s ease;
}
@keyframes wobble {
0% {
transform: scale(1);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
.slider-bg {
z-index: 1;
width: 100%;
position: absolute;
top: calc(50% - 4px);
height: 8px;
-webkit-appearance: none;
outline: none;
border-radius: 8px;
cursor: pointer;
background: #d3d3d3;
pointer-events: none;
}
.slider-bg.bg-clicked {
background: #e0e0e0;
}
.slider {
z-index: 3;
width: 100%;
position: absolute;
top: calc(50% - 4px);
height: 8px;
-webkit-appearance: none;
outline: none;
border-radius: 8px;
cursor: pointer;
background: transparent;
}
.slider.clicked::-webkit-slider-thumb {
background-color: #f45246;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: rgba(251, 194, 194, 0.86);
border-radius: 50%;
cursor: pointer;
transition: background-color 0.3s ease;
}
.line-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
pointer-events: none;
}
.line-svg polyline {
stroke: rgba(251, 194, 194, 0.86);
transition: stroke 0.3s ease;
}
.line-svg polyline.clicked {
stroke: #fa574d;
}
.reset-container {
display: flex;
justify-content: center;
margin-top: 10px;
}
</style>