336 lines
6.9 KiB
Vue
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>
|