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

103
pages/auth/forgot.vue Normal file
View File

@@ -0,0 +1,103 @@
<template>
<div>
<video-background
src="/video/bg-video.mp4"
style=" height: 100vh;"
poster="/images/poster.png"
>
<div class="container-fluid">
<div class="row">
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
<div class="row">
<div class="col-12 py-3 text-center">
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
</div>
</div>
<div class="row mt-lg-0 mt-4" style="height: 90%">
<div class="col-12 my-auto">
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
<h1 class="fw-bolder h3" style="color: #585C5E;">
{{ t("Forgot Password?") }}
</h1>
<h2 class="text-muted h5 pt-2">
{{ t("We send link") }}
</h2>
</div>
<div class="row pt-2">
<div class="col-12 col-md-8 col-xl-8 mx-auto">
<div v-if="resetSuccess" class="alert alert-success text-center">
{{ t('Reset Password Message') }}
</div>
<form method="POST" @submit.prevent="submit">
<div class="row pt-4">
<div class="col-12">
<label class="form-label">{{ t('Email') }}</label>
<input v-model="email" type="email" placeholder="email@mail.com" class="form-control">
<div v-if="errors.email" class="invalid-feedback d-block">
{{ t(errors.email[0]) }}
</div>
</div>
</div>
<div class="row pt-4">
<div class="col">
<button type="submit" style="min-width:fit-content" class="login-btn col-4">
{{ t('Reset Password') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
email: '',
resetSuccess: false,
errors: [],
loading: false
}
},
methods: {
submit () {
this.loading = true
this.errors = []
this.$axios.post('/api/password/email', { email: this.email })
.then(({ data }) => {
this.loading = false
this.$toast.success('Wenn die E-Mail existiert, wurde ein Link zum Zurücksetzen gesendet.')
this.resetSuccess = true
})
.catch((error) => {
this.loading = false
if (error.response.status === 422) {
this.errors = error.response.data.errors
} else {
this.$toast.error('Ein Fehler ist aufgetreten.')
}
})
}
}
}
</script>

214
pages/auth/login.vue Normal file
View File

@@ -0,0 +1,214 @@
<template>
<div>
<video-background
src="/video/bg-video.mp4"
poster="/images/poster.png"
style="height: 100vh;"
>
<div class="container-fluid">
<div class="row ">
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
<div class="row">
<div class="col-12 py-3 text-center">
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
</div>
</div>
<div class="row mt-lg-0 mt-4" style="height: 90%">
<div class="col-12 my-auto">
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
<h1 class="fw-bolder h3" style="color: #585C5E;">
{{ t('Log in Header') }}
</h1>
<h2 class="text-muted h5 pt-2">
{{ t('Log in') }}
</h2>
</div>
<div class="row pt-2">
<div class="col-12 col-md-8 col-xl-8 mx-auto">
<form method="POST" @submit.prevent="loginNow">
<div class="row pt-4">
<div class="col-12">
<label class="form-label">{{ t('Email') }}</label>
<input v-model="form.email" type="email" autocomplete="email" class="form-control">
<div v-if="errors.email" class="invalid-feedback d-block">
{{ errors.email[0] }}
</div>
<div v-if="auth_error" class="invalid-feedback d-block">
{{ t(auth_error_message) }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col">
<label class="form-label">{{ t('Password') }}</label>
<input
v-model="form.password"
type="password"
name="password"
autocomplete="current-password"
class="form-control"
>
<span class="float-end pt-2">
<router-link :to="localePath('/auth/forgot')" class="forgot-link" href="#">{{ t('Forgot Password?') }}</router-link>
</span>
</div>
</div>
<div class="row pt-4">
<div class="col">
<button type="submit" style="min-width:fit-content" class="login-btn col-4">
{{ t('Sign in') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t('Loading...') }}</span>
</div>
</button>
<p class="pt-3">
{{ t('No account?') }} <NuxtLink class="signup-link" :to="localePath('/auth/signup')">
{{ t("Sign up now") }}
</NuxtLink>
</p>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import backgroundImagePath from '~/assets/image/login4.png'
import { useUserStore } from '@/stores/user'
// import { useBufferStore } from '~/stores/buffer'
export default {
name: 'Login',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
loading: false,
form: {
email: '',
password: ''
},
auth_error: false,
auth_error_message: '',
backgroundImagePath,
errors: []
}
},
computed: {
...mapState(useUserStore, ['is_login', 'user', 'token']),
translate (tag) {
return this.t(tag)
}
},
mounted () {
if (this.$route.query.verified === '1') {
this.$toast.success('Deine E-Mail wurde erfolgreich verifiziert. Bitte melde dich an.')
}
// useNuxtApp().$logger.log({ nuxtApp })
// useNuxtApp().$logger.log({ db })
// if (this.is_login){
// this.$router.push('/onboarding');
// }
},
methods: {
...mapActions(useUserStore, ['login', 'logout']),
loginNow () {
this.errors = []
this.auth_error = false
this.loading = true
this.$axios.post('/api/login', this.form).then(({ data }) => {
this.loading = false
if (data.status === 'success') {
this.$axios.head.Authorization = 'bearer ' + data.authorisation.token
this.login(data.user, data.authorisation.token)
this.$toast.success('Login successful!')
//
// If a user already did the onboarding, then the soundscape will be defined, so we can jump
// directly to the selected soundscape and start play.
//
let soundscape = 'Lagoon'
if (data.user.settings) { // if the user logged in first time no settings available, so push to onboarding
soundscape = data.user.settings.soundscape
if (soundscape !== '') {
// const soundscape = user.user.settings.soundscape
let url
switch (soundscape) {
case 'Lagoon':
url = '/'
break
case 'Meadow':
url = '/'
break
case 'Tropics':
url = '/'
break
case 'Forest':
url = '/'
break
default:
url = '/'
break
}
this.$router.push(this.localePath(url))
}
} else {
this.$router.push(this.localePath('/onboarding'))
}
} else {
this.$toast.error('Email or password is incorrect.')
}
}).catch((error) => {
if (error.message === 'user is not defined') {
this.loading = false
} else {
this.loading = false
if ((error.code === 'ERR_NETWORK' || error.code === 'NS_ERROR_DOM_BAD_URI') && this.user !== null && this.token !== '') {
this.$toast.warn(this.translate('Offline mode, please go online soon.'))
this.login(this.user, this.token)
}
this.auth_error = true
if (error.response.status === 500) {
this.auth_error_message = 'Es ist etwas schiefgelaufen. Versuche es später erneut'
}
if (error.response.status === 401) {
this.auth_error_message = 'Email oder Passwort ist falsch.'
}
if (error.response.status === 403) {
this.auth_error_message = 'Bitte bestätige zuerst deine E-Mail-Adresse.'
}
if (error.response.status === 422) {
this.errors = error.response.data.errors
this.auth_error = false
}
}
})
}
}
}
</script>
<style>
.videobg-content{
overflow: auto !important;
}
</style>
~/stores/audio

131
pages/auth/newpassword.vue Normal file
View File

@@ -0,0 +1,131 @@
<template>
<div>
<video-background
src="/video/bg-video.mp4"
poster="/images/poster.png"
style="height: 100vh;"
>
<div class="container-fluid">
<div class="row ">
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
<div class="row">
<div class="col-12 py-3 text-center">
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
</div>
</div>
<div class="row mt-lg-0 mt-4" style="height: 90%">
<div class="col-12 my-auto">
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
<h1 class="fw-bolder h3" style="color: #585C5E;">
{{ t('New Password') }}
</h1>
</div>
<div class="row pt-2">
<div class="col-12 col-md-8 col-xl-8 mx-auto">
<form method="POST" @submit.prevent="validateAndReset">
<div class="row pt-4">
<div class="col-12">
<label for="password" class="text-muted ">{{ t("Password") }} </label>
<input id="password" v-model="form.password" type="password" class="form-control" placeholder="***">
<div v-if="errors.password" class="invalid-feedback d-block">
{{ t(errors.password[0]) }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12">
<label for="confirm-password" class="text-muted ">{{ t("Confirm Password") }} </label>
<input id="confirm-password" v-model="form.password_confirmation" type="password" class="form-control" placeholder="***">
<div v-if="errors.password_confirmation" class="invalid-feedback d-block">
{{ t(errors.password_confirmation[0]) }}
</div>
</div>
</div>
<div class="row pt-4">
<div class="col">
<button type="submit" class="login-btn col-4" style="min-width:fit-content">
{{ t("Reset Password") }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const router = useRouter()
return {
t,
localePath,
router
}
},
data () {
return {
form: {
email: '', // wird aus URL-Param geholt
token: '', // wird aus URL-Param geholt
password: '',
password_confirmation: ''
},
errors: [],
loading: false
}
},
mounted () {
this.form.token = this.$route.query.token || ''
this.form.email = this.$route.query.email || ''
},
methods: {
validateAndReset () {
this.errors = []
if (this.form.password.length < 6) {
this.errors.password = ['The password must be at least 6 characters.']
return
}
if (this.form.password !== this.form.password_confirmation) {
this.errors.password_confirmation = ['The passwords do not match.']
return
}
this.resetPassword()
},
resetPassword () {
this.loading = true
this.errors = []
this.$axios.post('/api/password/reset', this.form).then(({ data }) => {
this.loading = false
if (data.success) {
this.$toast.success('Password successfully reset!')
this.$router.push(this.localePath('/auth/login'))
}
}).catch((error) => {
this.loading = false
this.$toast.error('Fehler beim Zurücksetzen des Passworts.')
if (error.response && error.response.status === 422) {
this.errors = error.response.data.errors
}
})
}
}
}
</script>

216
pages/auth/signup.vue Normal file
View File

@@ -0,0 +1,216 @@
<template>
<div>
<video-background
src="/video/bg-video.mp4"
poster="/images/poster.png"
style=" height: 100vh;"
>
<div class="container-fluid">
<div class="row">
<div
class="col-12 col-lg-4 bg-img d-none d-lg-block"
style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;"
/>
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
<div class="row">
<div class="col-12 py-3 text-center">
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
</div>
</div>
<div class="row mt-lg-0 mt-4" style="height: 90%">
<div class="col-12 my-auto">
<div class="text-center pt-4 pt-md-2 col-12 col-md-8 col-xl-8 mx-auto">
<h1 class="fw-bolder h3" style="color: #585C5E;">
{{ t('Sign up Header') }}
</h1>
<h2 class="text-muted h5 pt-2">
{{ t('Sign up') }}
</h2>
</div>
<div class="row pt-2">
<div class="col-12 col-xl-10 mx-auto">
<div v-if="registrationSuccess" class="alert alert-success text-center mx-0 mx-md-4">
{{ t('registersuccess') }}
</div>
<div
v-if="registrationError"
class="alert alert-danger text-center mx-0 mx-md-4"
>
{{ registrationErrorMessage }}
</div>
<form method="POST" @submit.prevent="submitRegister">
<!-- Hidden input for the token -->
<input type="hidden" name="token" :value="token">
<div class="row px-0 px-md-4 pt-3">
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
<label class="form-label">{{ t("First Name") }}</label>
<input v-model="form.first_name" autocomplete="given-name" type="text" :placeholder="t('Your first name')" class="form-control">
<div v-if="errors.first_name" class="invalid-feedback d-block">
{{ t(errors.first_name[0]) }}
</div>
</div>
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
<label class="form-label">{{ t("Surname") }}</label>
<input v-model="form.surname" autocomplete="family-name" type="text" :placeholder="t('Your surname')" class="form-control">
<div v-if="errors.surname" class="invalid-feedback d-block">
{{ t(errors.surname[0]) }}
</div>
</div>
</div>
<div class="row px-0 px-md-4 pt-3">
<div class="col-12 col-md-6 pt-2 col-lg-6 ">
<label class="form-label">{{ t("Email") }}</label>
<input v-model="form.email" autocomplete="email" type="email" placeholder="alex@mail.com" class="form-control">
<div v-if="errors.email" class="invalid-feedback d-block">
{{ t(errors.email[0]) }}
</div>
</div>
<div class="col-12 pt-2 col-md-6 col-lg-6 ">
<label class="form-label">{{ t("Password") }}</label>
<input v-model="form.password" type="password" autocomplete="new-password" :placeholder="t('Your password')" class="form-control">
<div v-if="errors.password" class="invalid-feedback d-block">
{{ t(errors.password[0]) }}
</div>
</div>
</div>
<div class="row px-md-4 pt-5 pb-2">
<div class="col-12 col-md-10">
<div class="form-check">
<input id="flexCheckIndeterminate" v-model="form.privacy" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="flexCheckIndeterminate">
{{ t("I have read") }}
<NuxtLink class="signup-link" :to="localePath('/settings/termsandcondition')">
{{ t("Terms & Conditions") }}
</NuxtLink>
{{ t("and the") }}
<NuxtLink class="signup-link" :to="localePath('/settings/dataprotection')">
{{ t("Privacy Policy") }}
</NuxtLink>
{{ t("and accept") }}
</label>
<div v-if="errors.privacy" class="invalid-feedback d-block">
{{ t(errors.privacy[0]) }}
</div>
</div>
</div>
</div>
<div class="row px-0 px-md-4 pt-4">
<div class="col">
<button :disabled="loading" type="submit" style="min-width:fit-content" class="login-btn col-4">
{{ t("Sign up btn") }}
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
</button>
<p class="pt-3">
{{ t("Already have an Account?") }} <NuxtLink class="signup-link" :to="localePath('/auth/login')">
{{ t("Log in now") }}
</NuxtLink>
</p>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
import { useRoute } from 'vue-router'
import { mapState, mapActions } from 'pinia'
import { ref, watch } from 'vue'
import backgroundImagePath from '~/assets/image/login4.png'
import { useUserStore } from '@/stores/user'
export default {
name: 'LoGin',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const route = useRoute() // Get the current route
const token = ref(route.query.token) // Extract the token from the query parameters
// Update form token whenever route.query.token changes
watch(() => route.query.token, (newToken) => {
token.value = newToken
})
return {
t,
localePath,
token
}
},
data () {
return {
backgroundImagePath,
loading: false,
errors: [],
registrationSuccess: false,
registrationError: false,
registrationErrorMessage: '',
form: {
first_name: '',
surname: '',
email: '',
password: '',
privacy: false,
token: '',
language: this.$i18n.locale
}
}
},
computed: {
...mapState(useUserStore, ['user'])
},
mounted () {
if (this.$route.query.error === 'invalid_or_expired_token') {
this.registrationError = true
this.registrationErrorMessage = 'Invalid or expired token. Please try again.'
}
if (this.$route.query.error === 'verification_failed') {
this.registrationError = true
this.registrationErrorMessage = 'Something went wrong. Please try again later.'
}
},
methods: {
...mapActions(useUserStore, ['login', 'logout']),
submitRegister () {
this.loading = true
this.errors = []
// Update form with the token from setup
this.form.token = this.token
this.$axios.post('/api/register', this.form)
.then(({ data }) => {
// useNuxtApp().$logger.log(data)
this.loading = false
this.$toast.success('Signup successful!')
this.registrationSuccess = true
})
.catch((error) => {
this.loading = false
if (error.response.status === 422) {
this.errors = error.response.data.errors
this.$toast.error(error.response.data.message)
}
})
}
}
} </script>
<style scoped>
@media only screen and (max-width: 587px) {
p{
font-size: 16px !important;
}
}
</style>

230
pages/auth/testerlogin.vue Normal file
View File

@@ -0,0 +1,230 @@
<template>
<div>
<video-background
src="/video/bg-video.mp4"
style="height: 100vh;"
poster="/images/poster.png"
>
<div class="container-fluid overflow-auto">
<div class="row ">
<div class="col-12 col-lg-4 bg-img d-none d-lg-block" style="background-image: url('/images/login.svg');background-size: cover;height: 100vh;" />
<div class="col-12 col-lg-8 pt-4 px-3 px-sm-5 px-md-5 pb-5">
<div class="row">
<div class="col-12 py-3 text-center">
<img src="/images/logo.svg" height="35" class="img " alt="Mindboost Logo">
</div>
</div>
<div class="row" style="height: 90%">
<div class="col-12 my-auto">
<div class="text-center pt-1 ">
<h2 class="fw-bolder display-6 text-center">
{{ t('Welcome, Tester!') }}
</h2>
<span class="fs-5 pt-1 text-muted">{{ t('Thank you for using and improving Mindboost') }}</span>
</div>
<div class="row pt-2">
<div class="col-12">
<form method="POST" class=" pb-5 " @submit.prevent="submitRegister()">
<div class="row pt-3 justify-content-center">
<div class="col-12 col-sm-8 col-md-8 col-lg-6">
<label class="form-label">{{ t('Name') }}</label>
<input v-model="form.email" type="input" placeholder="e.g. your name, a tag, date or what ever you like.." class="form-control">
<div v-if="errors.email" class="invalid-feedback d-block">
{{ t('Please add first a name') }}
</div>
<div v-if="auth_error" class="invalid-feedback d-block">
{{ t('Something is wrong, please try again!') }}
</div>
</div>
</div>
<div class="row justify-content-center pt-3">
<div class="col-12 col-sm-8 col-md-8 col-lg-6">
<label class="form-label">{{ t('Comments') }}</label>
<input v-model="form.password" type="input" :placeholder="t('How did you discover Mindboost?')" class="form-control">
</div>
</div>
<div class="row justify-content-center pb-5">
<div class="col-12 col-sm-8 col-md-8 col-lg-6 pt-4 text-center">
<button type="submit" class=" login-btn col-12">
{{ t('Go') }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t('Loading...') }}</span>
</div>
</button>
<h5 class="text-center pt-3">
{{ t("Want to have an Account?") }} <NuxtLink class="signup-link" :to="localePath('/auth/signup')">
{{ t("Sign Up") }}
</NuxtLink>
</h5>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import backgroundImagePath from '~/assets/image/login4.png'
import { useUserStore } from '@/stores/user'
// import { useBufferStore } from '~/stores/buffer'
export default {
name: 'TestLogin',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
loading: false,
form: {
email: '',
password: ''
},
auth_error: false,
backgroundImagePath,
errors: []
}
},
computed: {
...mapState(useUserStore, ['is_login', 'user', 'token']),
translate (tag) {
const { t } = useI18n()
return t(tag)
}
},
mounted () {
// useNuxtApp().$logger.log({ nuxtApp })
// useNuxtApp().$logger.log({ db })
// if (this.is_login){
// this.$router.push('/onboarding');
// }
},
methods: {
...mapActions(useUserStore, ['login', 'logout']),
formatToValidEmail (input) {
// eslint-disable-next-line no-useless-escape
let email = input.replace(/[^a-zA-Z0-9\._-]/g, '') // Remove invalid characters
if (!/@/.test(email)) {
email += '@betatester.mindboost.team' // Append a default domain if no '@'
} else if (email.split('@')[1] === '') {
email += '@betatester.mindboost.team' // Append "example.com" if there is no domain part
}
return email
},
submitRegister () {
this.loading = true
const email = this.formatToValidEmail(this.form.email)
this.$axios.post('/api/register', {
first_name: this.form.email,
surname: this.form.password || 'no comments',
email,
password: email
}).then(({ data }) => {
this.loading = false
this.$toast.success('Signup successfully....')
this.login(data.user, data.authorisation.token)
this.$router.push(this.localePath('/onboarding'))
}).catch((error) => {
this.loading = false
if (error.response.status === 422) {
this.errors = error.response.data.errors
this.$toast.error(error.response.data.message)
}
})
},
loginNow () {
this.errors = []
this.auth_error = false
this.loading = true
this.$axios.post('/api/login', this.form).then(({ data }) => {
this.loading = false
if (data.status === 'success') {
this.$axios.head.Authorization = 'bearer ' + data.authorisation.token
this.login(data.user, data.authorisation.token)
this.$toast.success('Login Successfully....')
//
// If a user already did the onboarding, then the soundscape will be defined, so we can jump
// directly to the selected soundscape and start play.
//
let soundscape = 'Lagoon'
if (data.user.settings) { // if the user logged in first time no settings available, so push to onboarding
soundscape = data.user.settings.soundscape
if (soundscape !== '') {
// const soundscape = user.user.settings.soundscape
let url
switch (soundscape) {
case 'Lagoon':
url = '/'
break
case 'Meadow':
url = '/'
break
case 'Tropics':
url = '/'
break
case 'Forest':
url = '/'
break
default:
url = '/'
break
}
this.$router.push(this.localePath(url))
}
} else {
this.$router.push(this.localePath('/onboarding'))
}
} else {
this.$toast.error('Email or password is incorrect.')
}
}).catch((error) => {
if (error.message === 'user is not defined') {
this.loading = false
} else {
this.loading = false
if ((error.code === 'ERR_NETWORK' || error.code === 'NS_ERROR_DOM_BAD_URI') && this.user !== null && this.token !== '') {
this.$toast.warn(this.translate('Offline mode, please go online soon.'))
this.login(this.user, this.token)
}
if (error.response.status === 500) {
this.auth_error = true
this.$toast.error('Es ist etwas schiefgelaufen. Versuche es später erneut')
}
if (error.response.status === 401) {
this.auth_error = true
this.$toast.error('Email or password is incorrect.')
}
if (error.response.status === 422) {
this.errors = error.response.data.errors
}
}
})
}
}
}
</script>
<style>
.videobg-content{
overflow: auto !important;
}
</style>
~/stores/audio

View File

@@ -0,0 +1,43 @@
<template>
<div class="text-center mt-10">
<h1 class="text-2xl font-bold">E-Mail wird verifiziert...</h1>
<p>Bitte einen Moment Geduld.</p>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const localePath = useLocalePath()
const config = useRuntimeConfig()
const apiBase = config.public.apiUrl
onMounted(async () => {
try {
const token = route.params.token
await axios.get(`${apiBase}/api/verify-email/${token}`)
// Weiterleitung mit Query-Param (z.B. für Erfolgsmeldung im Login)
router.push({
path: localePath('/auth/login'),
query: { verified: '1' }
})
} catch (error) {
if (error.response && (error.response.status === 400 || error.response.status === 404)) {
// Token ungültig oder abgelaufen Weiterleitung zur Registrierung
router.push({
path: localePath('/auth/signup'),
query: { error: 'invalid_or_expired_token' }
})
} else {
router.push({
path: localePath('/auth/login'),
query: { error: 'verification_failed' }
})
}
}
})
</script>

View File

@@ -0,0 +1,26 @@
<template>
<div>
<svg
width="150px"
height="150px"
class="icon"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
fill="#CCCCCC"
>
<g id="SVGRepo_bgCarrier" stroke-width="0" />
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round" />
<g id="SVGRepo_iconCarrier">
<path d="M512 669.824a155.584 155.584 0 0 0 155.136-155.136V223.808c0-85.312-69.824-155.136-155.136-155.136S356.864 138.496 356.864 223.808v290.88A155.584 155.584 0 0 0 512 669.824z" fill="" />
<path d="M512 625.024a110.4 110.4 0 0 1-110.336-110.336V223.808c0-60.864 49.472-110.336 110.336-110.336s110.336 49.472 110.336 110.336v290.88c0 60.864-49.472 110.336-110.336 110.336z" fill="#e9c045" />
<path d="M753.216 398.336a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.32v117.632A203.008 203.008 0 0 1 512 718.976a203.136 203.136 0 0 1-202.752-203.008V398.336h-0.32a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.256v117.632c0 126.656 98.176 229.696 222.144 239.808v108.672H376.256a19.392 19.392 0 1 0 0 38.848h271.488a19.392 19.392 0 0 0 0-38.848H531.392V755.84c123.968-10.112 222.144-113.216 222.144-239.808V398.336h-0.32z" fill="" />
<path d="M753.216 398.336a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.32v117.632A203.008 203.008 0 0 1 512 718.976a203.136 203.136 0 0 1-202.752-203.008V398.336h-0.32a19.2 19.2 0 0 0-19.072-18.624 19.328 19.328 0 0 0-19.136 18.624h-0.256v117.632c0 126.656 98.176 229.696 222.144 239.808v108.672H376.256a19.392 19.392 0 1 0 0 38.848h271.488a19.392 19.392 0 0 0 0-38.848H531.392V755.84c123.968-10.112 222.144-113.216 222.144-239.808V398.336h-0.32zM390.72 305.344a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72zM390.72 379.712a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72z" fill="" />
<path d="M390.72 454.08a16 16 0 0 1 0-32h129.344a16 16 0 0 1 0 32H390.72z" fill="" />
</g>
</svg>
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div>
<AudioFileSelector @file-selected="updateSrc" />
<KeyboardPlayHandler />
<AudioTag :src="audioSrc" :volume="volume" :play="playing" />
<input
id="gain-control"
v-model="volume"
type="range"
min="0"
max="1.0"
step="0.01"
@wheel="changeNoiseGain"
>
</div>
</template>
<script setup>
import { ref } from 'vue'
import AudioTag from '~/components/experiments/AudioTag.vue'
import tracksConfig from '~/tracks.config'
import AudioFileSelector from '~/components/AudioFileSelector.vue'
import { useAudioStore } from '~/stores/audio'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
const audioSrc = ref('')
const volume = ref(0)
const playing = computed(() => useAudioStore().playing)
const updateSrc = (e) => {
useNuxtApp().$logger.log('file Selected update', { e })
// Update the audioSrc with the selected file
audioSrc.value = e
}
const changeNoiseGain = (event) => {
event.preventDefault()
const volumepercents = volume.value * 100
// Determine the direction of the scroll
const delta = Math.sign(event.deltaY)
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
volume.value -= delta / 100
}
}
</script>

View File

@@ -0,0 +1,40 @@
<template>
<div>
<AudioFileSelector @file-selected="updateSrc" />
<AudioTagWebAudio :src="audioSrc" :volume="volume" />
<input
id="gain-control"
v-model="volume"
type="range"
min="0"
max="1.0"
step="0.01"
@wheel="changeNoiseGain"
>
</div>
</template>
<script setup>
import { ref } from 'vue'
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
import tracksConfig from '~/tracks.config'
import AudioFileSelector from '~/components/AudioFileSelector.vue'
const audioSrc = ref('')
const volume = ref(0)
const updateSrc = (e) => {
useNuxtApp().$logger.log('file Selected update', { e })
// Update the audioSrc with the selected file
audioSrc.value = e
}
const changeNoiseGain = (event) => {
event.preventDefault()
const volumepercents = volume.value * 100
// Determine the direction of the scroll
const delta = Math.sign(event.deltaY)
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
volume.value -= delta / 100
}
}
</script>

View File

@@ -0,0 +1,39 @@
<template>
<div>
<AudioFileSelector @file-selected="updateSrc" />
<AudioTagWebAudioStreaming :src="audioSrc" :volume="volume" />
<input
id="gain-control"
v-model="volume"
type="range"
min="0"
max="1.0"
step="0.01"
@wheel="changeNoiseGain"
>
</div>
</template>
<script setup>
import { ref } from 'vue'
import AudioFileSelector from '~/components/AudioFileSelector.vue'
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
const audioSrc = ref('')
const volume = ref(0)
const updateSrc = (e) => {
useNuxtApp().$logger.log('file Selected update', { e })
// Update the audioSrc with the selected file
audioSrc.value = e
}
const changeNoiseGain = (event) => {
event.preventDefault()
const volumepercents = volume.value * 100
// Determine the direction of the scroll
const delta = Math.sign(event.deltaY)
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
volume.value -= delta / 100
}
}
</script>

View File

@@ -0,0 +1,44 @@
<template>
<div>
<AudioFileSelector @file-selected="updateSrc" />
<AudioTag :src="audioSrc" :volume="volume" />
<AudioTagWebAudio :src="audioSrc" :volume="volume" />
<AudioTagWebAudioStreaming :src="audioSrc" :volume="volume" />
<input
id="gain-control"
v-model="volume"
type="range"
min="0"
max="1.0"
step="0.01"
@wheel="changeNoiseGain"
>
</div>
</template>
<script setup>
import { ref } from 'vue'
import AudioTag from '~/components/experiments/AudioTag.vue'
import AudioFileSelector from '~/components/AudioFileSelector.vue'
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
const audioSrc = ref('')
const volume = ref(0)
const updateSrc = (e) => {
useNuxtApp().$logger.log('file Selected update', { e })
// Update the audioSrc with the selected file
audioSrc.value = e
}
const changeNoiseGain = (event) => {
event.preventDefault()
const volumepercents = volume.value * 100
// Determine the direction of the scroll
const delta = Math.sign(event.deltaY)
if (volumepercents - delta < 101 && volumepercents - delta > -1) {
volume.value -= delta / 100
}
}
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<NoisePatchMusic />
</div>
</template>
<script setup>
import NoisePatchMusic from '~/archive/components/tests/NoisePatchMusic_glitchOnLoad.vue'
</script>

View File

@@ -0,0 +1,10 @@
<template>
<div>
<MicNoisePatchMusic2 />
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
import MicNoisePatchMusic2 from '~/archive/components/tests/MicNoisePatchMusic2.vue'
</script>

View File

@@ -0,0 +1,69 @@
<template>
<div v-show="true">
<h1>Microphone</h1>
<button class="btn btn-primary" @click="attach">
{{ 'Mikrofon aktivieren' }}
</button>
<button class="btn btn-secondary" @click="detach">
{{ 'Mikrofon trennen' }}
</button>
</div>
</template>
<script lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import type { Logger } from 'pino'
import { useMicStore } from '~/stores/microphone'
import { useAudioStore } from '~/stores/audio'
export default {
name: 'MicrophoneStoreHandler',
emits: ['update:attach'],
setup (_props, { emit }) {
const audioStore = useAudioStore()
const microphone = ref<Promise<MediaStream> | null>(null)
const microphoneActive = ref(false)
const logger = useNuxtApp().$logger as Logger
const attach = () => {
logger.info('attach microphone')
if (!microphone.value) {
microphone.value = navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
},
video: false
})
microphoneActive.value = true
}
return microphone.value.then((stream) => {
emit('update:attach', stream)
return stream
})
}
const detach = async () => {
logger.info('detach microphone')
if (microphone.value) {
try {
const stream = await microphone.value
stream.getTracks().forEach(track => track.stop())
} catch (error) {
}
microphone.value = null
microphoneActive.value = false
}
}
// Return the public properties and methods
return {
attach,
detach,
isPlaying: () => audioStore.playing,
microphoneActive
}
}
}
</script>

View File

@@ -0,0 +1,10 @@
<template>
<div>
<StateBar />
</div>
</template>
<script setup>
import Microphone from '~/components/experiments/tests/Microphone.vue'
import StateBar from '~/components/experiments/statemanagement/StateBar.vue'
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<NoiseGain />
</div>
</template>
<script setup>
import NoiseGain from '~/components/experiments/homepages/NoiseGain.vue'
</script>

View File

@@ -0,0 +1,10 @@
<template>
<div>
<NoiseControlledBand center-frequency="1000" />
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
import NoiseControlledBand from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudioBand.vue'
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div>
<KeyboardPlayHandler />
<NoiseMusicGain />
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
import NoiseMusicGain from '~/components/experiments/tests/NoiseMusicGain.vue'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<NoiseMusicGainFadeIn />
</div>
</template>
<script setup>
import NoiseMusicGainFadeIn from '~/components/experiments/tests/NoiseMusicGainFadeIn.vue'
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<NoiseMusicGainMic />
</div>
</template>
<script setup>
import NoiseMusicGainMic from '~/components/experiments/tests/NoiseMusicGainMic.vue'
</script>

View File

@@ -0,0 +1,10 @@
<template>
<div>
<NoiseMusicGainPlayPause />
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
import NoiseMusicGainPlayPause from '~/components/experiments/tests/NoiseMusicGainPlayPause.vue'
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div>
<KeyboardPlayHandler />
<NoiseMusicGainPlayPauseDevice />
</div>
</template>
<script setup>
import NoiseMusicGainPlayPauseDevice from '~/archive/components/tests/NoiseMusicGainPlayPauseDevice.vue'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
</script>

View File

@@ -0,0 +1,11 @@
<template>
<div>
Version 1 Noise Music WebAudio:
<NoiseMusicWebAudio />
</div>
</template>
<script setup>
// import Player from '~/components/Player.vue'
import NoiseMusicWebAudio from '~/components/experiments/tests/NoiseMusicWebAudio.vue'
</script>

View File

@@ -0,0 +1,77 @@
<!-- eslint-disable vue/no-multiple-template-root -->
<template>
<h4> Die Audio HowlWebAudioBridge sorgt zwar für ein sauberes Streaming, aber lässt sich dann nicht über WebAudio regeln</h4>
<div>
<HowlWebAudioBridge
:src="source"
:audio-context="audioContext"
:on-ready="handleAudioNode"
/>
<button @click="testAudio">Test Audio</button>
</div>
</template>
<script setup lang="ts">
import HowlWebAudioBridge from '~/components/Player/HowlWebAudioBridge.vue'
import tracksConfig from '~/tracks.config'
import { useAudioStore, ensureAudio } from '~/stores/audio'
const audioContext = useAudioStore().getContext()
const source = tracksConfig.debug_src
const audioNode = ref({} as AudioBufferSourceNode | MediaElementAudioSourceNode | MediaStreamAudioSourceNode)
const gainNode = ref({} as GainNode)
const audioReady = ref(false as boolean)
const howlElement = ref({} as Howl)
async function testAudio () {
await ensureAudio()
if (audioReady && gainNode && howlElement) {
howlElement.value.play()
const audioDestination = audioContext.destination
const gain = gainNode.value as GainNode
if (audioNode instanceof AudioBufferSourceNode) {
audioNode.loop = true
audioNode.playbackRate.value = 1
audioNode.connect(gain).connect(audioDestination)
}
if (audioNode instanceof MediaElementAudioSourceNode) {
audioNode.connect(gain).connect(audioDestination)
}
if (audioNode instanceof MediaStreamAudioSourceNode) {
audioNode.connect(gain).connect(audioDestination)
}
gain.gain.cancelScheduledValues(audioContext.currentTime)
gain.gain.linearRampToValueAtTime(1, audioContext.currentTime + 2)
useNuxtApp().$logger.log('Gain connected to destination')
} else {
useNuxtApp().$logger.warn('Audio is not yet ready to test it')
}
}
function handleAudioNode (node: MediaElementAudioSourceNode | AudioBufferSourceNode | MediaStreamAudioSourceNode, howl: Howl | null) {
useNuxtApp().$logger.log('AUDIONODE IST ANGEKOMMEN ')
useNuxtApp().$logger.log({ node })
useNuxtApp().$logger.log({ howl })
audioNode.value = node
if (howl) { howlElement.value = howl }
gainNode.value = audioContext.createGain() as GainNode
gainNode.value.gain.setValueAtTime(0, audioContext.currentTime)
// Not connected to any destination, gain currently muted
if (node instanceof AudioBufferSourceNode) {
node.loop = true
node.playbackRate.value = 1
node.connect(gainNode.value)
}
if (node instanceof MediaElementAudioSourceNode) {
node.connect(gainNode.value)
}
if (node instanceof MediaStreamAudioSourceNode) {
node.connect(gainNode.value)
}
audioReady.value = true
}
</script>

View File

@@ -0,0 +1,12 @@
<template>
<div>
<RNBODevice />
<GainController />
</div>
</template>
<script setup>
import GainController from '~/components/experiments/GainController.vue'
import RNBODevice from '~/components/experiments/homepages/RNBODevice.vue'
import NavigationBar from '~/components/NavigationBar.vue'
</script>

View File

@@ -0,0 +1,49 @@
<template>
<div class="p-4">
<h1 class="text-xl font-bold mb-4">Audio Fade-In Beispiel</h1>
<audio
ref="audioEl"
:src="source"
crossorigin="anonymous"
/>
<button
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition"
@click="playWithFadeIn"
>
Abspielen mit Fade-In
</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
import tracksConfig from '~/tracks.config'
const audioEl = ref(null)
const source = ref(tracksConfig.lagoon_48_mp3_src)
const playWithFadeIn = async () => {
const audio = audioEl.value
const sink = useUserStore().audioOutputDevice
audio.value?.setSinkId(sink.deviceId)
if (!audio) { return }
const audioContext = new (window.AudioContext || window.webkitAudioContext)()
const sourceNode = audioContext.createMediaElementSource(audio)
const gainNode = audioContext.createGain()
// Anfangslautstärke auf 0 setzen
gainNode.gain.setValueAtTime(0, audioContext.currentTime)
// Ziel-Lautstärke in 3 Sekunden erreichen
gainNode.gain.linearRampToValueAtTime(1.0, audioContext.currentTime + 3)
// Verkabeln
sourceNode.connect(gainNode)
gainNode.connect(audioContext.destination)
// Abspielen
await audio.play()
}
</script>

View File

@@ -0,0 +1,68 @@
<template>
<div class="p-4">
<h1 class="text-xl font-bold mb-4">Howler MediaStream + volume Fade-In</h1>
<h3>Startet, Kontrolle der Lautstärke über volume-API, aber erst kommt 100% Lautstärke Audio, bevor es verstummt und dann einfaded</h3>
<button
class="px-4 py-2 bg-indigo-600 text-white rounded hover:bg-indigo-700 transition"
@click="startCaptureWithVolumeRamp"
>
Starten & Lautstärke einblenden
</button>
<div v-if="mediaStream" class="mt-4 text-green-700 font-mono">
MediaStream erzeugt!
</div>
<p v-if="error" class="text-red-600 mt-2">{{ error }}</p>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Howl } from 'howler'
import tracksConfig from '~/tracks.config'
const mediaStream = ref(null)
const error = ref(null)
const startCaptureWithVolumeRamp = async () => {
try {
const sound = new Howl({
src: [tracksConfig.lagoon_48_mp3_src],
html5: true,
preload: true,
muted: true
})
await new Promise(resolve => sound.once('load', resolve))
const audioElement = sound._sounds[0]._node
// Volume auf 0 (mute) setzen
audioElement.volume = 0
// captureStream vor play aufrufen
const stream = audioElement.captureStream()
// Starte Audio
sound.play()
// Starte Volume-Ramp (linear über ~3 Sekunden)
let volume = 0
const step = 0.05 // Schrittgröße
const interval = 150 // ms zwischen Schritten
const ramp = setInterval(() => {
volume += step
audioElement.volume = Math.min(1, volume)
if (volume >= 1) {
clearInterval(ramp)
}
}, interval)
mediaStream.value = stream
} catch (err) {
useNuxtApp().$logger.error(err)
error.value = 'Fehler beim Start: ' + err.message
}
}
</script>

View File

@@ -0,0 +1,9 @@
<template>
<div>
<Player />
</div>
</template>
<script setup>
import Player from '~/components/experiments/tests/showcases/PlayerComponent.vue'
</script>

View File

@@ -0,0 +1,168 @@
<template>
<div>
<h1>Test Fall 4</h1>
<h3>Howler als Stream mit der HowlWebAudioBridge (createMediaElementSource)</h3>
<KeyboardPlayHandler />
<button @click="play">Play</button>
<div v-if="false" id="statistics">
{{ Object.keys(gains).length }}
{{ Object.keys(nodes).length }}
</div>
<HowlWebAudioBridge
:src="source[0]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 63)"
/>
<HowlWebAudioBridge
:src="source[1]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 125)"
/>
<HowlWebAudioBridge
:src="source[2]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 250)"
/>
<HowlWebAudioBridge
:src="source[3]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 500)"
/>
<HowlWebAudioBridge
:src="source[4]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 1000)"
/>
<HowlWebAudioBridge
:src="source[5]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 2000)"
/>
<HowlWebAudioBridge
:src="source[6]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 4000)"
/>
<HowlWebAudioBridge
:src="source[7]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 8000)"
/>
<HowlWebAudioBridge
:src="source[8]"
:audio-context="audioContext"
:on-ready="(node, howl) => registerAudioNode(node, howl, 16000)"
/>
</div>
</template>
<script setup lang="ts">
import type { Logger } from 'pino'
import { useAudioStore } from '~/stores/audio'
import tracksConfig from '~/tracks.config'
import HowlWebAudioBridge from '~/components/Player/HowlWebAudioBridge.vue'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
const source = ref([tracksConfig['63_src'], tracksConfig['125_src'], tracksConfig['250_src'], tracksConfig['500_src'], tracksConfig['1000_src'], tracksConfig['2000_src'], tracksConfig['4000_src'], tracksConfig['8000_src'], tracksConfig['16000_src']])
const audioContext = ref(useAudioStore().getContext()as AudioContext)
const nodes = ref([] as Record<number, AudioNode>)
const gains = ref([] as Record<number, GainNode>)
const howls = ref([] as Array<Howl>)
const masterGain = ref(null as (GainNode | null))
const lastAction = audioContext.value.currentTime
const logger = useNuxtApp().$logger as Logger
const registerAudioNode = (node:AudioNode | null, howl: Howl| null, freq: number) => {
if (howl) {
logger.info('register new Node push it', howl.state)
howls.value.push(howl)
const mediaElement = (howl as any)._sounds[0]._node as HTMLAudioElement
mediaElement.muted = false
logger.info('mediaElement muted')
}
if (node) {
const gainNode = node.context.createGain()
const pannerNode = audioContext.value.createStereoPanner()
pannerNode.pan.value = 1
gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
gainNode.gain.linearRampToValueAtTime(0.5, audioContext.value.currentTime + 2)
const master = masterGain.value || useAudioStore().getMasterGainNoise()
nodes.value[freq] = node
gains.value[freq] = gainNode
node.connect(gainNode)
gainNode.connect(master)
pannerNode.connect(master)
logger.info('mediaElement nicht mehr gemutet')
logger.info('AudioNode ready, but not connected to destination', { node })
logger.info('Number of GainNodes', Object.keys(gains.value).length)
logger.info('Number of AudioNodes', Object.keys(nodes.value).length)
}
}
watch(() => useAudioStore().playing, // Überwache explizit den state
(newState) => {
if (newState) {
play()
} else {
pause()
}
}
)
watch(() => lastAction, // Überwache explizit den state
(lastTimestamp) => {
if (lastTimestamp > 50) {
play() // Overlay anzeigen
} else {
pause()
}
}
)
const pause = () => {
logger.info('pause')
const master = masterGain.value
const currentTime = audioContext.value.currentTime
// master?.gain.cancelScheduledValues(currentTime)
master?.gain.linearRampToValueAtTime(0, currentTime + 5)
const lastAction = currentTime
howls.value.forEach((howl) => {
howl.pause()
}
)
master?.disconnect()
// if (gainNode) {
// gainNode.connect(audioContext.value.destination)
// gainNode.gain.cancelScheduledValues(audioContext.value.currentTime)
// gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
// gainNode.gain.linearRampToValueAtTime(2, audioContext.value.currentTime + 4)
// } else {
// logger.info('GAINNODE NOT CREATED WE WILL NOT START')
// }
}
const play = () => {
const newHowls = howls.value
const master = masterGain.value || useAudioStore().getMasterGainNoise()
logger.info('Start this', { newHowls })
howls.value.forEach(async (howl) => {
howl.play()
logger.info('Start this', { newHowls })
masterGain?.value?.gain.linearRampToValueAtTime(1, audioContext.value.currentTime + 3)
const mediaElement = await (howl as any)._sounds[0]._node as HTMLAudioElement
mediaElement.muted = false
}
)
master.connect(audioContext.value.destination)
// if (gainNode) {
// gainNode.connect(audioContext.value.destination)
// gainNode.gain.cancelScheduledValues(audioContext.value.currentTime)
// gainNode.gain.setValueAtTime(0, audioContext.value.currentTime)
// gainNode.gain.linearRampToValueAtTime(2, audioContext.value.currentTime + 4)
// } else {
// logger.info('GAINNODE NOT CREATED WE WILL NOT START')
// }
}
</script>

View File

@@ -0,0 +1,105 @@
<!--
/**
* @component ControlValueBasedPlayer
* @description A component that renders multiple NoiseControlledBand components,
* each centered around a specific frequency. This can be used for
* audio spectrum visualization or control. Now includes a loading spinner
* that disappears when all NoiseControlledBand components are ready.
*
* @uses NoiseControlledBand
*
* @example
* <ControlValueBasedPlayer />
*
* @remarks
* - Utilizes Vue 3 Composition API
* - Renders NoiseControlledBand components for standard audio frequency bands
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
* - Includes a loading spinner that disappears when all components are ready
*/
-->
<template>
<div>
<h1> Test Fall 1 mit Control Value Patch</h1>
<h3> Ein AudioTag, dass über die Volume-APi gesteuert wird</h3>
<div v-if="isExperimentsRoute">
<KeyboardPlayHandler />
<PlayButton />
</div>
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
Loaded Bands: {{ loadedBands }}
<div>
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterAttack"
type="range"
:min="4800"
:max="1920000"
>
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterRelease"
type="range"
:min="4800"
:max="1920000"
>
</div>
<NoiseControlledBand
v-for="frequency in frequencies"
:key="frequency"
:master-attack="masterAttack"
:master-release="masterRelease"
:center-frequency="frequency"
@ready="onBandReady"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
import NoiseControlledBand from '~/components/experiments/tests/ControlValues/NoiseControlledBand.vue'
export default defineComponent({
components: {
NoiseControlledBand,
KeyboardPlayHandler,
PlayButton
},
setup () {
const { t } = useI18n()
const frequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
const loadedBands = ref(0)
const route = useRoute()
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
const masterAttack = ref(120000) // Beispielwert in Samples
const masterRelease = ref(144000)
const loading = computed(() => loadedBands.value < frequencies.value.length)
const onBandReady = () => {
loadedBands.value++
}
return {
frequencies,
loading,
onBandReady,
t,
loadedBands,
masterAttack,
masterRelease,
isExperimentsRoute
}
}
})
</script>

View File

@@ -0,0 +1,74 @@
<template>
<div>
<KeyboardPlayHandler />
<h1> Test Fall 2 mit Control Value Patch</h1>
<h2>Use AudioBufferSourceNode over controlled NoiseControlledWebAudioBand</h2>
<h3> press space to start</h3>
<div>
Masking Gain : <input
id="gain-control"
:onchange="updateMasterGain"
type="range"
min="0"
max="1"
step="0.01"
>
</div>
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
Loaded Bands: {{ loadedBands }}
<NoiseControlledWebAudioBand
v-for="frequency in frequencies"
v-show="false"
:key="frequency"
:center-frequency="frequency"
:master-gain="masterGainNode"
@ready="onBandReady"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
import NoiseControlledWebAudioBand from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudioBand.vue'
import { useAudioStore } from '~/stores/audio'
export default defineComponent({
components: {
NoiseControlledWebAudioBand,
KeyboardPlayHandler
},
setup () {
const { t } = useI18n()
const frequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
const loadedBands = ref(0)
const masterGainNode = computed(() => { return useAudioStore().getMasterGainNoise() })
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
const masterRelease = ref(144000 * 2)
const loading = computed(() => loadedBands.value < frequencies.value.length)
const updateMasterGain = (changeEvent:Event) => {
const newValue = changeEvent?.target as any
masterGainNode.value.gain.linearRampToValueAtTime(newValue.value, masterGainNode.value.context.currentTime + 0.25)
}
const onBandReady = () => {
loadedBands.value++
}
return {
frequencies,
loading,
onBandReady,
t,
loadedBands,
masterGainNode,
updateMasterGain,
masterAttack,
masterRelease
}
}
})
</script>

View File

@@ -0,0 +1,110 @@
<!--
/**
* @component ControlValueBasedPlayer
* @description A component that renders multiple NoiseControlledBand components,
* each centered around a specific frequency. This can be used for
* audio spectrum visualization or control. Now includes a loading spinner
* that disappears when all NoiseControlledBand components are ready.
*
* @uses NoiseControlledBand
*
* @example
* <ControlValueBasedPlayer />
*
* @remarks
* - Utilizes Vue 3 Composition API
* - Renders NoiseControlledBand components for standard audio frequency bands
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
* - Includes a loading spinner that disappears when all components are ready
*/
-->
<template>
<div>
<h1> Test Fall 3 mit Control Value Patch</h1>
<h3> Drei AudioTags gesteuert über 3 RNBOControlValues. Über die Volume-APi wird die Lautstärke gesteuert</h3>
<div v-if="isExperimentsRoute">
<KeyboardPlayHandler />
<PlayButton />
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
Loaded Bands: {{ loadedBands }}
<div>
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterAttack"
type="range"
:min="4800"
:max="1920000"
>
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterRelease"
type="range"
:min="4800"
:max="1920000"
>
</div>
<NoiseControlled3Band
v-for="(frequency, index) in frequencies"
:key="frequency"
:master-attack="masterAttack"
:master-release="masterRelease"
:center-frequency="frequency"
:q-factor="qFactors[index]"
@ready="onBandReady"
@update:mid-volume="controlMusicGain"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
import NoiseControlled3Band from '~/components/experiments/tests/ControlValues/NoiseControlled3Band.vue'
export default defineComponent({
components: {
NoiseControlled3Band,
KeyboardPlayHandler,
PlayButton
},
setup () {
const { t } = useI18n()
const frequencies = ref([150, 1500, 8000])
const qFactors = ref([0.8, 0.9, 0.6])
const loadedBands = ref(0)
const route = useRoute()
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
const masterRelease = ref(144000 * 2)
const loading = computed(() => loadedBands.value < frequencies.value.length)
const onBandReady = () => {
loadedBands.value++
}
return {
frequencies,
loading,
onBandReady,
t,
loadedBands,
masterAttack,
masterRelease,
isExperimentsRoute,
qFactors,
controlMusicGain
}
}
})
</script>

View File

@@ -0,0 +1,180 @@
<!--
/**
* @component ControlValueBasedPlayer
* @description A component that renders multiple NoiseControlledBand components,
* each centered around a specific frequency. This can be used for
* audio spectrum visualization or control. Now includes a loading spinner
* that disappears when all NoiseControlledBand components are ready.
*
* @uses NoiseControlledBand
*
* @example
* <ControlValueBasedPlayer />
*
* @remarks
* - Utilizes Vue 3 Composition API
* - Renders NoiseControlledBand components for standard audio frequency bands
* - Frequencies: 63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000 Hz
* - Includes a loading spinner that disappears when all components are ready
*/
-->
<template>
<div>
<div v-if="isExperimentsRoute">
<h1> Test Fall 4 mit Control Value Patch</h1>
<h3> Drei WebAudioTag gesteuert über 3 RNBOControlValues. Über die WebAudio-APi wird die Lautstärke gesteuert</h3>
<KeyboardPlayHandler />
<PlayButton />
<div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }}</span>
</div>
Loaded Bands: {{ loadedBands }}
<div>
<label>Attack: {{ (masterAttack / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterAttack"
type="range"
:min="4800"
:max="1920000"
>
<label>Release: {{ (masterRelease / 480000).toFixed(2) }}s</label>
<input
v-model.number="masterRelease"
type="range"
:min="4800"
:max="1920000"
>
</div>
</div>
<NoiseControlled3BandWebAudio
v-for="(frequency, index) in frequencies"
v-show="false"
:key="frequency"
:master-attack="masterAttack"
:master-release="masterRelease"
:center-frequency="frequency"
:master-gain="masterGain"
:q-factor="qFactors[index]"
@ready="onBandReady"
@update:mid-volume="controlMusicGain"
/>
<div class="slider-wrapper">
<img
v-if="muted"
style="width: 25px; height: 25px;"
src="~/assets/image/sound_muted.svg"
title="Click to unmute"
@click="toggleMute()"
>
<img
v-else
style="width: 25px; height: 25px;"
src="~/assets/image/sound.svg"
title="Click to mute"
@click="toggleMute()"
>
<div class="slider">
<input
id="gain-control"
v-model="masterGain.gain.value"
type="range"
min="0"
max="1"
step="0.02"
data-toggle="tooltip"
data-placement="top"
title="Change the volume by click, scroll or touch"
@wheel.prevent="changeVolumeOnWheel"
>
<span
class="slider-progress-bar"
:style="{ width: `${masterGain.gain.value * 100}%` }"
/>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import KeyboardPlayHandler from '~/archive/components/KeyboardPlayHandler.vue'
import PlayButton from '~/components/experiments/statemanagement/PlayButton.vue'
import NoiseControlled3BandWebAudio from '~/components/experiments/tests/ControlValues/NoiseControlledWebAudio3Band.vue'
import { useAudioStore } from '~/stores/audio'
export default defineComponent({
components: {
NoiseControlled3BandWebAudio,
KeyboardPlayHandler,
PlayButton
},
setup () {
const masterGain = ref(useAudioStore().getMasterGainNoise())
const { t } = useI18n()
const frequencies = ref([150, 1500, 8000])
const qFactors = ref([0.8, 0.9, 0.6])
const loadedBands = ref(0)
const muted = computed(() => useAudioStore().getMasterGainNoise().gain.value === 0)
let oldVolume = 0
const route = useRoute()
const isExperimentsRoute = computed(() => route.path.match(/\/[a-z]{2}\/experiments/))
const masterAttack = ref(120000 * 2) // Beispielwert in Samples
const masterRelease = ref(144000 * 2)
const loading = computed(() => loadedBands.value < frequencies.value.length)
const onBandReady = () => {
loadedBands.value++
}
const toggleMute = () => {
if (!muted.value) {
oldVolume = masterGain.value.gain.value
masterGain.value.gain.linearRampToValueAtTime(0, masterGain.value.context.currentTime + 0.4)
} else if (oldVolume > 0) {
masterGain.value.gain.linearRampToValueAtTime(oldVolume, masterGain.value.context.currentTime + 0.4)
} else {
masterGain.value.gain.linearRampToValueAtTime(1, masterGain.value.context.currentTime + 0.4)
}
}
const controlMusicGain = (value: string) => {
}
const changeVolumeOnWheel = (event:WheelEvent) => {
let gainValue = masterGain.value.gain.value
// Adjust volume on wheel scroll
const deltaY = event.deltaY
if (deltaY < 0) {
const volumeAdd = (Math.min(1, gainValue + 0.02))
gainValue = volumeAdd
} else {
const volumeCut = (Math.max(0, gainValue - 0.02))
gainValue = volumeCut
}
}
return {
frequencies,
loading,
onBandReady,
t,
loadedBands,
masterAttack,
masterRelease,
isExperimentsRoute,
qFactors,
controlMusicGain,
masterGain,
changeVolumeOnWheel,
toggleMute,
muted
}
}
})
</script>

View File

@@ -0,0 +1,50 @@
<template>
<div id="spotify">
<spotify-embed :spotify-uri="currentSpotifyUri" />
<pomodoro-playlist />
<state-bar />
<button @click="changeTrack">
Change Track
</button>
</div>
</template>
<script>
import SpotifyEmbed from '@/components/experiments/spotify/SpotifyEmbed.vue'
import PomodoroPlaylist from '@/components/experiments/spotify/PomodoroPlaylist.vue'
import StateBar from '@/components/experiments/statemanagement/StateBar.vue'
export default {
components: {
SpotifyEmbed, PomodoroPlaylist, StateBar
},
data () {
return {
audioList: [
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a', spotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2' },
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a', spotifyUri: 'track/0MEMvf26SLFCXhnPj1qXJ1' },
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a', spotifyUri: 'track/48NC8HTZo0N3Kdpicx9wVp' },
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a', spotifyUri: 'track/12QLlNzfZTmSO6OMrjujKt' }
],
currentSpotifyUri: 'track/1hR0fIFK2qRG3f3RF70pb7' // Default URI
}
},
methods: {
changeTrack () {
const newSong = this.getSong(this.currentSpotifyUri, 'next')
this.currentSpotifyUri = newSong.spotifyUri // Change this to the new URI
},
getSong (currentTitle, direction) {
const index = this.audioList.findIndex(song => song.spotifyUri === currentTitle)
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
// Loop back to the first song if 'next' goes beyond the last index
if (adjacentIndex >= this.audioList.length) {
adjacentIndex = 0
} else if (adjacentIndex < 0) {
adjacentIndex = this.audioList.length - 1
}
return this.audioList[adjacentIndex]
}
}
}
</script>

View File

@@ -0,0 +1,14 @@
<template>
<div>
<h3>
component: TotalGainController darin enthalten, AudioTagWebAudio & RNBOControlValue, AudioFileSelector <br>
features: Anpassbare Ramptime <br>
rnbo: ja
</h3>
<GainController audio-src="" />
</div>
</template>
<script setup>
import GainController from '~/components/experiments/TotalGainController.vue'
</script>

View File

@@ -0,0 +1,7 @@
<template>
<RNBOControlValue />
</template>
<script setup>
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
</script>

View File

@@ -0,0 +1,106 @@
<template>
<div>
<h1>Test Fall 3</h1>
<h3>Dynamic Patches in parallel</h3>
<div>
<label for="patchCount">Number of Patches:</label>
<input
id="patchCount"
v-model.number="patchCount"
type="number"
min="1"
max="20"
:disabled="isStarted"
>
</div>
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
<table v-if="isStarted">
<thead>
<tr>
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
</tr>
</thead>
<tbody>
<tr>
<td v-for="(value, index) in controlValues" :key="index">{{ value?.value.toFixed(2) }}</td>
</tr>
</tbody>
</table>
<RNBOControlValue
v-for="index in patchCount"
:key="index"
:ref="el => { if (el) rnboRefs[index - 1] = el }"
:center-frequency="centerFrequencies.at(index - 1)"
@control-value-change="updateValueTable(index - 1, $event)"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
const patchCount = ref(9)
const controlValues = ref([])
const rnboRefs = ref([])
const isStarted = ref(false)
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
const updateValueTable = (index, value) => {
controlValues.value[index] = value
}
const startAllPatches = () => {
isStarted.value = true
rnboRefs.value.forEach((rnbo) => {
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
rnbo.testControlValuesDevice()
}
})
}
const formatValue = (value) => {
return Number(value).toFixed(2)
}
watch(patchCount, (newCount) => {
controlValues.value = new Array(newCount).fill(0)
rnboRefs.value = new Array(newCount).fill(null)
})
onMounted(() => {
controlValues.value = new Array(patchCount.value).fill(0)
rnboRefs.value = new Array(patchCount.value).fill(null)
})
</script>
<style scoped>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
th {
background-color: #f2f2f2;
}
button, input {
margin: 10px;
padding: 10px;
font-size: 16px;
}
button {
cursor: pointer;
}
input:disabled, button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,155 @@
<template>
<div>
<h1>Dynamic Patches in parallel mit knistern</h1>
<p>In diesem Test funktioniert die Messung der Störwerte sowie eine Umwandlung nach StevensLoudness. Verwendet wird hier eine AudioBufferSourceNode pro Band.</p>
<div>
<label for="patchCount">Number of Patches:</label>
<input
id="patchCount"
v-model.number="patchCount"
type="number"
min="1"
max="20"
:disabled="isStarted"
>
</div>
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
<table v-if="isStarted">
<thead>
<tr>
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
</tr>
</thead>
<tbody>
<tr>
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(value.value) }} </td>
</tr>
<tr>
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(dBToStevensLoudness(value?.value)) }} </td>
</tr>
<!--
<tr>
<td v-for="(value, index) in controlValues" :key="index"> <AudioTagWebAudio :src="audioSources[index]" :volume="dBToStevensLoudness(value?.value)" /> </td>
</tr>
-->
</tbody>
</table>
<AudioTagWebAudio
v-for="index in patchCount"
:key="index"
:ref="el => { if (el) audioRefs[index - 1] = el }"
:src="audioSources.at(index-1)"
:volume="dBToStevensLoudness(controlValues[index])"
/>
<RNBOControlValue
v-for="index in patchCount"
:key="index"
:ref="el => { if (el) rnboRefs[index - 1] = el }"
:center-frequency="centerFrequencies.at(index-1)"
@control-value-change="updateValueTable(index - 1, $event)"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
import AudioTagWebAudio from '~/components/experiments/AudioTagWebAudio.vue'
import { calculateNormalizedVolume } from '~/lib/AudioFunctions'
const patchCount = ref(9)
const controlValues = ref([])
const rnboRefs = ref([])
const audioRefs = ref([])
const isStarted = ref(false)
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
const audioSources = ref(
[
'https://192.168.188.182:3000/masking/bands/Quellrauschen63.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen125.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen250.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen500.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen1000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen2000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen4000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen8000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen16000.wav'
])
const updateValueTable = (index, value) => {
controlValues.value[index] = value
}
const startAllPatches = () => {
isStarted.value = true
rnboRefs.value.forEach((rnbo) => {
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
rnbo.testControlValuesDevice()
}
})
audioRefs.value.forEach((audio) => {
useNuxtApp().$logger.log('AudioRef = ', { audio })
})
}
const formatValue = (value) => {
return Number(value).toFixed(2)
}
const mapLinearRange = (
value,
inMin,
inMax,
outMin,
outMax
) => {
const clamped = Math.max(inMin, Math.min(value, inMax)) // optional: clamp
return ((clamped - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin
}
const dBToStevensLoudness = (db) => {
return calculateNormalizedVolume(db)
}
watch(patchCount, (newCount) => {
controlValues.value = new Array(newCount).fill(0)
rnboRefs.value = new Array(newCount).fill(null)
})
onMounted(() => {
controlValues.value = new Array(patchCount.value).fill(0)
rnboRefs.value = new Array(patchCount.value).fill(null)
})
</script>
<style scoped>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
th {
background-color: #f2f2f2;
}
button, input {
margin: 10px;
padding: 10px;
font-size: 16px;
}
button {
cursor: pointer;
}
input:disabled, button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

View File

@@ -0,0 +1,143 @@
<template>
<div>
<h1>Dynamic Patches in parallel</h1>
<p>In diesem Test funktioniert die Messung der Störwerte sowie eine Umwandlung nach StevensLoudness, nach dem Starten der Audio-Nodes (fetch) wird der AudioContext gestoppt. Eventuell von Howler </p>
<div>
<label for="patchCount">Number of Patches:</label>
<input
id="patchCount"
v-model.number="patchCount"
type="number"
min="1"
max="20"
:disabled="isStarted"
>
</div>
<button :disabled="isStarted" @click="startAllPatches">Start All Patches</button>
<table v-if="isStarted">
<thead>
<tr>
<th v-for="index in patchCount" :key="index">Patch {{ index }}</th>
</tr>
</thead>
<tbody>
<tr>
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(value.value) }} </td>
</tr>
<tr>
<td v-for="(value, index) in controlValues" :key="index">{{ formatValue(dBToStevensLoudness(value?.value)) }} </td>
</tr>
<tr>
<td v-for="(value, index) in controlValues" :key="index"> <AudioTagWebAudioStreaming :src="audioSources[index]" :volume="dBToStevensLoudness(value?.value)" /> </td>
</tr>
</tbody>
</table>
<RNBOControlValue
v-for="index in patchCount"
:key="index"
:ref="el => { if (el) rnboRefs[index - 1] = el }"
:center-frequency="centerFrequencies.at(index-1)"
@control-value-change="updateValueTable(index - 1, $event)"
/>
</div>
</template>
<script setup>
import { ref, onMounted, watch } from 'vue'
import RNBOControlValue from '@/components/experiments/tests/ControlValues/RNBOControlValue.vue'
import AudioTagWebAudioStreaming from '~/components/experiments/AudioTagWebAudioStreaming.vue'
const patchCount = ref(9)
const controlValues = ref([])
const rnboRefs = ref([])
const isStarted = ref(false)
const centerFrequencies = ref([63, 125, 250, 500, 1000, 2000, 4000, 8000, 16000])
const audioSources = ref(
[
'https://192.168.188.182:3000/masking/bands/Quellrauschen63.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen125.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen250.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen500.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen1000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen2000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen4000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen8000.wav',
'https://192.168.188.182:3000/masking/bands/Quellrauschen16000.wav'
])
const updateValueTable = (index, value) => {
controlValues.value[index] = value
}
const startAllPatches = () => {
isStarted.value = true
rnboRefs.value.forEach((rnbo) => {
if (rnbo && typeof rnbo.testControlValuesDevice === 'function') {
rnbo.testControlValuesDevice()
}
})
}
const formatValue = (value) => {
return Number(value).toFixed(2)
}
const mapLinearRange = (
value,
inMin,
inMax,
outMin,
outMax
) => {
const clamped = Math.max(inMin, Math.min(value, inMax)) // optional: clamp
return ((clamped - inMin) / (inMax - inMin)) * (outMax - outMin) + outMin
}
const dBToStevensLoudness = (db, exponent = 0.3, minDb = -60) => {
const clampedDb = Math.max(db, minDb)
const intensity = Math.pow(10, clampedDb / 10)
const loudness = Math.pow(intensity, exponent)
return loudness
}
watch(patchCount, (newCount) => {
controlValues.value = new Array(newCount).fill(0)
rnboRefs.value = new Array(newCount).fill(null)
})
onMounted(() => {
controlValues.value = new Array(patchCount.value).fill(0)
rnboRefs.value = new Array(patchCount.value).fill(null)
})
</script>
<style scoped>
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
th, td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}
th {
background-color: #f2f2f2;
}
button, input {
margin: 10px;
padding: 10px;
font-size: 16px;
}
button {
cursor: pointer;
}
input:disabled, button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
</style>

379
pages/fixproblem.vue Normal file
View File

@@ -0,0 +1,379 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="/video/bg-video.mp4"
style=" height: 100vh;"
poster="/images/poster.png"
>
<div class="container-fluid pt-3 px-lg-4">
<div class="header d-flex flex-column flex-fill flex-md-row justify-content-between">
<div class="header__logo pt-md-1 pt-lg-1">
<nuxt-link class="navbar-brand" to="/">
<div class="text-center text-md-start">
<img src="/mindboostlogo.svg" height="35" class="img " alt="imae">
</div>
</nuxt-link>
</div>
</div>
<div class="content">
<div class="row text-center">
<div class="col-12">
<h1 v-if="true" class="h3 fw-bold mb-4">
{{ t("The microphone does not provide any level") }}
</h1>
<h1 v-else class="h3 fw-bold mb-4">
{{ t("Nothing to worry, the microphone is working.") }}
</h1>
<p class="text-muted">
{{ t("Please check the input level of the microphone in the audio") }}
</p>
</div>
<div class="row justify-content-center pt-4">
<div class="col-11">
<form>
<div class="row justify-content-center mb-4">
<div class="col-md-5 text-center">
<label for="inputSelect" class="fw-semibold">{{ t('Input device:') }}</label>
<p class="pt-0 mt-0 text-muted pb-2 mb-0" style="font-size: 14px;font-weight: 500">
({{ t('select laptop or mobile device microphone') }})
</p>
<select id="inputSelect" v-model="selectedInput" class="form-select pt-1 mt-0 select-box" @change="handleInputChange">
<option v-for="device in audioInputDevices" :key="device.value" :value="device.value">
{{ device.label }}
</option>
</select>
</div>
</div>
</form>
</div>
<div class=" pt-3 text-center">
<div class="progress-container">
<div class="checkmark col-md-6 pt-2 d-block d-sm-inline-block d-inline-block">
<p class="fw-semibold">{{ t("Microphone Indicator") }}</p>
<div class="col-12 text-center d-flex justify-content-center col-lg-12 pb-5">
<AudioReactiveBar ref="AudioReactiveBar" />
</div>
<div class="col-12 text-center pt-1">
<a href="/" class="btn text-white px-4 fs-5 fw-bolder py-2" type="button" style="background-color: rgb(233, 192, 70);">{{ t('Next') }}</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</video-background>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapState } from 'pinia'
import { useCounterStore } from '@/stores/counter'
import { useMicStore } from '@/stores/microphone'
import { useUserStore } from '@/stores/user'
export default {
name: 'FixProblem',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const AudioReactiveBar = ref(null)
const micStore = useMicStore()
return {
micStore,
t,
localePath,
AudioReactiveBar
}
},
data () {
return {
bar_val: 100,
audioOutputDevices: [],
selectedInput: null,
selectedOutput: null,
stream: null,
micReady: false,
detectedInput: false
}
},
computed: {
...mapState(useCounterStore, ['count']),
// ...mapState(useMicStore, ['microphones'])
audioInputDevices () {
return this.micStore.availableDevices.map(device => ({
label: device.label || `Microphone ${device.deviceId}`,
value: device.deviceId
}))
}
},
created () {
this.$watch(
() => this.audioInputDevices,
(newDevices) => {
if (newDevices.length > 0 && this.selectedInput === null) {
this.selectedInput = newDevices[0].value
}
},
{ immediate: true }
)
},
fillInputList () {
try {
this.getUserMedia()
.then(() => {
return navigator.mediaDevices.enumerateDevices()
})
.then((devices) => {
// useNuxtApp().$logger.log('DEVICES', { devices })
// Filtere die Geräte nach Typ
this.audioInputDevices = devices.filter(device => device.kind === 'audioinput')
this.audioOutputDevices = devices.filter(device => device.kind === 'audiooutput')
// Setze die Standardwerte, falls keine Geräte verfügbar sind
this.selectedDevice = this.audioInputDevices.length > 0 ? this.audioInputDevices[0].deviceId : null
// Überprüfe, ob `audioInputDevice` und `audioOutputDevice` existieren
if (this.audioInputDevice && this.audioInputDevice.deviceId) {
this.selectedInput = this.audioInputDevices.findIndex(item => item.deviceId === this.audioInputDevice.deviceId)
} else {
this.selectedInput = -1 // Kein passendes Gerät gefunden
}
if (this.audioOutputDevice && this.audioOutputDevice.deviceId) {
this.selectedOutput = this.audioOutputDevices.findIndex(item => item.deviceId === this.audioOutputDevice.deviceId)
} else {
this.selectedOutput = -1 // Kein passendes Gerät gefunden
}
// Standardauswahl, falls nichts gefunden wird
if (this.selectedInput < 0) { this.selectedInput = 0 }
if (this.selectedOutput < 0) { this.selectedOutput = 0 }
// Warnung anzeigen, wenn Geräte gleich sind
if (this.checkIfSameDevice()) {
this.$toast.warning(this.t('bluetoothWarning'), {
duration: 6000,
pauseOnHover: true,
dismissible: true,
queue: true,
position: 'bottom-left'
})
}
})
.catch((error) => {
// Fehler bei der Geräteabfrage behandeln
this.$toast.error(`Error enumerating media devices: ${error.message}`)
useNuxtApp().$logger.error('Error during device enumeration:', error)
})
} catch (error) {
// Allgemeine Fehlerbehandlung
this.$toast.error('Unexpected error occurred.')
useNuxtApp().$logger.error('Unexpected error:', error)
}
},
async mounted () {
this.increment(75)
await this.micStore.updateAvailableDevices()
},
beforeUnmount () {
this.detachMicrophone()
},
methods: {
...mapActions(useCounterStore, ['increment']),
...mapActions(useMicStore, ['getMicrophone', 'getMediaStream', 'switchMicrophone', 'detachMicrophone']),
...mapActions(useUserStore, ['saveInputdevice', 'saveOutputDevice']),
checkMicrophoneReadiness (mic) {
if (mic.microphoneNode instanceof MediaStreamAudioSourceNode &&
mic.microphoneStream instanceof MediaStream) { this.micReady = true }
this.stream = mic.microphoneStream
useNuxtApp().$logger.log(this.micReady)
},
normalizeDeviceName (deviceName) {
return deviceName
.replace('Standard - ', '') // Remove 'Standard - '
.replace(/ \(.*\)$/, '') // Remove any content in parentheses
.trim()
},
async check (event) {
useNuxtApp().$logger.log({ event })
const inputDeviceId = this.audioInputDevices[(event.target).value].deviceId
useNuxtApp().$logger.log('INPUT DEVICE', { inputDeviceId })
await this.switchMicrophone(inputDeviceId)
this.checkIfSameDevice()
},
checkInputLevel () {
const audioreactivebar = AudioReactiveBar.value
useNuxtApp().$logger.log({ audioreactivebar })
this.detectedInput = true
},
getDeviceLabel (outputDevice) {
if (!outputDevice || !outputDevice.label) {
return 'Unknown Device' // Fallback-Label
}
return outputDevice.label
},
checkIfSameDevice () {
const outputDevice = this.audioOutputDevices[this.selectedOutput] || this.audioOutputDevices[this.index]
const inputDevice = this.audioInputDevices[this.selectedInput]
const inputLabel = this.getDeviceLabel(inputDevice)
const outputLabel = this.getDeviceLabel(outputDevice)
if (!inputLabel || !outputLabel) {
throw new Error('Device labels are not selected or not available')
}
const normalizedInput = this.normalizeDeviceName(inputLabel)
const normalizedOutput = this.normalizeDeviceName(outputLabel)
// useNuxtApp().$logger.log(`Normalized Input: ${normalizedInput}, Normalized Output: ${normalizedOutput}`)
return normalizedInput === normalizedOutput
},
saveDevices () {
this.saveInputdevice(this.audioInputDevices[this.selectedInput])
this.saveOutputDevice(this.audioOutputDevices[this.selectedOutput])
this.$router.push(this.localePath('/onboarding'))
},
getUserMedia () {
const constraints = {
audio: { deviceId: this.selectedDevice ? { exact: this.selectedDevice } : undefined }
}
try {
return navigator.mediaDevices.getUserMedia(constraints)
} catch (error) {
this.$toast.error('Error accessing media devices: ', error)
}
},
async handleInputChange () {
await this.switchMicrophone(this.selectedInput)
this.checkIfSameDevice()
if (this.$refs.AudioReactiveBar) {
this.$refs.AudioReactiveBar.updateMicrophone(this.selectedInput)
}
}
}
}
</script>
<style>
.progress-container {
display: flex;
flex-direction: column; /* Elemente werden vertikal angeordnet */
align-items: center; /* Zentriert die Progressbar und den Button horizontal */
}
.checklabel:hover{
border-color: #e9c046;
}
.checklabel {
color: #e9c046 !important;
border: 1px solid #e9c046;
}
.checkmark .checklabel svg path{
/*background-color: white;*/
fill: #e9c046;
}
.checkmark input:checked ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
.checkmark input:checked ~ .checklabel svg path {
fill: white;
}
.checkmark input:hover ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
.checkmark input:hover ~ .checklabel svg path {
fill: white;
}
.tooltip {
position: relative;
display: inline-block;
cursor: pointer;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 200px;
background-color: #333;
color: #fff;
text-align: center;
border-radius: 4px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%; /* Position above the text */
left: 50%;
margin-left: -100px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
.bar{
background-color: #e9c046 !important;
}
.checklabel{
background-color: white !important;
width: 150px ;
height: 134px ;
}
.px-4{
transition: 1s;
}
.content {
background-color: rgba(255, 255, 255, 0.6);
border-radius: 10px;
padding: 4.5em;
max-width: 1120px;
margin: 10% auto;
min-height: 50vh;
max-height: 75vh;
overflow-y: scroll;
}
@media only screen and (max-width: 768px) {
.content {
padding: 4.5em 3em;
}
}
@media only screen and (max-width: 576px) {
.content {
padding: 2em 1em;
margin: 2.5em auto;
overflow-y: scroll;
max-height: 75vh;
}
}
.nav-buttons {
position: fixed;
bottom: 0px;
left: 0;
right: 0;
background: rgb(255,255,255);
background: linear-gradient(0deg, rgba(255,255,255,1) 20%, rgba(255,255,255,0) 100%);
}
.checkmark {
background-color: transparent;
border: none;
}
</style>

194
pages/free/index.vue Normal file
View File

@@ -0,0 +1,194 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="/video/Tropics.mp4"
preload="auto"
poster="/images/posters/poster.png"
style=" height: 100vh;"
>
<home-bar :title="title" />
<div class="overlay tooltip">
<span class="tooltiptext">Upgrade to Pro to access this feature</span>
</div>
<div class="container-fluid">
<div class="row justify-content-center">
<div class="adaptive pb-3">
<spotify-embed :spotify-uri="currentSpotifyUri" class="spotifyplayer" />
<button class="btn btn-dark" style="margin:auto; margin-bottom:2em" @click="changeTrack">
Next
</button>
<div class="col-12 ">
<div class="d-none d-md-block mx-auto pb-1" style="width: 300px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
<div class="d-flex justify-content-center mb-1">
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('Off') }}</span>
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
</div>
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
</div>
<BootomBar />
</div>
</div>
</div>
</video-background>
</div>
</div>
</div>
</div>
</template>
<script>
import HomeBar from '../components/homebar'
import SpotifyEmbed from '@/components/experiments/spotify/SpotifyEmbed.vue'
export default {
components: { HomeBar, SpotifyEmbed },
setup () {
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
audioList: [
{ id: 1, title: 'Lagoon', src: window.location.origin + '/sounds/lagoon.m4a', spotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2' },
{ id: 2, title: 'Forest', src: window.location.origin + '/sounds/forest.m4a', spotifyUri: 'track/0MEMvf26SLFCXhnPj1qXJ1' },
{ id: 3, title: 'Meadow', src: window.location.origin + '/sounds/meadow.m4a', spotifyUri: 'track/48NC8HTZo0N3Kdpicx9wVp' },
{ id: 4, title: 'Tropics', src: window.location.origin + '/sounds/tropics.m4a', spotifyUri: 'track/12QLlNzfZTmSO6OMrjujKt' }
],
currentSpotifyUri: 'track/4O2wnyNQ2NLmd5BHkLTgu2', // Default URI
bar_width: 230,
analyser: null,
dataArray: null,
bar_val: 25
}
},
computed: {
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
title () {
const title = this.audioList.filter(element =>
element.spotifyUri === this.currentSpotifyUri)[0].title
return this.$t(title)
}
},
methods: {
changeTrack () {
const newSong = this.getSong(this.currentSpotifyUri, 'next')
this.currentSpotifyUri = newSong.spotifyUri // Change this to the new URI
},
getSong (currentTitle, direction) {
const index = this.audioList.findIndex(song => song.spotifyUri === currentTitle)
let adjacentIndex = index + (direction === 'next' ? 1 : -1)
// Loop back to the first song if 'next' goes beyond the last index
if (adjacentIndex >= this.audioList.length) {
adjacentIndex = 0
} else if (adjacentIndex < 0) {
adjacentIndex = this.audioList.length - 1
}
return this.audioList[adjacentIndex]
}
}
}
</script>
<style>
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.spotifyplayer{
display: flex;
flex-direction: column; /* Ensures vertical centering if needed */
align-items: center; /* Horizontally centers content in a flex-direction: column */
justify-content: center;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(128, 128, 128, 0.5); /* Grey color with opacity */
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
pointer-events: none; /* Allows interaction with elements under the overlay */
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>

56
pages/getstarted.vue Normal file
View File

@@ -0,0 +1,56 @@
<template>
<div>
<video-background
src="video/bg-video.mp4"
style=" height: 100vh;"
>
<top-logo-bar />
<div class="container-fluid">
<div class="row justify-content-center">
<div class="col-6 pt-2">
<h3 class="text-center fs-2">
Hey, Were About to Get Started
</h3>
<p class="text-center text-muted fs-5 col-8 mx-auto">
Please answer a few questions while we analyze the acoustics in your room.
</p>
<div class="text-center">
<img src="~/assets/image/Delivery.png">
</div>
<form>
<div class="row pt-4 justify-content-center">
<div class="col-7 ">
<div class="form-check">
<input id="flexCheckIndeterminate" class="form-check-input" type="checkbox" value="">
<label class="form-check-label" for="flexCheckIndeterminate">
I give the app permission to use my microphone
{{ t('I give the app permission to use my microphone') }}
</label>
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12 text-center pt-3">
<button type="button" class="login-btn col-4">
{{ t('Next') }}
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</video-background>
</div>
</template>
<script>
// can be used both composition api and roller api
export default {
components: { },
methods: {
handleAnimation: function (anim) {
this.anim = anim
}
}
}
</script>

286
pages/homeforest.vue Normal file
View File

@@ -0,0 +1,286 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
poster="/images/posters/poster2.png"
src="/video/Forest.mp4"
style=" height: 100vh;"
preload="auto"
>
<home-bar :title="title" />
<div class="container-fluid">
<div class="row justify-content-center">
<div class="adaptive pb-3">
<div class="col-12 ">
<NoiseMusicGainForest />
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
<div class="d-flex justify-content-center mb-1">
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
{{ t('Adaptive soundscape') }} : <span class="" style="color: #e9c046">{{ t('On') }}</span>
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex mt-1 fa-chevron-right" /></span>
</div>
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
</div>
<BootomBar />
</div>
</div>
</div>
</video-background>
<!-- <video muted id="myVideo" autoplay loop>-->
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
<!-- Your browser does not support HTML5 video.-->
<!-- </video>-->
</div>
</div>
</div>
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-body">
<div class=" bg-white " style="border-radius: 15px">
<div class="row px-3">
<div class="col-2">
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
</div>
<div class="col-8">
<h4 class="text-center fw-bolder">
{{ t('Adaptive soundscape') }}
</h4>
</div>
<div class="col-2">
<div class="form-check form-switch float-end">
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
</div>
<div class="row pt-4 ps-3">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="54" viewBox="0 0 60 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Noisy Environment") }}</h5>
<p>{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}</p>
</div>
</div>
</div>
</div>
<div class="row px-2 pt-4">
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
<p class="p-0 m-0">
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
</p>
<p class="p-0 m-0" />
<p class="">
{{ t("Currently, your room has a:") }}
</p>
</div>
</div>
<div class="row pt-4 ps-3">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Noisy Environment") }}</h5>
<p>{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-75">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Medium-noise Environment") }}</h5>
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-50">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Good Environment") }}</h5>
<p />
{{ t("The background noise at your workplace provides a longterm healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import HomeBar from '../components/homebar.vue'
import NoiseMusicGainForest from '@/components/experiments/homepages/NoiseMusicGainForest.vue'
import { useAudioStore } from '@/stores/audio'
import { useMicStore } from '@/stores/microphone'
export default {
components: { HomeBar, NoiseMusicGainForest },
setup () {
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
bar_width: 0,
analyser: null,
dataArray: null,
bar_val: 25
}
},
computed: {
...mapState(useAudioStore, ['audioContext', 'microphone']),
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
title () {
return this.$t('Forest')
}
},
beforeUnmount () {
// Stop each track of the microphone stream and reset the AudioContext
try {
useMicStore().detachMicrophone()
useAudioStore().pauseContext()
} catch (_error) {
// useNuxtApp().$logger.log(_error)
}
},
async mounted () {
},
methods: {
...mapActions(useAudioStore, ['getAudioContext']),
updateMeter () {
requestAnimationFrame(this.updateMeter)
this.analyser.getByteFrequencyData(this.dataArray)
const rms = this.getRMS(this.dataArray)
let level = 20 * Math.log10(rms / 128)
level = Math.max(0, Math.min(100, level + 100))
// bar.style.width = level + '%'
this.bar_width = level
},
getRMS (dataArray) {
let rms = 0
for (let i = 0; i < dataArray.length; i++) {
rms += dataArray[i] * dataArray[i]
}
rms /= dataArray.length
rms = Math.sqrt(rms)
return rms
},
handleAnimation: function (anim) {
this.anim = anim
}
}
}
</script>
<style>
.container {
display: flex;
flex-wrap: wrap;
gap: 20px; /* Spacing between items */
width: 225px;
margin-bottom: 20px;
}
.icon, .slider {
flex: 1 1 100px; /* Flex-grow, flex-shrink, flex-basis */
display: flex;
align-items: center; /* Center items vertically */
justify-content: center; /* Center items horizontally */
}
.icon {
/* Add padding around the icon for margin */
margin-right: 15px; /* Adjust this value as needed */
/* Align items if using flexbox */
display: flex;
align-items: center;
justify-content: center;
}
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.rnboplayer{
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
</style>
~/stores/audio

262
pages/homemeadow.vue Normal file
View File

@@ -0,0 +1,262 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
preload="auto"
src="/video/Meadow.mp4"
poster="/images/posters/poster2.png"
style=" height: 100vh;"
>
<home-bar :title="title" />
<div class="container-fluid">
<div class="row justify-content-center">
<div class="adaptive pb-3">
<NoiseMusicGainMeadow />
<div class="col-12 ">
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
<div class="d-flex justify-content-center mb-1">
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('On') }}</span>
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
</div>
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
</div>
<BootomBar />
</div>
</div>
</div>
</video-background>
<!-- <video muted id="myVideo" autoplay loop>-->
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
<!-- Your browser does not support HTML5 video.-->
<!-- </video>-->
</div>
</div>
</div>
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-body">
<div class=" bg-white " style="border-radius: 15px">
<div class="row px-3">
<div class="col-2">
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
</div>
<div class="col-8">
<h4 class="text-center fw-bolder">
{{ t('Adaptive soundscape') }}
</h4>
</div>
<div class="col-2">
<div class="form-check form-switch float-end">
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
</div>
</div>
</div>
<div class="row px-2 pt-4">
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
<p class="p-0 m-0">
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
</p>
<p class="p-0 m-0" />
<p class="">
{{ t("Currently, your room has a:") }}
</p>
</div>
</div>
<div class="row pt-4 ps-3">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Noisy Environment") }}</h5>
<p>
{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}
</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-75">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Medium-noise Environment") }}</h5>
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-50">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Good Environment") }}</h5>
<p>
{{ t("The background noise at your workplace provides a longterm healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import HomeBar from '../components/homebar'
import NoiseMusicGainMeadow from '@/components/experiments/homepages/NoiseMusicGainMeadow.vue'
import { useAudioStore } from '@/stores/audio'
export default {
components: { HomeBar, NoiseMusicGainMeadow },
setup () {
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
bar_width: 0,
analyser: null,
dataArray: null,
bar_val: 25
}
},
computed: {
...mapState(useAudioStore, ['audioContext', 'microphone']),
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
title () {
return this.$t('Meadow')
}
},
beforeUnmount () {
// Stop each track of the microphone stream and reset the AudioContext
try {
useMicStore().detachMicrophone()
useAudioStore().resetAudioContext()
} catch (_error) {
// useNuxtApp().$logger.log(error)
}
},
async mounted () {
/*
await useMicStore().getMicrophone().then((mic) => {
const audioCtx = mic.microphoneNode.context
this.analyser = audioCtx.createAnalyser()
mic.microphoneNode.connect(this.analyser)
this.analyser.fftSize = 2048
const bufferLength = this.analyser.frequencyBinCount
this.dataArray = new Uint8Array(bufferLength)
this.updateMeter()
}) */
},
methods: {
...mapActions(useAudioStore, ['getAudioContext']),
updateMeter () {
requestAnimationFrame(this.updateMeter)
this.analyser.getByteFrequencyData(this.dataArray)
const rms = this.getRMS(this.dataArray)
let level = 20 * Math.log10(rms / 128)
level = Math.max(0, Math.min(100, level + 100))
// bar.style.width = level + '%';
this.bar_width = level
},
getRMS (dataArray) {
let rms = 0
for (let i = 0; i < dataArray.length; i++) {
rms += dataArray[i] * dataArray[i]
}
rms /= dataArray.length
rms = Math.sqrt(rms)
return rms
},
handleAnimation: function (anim) {
this.anim = anim
}
}
}
</script>
<style>
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.rnboplayer{
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
</style>

250
pages/hometropics.vue Normal file
View File

@@ -0,0 +1,250 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="/video/Tropics.mp4"
preload="auto"
poster="/images/posters/poster2.png"
style=" height: 100vh;"
>
<home-bar :title="title" />
<div class="container-fluid">
<div class="row justify-content-center">
<div class="adaptive pb-3">
<NoiseMusicGainTropics />
<div class="col-12 ">
<div class="d-none d-md-block mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
<div class="d-flex justify-content-center mb-1">
<nuxt-link to="#adaptive-modal" data-bs-target="#adaptive-modal" data-bs-toggle="modal" class="text-muted text-decoration-none fw-bold fs-6 ">
{{ t('Adaptive soundscape') }}: <span class="" style="color: #e9c046">{{ t('On') }}</span>
</nuxt-link><span class="ps-3"><i style="padding: 5px 0px;" class="fa-solid text-muted d-flex fa-chevron-right" /></span>
</div>
<div class="d-block d-md-none mx-auto pb-1" style="width: 225px">
<div class="progress " style="height: 10px">
<div
class="progress-bar bar"
role="progressbar"
aria-label="Basic example"
:style="{width:bar_width+'%'}"
style="background-color: #e9c046;transition: 0.1s"
aria-valuenow="75"
aria-valuemin="0"
aria-valuemax="100"
/>
</div>
</div>
</div>
<BootomBar />
</div>
</div>
</div>
</video-background>
<!-- <video muted id="myVideo" autoplay loop>-->
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
<!-- Your browser does not support HTML5 video.-->
<!-- </video>-->
</div>
</div>
</div>
<div id="adaptive-modal" class="modal fade " tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content">
<div class="modal-body">
<div class=" bg-white " style="border-radius: 15px">
<div class="row px-3">
<div class="col-2">
<span><i class="fa-solid fs-3 fa-arrow-left-long" style="cursor: pointer" data-bs-dismiss="modal" /></span>
</div>
<div class="col-8">
<h4 class="text-center fw-bolder">
{{ t('Adaptive soundscape') }}
</h4>
</div>
<div class="col-2">
<div class="form-check form-switch float-end">
<input id="flexSwitchCheckDefault" class="form-check-input" type="checkbox" role="switch">
</div>
</div>
</div>
<div class="row px-2 pt-4">
<div class="col-12 ps-3 fs-5" style="line-height: 25px">
<p class="p-0 m-0">
{{ t("The Mindboost sounscape responds to the acousitcs in your room.") }}
</p>
<p class="p-0 m-0" />
<p class="">
{{ t("Currently, your room has a:") }}
</p>
</div>
</div>
<div class="row pt-4 ps-3">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 60 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM45.9379 5.1751C45.4129 5.26416 45.0098 5.69072 44.9488 6.22041C44.8926 6.7501 45.1832 7.25635 45.6754 7.4626C52.8144 11.0626 57.6754 18.4454 57.6754 27.0001C57.6754 35.5548 52.8144 42.9376 45.6754 46.5376C45.2348 46.6876 44.9254 47.0813 44.8738 47.5407C44.8223 48.0048 45.0426 48.4548 45.441 48.6985C45.8348 48.9376 46.3363 48.9282 46.7254 48.6751C54.6473 44.6767 60.0754 36.4735 60.0754 27.0001C60.0754 17.5267 54.6473 9.32353 46.7254 5.3251C46.5191 5.20791 46.2848 5.15635 46.0504 5.1751C46.0129 5.1751 45.9754 5.1751 45.9379 5.1751ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.6269 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Noisy Environment") }}</h5>
<p>
{{ t("The background noise at your workplace is disturbing. Your concentration is severely impaired. Mindboost protects you from the disturbing background noise.") }}
</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-75">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="52" height="54" viewBox="0 0 52 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM41.6629 11.5501C41.1285 11.6157 40.702 12.0329 40.627 12.5673C40.5473 13.097 40.8332 13.6173 41.3254 13.8376C46.0082 16.3642 49.2004 21.2954 49.2004 27.0001C49.2004 32.686 46.0223 37.6313 41.3629 40.1626C40.7816 40.4813 40.5707 41.2126 40.8941 41.7938C41.2129 42.3751 41.9441 42.586 42.5254 42.2626C47.9348 39.3235 51.6004 33.5813 51.6004 27.0001C51.6004 20.4001 47.9254 14.6345 42.4879 11.7001C42.2723 11.5782 42.0238 11.5267 41.7754 11.5501C41.7379 11.5501 41.7004 11.5501 41.6629 11.5501ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Medium-noise Environment") }}</h5>
<p>{{ t("The background noise at your workplace should be optimized. In the long term, it could disturb and have a negative impact on your health. Protect yourself with mindboost.") }}</p>
</div>
</div>
<div class="row pt-3 ps-3 opacity-50">
<div class="col-auto">
<span>
<svg xmlns="http://www.w3.org/2000/svg" width="44" height="54" viewBox="0 0 44 54" fill="none">
<path d="M28.9879 0.600098C28.6223 0.600098 28.2613 0.675098 27.9004 0.787598C27.1738 1.01728 26.4801 1.44853 25.8754 2.0626L11.7754 16.1626H3.60039C1.59883 16.1626 -0.0371094 17.7985 -0.0371094 19.8001V34.2001C-0.0371094 36.2017 1.59883 37.8376 3.60039 37.8376H11.7754L25.8379 51.8251C27.1785 53.1704 28.8988 53.761 30.3004 53.1001C31.7113 52.4345 32.4004 50.8313 32.4004 48.9751V4.5751C32.4004 2.7751 31.4816 1.27978 30.0754 0.787598C29.7238 0.665723 29.3535 0.600098 28.9879 0.600098ZM29.0254 3.0001C29.1473 3.0001 29.241 3.00947 29.3254 3.0376C29.6676 3.15478 30.0004 3.45947 30.0004 4.5751V48.9751C30.0004 50.2548 29.5785 50.8267 29.2879 50.9626C28.9973 51.0985 28.4113 51.1126 27.4879 50.1751L12.3754 35.0626C12.352 35.0485 12.3238 35.0345 12.3004 35.0251C12.3895 35.1048 12.0004 34.3454 12.0004 33.9001V20.1001C12.0004 19.6548 12.4035 18.886 12.3004 18.9751C12.3285 18.9517 12.352 18.9282 12.3754 18.9001L27.5629 3.7501C28.1254 3.18291 28.6551 3.00479 29.0254 3.0001ZM3.60039 18.6376H9.82539C9.6707 19.097 9.60039 19.5985 9.60039 20.1001V33.9001C9.60039 34.4017 9.67539 34.8985 9.82539 35.3626H3.60039C2.95352 35.3626 2.43789 34.847 2.43789 34.2001V19.8001C2.43789 19.1532 2.95352 18.6376 3.60039 18.6376ZM36.6379 19.0876C36.0566 19.1579 35.616 19.636 35.5879 20.222C35.5598 20.8032 35.9535 21.3235 36.5254 21.4501C38.9816 22.0923 40.8004 24.3329 40.8004 27.0001C40.8004 29.6673 38.9816 31.9079 36.5254 32.5501C35.8832 32.7142 35.4988 33.3704 35.6629 34.0126C35.827 34.6548 36.4832 35.0392 37.1254 34.8751C40.6176 33.961 43.2004 30.7642 43.2004 27.0001C43.2004 23.236 40.6176 20.0392 37.1254 19.1251C36.966 19.0782 36.802 19.0688 36.6379 19.0876Z" fill="#E9C046" />
</svg>
</span>
</div>
<div class="col-11 col-md-10 ps-3">
<h5>{{ t("Good Environment") }}</h5>
<p>
{{ t("The background noise at your workplace provides a longterm healthy basis for concentrated work. With Mindboost you make sure that even sudden disturbances do not distract you.") }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import NoiseMusicGainTropics from '../components/experiments/homepages/NoiseMusicGainTropics'
import HomeBar from '../components/homebar'
export default {
components: { HomeBar, NoiseMusicGainTropics },
setup () {
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
bar_width: 0,
analyser: null,
dataArray: null,
bar_val: 25
}
},
computed: {
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
title () {
return this.$t('Tropics')
}
},
beforeUnmount () {
},
async mounted () {
/* await useMicStore().getMicrophone().then((mic) => {
const audioCtx = mic.microphoneNode.context
this.analyser = audioCtx.createAnalyser()
mic.microphoneNode.connect(this.analyser)
this.analyser.fftSize = 2048
const bufferLength = this.analyser.frequencyBinCount
this.dataArray = new Uint8Array(bufferLength)
this.updateMeter()
}) */
},
methods: {
handleAnimation: function (anim) {
this.anim = anim
},
updateMeter () {
requestAnimationFrame(this.updateMeter)
this.analyser.getByteFrequencyData(this.dataArray)
const rms = this.getRMS(this.dataArray)
let level = 20 * Math.log10(rms / 128)
level = Math.max(0, Math.min(100, level + 100))
// bar.style.width = level + '%';
this.bar_width = level
},
getRMS (dataArray) {
let rms = 0
for (let i = 0; i < dataArray.length; i++) {
rms += dataArray[i] * dataArray[i]
}
rms /= dataArray.length
rms = Math.sqrt(rms)
return rms
}
}
}
</script>
<style>
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.rnboplayer{
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
</style>

283
pages/index.vue Normal file
View File

@@ -0,0 +1,283 @@
<template>
<div id="step-6">
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<div class="video-wrapper">
<!-- Poster -->
<img
v-if="!videoLoaded"
:src="posterPath"
class="background-poster"
alt="Poster"
>
<!-- Video -->
<transition name="fade">
<video
v-if="showVideo && videoPath"
:key="videoPath"
class="background-video"
:src="videoPath"
autoplay
muted
loop
playsinline
@loadedmetadata="handleFadeIn"
/>
</transition>
<div class="overlay-gradient" />
<div class="page-content">
<PageHeader
:title="currentTitle"
/>
<div v-if="showOverlay" class="overlay">
<button
class="btn btn-primary btn-lg"
style="background-color:#e9c046; border:none; color:black;"
@click="unlockAudio"
>
{{ t('Ready') }}
</button>
</div>
<div class="container-fluid">
<div class="row justify-content-center">
<NavigationBar
v-model:current-index="currentIndex"
:current-soundscape="currentTitle"
:adaptive="adaptive"
@update:soundscape="changeSoundscape"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { useAudioStore } from '@/stores/audio'
import { useMicStore } from '@/stores/microphone'
import { useUserStore } from '@/stores/user'
import { getSoundcapeList } from '~/tracks.config.ts'
import PageHeader from '@/components/PageHeader.vue'
import NavigationBar from '@/components/NavigationBar.vue'
// import VideoBackground from '@/components/VideoBackground.vue'
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const router = useRouter()
const audioStore = useAudioStore()
const userStore = useUserStore()
const { $axios } = useNuxtApp()
// States
const isReady = ref(false)
const showOverlay = ref(false)
const soundscapes = getSoundcapeList()
const defaultSoundscape = 'Lagoon'
const currentIndex = userStore.user?.settings?.soundscape
? ref(soundscapes.indexOf(userStore.user.settings.soundscape))
: ref(0)
// Soundscape aus Store oder Fallback
const adaptive = userStore.user?.settings?.adaptive_sound_scape || 'no'
// Computed
const currentTitle = computed(() => userStore.user?.settings?.soundscape || defaultSoundscape)
const videoPath = computed(() => `/video/${currentTitle.value}.mp4`)
const posterPath = computed(() => `/images/posters/${currentTitle.value}.jpg`)
const showVideo = ref(true)
const videoLoaded = ref(false)
// Watcher für AudioContext-Zustand
watch(() => audioStore.audioContext?.state, (newState) => {
showOverlay.value = newState === 'suspended'
})
// Wechsel Video bei Klangänderung
watch(currentIndex, async () => {
videoLoaded.value = false
showVideo.value = false
await nextTick()
showVideo.value = true
})
// Methoden
function changeSoundscape (newSoundscape) {
const map = {
Lagoon: 0,
Tropics: 1,
Forest: 2,
Meadow: 3
}
currentIndex.value = map[newSoundscape.soundscape] ?? 0
unmuteActive()
// Speichern der Einstellungen im Backend
$axios.post('/api/update-setting', newSoundscape).then((response) => {
if (soundscapes.includes(newSoundscape.soundscape)) { userStore.user.settings.soundscape = newSoundscape.soundscape }
if (response.status !== 200) {
// wenn der request fehlgeschlagen hat aus welchen gründen auch immer,
// z.B. keine Internetverbindung, wird ein Interval angeschmissen, dass alle 30 Sekunden probiert die settings zu aktualisieren
// bis es am Ende mal klappt
} else {
// reponse is not 200
}
}).catch((e) => {
useNuxtApp().$toast.error('save: something went wrong while saving...')
})
}
function unmuteActive () {
audioStore.setPlaying(false)
setTimeout(() => {
audioStore.setPlaying(true)
}, 50)
}
async function unlockAudio () {
await audioStore.ensureAudioContextRunning()
}
function handleFadeIn (e) {
e.target.style.opacity = 1
videoLoaded.value = true
}
async function fetchSettings () {
try {
const { data } = await useNuxtApp().$axios.post('/api/fetch-settings')
if (data.success) {
userStore.settings = data.setting
const soundscape = data.setting.soundscape || 'Lagoon'
const index = soundscapes.indexOf(soundscape)
currentIndex.value = index >= 0 ? index : 0 // jetzt erst setzen
// audioStore.changeTrack(soundscape)
}
} catch (error) {
useNuxtApp().$logger.error('Nothing fetched from the backend, please reload and try again.')
}
}
// Lifecycle
onMounted(async () => {
showOverlay.value = true
await useNuxtApp().$audioPrepared
showOverlay.value = false
await fetchSettings()
isReady.value = true
})
onBeforeUnmount(() => {
useAudioStore().stopAudioContextMonitor()
useMicStore().detachMicrophone()
})
</script>
<style>
.video-wrapper {
position: relative;
width: 100%;
height: 100vh;
overflow: hidden;
}
.background-poster,
.background-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
z-index: 0;
}
.background-video {
opacity: 0;
transition: opacity 1s ease-in-out;
}
.page-content {
position: relative;
z-index: 1;
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 1.5s ease-in-out;
}
.fade-enter-from, .fade-leave-to {
opacity: 0;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.rnboplayer{
position:sticky;
width: 225px;
height: inherit;
display:flex;
float: bottom;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 24px;
z-index: 1000;
}
.overlay button {
padding: 10px 20px;
font-size: 18px;
cursor: pointer;
}
.bg-gradient {
position: relative;
&::after {
content: '';
position: absolute;
left: 0;
top: 0;
right: 0;
background: rgb(153,166,188);
background: linear-gradient(0deg, rgba(153,166,188,0) 0%, rgba(153,166,188,0.413624824929972) 55%, rgba(153,166,188,1) 100%);
height: 40vh;
}
}
</style>

40
pages/index2.vue Normal file
View File

@@ -0,0 +1,40 @@
<template>
<div class="container-fluid">
<HomeBar />
<div v-if="soundscapeSelected === 'Forest'">
Forest
<MusicGainForest />
</div>
<div v-if="soundscapeSelected === 'Lagoon'">
Laggon
<MusicGainLagoon />
</div>
<div @click="changeSoundscape">
Change
</div>
</div>
</template>
<script lang="ts">
import HomeBar from '@/components/homebar.vue'
import MusicGainForest from '~/components/experiments/homepages/MusicGainForest.vue'
import MusicGainLagoon from '~/components/experiments/homepages/MusicGainLagoon.vue'
export default {
components: { HomeBar, MusicGainForest, MusicGainLagoon },
data () {
return {
soundscapeSelected: 'Lagoon'
}
},
methods: {
changeSoundscape () {
if (this.soundscapeSelected === 'Lagoon') {
this.soundscapeSelected = 'Forest'
} else {
this.soundscapeSelected = 'Lagoon'
}
}
}
}
</script>

170
pages/onboarding.vue Normal file
View File

@@ -0,0 +1,170 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="/video/bg-video.mp4"
style=" height: 100vh;"
poster="/images/poster.png"
>
<div class="container-fluid pt-3 px-lg-4">
<div class="header d-flex flex-column flex-fill flex-md-row justify-content-between">
<div class="header__logo pt-md-1 pt-lg-1">
<nuxt-link class="navbar-brand" to="/">
<div class="text-center text-md-start">
<img src="/mindboostlogo.svg" height="35" class="img " alt="imae">
</div>
</nuxt-link>
</div>
</div>
<div class="content-wrapper">
<component :is="currentStep" @next-step="nextStep" @previous-step="previousStep" />
</div>
<div class="nav-buttons row pt-5 mt-0 mt-sm-4">
<div class="col-12 text-center pt-5 mt-3 pb-2 mb-2">
<button
v-for="(step, index) in steps"
:key="index"
class="btn btn-bullet mx-1"
:class="{ 'is-active': currentStepIndex === index }"
@click="goToStep(index)"
/>
</div>
</div>
</div>
</video-background>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import { ref, markRaw } from 'vue'
import Onboarding1 from './onboarding/onboarding1.vue'
import Onboarding2 from './onboarding/onboarding2.vue'
import Onboarding3 from './onboarding/onboarding3.vue'
import Onboarding4 from './onboarding/onboarding4.vue'
import Onboarding5 from './onboarding/onboarding5.vue'
// import Onboarding6 from './onboarding/onboarding6.vue'
import { useMicStore } from '@/stores/microphone'
import { useAudioStore } from '@/stores/audio'
import { useCounterStore } from '@/stores/counter'
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const currentStepIndex = ref(0)
const steps = [
markRaw(Onboarding1),
markRaw(Onboarding2),
markRaw(Onboarding3),
markRaw(Onboarding4),
markRaw(Onboarding5)
]
const currentStep = computed(() => steps[currentStepIndex.value])
const nextStep = () => {
if (currentStepIndex.value < steps.length - 1) {
currentStepIndex.value++
}
}
const previousStep = () => {
if (currentStepIndex.value > 0) {
currentStepIndex.value--
}
}
const goToStep = (index) => {
currentStepIndex.value = index
}
return {
t,
localePath,
currentStep,
currentStepIndex,
steps,
nextStep,
previousStep,
goToStep
}
},
data () {
return {
bar_val: 25,
bar_width: 0,
analyser: null,
dataArray: null
}
},
computed: {
...mapState(useCounterStore, ['count']),
...mapState(useAudioStore, ['audioContext']),
isFixProblemRoute () {
return !this.$route.name.includes('fixproblem')
}
}
}
</script>
<style>
.bar{
background-color: #e9c046 !important;
}
.checklabel{
background-color: white !important;
width: 150px ;
height: 134px ;
}
.px-4{
transition: 1s;
}
.content-wrapper {
background-color: rgba(255, 255, 255, 0.6);
border-radius: 10px;
padding: 4em;
max-width: 1120px;
margin: 100px auto 1.5em;
min-height: 50vh;
overflow-y: scroll;
}
@media only screen and (max-width: 1024px) {
.content-wrapper {
padding: 4em;
margin-top: 70px;
max-height: 80vh;
}
}
@media only screen and (max-width: 768px) {
.content-wrapper {
padding: 4em;
}
}
@media only screen and (max-width: 576px) {
.content-wrapper {
padding: 2em 1em;
margin: 4.5em auto 1.5em;
}
}
.nav-buttons {
position: fixed;
bottom: 0px;
left: 0;
right: 0;
background: rgb(255,255,255);
background: linear-gradient(0deg, rgba(255,255,255,1) 20%, rgba(255,255,255,0) 100%);
}
.checkmark {
background-color: transparent;
border: none;
}
</style>

View File

@@ -0,0 +1,191 @@
<template>
<div>
<div class="row text-center">
<div class="col-12">
<h1 class="h3 fw-bold mb-4">
{{ t("How is your audio hardware connected?") }}
</h1>
<p class="text-muted mx-auto">
{{ t('Onboarding-input') }}<br>
{{ t('Onboarding-output') }}
</p>
</div>
</div>
<div class="row justify-content-center pt-4">
<div class="col-11">
<form v-if="!loading">
<div class="row justify-content-center mb-4">
<div class="col-md-5 text-center">
<label for="inputSelect" class="fw-semibold">{{ t('Input device:') }}</label>
<p class="pt-0 mt-0 text-muted pb-2 mb-0" style="font-size: 14px;font-weight: 500">
({{ t('select laptop or mobile device microphone') }})
</p>
<select id="inputSelect" v-model="selectedInput" class="form-select pt-1 mt-0 select-box" @change="checkIfSameDevice">
<option v-for="(item,index) in audioInputDevices" :key="index" :value="index">
{{ item.label || `Eingabegerät ${index + 1}` }}
</option>
</select>
</div>
</div>
<div class="row justify-content-center mb-4">
<div class="col-md-5 text-center">
<label for="outputSelect" class="fw-semibold">{{ t('Output device:') }}</label>
<p class="pt-0 mt-0 text-muted pb-0 mb-0" style="font-size: 14px;font-weight: 500">
{{ t('select headphones or headphone output') }}
</p>
<select id="outputSelect" v-model="selectedOutput" class="form-select pt-1 mt-0 select-box" @change="checkIfSameDevice">
<option v-for="(item,index) in audioOutputDevices" :key="index" :value="index">
{{ item.label || `Ausgabegerät ${index + 1}` }}
</option>
</select>
</div>
</div>
<div class="row justify-content-center pt-4">
<div class="col-md-3 text-center" style="z-index: 1000000;">
<button
type="button"
class="btn col-4 btn-primary-custom"
:disabled="audioInputDevices.length === 0 || audioOutputDevices.length === 0"
@click.prevent="saveDevices"
>
{{ t("Next") }}
</button>
</div>
</div>
</form>
<div v-else class="text-center">
<p>Lade Geräte...</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import { useUserStore } from '~/stores/user'
export default {
emits: ['next-step'],
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
loading: true,
audioInputDevices: [],
audioOutputDevices: [],
selectedInput: null,
selectedOutput: null,
stream: null
}
},
computed: {
...mapState(useUserStore, ['audioInputDevice', 'audioOutputDevice'])
},
async created () {
try {
// Zuerst Media-Zugriff einholen (sonst keine Labels)
await this.getUserMedia()
const devices = await navigator.mediaDevices.enumerateDevices()
this.audioInputDevices = devices.filter(d => d.kind === 'audioinput')
this.audioOutputDevices = devices.filter(d => d.kind === 'audiooutput')
// Setze Indexe basierend auf gespeicherten IDs
this.selectedInput = this.audioInputDevices.findIndex(d => d.deviceId === this.audioInputDevice.deviceId)
this.selectedOutput = this.audioOutputDevices.findIndex(d => d.deviceId === this.audioOutputDevice.deviceId)
// Fallback auf erstes Gerät
if (this.selectedInput < 0) { this.selectedInput = 0 }
if (this.selectedOutput < 0) { this.selectedOutput = 0 }
// Warnung, wenn gleiche Geräte gewählt sind
if (this.checkIfSameDevice()) {
this.$toast.warning(this.t('bluetoothWarning'), {
duration: 6000,
pauseOnHover: true,
dismissible: true,
queue: true,
position: 'bottom-left'
})
}
} catch (error) {
this.$toast.error(this.t('Could not access audio devices.'))
} finally {
this.loading = false
}
},
methods: {
...mapActions(useUserStore, ['saveInputdevice', 'saveOutputDevice']),
normalizeDeviceName (deviceName) {
return deviceName
.replace('Standard - ', '') // Remove 'Standard - '
.replace(/ \(.*\)$/, '') // Remove any content in parentheses
.trim()
},
checkIfSameDevice () {
const input = this.audioInputDevices[this.selectedInput]
const output = this.audioOutputDevices[this.selectedOutput]
if (!input?.label || !output?.label) { return false }
return this.normalizeDeviceName(input.label) === this.normalizeDeviceName(output.label)
},
saveDevices () {
this.saveInputdevice(this.audioInputDevices[this.selectedInput])
this.saveOutputDevice(this.audioOutputDevices[this.selectedOutput])
this.$emit('next-step')
},
async getUserMedia () {
try {
return await navigator.mediaDevices.getUserMedia({ audio: true })
} catch (error) {
this.$logger('keine Mikrofon-Berechtigung erteilt.')
}
}
}
}
</script>
<style scoped>
.bar{
background-color: #e9c046;
}
.select-box{
border: 2px solid #e9c046;
box-shadow: none;
}
.select-box:focus{
border: 2px solid #e9c046;
box-shadow: none;
}
.btn-primary-custom {
background-color: #e9c046;
border-color: #e9c046;
color: white;
font-weight: 700;
}
.btn-primary-custom:hover, .btn-primary-custom:focus{
background-color: transparent;
border-color: #e9c046;
color: #e9c046;
}
</style>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,132 @@
<template>
<div>
<div class="row text-center">
<div class="col-12">
<h1 class="h3 fw-bold mb-4">
{{ t('Do your headphones have ANC?') }}
</h1>
</div>
<div class="col-12 pt-4">
<div class="d-flex justify-content-center">
<div class="checkmark pt-2 px-1 d-inline-block" @click.prevent="saveSetting('Yes')">
<input id="danger-outlined1" type="radio" class="btn-check checkmark" name="options-outlined" autocomplete="off">
<label class="btn checklabel ms-0 ms-sm-3 pt-4" for="danger-outlined1">
<svg xmlns="http://www.w3.org/2000/svg" width="60" height="60" viewBox="0 0 60 46" fill="none">
<path d="M15.4874 0.162403C15.4358 0.171779 15.3843 0.18584 15.3374 0.199903C14.7796 0.326466 14.3858 0.828029 14.3999 1.3999V44.5999C14.3952 45.0312 14.6202 45.4343 14.9952 45.6546C15.3702 45.8702 15.8296 45.8702 16.2046 45.6546C16.5796 45.4343 16.8046 45.0312 16.7999 44.5999V1.3999C16.8139 1.05303 16.6733 0.720216 16.4249 0.48584C16.1718 0.246778 15.8296 0.12959 15.4874 0.162403ZM44.2874 0.162403C44.2358 0.171779 44.1843 0.18584 44.1374 0.199903C43.5796 0.326466 43.1858 0.828029 43.1999 1.3999V44.5999C43.1952 45.0312 43.4202 45.4343 43.7952 45.6546C44.1702 45.8702 44.6296 45.8702 45.0046 45.6546C45.3796 45.4343 45.6046 45.0312 45.5999 44.5999V1.3999C45.6139 1.05303 45.4733 0.720216 45.2249 0.48584C44.9718 0.246778 44.6296 0.12959 44.2874 0.162403ZM8.28738 10.9624C8.23581 10.9718 8.18425 10.9858 8.13738 10.9999C7.57956 11.1265 7.18581 11.628 7.19988 12.1999V33.7999C7.19519 34.2312 7.42019 34.6343 7.79519 34.8546C8.17019 35.0702 8.62956 35.0702 9.00456 34.8546C9.37956 34.6343 9.60456 34.2312 9.59988 33.7999V12.1999C9.61394 11.853 9.47331 11.5202 9.22488 11.2858C8.97175 11.0468 8.62956 10.9296 8.28738 10.9624ZM51.4874 10.9624C51.4358 10.9718 51.3843 10.9858 51.3374 10.9999C50.7796 11.1265 50.3858 11.628 50.3999 12.1999V33.7999C50.3952 34.2312 50.6202 34.6343 50.9952 34.8546C51.3702 35.0702 51.8296 35.0702 52.2046 34.8546C52.5796 34.6343 52.8046 34.2312 52.7999 33.7999V12.1999C52.8139 11.853 52.6733 11.5202 52.4249 11.2858C52.1718 11.0468 51.8296 10.9296 51.4874 10.9624ZM22.6874 12.1624C22.6358 12.1718 22.5843 12.1858 22.5374 12.1999C21.9796 12.3265 21.5858 12.828 21.5999 13.3999V22.9999V32.5999C21.5952 33.0312 21.8202 33.4343 22.1952 33.6546C22.5702 33.8702 23.0296 33.8702 23.4046 33.6546C23.7796 33.4343 24.0046 33.0312 23.9999 32.5999V13.3999C24.0139 13.053 23.8733 12.7202 23.6249 12.4858C23.3718 12.2468 23.0296 12.1296 22.6874 12.1624ZM37.0874 12.1624C37.0358 12.1718 36.9843 12.1858 36.9374 12.1999C36.3796 12.3265 35.9858 12.828 35.9999 13.3999V32.5999C35.9952 33.0312 36.2202 33.4343 36.5952 33.6546C36.9702 33.8702 37.4296 33.8702 37.8046 33.6546C38.1796 33.4343 38.4046 33.0312 38.3999 32.5999V13.3999C38.4139 13.053 38.2733 12.7202 38.0249 12.4858C37.7718 12.2468 37.4296 12.1296 37.0874 12.1624ZM1.08738 18.1624C1.03581 18.1718 0.984252 18.1858 0.937377 18.1999C0.379564 18.3265 -0.0141859 18.828 -0.000123334 19.3999V26.5999C-0.00481083 27.0312 0.220189 27.4343 0.59519 27.6546C0.97019 27.8702 1.42956 27.8702 1.80456 27.6546C2.17956 27.4343 2.40456 27.0312 2.39988 26.5999V19.3999C2.41394 19.053 2.27331 18.7202 2.02488 18.4858C1.77175 18.2468 1.42956 18.1296 1.08738 18.1624ZM29.8874 18.1624C29.8358 18.1718 29.7843 18.1858 29.7374 18.1999C29.1796 18.3265 28.7858 18.828 28.7999 19.3999V26.5999C28.7952 27.0312 29.0202 27.4343 29.3952 27.6546C29.7702 27.8702 30.2296 27.8702 30.6046 27.6546C30.9796 27.4343 31.2046 27.0312 31.1999 26.5999V19.3999C31.2139 19.053 31.0733 18.7202 30.8249 18.4858C30.5718 18.2468 30.2296 18.1296 29.8874 18.1624ZM58.6874 18.1624C58.6358 18.1718 58.5843 18.1858 58.5374 18.1999C57.9796 18.3265 57.5858 18.828 57.5999 19.3999V26.5999C57.5952 27.0312 57.8202 27.4343 58.1952 27.6546C58.5702 27.8702 59.0296 27.8702 59.4046 27.6546C59.7796 27.4343 60.0046 27.0312 59.9999 26.5999V19.3999C60.0139 19.053 59.8733 18.7202 59.6249 18.4858C59.3718 18.2468 59.0296 18.1296 58.6874 18.1624Z" fill="white" />
</svg>
<p class="text-center">{{ t("Yes") }}</p>
</label>
</div>
<div class="checkmark pt-2 px-1 d-block d-sm-inline-block d-inline-block" @click.prevent="saveSetting('No')">
<input id="success-outlined1" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
<label class="btn pt-4 checklabel" for="success-outlined1">
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_73_763)">
<path d="M15.4874 7.1624C15.4358 7.17178 15.3843 7.18584 15.3374 7.1999C14.7796 7.32647 14.3858 7.82803 14.3999 8.3999V51.5999C14.3952 52.0312 14.6202 52.4343 14.9952 52.6546C15.3702 52.8702 15.8296 52.8702 16.2046 52.6546C16.5796 52.4343 16.8046 52.0312 16.7999 51.5999V8.3999C16.8139 8.05303 16.6733 7.72022 16.4249 7.48584C16.1718 7.24678 15.8296 7.12959 15.4874 7.1624ZM44.2874 7.1624C44.2358 7.17178 44.1843 7.18584 44.1374 7.1999C43.5796 7.32647 43.1858 7.82803 43.1999 8.3999V51.5999C43.1952 52.0312 43.4202 52.4343 43.7952 52.6546C44.1702 52.8702 44.6296 52.8702 45.0046 52.6546C45.3796 52.4343 45.6046 52.0312 45.5999 51.5999V8.3999C45.6139 8.05303 45.4733 7.72022 45.2249 7.48584C44.9718 7.24678 44.6296 7.12959 44.2874 7.1624ZM8.28738 17.9624C8.23581 17.9718 8.18425 17.9858 8.13738 17.9999C7.57956 18.1265 7.18581 18.628 7.19988 19.1999V40.7999C7.19519 41.2312 7.42019 41.6343 7.79519 41.8546C8.17019 42.0702 8.62956 42.0702 9.00456 41.8546C9.37956 41.6343 9.60456 41.2312 9.59988 40.7999V19.1999C9.61394 18.853 9.47331 18.5202 9.22488 18.2858C8.97175 18.0468 8.62956 17.9296 8.28738 17.9624ZM51.4874 17.9624C51.4358 17.9718 51.3843 17.9858 51.3374 17.9999C50.7796 18.1265 50.3858 18.628 50.3999 19.1999V40.7999C50.3952 41.2312 50.6202 41.6343 50.9952 41.8546C51.3702 42.0702 51.8296 42.0702 52.2046 41.8546C52.5796 41.6343 52.8046 41.2312 52.7999 40.7999V19.1999C52.8139 18.853 52.6733 18.5202 52.4249 18.2858C52.1718 18.0468 51.8296 17.9296 51.4874 17.9624ZM22.6874 19.1624C22.6358 19.1718 22.5843 19.1858 22.5374 19.1999C21.9796 19.3265 21.5858 19.828 21.5999 20.3999V29.9999V39.5999C21.5952 40.0312 21.8202 40.4343 22.1952 40.6546C22.5702 40.8702 23.0296 40.8702 23.4046 40.6546C23.7796 40.4343 24.0046 40.0312 23.9999 39.5999V20.3999C24.0139 20.053 23.8733 19.7202 23.6249 19.4858C23.3718 19.2468 23.0296 19.1296 22.6874 19.1624ZM37.0874 19.1624C37.0358 19.1718 36.9843 19.1858 36.9374 19.1999C36.3796 19.3265 35.9858 19.828 35.9999 20.3999V39.5999C35.9952 40.0312 36.2202 40.4343 36.5952 40.6546C36.9702 40.8702 37.4296 40.8702 37.8046 40.6546C38.1796 40.4343 38.4046 40.0312 38.3999 39.5999V20.3999C38.4139 20.053 38.2733 19.7202 38.0249 19.4858C37.7718 19.2468 37.4296 19.1296 37.0874 19.1624ZM1.08738 25.1624C1.03581 25.1718 0.984252 25.1858 0.937377 25.1999C0.379564 25.3265 -0.0141859 25.828 -0.000123334 26.3999V33.5999C-0.00481083 34.0312 0.220189 34.4343 0.59519 34.6546C0.97019 34.8702 1.42956 34.8702 1.80456 34.6546C2.17956 34.4343 2.40456 34.0312 2.39988 33.5999V26.3999C2.41394 26.053 2.27331 25.7202 2.02488 25.4858C1.77175 25.2468 1.42956 25.1296 1.08738 25.1624ZM29.8874 25.1624C29.8358 25.1718 29.7843 25.1858 29.7374 25.1999C29.1796 25.3265 28.7858 25.828 28.7999 26.3999V33.5999C28.7952 34.0312 29.0202 34.4343 29.3952 34.6546C29.7702 34.8702 30.2296 34.8702 30.6046 34.6546C30.9796 34.4343 31.2046 34.0312 31.1999 33.5999V26.3999C31.2139 26.053 31.0733 25.7202 30.8249 25.4858C30.5718 25.2468 30.2296 25.1296 29.8874 25.1624ZM58.6874 25.1624C58.6358 25.1718 58.5843 25.1858 58.5374 25.1999C57.9796 25.3265 57.5858 25.828 57.5999 26.3999V33.5999C57.5952 34.0312 57.8202 34.4343 58.1952 34.6546C58.5702 34.8702 59.0296 34.8702 59.4046 34.6546C59.7796 34.4343 60.0046 34.0312 59.9999 33.5999V26.3999C60.0139 26.053 59.8733 25.7202 59.6249 25.4858C59.3718 25.2468 59.0296 25.1296 58.6874 25.1624Z" fill="white" />
<rect
x="5.47949"
y="2.36826"
width="73.529"
height="4.4"
rx="2.2"
transform="rotate(45 5.47949 2.36826)"
fill="white"
stroke="#E9C046"
stroke-width="2"
/>
</g>
<defs>
<clipPath id="clip0_73_763">
<rect width="60" height="60" fill="white" />
</clipPath>
</defs>
</svg>
<p class=" text-center">{{ t("No") }}</p>
</label>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import { useCounterStore } from '@/stores/counter'
import { useAudioStore } from '@/stores/audio'
import { ANC } from '@/stores/interfaces/ANC'
export default {
emits: ['next-step'],
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
bar_val: 25,
form: {
anc_type: 'No'
}
}
},
computed: { ...mapState(useCounterStore, ['count']) },
...mapState(useAudioStore, ['ancDevice']),
mounted () {
this.increment(50)
},
methods: {
...mapActions(useCounterStore, ['increment']),
saveSetting (value) {
this.form.anc_type = value
if (value === 'Yes') { this.ancDevice = ANC.Yes } else { this.ancDevice = ANC.No }
this.$axios.post('/api/update-setting', this.form).then(({ data }) => {
if (data.success) {
// this.$toast.success(data.message);
this.$emit('next-step')
}
}).catch(() => {
this.$emit('next-step')
this.$toast.error('something went wrong while saving...')
})
}
}
}
</script>
<style scoped>
.bar{
background-color: #e9c046 !important;
}
.checklabel:hover{
border-color: #e9c046;
}
.checklabel {
color: #e9c046 !important;
border: 1px solid #e9c046;
}
.checkmark .checklabel svg path{
/*background-color: white;*/
fill: #e9c046;
}
.checkmark input:checked ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
.checkmark input:checked ~ .checklabel svg path {
fill: white;
}
.checkmark input:hover ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
.checkmark input:hover ~ .checklabel svg path {
fill: white;
}
</style>
~/stores/audio

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,150 @@
<template>
<div>
<div class="row text-center">
<div class="col-12">
<h1 class="h3 fw-bold mb-4">
{{ t("Please choose a soundscape") }}
</h1>
<p class="text-muted mx-auto">
{{ t("You can change your selection at any time.") }}
</p>
</div>
<div class="col-12 pt-4">
<div class="d-flex justify-content-center">
<div class="checkmark px-1 pt-2 d-block d-sm-inline-block d-inline-block" @click.prevent="saveSetting('Lagoon')">
<input id="success-outlined1" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
<label class="btn pt-4 checklabel" for="success-outlined1">
<svg width="75" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M45.524 2c-6.347 0-11.391 2.593-15.426 6.39-4.034 3.797-7.11 8.764-9.814 13.642-2.703 4.878-5.047 9.685-7.452 13.157-2.406 3.47-4.707 5.458-7.474 5.458a1.219 1.219 0 0 0-.518.107 1.339 1.339 0 0 0-.441.32 1.506 1.506 0 0 0-.295.485 1.63 1.63 0 0 0 0 1.148c.068.181.169.346.295.484.126.139.276.248.44.321.165.074.342.11.519.107 3.927 0 6.981-2.844 9.597-6.619 2.616-3.775 4.958-8.629 7.61-13.412 2.651-4.784 5.6-9.479 9.265-12.928 3.664-3.449 7.992-5.687 13.694-5.687 1.97 0 5.187.55 7.72 1.977 2.533 1.427 4.33 3.462 4.33 6.941 0 4.46-6.184 6.167-8.208 2.236a1.432 1.432 0 0 0-.434-.51 1.217 1.217 0 0 0-1.22-.136 1.349 1.349 0 0 0-.517.405c-1.007 1.278-1.838 1.665-2.536 1.724-.699.06-1.396-.226-2.09-.813-.693-.586-1.331-1.464-1.765-2.328-.433-.864-.635-1.755-.635-2.064a1.629 1.629 0 0 0-.097-.575 1.509 1.509 0 0 0-.289-.49 1.34 1.34 0 0 0-.436-.328 1.226 1.226 0 0 0-1.034 0 1.339 1.339 0 0 0-.437.328c-.124.14-.222.307-.289.49a1.638 1.638 0 0 0-.096.575c0 1.177.384 2.33.975 3.51.592 1.18 1.418 2.345 2.482 3.245a6.356 6.356 0 0 0 1.925 1.13c.257 6.345 3.448 12.1 8.177 16.272 4.934 4.352 11.567 7.057 18.574 7.057a1.23 1.23 0 0 0 .519-.107 1.34 1.34 0 0 0 .44-.32c.127-.139.227-.304.295-.485a1.627 1.627 0 0 0 0-1.148 1.505 1.505 0 0 0-.295-.485 1.344 1.344 0 0 0-.44-.32 1.22 1.22 0 0 0-.519-.107c-6.382 0-12.468-2.498-16.906-6.414-4.182-3.689-6.837-8.578-7.134-13.807.837-.253 1.66-.796 2.459-1.507 3.944 4.576 12.209 1.793 12.209-5.028 0-4.695-2.721-7.863-5.795-9.595C51.384 2.565 47.906 2 45.524 2ZM37.47 22.79a1.28 1.28 0 0 0-.94.45 1.578 1.578 0 0 0-.378 1.056c0 3.468 1.48 7.008 3.415 10.132 1.935 3.124 4.345 5.82 6.554 7.455.146.112.31.19.484.231.174.04.353.042.528.005.174-.037.34-.112.488-.221.148-.11.275-.25.374-.414.098-.164.166-.349.2-.542a1.646 1.646 0 0 0-.004-.586 1.576 1.576 0 0 0-.207-.538 1.41 1.41 0 0 0-.378-.408c-1.809-1.339-4.084-3.845-5.832-6.666-1.747-2.821-2.944-5.97-2.944-8.448a1.623 1.623 0 0 0-.099-.58 1.508 1.508 0 0 0-.294-.492 1.334 1.334 0 0 0-.444-.326 1.224 1.224 0 0 0-.523-.109Zm-7.994 25.265c-.517 0-.994.338-1.213.86 0 0-2.327 5.112-6.82 5.112-4.494 0-6.82-5.112-6.82-5.112-.246-.564-.78-.902-1.339-.84l-.126.003c-.423.094-.778.401-.962.837 0 0-2.395 5.112-6.82 5.112-.48-.006-.93.27-1.177.734a1.648 1.648 0 0 0 0 1.501c.247.465.697.744 1.177.738 4.566 0 6.846-3.033 8.033-4.973C14.592 53.972 16.85 57 21.444 57c4.592 0 6.85-3.028 8.033-4.973C30.653 53.972 32.917 57 37.51 57c4.593 0 6.857-3.028 8.034-4.973C46.719 53.972 48.978 57 53.576 57c4.598 0 6.856-3.028 8.033-4.973C62.791 53.967 65.071 57 69.642 57c.481.006.93-.274 1.177-.738a1.648 1.648 0 0 0 0-1.5c-.246-.466-.696-.74-1.177-.735-4.435 0-6.82-5.112-6.82-5.112-.22-.524-.696-.86-1.213-.86-.517 0-.994.338-1.213.86 0 0-2.322 5.112-6.82 5.112-4.499 0-6.82-5.112-6.82-5.112-.22-.524-.697-.86-1.213-.86-.517 0-.994.338-1.214.86 0 0-2.321 5.112-6.82 5.112-4.498 0-6.82-5.112-6.82-5.112-.22-.524-.696-.86-1.213-.86Z" fill="currentColor" stroke="currentColor" /></svg>
<p class="text-center">{{ t("Lagoon") }}</p>
</label>
</div>
<div class="checkmark px-1 pt-2 d-inline-block" @click.prevent="saveSetting('Tropics')">
<input id="danger-outlined1" type="radio" class="btn-check checkmark" name="options-outlined" autocomplete="off">
<label class="btn checklabel ms-0 ms-sm-3 pt-4 " for="danger-outlined1">
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M29.965 1.006c-.059.011-.118.027-.172.043-.64.146-1.09.722-1.074 1.38v8.276c-.006.495.252.959.682 1.212.43.248.957.248 1.386 0 .43-.253.688-.717.683-1.212V2.429a1.373 1.373 0 0 0-.43-1.051 1.373 1.373 0 0 0-1.075-.372ZM10.363 9.11c-.515.091-.93.469-1.074.97a1.388 1.388 0 0 0 .387 1.4l5.846 5.863c.333.41.865.598 1.38.48.511-.12.914-.523 1.032-1.035a1.388 1.388 0 0 0-.478-1.385L11.61 9.541a1.379 1.379 0 0 0-1.118-.431h-.129Zm39.032 0c-.312.043-.602.2-.817.43l-5.846 5.863a1.388 1.388 0 0 0-.478 1.385c.118.512.521.916 1.032 1.034a1.38 1.38 0 0 0 1.38-.48l5.847-5.861c.43-.41.553-1.051.3-1.59a1.375 1.375 0 0 0-1.418-.781Zm-19.43 7.112c-.043.01-.086.027-.129.043a1.422 1.422 0 0 0-.258.043l-.043.043c-7.297.313-13.197 6.272-13.197 13.664 0 7.592 6.185 13.794 13.756 13.794 7.571 0 13.756-6.202 13.756-13.794 0-7.365-5.852-13.303-13.111-13.664-.048 0-.08-.043-.129-.043a1.324 1.324 0 0 0-.43-.086h-.215Zm.043 2.759h.216c6.017.07 10.875 4.983 10.875 11.034 0 6.1-4.922 11.035-11.005 11.035-6.077 0-11.004-4.935-11.004-11.035 0-6.067 4.879-10.986 10.918-11.034ZM2.196 28.636a1.392 1.392 0 0 0 .387 2.759h8.253c.495.005.957-.254 1.21-.685a1.396 1.396 0 0 0 0-1.39 1.383 1.383 0 0 0-1.21-.684H2.195Zm46.77 0a1.392 1.392 0 0 0 .387 2.759h8.252c.495.005.957-.254 1.21-.685a1.396 1.396 0 0 0 0-1.39 1.383 1.383 0 0 0-1.21-.684H48.965Zm-32.628 13.62a1.38 1.38 0 0 0-.816.432L9.676 48.55a1.388 1.388 0 0 0-.479 1.385c.119.512.522.916 1.032 1.034a1.38 1.38 0 0 0 1.381-.48l5.846-5.861a1.378 1.378 0 0 0 .312-1.525 1.378 1.378 0 0 0-1.3-.846h-.13Zm27.082 0c-.516.093-.93.47-1.075.97a1.388 1.388 0 0 0 .387 1.401l5.846 5.863c.333.41.865.598 1.381.48.51-.12.914-.523 1.032-1.035a1.388 1.388 0 0 0-.478-1.385l-5.846-5.862a1.373 1.373 0 0 0-.99-.431h-.258Zm-13.455 5.648c-.059.01-.118.026-.172.043-.64.145-1.09.722-1.074 1.379v8.276c-.006.496.252.959.682 1.212.43.248.957.248 1.386 0 .43-.253.688-.716.683-1.212v-8.276a1.373 1.373 0 0 0-.43-1.05 1.373 1.373 0 0 0-1.075-.373Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" /></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h60v60H0z" /></clipPath></defs></svg>
<p class="text-center">{{ t("Tropics") }}</p>
</label>
</div>
</div>
<div class="d-flex mx-auto justify-content-center ">
<div class="checkmark box12 pt-2 pt-sm-3 px-1 d-block d-sm-inline-block" @click.prevent="saveSetting('Forest')">
<input id="success-outlined2" type="radio" class="btn-check" name="options-outlined" autocomplete="off">
<label class="btn pt-4 checklabel" for="success-outlined2">
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path
fill="none"
d="M16.998 39.947V57m17.053-8.526V57m-8.526-34.105v.568a8.526 8.526 0 0 1-3.127 16.484s-9.11.05-11.084 0a8.526 8.526 0 0 1-2.842-16.484v-.568a8.526 8.526 0 1 1 17.053 0Z"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/><path
fill="none"
d="M31.209 48.474h23.59a2.842 2.842 0 0 0 1.989-4.832l-8.526-9.379h.852a2.843 2.843 0 0 0 1.99-4.831l-8.527-9.38h.569a2.843 2.843 0 0 0 2.273-4.831L34.051 3l-3.979 4.263"
stroke="currentColor"
stroke-width="4"
stroke-linecap="round"
stroke-linejoin="round"
/></svg>
<p class="text-center">{{ t("Forest") }}</p>
</label>
</div>
<div class="checkmark box12 pt-2 px-1 pt-sm-3 d-block d-sm-inline-block" @click.prevent="saveSetting('Meadow')">
<input id="danger-outlined" type="radio" class="btn-check " name="options-outlined" autocomplete="off">
<label class="btn pt-4 checklabel ms-0 ms-lg-3" for="danger-outlined">
<svg width="60" height="60" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12.716 1.592C13.234 1.155 14.08.975 15 1c1.574.042 2.951 1.506 3.5 2 10 9 15.035 13.525 17.402 23.261 1.598 6.573 2.014 11.137 2.5 13.573 3.316-5.574 8.12-15.3 16.098-18.49.563-.226 1.404-.61 2-.5.596.108 1.053.09 1.5.5.447.408.693.415.854.999.162.584.174.92 0 1.5l-.934 2.418c-4.382 14.59-7.018 19.582-6.518 30.073 0 .855.004 1.134-.402 1.666-.124.162-.743.834-1.598.834-.855 0-1.41-.072-1.902-.834-.413-.64-.486-1.533-.486-2.388 0-9.844 4.198-19.51 7.388-30.278-3.615 2.398-9.402 9.917-11 13-2.465 4.75-3.777 10.205-3.777 17.278 0 .855-.118 1.603-.625 2.388-.394.61-.798.834-1.653.834s-1.379-.578-1.847-1.334c-.398-.643-.432-1.033-.432-1.888 0-9.64-1.736-24.465-4.568-32.778-1.901-5.571-8.871-11.824-13.692-16.5 3.277 12.438 7.65 31.088 7.65 48.456v.822c0 .855.115 1.631-.458 2.388-.425.561-.931.826-1.777.834-.838.009-1.28-.33-1.723-.834-.657-.748-.5-1.645-.5-2.5 0-7.379-.16-13.989-1.987-19.657-1.05-3.264-8.977-12.07-11.693-14.5C9.284 31.503 13 44.923 13 55.612c0 .855.124 1.668-.5 2.388-.439.507-.83.834-1.5.834s-1.058-.33-1.5-.834c-.657-.748-.5-1.645-.5-2.5 0-12.45-2.174-20.043-6.933-34.314a3.222 3.222 0 0 1 4.253-4.008c4.495 1.794 9.116 9.046 11.693 11.878-.859-6.502-3.746-17.723-6.212-23.8a3.222 3.222 0 0 1 .915-3.664Z" fill="currentColor" /></svg>
<p class="text-center">{{ t("Meadow") }}</p>
</label>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
import { useAudioStore } from '@/stores/audio'
import { useCounterStore } from '@/stores/counter'
export default {
emits: ['next-step'],
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
bar_val: 100,
form: {
soundscape: ''
}
}
},
computed: { ...mapState(useCounterStore, ['count']) },
...mapState(useAudioStore, ['connectedSoundScape']),
mounted () {
this.increment(100)
},
methods: {
...mapActions(useCounterStore, ['increment']),
...mapActions(useCounterStore, ['decrement']),
async saveSetting (value) {
this.form.soundscape = value
this.connectedSoundScape = value
try {
const { data } = await this.$axios.post('/api/update-setting', this.form)
if (data.success) {
this.form = data.setting
// Jetzt erst weiterleiten
useRouter().push('/')
} else {
this.$toast.error('Speichern fehlgeschlagen.')
}
} catch (e) {
this.$toast.error('something went wrong while saving...')
}
}
}
}
</script>
<style scoped>
.bar{
background-color: #e9c046;
}
.checklabel:hover{
border-color: #e9c046;
}
.checklabel {
color: #e9c046 !important;
border: 1px solid #e9c046;
}
.checkmark input:checked ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
.checkmark input:checked ~ .checklabel svg path {
fill: white;
}
.checkmark input:hover ~ .checklabel {
background-color: #e9c046;
border: 1px solid #e9c046;
color: #f4f5f7 !important;
}
@media only screen and (max-width: 992px) and (min-width: 575px) {
.box12{
padding: 11px !important;
}
}
</style>

File diff suppressed because one or more lines are too long

23
pages/routes.vue Normal file
View File

@@ -0,0 +1,23 @@
<template>
<div>
<h1>Routen-Übersicht</h1>
<ul>
<li v-for="route in routes" :key="route.path">
<NuxtLink :to="route.path">{{ route.path }}</NuxtLink>
<ul v-if="route.children">
<li v-for="child in route.children" :key="child.path">
<NuxtLink :to="child.path">{{ child.path }}</NuxtLink>
</li>
</ul>
</li>
</ul>
</div>
</template>
<script setup>
import { useRouter } from 'vue-router'
import { NuxtLink } from '#components'
const router = useRouter()
const routes = router.getRoutes()
</script>

226
pages/settings.vue Normal file
View File

@@ -0,0 +1,226 @@
<template>
<div>
<div class="container-fluid p-0">
<div class="settings d-flex flex-nowrap">
<div class="settings__sidebar col-lg-3 col-md-4 px-0" style="background-color: #f4f5f7">
<div class="d-flex flex-column align-items-sm-start pt-2 text-white min-vh-100 h-100">
<nuxt-link :to="localePath('/')" class="d-flex align-items-center pb-5 text-white text-decoration-none">
<div class="d-none d-md-inline px-sm-2 px-md-3 px-lg-3 pt-3 main-logo">
<img src="/logo-gray.svg" height="35" class="img " alt="Mindboost Logo">
</div>
<div class="d-md-none px-2 px-md-3 px-lg-3 pt-3 main-logo-mobile">
<img src="/mindboost-logo-small.svg" width="45" class="img " alt="Mindboost Logo">
</div>
</nuxt-link>
<div class="sidebar sidebar--settings">
<ul class="sidebar__main-menu">
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/account')" class="sidebar__link list-active">
<i class="fa-solid d-md-none d-block fa-user" /><span class="d-none d-md-inline">{{ t('Account') }}</span>
</nuxt-link>
</li>
<li v-if="dataStore.user.team_subscription_plan == '0' && dataStore.user.enterprise_subscription_plan == '0' && (dataStore.user.basic_subscription_plan == '1')" class="sidebar__item">
<nuxt-link :to="localePath('/settings/subscription')" class="sidebar__link list-active">
<i class="fa-solid d-md-none d-block fa-credit-card" /><span class="d-none d-md-inline">{{ t('Subscription') }}</span>
</nuxt-link>
</li>
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/soundscape')" class="sidebar__link list-active">
<i class="fa-solid d-md-none d-block fa-volume-high" /><span class="d-none d-md-inline">{{ t('Settings') }}</span>
</nuxt-link>
</li>
<!-- <li class="sidebar__item">
<nuxt-link :to="localePath('/settings/timersettings')" class="sidebar__link list-active">
<i class="fa-solid fa-volume-high sidebar__icon" /><span class="sidebar__text">{{ t('Timer') }}</span>
</nuxt-link>
</li> -->
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/about')" class="sidebar__link list-active">
<i class="fa-solid fa-circle-info d-block d-md-none" /><span class="d-none d-md-inline"> {{ t('About Mindboost') }}</span>
</nuxt-link>
</li>
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/faq')" class="sidebar__link list-active">
<i class="fa-solid d-block d-md-none fa-circle-question" /><span class="d-none d-md-inline">{{ t('FAQ') }} </span>
</nuxt-link>
</li>
</ul>
<ul class="sidebar__secondary-menu">
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/imprint')" class="sidebar__link sidebar__link--secondary">
<i class="fa-solid fa-stamp d-md-none d-block" /><span class="d-none d-md-inline">{{ t('Imprint') }} </span>
</nuxt-link>
</li>
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/dataprotection')" class="sidebar__link sidebar__link--secondary">
<i class="fa-solid fa-shield d-block d-md-none" /><span class="d-none d-md-inline">{{ t('Data Protection') }}</span>
</nuxt-link>
</li>
<li class="sidebar__item">
<nuxt-link :to="localePath('/settings/termsandcondition')" class="sidebar__link sidebar__link--secondary">
<i class="fa-solid fa-scale-balanced d-block d-md-none" /><span class="d-none d-md-inline">{{ t('Terms & Conditions') }}</span>
</nuxt-link>
</li>
</ul>
</div>
</div>
</div>
<div class="settings__content col-md-8 col-lg-9 justify-content-end px-3">
<div class="d-flex justify-content-end pb-sm-5 pt-3">
<router-link :to="localePath('/')" style="cursor: pointer">
<svg width="36" height="36" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m18 20.1-7.35 7.35c-.275.275-.625.413-1.05.413-.425 0-.775-.138-1.05-.413-.275-.275-.412-.625-.412-1.05 0-.425.137-.775.412-1.05L15.9 18l-7.35-7.35c-.275-.275-.412-.625-.412-1.05 0-.425.137-.775.412-1.05.275-.275.625-.413 1.05-.413.425 0 .775.138 1.05.413L18 15.9l7.35-7.35c.275-.275.625-.413 1.05-.413.425 0 .775.138 1.05.413.275.275.413.625.413 1.05 0 .425-.138.775-.413 1.05L20.1 18l7.35 7.35c.275.275.413.625.413 1.05 0 .425-.138.775-.413 1.05-.275.275-.625.413-1.05.413-.425 0-.775-.138-1.05-.413L18 20.1Z" fill="#585C5E" /></svg>
</router-link>
</div>
<nuxt-page key="child" />
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions } from 'pinia'
import { ANC } from '@/stores/interfaces/ANC'
import { HeadsetType } from '@/stores/interfaces/HeadsetType'
import { useUserStore } from '~/stores/user'
export default {
setup () {
definePageMeta({
middleware: 'auth'
})
// Use useUserStore to get the user store
const dataStore = useUserStore()
const { t } = useI18n()
const localePath = useLocalePath()
// useNuxtApp().$logger.log('dataStore.user.email', dataStore.user.basic_subscription_plan)
return { t, localePath, dataStore }
},
data () {
return {
adaptiveSounscape: false,
form: {
soundscape: '',
headphone_type: '',
anc_type: '',
adaptive_sound_scape: '',
dataStore: useUserStore()
}
}
},
mounted () {
this.fetchSettings()
},
methods: {
...mapActions(useUserStore, ['updateHeadsetType', 'updateANC']),
fetchSettings () {
this.$axios.post('/api/fetch-settings').then(({ data }) => {
if (data.success) {
this.form = data.setting
if (data.setting.adaptive_sound_scape === 'yes' || data.setting.adaptive_sound_scape === 'Yes') {
this.adaptiveSounscape = true
} else {
this.adaptiveSounscape = false
}
}
}).catch(() => {
this.$toast.error('something went wrong while saving...')
})
},
saveSetting (value, type) {
if (type === 'soundscape') {
this.form.soundscape = value
}
if (type === 'headphone') {
this.form.headphone_type = value
if (value === 'In-ear') { this.updateHeadsetType(HeadsetType.InEar) } else { this.updateHeadsetType(HeadsetType.OverEar) }
}
if (type === 'anc') {
this.form.anc_type = value
if (value === 'yes' || value === 'Yes') { this.updateANC(ANC.Yes) } else { this.updateANC(ANC.No) }
}
if (type === 'adaptivesoundscape') {
if (this.adaptiveSounscape === true) {
this.form.adaptive_sound_scape = 'no'
} else {
this.form.adaptive_sound_scape = 'yes'
}
}
this.$axios.post('/api/update-setting', this.form).then(({ data }) => {
if (data.success) {
this.$toast.success(data.message)
this.form = data.setting
}
}).catch(() => {
this.$toast.error('something went wrong while saving...')
})
}
}
}
</script>
<style scoped>
.settings__sidebar {
height: 100vh;
position: sticky;
left: 0;
top: 0;
}
.settings__content {
flex-grow: 1;
position: relative;
}
.list-active{
width: 100% !important;
font-size: 18px;
}
.list-active.router-link-exact-active{
background-color: #e9c046;
color: white !important;
width: 100% !important;
border-radius: 0;
}
.list-active:active a{
color: white !important;
}
.list-active:hover, .list-active:focus {
border-radius: 0;
}
.main-logo img {
width: 100%;
}
@media only screen and (max-width: 1024px) {
.main-logo img {
height: auto;
width: 100%;
}
}
.fivith-icon.router-link-exact-active{
content: url('~/assets/image/Vector (4).png');
background-color: #e9c046;
border-radius: 5px;
padding: 10px 10px;
text-align: center;
}
.accordion-button-homebar::after {
background-size: 16px;
background-position-y: 4px;
}
@media only screen and (max-width: 768px) {
.navbar-logo{
width: 160px !important;
padding-bottom: 10px;
}
}
.drop-active{
color: white !important;
background-color: #e9c046;
border-radius: 10px;
margin-top: 2px;
}
</style>

207
pages/settings/_index.vue Normal file
View File

@@ -0,0 +1,207 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<!-- Header Section -->
<div class="row">
<div class="col-12">
<h1 class="h3 fw-bold text-center pt-3">
{{ t('Account') }}
</h1>
</div>
</div>
<!-- Account Details -->
<div class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('First Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.first_name }}
</p>
</div>
</div>
<div class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Last Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.last_name }}
</p>
</div>
</div>
<div class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Language') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.language == 'de' ? 'German' : 'English' }}
</p>
</div>
</div>
<div class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Email') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.email }}
</p>
</div>
</div>
<div class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Password') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted"></p>
</div>
</div>
<!-- Conditional Manager Details -->
<div v-if="user.inviter" class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Manager Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.inviter.first_name ?? '' }} {{ user.inviter.last_name ?? '' }}
</p>
</div>
</div>
<div v-if="user.inviter" class="border-bottom row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Manager Email') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ user.inviter.email ?? '' }}
</p>
</div>
</div>
<div v-if="user.inviter" class="row pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Current Package') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-end text-muted">
{{ showManagerPlan() }}
</p>
</div>
</div>
<!-- Logout Button -->
<div class="account-buttons d-flex justify-content-center pt-5">
<nuxt-link
:to="localePath('/settings/editaccount')"
class="btn btn-primary-custom text-decoration-none"
>
{{ t('Edit_') }}
</nuxt-link>
<button class="btn fw-bold btn-outline-dark" @click="logoutNow">
{{ t("Log Out") }}
</button>
</div>
<!-- Subscription Call-to-Action -->
<div class="row pt-5 pb-4 px-2 mx-0">
<div
class="col-12 rounded text-center py-3 py-md-5"
style="background-image: linear-gradient(40.53deg, #E9A446 6.68%, #E9C046 100%)"
>
<h4 class="text-center text-white">
{{ t("Get another free 30 days") }}
</h4>
<h5 class="text-center px-2 px-md-5 text-white pt-2">
{{ t("You can still use Mindboost for 30 days for free. Subscribe to increase your productivity with Mindboost afterwards.") }}
</h5>
<NuxtLink
disabled
type="button"
class="btn px-4 py-2 mt-2 mt-md-3 fs-5"
style="background-color: white; gap: 10px;"
>
{{ t("Subscribe") }}
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
// Composition API setup
const { t } = useI18n()
const localePath = useLocalePath()
const userStore = useUserStore()
const router = useRouter()
// Computed property for user state
const user = computed(() => userStore.user)
// Logout method
const logoutNow = async () => {
await userStore.logout()
try {
await $axios.post('/api/logout')
} catch (error) {
// Handle the error if needed
}
router.push(localePath('/auth/login'))
}
function showManagerPlan () {
if (user.value.inviter.current_subscription_plan === 1) {
return 'Basic'
} else if (user.value.inviter.current_subscription_plan === 2) {
return 'Team'
} else if (user.value.inviter.current_subscription_plan === 3) {
return 'Enterprise'
}
// this.$toast.success(`Manager already has the ${managerPlan} plan`) // Fixed typo
}
</script>
<style>
.account-buttons {
gap: 1.5em;
}
@media only screen and (max-width: 576px) {
.account-buttons {
flex-direction: column;
gap: 0.75em;
}
}
</style>

60
pages/settings/about.vue Normal file
View File

@@ -0,0 +1,60 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold text-center py-3">
{{ t('About Mindboost') }}
</h1>
<div class="row pt-4">
<div class="col-12">
<h2 class="h6 fw-bold">
{{ t('WHAT IS MINDBOOST?') }}
</h2>
<p class="text-muted ">
{{ t('Mindboost creates soundscapes for your headphones that help you focus up to 35% better. Mindboost combines voice masking, binaural beats and alpha waves.') }}
</p>
</div>
<div class="col-12 pt-3">
<h2 class="h6 fw-bold">
{{ t('HOW DOES MINDBOOST WORK?') }}
</h2>
<p class="text-muted ">
{{ t('Our algorithm measures the acoustics of your cell phone or other device in the room in real time and generates a soundscape that optimally blocks out disruptive background noise such as colleagues on the phone.') }}
</p>
</div>
<div class="col-12 pt-3">
<h2 class="h6 fw-bold">
{{ t('WHY DOES MINDBOOST WORK?') }}
</h2>
<p class="text-muted ">
{{ t('All soundscapes in Mindboost have been tested and optimized in listening tests in cooperation with the Fraunhofer IBP. So you can be sure that Mindboost supports you optimally with your concentration.') }}
</p>
</div>
<div class="col-12 pt-3">
<h2 class="h6 fw-bold">
{{ t('WHO WE ARE?') }}
</h2>
<p class="text-muted ">
{{ t('We are a small team of audio developers. Inspiried by the power of sound, well known from music, we cannot stop exploring the digital audio world. We are happy that we can be part of a community that uses audio next to practical services mainly to generate fun.') }}
</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SettingPage',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return { t, localePath }
}
}
</script>
<style>
</style>

271
pages/settings/account.vue Normal file
View File

@@ -0,0 +1,271 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<!-- Header Section -->
<div class="row">
<div class="col-12">
<h1 class="h3 fw-bold text-center pt-3">
{{ t('Account') }}
</h1>
</div>
</div>
<!-- Account Details -->
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('First Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.first_name }}
</p>
</div>
</div>
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Last Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.surname }}
</p>
</div>
</div>
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Language') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.language == 'de' ? 'German' : 'English' }}
</p>
</div>
</div>
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Email') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.email }}
</p>
</div>
</div>
<div class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Password') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted"></p>
</div>
</div>
<!-- Conditional Manager Details -->
<div v-if="user.inviter" class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Manager Name') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.inviter.first_name ?? '' }} {{ user.inviter.surname ?? '' }}
</p>
</div>
</div>
<div v-if="user.inviter" class="border-bottom row flex-column flex-sm-row mx-0 pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Manager Email') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ user.inviter.email ?? '' }}
</p>
</div>
</div>
<div v-if="user.inviter" class="row flex-column flex-sm-row pt-4">
<div class="col-6">
<p class="fw-bold text-start">
{{ t('Current Package') }}
</p>
</div>
<div class="col-6">
<p class="fw-bold text-sm-end text-muted">
{{ showManagerPlan() }}
</p>
</div>
</div>
<!-- Logout Button -->
<div class="account-buttons d-flex justify-content-center pt-5">
<nuxt-link
:to="localePath('/settings/editaccount')"
class="btn btn-primary-custom text-decoration-none"
>
{{ t('Edit_') }}
</nuxt-link>
<button class="btn fw-bold btn-outline-dark" @click="logoutNow">
{{ t("Log Out") }}
</button>
</div>
</div>
</div>
<div class="row">
<button type="button" class="btn btn-link position-absolute bottom-0 start-50 translate-middle-x mb-3" @click="showModal = true">
{{ t("Delete Account") }}
</button>
</div>
<div
class="modal fade"
tabindex="-1"
:class="{ show: showModal }"
:style="showModal ? 'display: block;' : 'display: none;'"
aria-modal="true"
role="dialog"
@click.self="closeModal"
>
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{{ t("Delete Account") }}</h5>
<button type="button" class="btn-close" @click="closeModal" />
</div>
<div class="modal-body">
<p>{{ t('Delete Account Text') }}</p>
</div>
<div class="modal-footer">
<button class="btn btn-dark-custom" @click="closeModal">{{ t('Cancel') }}</button>
<button class="btn btn-primary-custom" @click="deleteAccount">{{ t('Delete Account') }}</button>
</div>
</div>
</div>
</div>
<!-- Hintergrund-Overlay -->
<div v-if="showModal" class="modal-backdrop fade show" />
</div>
</template>
<script>
import { mapActions, mapState } from 'pinia'
import { useI18n } from 'vue-i18n'
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
export default {
name: 'Account',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const userStore = useUserStore()
const user = useUserStore().user
const router = useRouter()
return { t, localePath, router, user, userStore }
},
data () {
return {
showModal: false,
loading: false
}
},
methods: {
async logoutNow () {
await this.userStore.logout()
try {
await this.$axios.post('/api/logout')
} catch (error) {
// Handle the error if needed
}
this.$router.push(this.localePath('/auth/login'))
},
closeModal () {
this.showModal = false
},
async deleteAccount () {
this.loading = true
try {
await this.$axios.post('/api/account/delete')
await this.userStore.logout()
this.$router.push(this.localePath('/auth/login'))
this.$toast.success('Account gelöscht. Du wurdest ausgeloggt.')
} catch (error) {
this.$toast.error('Fehler beim Löschen: ' + (error.response?.data?.message || 'Unbekannter Fehler'))
} finally {
this.loading = false
this.closeModal()
}
},
showManagerPlan () {
if (user.value.inviter.current_subscription_plan === 1) {
return 'Basic'
} else if (user.value.inviter.current_subscription_plan === 2) {
return 'Team'
} else if (user.value.inviter.current_subscription_plan === 3) {
return 'Enterprise'
}
}
}
}
</script>
<style lang="scss" scoped>
.btn-primary-custom {
background-color: #e9c046;
border-color: #e9c046;
color: white;
font-weight: 700;
}
.btn-primary-custom:hover, .btn-primary-custom:focus{
background-color: transparent;
border-color: #e9c046;
color: #e9c046;
}
.account-buttons {
gap: 1.5em;
}
@media only screen and (max-width: 576px) {
.account-buttons {
flex-direction: column;
gap: 0.75em;
}
}
.modal {
z-index: 1050;
}
.modal-backdrop {
z-index: 1040;
}
.btn-link {
color: black;
transition: 250ms ease-in-out;
&:hover, &:focus {
color: #e9c046;
}
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold py-3">Datenschutz&shy;erkl&auml;rung</h1>
<div class="row pt-4">
<div class="col-12">
<h2 class="h4">1. Datenschutz auf einen Blick</h2>
<h3 class="h5">Allgemeine Hinweise</h3> <p>Die folgenden Hinweise geben einen einfachen &Uuml;berblick dar&uuml;ber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie pers&ouml;nlich identifiziert werden k&ouml;nnen. Ausf&uuml;hrliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgef&uuml;hrten Datenschutzerkl&auml;rung.</p>
<h3 class="h5">Datenerfassung auf dieser Website</h3>
<h4 class="h6">Wer ist verantwortlich f&uuml;r die Datenerfassung auf dieser Website?</h4> <p>Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten k&ouml;nnen Sie dem Abschnitt &bdquo;Hinweis zur Verantwortlichen Stelle&ldquo; in dieser Datenschutzerkl&auml;rung entnehmen.</p> <h4 class="h6">Wie erfassen wir Ihre Daten?</h4> <p>Ihre Daten werden zum einen dadurch erhoben, dass Sie uns diese mitteilen. Hierbei kann es sich z.&nbsp;B. um Daten handeln, die Sie in ein Kontaktformular eingeben.</p> <p>Andere Daten werden automatisch oder nach Ihrer Einwilligung beim Besuch der Website durch unsere IT-Systeme erfasst. Das sind vor allem technische Daten (z.&nbsp;B. Internetbrowser, Betriebssystem oder Uhrzeit des Seitenaufrufs). Die Erfassung dieser Daten erfolgt automatisch, sobald Sie diese Website betreten.</p> <h4 class="h6">Wof&uuml;r nutzen wir Ihre Daten?</h4> <p>Ein Teil der Daten wird erhoben, um eine fehlerfreie Bereitstellung der Website zu gew&auml;hrleisten. Andere Daten k&ouml;nnen zur Analyse Ihres Nutzerverhaltens verwendet werden.</p> <h4 class="h6">Welche Rechte haben Sie bez&uuml;glich Ihrer Daten?</h4> <p>Sie haben jederzeit das Recht, unentgeltlich Auskunft &uuml;ber Herkunft, Empf&auml;nger und Zweck Ihrer gespeicherten personenbezogenen Daten zu erhalten. Sie haben au&szlig;erdem ein Recht, die Berichtigung oder L&ouml;schung dieser Daten zu verlangen. Wenn Sie eine Einwilligung zur Datenverarbeitung erteilt haben, k&ouml;nnen Sie diese Einwilligung jederzeit f&uuml;r die Zukunft widerrufen. Au&szlig;erdem haben Sie das Recht, unter bestimmten Umst&auml;nden die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Des Weiteren steht Ihnen ein Beschwerderecht bei der zust&auml;ndigen Aufsichtsbeh&ouml;rde zu.</p> <p>Hierzu sowie zu weiteren Fragen zum Thema Datenschutz k&ouml;nnen Sie sich jederzeit an uns wenden.</p>
<h2 class="h4">2. Hosting</h2>
<p>Wir hosten die Inhalte unserer Website bei folgendem Anbieter:</p>
<h3 class="h5">Strato</h3> <p>Anbieter ist die Strato AG, Otto-Ostrowski-Stra&szlig;e 7, 10249 Berlin (nachfolgend &bdquo;Strato&ldquo;). Wenn Sie unsere Website besuchen, erfasst Strato verschiedene Logfiles inklusive Ihrer IP-Adressen.</p> <p>Weitere Informationen entnehmen Sie der Datenschutzerkl&auml;rung von Strato: <a href="https://www.strato.de/datenschutz/" target="_blank" rel="noopener noreferrer">https://www.strato.de/datenschutz/</a>.</p> <p>Die Verwendung von Strato erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO. Wir haben ein berechtigtes Interesse an einer m&ouml;glichst zuverl&auml;ssigen Darstellung unserer Website. Sofern eine entsprechende Einwilligung abgefragt wurde, erfolgt die Verarbeitung ausschlie&szlig;lich auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO und &sect; 25 Abs. 1 TTDSG, soweit die Einwilligung die Speicherung von Cookies oder den Zugriff auf Informationen im Endger&auml;t des Nutzers (z.&nbsp;B. Device-Fingerprinting) im Sinne des TTDSG umfasst. Die Einwilligung ist jederzeit widerrufbar.</p>
<h4 class="h6">Auftragsverarbeitung</h4> <p>Wir haben einen Vertrag &uuml;ber Auftragsverarbeitung (AVV) zur Nutzung des oben genannten Dienstes geschlossen. Hierbei handelt es sich um einen datenschutzrechtlich vorgeschriebenen Vertrag, der gew&auml;hrleistet, dass dieser die personenbezogenen Daten unserer Websitebesucher nur nach unseren Weisungen und unter Einhaltung der DSGVO verarbeitet.</p>
<h2 class="h4">3. Allgemeine Hinweise und Pflicht&shy;informationen</h2>
<h3 class="h5">Datenschutz</h3> <p>Die Betreiber dieser Seiten nehmen den Schutz Ihrer pers&ouml;nlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerkl&auml;rung.</p> <p>Wenn Sie diese Website benutzen, werden verschiedene personenbezogene Daten erhoben. Personenbezogene Daten sind Daten, mit denen Sie pers&ouml;nlich identifiziert werden k&ouml;nnen. Die vorliegende Datenschutzerkl&auml;rung erl&auml;utert, welche Daten wir erheben und wof&uuml;r wir sie nutzen. Sie erl&auml;utert auch, wie und zu welchem Zweck das geschieht.</p> <p>Wir weisen darauf hin, dass die Daten&uuml;bertragung im Internet (z.&nbsp;B. bei der Kommunikation per E-Mail) Sicherheitsl&uuml;cken aufweisen kann. Ein l&uuml;ckenloser Schutz der Daten vor dem Zugriff durch Dritte ist nicht m&ouml;glich.</p>
<h3 class="h5">Hinweis zur verantwortlichen Stelle</h3> <p>Die verantwortliche Stelle f&uuml;r die Datenverarbeitung auf dieser Website ist:</p> <p>
Robert-Carl Rapp<br>
Sindlingerstr. 23,<br>
71083 Herrenberg
</p>
<p>
Telefon: (+49) 15784037946<br>
E-Mail: kontakt@mindboost.team
</p>
<p>Verantwortliche Stelle ist die nat&uuml;rliche oder juristische Person, die allein oder gemeinsam mit anderen &uuml;ber die Zwecke und Mittel der Verarbeitung von personenbezogenen Daten (z.&nbsp;B. Namen, E-Mail-Adressen o. &Auml;.) entscheidet.</p>
<h3 class="h5">Speicherdauer</h3> <p>Soweit innerhalb dieser Datenschutzerkl&auml;rung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck f&uuml;r die Datenverarbeitung entf&auml;llt. Wenn Sie ein berechtigtes L&ouml;schersuchen geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen, werden Ihre Daten gel&ouml;scht, sofern wir keine anderen rechtlich zul&auml;ssigen Gr&uuml;nde f&uuml;r die Speicherung Ihrer personenbezogenen Daten haben (z.&nbsp;B. steuer- oder handelsrechtliche Aufbewahrungsfristen); im letztgenannten Fall erfolgt die L&ouml;schung nach Fortfall dieser Gr&uuml;nde.</p>
<h3 class="h5">Allgemeine Hinweise zu den Rechtsgrundlagen der Datenverarbeitung auf dieser Website</h3> <p>Sofern Sie in die Datenverarbeitung eingewilligt haben, verarbeiten wir Ihre personenbezogenen Daten auf Grundlage von Art. 6 Abs. 1 lit. a DSGVO bzw. Art. 9 Abs. 2 lit. a DSGVO, sofern besondere Datenkategorien nach Art. 9 Abs. 1 DSGVO verarbeitet werden. Im Falle einer ausdr&uuml;cklichen Einwilligung in die &Uuml;bertragung personenbezogener Daten in Drittstaaten erfolgt die Datenverarbeitung au&szlig;erdem auf Grundlage von Art. 49 Abs. 1 lit. a DSGVO. Sofern Sie in die Speicherung von Cookies oder in den Zugriff auf Informationen in Ihr Endger&auml;t (z.&nbsp;B. via Device-Fingerprinting) eingewilligt haben, erfolgt die Datenverarbeitung zus&auml;tzlich auf Grundlage von &sect; 25 Abs. 1 TTDSG. Die Einwilligung ist jederzeit widerrufbar. Sind Ihre Daten zur Vertragserf&uuml;llung oder zur Durchf&uuml;hrung vorvertraglicher Ma&szlig;nahmen erforderlich, verarbeiten wir Ihre Daten auf Grundlage des Art. 6 Abs. 1 lit. b DSGVO. Des Weiteren verarbeiten wir Ihre Daten, sofern diese zur Erf&uuml;llung einer rechtlichen Verpflichtung erforderlich sind auf Grundlage von Art. 6 Abs. 1 lit. c DSGVO. Die Datenverarbeitung kann ferner auf Grundlage unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO erfolgen. &Uuml;ber die jeweils im Einzelfall einschl&auml;gigen Rechtsgrundlagen wird in den folgenden Abs&auml;tzen dieser Datenschutzerkl&auml;rung informiert.</p>
<h3 class="h5">Empfänger von personenbezogenen Daten</h3> <p>Im Rahmen unserer Gesch&auml;ftst&auml;tigkeit arbeiten wir mit verschiedenen externen Stellen zusammen. Dabei ist teilweise auch eine &Uuml;bermittlung von personenbezogenen Daten an diese externen Stellen erforderlich. Wir geben personenbezogene Daten nur dann an externe Stellen weiter, wenn dies im Rahmen einer Vertragserf&uuml;llung erforderlich ist, wenn wir gesetzlich hierzu verpflichtet sind (z.&nbsp;B. Weitergabe von Daten an Steuerbeh&ouml;rden), wenn wir ein berechtigtes Interesse nach Art. 6 Abs. 1 lit. f DSGVO an der Weitergabe haben oder wenn eine sonstige Rechtsgrundlage die Datenweitergabe erlaubt. Beim Einsatz von Auftragsverarbeitern geben wir personenbezogene Daten unserer Kunden nur auf Grundlage eines g&uuml;ltigen Vertrags &uuml;ber Auftragsverarbeitung weiter. Im Falle einer gemeinsamen Verarbeitung wird ein Vertrag &uuml;ber gemeinsame Verarbeitung geschlossen.</p>
<h3 class="h5">Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3> <p>Viele Datenverarbeitungsvorg&auml;nge sind nur mit Ihrer ausdr&uuml;cklichen Einwilligung m&ouml;glich. Sie k&ouml;nnen eine bereits erteilte Einwilligung jederzeit widerrufen. Die Rechtm&auml;&szlig;igkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unber&uuml;hrt.</p>
<h3 class="h5">Widerspruchsrecht gegen die Datenerhebung in besonderen F&auml;llen sowie gegen Direktwerbung (Art. 21 DSGVO)</h3> <p>WENN DIE DATENVERARBEITUNG AUF GRUNDLAGE VON ART. 6 ABS. 1 LIT. E ODER F DSGVO ERFOLGT, HABEN SIE JEDERZEIT DAS RECHT, AUS GR&Uuml;NDEN, DIE SICH AUS IHRER BESONDEREN SITUATION ERGEBEN, GEGEN DIE VERARBEITUNG IHRER PERSONENBEZOGENEN DATEN WIDERSPRUCH EINZULEGEN; DIES GILT AUCH F&Uuml;R EIN AUF DIESE BESTIMMUNGEN GEST&Uuml;TZTES PROFILING. DIE JEWEILIGE RECHTSGRUNDLAGE, AUF DENEN EINE VERARBEITUNG BERUHT, ENTNEHMEN SIE DIESER DATENSCHUTZERKL&Auml;RUNG. WENN SIE WIDERSPRUCH EINLEGEN, WERDEN WIR IHRE BETROFFENEN PERSONENBEZOGENEN DATEN NICHT MEHR VERARBEITEN, ES SEI DENN, WIR K&Ouml;NNEN ZWINGENDE SCHUTZW&Uuml;RDIGE GR&Uuml;NDE F&Uuml;R DIE VERARBEITUNG NACHWEISEN, DIE IHRE INTERESSEN, RECHTE UND FREIHEITEN &Uuml;BERWIEGEN ODER DIE VERARBEITUNG DIENT DER GELTENDMACHUNG, AUS&Uuml;BUNG ODER VERTEIDIGUNG VON RECHTSANSPR&Uuml;CHEN (WIDERSPRUCH NACH ART. 21 ABS. 1 DSGVO).</p> <p>WERDEN IHRE PERSONENBEZOGENEN DATEN VERARBEITET, UM DIREKTWERBUNG ZU BETREIBEN, SO HABEN SIE DAS RECHT, JEDERZEIT WIDERSPRUCH GEGEN DIE VERARBEITUNG SIE BETREFFENDER PERSONENBEZOGENER DATEN ZUM ZWECKE DERARTIGER WERBUNG EINZULEGEN; DIES GILT AUCH F&Uuml;R DAS PROFILING, SOWEIT ES MIT SOLCHER DIREKTWERBUNG IN VERBINDUNG STEHT. WENN SIE WIDERSPRECHEN, WERDEN IHRE PERSONENBEZOGENEN DATEN ANSCHLIESSEND NICHT MEHR ZUM ZWECKE DER DIREKTWERBUNG VERWENDET (WIDERSPRUCH NACH ART. 21 ABS. 2 DSGVO).</p>
<h3 class="h5">Beschwerde&shy;recht bei der zust&auml;ndigen Aufsichts&shy;beh&ouml;rde</h3> <p>Im Falle von Verst&ouml;&szlig;en gegen die DSGVO steht den Betroffenen ein Beschwerderecht bei einer Aufsichtsbeh&ouml;rde, insbesondere in dem Mitgliedstaat ihres gew&ouml;hnlichen Aufenthalts, ihres Arbeitsplatzes oder des Orts des mutma&szlig;lichen Versto&szlig;es zu. Das Beschwerderecht besteht unbeschadet anderweitiger verwaltungsrechtlicher oder gerichtlicher Rechtsbehelfe.</p>
<h3 class="h5">Recht auf Daten&shy;&uuml;bertrag&shy;barkeit</h3> <p>Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erf&uuml;llung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem g&auml;ngigen, maschinenlesbaren Format aush&auml;ndigen zu lassen. Sofern Sie die direkte &Uuml;bertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.</p>
<h3 class="h5">Auskunft, Berichtigung und L&ouml;schung</h3> <p>Sie haben im Rahmen der geltenden gesetzlichen Bestimmungen jederzeit das Recht auf unentgeltliche Auskunft &uuml;ber Ihre gespeicherten personenbezogenen Daten, deren Herkunft und Empf&auml;nger und den Zweck der Datenverarbeitung und ggf. ein Recht auf Berichtigung oder L&ouml;schung dieser Daten. Hierzu sowie zu weiteren Fragen zum Thema personenbezogene Daten k&ouml;nnen Sie sich jederzeit an uns wenden.</p>
<h3 class="h5">Recht auf Einschr&auml;nkung der Verarbeitung</h3> <p>Sie haben das Recht, die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen. Hierzu k&ouml;nnen Sie sich jederzeit an uns wenden. Das Recht auf Einschr&auml;nkung der Verarbeitung besteht in folgenden F&auml;llen:</p> <ul> <li>Wenn Sie die Richtigkeit Ihrer bei uns gespeicherten personenbezogenen Daten bestreiten, ben&ouml;tigen wir in der Regel Zeit, um dies zu &uuml;berpr&uuml;fen. F&uuml;r die Dauer der Pr&uuml;fung haben Sie das Recht, die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn die Verarbeitung Ihrer personenbezogenen Daten unrechtm&auml;&szlig;ig geschah/geschieht, k&ouml;nnen Sie statt der L&ouml;schung die Einschr&auml;nkung der Datenverarbeitung verlangen.</li> <li>Wenn wir Ihre personenbezogenen Daten nicht mehr ben&ouml;tigen, Sie sie jedoch zur Aus&uuml;bung, Verteidigung oder Geltendmachung von Rechtsanspr&uuml;chen ben&ouml;tigen, haben Sie das Recht, statt der L&ouml;schung die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> <li>Wenn Sie einen Widerspruch nach Art. 21 Abs. 1 DSGVO eingelegt haben, muss eine Abw&auml;gung zwischen Ihren und unseren Interessen vorgenommen werden. Solange noch nicht feststeht, wessen Interessen &uuml;berwiegen, haben Sie das Recht, die Einschr&auml;nkung der Verarbeitung Ihrer personenbezogenen Daten zu verlangen.</li> </ul> <p>Wenn Sie die Verarbeitung Ihrer personenbezogenen Daten eingeschr&auml;nkt haben, d&uuml;rfen diese Daten &ndash; von ihrer Speicherung abgesehen &ndash; nur mit Ihrer Einwilligung oder zur Geltendmachung, Aus&uuml;bung oder Verteidigung von Rechtsanspr&uuml;chen oder zum Schutz der Rechte einer anderen nat&uuml;rlichen oder juristischen Person oder aus Gr&uuml;nden eines wichtigen &ouml;ffentlichen Interesses der Europ&auml;ischen Union oder eines Mitgliedstaats verarbeitet werden.</p>
<h3 class="h5">SSL- bzw. TLS-Verschl&uuml;sselung</h3> <p>Diese Seite nutzt aus Sicherheitsgr&uuml;nden und zum Schutz der &Uuml;bertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschl&uuml;sselung. Eine verschl&uuml;sselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von &bdquo;http://&ldquo; auf &bdquo;https://&ldquo; wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.</p> <p>Wenn die SSL- bzw. TLS-Verschl&uuml;sselung aktiviert ist, k&ouml;nnen die Daten, die Sie an uns &uuml;bermitteln, nicht von Dritten mitgelesen werden.</p>
<h3 class="h5">Widerspruch gegen Werbe-E-Mails</h3> <p>Der Nutzung von im Rahmen der Impressumspflicht ver&ouml;ffentlichten Kontaktdaten zur &Uuml;bersendung von nicht ausdr&uuml;cklich angeforderter Werbung und Informationsmaterialien wird hiermit widersprochen. Die Betreiber der Seiten behalten sich ausdr&uuml;cklich rechtliche Schritte im Falle der unverlangten Zusendung von Werbeinformationen, etwa durch Spam-E-Mails, vor.</p>
<h2>4. Datenerfassung auf dieser Website</h2>
<h3 class="h5">Kontaktformular</h3> <p>Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und f&uuml;r den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p> <p>Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit der Erf&uuml;llung eines Vertrags zusammenh&auml;ngt oder zur Durchf&uuml;hrung vorvertraglicher Ma&szlig;nahmen erforderlich ist. In allen &uuml;brigen F&auml;llen beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die Einwilligung ist jederzeit widerrufbar.</p> <p>Die von Ihnen im Kontaktformular eingegebenen Daten verbleiben bei uns, bis Sie uns zur L&ouml;schung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck f&uuml;r die Datenspeicherung entf&auml;llt (z.&nbsp;B. nach abgeschlossener Bearbeitung Ihrer Anfrage). Zwingende gesetzliche Bestimmungen &ndash; insbesondere Aufbewahrungsfristen &ndash; bleiben unber&uuml;hrt.</p>
<h3 class="h5">Anfrage per E-Mail, Telefon oder Telefax</h3> <p>Wenn Sie uns per E-Mail, Telefon oder Telefax kontaktieren, wird Ihre Anfrage inklusive aller daraus hervorgehenden personenbezogenen Daten (Name, Anfrage) zum Zwecke der Bearbeitung Ihres Anliegens bei uns gespeichert und verarbeitet. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.</p> <p>Die Verarbeitung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. b DSGVO, sofern Ihre Anfrage mit der Erf&uuml;llung eines Vertrags zusammenh&auml;ngt oder zur Durchf&uuml;hrung vorvertraglicher Ma&szlig;nahmen erforderlich ist. In allen &uuml;brigen F&auml;llen beruht die Verarbeitung auf unserem berechtigten Interesse an der effektiven Bearbeitung der an uns gerichteten Anfragen (Art. 6 Abs. 1 lit. f DSGVO) oder auf Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO) sofern diese abgefragt wurde; die Einwilligung ist jederzeit widerrufbar.</p> <p>Die von Ihnen an uns per Kontaktanfragen &uuml;bersandten Daten verbleiben bei uns, bis Sie uns zur L&ouml;schung auffordern, Ihre Einwilligung zur Speicherung widerrufen oder der Zweck f&uuml;r die Datenspeicherung entf&auml;llt (z.&nbsp;B. nach abgeschlossener Bearbeitung Ihres Anliegens). Zwingende gesetzliche Bestimmungen &ndash; insbesondere gesetzliche Aufbewahrungsfristen &ndash; bleiben unber&uuml;hrt.</p>
<h2>5. Newsletter</h2>
<h3 class="h5">Newsletter&shy;daten</h3> <p>Wenn Sie den auf der Website angebotenen Newsletter beziehen m&ouml;chten, ben&ouml;tigen wir von Ihnen eine E-Mail-Adresse sowie Informationen, welche uns die &Uuml;berpr&uuml;fung gestatten, dass Sie der Inhaber der angegebenen E-Mail-Adresse sind und mit dem Empfang des Newsletters einverstanden sind. Weitere Daten werden nicht bzw. nur auf freiwilliger Basis erhoben. Diese Daten verwenden wir ausschlie&szlig;lich f&uuml;r den Versand der angeforderten Informationen und geben diese nicht an Dritte weiter.</p> <p>Die Verarbeitung der in das Newsletteranmeldeformular eingegebenen Daten erfolgt ausschlie&szlig;lich auf Grundlage Ihrer Einwilligung (Art. 6 Abs. 1 lit. a DSGVO). Die erteilte Einwilligung zur Speicherung der Daten, der E-Mail-Adresse sowie deren Nutzung zum Versand des Newsletters k&ouml;nnen Sie jederzeit widerrufen, etwa &uuml;ber den &bdquo;Austragen&ldquo;-Link im Newsletter. Die Rechtm&auml;&szlig;igkeit der bereits erfolgten Datenverarbeitungsvorg&auml;nge bleibt vom Widerruf unber&uuml;hrt.</p> <p>Die von Ihnen zum Zwecke des Newsletter-Bezugs bei uns hinterlegten Daten werden von uns bis zu Ihrer Austragung aus dem Newsletter bei uns bzw. dem Newsletterdiensteanbieter gespeichert und nach der Abbestellung des Newsletters oder nach Zweckfortfall aus der Newsletterverteilerliste gel&ouml;scht. Wir behalten uns vor, E-Mail-Adressen aus unserem Newsletterverteiler nach eigenem Ermessen im Rahmen unseres berechtigten Interesses nach Art. 6 Abs. 1 lit. f DSGVO zu l&ouml;schen oder zu sperren.</p> <p>Daten, die zu anderen Zwecken bei uns gespeichert wurden, bleiben hiervon unber&uuml;hrt.</p> <p>Nach Ihrer Austragung aus der Newsletterverteilerliste wird Ihre E-Mail-Adresse bei uns bzw. dem Newsletterdiensteanbieter ggf. in einer Blacklist gespeichert, sofern dies zur Verhinderung k&uuml;nftiger Mailings erforderlich ist. Die Daten aus der Blacklist werden nur f&uuml;r diesen Zweck verwendet und nicht mit anderen Daten zusammengef&uuml;hrt. Dies dient sowohl Ihrem Interesse als auch unserem Interesse an der Einhaltung der gesetzlichen Vorgaben beim Versand von Newslettern (berechtigtes Interesse im Sinne des Art. 6 Abs. 1 lit. f DSGVO). Die Speicherung in der Blacklist ist zeitlich nicht befristet. <strong>Sie k&ouml;nnen der Speicherung widersprechen, sofern Ihre Interessen unser berechtigtes Interesse &uuml;berwiegen.</strong></p>
<h2>6. Plugins und Tools</h2>
<h3 class="h5">Google Fonts (lokales Hosting)</h3> <p>Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten so genannte Google Fonts, die von Google bereitgestellt werden. Die Google Fonts sind lokal installiert. Eine Verbindung zu Servern von Google findet dabei nicht statt.</p> <p>Weitere Informationen zu Google Fonts finden Sie unter <a href="https://developers.google.com/fonts/faq" target="_blank" rel="noopener noreferrer">https://developers.google.com/fonts/faq</a> und in der Datenschutzerkl&auml;rung von Google: <a href="https://policies.google.com/privacy?hl=de" target="_blank" rel="noopener noreferrer">https://policies.google.com/privacy?hl=de</a>.</p>
<h3 class="h5">Font Awesome (lokales Hosting)</h3> <p>Diese Seite nutzt zur einheitlichen Darstellung von Schriftarten Font Awesome. Font Awesome ist lokal installiert. Eine Verbindung zu Servern von Fonticons, Inc. findet dabei nicht statt.</p> <p>Weitere Informationen zu Font Awesome finden Sie in der Datenschutzerkl&auml;rung f&uuml;r Font Awesome unter: <a href="https://fontawesome.com/privacy" target="_blank" rel="noopener noreferrer">https://fontawesome.com/privacy</a>.</p>
<p>Quelle: <a href="https://www.e-recht24.de">https://www.e-recht24.de</a></p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return { t, localePath }
}
}
</script>

310
pages/settings/donation.vue Normal file
View File

@@ -0,0 +1,310 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold text-center pt-3">
{{ t("Donation") }}
</h1>
<div class="row">
<div class="col-12">
<p class="fw-light text-center py-3">
{{ t("DonationHeading") }}
</p>
</div>
</div>
<div class="d-flex flex-column flex-md-row gap-3">
<div class="col-12 py-1 flex-auto">
<div class="card" :class="{'active-sub':oneMonth,'sub-card':!oneMonth}" @click="oneMonth=true;twoMonth=false;threeMonth=false">
<div class="card-body py-4">
<h2 class="text-center fw-bolder">
3.00
</h2>
<p class="text-center">
{{ t("SmallAmount") }}
</p>
</div>
</div>
</div>
<div class="col-12 py-1 flex-auto">
<div class="card" :class="{'active-sub':twoMonth,'sub-card':!twoMonth}" @click="twoMonth=true;oneMonth=false;threeMonth=false">
<div class="card-body py-4">
<h2 class="text-center fw-bolder">
10.00
</h2>
<p class=" text-center">
{{ t("MediumAmount") }}
</p>
</div>
</div>
</div>
<div class="col-12 py-1 flex-auto">
<div class="card" :class="{'active-sub':threeMonth,'sub-card':!threeMonth}" @click="threeMonth=true;twoMonth=false;oneMonth=false">
<div class="card-body py-4">
<h2 class="text-center fw-bolder">
15.00
</h2>
<p class=" text-center">
{{ t("BigAmount") }}
</p>
</div>
</div>
</div>
</div>
<div class="row pt-5">
<div class="col-12 px-md-5 px-0">
<p class="text-center text-muted">
{{ t("EndStatement") }}
</p>
</div>
<div class="col-12 text-end" />
</div>
<div class="row pt-5 justify-content-center ">
<div class="col-12 col-md-6 col-lg-6 text-center">
<div class="row">
<div class="col-12">
<p class="fw-medium text-center"><strong>{{ t("bankaccount") }}:</strong><br></p>
<p class="fw-light text-center">
{{ t("Name") }}: Robert Rapp<br>
</p>
<p class="fw-light text-center">
IBAN: DE42 1001 1001 2629 4590 53
<span class="tooltip-container">
<i class="fas fa-copy ml-2 cursor-pointer" title="In die Zwischenablage kopieren" @click="copyIBANToClipboard('DE42 1001 1001 2629 4590 53')" />
<span class="tooltiptext" :class="{ 'show': showTooltip }">Kopiert!</span>
</span>
</p>
<p class="fw-light text-center">
BIC: NTSBDEB1XXX
</p>
<p class="fw-light text-center">
{{ userVerwendungszweck }}
<span class="tooltip-container">
<i class="fas fa-copy ml-2 cursor-pointer" title="In die Zwischenablage kopieren" @click="copyToClipboard(userVerwendungszweck)" />
<span class="tooltiptext" :class="{ 'show': showTooltip1 }">Kopiert!</span>
</span>
</p>
</div>
</div>
</div>
<!-- <StripeElements-->
<!-- v-if="stripeLoaded"-->
<!-- v-slot="{ elements, instance }" ref="elms"-->
<!-- :stripe-key="stripeKey"-->
<!-- :instance-options="instanceOptions"-->
<!-- :elements-options="elementsOptions"-->
<!-- >-->
<!-- <StripeElement-->
<!-- ref="card"-->
<!-- :elements="elements"-->
<!-- :options="cardOptions"-->
<!-- />-->
<!-- </StripeElements>-->
<!-- <button type="button" class="btn text-white fs-5 col-12 fw-bold py-2" @click="pay" style="background-color: #e9c046">Pay</button>-->
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapStores } from 'pinia'
import { useUserStore } from '~/stores/user'
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const userVerwendungszweck = ref(useUserStore().user.email)
const usermail = useUserStore().user.email
return { t, localePath, userVerwendungszweck, usermail }
},
data () {
return {
oneMonth: false,
twoMonth: false,
threeMonth: false,
loading: false,
cancel_loading: false,
showTooltip: false,
showTooltip1: false,
subscription: '',
form: {
type: 'one'
}
}
},
computed: {
// ...mapState(useUserStore, ['user']),
...mapStores(useUserStore)
},
mounted () {
this.fetchSubscription()
},
methods: {
...mapActions(useUserStore, ['updateUser', 'logout']),
copyToClipboard (text) {
navigator.clipboard.writeText(text).then(() => {
this.showTooltip1 = true
setTimeout(() => {
this.showTooltip1 = false
}, 2000) // Tooltip verschwindet nach 2 Sekunden
}).catch((err) => {
useNuxtApp().$logger.error('Fehler beim Kopieren: ', err)
})
},
copyIBANToClipboard (text) {
navigator.clipboard.writeText(text).then(() => {
this.showTooltip = true
setTimeout(() => {
this.showTooltip = false
}, 2000) // Tooltip verschwindet nach 2 Sekunden
}).catch((err) => {
useNuxtApp().$logger.error('Fehler beim Kopieren: ', err)
})
},
updateUserNow () {
this.$axios.post('/api/auth/me').then(({ data }) => {
this.updateUser(data.user)
}).catch(() => {
this.logout()
this.$router.push(this.localePath('/auth/login'))
})
},
paypal () {
window.open('https://www.paypal.com/pools/c/93YzF9GqEP', '_blank')
},
fetchSubscription () {
this.$axios.post('/api/fetch-subscription').then(({ data }) => {
this.subscription = data.name
if (data.name === 'monthly') {
this.oneMonth = true
this.twoMonth = false
this.threeMonth = false
}
if (data.name === 'sixmonth') {
this.oneMonth = false
this.twoMonth = true
this.threeMonth = false
}
if (data.name === 'yearly') {
this.oneMonth = false
this.twoMonth = false
this.threeMonth = true
}
})
}
}
}
// export default {
// name: 'CardOnly',
// data() {
// return {
// stripeKey: 'pk_test_0S0H9CTYtkhPlArgg4KkPFcZ',
// instanceOptions: {},
// elementsOptions: {},
// cardOptions: {},
// stripeLoaded: false,
// card: null,
// elms: null,
// oneMonth:false,
// twoMonth:false,
// threeMonth:false,
// };
// },
// components: {
// StripeElements,
// StripeElement,
// },
// beforeMount() {
// const stripePromise = loadStripe(this.stripeKey);
// stripePromise.then(() => {
// this.stripeLoaded = true;
// });
// },
// methods: {
// pay() {
// const cardElement = this.$refs.card.stripeElement;
//
// this.$refs.elms.instance
// .createPaymentMethod({ type: 'card', card: cardElement })
// .then((result) => {
// // useNuxtApp().$logger.log(result);
// // useNuxtApp().$logger.log(result.paymentMethod);
// this.$axios.post('/api/subscribe', result).then((response) => {
// // useNuxtApp().$logger.log(response.data);
// });
// });
// },
// },
// computed: {
//
// },
// created() {
//
// },
// };
</script>
<style>
.flex-auto {
flex: 1 1 auto;
}
.active-sub{
background: #E9C046;
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
border-radius: 12px;
color:white;
background-color:#E9C046 !important;
}
.sub-card{
background: #FFFFFF;
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
border-radius: 12px;
color:black;
height: 100%;
transition: 250ms ease-in-out;
}
.sub-card:hover, .sub-card:focus{
background: #E9C046;
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
border-radius: 12px;
color:white;
border-color: #E9C046;
background-color:#E9C046 !important;
}
.tooltip-container {
margin-left: 10px;
font-size: smaller;
position: relative;
display: inline-block;
}
.tooltiptext {
visibility: hidden;
font-family: 'Montserrat', sans-serif;
font-size: 14px;
width: auto;
background-color: #e9c046;
color: #000000;
text-align: center;
border-radius: 6px;
padding: 5px;
position: absolute;
z-index: 1;
bottom: 125%;
left: 20%;
margin-left: -30px;
opacity: 0;
transition: opacity 0.3s;
}
.tooltiptext.show {
visibility: visible;
opacity: 1;
}
</style>

View File

@@ -0,0 +1,632 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-sm-9">
<h2 class="fw-bold text-center me-5 pt-5">
<span class="float-start" style="cursor: pointer" @click="$router.go(-1)"><i
class="fa-solid fa-arrow-left-long"
/></span> {{ t("Donation") }}
</h2>
<div class="row">
<div class="col-12">
<h4 class="fw-light text-center pt-3">
{{ t("DonationHeading") }}
</h4>
</div>
</div>
<div class="row">
<section class="pricing py-5">
<div class="row">
<!-- Basic Tier -->
<div class="col-md-4">
<div id="basic-plan" class="card mb-5 mb-lg-0" data-plan="starter">
<div class="card-body">
<h5 class="card-title text-muted text-uppercase text-center">
Basic
</h5>
<h6 class="card-price text-center">
<span class="price" data-price-type="starter">8.00</span><span class="period">/month</span>
</h6>
<div v-if="currentSubscription() == 1" class="d-flex justify-content-center my-2">
<span class="badge bg-warning">Active</span>
</div>
<hr>
<ul class="fa-ul">
<li><span class="fa-li"><i class="fas fa-check" /></span>Good for students, employees</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>30 Days Trial</li>
<li>
<div class="pricing-toggle my-2 d-flex align-items-center">
<div class="me-3">
<input
id="starter-month"
type="radio"
class="me-1"
name="cycle-starter"
value="month"
:checked="currentSubscription() === 1 && dataStore.user.montly === '1' || currentSubscription() !== 1"
>
<label for="starter-month" class="form-check-label">Monthly</label>
</div>
<div>
<input
id="starter-year"
type="radio"
class="me-1"
name="cycle-starter"
:checked="currentSubscription() == 1 && dataStore.user.yearly === '1'"
value="year"
>
<label for="starter-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
</div>
</div>
</li>
</ul>
<div class="d-grid">
<span v-if="isInvited">
<div class="row">
<button class="btn btn-warning text-uppercase buy-now" @click="showManagerPlan()">Buy
Now</button>
</div>
</span>
<span v-else>
<div class="row">
<button
class="btn btn-warning text-uppercase buy-now"
:disabled="currentSubscription() === 1"
>Active</button>
</div>
</span>
</div>
</div>
</div>
</div>
<!-- Team Tier -->
<div class="col-md-4">
<div id="team-plan" class="card mb-5 mb-lg-0" data-plan="pro">
<div class="card-body">
<h5 class="card-title text-muted text-uppercase text-center">
Team
</h5>
<h6 class="card-price text-center">
<span class="price" data-price-type="pro">5</span><span class="period">/month</span>
</h6>
<div v-if="currentSubscription() == 2" class="d-flex justify-content-center my-2">
<span class="badge bg-warning">Active</span>
</div>
<hr>
<ul class="fa-ul">
<li>
<span class="fa-li"><i class="fas fa-check" /></span>Good for colleagues, groups, teams,
departments
</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>Individual User Management</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>Flexible Trial Version</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>Coherent invoicing</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span> + Basic Per User</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>30 Days Trial</li>
<li>
<div class="pricing-toggle my-2 d-flex align-items-center">
<div class="me-3">
<input
id="pro-month"
type="radio"
class="me-1"
name="cycle-pro"
value="month"
:checked="currentSubscription() === 2 && dataStore.user.montly === '1' || currentSubscription() !== 2"
>
<label for="pro-month" class="form-check-label">Monthly</label>
</div>
<div>
<input
id="pro-year"
type="radio"
class="me-1"
name="cycle-pro"
:checked="currentSubscription() === 2 && dataStore.user.yearly === '1'"
value="year"
>
<label for="pro-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
</div>
</div>
</li>
</ul>
<div class="d-grid">
<span v-if="isInvited">
<div class="row">
<button class="btn btn-warning text-uppercase team-plan" @click="showManagerPlan()">Buy
Now</button>
</div>
</span>
<span v-else>
<div class="row">
<button
class="btn btn-warning text-uppercase team-plan"
:disabled="currentSubscription() === 2"
>Active</button>
</div>
</span>
</div>
</div>
</div>
</div>
<!-- Enterprise Tier -->
<div class="col-md-4">
<div id="enterprise-plan" class="card" data-plan="enterprise">
<div class="card-body">
<h5 class="card-title text-muted text-uppercase text-center">
Enterprise
</h5>
<h6 class="card-price text-center">
<span>Depends</span>
</h6>
<div v-if="currentSubscription() == 3" class="d-flex justify-content-center my-2">
<span class="badge bg-warning">Active</span>
</div>
<hr>
<ul class="fa-ul">
<li><span class="fa-li"><i class="fas fa-check" /></span>Good For Enterprise > 200</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Full access to app</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>3 Professional soundscapes</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>mindboost@technology</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Individual User Management</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Flexible Trial Version</li>
<li><span class="fa-li"><i class="fas fa-check" /></span>Coherent invoicing</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>Dedicated Partnership</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>In-person Onboarding</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>Individual Pricing</li>
<li><span class="fa-li"><i class="fas fa-plus" /></span>30 Days Trial</li>
<li>
<div class="pricing-toggle my-2 d-flex align-items-center">
<div class="me-3">
<input
id="enterprise-month"
type="radio"
class="me-1"
name="cycle-enterprise"
value="month"
checked
>
<label for="enterprise-month" class="form-check-label">Monthly</label>
</div>
<div>
<input
id="enterprise-year"
type="radio"
class="me-1"
name="cycle-enterprise"
value="year"
>
<label for="enterprise-year" class="form-check-label">Yearly <sup>save 20%</sup></label>
</div>
</div>
</li>
</ul>
<div class="d-grid">
<span v-if="isInvited">
<div class="row">
<button type="button" class="btn btn-warning text-uppercase" @click="showManagerPlan()">
contact us
</button>
</div>
</span>
<span v-else>
<div class="row">
<button
type="button"
class="btn btn-warning text-uppercase"
data-bs-toggle="modal"
data-bs-target="#exampleModal"
>
contact us
</button>
</div>
</span>
<!-- Modal for enterprise contact us form -->
<div
id="exampleModal"
class="modal fade"
tabindex="-1"
aria-labelledby="exampleModalLabel"
aria-hidden="true"
>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 id="exampleModalLabel" class="modal-title fs-5">
Contact Us
</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" />
</div>
<form @submit.prevent="submitForm">
<div class="modal-body">
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input
id="name"
v-model="contact_form.name"
type="text"
class="form-control"
required
>
</div>
<div class="mb-3">
<label for="companyName" class="form-label">Company Name</label>
<input
id="companyName"
v-model="contact_form.company_name"
type="text"
class="form-control"
required
>
</div>
<div class="mb-3">
<label for="message" class="form-label">Message</label>
<textarea id="message" v-model="contact_form.message" class="form-control" required />
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-warning text-uppercase">
Send Message
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapActions, mapStores } from 'pinia'
import { useUserStore } from '~/stores/user'
// entery point
export default {
components: { },
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const dataStore = useUserStore() // Initialize store in setup()
return {
t,
localePath,
dataStore
}
},
// dara store
data () {
return {
oneMonth: false,
twoMonth: false,
threeMonth: false,
loading: false,
cancel_loading: false,
subscription: '',
form: {
type: 'one'
},
contact_form: {
name: '',
company_name: '',
email: 'dowhf@access.bro', // Removed direct reference to userStore
message: ''
},
isInvited: 0,
isManagerPlan: 0
}
},
// computed
computed: {
...mapStores(useUserStore)
},
// mounted
mounted () {
const urlBackend = useRuntimeConfig().public.BACKEND_URL || 'https://b.mindboost.team'
this.fetchSubscription()
this.GetManagerDetails()
if (window.Paddle) {
// Initialize Paddle
Paddle.Environment.set('sandbox')
Paddle.Initialize({
token: 'test_27bb608597d1a4d75fe1bce2be7',
eventCallback: async function (data) {
if (data.name === 'checkout.completed') {
// useNuxtApp().$logger.log(data)
try {
const response = await axios.post(urlBackend + '/api/paddle-webhook', data)
// useNuxtApp().$logger.log('this is subscription data data', response)
// Access the Pinia store and update the user data
const userStore = useUserStore()
userStore.updateUser(response.data.user)
} catch (error) {
// useNuxtApp().$logger.log(error)
}
}
}
})
// Product definitions for each plan
const products = {
starter: {
productId: 'pro_01j508hk3rp5f5g68n7tqv1thd',
monthPriceId: 'pri_01j508ssstj4n6xra9j8ymr3d1',
yearPriceId: 'pri_01j508x0rwntqs581rj3xfx49n'
},
pro: {
productId: 'pro_01j5090jy3jkf4w9vr0j279fm5',
monthPriceId: 'pri_01j50947nd6t9ztgsrq5h6fsmx',
yearPriceId: 'pri_01j5097bzyp9zrevw0nsagb477'
},
enterprise: {
productId: 'pro_01j509nre47wbdtmqmd6x098s5',
monthPriceId: 'pri_01j509qknvfx3hv0459kpkvvar',
yearPriceId: 'pri_01j509ryg22rzs0ahwrb2vwzhq'
}
}
// Functions to handle pricing updates
const updateBasicPlan = () => {
const plan = 'starter'
const cycle = document.querySelector('input[name="cycle-starter"]:checked').value
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
Paddle.PricePreview({ items: [{ quantity: 1, priceId }] })
.then((result) => {
document.querySelector('#basic-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
document.querySelector('#basic-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
})
}
// Update the basic plan pricing on page load
const updateTeamPlan = () => {
const plan = 'pro'
const cycle = document.querySelector('input[name="cycle-pro"]:checked').value
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
Paddle.PricePreview({ items: [{ quantity: 5, priceId }] })
.then((result) => {
document.querySelector('#team-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
document.querySelector('#team-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
})
}
// Update the enterprise plan pricing on page load
const updateEnterprisePlan = () => {
const plan = 'enterprise'
const cycle = document.querySelector('input[name="cycle-enterprise"]:checked').value
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
Paddle.PricePreview({ items: [{ quantity: 1, priceId }] })
.then((result) => {
document.querySelector('#enterprise-plan .price').textContent = result.data.details.lineItems[0].formattedTotals.subtotal
document.querySelector('#enterprise-plan .period').textContent = cycle === 'month' ? '/month' : '/year'
})
}
// Event handlers for plan and cycle changes
const handlePlanChange = (event) => {
const plan = event.target.closest('.card').getAttribute('data-plan')
if (plan === 'starter') {
updateBasicPlan()
} else if (plan === 'pro') {
updateTeamPlan()
} else if (plan === 'enterprise') {
updateEnterprisePlan()
}
}
// Set event listeners for plan and cycle changes
document.querySelectorAll('input[name^="cycle-"]').forEach((input) => {
input.addEventListener('change', handlePlanChange)
})
// Event handlers for buy now buttons
document.querySelectorAll('.buy-now').forEach((button) => {
button.addEventListener('click', () => {
const plan = button.closest('.card').getAttribute('data-plan')
const cycle = document.querySelector(`input[name="cycle-${plan}"]:checked`).value
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
Paddle.Checkout.open({
items: [{ priceId, quantity: 1 }],
customer: {
email: this.dataStore.user.email,
address: { countryCode: 'PK', postalCode: '59351' }
},
source: window.location.href
})
})
})
// Event handlers for team-plan class
document.querySelectorAll('.team-plan').forEach((button) => {
button.addEventListener('click', () => {
const plan = button.closest('.card').getAttribute('data-plan')
const cycle = document.querySelector(`input[name="cycle-${plan}"]:checked`).value
const priceId = cycle === 'month' ? products[plan].monthPriceId : products[plan].yearPriceId
Paddle.Checkout.open({
items: [{ priceId, quantity: 5 }],
customer: {
email: this.dataStore.user.email,
address: { countryCode: 'PK', postalCode: '59351' }
},
source: window.location.href
})
})
})
// Initialize prices
updateBasicPlan()
updateTeamPlan()
updateEnterprisePlan()
}
},
// ======================= methods =======================
methods: {
// check user plan
isDisabled () {
const dataStore = useUserStore()
const userasdf = dataStore
// useNuxtApp().$logger.log('store', user); // Debugging
return !(userasdf.user.team_subscription_plan === '1' ||
userasdf.user.enterprise_subscription_plan === '1') &&
(userasdf.user.basic_subscription_plan !== '0')
},
// get user current plan
currentSubscription () {
const dataStore = useUserStore()
const userasdf = dataStore
return (userasdf.user.current_subscription_plan)
},
/**
* showManagerPlan in toster form
*/
showManagerPlan () {
let managerPlan = '' // Changed from const to let
if (this.isManagerPlan === 1) {
managerPlan = 'Basic'
} else if (this.isManagerPlan === 2) {
managerPlan = 'Team'
} else if (this.isManagerPlan === 3) {
managerPlan = 'Enterprise'
}
this.$toast.success(`Manager already has the ${managerPlan} plan`) // Fixed typo
},
/**
* Submits the contact form to the server
*/
async submitForm () {
try {
await axios.post(urlBackend + '/api/enterprise-plan-contact', this.contact_form)
// useNuxtApp().$logger.log('Success:', response.data)
Swal.fire({
title: 'Good job!',
text: 'Message sent successfully! Support team will contact you soon.',
icon: 'success'
})
} catch (error) {
// useNuxtApp().$logger.error('Error:', error.response);
this.$toast.error('Oops, something went wrong!')
}
},
...mapActions(useUserStore, ['updateUser', 'logout']),
updateUserNow () {
this.$axios.post('/api/auth/me').then(({ data }) => {
this.updateUser(data.user)
}).catch(() => {
this.logout()
this.$router.push(this.localePath('/auth/login'))
})
},
paypal () {
window.open('https://www.paypal.com/pools/c/93YzF9GqEP', '_blank')
},
fetchSubscription () {
this.$axios.post('/api/fetch-subscription').then(({ data }) => {
this.subscription = data.name
this.oneMonth = data.name === 'monthly'
this.twoMonth = data.name === 'sixmonth'
this.threeMonth = data.name === 'yearly'
})
},
async GetManagerDetails () {
const userStore = useUserStore()
if (userStore.user.inviter_id) {
const response = await axios.post(urlBackend + '/api/fetch-manger-detail', { manager_id: userStore.user.inviter_id })
// useNuxtApp().$logger.log('manager_id', response.data.data)
this.isInvited = 1
// useNuxtApp().$logger.log('response.data.data.inviter_id', response.data.data.inviter_id)
this.isManagerPlan = response.data.data.current_subscription_plan
}
}
}
}
</script>
<style>
.active-sub {
background: #E9C046;
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
border-radius: 12px;
color: white;
background-color: #E9C046 !important;
}
.sub-card {
background: #FFFFFF;
box-shadow: 0px 0px 16px 1px rgba(108, 97, 97, 0.05);
border-radius: 12px;
color: black;
}
/* style for plan cards */
.pricing .card {
border: none;
border-radius: 1rem;
transition: all 0.2s;
box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.1);
}
.pricing hr {
margin: 1.5rem 0;
}
.pricing .card-title {
margin: 0.5rem 0;
font-size: 0.9rem;
letter-spacing: .1rem;
font-weight: bold;
}
.pricing .card-price {
font-size: 3rem;
margin: 0;
}
.pricing .card-price .period {
font-size: 0.8rem;
}
.pricing ul li {
margin-bottom: 1rem;
}
.pricing .text-muted {
opacity: 0.7;
}
.pricing .btn {
font-size: 80%;
border-radius: 5rem;
letter-spacing: .1rem;
font-weight: bold;
padding: 1rem;
opacity: 0.7;
transition: all 0.2s;
}
/* Hover Effects on Card */
@media (min-width: 992px) {
.pricing .card:hover {
margin-top: -.25rem;
margin-bottom: .25rem;
box-shadow: 0 0.5rem 1rem 0 rgba(0, 0, 0, 0.3);
}
.pricing .card:hover .btn {
opacity: 1;
}
}
</style>

View File

@@ -0,0 +1,175 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold text-center py-3">
<span class="float-start" style="cursor: pointer" @click="$router.go(-1)"><i class="fa-solid fa-arrow-left-long" /></span>
{{ t("Edit Account") }}
</h1>
<div class="row pt-4">
<div class="col-12">
<form @submit.prevent="validateAndSave">
<div class="row">
<div class="col-12">
<label for="firstname" class="text-muted ">{{ t("First Name") }} </label>
<input id="firstname" v-model="form.first_name" type="text" class="form-control" :placeholder="t('First Name')">
<div v-if="errors.first_name" class="invalid-feedback d-block">
{{ errors.first_name[0] }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12">
<label for="surname" class="text-muted ">{{ t("Surname") }} </label>
<input id="surname" v-model="form.surname" type="text" class="form-control" :placeholder="t('Surname')">
<div v-if="errors.surname" class="invalid-feedback d-block">
{{ errors.surname[0] }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12">
<label for="email" class="text-muted ">{{ t("Email") }} </label>
<input id="email" v-model="form.email" type="email" class="form-control" :placeholder="t('Email')">
<div v-if="errors.email" class="invalid-feedback d-block">
{{ errors.email[0] }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12 col-sm-6">
<label for="password" class="text-muted ">{{ t("Password") }} </label>
<input id="password" v-model="form.password" type="password" class="form-control" placeholder="***">
<div v-if="errors.password" class="invalid-feedback d-block">
{{ errors.password[0] }}
</div>
</div>
<div class="col-12 col-sm-6">
<label for="confirm-password" class="text-muted ">{{ t("Confirm Password") }} </label>
<input id="confirm-password" v-model="form.confirm_password" type="password" class="form-control" placeholder="***">
<div v-if="errors.confirm_password" class="invalid-feedback d-block">
{{ errors.confirm_password[0] }}
</div>
</div>
</div>
<div class="row pt-3">
<div class="col-12">
<label for="language" class="text-muted ">{{ t("Language") }} </label>
<select id="language" v-model="form.language" class="form-select" @change="changeLanguage">
<option value="en">
English
</option>
<option value="de">
German
</option>
</select>
<div v-if="errors.language" class="invalid-feedback d-block">
{{ errors.language[0] }}
</div>
</div>
</div>
<div class="row pt-5 g-2">
<button type="submit" class="btn btn-primary-custom mx-auto col-4" style="background-color: #e9c046">
{{ t("Save Changes") }} <div v-if="loading" class="spinner-border spinner-border-sm" role="status">
<span class="sr-only">{{ t("Loading...") }} </span>
</div>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapActions } from 'pinia'
// import Index from '~/pages/index.vue'
import { useUserStore } from '~/stores/user'
export default {
name: 'EditAccount',
components: {
// Index
},
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
const changeLanguage = (locale) => {
// useNuxtApp().$logger.log('switch', locale)
useRouter().push(switchLocalePath(locale))
// i18n.global.locale.value=
}
return { t, localePath, changeLanguage }
},
data () {
return {
loading: false,
form: {
first_name: '',
surname: '',
email: 'email',
password: '',
confirm_password: '',
language: 'de'
},
errors: []
}
},
computed: {
...mapState(useUserStore, ['user'])
},
mounted () {
this.form.first_name = this.user.first_name
this.form.email = this.user.email
this.form.surname = this.user.surname
this.form.language = this.user.language
},
methods: {
...mapActions(useUserStore, ['updateUser']),
validateAndSave () {
this.errors = []
// Wenn Passwort leer ist, lassen wir die Prüfung zu Änderung des Passworts ist optional
if (this.form.password !== '') {
if (this.form.password !== this.form.confirm_password) {
this.errors.confirm_password = ['Passwörter stimmen nicht überein.']
return
}
}
this.saveUser()
},
saveUser () {
this.loading = true
const payload = { ...this.form }
if (!payload.password) { delete payload.password }
if (!payload.confirm_password) { delete payload.confirm_password }
this.$axios.post('/api/account/update', payload).then(({ data }) => {
this.changeLanguage(this.form.language)
this.loading = false
if (data.success) {
this.$toast.success(data.message)
this.updateUser(data.user)
this.$router.push(this.localePath('/settings/account'))
}
}).catch((error) => {
this.loading = false
this.$toast.error('Something wrong while saving...')
if (error.response.status === 422) {
this.errors = error.response.data.errors
}
})
}
}
}
</script>

238
pages/settings/faq.vue Normal file
View File

@@ -0,0 +1,238 @@
<template>
<div>
<div class="row justify-content-center m-0 p-0">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<div class="row">
<div class="col-12">
<div class="me-4">
<h1 class="h3 fw-bold text-center py-3">
{{ t("FAQ") }}
</h1>
</div>
<div id="accordionExample" class="accordion">
<div class="accordion-item">
<h2 id="headingOne" class="accordion-header">
<button
class="accordion-button fw-semibold"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseOne"
aria-expanded="true"
aria-controls="collapseOne"
>
{{ t("question1") }}
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse show" aria-labelledby="headingOne" data-bs-parent="#accordionExample">
<div class="accordion-body">
<p>
{{ t("answer_part_1") }}
<br>{{ t("answer_part_2") }}
</p>
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingTwo" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
{{ t("question_2") }}
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_2") }} 01
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingThree" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseThree"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_3") }}
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_3") }}
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingFour" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseFour"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_4") }}
</button>
</h2>
<div id="collapseFour" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_4") }}
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingFive" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseFive"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_5") }}
</button>
</h2>
<div id="collapseFive" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_5") }}
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingsix" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapsesix"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_6") }}
</button>
</h2>
<div id="collapsesix" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_6") }}
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingsaven" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseseven"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_7") }}
</button>
</h2>
<div id="collapseseven" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_7") }}
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingeight" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapseeight"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_8") }}
</button>
</h2>
<div id="collapseeight" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
<ol class="px-0 mx-0">
<li>{{ t("answer_8_1") }}</li>
<li>{{ t("answer_8_2") }}</li>
<li>{{ t("answer_8_3") }}</li>
<li>{{ t("answer_8_4") }}</li>
</ol>
</div>
</div>
</div>
<div class="accordion-item">
<h2 id="headingnine" class="accordion-header">
<button
class="accordion-button fw-semibold collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapsenine"
aria-expanded="false"
aria-controls="collapseThree"
>
{{ t("question_9") }}
</button>
</h2>
<div id="collapsenine" class="accordion-collapse collapse" aria-labelledby="headingThree" data-bs-parent="#accordionExample">
<div class="accordion-body">
{{ t("answer_9") }}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Subscription',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return { t, localePath }
}
}
</script>
<style scoped>
.accordion-item {
border: none;
margin-bottom: 0.5em;
}
.accordion-button {
background-color: rgb(244, 245, 247) !important;
}
.accordion-button:focus {
z-index: 3;
border:none;
outline: 0;
box-shadow: none;
}
.accordion-button:not(.open) {
color: black;
box-shadow: none;
background-color: rgb(244, 245, 247) !important;
font-weight: 600;
}
.accordion-collapse {
background-color: rgb(244, 245, 247);
}
.accordion-button:not(.open):hover, .accordion-button:not(.open):focus {
background-color: rgb(226, 229, 234) !important;
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold py-3">Impressum</h1>
<div class="row pt-4">
<div class="col-12">
<h2 class="h4 mt-4">Angaben gem&auml;&szlig; &sect; 5 TMG</h2>
<p>
Robert-Carl Rapp<br>
Sindlingerstr. 23<br>
71083 Herrenberg
</p>
<h2 class="h4 mt-4">Postadresse</h2>
<p>
Sindlingerstr. 23</br>
71083 Herrenberg
</p>
<h2 class="h4 mt-4">Kontakt</h2>
<p>
Telefon: +(49)15784037946<br>
E-Mail: kontakt@mindboost.team
</p>
<p>Quelle: <a href="https://www.e-recht24.de/impressum-generator.html">Generated by eRecht24</a></p>
<h2 class="h4 mt-4">Bildnachweise</h2>
<a href="https://de.vecteezy.com/kostenlos-videos/wei%C3%9F">Weiß Stockvideos von Vecteezy</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'SettingPage',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return { t, localePath }
}
}
</script>
<style>
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold py-3">Nutzungsbedingungen der App von Mindboost</h1>
<div class="row pt-4">
<div class="col-12">
<h2 class="h4">1. Geltungsbereich</h2>
<p>Diese Nutzungsbedingungen gelten für alle Dienste und Inhalte, die durch die App von Mindboost bereitgestellt werden. Mit der Registrierung bei oder der Nutzung unserer Dienste erklären Sie sich mit diesen Bedingungen einverstanden.</p>
<h2 class="h4">2. Nutzung von Audiodateien</h2>
<p>Die in dieser App verfügbaren Audiodateien dürfen ausschließlich auf der Webseite von Mindboost genutzt werden. Jede Verwendung dieser Audiodateien außerhalb dieser Domain ist strengstens untersagt.</p>
<h2 class="h4">3. Nutzung des Algorithmus</h2>
<p>Der in dieser App enthaltene Algorithmus darf nur von Nutzern mit einer aktiven und gültigen Lizenz verwendet werden. Die Lizenz muss während der gesamten Nutzungsdauer des Algorithmus gültig sein. Eine Nutzung ohne gültige Lizenz verstößt gegen unsere Nutzungsbedingungen.</p>
<h2 class="h4">4. Trial-Version</h2>
<p>Jede natürliche oder juristische Person darf die Trial-Version dieser App nur einmal nutzen. Eine erneute Nutzung der Trial-Version ohne vorherige Absprache mit uns ist nicht erlaubt.</p>
<h2 class="h4">5. Account-Registrierung und Verantwortlichkeit</h2>
<p>Die Nutzer sind verantwortlich für das sichere Aufbewahren ihrer Passwörter und für alle Aktivitäten, die unter ihrem Account stattfinden. Jeder Verdacht auf unbefugte Nutzung des Accounts sollte unverzüglich an unseren Kundenservice gemeldet werden.</p>
<h2 class="h4">6. Zahlungsbedingungen</h2>
<p>Für bestimmte Teile unserer Dienste kann eine Gebühr anfallen. Alle Gebühren sind klar angegeben und sind zum Zeitpunkt der Fälligkeit zahlbar. Zahlungsverzögerungen können zur Deaktivierung des Zugangs führen.</p>
<h2 class="h4">7. Datenschutz</h2>
<p>Ihre persönlichen Daten werden gemäß unserer Datenschutzrichtlinie behandelt, die Sie auf unserer Webseite einsehen können. Mit der Nutzung unserer Dienste stimmen Sie der Verarbeitung Ihrer Daten zu.</p>
<h2 class="h4">8. Änderungen der Dienste</h2>
<p>Wir behalten uns das Recht vor, jederzeit Änderungen an unseren Diensten vorzunehmen oder Dienste einzustellen. Wir werden uns bemühen, die Nutzer rechtzeitig über solche Änderungen zu informieren.</p>
<h2 class="h4">9. Kündigung</h2>
<p>Die Nutzer können ihr Abonnement jederzeit kündigen. Mindboost behält sich das Recht vor, den Service eines Nutzers zu kündigen oder zu suspendieren, wenn dieser gegen die Nutzungsbedingungen verstößt.</p>
<h2 class="h4">10. Haftungsbeschränkung</h2>
<p>Mindboost haftet nicht für indirekte, zufällige, spezielle oder folgeschädigte Schäden, die aus der Nutzung oder Unfähigkeit zur Nutzung der Dienste entstehen.</p>
<h2 class="h4">11. Geltendes Recht und Gerichtsstand</h2>
<p>Diese Nutzungsbedingungen unterliegen dem Recht des Landes, in dem Mindboost seinen Sitz hat, und alle Streitigkeiten werden ausschließlich vor den dortigen Gerichten verhandelt. </p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return { t, localePath }
}
}
</script>

View File

@@ -0,0 +1,298 @@
<template>
<div>
<div class="row justify-content-center">
<div class="col-12 col-lg-9 col-md-11 col-sm-12 col-xl-8">
<h1 class="h3 fw-bold text-center py-3">
Timer Settings
</h1>
<div class="timer__settings">
<!-- Timer Settings -->
<v-list class="px-2 px-md-16">
<!-- Work Time -->
<div class="slider">
<input
id="gain-control"
v-model="timer().settings.timer.work"
type="range"
min="5"
max="60"
step="5"
data-toggle="tooltip"
data-placement="top"
:title="tooltipTitle"
@wheel.prevent="changeVolumeOnWheel"
>
<span
class="slider-progress-bar"
:style="{ width: `${volume * 100}%` }"
/>
</div>
<!-- <v-list-item title="Work Sessions">
<v-slider
v-model="settings.timer.work"
min="5"
max="60"
step="5"
show-ticks
thumb-label
:color="timer().getCurrentColor"
class="w-100"
:ticks="sliderTicks.workTimer"
:rules="rules.workRules"
>
<template #append>
<v-text-field
v-model="settings.timer.work"
hide-details
single-line
density="compact"
type="number"
style="width: 70px"
min="5"
max="60"
/>
</template>
</v-slider> -->
<!-- </v-list-item> -->
<!-- Breaks Sessions -->
<v-list-item
v-for="value, i in ['short-break', 'long-break']"
:key="i"
:title="timer().settings.timer[value].text"
>
<v-slider
v-model="settings.timer[value]"
min="0"
max="60"
step="5"
show-ticks
thumb-label
:color="timer().getCurrentColor"
class="w-100"
:ticks="sliderTicks.breakTimers"
:rules="rules.breakRules"
>
<template #append>
<v-text-field
v-model="settings.timer[value]"
hide-details
single-line
density="compact"
type="number"
style="width: 70px"
min="0"
max="60"
/>
</template>
</v-slider>
</v-list-item>
<v-list-item
title="Max Sessions"
subtitle="The number of work sessions before a long break"
>
<v-slider
v-model="settings.maxSessions"
min="1"
max="10"
step="1"
show-ticks
thumb-label
:color="timer().getCurrentColor"
class="w-100"
:ticks="sliderTicks.maxSessions"
:rules="rules.maxSessionsRules"
>
<template #append>
<v-text-field
v-model="settings.maxSessions"
hide-details
single-line
density="compact"
type="number"
style="width: 70px"
min="1"
max="10"
/>
</template>
</v-slider>
</v-list-item>
<v-list-item>
<v-checkbox
v-model="timer().settings.autoStart"
:color="timer().getCurrentColor"
label="Auto Start Sessions"
true-icon="mdi-timer-play-outline"
false-icon="mdi-timer-play-outline"
/>
</v-list-item>
<v-divider />
<!-- General Settings -->
<v-list-subheader>General Settings</v-list-subheader>
<v-list-item>
<v-checkbox
v-model="settings.notificationsEnabled"
:color="timer().getCurrentColor"
label="Notifications"
true-icon="mdi-bell"
false-icon="mdi-bell-off"
/>
</v-list-item>
<v-list-item>
<v-checkbox
v-model="timer().playSessionEndSound"
:color="timer().getCurrentColor"
label="Sounds"
true-icon="mdi-volume-high"
false-icon="mdi-volume-off"
/>
</v-list-item>
<!-- <v-list-item>
<v-checkbox
v-model="app().showThemeToggle"
:color="timer().getCurrentColor"
label="Show Theme Toggle"
true-icon="mdi-theme-light-dark"
false-icon="mdi-theme-light-dark"
/>
</v-list-item> -->
</v-list>
</div>
</div>
</div>
</div>
</template>
<script>
import { useTimerStore as timer } from '~~/stores/timer'
export default {
name: 'TimerSettings',
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
const sliderTicks = reactive({
workTimer: {
5: '5',
60: '60'
},
breakTimers: {
0: '0',
60: '60'
},
maxSessions: {
1: '1',
10: '10'
}
})
// only the settings that need validation
const settings = reactive({
timer: {
work: 25,
'short-break': 5,
'long-break': 15
},
maxSessions: 4,
notificationsEnabled: false
})
const state = reactive({
dialog: false,
snackbar: false
})
// rules for validating inputs
const rules = reactive({
workRules: [
(value) => {
if (value >= 5 && value <= 60) { return true }
if (value <= 0) { return "Are you kidding me? if you don't wanna work then just go find something better to do than playing with my settings." }
if (value < 5) { return 'Do you want to work for less than 5 mins?! You can do better.' }
return "Don't be so hard on yourself, work for an hour or less before taking a break."
}
],
breakRules: [
(value) => {
if (value >= 0 && value <= 60) { return true }
if (value < 0) { return 'How tf would you take a negative break time you moron?' }
return "That's too much time for a break, stop wasting your time."
}
],
maxSessionsRules: [
(value) => {
if (value >= 1) { return true }
return "Max sessions can't be less than one!"
}
]
})
// save settings to localstorage
const saveSettings = () => {
saveNotification()
// validate user inputs before saving
if (rules.workRules[0](settings.timer.work) === true) {
timer().settings.timer.work.time = settings.timer.work
}
if (rules.breakRules[0](settings.timer['short-break']) === true) {
timer().settings.timer['short-break'].time = settings.timer['short-break']
}
if (rules.breakRules[0](settings.timer['long-break']) === true) {
timer().settings.timer['long-break'].time = settings.timer['long-break']
}
if (rules.maxSessionsRules[0](settings.maxSessions) === true) {
timer().settings.maxSessions = settings.maxSessions
}
storeSettings()
state.dialog = false
state.snackbar = true
}
return { t, localePath }
}
}
// watch(
// () => state.dialog,
// (dialog) => {
// if (!dialog) { // if dialog is closed
// saveSettings()
// } else {
// // retrieve settings from state when dialog is opened
// // settings.notificationsEnabled = timer().showNotification
// settings.maxSessions = timer().settings.maxSessions
// for (const session of Object.keys(settings.timer)) {
// settings.timer[session] = timer().settings.timer[session].time
// }
// }
// }
// )
// get notification permission from the browser
// const saveNotification = () => {
// const { isSupported, show } = useWebNotification({
// title: 'Notifications enabled.'
// })
// if (isSupported.value) {
// if (!timer().showNotification && settings.notificationsEnabled) { show() }
// } else {
// useNuxtApp().$logger.error('Your browser does not support notifications.')
// }
// timer().showNotification = settings.notificationsEnabled
// }
</script>
<style>
.dialog-bottom-transition-enter-active,
.dialog-bottom-transition-leave-active {
transition: transform .1s ease-in-out;
}
.v-footer {
row-gap: 0.4rem;
}
</style>

106
pages/success.vue Normal file
View File

@@ -0,0 +1,106 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="video/bg-video.mp4"
poster="images/posters/poster1.png"
style=" height: 100vh;"
>
<home-bar :title="title" />
<div class="container pt-4">
<div class="row justify-content-center px-1 ">
<div class="col-12 col-sm-11 col-md-10 p-3 py-4 col-lg-7 " style="border-radius: 15px;background-color: rgb(233, 192, 70);">
<div class="success-page">
<div class="success-icon">
<i class="fas fa-check-circle" />
</div>
<div class="success-message">
{{ t("Successfully subscribed") }}
</div>
</div>
</div>
</div>
</div>
<BootomBar style="position: fixed;bottom: 10px;" />
</video-background>
<!-- <video muted id="myVideo" autoplay loop>-->
<!-- <source src="~/assets/video/bg-video.mp4" type="video/mp4">-->
<!-- <source src="~/assets/video/bg-video.ogg" type="video/ogg">-->
<!-- Your browser does not support HTML5 video.-->
<!-- </video>-->
</div>
</div>
</div>
</div>
</template>
<script>
import HomeBar from '../components/homebar'
export default {
components: { HomeBar },
setup () {
const { t } = useI18n()
const localePath = useLocalePath()
return {
t,
localePath
}
},
data () {
return {
title: 'Subscribed Successfully'
}
},
methods: {
handleAnimation: function (anim) {
this.anim = anim
}
}
}
</script>
<style>
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.success-page {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.success-icon {
font-size: 64px;
color: white;
margin-bottom: 32px;
}
.success-message {
font-size: 24px;
font-weight: bold;
color: white;
text-align: center;
}
</style>

118
pages/viz/index.vue Normal file
View File

@@ -0,0 +1,118 @@
<template>
<div>
<div class="container-fluid">
<div class="row">
<div class="col-12 px-0 mx-0">
<video-background
src="/video/Tropics.mp4"
preload="auto"
poster="/images/posters/poster.png"
style=" height: 100vh;"
>
<h2>Line Chart</h2>
<LineChart />
<h2>Time Chart</h2>
<TimeDifference />
</video-background>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">
// import DeepCareAnalyzer from '../components/viz/DeepCareAnalyzer'
import LineChart from '~/components/viz/LineChart.vue'
import TimeDifference from '~/components/viz/TimeDifference.vue'
export default {
components: { LineChart, TimeDifference },
setup () {
definePageMeta({
middleware: 'auth'
})
const { t } = useI18n()
const localePath = useLocalePath()
const lineChart = ref(null)
return {
t,
localePath,
lineChart
}
},
computed: {
// Berechnete Eigenschaft für die dynamische Übersetzung des Titels
title () {
return 'Deep Care Viz'
}
},
methods: {
}
}
</script>
<style>
#myVideo{
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
padding: 0;
margin: 0;
z-index: -99;
}
.adaptive{
position: fixed;
bottom: 0;
text-align: center;
width: 100%;
}
.spotifyplayer{
display: flex;
flex-direction: column; /* Ensures vertical centering if needed */
align-items: center; /* Horizontally centers content in a flex-direction: column */
justify-content: center;
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(128, 128, 128, 0.5); /* Grey color with opacity */
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 14px;
pointer-events: none; /* Allows interaction with elements under the overlay */
}
.tooltip {
position: relative;
display: inline-block;
}
.tooltip .tooltiptext {
visibility: hidden;
width: 120px;
background-color: black;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 1;
bottom: 125%;
left: 50%;
margin-left: -60px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
}
</style>

1012
pages/viz_band.vue Normal file

File diff suppressed because it is too large Load Diff