fix: FAQ in NuxtContent

FAQ converted to NuxtContent
This commit is contained in:
webfussel 2025-06-10 20:03:42 +02:00
parent 8668b96eff
commit 48efe0f75b
14 changed files with 151 additions and 96 deletions

View file

@ -123,6 +123,10 @@ a.mail {
} }
ul { ul {
list-style-position: inside;
}
ul.row {
list-style: none; list-style: none;
display: flex; display: flex;
} }

View file

@ -1,6 +1,5 @@
.Spoiler { .Spoiler {
background: var(--color-black); background: var(--color-black);
padding: 1rem 2rem;
border-radius: 20px; border-radius: 20px;
& .icon { & .icon {
@ -17,13 +16,20 @@
align-items: center; align-items: center;
gap: 1rem; gap: 1rem;
font-weight: bold; font-weight: bold;
padding: 1rem 2rem;
border-radius: 20px;
transition: 150ms ease-in-out;
&:hover {
background: rgba(0, 0, 0, .3);
}
} }
& > div { & > div {
margin-top: 1rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1rem; gap: 1rem;
padding: 1rem 2rem;
} }
} }

View file

@ -1,7 +1,7 @@
<template> <template>
<div class="Burger" :class="{ open }"> <div class="Burger" :class="{ open }">
<nav class="z-4" ref="navElement"> <nav class="z-4" ref="navElement">
<ul> <ul class="row">
<li v-for="({label, to, aria, icon}) in navigation" :key="label"> <li v-for="({label, to, aria, icon}) in navigation" :key="label">
<NuxtLink :to="to" :aria-label="aria" active-class="active" class="inline-flex-row big-gap" @click="close"> <NuxtLink :to="to" :aria-label="aria" active-class="active" class="inline-flex-row big-gap" @click="close">
<Icon :name="icon" mode="svg" /> <Icon :name="icon" mode="svg" />

View file

@ -7,7 +7,7 @@
<main> <main>
<small class="customer">{{ company }}</small> <small class="customer">{{ company }}</small>
<h3 class="title">{{ title }}</h3> <h3 class="title">{{ title }}</h3>
<ul> <ul class="row">
<li v-for="skill in tech"> <li v-for="skill in tech">
<Technology v-bind="skill" link="" /> <Technology v-bind="skill" link="" />
</li> </li>

View file

@ -43,8 +43,8 @@
</article> </article>
</div> </div>
<div class="flex-col gap-sm margin-top"> <div v-if="faq" class="flex-col gap-sm margin-top">
<Spoiler v-for="entry in faq" v-bind="entry" class="z-2" /> <ContentRenderer :value="faq" :style="{ display: 'contents' }" />
</div> </div>
</section> </section>
</template> </template>
@ -71,7 +71,8 @@ const intl = new Intl.NumberFormat(
currency: 'EUR', currency: 'EUR',
minimumFractionDigits: 0, minimumFractionDigits: 0,
maximumFractionDigits: 0, maximumFractionDigits: 0,
}) }
)
const oneOff : Service[] = [ const oneOff : Service[] = [
{ {
@ -128,36 +129,5 @@ const oneOff : Service[] = [
}, },
] ]
const faq = [ const faq = await queryCollection('faq').path('/snippets/faq/booking').first()
{
header: 'Warum machst du keine Stundensätze?',
content: [
'Ich finde Stundensätze haben für beide Seiten nur Nachteile:',
'Wenn ich schnell und gut arbeite, dann bekomme ich weniger Geld. Hab ich mal einen Knoten im Gehirn und brauche sehr lange, muss der Kunde mehr zahlen.',
'Klar kann man sagen, dass sich das irgendwann ausgleichen könnte - aber so weit will ich es garnicht erst kommen lassen.'
]
},
{
header: 'Welche Themen bietest du für deine Schulungen an?',
content: [
'Sprachen: JavaScript, TypeScript, HTML, CSS',
'Frameworks: Vue, Nuxt',
]
},
{
header: 'Wo finden die Schulungen statt?',
content: [
'Die Schulungen finden online statt. Normalerweise nutze ich dafür Google Meet, aber wenn du oder deine Firma eine andere Plattform wünschen und bereitstellen bin ich natürlich flexibel.',
'Wenn sich deine Firma in der Nähe meines Wohnortes befindet - und damit meine ich "In einer Stunde mit der Straßenbahn zu erreichen", dann kann alles natürlich auch vor Ort stattfinden.',
]
},
{
header: 'Ich hab ein cooles Projekt! Aber kein Geld...',
content: [
'Tja.',
'Ne, awas. Meld dich einfach trotzdem über meine E-Mail-Adresse und vielleicht finden wir eine Lösung.'
]
}
]
</script> </script>

View file

@ -15,7 +15,7 @@
<p>Falls du irgendwo einen anderen Social Media Account von mir findest, der nicht hier aufgelistet ist, aber aktiv postet, dann ist dieser höchstwahrscheinlich <Highlight>Fake</Highlight>. <p>Falls du irgendwo einen anderen Social Media Account von mir findest, der nicht hier aufgelistet ist, aber aktiv postet, dann ist dieser höchstwahrscheinlich <Highlight>Fake</Highlight>.
<br />Meld' dich gerne bei mir, wenn du so einen findest. <br />Meld' dich gerne bei mir, wenn du so einen findest.
</p> </p>
<ul class="social-media"> <ul class="row social-media">
<li v-for="({icon, name, ...rest}) in socials" :key="rest.href"> <li v-for="({icon, name, ...rest}) in socials" :key="rest.href">
<a v-bind="rest" target="_blank"> <a v-bind="rest" target="_blank">
<Icon :name="icon" :alt="rest['aria-label']" size="1.5em" mode="svg" /> <Icon :name="icon" :alt="rest['aria-label']" size="1.5em" mode="svg" />

View file

@ -43,8 +43,8 @@
</article> </article>
</div> </div>
<div class="flex-col gap-sm margin-top"> <div v-if="faq" class="flex-col gap-sm margin-top">
<Spoiler v-for="entry in faq" v-bind="entry" class="z-2" /> <ContentRenderer :value="faq" :style="{ display: 'contents' }" />
</div> </div>
</section> </section>
</template> </template>
@ -138,48 +138,5 @@ const flatrate : Service[] =
} }
] ]
const faq = [ const faq = await queryCollection('faq').path('/snippets/faq/flatrate').first()
{
header: 'Was ist eine Entwickler-Flatrate?',
content: [
'Die Entwickler-Flatrate ist ein Angebot, bei dem du für eine bestimmte Zeit eine bestimmte Menge an Leistungen erhältst.',
'Sie wird auch oft als sogenannter "Retainer" bezeichnet und ist eine günstige Art immer wieder anfallende Aufgaben auszulagern.'
]
},
{
header: 'Wie läuft die Zusammenarbeit ab?',
content: [
'Nach einer ersten Analyse deines Projekts legen wir gemeinsam den Umfang und die monatlichen Aufgaben fest. Du kannst mich je nach Paket auf den vereinbarten Wegen jederzeit kontaktieren.',
'Die Bearbeitung erfolgt innerhalb der vereinbarten Fristen.',
]
},
{
header: 'Wie wird abgerechnet?',
content: [
'Du zahlst monatlich einen festen Betrag im Prepaid-Format unabhängig davon wie viele Aufgaben tatsächlich anfallen.',
'Dadurch kannst du dein Budget besser einplanen und Aufgaben verteilen.'
]
},
{
header: 'Kann ich den Retainer jederzeit kündigen?',
content: [
'Ja klar. Du musst dabei nur die Mindestvertragslaufzeit beachten - die bezahlte Leistung steht dir weiterhin zu.',
'Falls wir merken, dass wir für eine Zusammenarbeit mehr als ungeeignet sind, bekommst du dein Geld anteilig der verbleibenden Zeit zurück und verlierst den Anspruch auf Leistungen ab diesem Zeitpunkt.'
]
},
{
header: 'Was passiert, wenn ich dich mal weniger brauche?',
content: [
'Der monatliche feste Betrag bleibt bestehen. Ähnlich wie bei einer Versicherung bezahlst du hier für den Fall, dass du mich brauchst und erhältst dann entsprechend die vereinbarten Leistungen.',
'Wenn du merkst, dass du mich nicht mehr brauchst, dann kannst du den Vertrag jederzeit kündigen und die Leistungen auslaufen lassen.'
]
},
{
header: 'Für wen lohnt sich sowas überhaupt?',
content: [
'Vor allem Unternehmen, bei denen immer wieder Aufgaben anfallen für die sich aber kein komplettes Projekt oder gar eine feste Stelle in der Firma lohnt, profitieren von dieser Art von Leistung.',
'Sie ist ideal für eine langfristige und zuverlässige Zusammenarbeit.'
]
}
]
</script> </script>

View file

@ -1,18 +1,18 @@
<template> <template>
<footer class="Footer flex-col gap-default"> <footer class="Footer flex-col gap-default">
<ul class="sitemap gap-default"> <ul class="row sitemap gap-default">
<li v-for="{ label, ...rest} in nav" :key="label"> <li v-for="{ label, ...rest} in nav" :key="label">
<NuxtLink v-bind="rest">{{label}}</NuxtLink> <NuxtLink v-bind="rest">{{label}}</NuxtLink>
</li> </li>
</ul> </ul>
<ul class="sitemap gap-default"> <ul class="row sitemap gap-default">
<li v-for="({icon, ...rest}) in socials" :key="rest.href"> <li v-for="({icon, ...rest}) in socials" :key="rest.href">
<a v-bind="rest" target="_blank"> <a v-bind="rest" target="_blank">
<Icon :name="icon" :alt="rest['aria-label']" size="1.5em" mode="svg" /> <Icon :name="icon" :alt="rest['aria-label']" size="1.5em" mode="svg" />
</a> </a>
</li> </li>
</ul> </ul>
<ul class="sitemap gap-default"> <ul class="row sitemap gap-default">
<li class="tip-container"> <li class="tip-container">
<Icon name="wf:cookie-slash" size="1.5rem" mode="svg" /> <Icon name="wf:cookie-slash" size="1.5rem" mode="svg" />
<span class="tip">Ohne Cookies</span> <span class="tip">Ohne Cookies</span>

View file

@ -12,7 +12,7 @@
<Icon name="ph:waves" mode="svg" size="2em" /> <Icon name="ph:waves" mode="svg" size="2em" />
</button> </button>
<nav> <nav>
<ul class="main-nav"> <ul class="row main-nav">
<li v-for="({label, to, aria, icon}) in navigation" :key="label"> <li v-for="({label, to, aria, icon}) in navigation" :key="label">
<NuxtLink :to="to" :aria-label="aria" active-class="active" class="inline-flex-row big-gap"> <NuxtLink :to="to" :aria-label="aria" active-class="active" class="inline-flex-row big-gap">
<Icon :name="icon" mode="svg" /> <Icon :name="icon" mode="svg" />

View file

@ -9,7 +9,7 @@
</div> </div>
<Card title="Technologien" titleTag="h3" class="margin-top tech-list"> <Card title="Technologien" titleTag="h3" class="margin-top tech-list">
<p>Neben den klassischen Webentwicklungsstandards JavaScript, HTML und CSS biete ich außerdem folgende Technologien.</p> <p>Neben den klassischen Webentwicklungsstandards JavaScript, HTML und CSS biete ich außerdem folgende Technologien.</p>
<ul class="gap-default margin-top-small"> <ul class="row gap-default margin-top-small">
<li v-for="tech in technologies"> <li v-for="tech in technologies">
<Technology v-bind="tech" size="l"/> <Technology v-bind="tech" size="l"/>
</li> </li>

View file

@ -1,16 +1,15 @@
<template> <template>
<details class="Spoiler" :open="open" @click="toggle"> <details class="Spoiler" :open="open">
<summary><Icon class="icon" :name="icon" mode="svg" />{{ header }}</summary> <summary @click="toggle"><Icon class="icon" :name="icon" mode="svg" />{{ title }}</summary>
<div> <div>
<p v-for="text in content">{{ text }}</p> <slot />
</div> </div>
</details> </details>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
type Props = { type Props = {
header: string title: string
content: string[]
} }
defineProps<Props>() defineProps<Props>()

View file

@ -18,6 +18,14 @@ export default defineContentConfig({
position: z.string(), position: z.string(),
}), }),
}) })
}),
faq: defineCollection({
type: 'page',
source: 'snippets/faq/*.md',
schema: z.object({
title: z.string(),
})
}) })
} }
}) })

View file

@ -0,0 +1,58 @@
::spoiler
---
title: "Warum machst du keine Stundensätze?"
---
Ich finde, Stundensätze haben für beide Seiten nur Nachteile:
Wenn ich schnell und gut arbeite, bekomme ich weniger Geld. Hab ich mal einen Knoten im Gehirn und brauche sehr lange, muss der Kunde mehr zahlen.
Klar kann man sagen, dass sich das irgendwann ausgleichen könnte aber so weit will ich es gar nicht erst kommen lassen.
::
::spoiler
---
title: "Was bekomme ich denn für die 999 €?"
---
Grundsätzlich kommt das natürlich immer darauf an, was du genau willst. Da spielen einige Faktoren eine Rolle.
Ein Beispiel für eine 999 € Seite wäre:
- Kein CMS
- 2 Sections auf der Landingpage
- Impressum, Datenschutzerklärung
- Standarddesign
Je nach dem was du willst, kann sich das aber auch anders zusammensetzen.
::
::spoiler
---
title: "Welche Themen bietest du für deine Schulungen an?"
---
<div>
<Highlight>Sprachen</Highlight>
<br />JavaScript, TypeScript, HTML, CSS
</div>
<div>
<Highlight>Frameworks</Highlight>
<br />Vue, Nuxt
</div>
::
::spoiler
---
title: "Wo finden die Schulungen statt?"
---
Die Schulungen finden <Highlight>online/Highlight> statt. Normalerweise nutze ich dafür Google Meet, aber wenn du oder deine Firma eine andere Plattform wünschen und bereitstellen bin ich natürlich flexibel.
Wenn sich deine Firma in der Nähe meines Wohnortes befindet und damit meine ich "In einer Stunde mit der Straßenbahn zu erreichen", dann kann alles natürlich auch vor Ort stattfinden.
::
::spoiler
---
title: "Ich hab ein cooles Projekt! Aber kein Geld …"
---
Tja.
Ne, awas. Meld dich einfach trotzdem über meine E-Mail-Adresse und vielleicht finden wir eine Lösung.
::

View file

@ -0,0 +1,53 @@
::spoiler
---
title: "Was ist eine Entwickler-Flatrate?"
---
Die Entwickler-Flatrate ist ein Angebot, bei dem du für eine bestimmte Zeit eine bestimmte Menge an Leistungen erhältst.
Sie wird auch oft als sogenannter "Retainer" bezeichnet und ist eine günstige Art, immer wieder anfallende Aufgaben auszulagern.
::
::spoiler
---
title: "Wie läuft die Zusammenarbeit ab?"
---
Nach einer ersten Analyse deines Projekts legen wir gemeinsam den Umfang und die monatlichen Aufgaben fest. Du kannst mich je nach Paket auf den vereinbarten Wegen jederzeit kontaktieren.
Die Bearbeitung erfolgt innerhalb der vereinbarten Fristen.
::
::spoiler
---
title: "Wie wird abgerechnet?"
---
Du zahlst monatlich einen festen Betrag im Prepaid-Format unabhängig davon, wie viele Aufgaben tatsächlich anfallen.
Dadurch kannst du dein Budget besser einplanen und Aufgaben verteilen.
::
::spoiler
---
title: "Kann ich den Retainer jederzeit kündigen?"
---
Ja klar. Du musst dabei nur die Mindestvertragslaufzeit beachten die bezahlte Leistung steht dir weiterhin zu.
Falls wir merken, dass wir für eine Zusammenarbeit mehr als ungeeignet sind, bekommst du dein Geld anteilig der verbleibenden Zeit zurück und verlierst den Anspruch auf Leistungen ab diesem Zeitpunkt.
::
::spoiler
---
title: "Was passiert, wenn ich dich mal weniger brauche?"
---
Der monatliche feste Betrag bleibt bestehen. Ähnlich wie bei einer Versicherung bezahlst du hier für den Fall, dass du mich brauchst, und erhältst dann entsprechend die vereinbarten Leistungen.
Wenn du merkst, dass du mich nicht mehr brauchst, dann kannst du den Vertrag jederzeit kündigen und die Leistungen auslaufen lassen.
::
::spoiler
---
title: "Für wen lohnt sich sowas überhaupt?"
---
Vor allem Unternehmen, bei denen immer wieder Aufgaben anfallen, für die sich aber kein komplettes Projekt oder gar eine feste Stelle in der Firma lohnt, profitieren von dieser Art von Leistung.
Sie ist ideal für eine langfristige und zuverlässige Zusammenarbeit.
::