First draft
This commit is contained in:
98
src/components/bits/BlogCard.astro
Normal file
98
src/components/bits/BlogCard.astro
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
|
||||
interface Props {
|
||||
blogPost: CollectionEntry<"blog">;
|
||||
}
|
||||
|
||||
const { blogPost } = Astro.props;
|
||||
---
|
||||
|
||||
<a href=`/blog/${blogPost.id}` id="card-link">
|
||||
<section class="glass">
|
||||
<h2>{blogPost.data.title}</h2>
|
||||
<hr />
|
||||
<p>{blogPost.body?.substring(0, 150)}{blogPost.body && blogPost.body.length > 150 ? "..." : ""}</p>
|
||||
{ blogPost.data.tags ?
|
||||
<div id="tag-area">Tags: <ul>{blogPost.data.tags.map(tag => <li>{ tag }</li>)}</ul></div>
|
||||
: null
|
||||
}
|
||||
</section>
|
||||
</a>
|
||||
|
||||
<style lang="scss">
|
||||
#card-link {
|
||||
display: inline-block;
|
||||
width: min-content;
|
||||
min-width: 250px;
|
||||
max-width: 350px;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
section {
|
||||
--border-width: 1px;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
z-index: -9999;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
border: 5px solid var(--glass-color);
|
||||
filter: blur(4px);
|
||||
opacity: 0;
|
||||
transition: opacity .3s, filter 1s;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 1;
|
||||
animation: glow 3s infinite cubic-bezier(.45,.05,.55,.95);
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% {
|
||||
filter: blur(4px);
|
||||
}
|
||||
50% {
|
||||
filter: blur(9px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#tag-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
ul {
|
||||
list-style: "#";
|
||||
list-style-position: inside;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: start;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: .3em;
|
||||
|
||||
li {
|
||||
font-size: 80%;
|
||||
border: 1px solid #888;
|
||||
border-radius: .8rem;
|
||||
padding: .3em .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
98
src/components/bits/ProjectCard.astro
Normal file
98
src/components/bits/ProjectCard.astro
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
import type { CollectionEntry } from 'astro:content';
|
||||
|
||||
interface Props {
|
||||
project: CollectionEntry<"projects">;
|
||||
}
|
||||
|
||||
const { project } = Astro.props;
|
||||
---
|
||||
|
||||
<a href=`/projects/${project.id}` id="card-link">
|
||||
<section class="glass">
|
||||
<h2>{project.data.title}</h2>
|
||||
<hr />
|
||||
<p>{project.data.description}</p>
|
||||
{ project.data.tags ?
|
||||
<div id="tag-area">Tags: <ul>{project.data.tags.map(tag => <li>{ tag }</li>)}</ul></div>
|
||||
: null
|
||||
}
|
||||
</section>
|
||||
</a>
|
||||
|
||||
<style lang="scss">
|
||||
#card-link {
|
||||
display: inline-block;
|
||||
width: min-content;
|
||||
min-width: 250px;
|
||||
max-width: 350px;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
scroll-snap-align: start;
|
||||
}
|
||||
|
||||
section {
|
||||
--border-width: 1px;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
z-index: -9999;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
border: 5px solid var(--glass-color);
|
||||
filter: blur(4px);
|
||||
opacity: 0;
|
||||
transition: opacity .3s, filter 1s;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 1;
|
||||
animation: glow 3s infinite cubic-bezier(.45,.05,.55,.95);
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% {
|
||||
filter: blur(4px);
|
||||
}
|
||||
50% {
|
||||
filter: blur(9px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#tag-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: .5em;
|
||||
|
||||
ul {
|
||||
list-style: "#";
|
||||
list-style-position: inside;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: start;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
gap: .3em;
|
||||
|
||||
li {
|
||||
font-size: 80%;
|
||||
border: 1px solid #888;
|
||||
border-radius: .8rem;
|
||||
padding: .3em .5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
37
src/components/bits/SlideCard.astro
Normal file
37
src/components/bits/SlideCard.astro
Normal file
@@ -0,0 +1,37 @@
|
||||
---
|
||||
export interface Props {
|
||||
totalNoOfSlides: number;
|
||||
header: string;
|
||||
[key: string]: any; // Erlaubt zusätzliche Eigenschaften
|
||||
}
|
||||
|
||||
const { totalNoOfSlides, header, ...args } = Astro.props;
|
||||
---
|
||||
|
||||
<div class="slide" {...args}>
|
||||
<h1 class="slide-title">{header}</h1>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.slide {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
scroll-snap-align: start;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.slide-title {
|
||||
display: inline-block;
|
||||
margin: 2rem 2rem;
|
||||
padding: .5rem 3rem;
|
||||
font-weight: bold;
|
||||
font-size: 2em;
|
||||
text-align: center;
|
||||
background: rgba(255, 255, 255, 0.1); /* Halbtransparenter Hintergrund */
|
||||
backdrop-filter: blur(10px); /* Blur-Effekt */
|
||||
/*clip-path: polygon(0 100%, 0 0, calc(100% - 2.5rem) 0, calc(100% - 1.5rem) 100%, 100% 100%, calc(100% - 1rem) 0, calc(100% - 2rem) 0, calc(100% - 1rem) 100%);*/
|
||||
clip-path: polygon(0 0, 1.7rem 0, 2.7rem 100%, calc(100% - 1.7rem) 100%, calc(100% - 2.7rem) 0, calc(100% - 1rem) 0, 100% 100%, calc(100% - 1rem) 100%, calc(100% - 2rem) 0, 1rem 0, 2rem 100%, 1rem 100%);
|
||||
}
|
||||
|
||||
</style>
|
||||
34
src/components/bits/SlideContainer.astro
Normal file
34
src/components/bits/SlideContainer.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
interface Props {
|
||||
anchorLink: string;
|
||||
background?: string;
|
||||
bgImageUrl?: string;
|
||||
}
|
||||
|
||||
const { anchorLink, background, bgImageUrl } = Astro.props;
|
||||
|
||||
const backgroundColor =background ?? "transparent";
|
||||
const backgroundImage = bgImageUrl ? `url(${bgImageUrl})` : "none";
|
||||
---
|
||||
|
||||
<section class="slide-container" id={"slide-" + anchorLink}>
|
||||
<slot name="main-slide" />
|
||||
<slot />
|
||||
</section>
|
||||
|
||||
<style define:vars={{ backgroundColor, backgroundImage }}>
|
||||
.slide-container {
|
||||
background-color: var(--backgroundColor);
|
||||
background-image: var(--backgroundImage);
|
||||
width: 100dvw;
|
||||
height: 100dvh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: start;
|
||||
justify-content: start;
|
||||
scroll-snap-type: x mandatory;
|
||||
scroll-snap-align: start;
|
||||
overflow-x: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
164
src/components/bits/WelcomeTypewriter.astro
Normal file
164
src/components/bits/WelcomeTypewriter.astro
Normal file
@@ -0,0 +1,164 @@
|
||||
---
|
||||
---
|
||||
|
||||
<div id="welcome-typewriter-container">
|
||||
<noscript>
|
||||
<p class="welcome-typewriter-nostyle">Automatisierungstechnik</p>
|
||||
<p class="welcome-typewriter-nostyle">Software, und mehr...</p>
|
||||
</noscript>
|
||||
<p id="welcome-typewriter" style="visibility: hidden;"><span id="welcome-typewriter-text"></span><span id="caret"></span></p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#welcome-typewriter-container, .welcome-typewriter-nostyle {
|
||||
min-height: 1em;
|
||||
font-size: min(4vw, 1.5rem);
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
max-width: 90dvw;
|
||||
font-family: 'Cascadia Code';
|
||||
letter-spacing: .36em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#welcome-typewriter-container .welcome-typewriter-nostyle {
|
||||
border-right: 2px solid orange;
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
overflow-x: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: clip;
|
||||
width: var(--text-length);
|
||||
}
|
||||
|
||||
#welcome-typewriter-container .welcome-typewriter-nostyle:nth-of-type(1) {
|
||||
animation: blink-caret 1s step-end infinite,
|
||||
0s 2s hide-caret forwards,
|
||||
2s text-typing steps(22, end) forwards normal 1;
|
||||
--text-length: 22em;
|
||||
}
|
||||
|
||||
#welcome-typewriter-container .welcome-typewriter-nostyle:nth-of-type(2) {
|
||||
animation: hide-text 0s forwards 1,
|
||||
hide-caret 0s forwards 1,
|
||||
1s 2s blink-caret step-end infinite,
|
||||
2s 2s text-typing steps(22, end) forwards normal 1;
|
||||
--text-length: 19em;
|
||||
}
|
||||
|
||||
#welcome-typewriter-container {
|
||||
white-space: break-spaces;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#caret {
|
||||
display: inline-block;
|
||||
height: 1em;
|
||||
width: 0px;
|
||||
border-right: 2px solid orange;
|
||||
animation: blink-caret 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes hide-text {
|
||||
to { width: 0; }
|
||||
}
|
||||
|
||||
@keyframes blink-caret {
|
||||
from, to { border-color: orange }
|
||||
50% { border-color: transparent; }
|
||||
}
|
||||
|
||||
@keyframes hide-caret {
|
||||
to { border-color: transparent; }
|
||||
}
|
||||
|
||||
@keyframes text-typing {
|
||||
from { width: 0; }
|
||||
to { width: var(--text-length); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
#welcome-typewriter-container .welcome-typewriter-nostyle {
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
|
||||
#caret {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const element = document.getElementById("welcome-typewriter-text");
|
||||
const possibleWords: string[] = [
|
||||
"Automatisierungstechnik",
|
||||
"Software",
|
||||
"Web Development",
|
||||
"Datenbanken",
|
||||
"Katzen",
|
||||
"Frontend",
|
||||
"Backend",
|
||||
"Fullstack",
|
||||
"React",
|
||||
"Node.js",
|
||||
"Python",
|
||||
"C#",
|
||||
"Projektmanagement",
|
||||
"IT-Sicherheit",
|
||||
"Embedded",
|
||||
"Linux",
|
||||
"IoT",
|
||||
"Industrie 4.0",
|
||||
"Software-Architektur",
|
||||
"Windows",
|
||||
"Arduino",
|
||||
"C/C++",
|
||||
"git",
|
||||
"CI/CD"
|
||||
]//.map(word => word.replaceAll(" ", "\u00A0"));
|
||||
|
||||
if (element) {
|
||||
// Initial word setup
|
||||
let currentWord = possibleWords[Math.floor(Math.random() * possibleWords.length)];
|
||||
let currentStep = 0;
|
||||
// Show the element
|
||||
element.parentElement?.style.removeProperty("visibility");
|
||||
|
||||
// Get reduced motion settings from user preferences
|
||||
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
// If the user prefers reduced motion, just show the first word without animation
|
||||
element.innerText = currentWord;
|
||||
} else {
|
||||
// Otherwise, start the typewriter animation}
|
||||
|
||||
window.setInterval(() => {
|
||||
if (currentStep < currentWord.length) {
|
||||
// Add the next character
|
||||
element.innerText += currentWord[currentStep];
|
||||
currentStep++;
|
||||
} else if (currentStep < currentWord.length + 24) { // 1.2s/50ms = 1200/50 steps = 24 steps
|
||||
// Just wait a bit
|
||||
currentStep++;
|
||||
} else if (currentStep < currentWord.length * 2 + 24) {
|
||||
// Remove the last character
|
||||
element.innerText = element.innerText.slice(0, -1);
|
||||
currentStep++;
|
||||
} else if (currentStep < currentWord.length * 2 + 24 + 4) { // 200ms
|
||||
// Wait a bit before the next word
|
||||
currentStep++;
|
||||
} else {
|
||||
// Reset the step and choose new word
|
||||
currentStep = 0;
|
||||
let newWord;
|
||||
do {
|
||||
newWord = possibleWords[Math.floor(Math.random() * possibleWords.length)];
|
||||
} while (newWord === currentWord);
|
||||
currentWord = newWord;
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
124
src/components/bits/WritingCard.astro
Normal file
124
src/components/bits/WritingCard.astro
Normal file
@@ -0,0 +1,124 @@
|
||||
---
|
||||
import { getCollection, type CollectionEntry } from 'astro:content';
|
||||
|
||||
interface Props {
|
||||
chapter: CollectionEntry<"writingChapters">;
|
||||
}
|
||||
const { chapter } = Astro.props;
|
||||
|
||||
const entries = (await getCollection("writing")).filter(entry => entry.data.chapter.id === chapter.id);
|
||||
entries.sort((a, b) => a.data.part - b.data.part);
|
||||
---
|
||||
|
||||
<section class="glass">
|
||||
<hgroup>
|
||||
<h2>{chapter.data.name}</h2>
|
||||
<p>{chapter.data.subtitle}</p>
|
||||
</hgroup>
|
||||
<ol start="0">
|
||||
{ entries.map(entry => (
|
||||
<li><a href={`/code/${entry.id}`}>{entry.data.title}</a></li>
|
||||
)) }
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
section {
|
||||
--border-width: 1px;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
width: 33dvw;
|
||||
min-width: 350px;
|
||||
|
||||
hgroup {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
hgroup h2 {
|
||||
margin: 0;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
hgroup p {
|
||||
margin-left: 1rem;
|
||||
margin-top: .2rem;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -4px;
|
||||
z-index: -9999;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
border: 5px solid var(--glass-color);
|
||||
filter: blur(4px);
|
||||
opacity: 0;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 1;
|
||||
animation: glow 3s infinite cubic-bezier(.45,.05,.55,.95);
|
||||
|
||||
@keyframes glow {
|
||||
0%, 100% {
|
||||
filter: blur(4px);
|
||||
}
|
||||
50% {
|
||||
filter: blur(9px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
counter-reset: ol-counter -1;
|
||||
|
||||
& li {
|
||||
counter-increment: ol-counter;
|
||||
border: 1px solid var(--glass-color);
|
||||
margin: .2rem;
|
||||
border-radius: .5rem;
|
||||
position: relative;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -2px;
|
||||
pointer-events: none;
|
||||
border-radius: inherit;
|
||||
background: transparent;
|
||||
border: 2px solid var(--glass-color);
|
||||
filter: blur(5px);
|
||||
opacity: 0;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
|
||||
&:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
& a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding: .5rem;
|
||||
text-decoration: none;
|
||||
|
||||
&::before {
|
||||
content: counter(ol-counter) ".";
|
||||
flex-shrink: 0;
|
||||
min-width: 1.5rem;
|
||||
color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user