This commit is contained in:
Sabrina Hennrich 2025-05-20 13:57:41 +02:00
parent f1c1537fe4
commit 954a1febf7
21 changed files with 226 additions and 131 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -16,35 +16,35 @@ $breakPointXXL: 1400px
@font-face
font-family: 'Mainfont'
src: url('@/assets/fonts/montserrat/Montserrat-Light.otf') format("opentype")
src: url('@/assets/fonts/woff2/Montserrat-Light.woff2') format("woff2")
font-weight: normal
font-style: normal
font-display: swap
@font-face
font-family: 'Mainfont-Bold'
src: url('@/assets/fonts/montserrat/Montserrat-Medium.otf') format("opentype")
src: url('@/assets/fonts/woff2/Montserrat-Medium.woff2') format("woff2")
font-weight: normal
font-style: normal
font-display: swap
@font-face
font-family: 'Comfortaa'
src: url('@/assets/fonts/Comfortaa-Light.ttf') format("truetype")
src: url('@/assets/fonts/woff2/Comfortaa-Light.woff2') format("woff2")
font-weight: normal
font-style: normal
font-display: swap
@font-face
font-family: 'Comfortaa-Bold'
src: url('@/assets/fonts/Comfortaa-Bold.ttf') format("truetype")
src: url('@/assets/fonts/woff2/Comfortaa-Bold.woff2') format("woff2")
font-weight: normal
font-style: normal
font-display: swap
@font-face
font-family: 'Typewriter'
src: url('@/assets/fonts/JMH_Typewriter.ttf') format("truetype")
src: url('@/assets/fonts/woff2/JMH_Typewriter.woff2') format("woff2")
font-weight: normal
font-style: normal
font-display: swap

View File

@ -27,10 +27,12 @@
<NuxtImg
v-if="screenWidth <= 768"
class="mobileAspBox"
:src="cmsUrl + companyinfo?.profileImage?.data?.attributes?.url"
:src="companyinfo?.profileImage?.data?.attributes?.url"
alt="Sabrina Hennrich"
:width="400"
format="webp"
provider="strapi"
loading="lazy"
/>
<h2 id="contactTitle">{{ $t('contactForm.yourcontact2us') }}</h2>
<p class="my-4">
@ -45,12 +47,16 @@
{{ companyinfo.company }}<br>{{ companyinfo.street }} <br>{{ companyinfo.postalcode }} {{ companyinfo.city }}
</p>
<p class="aspProf">{{ $t('contactForm.yourcontactperson') }} <b>Sabrina Hennrich</b></p>
<div class="aspBox"><NuxtImg
:src="cmsUrl + companyinfo?.profileImage?.data?.attributes?.url"
<div class="aspBox">
<NuxtImg
:src="companyinfo?.profileImage?.data?.attributes?.url"
alt="Ansprechpartner Sabrina Hennrich"
:width="150"
format="webp"
/></div>
provider="strapi"
loading="lazy"
/>
</div>
</div>
</div>
<div class="col-md-6">
@ -151,9 +157,7 @@ const { t } = useI18n();
// Zugriff auf den Pinia-Store
const mainStore = useMainStore();
const { companyinfo } = storeToRefs(mainStore);
const config = useRuntimeConfig();
const cmsUrl = computed(() => config.public.cmsBaseUrl);
const isContactBubbleOpen = computed(() => mainStore.contactBoxOpen);
const toggleContactBubble = () => mainStore.toggleContactBubble();

View File

@ -23,7 +23,7 @@
</div>
<!-- Slider -->
<input v-model="sliderValue" type="range" min="0" max="100" class="slider" />
<input v-model="sliderValue" type="range" min="0" max="100" class="slider" >
<!-- Vertikale Trennlinie -->
<div class="slider-line" :style="{ left: sliderValue + '%' }" />

View File

@ -32,7 +32,7 @@
loading="lazy"
/>
</p>
<p>{{ companyinfo?.contact }}</p>
<p v-if="false">{{ companyinfo?.contact }}</p>
<p>{{ companyinfo?.street }}</p>
<p>
{{ companyinfo?.postalcode }}
@ -176,6 +176,8 @@ footer
margin-bottom: 0.2rem
margin-top: .2rem
i
font-size: .8rem
a
cursor: pointer
color: white
@ -187,6 +189,9 @@ footer
background-color: rgba($primaryColor, .2)
border-radius: 4px
&.router-link-active
color: lighten($pink, 15%) !important
.logo
width: 10rem !important

View File

@ -1,39 +1,105 @@
// composables/usePageMeta.ts
import { watch, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useMainStore } from '@/stores/main'
import { useHead } from '@unhead/vue'
import { storeToRefs } from 'pinia'
// Importiere die Seiten-Routen
import { i18nPages } from '../i18n/i18n-pages' // relativ zu deinem Composable-Pfad anpassen!
export function usePageMeta () {
const route = useRoute()
const mainStore = useMainStore()
const { companyinfo } = storeToRefs(mainStore)
// ► Der aktuelle Page-Eintrag als computed
const currentPage = computed(() => mainStore.getPageByLink(route.path))
// ► Reagiere auf Route- oder Store-Änderungen
watch(
() => currentPage.value, // Quelle
(page) => { // Callback
if (!page) return
() => currentPage.value,
(page) => {
if (!page || !companyinfo.value) return
const metaTitle = page.SEO?.pageTitle ?? 'Standard Title'
const metaDescription = page.SEO?.seoDescription ?? 'Standard Description'
const metaImage = page.SEO?.seoImage?.url ?? '/default-image.jpg'
const metaTitle = page.SEO?.pageTitle ?? 'digimedialoop'
const metaDescription = page.SEO?.seoDescription ?? 'Webdesign und Webentwicklung'
const metaImage = page.SEO?.seoImage?.url ?? 'https://strapi.digimedialoop.de/uploads/DML_Logo_grey_2024_c51210b70c.svg'
// Canonical URL
const config = useRuntimeConfig()
const canonical = `${config.public.appUrl}${route.path}`
// Robots Meta Tag
const robotsContent = route.path === '/danke' ? 'noindex, nofollow' : 'index, follow'
// Prüfe, ob Route Home oder References in allen Sprachen ist
const isHomePage = Object.values(i18nPages.index).includes(route.path)
const isReferencesPage = Object.values(i18nPages.references).includes(route.path)
// Basis LocalBusiness JSON-LD
const baseJsonLd = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
name: companyinfo.value.company,
image: metaImage,
url: config.public.appUrl,
telephone: companyinfo.value.phone,
email: companyinfo.value.email,
address: {
'@type': 'PostalAddress',
streetAddress: companyinfo.value.street,
addressLocality: companyinfo.value.city,
postalCode: companyinfo.value.postalcode,
addressCountry: 'DE'
},
openingHoursSpecification: [
{
'@type': 'OpeningHoursSpecification',
dayOfWeek: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'],
opens: '09:00',
closes: '17:00'
}
]
}
// Falls Home oder References: ergänze das Rating (2 Bewertungen mit 5 Sternen)
let jsonLd = undefined
if (isHomePage || isReferencesPage) {
jsonLd = {
...baseJsonLd,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '5',
reviewCount: '2'
}
}
} else if (isHomePage) {
// Nur LocalBusiness ohne Rating z.B.
jsonLd = baseJsonLd
}
useHead({
title: metaTitle,
meta: [
{ name: 'description', content: metaDescription },
{ name: 'robots', content: robotsContent },
{ property: 'og:title', content: metaTitle },
{ property: 'og:description', content: metaDescription },
{ property: 'og:image', content: metaImage },
{ name: 'twitter:title', content: metaTitle },
{ name: 'twitter:description', content: metaDescription },
{ name: 'twitter:image', content: metaImage }
],
link: [
{ rel: 'canonical', href: canonical }
],
script: jsonLd
? [
{
type: 'application/ld+json',
children: JSON.stringify(jsonLd)
}
]
: []
})
},
{ immediate: true } // Sofort beim ersten Aufruf ausführen
{ immediate: true }
)
}

66
i18n/i18n-pages.ts Normal file
View File

@ -0,0 +1,66 @@
export const i18nPages = {
index: {
de: '/',
en: '/home',
fr: '/accueil',
it: '/home',
es: '/inicio',
tr: '/anasayfa'
},
webagency: {
de: '/webagentur',
en: '/webagency',
fr: '/agence-web',
it: '/agenzia-web',
es: '/agencia-web',
tr: '/web-ajansi'
},
services: {
de: '/leistungen',
en: '/services',
fr: '/services',
it: '/servizi',
es: '/servicios',
tr: '/hizmetler'
},
references: {
de: '/referenzen',
en: '/references',
fr: '/références',
it: '/referenze',
es: '/referencias',
tr: '/referanslar'
},
imprint: {
de: '/impressum',
en: '/imprint',
fr: '/mentions-legales',
it: '/note-legali',
es: '/aviso-legal',
tr: '/künye'
},
privacy: {
de: '/datenschutz',
en: '/privacy',
fr: '/confidentialite',
it: '/privacy',
es: '/privacidad',
tr: '/gizlilik'
},
terms: {
de: '/agb',
en: '/terms',
fr: '/conditions',
it: '/termini',
es: '/condiciones',
tr: '/kosullar'
},
magazin: {
de: '/wissenswertes',
en: '/magazine',
fr: '/magazine',
it: '/magazine',
es: '/revista',
tr: '/dergi'
}
}

View File

@ -51,10 +51,10 @@
"heroBox": {
"h1": "Ihre Agentur für individuelles Webdesign und professionelle Webentwicklung",
"h2": "Modulare Webseiten mit modernsten Technologien",
"h3": "So ist Ihre Website schnell, effizient und zukunftssicher!"
"h3": "Höchste Performanz - schnell, effizient und zukunftssicher!"
},
"solution": {
"title": "Websites, die mehr können: Performance, Freiheit & KI-Power für Ihr Business",
"title": "Webseiten, die mehr können: Performance, Freiheit & KI-Power",
"teaser": "Wir entwickeln maßgeschneiderte Webseiten mit JAMstack-Technologie, die perfekt auf Ihr Business abgestimmt sind und als leistungsstarkes Marketing- und Vertriebsinstrument für Ihren Erfolg sorgen.",
"text": "Durch die klare Trennung von Inhalt und Technik, unter Verwendung eines headless Content-Management-Systems, entstehen wartungsfreundliche, suchmaschinenoptimierte Lösungen, die nicht nur langfristig skalierbar sind, sondern auch Ihrem Marketing-Team die Arbeit erleichtern. Inhalte lassen sich ohne technische Hürden pflegen, neue Funktionen flexibel integrieren ganz ohne Plugin-Chaos oder Eingriffe ins Live-System. Dank sauberer semantischer Struktur sind unsere Lösungen zudem optimal auf AI-gestützte Suchsysteme vorbereitet und ermöglichen die einfache Integration in KI-gestützte Operator-Workflows.",
"buttonText": "Erfahren Sie mehr über Headless CMS"

View File

@ -1,4 +1,17 @@
import { defineNuxtConfig } from 'nuxt/config'
import { i18nPages } from './i18n/i18n-pages'
// Hilfsfunktion, um Objekt mit locales auf reine Strings zu mappen
function flattenPages(pagesObj: Record<string, Record<string, string>>) {
const result: Record<string, string> = {}
for (const pageKey in pagesObj) {
for (const locale in pagesObj[pageKey]) {
// z.B. 'index.de' = '/'
result[`${pageKey}.${locale}`] = pagesObj[pageKey][locale]
}
}
return result
}
export default defineNuxtConfig({
app: {
@ -31,8 +44,6 @@ export default defineNuxtConfig({
modules: [
'@nuxt/image',
'@nuxt/eslint',
'@nuxt/scripts',
'@nuxt/ui',
'@pinia/nuxt',
'@nuxtjs/i18n',
['@pinia/nuxt', {
@ -54,6 +65,7 @@ export default defineNuxtConfig({
},
runtimeConfig: {
public: {
appUrl: process.env.APP_URL,
cmsBaseUrl: process.env.CMS_URL,
cmsToken: process.env.CMS_TOKEN
}
@ -74,72 +86,7 @@ export default defineNuxtConfig({
{ code: 'tr', name: 'Türkçe', file: 'tr.json' }
],
customRoutes: 'config',
pages: {
index: {
de: '/',
en: '/home',
fr: '/accueil',
it: '/home',
es: '/inicio',
tr: '/anasayfa'
},
webagency: {
de: '/webagentur',
en: '/webagency',
fr: '/agence-web',
it: '/agenzia-web',
es: '/agencia-web',
tr: '/web-ajansi'
},
services: {
de: '/leistungen',
en: '/services',
fr: '/services',
it: '/servizi',
es: '/servicios',
tr: '/hizmetler'
},
references: {
de: '/referenzen',
en: '/references',
fr: '/références',
it: '/referenze',
es: '/referencias',
tr: '/referanslar'
},
imprint: {
de: '/impressum',
en: '/imprint',
fr: '/mentions-legales',
it: '/note-legali',
es: '/aviso-legal',
tr: '/künye'
},
privacy: {
de: '/datenschutz',
en: '/privacy',
fr: '/confidentialite',
it: '/privacy',
es: '/privacidad',
tr: '/gizlilik'
},
terms: {
de: '/agb',
en: '/terms',
fr: '/conditions',
it: '/termini',
es: '/condiciones',
tr: '/kosullar'
},
magazin: {
de: '/wissenswertes',
en: '/magazine',
fr: '/magazine',
it: '/magazine',
es: '/revista',
tr: '/dergi'
}
},
pages: flattenPages(i18nPages),
bundle: {
optimizeTranslationDirective: false
}

View File

@ -12,6 +12,7 @@
priority
loading="eager"
preload
fetchpriority="high"
/>
<div class="container-10">
<h1 id="hero-heading">{{ $t('home.heroBox.h1') }}</h1>
@ -76,7 +77,7 @@ class="pinkBtn" role="button"
<div class="row mb-5">
<div class="col-xl-6">
<div class="row">
<div class="col-md-6 my-5">
<div class="col-md-6 mb-5">
<div class="innerBox">
<div class="canDoItem">
<NuxtImg
@ -94,7 +95,7 @@ class="pinkBtn" role="button"
</div>
</div>
</div>
<div class="col-md-6 my-5">
<div class="col-md-6 mb-5">
<div class="innerBox">
<div class="canDoItem">
<NuxtImg
@ -116,7 +117,7 @@ class="pinkBtn" role="button"
</div>
<div class="col-xl-6">
<div class="row">
<div class="col-md-6 my-5">
<div class="col-md-6 mb-5">
<div class="innerBox">
<div class="canDoItem" role="group" aria-labelledby="cando-title">
<NuxtImg
@ -134,7 +135,7 @@ class="pinkBtn" role="button"
</div>
</div>
</div>
<div class="col-md-6 my-5">
<div class="col-md-6 mb-5">
<div class="innerBox">
<div class="canDoItem">
<NuxtImg
@ -249,7 +250,7 @@ const logoItems = computed(() => {
width: 100%
height: 100%
object-fit: cover
object-position: right bottom
object-position: center bottom
z-index: 0 // liegt unter Content
.container-10, h1, h2, h3
@ -272,6 +273,7 @@ const logoItems = computed(() => {
line-height: 1.5
margin-top: 1rem
max-width: 55%
font-family: 'Comfortaa'
@media (max-width: $breakPointMD)
max-width: 90%
h3
@ -311,10 +313,11 @@ const logoItems = computed(() => {
.webStrategy
padding: 4rem 0 3.5rem 0
h2
font-size: clamp(1.6rem, 1rem + 1vw, 1.8rem)
font-size: clamp(1.6rem, 1rem + 2vw, 1.8rem)
line-height: 150%
font-family: 'Comfortaa'
h3
font-size: clamp(1.2rem, .8rem + 1vw, 1.4rem)
font-size: clamp(1.1rem, .75rem + 1vw, 1.25rem)
line-height: 150%
img
width: 80%
@ -332,6 +335,8 @@ const logoItems = computed(() => {
justify-content: center
position: relative
padding: 3rem 0
h2
font-size: clamp(1.6rem, 1rem + 2vw, 1.8rem)
h3
font-size: clamp(1.1rem, .8rem + 1vw, 1.2rem)
line-height: 150%
@ -352,22 +357,25 @@ const logoItems = computed(() => {
.compBox
background-image: linear-gradient(to bottom left, white, #FEDEE8, white)
padding: 5rem 0 3rem 0
h2
font-family: 'Comfortaa'
h3
line-height: 1.5
p
padding-right: 1rem
.canDo
margin: 15vh 0
margin: 12vh 0
h2
margin-bottom: 3.5rem
font-size: clamp(1.6rem, 1rem + 2vw, 1.8rem)
.row
display: flex
flex-wrap: wrap
height: 100%
align-items: stretch
.innerBox
width: 90%
margin: 0 5% 0 5%
width: 100%
margin: 0 5%
display: flex
flex-direction: column
align-items: center
@ -375,13 +383,12 @@ const logoItems = computed(() => {
background-image: linear-gradient(to bottom right, transparent , white )
box-shadow: 3px 3px 8px 1px $lightgrey
border-bottom-right-radius: 1rem
padding: 0 2rem 1rem 2rem
padding: 0 5% 1rem 5%
border-right: 1px solid lighten($beige, 0%)
border-bottom: 1px solid lighten($beige, 0%)
height: 100%
.canDoItem
.imageBox
margin: 2rem auto
width: 100%