ADD: Components

Teaser, Card
This commit is contained in:
webfussel 2025-05-29 13:11:15 +02:00
parent 8943512328
commit c8e4bf4a14
8 changed files with 127 additions and 48 deletions

View file

@ -17,22 +17,6 @@
& .bottom { & .bottom {
align-items: center; align-items: center;
} }
& .image-container {
display: flex;
align-items: center;
justify-content: center;
}
& .skill-card {
display: flex;
flex-direction: row;
align-items: center;
&.reverse {
flex-direction: row-reverse;
}
}
} }
@media (width <= 1601px) { @media (width <= 1601px) {

43
app/assets/css/teaser.css Normal file
View file

@ -0,0 +1,43 @@
.Teaser {
display: flex;
flex-direction: row;
align-items: center;
& .image-container {
display: flex;
align-items: center;
justify-content: center;
}
&.right {
flex-direction: row-reverse;
}
}
@media (width <= 1601px) {
.Teaser {
flex: 1 0 300px;
flex-direction: column;
text-align: center;
justify-content: start;
&.right {
flex-direction: column;
}
& h3 {
text-align: center;
}
& .text-container,
& .text-container main {
height: 100%;
}
& .image-container img {
width: 100%;
height: auto;
}
}
}

15
app/components/Card.vue Normal file
View file

@ -0,0 +1,15 @@
<template>
<article class="z-2 card flex-col gap-sm">
<component :is="titleTag ?? 'h3'">{{ title }}</component>
<slot />
</article>
</template>
<script setup lang="ts">
type Props = {
title : string
titleTag ?: 'strong' | 'h3'
}
defineProps<Props>()
</script>

View file

@ -3,20 +3,18 @@
<h2>Meine Expertise.</h2> <h2>Meine Expertise.</h2>
<h3>Dies sind meine <span class="highlight">Spezialgebiete</span> - aber ich bin flexibel!</h3> <h3>Dies sind meine <span class="highlight">Spezialgebiete</span> - aber ich bin flexibel!</h3>
<div class="skill-list margin-top gap-default"> <div class="skill-list margin-top gap-default">
<article class="z-2 card flex-col gap-sm" v-for="skill in skills"> <Card v-for="skill in skills" :title="skill.title" titleTag="h3">
<h3>{{skill.title}}</h3>
<p v-for="(t, i) in skill.text" :class="[i === skill.text.length - 1 && 'bold']">{{t}}</p> <p v-for="(t, i) in skill.text" :class="[i === skill.text.length - 1 && 'bold']">{{t}}</p>
</article> </Card>
</div> </div>
<article class="tech-list z-2 card flex-col gap-default margin-top"> <Card title="Technologien" titleTag="h3" class="margin-top">
<h3>Technologien</h3>
<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"> <ul class="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>
</ul> </ul>
</article> </Card>
<div class="bottom flex-col margin-top gap-default"> <div class="bottom flex-col margin-top gap-default">
<h3>Manche von euch haben hier sicher kein Wort verstanden.</h3> <h3>Manche von euch haben hier sicher kein Wort verstanden.</h3>
<Button href="#skills_easy" class="cta"> <Button href="#skills_easy" class="cta">

View file

@ -3,25 +3,13 @@
<h2>Jetzt mal ganz konkret.</h2> <h2>Jetzt mal ganz konkret.</h2>
<h3>In diesem Abschnitt ganz <span class="highlight">ohne Technik-Blabla</span>.</h3> <h3>In diesem Abschnitt ganz <span class="highlight">ohne Technik-Blabla</span>.</h3>
<div class="skill-container flex-col margin-top gap-default"> <div class="skill-container flex-col margin-top gap-default">
<article class="skill-card z-2 card gap-default" v-for="(skill, skillIndex) in skills" :class="[skillIndex % 2 && 'reverse']"> <Teaser
<div class="image-container"> v-for="(skill, skillIndex) in skills"
<img :title="skill.title"
loading="lazy" :image="{ path: '/img/explanations/', name : skill.img, position: skillIndex % 2 ? 'right' : 'left'}"
width="550" >
height="350"
:srcset="[skill.img('1x', true), skill.img('2x', true), skill.img('3x', true)].join(', ')"
:src="skill.img('1x', false)"
aria-hidden="true"
alt=""
/>
</div>
<div class="text-container flex-col gap-default">
<h3>{{skill.title}}</h3>
<main class="flex-col gap-sm">
<RichText :elements="skill.text" /> <RichText :elements="skill.text" />
</main> </Teaser>
</div>
</article>
</div> </div>
<div class="bottom flex-col margin-top gap-default"> <div class="bottom flex-col margin-top gap-default">
<h3>Verwirkliche jetzt dein Webprojekt.</h3> <h3>Verwirkliche jetzt dein Webprojekt.</h3>
@ -37,7 +25,7 @@
import type { RichText } from '@/components/RichText/Types' import type { RichText } from '@/components/RichText/Types'
type Skill = { type Skill = {
img: Function img: string
title: string title: string
text: RichText[] text: RichText[]
} }
@ -46,7 +34,7 @@ const getExplanationImage = (img : string) => getImage('/img/explanations/', img
const skills : Skill[] = [ const skills : Skill[] = [
{ {
img: getExplanationImage('components'), img: 'components',
title: 'Das, was du sehen kannst', title: 'Das, was du sehen kannst',
text: [ text: [
{ {
@ -106,7 +94,7 @@ const skills : Skill[] = [
}, },
], ],
}, { }, {
img: getExplanationImage('cms'), img: 'cms',
title: 'Da, wo du eintragen kannst', title: 'Da, wo du eintragen kannst',
text: [ text: [
{ {
@ -207,7 +195,7 @@ const skills : Skill[] = [
}, },
], ],
}, { }, {
img: getExplanationImage('result'), img: 'result',
title: 'Was dabei am Ende rauskommt', title: 'Was dabei am Ende rauskommt',
text: [ text: [
{ {

42
app/components/Teaser.vue Normal file
View file

@ -0,0 +1,42 @@
<template>
<article
class="Teaser z-2 card flex-col gap-sm"
:class="[image.position ?? 'left']"
>
<div class="image-container">
<img
loading="lazy"
width="550"
height="350"
:srcset="imageSet.join(', ')"
:src="initialImage"
aria-hidden="true"
alt=""
/>
</div>
<div class="text-container flex-col gap-default">
<h3>{{title}}</h3>
<main class="flex-col gap-sm">
<slot />
</main>
</div>
</article>
</template>
<script setup lang="ts">
import { getImageSet, getInitialImage } from '../utils/image'
type Props = {
title : string
image : {
path : string
name : string
position ?: 'left' | 'right'
}
}
const { image } = defineProps<Props>()
const imageSet = getImageSet(image.path, image.name)
const initialImage = getInitialImage(image.path, image.name)
</script>

View file

@ -1,3 +1,11 @@
type Size = '1x' | '2x' | '3x'
const allSizes : Size[] = ['1x', '2x', '3x']
export const getImage = export const getImage =
(path: string, img: string) => (size: "1x" | "2x" | "3x", set: boolean) => (path: string, img: string) => (size: Size, set: boolean) =>
`${path}${img}@${size}.webp${set ? ` ${size}` : ""}`; `${path}${img}@${size}.webp${set ? ` ${size}` : ''}`;
export const getImageSet = (path: string, img: string) =>
allSizes.map((size) => getImage(path, img)(size, true))
export const getInitialImage = (path: string, img: string) => getImage(path, img)('1x', false)

View file

@ -32,6 +32,7 @@ export default defineNuxtConfig({
'~/assets/css/button.css', '~/assets/css/button.css',
'~/assets/css/spoiler.css', '~/assets/css/spoiler.css',
'~/assets/css/burger.css', '~/assets/css/burger.css',
'~/assets/css/teaser.css',
], ],
postcss: { postcss: {