dev test
This commit is contained in:
parent
3e302c5449
commit
f929763892
@ -110,6 +110,14 @@ body
|
|||||||
width: 80%
|
width: 80%
|
||||||
margin: auto 10%
|
margin: auto 10%
|
||||||
|
|
||||||
|
.container-15
|
||||||
|
width: 70%
|
||||||
|
margin: auto 15%
|
||||||
|
|
||||||
|
.container-20
|
||||||
|
width: 60%
|
||||||
|
margin: auto 20%
|
||||||
|
|
||||||
.fade-enter-active, .fade-leave-active
|
.fade-enter-active, .fade-leave-active
|
||||||
transition: opacity 1.2s ease
|
transition: opacity 1.2s ease
|
||||||
|
|
||||||
@ -155,7 +163,22 @@ body
|
|||||||
transform: translateY(200px)
|
transform: translateY(200px)
|
||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
|
/* Welle oben */
|
||||||
|
.sectionWave.wave-top
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
width: 100%
|
||||||
|
z-index: 1
|
||||||
|
transform: scaleY(1) scaleX(-1)
|
||||||
|
top: -2px
|
||||||
|
/* Welle unten */
|
||||||
|
.sectionWave.wave-bottom
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
width: 100%
|
||||||
|
z-index: 1
|
||||||
|
transform: scaleY(-1)
|
||||||
|
bottom: -2px
|
||||||
|
|
||||||
|
|
||||||
// +++++++++++++++++
|
// +++++++++++++++++
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<nav v-if="breadcrumbs && breadcrumbs.length" class="breadcrumbs" aria-label="Breadcrumb">
|
<nav v-if="breadcrumbs.length" class="breadcrumbs" aria-label="Breadcrumb">
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>
|
||||||
<router-link to="/" aria-label="Startseite">
|
<router-link to="/" aria-label="Startseite">
|
||||||
@ -10,58 +10,98 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</li>
|
</li>
|
||||||
<li v-for="(crumb, index) in breadcrumbs" :key="index">
|
<li v-for="(crumb, index) in breadcrumbs" :key="index">
|
||||||
<router-link v-if="index < breadcrumbs.length - 1" :to="crumb.to">
|
<router-link v-if="index < breadcrumbs.length - 1" :to="crumb.to" :title="crumb.labelFull">
|
||||||
{{ crumb.label }}
|
{{ crumb.label }}
|
||||||
</router-link>
|
</router-link>
|
||||||
<span v-else>{{ crumb.label }}</span>
|
<span v-else :title="crumb.labelFull">{{ crumb.label }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { i18nPages } from '~/i18n/i18n-pages'
|
||||||
|
|
||||||
export default {
|
interface Breadcrumb {
|
||||||
name: 'Breadcrumb',
|
label: string
|
||||||
computed: {
|
labelFull: string
|
||||||
breadcrumbs() {
|
to: string
|
||||||
const locale = this.$i18n.locale // aktives Sprachpräfix (z. B. "en", "de", etc.)
|
}
|
||||||
const pathWithoutLang = this.$route.path.replace(`/${locale}`, '') // Sprachprefix entfernen
|
|
||||||
|
function formatLabel(segment: string): { label: string; labelFull: string } {
|
||||||
|
const labelFull = segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
|
||||||
|
let label = labelFull
|
||||||
|
if (label.length > 25) {
|
||||||
|
label = label.slice(0, 22) + '...'
|
||||||
|
}
|
||||||
|
return { label, labelFull }
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildUrl(loc: string, path: string): string {
|
||||||
|
if (loc === 'de') {
|
||||||
|
return path.startsWith('/') ? path : '/' + path
|
||||||
|
}
|
||||||
|
return `/${loc}${path.startsWith('/') ? path : '/' + path}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
const { locale, t } = useI18n()
|
||||||
|
|
||||||
|
const breadcrumbs = computed<Breadcrumb[]>(() => {
|
||||||
|
const loc = locale.value
|
||||||
|
const pathWithoutLang = route.path.replace(`/${loc}`, '')
|
||||||
|
const segments = pathWithoutLang.split('/').filter(Boolean)
|
||||||
|
|
||||||
|
if (segments.length === 2 && segments[0] === 'projekt') {
|
||||||
|
const referencesPath = i18nPages.references?.[loc] || '/references'
|
||||||
|
|
||||||
|
// Übersetzung für "references" holen, Fallback zu englisch falls nicht vorhanden
|
||||||
|
const referencesLabel = t('references') || 'References'
|
||||||
|
|
||||||
|
const first = {
|
||||||
|
label: referencesLabel,
|
||||||
|
labelFull: referencesLabel,
|
||||||
|
to: buildUrl(loc, referencesPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { label, labelFull } = formatLabel(segments[1])
|
||||||
|
const second = {
|
||||||
|
label,
|
||||||
|
labelFull,
|
||||||
|
to: route.path
|
||||||
|
}
|
||||||
|
|
||||||
|
return [first, second]
|
||||||
|
}
|
||||||
|
|
||||||
const pathArray = pathWithoutLang.split('/').filter(p => p)
|
|
||||||
let path = ''
|
let path = ''
|
||||||
return pathArray.map(segment => {
|
return segments.map(segment => {
|
||||||
path += '/' + segment
|
path += '/' + segment
|
||||||
|
const { label, labelFull } = formatLabel(segment)
|
||||||
return {
|
return {
|
||||||
label: this.formatLabel(segment),
|
label,
|
||||||
to: `/${locale}${path}` // Sprachprefix im Link wieder einfügen
|
labelFull,
|
||||||
|
to: buildUrl(loc, path)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formatLabel(segment) {
|
|
||||||
return segment.charAt(0).toUpperCase() + segment.slice(1).replace(/-/g, ' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass" scoped>
|
||||||
.breadcrumbs
|
.breadcrumbs
|
||||||
position: fixed
|
position: fixed
|
||||||
top: 22vh
|
top: 22vh
|
||||||
left: 0
|
left: 0
|
||||||
text-align: left
|
text-align: left
|
||||||
padding: 1rem .25rem 1rem .5rem
|
padding: 1rem .25rem 1rem .5rem
|
||||||
//min-width: 200px
|
|
||||||
background-color: rgba(white, .98)
|
background-color: rgba(white, .98)
|
||||||
border: 1px solid darken($lightgrey, 5%)
|
border: 1px solid darken($lightgrey, 5%)
|
||||||
writing-mode: vertical-rl
|
writing-mode: vertical-rl
|
||||||
transform: rotate(180deg)
|
transform: rotate(180deg)
|
||||||
//border-bottom-left-radius: .65rem
|
|
||||||
border-top-left-radius: .8rem
|
border-top-left-radius: .8rem
|
||||||
border-bottom-left-radius: .8rem
|
border-bottom-left-radius: .8rem
|
||||||
text-transform: uppercase
|
text-transform: uppercase
|
||||||
@ -115,4 +155,3 @@
|
|||||||
span
|
span
|
||||||
font-weight: bold
|
font-weight: bold
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
33
components/CallToActionBox.vue
Normal file
33
components/CallToActionBox.vue
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<template>
|
||||||
|
<div class="ctaBox">
|
||||||
|
<h3>{{ headline }}</h3>
|
||||||
|
<p>{{ text }}</p>
|
||||||
|
<button class="pinkBtn mt-1" @click.prevent="toggleContactBubble" role="button">
|
||||||
|
{{ buttonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { useMainStore } from '@/stores/main'
|
||||||
|
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
|
||||||
|
const toggleContactBubble = () => {
|
||||||
|
mainStore.toggleContactBubble()
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
headline: { type: String, required: true },
|
||||||
|
text: { type: String, required: true },
|
||||||
|
buttonText: { type: String, required: true }
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="sass">
|
||||||
|
.ctaBox
|
||||||
|
margin: 5vh 10%
|
||||||
|
h3
|
||||||
|
font-size: 1.6rem
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -38,7 +38,7 @@
|
|||||||
afterImage: { type: String, required: true }
|
afterImage: { type: String, required: true }
|
||||||
})
|
})
|
||||||
|
|
||||||
const sliderValue = ref(60) // Startposition in der Mitte
|
const sliderValue = ref(80) // Startposition in der Mitte
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
@ -92,7 +92,7 @@
|
|||||||
width: 20px // Breite des Schiebereglers
|
width: 20px // Breite des Schiebereglers
|
||||||
height: 20px // Höhe des Schiebereglers
|
height: 20px // Höhe des Schiebereglers
|
||||||
border-radius: 50%
|
border-radius: 50%
|
||||||
background: $pink
|
background: $primaryColor
|
||||||
border: 1px solid #fff
|
border: 1px solid #fff
|
||||||
cursor: pointer
|
cursor: pointer
|
||||||
z-index: 15
|
z-index: 15
|
||||||
|
|||||||
@ -21,7 +21,7 @@
|
|||||||
<h2 class="pt-4 pb-3">{{ title }}</h2>
|
<h2 class="pt-4 pb-3">{{ title }}</h2>
|
||||||
|
|
||||||
<div class="marquee">
|
<div class="marquee">
|
||||||
<div class="marquee-track">
|
<div class="marquee-track" :style="`animation-duration: ${speed}s`">
|
||||||
<ul class="marquee-list">
|
<ul class="marquee-list">
|
||||||
<li
|
<li
|
||||||
v-for="(item, index) in items"
|
v-for="(item, index) in items"
|
||||||
@ -31,10 +31,11 @@
|
|||||||
<NuxtLink v-if="item.link" :to="item.link" class="custLogoLink">
|
<NuxtLink v-if="item.link" :to="item.link" class="custLogoLink">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
provider="strapi"
|
provider="strapi"
|
||||||
:src="item.logo.url"
|
:src="item.image.url"
|
||||||
:alt="item.logo.alternativeText || 'Logo'"
|
:alt="item.image.alternativeText || item.text || 'Image'"
|
||||||
|
:title="item.text"
|
||||||
width="250"
|
width="250"
|
||||||
height="50"
|
:height="logoHeight"
|
||||||
format="webp"
|
format="webp"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="custLogo"
|
class="custLogo"
|
||||||
@ -43,13 +44,15 @@
|
|||||||
<NuxtImg
|
<NuxtImg
|
||||||
v-else
|
v-else
|
||||||
provider="strapi"
|
provider="strapi"
|
||||||
:src="item.logo.url"
|
:src="item.image.url"
|
||||||
:alt="item.logo.alternativeText || 'Logo'"
|
:alt="item.image.alternativeText || item.text || 'Image'"
|
||||||
|
:title="item.text"
|
||||||
width="250"
|
width="250"
|
||||||
height="50"
|
height="50"
|
||||||
format="webp"
|
format="webp"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
class="custLogo"
|
:class="['custLogo', { greyscale: greyscale }]"
|
||||||
|
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
@ -86,6 +89,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
const runtimeConfig = useRuntimeConfig()
|
const runtimeConfig = useRuntimeConfig()
|
||||||
const cmsUrl = computed(() => runtimeConfig.public.cmsBaseUrl)
|
const cmsUrl = computed(() => runtimeConfig.public.cmsBaseUrl)
|
||||||
@ -94,12 +98,26 @@ const props = defineProps({
|
|||||||
items: {
|
items: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
|
default: () => [],
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
logoHeight: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 50 // bisheriger Wert in Pixel
|
||||||
|
},
|
||||||
|
speed: {
|
||||||
|
type: [String, Number],
|
||||||
|
default: 20 // bisheriger Wert in Sekunden
|
||||||
|
},
|
||||||
|
greyscale: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass" scoped>
|
<style lang="sass" scoped>
|
||||||
@ -144,9 +162,12 @@ const props = defineProps({
|
|||||||
.custLogo
|
.custLogo
|
||||||
width: auto
|
width: auto
|
||||||
max-width: 250px
|
max-width: 250px
|
||||||
height: 50px
|
transition: filter 0.3s ease, transform .5s ease
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.1)
|
||||||
|
|
||||||
|
.greyscale
|
||||||
filter: grayscale(100%)
|
filter: grayscale(100%)
|
||||||
transition: filter 0.3s ease
|
|
||||||
&:hover
|
&:hover
|
||||||
filter: grayscale(0)
|
filter: grayscale(0)
|
||||||
|
|
||||||
|
|||||||
@ -24,7 +24,7 @@ const screenWidth = computed(() => mainStore.screenWidth)
|
|||||||
height: auto
|
height: auto
|
||||||
z-index: 12
|
z-index: 12
|
||||||
&.mobile
|
&.mobile
|
||||||
top: 45vh
|
top: 20vh
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
67
components/SideBarNaviSlider.vue
Normal file
67
components/SideBarNaviSlider.vue
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
<template>
|
||||||
|
<div class="sideBarNaviSlider"
|
||||||
|
:class="{ 'slide-in': showSideBar }"
|
||||||
|
@click="navigate">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted, onUnmounted } from 'vue'
|
||||||
|
import { navigateTo } from '#app'
|
||||||
|
|
||||||
|
// Props für dynamischen Inhalt und Link
|
||||||
|
const props = defineProps({
|
||||||
|
link: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Animation für das Einfahren
|
||||||
|
const showSideBar = ref(false)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
setTimeout(() => {
|
||||||
|
showSideBar.value = true
|
||||||
|
}, 100) // Verzögerung für weiche Animation
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
showSideBar.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Funktion zur Navigation in Nuxt 3
|
||||||
|
const navigate = () => {
|
||||||
|
if (props.link) {
|
||||||
|
navigateTo(props.link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.sideBarNaviSlider
|
||||||
|
position: fixed
|
||||||
|
background-color: rgba($yellow, .8)
|
||||||
|
color: white
|
||||||
|
text-transform: uppercase
|
||||||
|
font-size: 1rem !important
|
||||||
|
letter-spacing: .05rem
|
||||||
|
top: 40vh
|
||||||
|
right: -80px // Startposition außerhalb des Bildschirms
|
||||||
|
padding: 1.2rem .8rem 1.6rem .8rem
|
||||||
|
writing-mode: vertical-lr
|
||||||
|
transform: rotate(180deg)
|
||||||
|
border-bottom-right-radius: 1rem
|
||||||
|
border-top-right-radius: 1rem
|
||||||
|
cursor: pointer
|
||||||
|
transition: right 0.6s ease-out, transform .8s
|
||||||
|
z-index: 10
|
||||||
|
|
||||||
|
&.slide-in
|
||||||
|
right: 0 // Fährt an die finale Position
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
transform: rotate(180deg) scale(1.1)
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ footer
|
|||||||
position: relative
|
position: relative
|
||||||
width: 100vw
|
width: 100vw
|
||||||
color: white
|
color: white
|
||||||
z-index: 10
|
z-index: 9
|
||||||
height: auto
|
height: auto
|
||||||
min-height: 120px
|
min-height: 120px
|
||||||
margin-top: 100px
|
margin-top: 100px
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// composables/useI18nPages.ts
|
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { i18nPages } from '@/i18n/i18n-pages'
|
import { i18nPages } from '@/i18n/i18n-pages'
|
||||||
|
|
||||||
@ -6,10 +5,23 @@ export function useI18nPages () {
|
|||||||
const { locale } = useI18n()
|
const { locale } = useI18n()
|
||||||
|
|
||||||
const getRoute = (key: keyof typeof i18nPages) => {
|
const getRoute = (key: keyof typeof i18nPages) => {
|
||||||
return i18nPages[key][locale.value]
|
const entry = i18nPages[key]
|
||||||
|
if (!entry) return ''
|
||||||
|
|
||||||
|
if ('paths' in entry) {
|
||||||
|
return entry.paths[locale.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry[locale.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
const getProjectLink = (slug: string) => {
|
||||||
|
const path = getRoute('projekt___link')
|
||||||
|
return path.replace(':link', slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getRoute
|
getRoute,
|
||||||
|
getProjectLink
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,16 @@ export const i18nPages = {
|
|||||||
es: '/aviso-legal',
|
es: '/aviso-legal',
|
||||||
tr: '/künye'
|
tr: '/künye'
|
||||||
},
|
},
|
||||||
|
'projekt___link': {
|
||||||
|
paths: {
|
||||||
|
de: '/projekt/:link',
|
||||||
|
en: '/projekt/:link',
|
||||||
|
fr: '/projekt/:link',
|
||||||
|
it: '/projekt/:link',
|
||||||
|
es: '/projekt/:link',
|
||||||
|
tr: '/projekt/:link'
|
||||||
|
}
|
||||||
|
},
|
||||||
privacy: {
|
privacy: {
|
||||||
de: '/datenschutz',
|
de: '/datenschutz',
|
||||||
en: '/privacy',
|
en: '/privacy',
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
"services": "Leistungen",
|
"services": "Leistungen",
|
||||||
"contact": "Kontakt",
|
"contact": "Kontakt",
|
||||||
"references": "Referenzen",
|
"references": "Referenzen",
|
||||||
|
"referenceoverview": "Referenzübersicht",
|
||||||
"imprint": "Impressum",
|
"imprint": "Impressum",
|
||||||
"privacy": "Datenschutz",
|
"privacy": "Datenschutz",
|
||||||
"privacyPolicy": "Datenschutzerklärung",
|
"privacyPolicy": "Datenschutzerklärung",
|
||||||
@ -130,8 +131,48 @@
|
|||||||
"title": "Du bist Grafikdesigner oder Mediengestalter?",
|
"title": "Du bist Grafikdesigner oder Mediengestalter?",
|
||||||
"button": "Hier unser Angebot für Dich"
|
"button": "Hier unser Angebot für Dich"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"services": {
|
||||||
|
"hero": {
|
||||||
|
"ariaLabel": "Einleitungsbereich über digitale Lösungen",
|
||||||
|
"imageAlt": "Illustration von Programmierung am Ammersee",
|
||||||
|
"headline1": "High-Performance-Webseiten",
|
||||||
|
"headline2": "mit moderner Headless-Architektur",
|
||||||
|
"headline3": "Schnell, effizient und leistungsstark!"
|
||||||
|
},
|
||||||
|
"explain": {
|
||||||
|
"ariaLabel": "Warum unsere Webseiten mehr können als nur gut aussehen",
|
||||||
|
"headline1": "Wir entwickeln Webseiten für Mensch und Maschine",
|
||||||
|
"headline2": "Design das Menschen begeistert mit Struktur die Maschinen verstehen",
|
||||||
|
"paragraph": "Damit eine Webseite heute erfolgreich ist, muss sie beides leisten: Emotionen wecken und von Algorithmen verstanden werden. Mit klarer Struktur, sauberer Semantik und starker Performance schaffen wir die Basis für Sichtbarkeit und nachhaltiges Wachstum.",
|
||||||
|
"bullet1": "Schnelle Ladezeiten durch moderne Headless-Technologien",
|
||||||
|
"bullet2": "Optimiert für Google, Bing & KI durch saubere Semantik",
|
||||||
|
"bullet3": "Barrierefrei entwickelt, damit zugänglich für alle Nutzer",
|
||||||
|
"bullet4": "Einfach erweiterbar dank modularer Architektur",
|
||||||
|
"bullet5": "SEO-freundlich mit klarer Struktur und strukturierten Daten"
|
||||||
|
|
||||||
|
},
|
||||||
|
"marquee": {
|
||||||
|
"title": "Erleben Sie unsere Projekte live!"
|
||||||
|
},
|
||||||
|
"ctaBox": {
|
||||||
|
"headline": "Sie wollen mehr Speed und Leistung für Ihr Business?",
|
||||||
|
"text": "Wir bringen Ihre Website auf das nächste Level! Schnell, zuverlässig und individuell.",
|
||||||
|
"button": "Sprechen Sie mit uns!"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"references": {
|
||||||
|
"hero": {
|
||||||
|
"h1": "Individuelle Webseiten, die überzeugen",
|
||||||
|
"h2": "Lassen Sie sich von unseren erfolgreichen Webprojekten inspirieren!",
|
||||||
|
"p": "Jede Website, die wir entwickeln, ist einzigartig – maßgeschneidert, funktional und wirkungsvoll. Unsere Referenzen zeigen, wie wir modernes Webdesign, durchdachte Entwicklung und gute Performance verbinden, um digitale Lösungen zu schaffen, die nicht nur gut aussehen, sondern auch Ergebnisse liefern."
|
||||||
|
},
|
||||||
|
"ctaBox": {
|
||||||
|
"headline": "Bereit für Ihr eigenes Webprojekt?",
|
||||||
|
"text": "Lassen Sie uns gemeinsam Ihre individuelle Website gestalten – perfekt abgestimmt auf Ihre Bedürfnisse und Ziele.",
|
||||||
|
"button": "Jetzt unverbindliches Angebot anfordern!"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,30 +9,30 @@
|
|||||||
"privacyPolicy": "Privacy Policy",
|
"privacyPolicy": "Privacy Policy",
|
||||||
"termsOfService": "Terms of Service",
|
"termsOfService": "Terms of Service",
|
||||||
"terms": "Terms",
|
"terms": "Terms",
|
||||||
"faq": "FAQ",
|
"faq": "Frequently Asked Questions",
|
||||||
"magazin": "Insights",
|
"magazin": "Knowledge",
|
||||||
"accessability": "Accessibility",
|
"accessability": "Accessibility",
|
||||||
"accessibilitySettings": "Accessibility Settings",
|
"accessibilitySettings": "Accessibility Settings",
|
||||||
"changeFontSize": "Increase text size",
|
"changeFontSize": "Increase Text Size",
|
||||||
"greyscale": "Greyscale",
|
"greyscale": "Grayscale",
|
||||||
"increaseContrast": "Increase contrast",
|
"increaseContrast": "Increase Contrast",
|
||||||
"borderFocus": "Enable focus highlight",
|
"borderFocus": "Enable Focus",
|
||||||
"hideImages": "Hide images",
|
"hideImages": "Hide Images",
|
||||||
"showLinks": "Highlight links",
|
"showLinks": "Highlight Links",
|
||||||
"infoAccessibility": "Information about the accessibility of our site",
|
"infoAccessibility": "Information about accessibility on our site",
|
||||||
"importantLinks": "Important Links",
|
"importantLinks": "Important Links",
|
||||||
"contactForm": {
|
"contactForm": {
|
||||||
"yourcontact2us": "Get in touch with us!",
|
"yourcontact2us": "Your contact to us!",
|
||||||
"ourOffice": "Our Office Address",
|
"ourOffice": "Our office address",
|
||||||
"yourcontactperson": "Your Contact Person",
|
"yourcontactperson": "Your contact person",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"phone": "Phone",
|
"phone": "Phone",
|
||||||
"message": "Message",
|
"message": "Message",
|
||||||
"company": "Company",
|
"company": "Company",
|
||||||
"sendMessage": "Send Message",
|
"sendMessage": "Send message",
|
||||||
"privacyInfotextBeforeLink": "By submitting the form, you agree to the storage of your data on our server for the purpose of contacting you.",
|
"privacyInfotextBeforeLink": "By submitting the form, you agree to the storage of your data on our server for the purpose of contacting you.",
|
||||||
"privacyInfotextLinkText": "Privacy Policy",
|
"privacyInfotextLinkText": "Information about data protection",
|
||||||
"validation": {
|
"validation": {
|
||||||
"nameRequired": "Name is a required field.",
|
"nameRequired": "Name is a required field.",
|
||||||
"emailOrPhoneRequired": "Please enter either an email address or a phone number.",
|
"emailOrPhoneRequired": "Please enter either an email address or a phone number.",
|
||||||
@ -40,63 +40,96 @@
|
|||||||
"invalidPhone": "Please enter a valid phone number."
|
"invalidPhone": "Please enter a valid phone number."
|
||||||
},
|
},
|
||||||
"successMessage": "Your message has been sent successfully.",
|
"successMessage": "Your message has been sent successfully.",
|
||||||
"errorMessage": "There is currently a problem with the internet connection!",
|
"errorMessage": "Unfortunately, there is currently an error with the internet connection!",
|
||||||
"confirmation": {
|
"confirmation": {
|
||||||
"thx": "Thank you for your message!",
|
"thx": "Thank you for your message!",
|
||||||
"info": "We will get back to you shortly...",
|
"info": "We will get back to you promptly...",
|
||||||
"salutation": "Your digimedialoop Team"
|
"salutation": "Your digimedialoop team"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"faqBox": {
|
||||||
|
"questions": "Questions?",
|
||||||
|
"faqsDefault": "Frequently Asked Questions (FAQs)",
|
||||||
|
"btnDefault": "Feel free to contact us!"
|
||||||
|
},
|
||||||
|
"pages": {
|
||||||
"home": {
|
"home": {
|
||||||
"heroBox": {
|
"heroBox": {
|
||||||
"h1": "Your agency for custom web design and professional web development",
|
"h1": "Your agency for individual web design and professional web development",
|
||||||
"h2": "Modular websites using the latest technologies",
|
"h2": "Modular websites with the latest technologies",
|
||||||
"h3": "Making your website fast, efficient and future-proof!"
|
"h3": "Highest performance – fast, efficient, and future-proof!"
|
||||||
},
|
},
|
||||||
"solution": {
|
"solution": {
|
||||||
"title": "How your website becomes a real business tool",
|
"title": "Performance, AI Compatibility & Accessibility",
|
||||||
"teaser": "We develop custom websites with JAMstack technology tailored to your business, serving as a powerful marketing and sales tool for your success.",
|
"teaser": "We develop tailor-made websites based on modern JAMstack technology, perfectly tailored to your requirements.",
|
||||||
"text": "By clearly separating content and technology and using a headless content management system, we create low-maintenance, SEO-optimized solutions that are not only scalable long-term but also make work easier for your marketing team: content can be managed without technical barriers, new features integrated flexibly – without plugin chaos or interfering with the live system.",
|
"text": "By clearly separating content and technology, using a headless content management system like Strapi, maintenance-friendly, SEO-optimized solutions are created that are not only scalable in the long term but also make work easier for your team. Content can be maintained without technical hurdles, and new features integrated flexibly – all without plugin chaos or interfering with the live system. Thanks to clean semantic structure, our solutions are also optimally prepared for AI-supported search systems and allow easy integration into AI-powered operator workflows.",
|
||||||
"buttonText": "Learn more about Headless CMS"
|
"buttonText": "Learn more about Headless CMS"
|
||||||
},
|
},
|
||||||
"invitation": {
|
"invitation": {
|
||||||
"title": "Is your website ready for the future?",
|
"title": "Is your website ready for the future?",
|
||||||
"teaser": "We'll show you how to optimize your digital presence, effectively reach your target audience, and benefit long-term from our scalable, low-maintenance solutions. In a free initial consultation, you’ll learn exactly what steps are needed to turn your website into a powerful marketing tool.",
|
"teaser": "We show you how to optimize your digital presence, effectively reach your target audience, and benefit long-term from our scalable, maintenance-friendly solutions. During a free initial consultation, you will learn exactly which steps are necessary to transform your website into a powerful marketing tool.",
|
||||||
"button": "Request your free initial consultation!"
|
"button": "Request free initial consultation!"
|
||||||
},
|
},
|
||||||
"canDo": {
|
"canDo": {
|
||||||
"title": "Start using your website’s full potential!",
|
"title": "You too can fully leverage your website’s potential in the future!",
|
||||||
"item1": {
|
"item1": {
|
||||||
"title": "Gain new customers and increase revenue",
|
"title": "Gain new customers and increase revenue",
|
||||||
"text": "Turn visitors into paying customers! With a clear strategy, compelling design and optimized user experience, your website becomes a lead machine."
|
"text": "Turn visitors into paying customers! With a clear strategy, convincing design, and optimized user guidance, your website becomes a lead machine."
|
||||||
},
|
},
|
||||||
"item2": {
|
"item2": {
|
||||||
"title": "Retain customers and members",
|
"title": "Retain customers and members",
|
||||||
"text": "Strengthen your customer relationships! With valuable content, exclusive offers and interactive features, your target group remains active and engaged."
|
"text": "Strengthen relationships with your customers! With valuable content, exclusive offers, and interactive features, your audience stays active and engaged."
|
||||||
},
|
},
|
||||||
"item3": {
|
"item3": {
|
||||||
"title": "Attract and inspire new employees",
|
"title": "Find and inspire employees",
|
||||||
"text": "Find the right talent! An authentic career page with clear benefits makes your company irresistible to applicants."
|
"text": "Attract the right talents! An authentic career page with clear benefits makes your company irresistible to applicants."
|
||||||
},
|
},
|
||||||
"item4": {
|
"item4": {
|
||||||
"title": "Reduce administrative effort",
|
"title": "Reduce administrative effort",
|
||||||
"text": "Fewer inquiries – more efficiency! With clear information and digital processes on your website, you save time and costs while easing the workload for your team."
|
"text": "Fewer inquiries – more efficiency! Clear information and digital processes on your website save time, costs, and relieve your team."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"compBox": {
|
"compBox": {
|
||||||
"title": "\"Design is the art of uniting function and aesthetics\"",
|
"title": "\"Design is the art of combining function and aesthetics\"",
|
||||||
"subtitle": "This is our approach when starting a client’s relaunch process.",
|
"subtitle": "With this claim, we start the relaunch process for our clients.",
|
||||||
"text": "We place great emphasis on a clean design that aligns with users’ mental models – so visitors always find exactly what they’re looking for, right where they expect it."
|
"text": "We place special value on a clean design that corresponds to users’ mental models – so visitors always find exactly what they are looking for, where they expect it."
|
||||||
},
|
},
|
||||||
"finalCall": {
|
"finalCall": {
|
||||||
"title": "Together, we’ll take your business to the next level!",
|
"title": "Together, we take your business to the next level!",
|
||||||
"button": "Contact us!"
|
"button": "Contact us!"
|
||||||
},
|
},
|
||||||
"marqueeBanner": {
|
"marqueeBanner": {
|
||||||
"title": "These companies trust us"
|
"title": "These companies trust us"
|
||||||
},
|
},
|
||||||
"faqArea": {
|
"faqArea": {
|
||||||
"headline": "Here you’ll find answers to frequently asked questions (FAQs) about website creation with digimedialoop"
|
"headline": "Here you will find answers to frequently asked questions (FAQs) about website creation with digimedialoop"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webagency": {
|
||||||
|
"hero": {
|
||||||
|
"title": "Your web agency for strategic web development and functional web design in Herrsching am Ammersee",
|
||||||
|
"subtitle": "We develop websites that turn visitors into customers!",
|
||||||
|
"text1": "We stand for professional, innovative, and strategic web solutions and combine technical know-how with a deep understanding of digital communication to successfully position companies online.",
|
||||||
|
"text2": "Our approach is always individual: Every project is realized with care, foresight, and the latest technologies. We rely on close cooperation and tailor-made solutions that fit our clients. We accompany companies from various industries – from small businesses to larger firms – on their way to a successful online presence.",
|
||||||
|
"text3": "Let us optimize your digital presence together!",
|
||||||
|
"button": "Your contact to us!"
|
||||||
|
},
|
||||||
|
"team": {
|
||||||
|
"title": "Your contact person at digimedialoop",
|
||||||
|
"name": "Sabrina Hennrich",
|
||||||
|
"position": "Consulting | Concept | Design | Development",
|
||||||
|
"text1": "With over 20 years of experience in web design, she is still a web developer out of pure passion!",
|
||||||
|
"text2": "After graduating as a business economist, she worked many years in marketing before deepening her knowledge with a psychology degree.",
|
||||||
|
"text3": "This combination of business know-how, strategic marketing experience, and psychological understanding enables her to develop digital solutions that are not only aesthetically appealing but also target-effective and economically well thought-out.",
|
||||||
|
"text4": "Additionally, since 2019 she is also a Certified Expert in User Experience & Usability, giving her profound knowledge in user-centered design and optimal usability.",
|
||||||
|
"quote": "Openness, transparency, and fairness are extremely important to me when working with my clients and partners. I only recommend what makes sense to me and fits my clients. For this, I gladly take the time for a thorough analysis of my clients’ needs or those of their target group.",
|
||||||
|
"button": "Feel free to contact me!"
|
||||||
|
},
|
||||||
|
"grafiker": {
|
||||||
|
"supheadline": "digimedialoop for creatives",
|
||||||
|
"title": "Are you a graphic designer or media designer?",
|
||||||
|
"button": "Here is our offer for you"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,6 +158,23 @@ main
|
|||||||
border: none
|
border: none
|
||||||
padding: .4rem .8rem
|
padding: .4rem .8rem
|
||||||
margin: 0 0 1rem 0
|
margin: 0 0 1rem 0
|
||||||
|
|
||||||
|
.check
|
||||||
|
list-style: none
|
||||||
|
padding: .2rem 1rem
|
||||||
|
margin: 0
|
||||||
|
|
||||||
|
li
|
||||||
|
position: relative
|
||||||
|
padding-left: 1.5em
|
||||||
|
margin: .8em 0
|
||||||
|
|
||||||
|
&::before
|
||||||
|
content: "\2713"
|
||||||
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
color: $primaryColor
|
||||||
|
|
||||||
section
|
section
|
||||||
margin-bottom: 5vh
|
margin-bottom: 5vh
|
||||||
position: relative
|
position: relative
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="homePage">
|
<div class="homePage">
|
||||||
<section class="heroBox" aria-labelledby="hero-heading">
|
<section class="heroBox_service" aria-labelledby="hero-heading">
|
||||||
<NuxtImg
|
<NuxtImg
|
||||||
provider="strapi"
|
provider="strapi"
|
||||||
src="/uploads/large_DML_Home_Hero_4f27bc7f8e.webp"
|
src="/uploads/large_DML_Home_Hero_4f27bc7f8e.webp"
|
||||||
@ -138,7 +138,7 @@ class="pinkBtn mt-3"
|
|||||||
@click.prevent="toggleContactBubble">{{ $t('pages.home.finalCall.button') }}</button>
|
@click.prevent="toggleContactBubble">{{ $t('pages.home.finalCall.button') }}</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<MarqueeBanner :items="logoItems" :logo-height="60" :title="$t('pages.home.marqueeBanner.title')" />
|
<MarqueeBanner :items="logoItems" :logo-height="60" :title="$t('pages.home.marqueeBanner.title')" :greyscale="true" />
|
||||||
<FAQArea page-link="/" :headline="$t('pages.home.faqArea.headline')" />
|
<FAQArea page-link="/" :headline="$t('pages.home.faqArea.headline')" />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@ -168,13 +168,14 @@ const waveHeight = computed(() => (screenWidth.value / 25).toFixed(0));
|
|||||||
|
|
||||||
const logoItems = computed(() => {
|
const logoItems = computed(() => {
|
||||||
return customers.value.map(customer => ({
|
return customers.value.map(customer => ({
|
||||||
company: customer.company || '',
|
text: customer.company || '',
|
||||||
logo: {
|
image: {
|
||||||
url: customer.logo?.url || '',
|
url: customer.logo?.url || '',
|
||||||
alternativeText: customer.company || ''
|
alternativeText: customer.logo?.alternativeText || customer.company || ''
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const canDoItems = [
|
const canDoItems = [
|
||||||
{
|
{
|
||||||
img: '/uploads/website_Erfolg_Marketing_3c36a43ba5.png',
|
img: '/uploads/website_Erfolg_Marketing_3c36a43ba5.png',
|
||||||
@ -208,7 +209,7 @@ const canDoItems = [
|
|||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
.homePage
|
.homePage
|
||||||
|
|
||||||
.heroBox
|
.heroBox_service
|
||||||
position: relative
|
position: relative
|
||||||
min-height: 35rem
|
min-height: 35rem
|
||||||
height: 70vh
|
height: 70vh
|
||||||
@ -267,22 +268,7 @@ const canDoItems = [
|
|||||||
.container
|
.container
|
||||||
z-index: 2
|
z-index: 2
|
||||||
|
|
||||||
/* Welle oben */
|
|
||||||
.sectionWave.wave-top
|
|
||||||
position: absolute
|
|
||||||
left: 0
|
|
||||||
width: 100%
|
|
||||||
z-index: 1
|
|
||||||
transform: scaleY(1) scaleX(-1)
|
|
||||||
top: -2px
|
|
||||||
/* Welle unten */
|
|
||||||
.sectionWave.wave-bottom
|
|
||||||
position: absolute
|
|
||||||
left: 0
|
|
||||||
width: 100%
|
|
||||||
z-index: 1
|
|
||||||
transform: scaleY(-1)
|
|
||||||
bottom: -2px
|
|
||||||
|
|
||||||
.webStrategy
|
.webStrategy
|
||||||
padding: 4rem 0 3.5rem 0
|
padding: 4rem 0 3.5rem 0
|
||||||
@ -349,8 +335,11 @@ const canDoItems = [
|
|||||||
max-width: 50%
|
max-width: 50%
|
||||||
.homeImageTop
|
.homeImageTop
|
||||||
margin: 4.5rem 0 8vh 3rem !important
|
margin: 4.5rem 0 8vh 3rem !important
|
||||||
|
|
||||||
.compBox
|
.compBox
|
||||||
background-image: linear-gradient(to bottom left, white, #FEDEE8, white)
|
background-image: url(https://strapi.digimedialoop.de/uploads/bubbles_DM_Lmint_trans_08ddb0a921.webp) //linear-gradient(to bottom left, white, #FEDEE8, white)
|
||||||
|
background-size: cover
|
||||||
|
background-repeat: no-repeat
|
||||||
padding: 5rem 0 3rem 0
|
padding: 5rem 0 3rem 0
|
||||||
h2
|
h2
|
||||||
font-family: 'Comfortaa'
|
font-family: 'Comfortaa'
|
||||||
|
|||||||
251
pages/projekt/[link].vue
Normal file
251
pages/projekt/[link].vue
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
<template>
|
||||||
|
<section class="project topSpace">
|
||||||
|
<!-- SideBarNaviSlider mit dynamischem Link und i18n-Label -->
|
||||||
|
<SideBarNaviSlider :link="localePath('references')">
|
||||||
|
{{ t('referenceoverview') }}
|
||||||
|
</SideBarNaviSlider>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-9">
|
||||||
|
<h1>{{ t('project.detail.title', 'Kundenprojektvorstellung') }}</h1>
|
||||||
|
<h2>{{ project.projectTitle }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<div class="customerBox">
|
||||||
|
<NuxtImg
|
||||||
|
:src="customer.logo.url"
|
||||||
|
:alt="customer.logo.alternativeText"
|
||||||
|
provider="strapi"
|
||||||
|
/>
|
||||||
|
{{ }}
|
||||||
|
<h4>
|
||||||
|
{{ project.customer.company }} |
|
||||||
|
{{ project.customer.city }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row detailBox">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<NuxtImg
|
||||||
|
v-if="currentImage"
|
||||||
|
id="currentImage"
|
||||||
|
:src="currentImage.url"
|
||||||
|
:alt="currentImage.alternativeText || project.projectTitle"
|
||||||
|
provider="strapi"
|
||||||
|
/>
|
||||||
|
</transition>
|
||||||
|
<div class="preview">
|
||||||
|
<h3>{{ t('project.detail.moreViews', 'Weitere Ansichten') }}</h3>
|
||||||
|
<div class="imageNavigation">
|
||||||
|
<NuxtImg
|
||||||
|
v-for="(img, index) in project.projectImages"
|
||||||
|
:key="index"
|
||||||
|
:src="img.url"
|
||||||
|
:alt="img.alternativeText"
|
||||||
|
provider="strapi"
|
||||||
|
@click="setCurrentImage(img)"
|
||||||
|
:class="{ active: currentImage?.url === img.url }"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="col-lg-8 pt-4">
|
||||||
|
<span v-html="htmlContent(project.projectDescription)"></span>
|
||||||
|
|
||||||
|
|
||||||
|
<h4>{{ t('project.detail.technologies', 'Verwendete Technologien') }}</h4>
|
||||||
|
<div class="techChipsBox">
|
||||||
|
<span
|
||||||
|
v-for="(tech, index) in project.technologies"
|
||||||
|
:key="index"
|
||||||
|
class="techChip"
|
||||||
|
>
|
||||||
|
{{ tech.titel }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row" v-if="project.webpage">
|
||||||
|
<div class="col-12 text-end">
|
||||||
|
<a
|
||||||
|
class="webPageBtn"
|
||||||
|
:href="project.webpage"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
<svg>
|
||||||
|
<use xlink:href="/assets/icons/collection.svg#desktop" />
|
||||||
|
</svg>
|
||||||
|
{{ t('project.detail.visitProject', 'Projekt live erleben') }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from "vue"
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
const { t } = useI18n()
|
||||||
|
const localePath = useLocalePath()
|
||||||
|
|
||||||
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
import { storeToRefs } from 'pinia'
|
||||||
|
import { useMainStore } from '@/stores/main'
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
const { cmsUrl, projects, dataFetched } = storeToRefs(mainStore)
|
||||||
|
const project = computed(() => mainStore.getProjectByLink(route.params.link))
|
||||||
|
const customer = computed(() => {
|
||||||
|
if (!project.value || !project.value.customer) return null
|
||||||
|
return mainStore.getCustomerById(project.value.customer.id)
|
||||||
|
})
|
||||||
|
const currentImage = ref(null)
|
||||||
|
|
||||||
|
if (project.value && project.value.projectImages?.length > 0) {
|
||||||
|
currentImage.value = project.value.projectImages[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(project, (newProject) => {
|
||||||
|
if (newProject && newProject.projectImages?.length > 0) {
|
||||||
|
currentImage.value = newProject.projectImages[0]
|
||||||
|
} else {
|
||||||
|
currentImage.value = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import { useHtmlConverter } from '@/composables/useHTMLConverter'
|
||||||
|
const { convertToHTML } = useHtmlConverter()
|
||||||
|
const htmlContent = (data: string) => {
|
||||||
|
return convertToHTML(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setze das aktuelle Bild
|
||||||
|
const setCurrentImage = (image: any) => {
|
||||||
|
currentImage.value = image
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="sass">
|
||||||
|
.project
|
||||||
|
h1
|
||||||
|
color: $pink //adjust-color($darkgrey, $lightness: 40%)
|
||||||
|
font-size: 1.1rem
|
||||||
|
text-transform: uppercase
|
||||||
|
margin-bottom: 0
|
||||||
|
letter-spacing: .08rem
|
||||||
|
h2
|
||||||
|
margin-top: 0
|
||||||
|
img
|
||||||
|
width: 100%
|
||||||
|
.preview
|
||||||
|
h3
|
||||||
|
font-size: 1rem
|
||||||
|
color: adjust-color($darkgrey, $lightness: 40%)
|
||||||
|
img
|
||||||
|
width: 100px
|
||||||
|
margin: 0
|
||||||
|
cursor: pointer
|
||||||
|
transition: .6s
|
||||||
|
padding: 1.2rem
|
||||||
|
border: 2px solid transparent
|
||||||
|
&.active
|
||||||
|
border: 2px solid $lightgrey
|
||||||
|
padding: .5rem
|
||||||
|
border-radius: .5rem
|
||||||
|
.customerBox
|
||||||
|
width: 100%
|
||||||
|
max-width: 50vw
|
||||||
|
text-align: center
|
||||||
|
background-image: linear-gradient(to left bottom, rgba($lightgrey, .6), transparent, transparent)
|
||||||
|
border-top-right-radius: 20px
|
||||||
|
padding: 1rem
|
||||||
|
border-top: 1px solid rgba($lightgrey, .3)
|
||||||
|
border-right: 1px solid rgba($lightgrey, .3)
|
||||||
|
margin: 1rem 0
|
||||||
|
img
|
||||||
|
min-height: 2rem
|
||||||
|
width: 50%
|
||||||
|
max-width: 200px
|
||||||
|
margin: 1rem
|
||||||
|
h4
|
||||||
|
font-size: .8rem
|
||||||
|
@media(max-width: $breakPointLG)
|
||||||
|
background-image: linear-gradient(to left, rgba($lightgrey, .6), transparent, transparent)
|
||||||
|
margin-top: 0
|
||||||
|
.detailBox
|
||||||
|
h4
|
||||||
|
font-size: 1rem
|
||||||
|
margin-top: 2.5rem
|
||||||
|
color: adjust-color($darkgrey, $lightness: 20%)
|
||||||
|
font-family: 'Mainfont-Bold'
|
||||||
|
.webPageBtn
|
||||||
|
font-size: .8rem
|
||||||
|
margin-top: 2rem
|
||||||
|
margin-right: 6%
|
||||||
|
text-decoration: none
|
||||||
|
border: 1px solid adjust-color($darkgrey, $lightness: 20%)
|
||||||
|
padding: .5rem 1rem
|
||||||
|
border-radius: 5px
|
||||||
|
display: inline-block
|
||||||
|
color: adjust-color($darkgrey, $lightness: 30%)
|
||||||
|
transition: .6s
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.1)
|
||||||
|
svg
|
||||||
|
height: .8rem
|
||||||
|
width: .9rem
|
||||||
|
margin-right: .3rem
|
||||||
|
fill: adjust-color($darkgrey, $lightness: 20%)
|
||||||
|
.ctaBox
|
||||||
|
padding: 3rem 0
|
||||||
|
h2
|
||||||
|
margin-bottom: .5rem
|
||||||
|
h3
|
||||||
|
margin-bottom: .5rem
|
||||||
|
.navigationBox
|
||||||
|
margin-top: 2rem
|
||||||
|
width: 100%
|
||||||
|
color: adjust-color($darkgrey, $lightness: 35%)
|
||||||
|
font-size: .85rem
|
||||||
|
|
||||||
|
&:hover
|
||||||
|
cursor: pointer
|
||||||
|
.navBtn
|
||||||
|
transition: .6s
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.05)
|
||||||
|
span
|
||||||
|
display: inline-block
|
||||||
|
svg
|
||||||
|
fill: adjust-color($lightgrey, $lightness: -10%)
|
||||||
|
width: 80%
|
||||||
|
max-width: 50px
|
||||||
|
.techChipsBox
|
||||||
|
display: block
|
||||||
|
width: 100%
|
||||||
|
.techChip
|
||||||
|
background-color: $lightgrey
|
||||||
|
padding: .2rem 1rem
|
||||||
|
margin: .3rem
|
||||||
|
border-radius: .6rem
|
||||||
|
font-size: .9rem
|
||||||
|
display: inline-block
|
||||||
|
color: adjust-color($darkgrey, $lightness: 25%)
|
||||||
|
</style>
|
||||||
|
|
||||||
@ -1,15 +1,183 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="container-10">
|
<div>
|
||||||
<h1>{{ $t('references') }}</h1>
|
<section class="topSpace">
|
||||||
|
<div class="container">
|
||||||
|
<h1>{{ $t('pages.references.hero.h1') }}</h1>
|
||||||
|
<p v-html="$t('pages.references.hero.p')"></p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="referenceBox" v-if="projects && projects.length">
|
||||||
|
<slot>
|
||||||
|
<NuxtLink
|
||||||
|
class="reference"
|
||||||
|
v-for="project in projects"
|
||||||
|
:key="project.id"
|
||||||
|
:to="getProjectLink(project.link)"
|
||||||
|
>
|
||||||
|
<NuxtImg
|
||||||
|
provider="strapi"
|
||||||
|
:src="project.projectImages?.[0]?.url"
|
||||||
|
:alt="project.projectImages?.[0]?.alternativeText || project.projectTitle"
|
||||||
|
width="200"
|
||||||
|
class="project-image"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
<div class="infoBox">
|
||||||
|
<div class="info-content">
|
||||||
|
<h2>{{ project.projectTitle }}</h2>
|
||||||
|
</div>
|
||||||
|
<div class="logo-wrapper">
|
||||||
|
<NuxtImg
|
||||||
|
provider="strapi"
|
||||||
|
:src="getCustomerLogo(project.customer?.id)"
|
||||||
|
:alt="getCustomerAlt(project.customer?.id)"
|
||||||
|
height="50"
|
||||||
|
priority
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NuxtLink>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<CallToActionBox
|
||||||
|
:headline="$t('pages.references.ctaBox.headline')"
|
||||||
|
:text="$t('pages.references.ctaBox.text')"
|
||||||
|
:buttonText="$t('pages.references.ctaBox.button')"
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
import { useMainStore } from '@/stores/main'
|
||||||
layout: 'default'
|
import { storeToRefs } from 'pinia'
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig();
|
||||||
|
const cmsUrl = runtimeConfig.public.cmsBaseUrl
|
||||||
|
|
||||||
|
const mainStore = useMainStore()
|
||||||
|
const { projects } = storeToRefs(mainStore)
|
||||||
|
|
||||||
|
import { useI18nPages } from '@/composables/useI18nPages'
|
||||||
|
const { getProjectLink } = useI18nPages()
|
||||||
|
|
||||||
|
function getCustomerLogo(customerId) {
|
||||||
|
if (!customerId) return ''
|
||||||
|
const customer = mainStore.customers?.find(c => c.id === customerId)
|
||||||
|
return customer?.logo?.url || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCustomerAlt(customerId) {
|
||||||
|
if (!customerId) return ''
|
||||||
|
const customer = mainStore.customers?.find(c => c.id === customerId)
|
||||||
|
return customer?.logo?.alternativeText || 'Kundenlogo'
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleContactBubble() {
|
||||||
|
console.log('Kontaktanfrage öffnen')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erstelle das JSON-LD für alle Projekte
|
||||||
|
const jsonLdProjects = computed(() => {
|
||||||
|
if (!projects.value || !Array.isArray(projects.value) || projects.value.length === 0) return null;
|
||||||
|
|
||||||
|
const origin = typeof window !== 'undefined' ? window.location.origin : '';
|
||||||
|
|
||||||
|
return {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ItemList",
|
||||||
|
"itemListElement": projects.value.map((project, index) => ({
|
||||||
|
"@type": "ListItem",
|
||||||
|
"position": index + 1,
|
||||||
|
"url": origin + (project.link ? getProjectLink(project.link) : ''),
|
||||||
|
"name": project.projectTitle || 'Projekt',
|
||||||
|
"image": cmsUrl + (project.projectImages?.[0]?.url) || '',
|
||||||
|
"description": project.projectDescription?.[0]?.children?.[0]?.text || ''
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// useHead einbinden, wenn Projekte da sind
|
||||||
|
watchEffect(() => {
|
||||||
|
if (jsonLdProjects.value) {
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
children: JSON.stringify(jsonLdProjects.value)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass" scoped>
|
||||||
|
.referenceBox
|
||||||
|
display: grid
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr))
|
||||||
|
gap: 4rem
|
||||||
|
width: 80%
|
||||||
|
margin: 0 10% 5rem auto
|
||||||
|
|
||||||
|
h2
|
||||||
|
font-size: 1.4rem
|
||||||
|
|
||||||
|
.reference
|
||||||
|
display: flex
|
||||||
|
flex-direction: column
|
||||||
|
position: relative
|
||||||
|
text-decoration: none
|
||||||
|
color: $darkgrey
|
||||||
|
background: white
|
||||||
|
border-radius: 10px
|
||||||
|
overflow: hidden
|
||||||
|
transition: transform 0.3s ease, box-shadow 0.3s ease
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.02)
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1)
|
||||||
|
|
||||||
|
img
|
||||||
|
width: 90%
|
||||||
|
height: auto
|
||||||
|
object-fit: cover
|
||||||
|
margin: 1rem 5% 0 5%
|
||||||
|
transition: transform 0.6s ease
|
||||||
|
&:hover
|
||||||
|
transform: scale(1.05)
|
||||||
|
|
||||||
|
.infoBox
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: space-between
|
||||||
|
padding: 1rem 1.5rem
|
||||||
|
border-bottom-left-radius: 10px
|
||||||
|
border-bottom-right-radius: 10px
|
||||||
|
background: linear-gradient(to bottom right, white 40%, $lightgrey)
|
||||||
|
min-height: 8rem
|
||||||
|
|
||||||
|
.info-content
|
||||||
|
flex: 2
|
||||||
|
h2
|
||||||
|
font-size: 1rem
|
||||||
|
margin: 0.5rem 0
|
||||||
|
line-height: 1.2
|
||||||
|
hyphens: auto
|
||||||
|
color: $darkgrey
|
||||||
|
|
||||||
|
.logo-wrapper
|
||||||
|
flex: 1
|
||||||
|
display: flex
|
||||||
|
justify-content: flex-end
|
||||||
|
align-items: center
|
||||||
|
|
||||||
|
img
|
||||||
|
max-height: 50px
|
||||||
|
max-width: 100%
|
||||||
|
object-fit: contain
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -1,15 +1,243 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div>
|
||||||
|
<section class="heroBox" :aria-label="$t('pages.services.hero.ariaLabel')">
|
||||||
|
<NuxtImg
|
||||||
|
provider="strapi"
|
||||||
|
src="/uploads/BG_technology_b6b0083811.png"
|
||||||
|
class="hero-bg"
|
||||||
|
sizes="sm:100vw md:100vw lg:100vw"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
priority
|
||||||
|
loading="eager"
|
||||||
|
preload
|
||||||
|
fetchpriority="high"
|
||||||
|
/>
|
||||||
<div class="container-10">
|
<div class="container-10">
|
||||||
<h1>{{ $t('services') }}</h1>
|
<h1>{{ $t('pages.services.hero.headline1') }}</h1>
|
||||||
|
<h2>{{ $t('pages.services.hero.headline2') }}</h2>
|
||||||
|
<h3>{{ $t('pages.services.hero.headline3') }}</h3>
|
||||||
|
</div>
|
||||||
|
<!-- Nach dem Container: Spiegelwelle unten -->
|
||||||
|
<svg class="sectionWave wave-bottom" style="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 20" aria-hidden="true">
|
||||||
|
<path d="M 0 0 L 500 0 L 500 14 Q 354.4 -2.8 250 11 Q 145.6 24.8 0 14 L 0 0 Z" fill="#FFF"/>
|
||||||
|
</svg>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="explainBox" :aria-label="$t('pages.services.explain.ariaLabel')">
|
||||||
|
<NuxtImg
|
||||||
|
src="/uploads/Human_Maschine_BG_5c91e9100f.webp"
|
||||||
|
provider="strapi"
|
||||||
|
format="webp"
|
||||||
|
sizes="100vw"
|
||||||
|
class="background-image"
|
||||||
|
alt=""
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
<div class="container-15 content">
|
||||||
|
<h2>{{ $t('pages.services.explain.headline1') }}</h2>
|
||||||
|
<h3>{{ $t('pages.services.explain.headline2') }}</h3>
|
||||||
|
|
||||||
|
<p>{{ $t('pages.services.explain.paragraph') }}</p>
|
||||||
|
<ul class="check">
|
||||||
|
<li>{{ t('pages.services.explain.bullet1') }}</li>
|
||||||
|
<li>{{ t('pages.services.explain.bullet2') }}</li>
|
||||||
|
<li>{{ t('pages.services.explain.bullet3') }}</li>
|
||||||
|
<li>{{ t('pages.services.explain.bullet4') }}</li>
|
||||||
|
<li>{{ t('pages.services.explain.bullet5') }}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<MarqueeBanner
|
||||||
|
:items="projectItems"
|
||||||
|
:logoHeight="200"
|
||||||
|
:title="$t('pages.services.marquee.title')"
|
||||||
|
link="projekt"
|
||||||
|
:aria-label="$t('pages.services.marquee.title')"
|
||||||
|
speed="15"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<CallToActionBox
|
||||||
|
:headline="$t('pages.services.ctaBox.headline')"
|
||||||
|
:text="$t('pages.services.ctaBox.text')"
|
||||||
|
:buttonText="$t('pages.services.ctaBox.button')"
|
||||||
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
definePageMeta({
|
import { storeToRefs } from 'pinia';
|
||||||
layout: 'default'
|
import { useMainStore } from '@/stores/main';
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const runtimeConfig = useRuntimeConfig()
|
||||||
|
const origin = runtimeConfig.public.appUrl
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
const { projects, companyinfo } = storeToRefs(mainStore)
|
||||||
|
|
||||||
|
const projectItems = computed(() => {
|
||||||
|
return projects.value
|
||||||
|
.filter(project => project.customer && project.projectImages.length > 0)
|
||||||
|
.map(project => ({
|
||||||
|
text: project.customer?.company || '',
|
||||||
|
image: {
|
||||||
|
url: project.projectImages[0]?.url || '',
|
||||||
|
alternativeText: project.projectImages[0]?.alternativeText || project.customer?.company || ''
|
||||||
|
},
|
||||||
|
link: project.link || ''
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const logoUrl = computed(() => {
|
||||||
|
if (!companyinfo) return origin + '/logo.svg';
|
||||||
|
return companyinfo.logo?.data?.attributes?.url
|
||||||
|
? origin + companyinfo.logo.data.attributes.url
|
||||||
|
: origin + '/logo.svg';
|
||||||
|
})
|
||||||
|
|
||||||
|
// JSON_LD für Services
|
||||||
|
const jsonLdServices = computed(() => {
|
||||||
|
|
||||||
|
if (!companyinfo?.value || !companyinfo.value.company) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "ProfessionalService",
|
||||||
|
"name": companyinfo.value.company,
|
||||||
|
"url": origin,
|
||||||
|
"logo": logoUrl.value || (origin + '/logo.svg'),
|
||||||
|
"description": "Spezialisiert auf JAMstack-Webentwicklung, Headless CMS-Integration und moderne Frontend-Lösungen.",
|
||||||
|
"address": {
|
||||||
|
"@type": "PostalAddress",
|
||||||
|
"streetAddress": companyinfo.value.street || '',
|
||||||
|
"addressLocality": companyinfo.value.city || '',
|
||||||
|
"addressRegion": "Bayern",
|
||||||
|
"postalCode": companyinfo.value.postalcode || '',
|
||||||
|
"addressCountry": "DE"
|
||||||
|
},
|
||||||
|
"hasOfferCatalog": {
|
||||||
|
"@type": "OfferCatalog",
|
||||||
|
"name": "Leistungen",
|
||||||
|
"itemListElement": [
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "JAMstack-Webentwicklung",
|
||||||
|
"description": "Moderne Webentwicklung mit statischen Seiten und Headless CMS."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Headless CMS Integration",
|
||||||
|
"description": "Flexible CMS-Lösungen für maximale Freiheit im Frontend."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Offer",
|
||||||
|
"itemOffered": {
|
||||||
|
"@type": "Service",
|
||||||
|
"name": "Frontend-Entwicklung",
|
||||||
|
"description": "Moderne Frontend-Frameworks und UI/UX Design."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (companyinfo.value && jsonLdServices.value) {
|
||||||
|
useHead({
|
||||||
|
script: [
|
||||||
|
{
|
||||||
|
type: 'application/ld+json',
|
||||||
|
children: JSON.stringify(jsonLdServices.value)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="sass">
|
<style lang="sass">
|
||||||
|
|
||||||
|
.heroBox
|
||||||
|
position: relative
|
||||||
|
min-height: 35rem
|
||||||
|
height: 70vh
|
||||||
|
display: flex
|
||||||
|
align-items: center
|
||||||
|
justify-content: center
|
||||||
|
overflow: hidden
|
||||||
|
|
||||||
|
.hero-bg
|
||||||
|
position: absolute
|
||||||
|
inset: 0
|
||||||
|
width: 100%
|
||||||
|
height: 100%
|
||||||
|
object-fit: cover
|
||||||
|
object-position: center center
|
||||||
|
z-index: 0
|
||||||
|
|
||||||
|
.container-10, h1, h2, h3
|
||||||
|
position: relative
|
||||||
|
z-index: 1
|
||||||
|
h1, h2, h3
|
||||||
|
color: white
|
||||||
|
z-index: 2
|
||||||
|
line-height: 1.5
|
||||||
|
max-width: 70%
|
||||||
|
@media (max-width: $breakPointMD)
|
||||||
|
max-width: 100%
|
||||||
|
h1
|
||||||
|
margin-top: 0
|
||||||
|
font-size: clamp(2rem, 1.4rem + 3vw, 2.8rem)
|
||||||
|
margin-bottom: 0
|
||||||
|
font-family: 'Comfortaa'
|
||||||
|
h2
|
||||||
|
font-size: clamp(1.2rem, .7rem + 2vw, 2rem)
|
||||||
|
margin: .8rem 0 .8rem 0
|
||||||
|
font-family: 'Comfortaa'
|
||||||
|
h3
|
||||||
|
font-size: 1.2rem
|
||||||
|
|
||||||
|
.explainBox
|
||||||
|
position: relative
|
||||||
|
overflow: hidden
|
||||||
|
min-height: 400px
|
||||||
|
margin: 5vh 0
|
||||||
|
padding: 8vh 0
|
||||||
|
|
||||||
|
.background-image
|
||||||
|
position: absolute
|
||||||
|
top: 0
|
||||||
|
left: 0
|
||||||
|
width: 100%
|
||||||
|
height: auto
|
||||||
|
object-fit: contain
|
||||||
|
object-position: center center
|
||||||
|
z-index: 0
|
||||||
|
pointer-events: none
|
||||||
|
|
||||||
|
.content
|
||||||
|
position: relative
|
||||||
|
z-index: 1
|
||||||
|
padding-left: 10%
|
||||||
|
|
||||||
|
h3
|
||||||
|
font-family: 'Mainfont-Bold'
|
||||||
|
font-size: 1.2rem
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@ -214,7 +214,7 @@ const navigateTo = useRouter().push;
|
|||||||
@media(max-width: $breakPointSM)
|
@media(max-width: $breakPointSM)
|
||||||
padding-left: 0
|
padding-left: 0
|
||||||
h2
|
h2
|
||||||
color: darken($lightgrey, 50%)
|
color: darken($lightgrey, 30%)
|
||||||
font-size: .9rem
|
font-size: .9rem
|
||||||
margin-bottom: 1rem
|
margin-bottom: 1rem
|
||||||
h3
|
h3
|
||||||
|
|||||||
@ -26,7 +26,7 @@ interface CompanyInfo {
|
|||||||
|
|
||||||
interface SEO {
|
interface SEO {
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
seoDescription: string // Achtung: Schreibfehler wird so übernommen
|
seoDescription: string
|
||||||
seoKeywords: string
|
seoKeywords: string
|
||||||
type: string
|
type: string
|
||||||
seoImage?: CompanyLogo | null
|
seoImage?: CompanyLogo | null
|
||||||
@ -62,6 +62,11 @@ interface CustomerProject {
|
|||||||
webpage?: string
|
webpage?: string
|
||||||
technologies: { titel: string; icon?: string }[]
|
technologies: { titel: string; icon?: string }[]
|
||||||
projectImages: CompanyLogo[]
|
projectImages: CompanyLogo[]
|
||||||
|
customer?: {
|
||||||
|
id: number
|
||||||
|
company: string
|
||||||
|
city: string
|
||||||
|
} | null
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Customer {
|
interface Customer {
|
||||||
@ -82,6 +87,7 @@ export const useMainStore = defineStore('main', {
|
|||||||
companyinfo: null as CompanyInfo | null,
|
companyinfo: null as CompanyInfo | null,
|
||||||
pages: [] as Page[],
|
pages: [] as Page[],
|
||||||
customers: [] as Customer[],
|
customers: [] as Customer[],
|
||||||
|
projects: [] as CustomerProject[],
|
||||||
dataFetched: false,
|
dataFetched: false,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null as { message: string; stack?: string } | null,
|
error: null as { message: string; stack?: string } | null,
|
||||||
@ -95,43 +101,34 @@ export const useMainStore = defineStore('main', {
|
|||||||
? `${runtimeConfig.public.cmsBaseUrl}${logoUrl}`
|
? `${runtimeConfig.public.cmsBaseUrl}${logoUrl}`
|
||||||
: '/uploads/dummy_Image_4abc3f04dd.webp'
|
: '/uploads/dummy_Image_4abc3f04dd.webp'
|
||||||
},
|
},
|
||||||
|
|
||||||
isMobile: (state) => state.screenWidth < 768,
|
isMobile: (state) => state.screenWidth < 768,
|
||||||
|
|
||||||
getPageByLink: (state) => (link: string) =>
|
getPageByLink: (state) => (link: string) =>
|
||||||
state.pages.find((p) => p.pageLink === link),
|
state.pages.find((p) => p.pageLink === link),
|
||||||
|
|
||||||
getCustomerById: (state) => (id: number) =>
|
getCustomerById: (state) => (id: number) =>
|
||||||
state.customers.find((c) => c.id === id),
|
state.customers.find((c) => c.id === id),
|
||||||
|
|
||||||
getFaqsByPageId: (state) => (pageId: number) =>
|
getFaqsByPageId: (state) => (pageId: number) =>
|
||||||
state.pages.find((p) => p.id === pageId)?.faqs ?? [],
|
state.pages.find((p) => p.id === pageId)?.faqs ?? [],
|
||||||
|
|
||||||
getFaqsByPageLink: (state) => (link: string) => {
|
getFaqsByPageLink: (state) => (link: string) => {
|
||||||
const page = state.pages.find(p => p.pageLink === link);
|
const page = state.pages.find(p => p.pageLink === link);
|
||||||
return page?.faqs ?? [];
|
return page?.faqs ?? [];
|
||||||
}
|
},
|
||||||
|
getProjectByLink: (state) => (link: string) =>
|
||||||
|
state.projects.find(project => project.link === link),
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
toggleMenu() {
|
toggleMenu() {
|
||||||
this.menuOpen = !this.menuOpen
|
this.menuOpen = !this.menuOpen
|
||||||
},
|
},
|
||||||
|
|
||||||
closeMenu() {
|
closeMenu() {
|
||||||
this.menuOpen = false
|
this.menuOpen = false
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleContactBubble() {
|
toggleContactBubble() {
|
||||||
this.contactBoxOpen = !this.contactBoxOpen
|
this.contactBoxOpen = !this.contactBoxOpen
|
||||||
},
|
},
|
||||||
|
|
||||||
setScrollPosition(pos: number) {
|
setScrollPosition(pos: number) {
|
||||||
this.scrollPosition = pos
|
this.scrollPosition = pos
|
||||||
},
|
},
|
||||||
|
|
||||||
setScreenWidth(width: number) {
|
setScreenWidth(width: number) {
|
||||||
this.screenWidth = width
|
this.screenWidth = width
|
||||||
},
|
},
|
||||||
@ -143,7 +140,7 @@ export const useMainStore = defineStore('main', {
|
|||||||
const { public: cfg } = useRuntimeConfig()
|
const { public: cfg } = useRuntimeConfig()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [companyRes, pagesRes, customersRes] = await Promise.all([
|
const [companyRes, pagesRes, customersRes, projectsRes] = await Promise.all([
|
||||||
$fetch(`${cfg.cmsBaseUrl}/api/companyinfo?populate=*`, {
|
$fetch(`${cfg.cmsBaseUrl}/api/companyinfo?populate=*`, {
|
||||||
headers: { Authorization: `Bearer ${cfg.cmsToken}` },
|
headers: { Authorization: `Bearer ${cfg.cmsToken}` },
|
||||||
}),
|
}),
|
||||||
@ -153,12 +150,13 @@ export const useMainStore = defineStore('main', {
|
|||||||
$fetch(`${cfg.cmsBaseUrl}/api/customers?populate=*`, {
|
$fetch(`${cfg.cmsBaseUrl}/api/customers?populate=*`, {
|
||||||
headers: { Authorization: `Bearer ${cfg.cmsToken}` },
|
headers: { Authorization: `Bearer ${cfg.cmsToken}` },
|
||||||
}),
|
}),
|
||||||
|
$fetch(`${cfg.cmsBaseUrl}/api/references?populate=projectImages,Technologien,customer&sort=launchDate:desc`, {
|
||||||
|
headers: { Authorization: `Bearer ${cfg.cmsToken}` },
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
// CompanyInfo (Single Type)
|
|
||||||
this.companyinfo = companyRes.data?.attributes ?? companyRes
|
this.companyinfo = companyRes.data?.attributes ?? companyRes
|
||||||
|
|
||||||
// Pages
|
|
||||||
this.pages = pagesRes.data.map((item: any) => {
|
this.pages = pagesRes.data.map((item: any) => {
|
||||||
const a = item.attributes
|
const a = item.attributes
|
||||||
return {
|
return {
|
||||||
@ -174,7 +172,7 @@ export const useMainStore = defineStore('main', {
|
|||||||
SEO: a.SEO
|
SEO: a.SEO
|
||||||
? {
|
? {
|
||||||
pageTitle: a.SEO.pageTitle,
|
pageTitle: a.SEO.pageTitle,
|
||||||
seoDescription: a.SEO.seoDesicription, // Fehler absichtlich
|
seoDescription: a.SEO.seoDesicription, // absichtlicher Fehler
|
||||||
seoKeywords: a.SEO.seoKeywords,
|
seoKeywords: a.SEO.seoKeywords,
|
||||||
type: a.SEO.type,
|
type: a.SEO.type,
|
||||||
seoImage: a.SEO.seoImage?.data
|
seoImage: a.SEO.seoImage?.data
|
||||||
@ -202,7 +200,6 @@ export const useMainStore = defineStore('main', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Customers
|
|
||||||
this.customers = customersRes.data.map((item: any) => {
|
this.customers = customersRes.data.map((item: any) => {
|
||||||
const a = item.attributes
|
const a = item.attributes
|
||||||
return {
|
return {
|
||||||
@ -211,22 +208,34 @@ export const useMainStore = defineStore('main', {
|
|||||||
city: a.city,
|
city: a.city,
|
||||||
logo: a.logo?.data?.attributes ?? null,
|
logo: a.logo?.data?.attributes ?? null,
|
||||||
invertLogo: a.invertLogo?.data?.attributes ?? null,
|
invertLogo: a.invertLogo?.data?.attributes ?? null,
|
||||||
projects: (a.projects?.data ?? []).map((p: any) => ({
|
projects: [], // Wird durch references geladen
|
||||||
id: p.id,
|
}
|
||||||
projectTitle: p.attributes.projectTitle,
|
})
|
||||||
projectImages: p.attributes.projectImages?.data?.map((img: any) => ({
|
|
||||||
url: img.attributes.url,
|
this.projects = projectsRes.data.map((item: any) => {
|
||||||
alternativeText: img.attributes.alternativeText,
|
const a = item.attributes
|
||||||
})) ?? [],
|
return {
|
||||||
launchDate: p.attributes.launchDate,
|
id: item.id,
|
||||||
projectDescription: p.attributes.projectDescription,
|
projectTitle: a.projectTitle,
|
||||||
link: p.attributes.link,
|
launchDate: a.launchDate,
|
||||||
webpage: p.attributes.webpage,
|
projectDescription: a.projectDescription,
|
||||||
technologies: p.attributes.Technologien?.data?.map((t: any) => ({
|
link: a.link,
|
||||||
|
webpage: a.webpage,
|
||||||
|
technologies: a.Technologien?.data?.map((t: any) => ({
|
||||||
titel: t.attributes.titel,
|
titel: t.attributes.titel,
|
||||||
icon: t.attributes.icon,
|
icon: t.attributes.icon,
|
||||||
})) ?? [],
|
})) ?? [],
|
||||||
})),
|
projectImages: a.projectImages?.data?.map((img: any) => ({
|
||||||
|
url: img.attributes.url,
|
||||||
|
alternativeText: img.attributes.alternativeText,
|
||||||
|
})) ?? [],
|
||||||
|
customer: a.customer?.data
|
||||||
|
? {
|
||||||
|
id: a.customer.data.id,
|
||||||
|
company: a.customer.data.attributes.company,
|
||||||
|
city: a.customer.data.attributes.city,
|
||||||
|
}
|
||||||
|
: null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user