20-02-2026
This commit is contained in:
parent
c62234e1ca
commit
98084de7d0
80 changed files with 9804 additions and 1771 deletions
110
frontend/src/components/GlowDot.vue
Normal file
110
frontend/src/components/GlowDot.vue
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div
|
||||
class="glow-dot"
|
||||
:class="{
|
||||
'glow-dot--ghost': isGhost,
|
||||
'glow-dot--selected': selected,
|
||||
'glow-dot--dimmed': isDimmed
|
||||
}"
|
||||
:style="dotStyle"
|
||||
@click.stop="onSelect"
|
||||
>
|
||||
<!-- White inner circle — shader provides the glow -->
|
||||
<div class="glow-dot__inner">
|
||||
<img
|
||||
v-if="event.image"
|
||||
:src="event.image"
|
||||
class="glow-dot__image"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue'
|
||||
import { useEventsStore } from 'stores/events'
|
||||
import { useSettingsStore } from 'stores/settings'
|
||||
|
||||
const props = defineProps({
|
||||
event: { type: Object, required: true },
|
||||
x: { type: Number, default: 0 },
|
||||
isGhost: { type: Boolean, default: false },
|
||||
selected: { type: Boolean, default: false }
|
||||
})
|
||||
|
||||
const emit = defineEmits(['select'])
|
||||
const eventsStore = useEventsStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
// Match shader circle: CSS diameter = 2 * circleRadiusPx / dpr
|
||||
const dpr = Math.min(window.devicePixelRatio || 1, 2)
|
||||
|
||||
const dotSize = computed(() => {
|
||||
return 2 * settingsStore.floatingLines.circleRadius / dpr
|
||||
})
|
||||
|
||||
// Y position: emotion +1 → top (15%), 0 → middle (50%), -1 → bottom (85%)
|
||||
const yPercent = computed(() => {
|
||||
return 50 - props.event.emotion * 35
|
||||
})
|
||||
|
||||
const dotStyle = computed(() => ({
|
||||
left: `${props.x}px`,
|
||||
top: `${yPercent.value}%`,
|
||||
width: `${dotSize.value}px`,
|
||||
height: `${dotSize.value}px`
|
||||
}))
|
||||
|
||||
const isDimmed = computed(() => {
|
||||
return eventsStore.selectedEventId !== null && !props.selected && !props.isGhost
|
||||
})
|
||||
|
||||
function onSelect() {
|
||||
if (!props.isGhost) {
|
||||
emit('select')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.glow-dot {
|
||||
position: absolute;
|
||||
transform: translate(-50%, -50%);
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
transition: opacity 0.3s ease, transform 0.15s ease;
|
||||
}
|
||||
|
||||
/* Clean inner circle — shader provides the glow around it */
|
||||
.glow-dot__inner {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.glow-dot__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* States */
|
||||
.glow-dot--ghost {
|
||||
opacity: 0.7;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.glow-dot--selected {
|
||||
transform: translate(-50%, -50%) scale(1.15);
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.glow-dot--dimmed {
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue