feat: add picture, improve layout
This commit is contained in:
parent
bf720b79ee
commit
b3d158d52c
10 changed files with 361 additions and 168 deletions
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue