ADD: Pricing charts layout
Finished Pricing Charts
This commit is contained in:
parent
84a295f011
commit
a1a711b015
7 changed files with 300 additions and 97 deletions
|
@ -133,13 +133,27 @@ span.highlight {
|
|||
}
|
||||
|
||||
span.chip {
|
||||
background: var(--color-orange);
|
||||
background: var(--background);
|
||||
color: var(--color);
|
||||
border-radius: 999px;
|
||||
font-size: 1rem;
|
||||
color: var(--color-black);
|
||||
height: max-content;
|
||||
padding: .5em 1em;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
width: max-content;
|
||||
|
||||
&:not(.dark) {
|
||||
--background: var(--color-orange);
|
||||
--color: var(--color-black);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
--background: var(--color-orange-dark);
|
||||
--color: var(--color-white);
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
.Pricing {
|
||||
display: grid;
|
||||
grid-template-rows: 150px auto auto auto 1fr;
|
||||
grid-template-rows: auto auto 1fr;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-areas: "left middle right" "left middle right" "left middle right";
|
||||
|
||||
& article {
|
||||
display: grid;
|
||||
|
@ -8,42 +10,85 @@
|
|||
grid-row: 1 / -1;
|
||||
background-color: var(--color-orange-black);
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
&:nth-child(2) {
|
||||
&:nth-child(1) {
|
||||
border-top-left-radius: 20px;
|
||||
border-bottom-left-radius: 20px;
|
||||
}
|
||||
|
||||
&:nth-child(4) {
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
grid-area: left;
|
||||
}
|
||||
|
||||
&:nth-child(3) {
|
||||
border-top-right-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
grid-area: right;
|
||||
}
|
||||
|
||||
&:nth-child(2) {
|
||||
z-index: 100;
|
||||
scale: 1.05;
|
||||
border-radius: 20px;
|
||||
grid-area: middle;
|
||||
}
|
||||
|
||||
& .price {
|
||||
font-size: 3rem;
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: .5rem;
|
||||
|
||||
& .post {
|
||||
font-size: .8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
& .claim {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
& .conmin {
|
||||
font-size: .8rem;
|
||||
color: var(--color-white-transparent);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
& ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
& li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
color: var(--color-white);
|
||||
|
||||
& .icon {
|
||||
flex: 0 0 1.5rem;
|
||||
|
||||
&.yes {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
&.no {
|
||||
color: var(--color-white-transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
& .value {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
|
||||
&.fat {
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
&.odd {
|
||||
background: var(--color-black);
|
||||
}
|
||||
}
|
||||
|
||||
& header,
|
||||
& main,
|
||||
& footer {
|
||||
padding: var(--spacing-standard);
|
||||
padding: 1.5rem var(--spacing-standard);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
@ -51,21 +96,15 @@
|
|||
|
||||
& footer {
|
||||
align-items: center;
|
||||
color: var(--color-orange);
|
||||
gap: .5rem;
|
||||
|
||||
& .price {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
&:nth-child(3) header {
|
||||
&:nth-child(2) header {
|
||||
background: var(--color-orange);
|
||||
color: var(--color-black);
|
||||
}
|
||||
|
||||
&:not(:nth-child(3)) header {
|
||||
&:not(:nth-child(2)) header {
|
||||
background: var(--color-orange-dark);
|
||||
color: var(--color-white);
|
||||
}
|
||||
|
@ -90,3 +129,20 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 1400px) {
|
||||
.Pricing {
|
||||
grid-template-rows: auto auto 1fr auto auto 1fr auto auto 1fr;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas: "middle" "middle" "middle" "left" "left" "left" "right" "right" "right";
|
||||
|
||||
& article {
|
||||
margin-bottom: 2rem;
|
||||
border-radius: 20px;
|
||||
|
||||
&:nth-child(2) {
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
app/assets/css/spoiler.css
Normal file
34
app/assets/css/spoiler.css
Normal file
|
@ -0,0 +1,34 @@
|
|||
.Spoiler {
|
||||
background: var(--color-black);
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 20px;
|
||||
|
||||
& .icon {
|
||||
color: var(--color-orange);
|
||||
}
|
||||
|
||||
& summary {
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
& > div {
|
||||
margin-top: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (width <= 820px) {
|
||||
.Spoiler {
|
||||
& summary {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,12 @@
|
|||
<section id="services" class="Services content">
|
||||
<h2>Services.</h2>
|
||||
<h3>Du hast also beschlossen, dass du <span class="highlight">meine Hilfe</span> brauchst. Cool!</h3>
|
||||
<p class="margin-top">Hinter meinen Angeboten gibt es <span class="highlight">keinerlei Abos oder versteckte Kosten</span>.
|
||||
<p class="margin-top">Hinter diesen Angeboten gibt es <span class="highlight">keinerlei Abos oder versteckte Kosten</span>.
|
||||
Aus Transparenzgründen sei aber gesagt, dass sich <span class="highlight">*alle Preise zzgl. 19 % Umsatzsteuer</span>. verstehen.</p>
|
||||
|
||||
<article class="z-2 card flex-col margin-top font-big text-center">
|
||||
<p>Derzeit habe ich <span class="highlight">keine freien Plätze</span>.</p>
|
||||
<p class="margin-top-small">Das ändert sich ab <span class="highlight">01. Juli 2025</span>.</p>
|
||||
<p class="margin-top-small">Das ändert sich ab <span class="highlight">01. Januar 2026</span>.</p>
|
||||
</article>
|
||||
|
||||
<h3 class="margin-top-big">One off Projekte</h3>
|
||||
|
@ -19,22 +19,26 @@
|
|||
<header>
|
||||
<strong>{{service.title}}</strong>
|
||||
<p>{{service.smallClaim}}</p>
|
||||
<div class="price">
|
||||
<span v-if="service.price.pre">{{service.price.pre}}</span>
|
||||
<span>{{typeof service.price.value === 'number' ? intl.format(service.price.value) : service.price.value}}</span>
|
||||
<span v-if="service.price.post" class="post">{{service.price.post}}</span>
|
||||
</div>
|
||||
<div aria-hidden="true" class="bg-icon">
|
||||
<Icon :name="`ph:${service.icon}-thin`" size="1.5em" mode="svg" />
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div class="list-container">
|
||||
<ul class="list" v-for="(list, index) in service.list" :key="index">
|
||||
<li v-for="point in list">
|
||||
<Icon class="point-icon" name="ph:caret-circle-double-right-duotone" size="1.5em" mode="svg" />
|
||||
<ul>
|
||||
<li v-for="point in service.list">
|
||||
<Icon class="icon yes" name="ph:caret-circle-double-right-duotone" size="1.5em" mode="svg" />
|
||||
<span class="label">{{ point }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<span class="price">{{service.price.pre ? `${service.price.pre} ` : '' }}{{typeof service.price.value === 'number' ? intl.format(service.price.value) : service.price.value}}</span>
|
||||
<div class="button-wrapper z-2">
|
||||
<Button :href="service.link" aria-label="Zur externen Seite Terminbuchung" :design="index === 1 ? 'default' : 'white'">
|
||||
{{ service.button }}
|
||||
|
@ -52,10 +56,6 @@
|
|||
<h3>Keine Kohle? Kommt vor.</h3>
|
||||
<p>Meld dich trotzdem. Eventuell ist dein Projekt ja cool genug, dass ich dir da auch entsprechend entgegenkommen kann. :)</p>
|
||||
</article>
|
||||
<h3 id="network" class="margin-top-big">Mein Netzwerk</h3>
|
||||
<p class="margin-top">Doch auch wenn ich mal voll ausgelastet bin - keine Sorge!
|
||||
Mein <span class="highlight">Netzwerk an Profis</span> kann dir sicher auch weiterhelfen.
|
||||
</p>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
@ -66,11 +66,12 @@ type Service = {
|
|||
price: {
|
||||
value: number | string
|
||||
pre?: string
|
||||
post?: string
|
||||
}
|
||||
icon: string
|
||||
button: string
|
||||
link: string
|
||||
list: string[][]
|
||||
list: string[]
|
||||
}
|
||||
|
||||
const intl = new Intl.NumberFormat(
|
||||
|
@ -93,15 +94,11 @@ const oneOff : Service[] = [
|
|||
button: 'Jetzt untersuchen',
|
||||
link: 'https://tidycal.com/webfussel/quick-check',
|
||||
list: [
|
||||
[
|
||||
'Untersuchung des Quellcodes',
|
||||
'Untersuchung der Performance',
|
||||
'Tipps zu CSS und Best Practices',
|
||||
],
|
||||
[
|
||||
'Behebung unkompliziert nachbuchen',
|
||||
'Für selbst gebaute Seiten',
|
||||
]
|
||||
],
|
||||
}, {
|
||||
title: 'Projektbuchung',
|
||||
|
@ -114,35 +111,29 @@ const oneOff : Service[] = [
|
|||
button: 'Jetzt durchstarten',
|
||||
link: 'https://tidycal.com/webfussel/project-booking',
|
||||
list: [
|
||||
[
|
||||
'Anforderungsanalyse',
|
||||
'Kontinuierliche Projekt-Updates',
|
||||
'Fixe Kosten und Feature-Sets',
|
||||
],
|
||||
[
|
||||
'Nur 50 % Projektpreis als Anzahlung',
|
||||
'Gestaffelte Abrechnung nach Milestones',
|
||||
]
|
||||
],
|
||||
}, {
|
||||
title: 'Enterprise',
|
||||
title: 'Schulung',
|
||||
price: {
|
||||
value: 'Auf Anfrage',
|
||||
pre: 'ab',
|
||||
value: 1499,
|
||||
post: '/ Tag / Person',
|
||||
},
|
||||
smallClaim: 'Für die ganz großen Sachen.',
|
||||
icon: 'building-office',
|
||||
smallClaim: 'Wenn man\'s selber können muss.',
|
||||
icon: 'graduation-cap',
|
||||
button: 'Frag nach!',
|
||||
link: 'https://tidycal.com/webfussel/project-booking',
|
||||
list: [
|
||||
[
|
||||
'Anforderungsanalyse',
|
||||
'Kontinuierliche Projekt-Updates',
|
||||
'Fixe Kosten und Feature-Sets',
|
||||
],
|
||||
[
|
||||
'Nur 50 % Projektpreis als Anzahlung',
|
||||
'Gestaffelte Abrechnung nach Milestones'
|
||||
],
|
||||
'Team-Workshops',
|
||||
'Azubi-Betreuung',
|
||||
'1:1 Mentoring',
|
||||
'Abrechnung pro Person und Tag',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
|
|
@ -4,35 +4,45 @@
|
|||
<h3>Genieße fusselige Qualität ohne groß herumzurechnen.</h3>
|
||||
|
||||
<p class="margin-top-small">Bei dir fällt ständig was an oder du hast ein langlaufendes Projekt, bei dem du immer wieder mal Unterstützung brauchst? Kein Ding.
|
||||
Hier gibt's die <span class="highlight">Entwickler-Flat</span> für planbare Kosten und On-Demand-Entwicklung.</p>
|
||||
Hier gibt's die <span class="highlight">Entwickler-Flat</span> für planbare Kosten und On-Demand-Entwicklung. Aus Transparenzgründen sei aber gesagt, dass sich <span class="highlight">*alle Preise zzgl. 19 % Umsatzsteuer</span>. verstehen.</p>
|
||||
|
||||
<div class="Pricing margin-top">
|
||||
<article>
|
||||
<div style="place-self: end; height: 1px;"/>
|
||||
<p class="fat value">Gleichzeitige Tickets</p>
|
||||
<p class="fat value odd">Erreichbarkeit</p>
|
||||
<p class="fat value">Monatlicher Preis</p>
|
||||
<div />
|
||||
</article>
|
||||
<article v-for="(service, index) in flatrate" :class=" { 'z-2' : index === 1, 'z-1' : index !== 1 }">
|
||||
<header>
|
||||
<strong>{{service.title}}</strong>
|
||||
<p>{{service.smallClaim}}</p>
|
||||
<span v-if="service.best" class="chip dark z-2"><Icon name="ph:fire-duotone" mode="svg"/> Beschd</span>
|
||||
<strong class="margin-top-small">{{service.title}}</strong>
|
||||
<p class="claim">{{service.smallClaim}}</p>
|
||||
<p class="price">{{intl.format(service.price)}}</p>
|
||||
<div aria-hidden="true" class="bg-icon">
|
||||
<Icon :name="`ph:${service.icon}-thin`" size="1.5em" mode="svg" />
|
||||
</div>
|
||||
</header>
|
||||
<p v-for="(point, pIndex) in service.list" class="value" :class="{ odd: pIndex === 1}">{{ typeof point === 'number' ? intl.format(point) : point }}</p>
|
||||
<main>
|
||||
<ul>
|
||||
<li>
|
||||
<Icon class="icon yes" name="ph:check-circle-duotone" mode="svg" />
|
||||
<span class="value">{{ service.hours }} Stunden pro Woche zugesichert</span>
|
||||
</li>
|
||||
<li v-for="(check, index) in service.checks">
|
||||
<Icon class="icon" :class="{ 'yes' : check, 'no' : !check }" :name="`ph:${check ? 'check' : 'x'}-circle-duotone`" mode="svg" />
|
||||
<span class="value">{{ points[index] }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</main>
|
||||
<footer>
|
||||
<!-- <span class="price">{{intl.format(service.price)}}</span>-->
|
||||
<div class="button-wrapper z-2">
|
||||
<Button :href="service.link" aria-label="Zur externen Seite Terminbuchung" :design="index === 1 ? 'default' : 'white'">
|
||||
{{ service.button }}
|
||||
</Button>
|
||||
</div>
|
||||
<p class="conmin margin-top-small">Die Mindestvertragslaufzeit beträgt {{ service.contractMin }} Monate.</p>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="flex-col gap-sm margin-top">
|
||||
<Spoiler v-for="entry in faq" v-bind="entry" />
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
@ -43,7 +53,11 @@ type Service = {
|
|||
icon: string
|
||||
button: string
|
||||
link: string
|
||||
list: (string | number)[]
|
||||
best: boolean
|
||||
price: number
|
||||
hours: number
|
||||
contractMin: number
|
||||
checks: boolean[]
|
||||
}
|
||||
|
||||
const intl = new Intl.NumberFormat(
|
||||
|
@ -55,6 +69,13 @@ const intl = new Intl.NumberFormat(
|
|||
maximumFractionDigits: 0,
|
||||
})
|
||||
|
||||
const points : string[] = [
|
||||
'Kontakt per E-Mail',
|
||||
'Kontakt per Instant Messaging',
|
||||
'Kontakt per Video Call',
|
||||
'Weiterführende Performance Optimierung',
|
||||
'Framework Migration',
|
||||
]
|
||||
|
||||
const flatrate : Service[] =
|
||||
[
|
||||
|
@ -64,10 +85,16 @@ const flatrate : Service[] =
|
|||
icon: 'baby-carriage',
|
||||
button: 'Jetzt klar machen',
|
||||
link: 'https://tidycal.com/webfussel/flatrate-casual',
|
||||
list: [
|
||||
'1',
|
||||
'E-Mail',
|
||||
2950,
|
||||
best: false,
|
||||
price: 2950,
|
||||
hours: 5,
|
||||
contractMin: 3,
|
||||
checks: [
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -76,10 +103,16 @@ const flatrate : Service[] =
|
|||
icon: 'coins',
|
||||
button: 'Jetzt Gold schürfen',
|
||||
link: 'https://tidycal.com/webfussel/flatrate-gold-fussel',
|
||||
list: [
|
||||
'2',
|
||||
'E-Mail, Instant-Messaging',
|
||||
4950,
|
||||
best: true,
|
||||
price: 4950,
|
||||
hours: 10,
|
||||
contractMin: 3,
|
||||
checks: [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -88,11 +121,62 @@ const flatrate : Service[] =
|
|||
icon: 'skull',
|
||||
button: 'Jetzt Fett trimmen',
|
||||
link: 'https://tidycal.com/webfussel/flatrate-big-chonker',
|
||||
list: [
|
||||
'3',
|
||||
'E-Mail, Instant-Messaging, Video-Chat',
|
||||
8950,
|
||||
best: false,
|
||||
price: 8950,
|
||||
hours: 15,
|
||||
contractMin: 6,
|
||||
checks: [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
],
|
||||
}
|
||||
]
|
||||
|
||||
const faq = [
|
||||
{
|
||||
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 Fall 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>
|
||||
|
|
23
app/components/Spoiler.vue
Normal file
23
app/components/Spoiler.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<details class="Spoiler" :open="open" @click="toggle">
|
||||
<summary><Icon class="icon" :name="`ph:${open ? 'minus' : 'plus'}-circle-duotone`" mode="svg" />{{ header }}</summary>
|
||||
<div>
|
||||
<p v-for="text in content">{{ text }}</p>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
type Props = {
|
||||
header: string
|
||||
content: string[]
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const open = ref(false)
|
||||
const toggle = (event) => {
|
||||
event.preventDefault()
|
||||
open.value = !open.value
|
||||
}
|
||||
</script>
|
|
@ -25,6 +25,7 @@ export default defineNuxtConfig({
|
|||
'~/assets/css/technology.css',
|
||||
'~/assets/css/person.css',
|
||||
'~/assets/css/button.css',
|
||||
'~/assets/css/spoiler.css',
|
||||
],
|
||||
|
||||
postcss: {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue