diff --git a/assets/fonts/Assistant-Regular.ttf b/assets/fonts/ttf/Assistant-Regular.ttf similarity index 100% rename from assets/fonts/Assistant-Regular.ttf rename to assets/fonts/ttf/Assistant-Regular.ttf diff --git a/assets/fonts/ColabThi.otf b/assets/fonts/ttf/ColabThi.otf similarity index 100% rename from assets/fonts/ColabThi.otf rename to assets/fonts/ttf/ColabThi.otf diff --git a/assets/fonts/Comfortaa-Bold.ttf b/assets/fonts/ttf/Comfortaa-Bold.ttf similarity index 100% rename from assets/fonts/Comfortaa-Bold.ttf rename to assets/fonts/ttf/Comfortaa-Bold.ttf diff --git a/assets/fonts/Comfortaa-Light.ttf b/assets/fonts/ttf/Comfortaa-Light.ttf similarity index 100% rename from assets/fonts/Comfortaa-Light.ttf rename to assets/fonts/ttf/Comfortaa-Light.ttf diff --git a/assets/fonts/Comfortaa-Regular.ttf b/assets/fonts/ttf/Comfortaa-Regular.ttf similarity index 100% rename from assets/fonts/Comfortaa-Regular.ttf rename to assets/fonts/ttf/Comfortaa-Regular.ttf diff --git a/assets/fonts/JMH_Typewriter.ttf b/assets/fonts/ttf/JMH_Typewriter.ttf similarity index 100% rename from assets/fonts/JMH_Typewriter.ttf rename to assets/fonts/ttf/JMH_Typewriter.ttf diff --git a/assets/fonts/woff2/ColabThi.woff2 b/assets/fonts/woff2/ColabThi.woff2 new file mode 100644 index 0000000..2600540 Binary files /dev/null and b/assets/fonts/woff2/ColabThi.woff2 differ diff --git a/assets/fonts/woff2/Comfortaa-Bold.woff2 b/assets/fonts/woff2/Comfortaa-Bold.woff2 new file mode 100644 index 0000000..8da8fd1 Binary files /dev/null and b/assets/fonts/woff2/Comfortaa-Bold.woff2 differ diff --git a/assets/fonts/woff2/Comfortaa-Light.woff2 b/assets/fonts/woff2/Comfortaa-Light.woff2 new file mode 100644 index 0000000..5046e51 Binary files /dev/null and b/assets/fonts/woff2/Comfortaa-Light.woff2 differ diff --git a/assets/fonts/woff2/Comfortaa-Regular.woff2 b/assets/fonts/woff2/Comfortaa-Regular.woff2 new file mode 100644 index 0000000..7aa1902 Binary files /dev/null and b/assets/fonts/woff2/Comfortaa-Regular.woff2 differ diff --git a/assets/fonts/woff2/Montserrat-Light.woff2 b/assets/fonts/woff2/Montserrat-Light.woff2 new file mode 100644 index 0000000..932d0b2 Binary files /dev/null and b/assets/fonts/woff2/Montserrat-Light.woff2 differ diff --git a/assets/fonts/woff2/Montserrat-Medium.woff2 b/assets/fonts/woff2/Montserrat-Medium.woff2 new file mode 100644 index 0000000..510a2d0 Binary files /dev/null and b/assets/fonts/woff2/Montserrat-Medium.woff2 differ diff --git a/assets/styles/main.sass b/assets/styles/main.sass index 064389e..bc35a7f 100644 --- a/assets/styles/main.sass +++ b/assets/styles/main.sass @@ -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 diff --git a/components/ContactForm.vue b/components/ContactForm.vue index bde909f..80afcba 100644 --- a/components/ContactForm.vue +++ b/components/ContactForm.vue @@ -27,10 +27,12 @@

{{ $t('contactForm.yourcontact2us') }}

@@ -40,18 +42,22 @@ {{ companyinfo.phone }}

-

{{ $t('contactForm.ourOffice') }}

-

- {{ companyinfo.company }}
{{ companyinfo.street }}
{{ companyinfo.postalcode }} {{ companyinfo.city }} -

-

{{ $t('contactForm.yourcontactperson') }} Sabrina Hennrich

-
-
+

{{ $t('contactForm.ourOffice') }}

+

+ {{ companyinfo.company }}
{{ companyinfo.street }}
{{ companyinfo.postalcode }} {{ companyinfo.city }} +

+

{{ $t('contactForm.yourcontactperson') }} Sabrina Hennrich

+
+ +
+
@@ -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(); diff --git a/components/ImageComparisonSlider.vue b/components/ImageComparisonSlider.vue index ed7d805..43e8d4e 100644 --- a/components/ImageComparisonSlider.vue +++ b/components/ImageComparisonSlider.vue @@ -23,7 +23,7 @@
- +
diff --git a/components/template/PageFooter.vue b/components/template/PageFooter.vue index 942c3df..0f3b8be 100644 --- a/components/template/PageFooter.vue +++ b/components/template/PageFooter.vue @@ -32,7 +32,7 @@ loading="lazy" />

-

{{ companyinfo?.contact }}

+

{{ companyinfo?.contact }}

{{ companyinfo?.street }}

{{ companyinfo?.postalcode }} @@ -176,16 +176,21 @@ footer margin-bottom: 0.2rem margin-top: .2rem + i + font-size: .8rem a - cursor: pointer - color: white - font-weight: bold - border-bottom: 0 + cursor: pointer + color: white + font-weight: bold + border-bottom: 0 - &:hover - box-shadow: 0 0 20px 0 rgba($primaryColor, .3) - background-color: rgba($primaryColor, .2) - border-radius: 4px + &:hover + box-shadow: 0 0 20px 0 rgba($primaryColor, .3) + background-color: rgba($primaryColor, .2) + border-radius: 4px + + &.router-link-active + color: lighten($pink, 15%) !important .logo width: 10rem !important diff --git a/composables/usePageMeta.ts b/composables/usePageMeta.ts index 85ef0ad..1198277 100644 --- a/composables/usePageMeta.ts +++ b/composables/usePageMeta.ts @@ -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 }, - { 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 } - ] + { 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 } ) } diff --git a/i18n/i18n-pages.ts b/i18n/i18n-pages.ts new file mode 100644 index 0000000..9cd506a --- /dev/null +++ b/i18n/i18n-pages.ts @@ -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' + } +} \ No newline at end of file diff --git a/i18n/locales/de.json b/i18n/locales/de.json index 538b53b..ae844ad 100644 --- a/i18n/locales/de.json +++ b/i18n/locales/de.json @@ -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" diff --git a/nuxt.config.ts b/nuxt.config.ts index a3583de..ff532e8 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -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>) { + const result: Record = {} + 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 } diff --git a/pages/index.vue b/pages/index.vue index f43ae9d..77501ae 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -12,6 +12,7 @@ priority loading="eager" preload + fetchpriority="high" />

{{ $t('home.heroBox.h1') }}

@@ -76,7 +77,7 @@ class="pinkBtn" role="button"
-
+
-
+
-
+
-
+
{ 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 - + .canDoItem .imageBox margin: 2rem auto width: 100%