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:description" content={description} />
|
||||
<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>
|
||||
)
|
||||
}
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,8 @@ import { Picture as AstroPicture } from "astro:assets";
|
|||
---
|
||||
|
||||
<AstroPicture
|
||||
src={Astro.props.src}
|
||||
alt={Astro.props.alt}
|
||||
formats={["avif", "webp"]}
|
||||
src={Astro.props.src}
|
||||
alt={Astro.props.alt}
|
||||
formats={["avif", "webp"]}
|
||||
{...Astro.props}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -3,146 +3,218 @@
|
|||
---
|
||||
|
||||
<script>
|
||||
const themeToggleBtns = document.querySelectorAll(
|
||||
".theme-toggle",
|
||||
) as NodeListOf<HTMLInputElement>;
|
||||
const sliders = document.querySelectorAll(".slider");
|
||||
const themeToggleBtns = document.querySelectorAll(
|
||||
".theme-toggle",
|
||||
) as NodeListOf<HTMLInputElement>;
|
||||
const sliders = document.querySelectorAll(".slider");
|
||||
|
||||
// Set initial state of toggle based on previous settings
|
||||
if (
|
||||
localStorage.getItem("color-theme") === "dark" ||
|
||||
(!("color-theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
// 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");
|
||||
} else {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
localStorage.setItem("color-theme", "dark");
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
// Remove no-transition class after initial load
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(() => {
|
||||
sliders.forEach((slider) =>
|
||||
slider.classList.remove("no-transition"),
|
||||
);
|
||||
}, 0);
|
||||
});
|
||||
themeToggleBtns.forEach((btn) => {
|
||||
btn.addEventListener("change", function () {
|
||||
// If is set in localStorage
|
||||
if (localStorage.getItem("color-theme")) {
|
||||
if (localStorage.getItem("color-theme") === "light") {
|
||||
document.documentElement.classList.add("dark");
|
||||
localStorage.setItem("color-theme", "dark");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
} else {
|
||||
document.documentElement.classList.remove("dark");
|
||||
localStorage.setItem("color-theme", "light");
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
}
|
||||
} else {
|
||||
if (document.documentElement.classList.contains("dark")) {
|
||||
document.documentElement.classList.remove("dark");
|
||||
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));
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
if (
|
||||
localStorage.getItem("color-theme") === "dark" ||
|
||||
(!("color-theme" in localStorage) &&
|
||||
window.matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = true));
|
||||
document.documentElement.classList.add("dark");
|
||||
} else {
|
||||
themeToggleBtns.forEach((btn) => (btn.checked = false));
|
||||
document.documentElement.classList.remove("dark");
|
||||
}
|
||||
// Remove no-transition class after initial load
|
||||
window.addEventListener("load", () => {
|
||||
setTimeout(() => {
|
||||
sliders.forEach((slider) => slider.classList.remove("no-transition"));
|
||||
}, 0);
|
||||
});
|
||||
themeToggleBtns.forEach((btn) => {
|
||||
btn.addEventListener("change", function () {
|
||||
// Prevent default theme switching - we'll handle it after animation
|
||||
const currentIsDark = document.documentElement.classList.contains("dark");
|
||||
const targetIsDark = !currentIsDark;
|
||||
|
||||
// Update toggle states immediately for visual feedback
|
||||
themeToggleBtns.forEach(
|
||||
(toggleBtn) => (toggleBtn.checked = targetIsDark),
|
||||
);
|
||||
|
||||
// Find the closest switch element to get position
|
||||
const switchElement = btn.closest(".switch") as HTMLElement;
|
||||
|
||||
// Animate the transition
|
||||
animateThemeTransition(switchElement, targetIsDark);
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<label class="switch" for="theme-toggle">
|
||||
<span class="sr-only">Toggle dark mode</span>
|
||||
<input
|
||||
id="theme-toggle"
|
||||
class="theme-toggle"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
/>
|
||||
<span class="slider round no-transition" aria-hidden="true"></span>
|
||||
<span class="sr-only">Toggle dark mode</span>
|
||||
<input
|
||||
id="theme-toggle"
|
||||
class="theme-toggle"
|
||||
type="checkbox"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
/>
|
||||
<span class="slider round no-transition" aria-hidden="true"></span>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
.switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 60px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
.switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--tw-mytheme-200);
|
||||
transition: 0.4s;
|
||||
}
|
||||
.slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--tw-mytheme-200);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: gold;
|
||||
transition: 0.4s;
|
||||
background: radial-gradient(
|
||||
yellow,
|
||||
orange 63%,
|
||||
transparent calc(63% + 3px) 100%
|
||||
);
|
||||
}
|
||||
.slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
left: 4px;
|
||||
bottom: 4px;
|
||||
background-color: gold;
|
||||
transition: 0.4s;
|
||||
background: radial-gradient(
|
||||
yellow,
|
||||
orange 63%,
|
||||
transparent calc(63% + 3px) 100%
|
||||
);
|
||||
}
|
||||
|
||||
input:checked + .slider {
|
||||
background-color: var(--tw-mytheme-600);
|
||||
}
|
||||
input:checked + .slider {
|
||||
background-color: var(--tw-mytheme-600);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
background-color: white;
|
||||
background: radial-gradient(
|
||||
circle at 19% 19%,
|
||||
transparent 41%,
|
||||
var(--tw-mytheme-50) 43%
|
||||
);
|
||||
}
|
||||
input:checked + .slider:before {
|
||||
background-color: white;
|
||||
background: radial-gradient(
|
||||
circle at 19% 19%,
|
||||
transparent 41%,
|
||||
var(--tw-mytheme-50) 43%
|
||||
);
|
||||
}
|
||||
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 5px var(--tw-mytheme-700);
|
||||
}
|
||||
input:focus + .slider {
|
||||
box-shadow: 0 0 5px var(--tw-mytheme-700);
|
||||
}
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
.slider.round {
|
||||
border-radius: 34px;
|
||||
}
|
||||
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
.slider.round:before {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.no-transition {
|
||||
transition: none !important;
|
||||
}
|
||||
.no-transition {
|
||||
transition: none !important;
|
||||
}
|
||||
|
||||
.no-transition:before {
|
||||
transition: none !important;
|
||||
}
|
||||
.no-transition:before {
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const lastUpdated = new Date(); // You can set this to a specific date or pull f
|
|||
<footer
|
||||
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 -->
|
||||
<div
|
||||
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
|
||||
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">
|
||||
<!-- Site Title - stays on far left -->
|
||||
<h2 class="font-bold text-xl mb-0 font-mono flex">
|
||||
<a
|
||||
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 />
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ interface Props {
|
|||
title?: string;
|
||||
description?: string;
|
||||
subtitle?: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const {
|
||||
title = SITE_TITLE,
|
||||
description = SITE_DESCRIPTION,
|
||||
subtitle,
|
||||
className = "max-w-2xl px-4 py-8",
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
|
|
@ -42,6 +44,8 @@ const {
|
|||
<body
|
||||
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) -->
|
||||
<MobileNavDrawer />
|
||||
|
||||
|
|
@ -50,7 +54,7 @@ const {
|
|||
|
||||
<!-- Main Content -->
|
||||
<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} />
|
||||
<slot />
|
||||
</div>
|
||||
|
|
@ -58,5 +62,19 @@ const {
|
|||
|
||||
<!-- 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>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -1,56 +1,137 @@
|
|||
---
|
||||
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
|
||||
title="Hi, my name is Alex!"
|
||||
subtitle="Software Engineer, Linux Enthusiast, Lightweight Systems Advocate"
|
||||
className="w-full py-16 gap-16 flex flex-col"
|
||||
>
|
||||
<p>
|
||||
My journey in the tech world has been a dynamic one. I've immersed myself in
|
||||
countless projects spanning various video games and, for the past few years,
|
||||
have been maintaining a small homelab, which ignited a passion for
|
||||
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>
|
||||
Currently, I am available for hire as a freelance Software Engineer,
|
||||
advising clients in optimizing and creating Web Applications. With a strong
|
||||
background in various programming languages and frameworks, I specialize in
|
||||
developing scalable and efficient web solutions tailored to meet the unique
|
||||
needs of each client.
|
||||
</p>
|
||||
<p>
|
||||
Contact me <a href="/contact">here</a>!
|
||||
</p>
|
||||
<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>
|
||||
|
||||
<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 class="max-w-2xl px-4">
|
||||
<p>
|
||||
I am a privacy-first software engineer passionate about building web
|
||||
applications that are efficient, user-friendly, and respectful of your
|
||||
data.
|
||||
</p>
|
||||
<p>
|
||||
I help clients design and develop sleek, standards-compliant web
|
||||
solutions, always prioritizing usability over visual clutter. With
|
||||
hands-on experience in Rust, Node.js and many more, I believe in using
|
||||
the best tools and technologies for each project, ensuring scalable and
|
||||
long-term maintainable results.
|
||||
</p>
|
||||
<p>
|
||||
Ready to collaborate? <Link href="/contact">Get in touch here</Link>!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
||||
<strong>Frontend:</strong><br />
|
||||
NextJS, SvelteKit, Astro, Tailwind
|
||||
<div>
|
||||
<Picture src={me} alt="me" class="w-64 h-auto" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
||||
<strong>Ops:</strong><br />
|
||||
Prometheus, Docker, Kubernetes, AWS, Github/lab actions
|
||||
<section class="py-16 bg-mytheme-200/70 dark:bg-mytheme-700/50">
|
||||
<div class="max-w-2xl mx-auto px-4">
|
||||
<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>
|
||||
</section>
|
||||
|
||||
<div class="flex-[0_1_calc(50%-0.5rem)]">
|
||||
<strong>Misc:</strong><br />
|
||||
git, agile, REST, GraphQL, web performance, Linux sysadmin, Vitest
|
||||
<section class="py-16">
|
||||
<div class="max-w-2xl mx-auto px-4">
|
||||
<h2
|
||||
class="text-3xl md:text-4xl font-bold mb-12 text-center text-slate-800 dark:text-slate-100"
|
||||
>
|
||||
What I Can Do
|
||||
</h2>
|
||||
<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 class="flex-[0_1_calc(50%-0.5rem)]">
|
||||
<strong>Languages:</strong><br />
|
||||
German (native), English
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</BaseLayout>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue