feat: add picture, improve layout
This commit is contained in:
parent
bf720b79ee
commit
b3d158d52c
10 changed files with 361 additions and 168 deletions
BIN
public/files/alexdaichendt.jpg
Normal file
BIN
public/files/alexdaichendt.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 779 KiB |
BIN
src/assets/me.jpg
Normal file
BIN
src/assets/me.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 779 KiB |
|
|
@ -63,3 +63,24 @@ const { title, description, image = "/blog-placeholder-1.jpg" } = Astro.props;
|
||||||
<meta property="twitter:title" content={title} />
|
<meta property="twitter:title" content={title} />
|
||||||
<meta property="twitter:description" content={description} />
|
<meta property="twitter:description" content={description} />
|
||||||
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
<meta property="twitter:image" content={new URL(image, Astro.url)} />
|
||||||
|
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Person",
|
||||||
|
"name": "Alexander Daichendt",
|
||||||
|
"url": "https://daichendt.one/",
|
||||||
|
"image": "https://daichendt.one/files/alexdaichendt.jpg",
|
||||||
|
"sameAs": ["https://github.com/alexdaichendt"],
|
||||||
|
"jobTitle": "IT Consultant",
|
||||||
|
"worksFor": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "Freelance"
|
||||||
|
},
|
||||||
|
"alumniOf": {
|
||||||
|
"@type": "CollegeOrUniversity",
|
||||||
|
"name": "Technical University Munich"
|
||||||
|
},
|
||||||
|
"description": "Short description about you and your expertise."
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,5 @@ const { title, subtitle } = Astro.props;
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div class="mt-8 w-24 h-1 bg-blue-600 mx-auto rounded-full"></div>
|
<div class="mt-8 w-24 h-1 bg-mytheme-400 mx-auto rounded-full"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,5 @@ import { Picture as AstroPicture } from "astro:assets";
|
||||||
src={Astro.props.src}
|
src={Astro.props.src}
|
||||||
alt={Astro.props.alt}
|
alt={Astro.props.alt}
|
||||||
formats={["avif", "webp"]}
|
formats={["avif", "webp"]}
|
||||||
|
{...Astro.props}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,70 @@
|
||||||
) as NodeListOf<HTMLInputElement>;
|
) as NodeListOf<HTMLInputElement>;
|
||||||
const sliders = document.querySelectorAll(".slider");
|
const sliders = document.querySelectorAll(".slider");
|
||||||
|
|
||||||
|
// Function to animate theme transition
|
||||||
|
function animateThemeTransition(
|
||||||
|
toggleButton: HTMLElement,
|
||||||
|
isDarkMode: boolean,
|
||||||
|
) {
|
||||||
|
// Set animation to start from top right corner
|
||||||
|
const centerX = window.innerWidth - 20; // 20px from right edge
|
||||||
|
const centerY = 20; // 20px from top edge
|
||||||
|
|
||||||
|
// Calculate radius needed to cover entire screen from top right
|
||||||
|
const maxDistance = Math.sqrt(
|
||||||
|
Math.pow(centerX, 2) + Math.pow(window.innerHeight - centerY, 2),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create a temporary pseudo-element animation using CSS custom properties
|
||||||
|
const newThemeColor = isDarkMode ? "rgb(17, 24, 39)" : "rgb(255, 255, 255)";
|
||||||
|
|
||||||
|
// Apply the animation using CSS custom properties
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--theme-transition-color",
|
||||||
|
newThemeColor,
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--theme-transition-x",
|
||||||
|
`${centerX}px`,
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--theme-transition-y",
|
||||||
|
`${centerY}px`,
|
||||||
|
);
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--theme-transition-radius",
|
||||||
|
"0px",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add animation class but don't change theme colors yet
|
||||||
|
document.body.classList.add("theme-transitioning");
|
||||||
|
|
||||||
|
// Start animation
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--theme-transition-radius",
|
||||||
|
`${maxDistance}px`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Switch theme colors halfway through animation when background has covered most content
|
||||||
|
setTimeout(() => {
|
||||||
|
if (isDarkMode) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
localStorage.setItem("color-theme", "dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
localStorage.setItem("color-theme", "light");
|
||||||
|
}
|
||||||
|
}, 300); // Half of the 600ms animation
|
||||||
|
|
||||||
|
// Clean up after full animation completes
|
||||||
|
setTimeout(() => {
|
||||||
|
// Remove animation class
|
||||||
|
document.body.classList.remove("theme-transitioning");
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
|
||||||
// Set initial state of toggle based on previous settings
|
// Set initial state of toggle based on previous settings
|
||||||
if (
|
if (
|
||||||
localStorage.getItem("color-theme") === "dark" ||
|
localStorage.getItem("color-theme") === "dark" ||
|
||||||
|
|
@ -23,35 +87,25 @@
|
||||||
// Remove no-transition class after initial load
|
// Remove no-transition class after initial load
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener("load", () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
sliders.forEach((slider) =>
|
sliders.forEach((slider) => slider.classList.remove("no-transition"));
|
||||||
slider.classList.remove("no-transition"),
|
|
||||||
);
|
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
themeToggleBtns.forEach((btn) => {
|
themeToggleBtns.forEach((btn) => {
|
||||||
btn.addEventListener("change", function () {
|
btn.addEventListener("change", function () {
|
||||||
// If is set in localStorage
|
// Prevent default theme switching - we'll handle it after animation
|
||||||
if (localStorage.getItem("color-theme")) {
|
const currentIsDark = document.documentElement.classList.contains("dark");
|
||||||
if (localStorage.getItem("color-theme") === "light") {
|
const targetIsDark = !currentIsDark;
|
||||||
document.documentElement.classList.add("dark");
|
|
||||||
localStorage.setItem("color-theme", "dark");
|
// Update toggle states immediately for visual feedback
|
||||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
themeToggleBtns.forEach(
|
||||||
} else {
|
(toggleBtn) => (toggleBtn.checked = targetIsDark),
|
||||||
document.documentElement.classList.remove("dark");
|
);
|
||||||
localStorage.setItem("color-theme", "light");
|
|
||||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
// Find the closest switch element to get position
|
||||||
}
|
const switchElement = btn.closest(".switch") as HTMLElement;
|
||||||
} else {
|
|
||||||
if (document.documentElement.classList.contains("dark")) {
|
// Animate the transition
|
||||||
document.documentElement.classList.remove("dark");
|
animateThemeTransition(switchElement, targetIsDark);
|
||||||
localStorage.setItem("color-theme", "light");
|
|
||||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
|
||||||
} else {
|
|
||||||
document.documentElement.classList.add("dark");
|
|
||||||
localStorage.setItem("color-theme", "dark");
|
|
||||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -145,4 +199,22 @@
|
||||||
.no-transition:before {
|
.no-transition:before {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Theme transition animation */
|
||||||
|
body.theme-transitioning::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--theme-transition-color);
|
||||||
|
clip-path: circle(
|
||||||
|
var(--theme-transition-radius) at var(--theme-transition-x)
|
||||||
|
var(--theme-transition-y)
|
||||||
|
);
|
||||||
|
transition: clip-path 0.6s ease-in-out;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ const lastUpdated = new Date(); // You can set this to a specific date or pull f
|
||||||
<footer
|
<footer
|
||||||
class="bg-gray-100/60 dark:bg-mytheme-900 shadow-sm text-gray-600 dark:text-gray-400 px-4 py-8"
|
class="bg-gray-100/60 dark:bg-mytheme-900 shadow-sm text-gray-600 dark:text-gray-400 px-4 py-8"
|
||||||
>
|
>
|
||||||
<div class="max-w-full mx-auto">
|
<div class="max-w-full lg:px-16 md:px-8 px-2 py-4">
|
||||||
<!-- Main horizontal layout -->
|
<!-- Main horizontal layout -->
|
||||||
<div
|
<div
|
||||||
class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4"
|
class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4"
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ import MobileNav from "./MobileNav.astro";
|
||||||
<header
|
<header
|
||||||
class="bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm border-b border-gray-200 dark:border-gray-700 z-20 relative"
|
class="bg-white/80 dark:bg-gray-900/80 backdrop-blur-sm border-b border-gray-200 dark:border-gray-700 z-20 relative"
|
||||||
>
|
>
|
||||||
<div class="max-w-full px-4 py-4">
|
<div class="max-w-full lg:px-16 md:px-8 px-2 py-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<!-- Site Title - stays on far left -->
|
<!-- Site Title - stays on far left -->
|
||||||
<h2 class="font-bold text-xl mb-0 font-mono flex">
|
<h2 class="font-bold text-xl mb-0 font-mono flex">
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
class="hover:text-blue-600 dark:hover:text-blue-400 transition-colors"
|
class="hover:text-mytheme-600 dark:hover:text-mytheme-400 transition-colors"
|
||||||
>
|
>
|
||||||
<Logo />
|
<Logo />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,14 @@ interface Props {
|
||||||
title?: string;
|
title?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
subtitle?: string;
|
subtitle?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
title = SITE_TITLE,
|
title = SITE_TITLE,
|
||||||
description = SITE_DESCRIPTION,
|
description = SITE_DESCRIPTION,
|
||||||
subtitle,
|
subtitle,
|
||||||
|
className = "max-w-2xl px-4 py-8",
|
||||||
} = Astro.props;
|
} = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -42,6 +44,8 @@ const {
|
||||||
<body
|
<body
|
||||||
class="bg-white dark:bg-gray-900 text-black dark:text-white min-h-screen flex flex-col"
|
class="bg-white dark:bg-gray-900 text-black dark:text-white min-h-screen flex flex-col"
|
||||||
>
|
>
|
||||||
|
<div id="theme-overlay" class="theme-overlay"></div>
|
||||||
|
|
||||||
<!-- Mobile Navigation Drawer (at body level for proper positioning) -->
|
<!-- Mobile Navigation Drawer (at body level for proper positioning) -->
|
||||||
<MobileNavDrawer />
|
<MobileNavDrawer />
|
||||||
|
|
||||||
|
|
@ -50,7 +54,7 @@ const {
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main class="flex-1">
|
<main class="flex-1">
|
||||||
<div class="max-w-2xl mx-auto px-4 py-8">
|
<div class={`${className} mx-auto`}>
|
||||||
<PageHeadline title={title} subtitle={subtitle} />
|
<PageHeadline title={title} subtitle={subtitle} />
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -58,5 +62,19 @@ const {
|
||||||
|
|
||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.theme-overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
display: none;
|
||||||
|
transition: clip-path 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,56 +1,137 @@
|
||||||
---
|
---
|
||||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
import me from "../assets/me.jpg";
|
||||||
|
import { Image } from "astro:assets";
|
||||||
|
import Picture from "../components/Picture.astro";
|
||||||
|
import Link from "../components/Link.astro";
|
||||||
|
|
||||||
|
const love = [
|
||||||
|
{
|
||||||
|
emoji: "🦀",
|
||||||
|
text: "Rust",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "🐧",
|
||||||
|
text: "Linux",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "📚",
|
||||||
|
text: "Books",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "🎨",
|
||||||
|
text: "Clean UI",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
emoji: "🤝",
|
||||||
|
text: "OpenSource",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const skills = {
|
||||||
|
"Core Stack": ["Rust", "TypeScript", "Node.js", "SQL", "HTML", "CSS"],
|
||||||
|
Frontend: ["Next.js", "SvelteKit", "Astro", "Tailwind"],
|
||||||
|
DevOps: [
|
||||||
|
"Prometheus",
|
||||||
|
"Docker",
|
||||||
|
"Kubernetes",
|
||||||
|
"AWS",
|
||||||
|
"GitHub Actions",
|
||||||
|
"GitLab Actions",
|
||||||
|
],
|
||||||
|
"Tools & Methods": [
|
||||||
|
"Git",
|
||||||
|
"Agile",
|
||||||
|
"REST",
|
||||||
|
"GraphQL",
|
||||||
|
"Web Performance",
|
||||||
|
"Linux Sysadmin",
|
||||||
|
"Vitest",
|
||||||
|
],
|
||||||
|
Languages: ["German (native)", "English"],
|
||||||
|
};
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
title="Hi, my name is Alex!"
|
title="Hi, my name is Alex!"
|
||||||
subtitle="Software Engineer, Linux Enthusiast, Lightweight Systems Advocate"
|
subtitle="Software Engineer, Linux Enthusiast, Lightweight Systems Advocate"
|
||||||
|
className="w-full py-16 gap-16 flex flex-col"
|
||||||
>
|
>
|
||||||
|
<section
|
||||||
|
class="max-w-8xl mx-auto grid grid-cols-[auto,1fr,auto] items-start gap-8"
|
||||||
|
>
|
||||||
|
<div class="w-64 hidden md:block"></div>
|
||||||
|
|
||||||
|
<div class="max-w-2xl px-4">
|
||||||
<p>
|
<p>
|
||||||
My journey in the tech world has been a dynamic one. I've immersed myself in
|
I am a privacy-first software engineer passionate about building web
|
||||||
countless projects spanning various video games and, for the past few years,
|
applications that are efficient, user-friendly, and respectful of your
|
||||||
have been maintaining a small homelab, which ignited a passion for
|
data.
|
||||||
automating infrastructure. I am a privacy enthusiast and advocate for
|
|
||||||
non-invasive software. Occasionally, I channel my creativity into building
|
|
||||||
sleek web applications that prioritize efficiency and usability over visual
|
|
||||||
clutter, and adhere to web standards and best practices.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Currently, I am available for hire as a freelance Software Engineer,
|
I help clients design and develop sleek, standards-compliant web
|
||||||
advising clients in optimizing and creating Web Applications. With a strong
|
solutions, always prioritizing usability over visual clutter. With
|
||||||
background in various programming languages and frameworks, I specialize in
|
hands-on experience in Rust, Node.js and many more, I believe in using
|
||||||
developing scalable and efficient web solutions tailored to meet the unique
|
the best tools and technologies for each project, ensuring scalable and
|
||||||
needs of each client.
|
long-term maintainable results.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Contact me <a href="/contact">here</a>!
|
Ready to collaborate? <Link href="/contact">Get in touch here</Link>!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2 class="mt-16">Skills</h2>
|
|
||||||
<div class="flex flex-wrap gap-4">
|
|
||||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
|
||||||
<strong>Core stack:</strong><br />
|
|
||||||
Typescript/Node.js, Python, Rust, SQL, vanilla HTML/css
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
<div>
|
||||||
<strong>Frontend:</strong><br />
|
<Picture src={me} alt="me" class="w-64 h-auto" />
|
||||||
NextJS, SvelteKit, Astro, Tailwind
|
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
||||||
<strong>Ops:</strong><br />
|
<div class="max-w-2xl mx-auto px-4">
|
||||||
Prometheus, Docker, Kubernetes, AWS, Github/lab actions
|
<h2
|
||||||
|
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
||||||
|
>
|
||||||
|
What I Love
|
||||||
|
</h2>
|
||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
{
|
||||||
|
love.map((item) => (
|
||||||
|
<div class="flex items-center justify-center p-6 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm rounded-xl shadow-sm hover:shadow-md transition-all duration-200 border border-slate-200/50 dark:border-slate-700/50 hover:scale-105 cursor-default">
|
||||||
|
<span class="text-3xl mr-3">{item.emoji}</span>
|
||||||
|
<span class="text-lg font-medium text-slate-700 dark:text-slate-200">
|
||||||
|
{item.text}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
<section class="py-16">
|
||||||
<strong>Misc:</strong><br />
|
<div class="max-w-2xl mx-auto px-4">
|
||||||
git, agile, REST, GraphQL, web performance, Linux sysadmin, Vitest
|
<h2
|
||||||
</div>
|
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
||||||
|
>
|
||||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
What I Can Do
|
||||||
<strong>Languages:</strong><br />
|
</h2>
|
||||||
German (native), English
|
<div class="space-y-8">
|
||||||
|
{
|
||||||
|
Object.entries(skills).map(([category, skillList]) => (
|
||||||
|
<div class="space-y-4">
|
||||||
|
<h3 class="text-xl font-semibold text-slate-700 dark:text-slate-200 border-b border-slate-200 dark:border-slate-700 pb-2">
|
||||||
|
{category}
|
||||||
|
</h3>
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
{skillList.map((skill, index) => (
|
||||||
|
<span class="inline-flex items-center px-3 py-1.5 text-sm font-medium bg-slate-100 hover:bg-slate-200 dark:bg-slate-700 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 border border-slate-200 dark:border-slate-600 transition-colors cursor-default rounded-full">
|
||||||
|
{skill}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue