Initial commit

This commit is contained in:
Mindboost
2025-07-01 10:53:26 +00:00
commit 38050e5c69
416 changed files with 48708 additions and 0 deletions

View File

@@ -0,0 +1,687 @@
<template>
<div class="nav">
<ul class="nav__list">
<li id="step-1" class="nav__item">
<a
v-if="!isPlaying()"
id="foucused-icon"
:class="{'is-active': true}"
class="nav__item-link"
@click.prevent="playSound"
>
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="m6.192 3.67 13.568 7.633a.8.8 0 0 1 0 1.394L6.192 20.33A.8.8 0 0 1 5 19.632V4.368a.8.8 0 0 1 1.192-.698Z"
fill="currentColor"
/>
</svg>
</a>
<a v-else id="foucused-icon" :class="{'is-active': false}" class="nav__item-link" @click.prevent="playSound">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#a)">
<path
d="M17.083 19.917a2.326 2.326 0 0 1-1.706-.71 2.332 2.332 0 0 1-.71-1.707V5.417c0-.665.236-1.234.71-1.706A2.333 2.333 0 0 1 17.083 3c.664 0 1.233.236 1.708.71.474.475.71 1.044.709 1.707V17.5a2.33 2.33 0 0 1-.71 1.707 2.322 2.322 0 0 1-1.707.71Zm-9.666 0a2.326 2.326 0 0 1-1.707-.71A2.332 2.332 0 0 1 5 17.5V5.417c0-.665.237-1.234.71-1.706A2.333 2.333 0 0 1 7.417 3c.663 0 1.233.236 1.707.71.475.475.71 1.044.71 1.707V17.5a2.33 2.33 0 0 1-.71 1.707 2.322 2.322 0 0 1-1.707.71Z"
fill="#e9c046"
/>
</g>
<defs>
<clipPath id="a">
<path fill="#e9c046" d="M0 0h24v24H0z" />
</clipPath>
</defs>
</svg>
</a>
</li>
<li
id="step-2"
ref="soundscape"
class="nav__item nav__item--toggle"
:class="{ 'nav__item--active': userStore.soundMode === 'soundscape' }"
@click="setSoundMode('soundscape')"
>
<a class="nav__item-link" aria-current="page" href="#" @click.prevent="toggleDropdown('soundscape')">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M8.286 18.875c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Zm0 0V6.479L19 4v12.396M8.286 10.73 19 8.25m0 8.5c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<ul
class="nav__dropdown nav__dropdown--no-padding"
:class="{ open: activeDropdown === 'soundscape' }"
@click.stop
>
<li>
<nuxt-link
id="lagoonLinkHomebar"
:class="{'is-select':form.soundscape=='Lagoon'}"
class="nav__dropdown-listItem py-2 fw-bold fs-6 "
@click="saveSetting('Lagoon', 'soundscape')"
>
{{ t('Lagoon') }}
<span class="float-end">
<WaveSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="tropicsLinkHomebar"
:class="{'is-select':form.soundscape=='Tropics'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Tropics', 'soundscape')"
>
{{ t('Tropics') }}
<span class="float-end">
<TropicsSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="forestLinkHomebar"
:class="{'is-select':form.soundscape=='Forest'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Forest', 'soundscape')"
>
{{ t('Forest') }}
<span class="float-end">
<ForestSVG />
</span>
</nuxt-link>
</li>
<li>
<nuxt-link
id="meadowLinkHomebar"
:class="{'is-select':form.soundscape=='Meadow'}"
class="nav__dropdown-listItem fw-bold fs-6 py-2"
@click="saveSetting('Meadow', 'soundscape')"
>
{{ t('Meadow') }}
<span class="float-end">
<MeadowSVG />
</span>
</nuxt-link>
</li>
</ul>
</li>
<li
id="step-3"
ref="music"
class="nav__item nav__item--toggle"
:class="{ 'nav__item--active': userStore.soundMode === 'music' }"
@click="setSoundMode('music')"
>
<a class="nav__item-link" aria-current="page" href="#" @click.prevent="toggleDropdown('music')">
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.293 2 3 21.707" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
<path
d="M8.286 18.875c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Zm0 0V6.479L19 4v12.396M8.286 10.73 19 8.25m0 8.5c0 .564-.226 1.104-.628 1.503a2.152 2.152 0 0 1-3.03 0 2.116 2.116 0 0 1 0-3.006 2.152 2.152 0 0 1 3.03 0c.402.399.628.94.628 1.503Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
</a>
<ul class="nav__dropdown text-muted-dark" :class="{ open: activeDropdown === 'music' }" @click.stop>
<p class="fw-bold pt-1">{{ t('musicmode_activated') }}</p>
<p>{{ t('musicmode_text') }}</p>
</ul>
</li>
<li id="step-4" ref="settings" class="nav__item" @click.stop="handleClickOutside">
<a class="nav__item-link" aria-current="page" href="#" @click.stop="toggleDropdown('settings')">
<svg width="18" height="18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M2.3 8.177V.05h2.15V8.205l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015v-.028Zm7.775-5.625v.028l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015V.05h2.15v2.502ZM15.7 8.177v.028l.025.015a2.2 2.2 0 1 1-2.2 0l.025-.015V.05h2.15v8.127ZM4.45 13.55v4.4H2.3v-4.4h2.15Zm5.625-5.625V17.95h-2.15V7.925h2.15Zm3.475 5.625h2.15v4.4h-2.15v-4.4Z"
fill="currentColor"
stroke="currentColor"
stroke-width=".1"
/>
</svg>
</a>
<ul class="nav__dropdown pt-4" :class="{ open: activeDropdown === 'settings' }" @click.stop>
<li v-for="soundscape in soundscapes" :key="soundscape.id">
<MusicGainSlider v-if="currentIndex === soundscape.id && userStore.soundMode !== 'music'" />
</li>
<li>
<NoiseGainSlider />
</li>
</ul>
</li>
<li
v-if="userStore.isTimerActivated"
id="step-5"
ref="pomodoro"
class="nav__item nav__item--timer-bg"
:style="`--progress-width: ${progressBarWidth}`"
:data-phase="timer.getCurrentSession"
:data-color="progressColor"
@click.stop="handleClickOutside"
>
<a
class="nav__item-link nav__item-link--timer"
aria-current="page"
:style="`--progress-color: ${progressColor}`"
href="#"
@click.stop="toggleDropdown('pomodoro')"
>
<svg width="27" height="27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.11 6.301a7.78 7.78 0 0 1-1.111 15.478A7.777 7.777 0 0 1 8.734 8.275L7.162 6.703a10 10 0 1 0 5.726-2.64v6.7h2.222V6.3Z"
fill="currentColor"
/>
<path
d="M9.229 10.342a1.11 1.11 0 0 0 0 1.571l3.143 3.144a1.111 1.111 0 0 0 1.571-1.572L10.8 10.343a1.111 1.111 0 0 0-1.57 0Z"
fill="currentColor"
/>
</svg>
</a>
<ul class="nav__dropdown" :class="{ open: activeDropdown === 'pomodoro' }" @click.stop>
<PomodoroTimer />
</ul>
</li>
</ul>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { mapActions } from 'pinia'
import { driver } from 'driver.js'
import MusicGainSlider from '../components/sliders/MusicGainSlider'
import NoiseGainSlider from '../components/sliders/NoiseGainSlider'
import AdaptiveNoiseGain from '../components/experiments/homepages/AdaptiveNoiseGain'
import 'driver.js/dist/driver.css'
import WaveSVG from './svg/homebar/WaveSVG.vue'
import TropicsSVG from './svg/homebar/TropicsSVG.vue'
import ForestSVG from './svg/homebar/ForestSVG.vue'
import MeadowSVG from './svg/homebar/MeadowSVG.vue'
import PomodoroTimer from '~/components/timer/PomodoroTimer.vue'
import { useTimerStore } from '~/stores/timer'
import { useAudioStore } from '~/stores/audio'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
import { useUserStore } from '~/stores/user'
export default {
name: 'NavigationBar',
components: {
WaveSVG,
TropicsSVG,
ForestSVG,
MeadowSVG,
NoiseGainSlider,
MusicGainSlider,
PomodoroTimer
},
props: {
title: {
type: String,
default: 'NavigationBar'
},
currentIndex: {
type: Number,
required: true
},
currentSoundscape: {
type: String,
required: true
},
adaptive: {
type: String,
required: true
}
},
emits: ['update:soundscape', 'update:currentIndex'],
setup () {
const { t } = useI18n()
const logger = useNuxtApp.$logger
const localePath = useLocalePath()
const timer = useTimerStore()
const audioStore = useAudioStore()
const userStore = useUserStore()
const { $axios } = useNuxtApp()
const activeDropdown = ref(null)
const progressBarWidth = computed(() => {
const totalTime = timer.getSessionTime * 60
return totalTime > 0 ? `${(timer.getTimeRemaining * 100) / totalTime}%` : '0%'
})
const progressColor = computed(() => {
return timer.getCurrentColor
})
const handleValueChange = (value) => {
// useNuxtApp().$logger.log('handle new control value: ' + value)
if (!isNaN(value)) {
const normaizedVolume = calculateNormalizedVolume(value)
// useNuxtApp().$logger.log('normalized volume: ' + normaizedVolume)
} else {
logger.log('value is not NaN', { value })
}
}
const onboarding = localStorage.getItem('onboarding')
const steps = [
{
element: '#step-1',
popover: {
title: t('Tooltip-start-title'),
description: t('Tooltip-start-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-2',
popover: {
title: t('Tooltip-sound-title'),
description: t('Tooltip-sound-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-3',
popover: {
title: t('Tooltip-music-title'),
description: t('Tooltip-music-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-4',
popover: {
title: t('Tooltip-settings-title'),
description: t('Tooltip-settings-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-5',
popover: {
title: t('Tooltip-timer-title'),
description: t('Tooltip-timer-content'),
side: 'top',
align: 'start'
}
},
{
element: '#step-6',
popover: {
title: t('Lets Go!'),
description: t('We have saved your selection.') + '</br>' + t('You can change your settings at any time.') + '</br>' + '<img src="/images/rocketman.svg" height="35" class="img " alt="image">',
align: 'center',
popoverClass: 'onboarding-popover--last'
}
}
]
// localstorage abgreifen und wenn onboarding null, dann starten
if (!userStore.isOnboardingCompleted) {
onMounted(() => {
const driverObj = driver({
popoverClass: 'onboarding-popover',
prevBtnText: '<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M10.707 1.636a1 1 0 0 1 0 1.414L5.757 8l4.95 4.95a1 1 0 0 1-1.414 1.414L3.636 8.707a1 1 0 0 1 0-1.414l5.657-5.657a1 1 0 0 1 1.414 0Z" fill="#585C5E"/></svg>',
nextBtnText: '<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5.293 1.636a1 1 0 0 0 0 1.414L10.243 8l-4.95 4.95a1 1 0 0 0 1.414 1.414l5.657-5.657a1 1 0 0 0 0-1.414L6.707 1.636a1 1 0 0 0-1.414 0Z" fill="#585C5E"/></svg>',
doneBtnText: t('Lets Go!'),
steps,
onDestroyed: async () => {
audioStore.setPlaying(true)
// Backend updaten
try {
await $axios.post('/api/account/onboarding-completed')
userStore.markOnboardingCompleted() // Store aktualisieren
} catch (error) {
logger.error('Error updating onboarding status:', error)
}
driverObj.destroy()
}
})
driverObj.drive()
})
}
return {
t,
localePath,
timer,
progressBarWidth,
progressColor,
handleValueChange
}
},
data () {
return {
activeNav: 'soundscape',
activeDropdown: null,
userStore: useUserStore(),
form: {
soundscape: '',
headphone_type: '',
anc_type: '',
adaptive_sound_scape: ''
},
bar_width: 230,
analyser: null,
dataArray: null,
bar_val: 25,
soundscapes: [
{ id: 0, title: 'Lagoon', src: window.location.origin + useRuntimeConfig().public.tracks.lagoon_src },
{ id: 1, title: 'Tropics', src: window.location.origin + useRuntimeConfig().public.tracks.tropics_src },
{ id: 2, title: 'Forest', src: window.location.origin + useRuntimeConfig().public.tracks.forest_src },
{ id: 3, title: 'Meadow', src: window.location.origin + useRuntimeConfig().public.tracks.meadow_src }
]
}
},
mounted () {
this.updateActiveSoundscape()
getSettings()
const timerActivated = localStorage.getItem('timerActivated')
if (timerActivated !== null) {
const timer = useTimerStore()
timer.activated = JSON.parse(timerActivated)
}
document.addEventListener('click', this.handleClickOutside)
},
beforeUnmount () {
document.removeEventListener('click', this.handleClickOutside)
},
methods: {
...mapActions(useUserStore, ['updateHeadsetType', 'updateANC', 'logout', 'soundscape']),
...mapActions(useAudioStore, ['isPlaying', 'setPlaying']),
updateMusicVolume (volume) {
const musicGain = this.$refs[this.currentSoundscape]
musicGain.updateMusicGain(volume)
},
handleAdaptiveSoundscapeChange (newValue) {
this.adaptiveSoundscape = newValue
this.saveSetting(newValue, 'adaptivesoundscape')
useNuxtApp().$logger.log('Adaptive soundscape setting saved lets change the index page')
},
saveSettings (value, key) {
useNuxtApp().$logger.log(`Saving ${key}: ${value}`)
},
updateActiveSoundscape () {
switch (this.currentSoundscape) {
case 'Lagoon':
document.getElementById('lagoonLinkHomebar').classList.add('active')
break
case 'Tropics':
document.getElementById('tropicsLinkHomebar').classList.add('active')
break
case 'Forest':
document.getElementById('forestLinkHomebar').classList.add('active')
break
case 'Meadow':
document.getElementById('meadowLinkHomebar').classList.add('active')
break
}
},
removeActiveSoundscape () {
// foreach nav__dropdown-listItem remove active class
const soundscapeLinks = document.querySelectorAll('.nav__dropdown-listItem')
soundscapeLinks.forEach((link) => {
link.classList.remove('active')
})
},
playSound () {
this.setPlaying(!this.isPlaying())
},
toggleDropdown (navItem) {
this.activeDropdown = this.activeDropdown === navItem ? null : navItem
},
handleClickOutside (event) {
if (
this.$refs.soundscape &&
!this.$refs.soundscape.contains(event.target) &&
this.$refs.music &&
!this.$refs.music.contains(event.target)
) {
this.activeDropdown = null
}
},
setSoundMode (mode) {
if (this.userStore.soundMode !== mode) {
this.userStore.soundMode = mode
if (mode === 'soundscape') {
this.$emit('update:soundscape', this.form)
}
}
},
saveSetting (value, type) {
if (type === 'soundscape') {
this.form.soundscape = value
this.$emit('update:soundscape', this.form)
// Finde die passende Soundscape id
const soundscape = this.soundscapes.find(s => s.title === value)
// remove active state from all soundscapes before nuxt link does its automatic magic
this.removeActiveSoundscape()
if (soundscape) {
this.$emit('update:currentIndex', soundscape.id)
}
}
}
}
}
</script>
<style style="scss" scoped>
.nav {
position: fixed;
bottom: 3em;
text-align: center;
width: 100%;
padding-right: 0;
z-index: -1;
@media only screen and (max-width: 576px) {
bottom: 1.5em;
}
.nav__list {
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
display: flex;
justify-content: space-between;
list-style: none;
padding: 0;
margin: 0 auto;
align-items: center;
position: relative;
border-radius: 6px;
background-color: white;
flex-basis: 100%;
max-width: 350px;
@media only screen and (max-width: 576px) {
width: 100%;
margin: 0 auto;
}
}
.nav__item {
height: 50px;
padding: 0.25em;
flex: 1;
&:hover:not(.nav__item--active) {
color: #e9c046;
/* .nav__dropdown {
display: block;
} */
}
&:first-of-type {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
&:last-of-type {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}
&.nav__item--toggle {
cursor: pointer;
padding-left: 0;
padding-right: 0;
&:first-of-type {
margin-left: 0.25em;
}
&:last-of-type {
margin-right: 0.25em;
}
&.nav__item--active {
.nav__item-link {
background-color: #fff;
}
}
.nav__item-link {
transition: background-color 0.25s, box-shadow 0.25s, color 0.25s;
background-color: #F4F5F8;
box-shadow: inset 0 0 3px 0 rgba(0, 0, 0, 0.1);
}
}
&.nav__item--timer-bg {
padding: 0;
border-left: 2px solid #F4F5F8;
overflow: hidden;
.nav__item-link {
position: relative;
svg {
z-index: 2;
}
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
right: calc(100% - var(--progress-width));
transition: right 0.25s ease-in-out;
bottom: 0;
background-color: var(--progress-color);
opacity: 0.5;
}
}
}
.nav__item-link {
color: #585C5E;
display: flex;
align-items: center;
cursor: pointer;
transition: 250ms ease-in-out;
height: 100%;
justify-content: center;
&:hover {
color: #e9c046;
}
}
.nav__item-link--timer {
&:hover {
color: #fff;
}
}
}
.nav__dropdown{
background-color: #F4F5F8;
position: absolute;
bottom: 0;
padding: 0.75em 1em 4em 1em;
left: 0;
right: 0;
display: none;
list-style: none;
border-radius: 6px;
box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-webkit-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: 0 0 14px 0 rgba(0, 0, 0, 0.1);
z-index: -1;
&.nav__dropdown--no-padding {
padding-left: 0;
padding-right: 0;
}
&.open {
display: block;
}
.nav__dropdown-listItem {
display: flex;
justify-content: space-between;
width: 100%;
padding: 0.5em 1em;
text-decoration: none;
cursor: pointer;
color: #585C5E;
transition: 250ms ease-in-out;
&.active, &.is-select {
background-color: #e9c046;
color: white;
}
&:hover {
background-color: #e9c046;
color: white;
}
}
p{
text-align: left;
}
.form-switch {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1em;
padding-left: 0;
.form-switch__label {
font-weight: 600;
}
}
}
}
</style>