feat: implement dark mode with theme toggle

- Add dark mode support across all components and pages
- Implement theme toggle button in navbar (desktop and mobile)
- Fix desktop theme toggle icon visibility issue
- Add dark mode color variants to all text, backgrounds and components
- Update Tailwind config to enable class-based dark mode
- Add dark mode support to prose styles for blog content
- Persist theme preference in localStorage
- Update all pages (index, bio, blog) with dark mode colors
- Optimize images (me.png and me-baby.jpg)
This commit is contained in:
Lorenzo Iovino 2026-01-09 16:20:24 +01:00
parent 7caf02fb36
commit df04844ca2
16 changed files with 392 additions and 203 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Before After
Before After

View file

@ -5,29 +5,31 @@ import tailwindIcon from "../assets/tailwind.svg";
import githubIcon from "../assets/github.svg"; import githubIcon from "../assets/github.svg";
--- ---
<div class="w-full bg-secondary py-2 px-4"> <div class="w-full bg-secondary dark:bg-gray-800 py-8 px-4 transition-colors duration-200">
<div class="max-w-6xl mx-auto flex justify-between flex-col-reverse sm:flex-row gap-4 sm:items-end items-center"> <div
<div class="text-center sm:text-left text-gray-800 flex flex-col items-center sm:items-start"> class="max-w-6xl mx-auto flex justify-between flex-col-reverse sm:flex-row gap-4 sm:items-end items-center"
<div class="flex flex-wrap items-center gap-3 text-sm"> >
<span class="font-medium text-gray-800">Lorenzo Iovino</span> <div
<span class="text-gray-800">•</span> class="text-center sm:text-left text-gray-800 dark:text-gray-200 flex flex-col items-center sm:items-start"
>
<div class="flex flex-wrap items-center gap-1 md:gap-3 text-xs">
<a <a
href="/rss.xml" href="/rss.xml"
class="transition-colors duration-200 inline-flex items-center gap-1" class="transition-colors duration-200 inline-flex items-center gap-1 hover:text-gray-600 dark:hover:text-gray-300"
title="RSS Feed" title="RSS Feed"
> >
RSS RSS
</a> </a>
<span class="text-gray-800">•</span> <span class="text-gray-800 dark:text-gray-400">•</span>
<a <a
href="/sitemap-index.xml" href="/sitemap-index.xml"
class="transition-colors duration-200" class="transition-colors duration-200 hover:text-gray-600 dark:hover:text-gray-300"
title="Sitemap">Sitemap</a title="Sitemap">Sitemap</a
> >
<span class="text-gray-800">•</span> <span class="text-gray-800 dark:text-gray-400">•</span>
<a <a
href="https://www.iubenda.com/privacy-policy/98687046" href="https://www.iubenda.com/privacy-policy/98687046"
class="transition-colors duration-200 iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe" class="transition-colors duration-200 iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe hover:text-gray-600 dark:hover:text-gray-300"
title="Privacy Policy">Privacy Policy</a title="Privacy Policy">Privacy Policy</a
> >
<script is:inline type="text/javascript"> <script is:inline type="text/javascript">
@ -47,11 +49,11 @@ import githubIcon from "../assets/github.svg";
} }
})(window, document); })(window, document);
</script> </script>
<span class="text-gray-800">•</span> <span class="text-gray-800 dark:text-gray-400">•</span>
<a <a
href="https://www.iubenda.com/privacy-policy/98687046/cookie-policy" href="https://www.iubenda.com/privacy-policy/98687046/cookie-policy"
class="transition-colors duration-200 iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe" class="transition-colors duration-200 iubenda-white iubenda-noiframe iubenda-embed iubenda-noiframe hover:text-gray-600 dark:hover:text-gray-300"
title="Cookie Policy">Cookie Policy</a title="Cookie Policy">Cookie Policy</a
> >
<script is:inline type="text/javascript"> <script is:inline type="text/javascript">
@ -73,53 +75,14 @@ import githubIcon from "../assets/github.svg";
</script> </script>
</div> </div>
</div> </div>
<div class="text-center sm:text-right text-gray-800 text-sm"> <div class="text-center sm:text-right text-gray-800 dark:text-gray-200 text-xs">
<div class="flex flex-col gap-1"> <div class="flex flex-col">
<div class="flex items-center justify-center sm:justify-end gap-1 flex-wrap">
<span>Made with</span>
<a
href="https://astro.build/"
class="inline-flex items-center transition-all duration-200"
aria-label="Astro"
>
<Image
class="h-5 w-5"
src={astroIcon}
width={20}
height={20}
loading="lazy"
alt=""
/>
</a>
<span>and</span>
<a
href="https://tailwindcss.com/"
class="inline-flex items-center center transition-all duration-200"
aria-label="TailwindCSS"
>
<Image
class="h-5 w-5"
src={tailwindIcon}
width={20}
height={20}
loading="lazy"
alt=""
/>
</a>
</div>
<a <a
href="https://github.com/thisloke/lorenzoiovino.com" href="https://github.com/thisloke/lorenzoiovino.com"
class="transition-colors duration-200 inline-flex items-center justify-center sm:justify-end gap-2 font-medium" class="transition-colors duration-200 inline-flex items-center justify-center sm:justify-end gap-2 font-medium hover:text-gray-600 dark:hover:text-gray-300"
> >
<span>Check the source code</span> <span>Check the source code</span>
<Image <Image src={githubIcon} class="h-5 w-5" width={20} height={20} loading="lazy" alt="" />
src={githubIcon}
class="h-5 w-5"
width={20}
height={20}
loading="lazy"
alt=""
/>
</a> </a>
</div> </div>
</div> </div>

View file

@ -9,7 +9,7 @@ import githubIcon from "../assets/github.svg";
<div class="relative px-6 lg:px-8"> <div class="relative px-6 lg:px-8">
<div class="mx-auto max-w-5xl"> <div class="mx-auto max-w-5xl">
<div <div
class="relative overflow-hidden rounded-3xl bg-white shadow-soft-lg hover:shadow-soft-lg transition-shadow duration-300" class="relative overflow-hidden rounded-3xl bg-white dark:bg-gray-800 shadow-soft-lg hover:shadow-soft-lg transition-all duration-300"
> >
<div class="relative flex flex-col md:flex-row items-center md:items-end"> <div class="relative flex flex-col md:flex-row items-center md:items-end">
<!-- Photo Section --> <!-- Photo Section -->
@ -36,23 +36,29 @@ import githubIcon from "../assets/github.svg";
<div class="space-y-4 md:space-y-6"> <div class="space-y-4 md:space-y-6">
<!-- Greeting --> <!-- Greeting -->
<div class="text-center md:text-left"> <div class="text-center md:text-left">
<h1 class="mb-3"> <h1 class="mb-3 text-gray-900 dark:text-gray-100">
Hey, I'm <span class="text-gray-900 font-extrabold">Lorenzo</span>! Hey, I'm <span class="text-gray-900 dark:text-gray-100 font-extrabold"
>Lorenzo</span
>!
</h1> </h1>
<div class="h-1 w-20 bg-secondary rounded-full mx-auto md:mx-0"></div> <div class="h-1 w-20 bg-secondary dark:bg-primary rounded-full mx-auto md:mx-0">
</div>
</div> </div>
<!-- Description --> <!-- Description -->
<div class="space-y-3 md:space-y-4"> <div class="space-y-3 md:space-y-4">
<p class="text-base md:text-lg text-gray-700"> <p class="text-base md:text-lg text-gray-700 dark:text-gray-300">
I'm on a quest to uncover life's meaning while diving deep into my passion for I'm on a quest to uncover life's meaning while diving deep into my passion for
Computer Science as a Computer Science as a
<span class="font-semibold text-gray-900 relative inline-block"> <span
class="font-semibold text-gray-900 dark:text-gray-100 relative inline-block"
>
Software Engineer Software Engineer
<span class="absolute bottom-0 left-0 w-full h-0.5 bg-secondary"></span> <span class="absolute bottom-0 left-0 w-full h-0.5 bg-secondary dark:bg-primary"
></span>
</span> </span>
</p> </p>
<p class="text-sm md:text-lg text-gray-600"> <p class="text-sm md:text-lg text-gray-600 dark:text-gray-400">
Here, on my personal website, I share my projects and occasional thoughts. Here, on my personal website, I share my projects and occasional thoughts.
</p> </p>
</div> </div>
@ -61,7 +67,7 @@ import githubIcon from "../assets/github.svg";
<div class="flex gap-3 md:gap-4 pt-2 justify-center md:justify-end"> <div class="flex gap-3 md:gap-4 pt-2 justify-center md:justify-end">
<a <a
href="https://www.linkedin.com/in/lorenzoiovino/" href="https://www.linkedin.com/in/lorenzoiovino/"
class="group relative p-3 bg-gray-50 hover:bg-secondary/10 rounded-xl transition-all duration-200 transform hover:-translate-y-1" class="group relative p-3 bg-gray-50 dark:bg-gray-700 hover:bg-secondary/10 dark:hover:bg-primary/10 rounded-xl transition-all duration-200 transform hover:-translate-y-1"
aria-label="LinkedIn" aria-label="LinkedIn"
> >
<Image <Image
@ -75,7 +81,7 @@ import githubIcon from "../assets/github.svg";
</a> </a>
<a <a
href="https://github.com/thisloke" href="https://github.com/thisloke"
class="group relative p-3 bg-gray-50 hover:bg-secondary/10 rounded-xl transition-all duration-200 transform hover:-translate-y-1" class="group relative p-3 bg-gray-50 dark:bg-gray-700 hover:bg-secondary/10 dark:hover:bg-primary/10 rounded-xl transition-all duration-200 transform hover:-translate-y-1"
aria-label="GitHub" aria-label="GitHub"
> >
<Image <Image

View file

@ -2,23 +2,55 @@
const currentPath = Astro.url.pathname; const currentPath = Astro.url.pathname;
--- ---
<nav class="fixed top-0 left-0 right-0 z-50 bg-secondary shadow-sm"> <nav
class="fixed top-0 left-0 right-0 z-50 bg-secondary dark:bg-gray-800 shadow-sm transition-colors duration-200"
>
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16"> <div class="flex justify-between items-center h-16">
<!-- Logo/Brand --> <!-- Logo/Brand -->
<a <a
href="/" href="/"
class="text-xl font-bold text-gray-800 hover:text-gray-200 transition-colors duration-200" class="text-xl font-bold text-gray-800 dark:text-gray-100 hover:text-gray-600 dark:hover:text-gray-300 transition-colors duration-200"
> >
Lorenzo Iovino Lorenzo Iovino
</a> </a>
<!-- Desktop Navigation --> <!-- Desktop Navigation -->
<div class="hidden md:flex items-center space-x-8"> <div class="hidden md:flex items-center space-x-8">
<!-- Dark Mode Toggle -->
<button
id="theme-toggle"
class="p-2 rounded-lg text-gray-800 dark:text-gray-200 hover:bg-white/20 dark:hover:bg-white/10 transition-colors duration-200"
aria-label="Toggle dark mode"
>
<svg
id="theme-toggle-dark-icon"
class="theme-toggle-dark-icon w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
<svg
id="theme-toggle-light-icon"
class="theme-toggle-light-icon w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"></path>
</svg>
</button>
<a <a
href="/" href="/"
class={`text-sm font-medium transition-colors duration-200 ${ class={`text-sm font-medium transition-colors duration-200 ${
currentPath === "/" ? "text-gray-800 font-semibold" : "text-gray-800/80 hover:text-gray-800" currentPath === "/"
? "text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Home Home
@ -26,7 +58,9 @@ const currentPath = Astro.url.pathname;
<a <a
href="/bio" href="/bio"
class={`text-sm font-medium transition-colors duration-200 ${ class={`text-sm font-medium transition-colors duration-200 ${
currentPath === "/bio" ? "text-gray-800 font-semibold" : "text-gray-800/80 hover:text-gray-800" currentPath === "/bio"
? "text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Bio Bio
@ -34,45 +68,80 @@ const currentPath = Astro.url.pathname;
<a <a
href="/blog" href="/blog"
class={`text-sm font-medium transition-colors duration-200 ${ class={`text-sm font-medium transition-colors duration-200 ${
currentPath === "/blog" ? "text-gray-800 font-semibold" : "text-gray-800/80 hover:text-gray-800" currentPath === "/blog"
? "text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 dark:text-gray-300 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Blog Blog
</a> </a>
</div> </div>
<!-- Mobile menu button --> <!-- Mobile controls (theme toggle + menu button) -->
<button <div class="md:hidden flex items-center space-x-2">
id="mobile-menu-button" <!-- Dark Mode Toggle (Mobile) -->
class="md:hidden p-2 rounded-lg text-gray-800 hover:bg-white/10 focus:outline-none focus:ring-2 focus:ring-white/50" <button
aria-label="Toggle menu" id="theme-toggle-mobile"
> class="p-2 rounded-lg text-gray-800 dark:text-gray-200 hover:bg-white/20 dark:hover:bg-white/10 transition-colors duration-200"
<svg aria-label="Toggle dark mode"
class="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
> >
<path <svg
stroke-linecap="round" class="theme-toggle-dark-icon w-5 h-5 hidden"
stroke-linejoin="round" fill="currentColor"
stroke-width="2" viewBox="0 0 20 20"
d="M4 6h16M4 12h16M4 18h16"></path> xmlns="http://www.w3.org/2000/svg"
</svg> >
</button> <path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
<svg
class="theme-toggle-light-icon w-5 h-5 hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"></path>
</svg>
</button>
<!-- Mobile menu button -->
<button
id="mobile-menu-button"
class="p-2 rounded-lg text-gray-800 dark:text-gray-200 hover:bg-white/10 dark:hover:bg-white/5 focus:outline-none focus:ring-2 focus:ring-white/50"
aria-label="Toggle menu"
>
<svg
class="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"></path>
</svg>
</button>
</div>
</div> </div>
</div> </div>
<!-- Mobile Navigation --> <!-- Mobile Navigation -->
<div id="mobile-menu" class="hidden md:hidden bg-secondary"> <div
id="mobile-menu"
class="hidden md:hidden bg-secondary dark:bg-gray-800 transition-colors duration-200"
>
<div class="px-4 py-3 space-y-3"> <div class="px-4 py-3 space-y-3">
<a <a
href="/" href="/"
class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${ class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${
currentPath === "/" currentPath === "/"
? "bg-white/20 text-gray-800 font-semibold" ? "bg-white/20 dark:bg-white/10 text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 hover:bg-white/10 hover:text-gray-800" : "text-gray-800/80 dark:text-gray-300 hover:bg-white/10 dark:hover:bg-white/5 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Home Home
@ -81,8 +150,8 @@ const currentPath = Astro.url.pathname;
href="/bio" href="/bio"
class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${ class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${
currentPath === "/bio" currentPath === "/bio"
? "bg-white/20 text-gray-800 font-semibold" ? "bg-white/20 dark:bg-white/10 text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 hover:bg-white/10 hover:text-gray-800" : "text-gray-800/80 dark:text-gray-300 hover:bg-white/10 dark:hover:bg-white/5 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Bio Bio
@ -91,8 +160,8 @@ const currentPath = Astro.url.pathname;
href="/blog" href="/blog"
class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${ class={`block px-3 py-2 rounded-lg text-base font-medium transition-colors duration-200 ${
currentPath === "/blog" currentPath === "/blog"
? "bg-white/20 text-gray-800 font-semibold" ? "bg-white/20 dark:bg-white/10 text-gray-800 dark:text-gray-100 font-semibold"
: "text-gray-800/80 hover:bg-white/10 hover:text-gray-800" : "text-gray-800/80 dark:text-gray-300 hover:bg-white/10 dark:hover:bg-white/5 hover:text-gray-800 dark:hover:text-gray-100"
}`} }`}
> >
Blog Blog
@ -102,10 +171,51 @@ const currentPath = Astro.url.pathname;
</nav> </nav>
<script> <script>
// Mobile menu toggle
const mobileMenuButton = document.getElementById("mobile-menu-button"); const mobileMenuButton = document.getElementById("mobile-menu-button");
const mobileMenu = document.getElementById("mobile-menu"); const mobileMenu = document.getElementById("mobile-menu");
mobileMenuButton?.addEventListener("click", () => { mobileMenuButton?.addEventListener("click", () => {
mobileMenu?.classList.toggle("hidden"); mobileMenu?.classList.toggle("hidden");
}); });
// Theme toggle functionality
const themeToggleBtns = [
document.getElementById("theme-toggle"),
document.getElementById("theme-toggle-mobile"),
];
const themeToggleDarkIcons = document.querySelectorAll(".theme-toggle-dark-icon");
const themeToggleLightIcons = document.querySelectorAll(".theme-toggle-light-icon");
// Check for saved theme preference or default to 'light' mode
const currentTheme = localStorage.getItem("theme") || "light";
// Set initial theme and icons
if (currentTheme === "dark") {
document.documentElement.classList.add("dark");
themeToggleLightIcons.forEach((icon) => icon.classList.remove("hidden"));
themeToggleDarkIcons.forEach((icon) => icon.classList.add("hidden"));
} else {
document.documentElement.classList.remove("dark");
themeToggleDarkIcons.forEach((icon) => icon.classList.remove("hidden"));
themeToggleLightIcons.forEach((icon) => icon.classList.add("hidden"));
}
// Toggle theme on button clicks
themeToggleBtns.forEach((btn) => {
btn?.addEventListener("click", () => {
document.documentElement.classList.toggle("dark");
// Swap icons
themeToggleDarkIcons.forEach((icon) => icon.classList.toggle("hidden"));
themeToggleLightIcons.forEach((icon) => icon.classList.toggle("hidden"));
// Save theme preference
if (document.documentElement.classList.contains("dark")) {
localStorage.setItem("theme", "dark");
} else {
localStorage.setItem("theme", "light");
}
});
});
</script> </script>

View file

@ -15,19 +15,21 @@ const {
noHeight = false, noHeight = false,
} = Astro.props; } = Astro.props;
const bgClass = backgroundColor === "light" ? "bg-white" : "bg-secondary"; const bgClass =
const titleColorClass = titleColor === "light" ? "text-white" : "text-secondary-700"; backgroundColor === "light" ? "bg-white dark:bg-gray-800" : "bg-secondary dark:bg-gray-900";
const titleColorClass =
titleColor === "light" ? "text-white dark:text-gray-100" : "text-gray-600 dark:text-gray-300";
const heightClass = noHeight ? "h-[0px]" : "h-[150px]"; const heightClass = noHeight ? "h-[0px]" : "h-[150px]";
--- ---
<div <div
class={`bg-center bg-no-repeat ${heightClass} ${bgClass}`} class={`bg-center bg-no-repeat ${heightClass} ${bgClass} transition-colors duration-200`}
style={backgroundImageUrl ? `background-image: url(${backgroundImageUrl})` : ""} style={backgroundImageUrl ? `background-image: url(${backgroundImageUrl})` : ""}
> >
{ {
title && ( title && (
<h2 <h2
class={`mx-24 pt-8 text-6xl drop-shadow-2xl shadow-black text-gray-600 mr-6 font-extrabold ${titleColorClass}`} class={`mx-24 pt-8 text-6xl drop-shadow-2xl shadow-black mr-6 font-extrabold ${titleColorClass}`}
> >
{title} {title}
</h2> </h2>
@ -35,7 +37,7 @@ const heightClass = noHeight ? "h-[0px]" : "h-[150px]";
} }
</div> </div>
<section <section
class="max-w-4xl min-h-full py-4 px-6 mx-auto text-gray-700 flex flex-col items-center justify-center" class="max-w-4xl min-h-full py-4 px-6 mx-auto text-gray-700 dark:text-gray-300 flex flex-col items-center justify-center transition-colors duration-200"
> >
<slot /> <slot />
</section> </section>

View file

@ -1,7 +1,7 @@
--- ---
title: "Welcome to My Blog!" title: "Welcome to My Blog!"
description: "First commit" description: "First commit"
pubDate: 2024-01-15 pubDate: 2026-01-08
heroImage: "../../assets/photos/remote.jpg" heroImage: "../../assets/photos/remote.jpg"
tags: ["personal", "welcome"] tags: ["personal", "welcome"]
--- ---
@ -9,7 +9,7 @@ tags: ["personal", "welcome"]
Hi, Im Lorenzo. Hi, Im Lorenzo.
Ive been thinking about writing for a long time, and I finally decided to publish this blog. Ive been thinking about writing for a long time, and I finally decided to publish this blog.
Not to be “a creator” or something like that just to keep track of what I learn and share it with whoever finds it useful. Not to be “a creator” or something like that, just to keep track of what I learn and share it with whoever finds it useful.
## What you will find here ## What you will find here

View file

@ -27,7 +27,9 @@ const {
const siteUrl = "https://lorenzoiovino.com"; const siteUrl = "https://lorenzoiovino.com";
const defaultImage = mePhoto.src; const defaultImage = mePhoto.src;
const fullImageUrl = image const fullImageUrl = image
? (image.startsWith("http") ? image : `${siteUrl}${image}`) ? image.startsWith("http")
? image
: `${siteUrl}${image}`
: `${siteUrl}${defaultImage}`; : `${siteUrl}${defaultImage}`;
const fullCanonicalUrl = canonicalUrl || `${siteUrl}${Astro.url.pathname}`; const fullCanonicalUrl = canonicalUrl || `${siteUrl}${Astro.url.pathname}`;
--- ---
@ -171,12 +173,16 @@ const fullCanonicalUrl = canonicalUrl || `${siteUrl}${Astro.url.pathname}`;
<style> <style>
@font-face { @font-face {
font-family: 'Inter'; font-family: "Inter";
font-style: normal; font-style: normal;
font-weight: 400 800; font-weight: 400 800;
font-display: swap; font-display: swap;
src: url(https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2) format('woff2'); src: url(https://fonts.gstatic.com/s/inter/v13/UcCO3FwrK3iLTeHuS_fvQtMwCp50KnMw2boKoduKmMEVuLyfAZ9hiA.woff2)
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; format("woff2");
unicode-range:
U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308,
U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF,
U+FFFD;
} }
</style> </style>
@ -184,7 +190,7 @@ const fullCanonicalUrl = canonicalUrl || `${siteUrl}${Astro.url.pathname}`;
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<title>{title}</title> <title>{title}</title>
</head> </head>
<body class="bg-secondary min-h-screen"> <body class="bg-white dark:bg-gray-900 min-h-screen transition-colors duration-200">
<slot /> <slot />
</body> </body>
</html> </html>

View file

@ -20,33 +20,34 @@ if (!bioEntry) {
const { Content } = await bioEntry.render(); const { Content } = await bioEntry.render();
--- ---
<BaseLayout <BaseLayout title="Lorenzo Iovino >> Bio" description="Biography and life story of Lorenzo Iovino">
title="Lorenzo Iovino >> Bio"
description="Biography and life story of Lorenzo Iovino"
>
<Navbar /> <Navbar />
<div class="min-h-screen pt-16 bg-secondary"> <div class="min-h-screen pt-16 bg-secondary dark:bg-gray-900 transition-colors duration-200">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20"> <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20">
<div class="mb-16 text-center"> <div class="mb-16 text-center">
<h1 class="text-gray-800 mb-4">{bioEntry.data.title}</h1> <h1 class="text-gray-800 dark:text-gray-100 mb-4">{bioEntry.data.title}</h1>
<p class="text-xl text-gray-700 max-w-2xl mx-auto"> <p class="text-xl text-gray-700 dark:text-gray-300 max-w-2xl mx-auto">
{bioEntry.data.description} {bioEntry.data.description}
</p> </p>
<div class="h-1 w-20 bg-gray-600 rounded-full mx-auto mt-6"></div> <div class="h-1 w-20 bg-gray-600 dark:bg-primary rounded-full mx-auto mt-6"></div>
</div> </div>
<div class="bg-white rounded-2xl shadow-soft-lg p-8 md:p-12 lg:p-16"> <div
class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg p-8 md:p-12 lg:p-16 transition-colors duration-200"
>
<div class="prose prose-xl max-w-none"> <div class="prose prose-xl max-w-none">
<Content /> <Content />
</div> </div>
</div> </div>
<div <div
class="mt-16 bg-gradient-to-br from-white to-gray-50 rounded-2xl shadow-soft-lg p-10 md:p-14 lg:p-16 text-center border border-gray-100" class="mt-16 bg-gradient-to-br from-white to-gray-50 dark:from-gray-800 dark:to-gray-700 rounded-2xl shadow-soft-lg p-10 md:p-14 lg:p-16 text-center border border-gray-100 dark:border-gray-600 transition-colors duration-200"
> >
<h3 class="text-3xl md:text-4xl font-bold text-gray-900 mb-6">Let's Connect!</h3> <h3 class="text-3xl md:text-4xl font-bold text-gray-900 dark:text-gray-100 mb-6">
Let's Connect!
</h3>
<p <p
class="text-gray-700 text-xl md:text-2xl leading-relaxed mb-10 max-w-3xl mx-auto font-light" class="text-gray-700 dark:text-gray-300 text-xl md:text-2xl leading-relaxed mb-10 max-w-3xl mx-auto font-light"
> >
I'm always excited to connect with fellow developers, discuss interesting projects, or I'm always excited to connect with fellow developers, discuss interesting projects, or
just have a chat about technology and life. Feel free to reach out! just have a chat about technology and life. Feel free to reach out!
@ -56,7 +57,7 @@ const { Content } = await bioEntry.render();
href="https://www.linkedin.com/in/lorenzoiovino/" href="https://www.linkedin.com/in/lorenzoiovino/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="inline-flex items-center px-10 py-5 bg-white hover:bg-gray-50 text-gray-700 font-semibold text-lg rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl border-2 border-gray-200 hover:border-gray-300 transform hover:-translate-y-1 hover:scale-105" class="inline-flex items-center px-10 py-5 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 font-semibold text-lg rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl border-2 border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500 transform hover:-translate-y-1 hover:scale-105"
> >
<Image <Image
src={linkedinIcon} src={linkedinIcon}
@ -71,15 +72,9 @@ const { Content } = await bioEntry.render();
href="https://github.com/thisloke" href="https://github.com/thisloke"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="inline-flex items-center px-10 py-5 bg-white hover:bg-gray-50 text-gray-700 font-semibold text-lg rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl border-2 border-gray-200 hover:border-gray-300 transform hover:-translate-y-1 hover:scale-105" class="inline-flex items-center px-10 py-5 bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-200 font-semibold text-lg rounded-xl transition-all duration-200 shadow-lg hover:shadow-xl border-2 border-gray-200 dark:border-gray-600 hover:border-gray-300 dark:hover:border-gray-500 transform hover:-translate-y-1 hover:scale-105"
> >
<Image <Image src={githubIcon} alt="" class="h-6 w-6 mr-3 opacity-80" width={24} height={24} />
src={githubIcon}
alt=""
class="h-6 w-6 mr-3 opacity-80"
width={24}
height={24}
/>
GitHub GitHub
</a> </a>
</div> </div>

View file

@ -21,7 +21,7 @@ const allTags: Record<string, number> = sortedPosts.reduce(
}); });
return acc; return acc;
}, },
{} as Record<string, number>, {} as Record<string, number>
); );
const sortedTags: TagCount[] = Object.entries(allTags) const sortedTags: TagCount[] = Object.entries(allTags)
@ -31,31 +31,30 @@ const sortedTags: TagCount[] = Object.entries(allTags)
const initialPosts: BlogPost[] = sortedPosts.slice(0, 6); const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
--- ---
<BaseLayout <BaseLayout title="Lorenzo Iovino >> Blog" description="Blog posts and articles by Lorenzo Iovino">
title="Lorenzo Iovino >> Blog"
description="Blog posts and articles by Lorenzo Iovino"
>
<Navbar /> <Navbar />
<div class="min-h-screen pt-16 bg-secondary"> <div class="min-h-screen pt-16 bg-secondary dark:bg-gray-900 transition-colors duration-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20">
<div class="mb-16 text-center"> <div class="mb-16 text-center">
<h1 class="text-gray-800 mb-4">Blog</h1> <h1 class="text-gray-800 dark:text-gray-100 mb-4">Blog</h1>
<p class="text-xl text-gray-800/90 max-w-2xl mx-auto"> <p class="text-xl text-gray-800/90 dark:text-gray-300 max-w-2xl mx-auto">
Thoughts, experiences, and insights about software engineering, technology, and life Thoughts, experiences, and insights about software engineering, technology, and life
</p> </p>
<div class="h-1 w-20 bg-gray-800 rounded-full mx-auto mt-6"></div> <div class="h-1 w-20 bg-gray-800 dark:bg-primary rounded-full mx-auto mt-6"></div>
</div> </div>
<div class="mb-12"> <div class="mb-12">
<div class="bg-white rounded-2xl shadow-soft-lg overflow-hidden"> <div
class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg overflow-hidden transition-colors duration-200"
>
<button <button
id="toggle-tags" id="toggle-tags"
class="w-full p-6 flex items-center justify-between hover:bg-gray-50 transition-colors duration-200" class="w-full p-6 flex items-center justify-between hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors duration-200"
> >
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-secondary-700" class="h-6 w-6 text-secondary-700 dark:text-primary"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -67,12 +66,12 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
></path> ></path>
</svg> </svg>
<h2 class="text-xl font-bold text-gray-900">Filter by Topic</h2> <h2 class="text-xl font-bold text-gray-900 dark:text-gray-100">Filter by Topic</h2>
</div> </div>
<svg <svg
id="chevron-icon" id="chevron-icon"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6 text-gray-400 transition-transform duration-200" class="h-6 w-6 text-gray-400 dark:text-gray-500 transition-transform duration-200"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -88,7 +87,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<a <a
href="/blog" href="/blog"
class="inline-block px-4 py-2 bg-secondary text-white rounded-lg font-medium transition-all duration-200 hover:bg-secondary/90" class="inline-block px-4 py-2 bg-secondary dark:bg-primary text-white dark:text-gray-900 rounded-lg font-medium transition-all duration-200 hover:bg-secondary/90 dark:hover:bg-primary/90"
> >
All Posts ({sortedPosts.length}) All Posts ({sortedPosts.length})
</a> </a>
@ -96,7 +95,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
sortedTags.map(({ tag, count }: TagCount) => ( sortedTags.map(({ tag, count }: TagCount) => (
<a <a
href={`/blog/tag/${tag}`} href={`/blog/tag/${tag}`}
class="inline-block px-4 py-2 bg-gray-100 text-gray-700 rounded-lg font-medium transition-all duration-200 hover:bg-secondary hover:text-white" class="inline-block px-4 py-2 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 rounded-lg font-medium transition-all duration-200 hover:bg-secondary hover:text-white dark:hover:bg-primary dark:hover:text-gray-900"
> >
{tag} ({count}) {tag} ({count})
</a> </a>
@ -110,7 +109,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
<div id="posts-container" class="space-y-8"> <div id="posts-container" class="space-y-8">
{ {
initialPosts.map((post: BlogPost) => ( initialPosts.map((post: BlogPost) => (
<article class="bg-white rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-shadow duration-300"> <article class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-all duration-300">
<div class="block"> <div class="block">
{post.data.heroImage && ( {post.data.heroImage && (
<a href={`/blog/${post.slug}`} class="block"> <a href={`/blog/${post.slug}`} class="block">
@ -129,7 +128,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
</a> </a>
)} )}
<div class="p-8"> <div class="p-8">
<div class="flex items-center gap-3 mb-4 text-sm text-gray-500"> <div class="flex items-center gap-3 mb-4 text-sm text-gray-500 dark:text-gray-400">
<time datetime={post.data.pubDate.toISOString()}> <time datetime={post.data.pubDate.toISOString()}>
{post.data.pubDate.toLocaleDateString("en-US", { {post.data.pubDate.toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
@ -144,7 +143,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
{post.data.tags.slice(0, 3).map((tag: string) => ( {post.data.tags.slice(0, 3).map((tag: string) => (
<a <a
href={`/blog/tag/${tag}`} href={`/blog/tag/${tag}`}
class="px-2 py-1 bg-secondary/10 text-gray-800 text-xs rounded-lg font-medium hover:bg-secondary hover:text-white transition-all duration-200" class="px-2 py-1 bg-secondary/10 dark:bg-primary/20 text-gray-800 dark:text-gray-200 text-xs rounded-lg font-medium hover:bg-secondary hover:text-white dark:hover:bg-primary dark:hover:text-gray-900 transition-all duration-200"
> >
{tag} {tag}
</a> </a>
@ -154,13 +153,13 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
)} )}
</div> </div>
<a href={`/blog/${post.slug}`} class="block group"> <a href={`/blog/${post.slug}`} class="block group">
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-3 group-hover:text-secondary-700 transition-colors"> <h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3 group-hover:text-secondary-700 dark:group-hover:text-primary transition-colors">
{post.data.title} {post.data.title}
</h2> </h2>
<p class="text-gray-700 text-lg leading-relaxed mb-4"> <p class="text-gray-700 dark:text-gray-300 text-lg leading-relaxed mb-4">
{post.data.description} {post.data.description}
</p> </p>
<div class="flex items-center text-secondary-800 font-medium"> <div class="flex items-center text-secondary-800 dark:text-primary font-medium">
Read more Read more
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -185,14 +184,14 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
<div id="loading" class="hidden text-center py-8"> <div id="loading" class="hidden text-center py-8">
<div <div
class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-white border-t-transparent" class="inline-block animate-spin rounded-full h-12 w-12 border-4 border-gray-800 dark:border-gray-200 border-t-transparent"
> >
</div> </div>
<p class="text-white mt-4">Loading more posts...</p> <p class="text-gray-800 dark:text-gray-200 mt-4">Loading more posts...</p>
</div> </div>
<div id="end-message" class="hidden text-center py-8"> <div id="end-message" class="hidden text-center py-8">
<p class="text-white/80 text-lg">You've reached the end! 🎉</p> <p class="text-gray-800/80 dark:text-gray-200/80 text-lg">You've reached the end! 🎉</p>
</div> </div>
</div> </div>
</div> </div>
@ -211,7 +210,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
function createPostElement(post) { function createPostElement(post) {
const article = document.createElement("article"); const article = document.createElement("article");
article.className = article.className =
"bg-white rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-shadow duration-300"; "bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-all duration-300";
const heroImageHTML = post.data.heroImage const heroImageHTML = post.data.heroImage
? ` ? `
@ -237,7 +236,7 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
.slice(0, 3) .slice(0, 3)
.map( .map(
(tag) => (tag) =>
`<a href="/blog/tag/${tag}" class="px-2 py-1 bg-secondary/10 text-secondary-700 text-xs rounded-lg font-medium hover:bg-secondary hover:text-white transition-all duration-200">${tag}</a>` `<a href="/blog/tag/${tag}" class="px-2 py-1 bg-secondary/10 dark:bg-primary/20 text-gray-800 dark:text-gray-200 text-xs rounded-lg font-medium hover:bg-secondary hover:text-white dark:hover:bg-primary dark:hover:text-gray-900 transition-all duration-200">${tag}</a>`
) )
.join("")} .join("")}
</div> </div>
@ -255,20 +254,20 @@ const initialPosts: BlogPost[] = sortedPosts.slice(0, 6);
<div class="block"> <div class="block">
${heroImageHTML} ${heroImageHTML}
<div class="p-8"> <div class="p-8">
<div class="flex items-center gap-3 mb-4 text-sm text-gray-500"> <div class="flex items-center gap-3 mb-4 text-sm text-gray-500 dark:text-gray-400">
<time datetime="${pubDate.toISOString()}"> <time datetime="${pubDate.toISOString()}">
${formattedDate} ${formattedDate}
</time> </time>
${tagsHTML} ${tagsHTML}
</div> </div>
<a href="/blog/${post.slug}" class="block group"> <a href="/blog/${post.slug}" class="block group">
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-3 group-hover:text-secondary-700 transition-colors"> <h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3 group-hover:text-secondary-700 dark:group-hover:text-primary transition-colors">
${post.data.title} ${post.data.title}
</h2> </h2>
<p class="text-gray-700 text-lg leading-relaxed mb-4"> <p class="text-gray-700 dark:text-gray-300 text-lg leading-relaxed mb-4">
${post.data.description} ${post.data.description}
</p> </p>
<div class="flex items-center text-secondary font-medium"> <div class="flex items-center text-secondary dark:text-primary font-medium">
Read more Read more
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -35,10 +35,13 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
tags={entry.data.tags} tags={entry.data.tags}
> >
<Navbar /> <Navbar />
<div class="min-h-screen pt-16 bg-gray-50"> <div class="min-h-screen pt-16 bg-gray-50 dark:bg-gray-900 transition-colors duration-200">
<article class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12"> <article class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8 md:py-12">
<header class="mb-8 md:mb-12"> <header class="mb-8 md:mb-12">
<time datetime={entry.data.pubDate.toISOString()} class="block mb-3 text-sm text-gray-600"> <time
datetime={entry.data.pubDate.toISOString()}
class="block mb-3 text-sm text-gray-600 dark:text-gray-400"
>
{ {
entry.data.pubDate.toLocaleDateString("en-US", { entry.data.pubDate.toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
@ -53,7 +56,7 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
{entry.data.tags.map((tag: string) => ( {entry.data.tags.map((tag: string) => (
<a <a
href={`/blog/tag/${tag}`} href={`/blog/tag/${tag}`}
class="px-3 py-1.5 bg-gray-100 text-gray-700 text-xs rounded-lg font-medium hover:bg-gray-900 hover:text-white transition-all duration-200" class="px-3 py-1.5 bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-200 text-xs rounded-lg font-medium hover:bg-gray-900 hover:text-white dark:hover:bg-primary dark:hover:text-gray-900 transition-all duration-200"
> >
{tag} {tag}
</a> </a>
@ -61,10 +64,12 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
</div> </div>
) )
} }
<h1 class="text-gray-900 mb-4 text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"> <h1
class="text-gray-900 dark:text-gray-100 mb-4 text-3xl md:text-4xl lg:text-5xl font-bold leading-tight"
>
{entry.data.title} {entry.data.title}
</h1> </h1>
<p class="text-lg md:text-xl text-gray-600 leading-relaxed mb-6"> <p class="text-lg md:text-xl text-gray-600 dark:text-gray-400 leading-relaxed mb-6">
{entry.data.description} {entry.data.description}
</p> </p>
</header> </header>
@ -87,13 +92,17 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
) )
} }
<div class="bg-white rounded-xl md:rounded-2xl shadow-xl p-6 sm:p-8 md:p-12 lg:p-16"> <div
class="bg-white dark:bg-gray-800 rounded-xl md:rounded-2xl shadow-xl p-6 sm:p-8 md:p-12 lg:p-16 transition-colors duration-200"
>
<div class="prose prose-xl max-w-none"> <div class="prose prose-xl max-w-none">
<Content /> <Content />
</div> </div>
</div> </div>
<div class="mt-8 md:mt-12 bg-white rounded-xl md:rounded-2xl shadow-lg p-6 sm:p-8"> <div
class="mt-8 md:mt-12 bg-white dark:bg-gray-800 rounded-xl md:rounded-2xl shadow-lg p-6 sm:p-8 transition-colors duration-200"
>
<div class="flex items-start gap-6"> <div class="flex items-start gap-6">
<Image <Image
src={mePhoto} src={mePhoto}
@ -105,8 +114,10 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
format="webp" format="webp"
/> />
<div> <div>
<h3 class="text-lg md:text-xl font-bold text-gray-900 mb-2">Lorenzo Iovino</h3> <h3 class="text-lg md:text-xl font-bold text-gray-900 dark:text-gray-100 mb-2">
<p class="text-sm md:text-base text-gray-700 mb-4"> Lorenzo Iovino
</h3>
<p class="text-sm md:text-base text-gray-700 dark:text-gray-300 mb-4">
Software Engineer based in Sicily, passionate about technology, remote work, and life Software Engineer based in Sicily, passionate about technology, remote work, and life
balance. When not coding, you'll find me working on my vineyard or exploring the balance. When not coding, you'll find me working on my vineyard or exploring the
beautiful Sicilian countryside. beautiful Sicilian countryside.
@ -116,7 +127,7 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
href="https://www.linkedin.com/in/lorenzoiovino/" href="https://www.linkedin.com/in/lorenzoiovino/"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-gray-600 hover:text-gray-900 transition-colors" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
aria-label="Visit Lorenzo Iovino's LinkedIn profile" aria-label="Visit Lorenzo Iovino's LinkedIn profile"
> >
<svg class="w-5 h-5 md:w-6 md:h-6" fill="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 md:w-6 md:h-6" fill="currentColor" viewBox="0 0 24 24">
@ -129,7 +140,7 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
href="https://github.com/thisloke" href="https://github.com/thisloke"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="text-gray-600 hover:text-gray-900 transition-colors" class="text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 transition-colors"
aria-label="Visit Lorenzo Iovino's GitHub profile" aria-label="Visit Lorenzo Iovino's GitHub profile"
> >
<svg class="w-5 h-5 md:w-6 md:h-6" fill="currentColor" viewBox="0 0 24 24"> <svg class="w-5 h-5 md:w-6 md:h-6" fill="currentColor" viewBox="0 0 24 24">
@ -146,7 +157,7 @@ const heroImageSrc = entry.data.heroImage?.src || mePhoto.src;
<div class="mt-8 md:mt-12 text-center"> <div class="mt-8 md:mt-12 text-center">
<a <a
href="/blog" href="/blog"
class="inline-flex items-center px-5 py-2.5 md:px-6 md:py-3 bg-white hover:bg-gray-50 text-gray-900 font-medium rounded-lg md:rounded-xl transition-all duration-200 shadow-md hover:shadow-lg border border-gray-200" class="inline-flex items-center px-5 py-2.5 md:px-6 md:py-3 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-900 dark:text-gray-100 font-medium rounded-lg md:rounded-xl transition-all duration-200 shadow-md hover:shadow-lg border border-gray-200 dark:border-gray-700"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View file

@ -41,12 +41,12 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
description={`Browse all blog posts about ${displayTag}. ${posts.length} article${posts.length !== 1 ? "s" : ""} found.`} description={`Browse all blog posts about ${displayTag}. ${posts.length} article${posts.length !== 1 ? "s" : ""} found.`}
> >
<Navbar /> <Navbar />
<div class="min-h-screen pt-16 bg-secondary"> <div class="min-h-screen pt-16 bg-secondary dark:bg-gray-900 transition-colors duration-200">
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20"> <div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-20">
<div class="mb-16 text-center"> <div class="mb-16 text-center">
<a <a
href="/blog" href="/blog"
class="inline-flex items-center text-white/80 hover:text-white transition-colors mb-6" class="inline-flex items-center text-gray-800/80 dark:text-gray-200/80 hover:text-gray-800 dark:hover:text-gray-200 transition-colors mb-6"
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -64,7 +64,7 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
<div class="flex items-center justify-center gap-3 mb-4"> <div class="flex items-center justify-center gap-3 mb-4">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8 text-white" class="h-8 w-8 text-gray-800 dark:text-gray-200"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -76,18 +76,18 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z"
></path> ></path>
</svg> </svg>
<h1 class="text-white mb-0">{displayTag}</h1> <h1 class="text-gray-800 dark:text-gray-100 mb-0">{displayTag}</h1>
</div> </div>
<p class="text-xl text-white/90 max-w-2xl mx-auto"> <p class="text-xl text-gray-800/90 dark:text-gray-300 max-w-2xl mx-auto">
{posts.length} article{posts.length !== 1 ? "s" : ""} about {displayTag.toLowerCase()} {posts.length} article{posts.length !== 1 ? "s" : ""} about {displayTag.toLowerCase()}
</p> </p>
<div class="h-1 w-20 bg-white rounded-full mx-auto mt-6"></div> <div class="h-1 w-20 bg-gray-800 dark:bg-primary rounded-full mx-auto mt-6"></div>
</div> </div>
<div class="space-y-8"> <div class="space-y-8">
{ {
posts.map((post: BlogPost) => ( posts.map((post: BlogPost) => (
<article class="bg-white rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-shadow duration-300"> <article class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg overflow-hidden hover:shadow-soft-lg transition-all duration-300">
<div class="block"> <div class="block">
{post.data.heroImage && ( {post.data.heroImage && (
<a href={`/blog/${post.slug}`} class="block"> <a href={`/blog/${post.slug}`} class="block">
@ -106,7 +106,7 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
</a> </a>
)} )}
<div class="p-8"> <div class="p-8">
<div class="flex items-center gap-3 mb-4 text-sm text-gray-500"> <div class="flex items-center gap-3 mb-4 text-sm text-gray-500 dark:text-gray-400">
<time datetime={post.data.pubDate.toISOString()}> <time datetime={post.data.pubDate.toISOString()}>
{post.data.pubDate.toLocaleDateString("en-US", { {post.data.pubDate.toLocaleDateString("en-US", {
year: "numeric", year: "numeric",
@ -135,13 +135,13 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
)} )}
</div> </div>
<a href={`/blog/${post.slug}`} class="block group"> <a href={`/blog/${post.slug}`} class="block group">
<h2 class="text-2xl md:text-3xl font-bold text-gray-900 mb-3 group-hover:text-secondary-700 transition-colors"> <h2 class="text-2xl md:text-3xl font-bold text-gray-900 dark:text-gray-100 mb-3 group-hover:text-secondary-700 dark:group-hover:text-primary transition-colors">
{post.data.title} {post.data.title}
</h2> </h2>
<p class="text-gray-700 text-lg leading-relaxed mb-4"> <p class="text-gray-700 dark:text-gray-300 text-lg leading-relaxed mb-4">
{post.data.description} {post.data.description}
</p> </p>
<div class="flex items-center text-secondary-700 font-medium"> <div class="flex items-center text-secondary-700 dark:text-primary font-medium">
Read more Read more
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -166,10 +166,10 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
{ {
posts.length === 0 && ( posts.length === 0 && (
<div class="bg-white rounded-2xl shadow-soft-lg p-12 text-center"> <div class="bg-white dark:bg-gray-800 rounded-2xl shadow-soft-lg p-12 text-center transition-colors duration-200">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 mx-auto text-gray-300 mb-4" class="h-16 w-16 mx-auto text-gray-300 dark:text-gray-600 mb-4"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor" stroke="currentColor"
@ -181,7 +181,7 @@ const displayTag: string = tag.charAt(0).toUpperCase() + tag.slice(1);
d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/> />
</svg> </svg>
<p class="text-gray-600 text-lg">No posts found with this tag.</p> <p class="text-gray-600 dark:text-gray-400 text-lg">No posts found with this tag.</p>
</div> </div>
) )
} }

View file

@ -6,19 +6,18 @@ import Navbar from "../components/Navbar.astro";
import BaseLayout from "../layouts/BaseLayout.astro"; import BaseLayout from "../layouts/BaseLayout.astro";
--- ---
<BaseLayout <BaseLayout title="Lorenzo Iovino >> Homepage " description="Lorenzo Iovino - Software Engineer">
title="Lorenzo Iovino >> Homepage "
description="Lorenzo Iovino - Software Engineer"
>
<Navbar /> <Navbar />
<div class="bg-secondary min-h-screen flex flex-col relative pt-16"> <div
class="bg-secondary dark:bg-gray-900 min-h-screen flex flex-col relative pt-16 transition-colors duration-200"
>
<div class="flex-grow flex items-center justify-center relative z-10 py-12"> <div class="flex-grow flex items-center justify-center relative z-10 py-12">
<Hero /> <Hero />
</div> </div>
<div class="relative"> <div class="relative">
<div <div
class="absolute bottom-0 left-0 right-0 pointer-events-none z-20" class="absolute bottom-0 left-0 right-0 pointer-events-none z-20"
style="height: 100px; margin-bottom: 0;" style="height: 110px; margin-bottom: 0;"
> >
<Fish /> <Fish />
</div> </div>

View file

@ -12,8 +12,11 @@
/* Typography base */ /* Typography base */
body { body {
@apply font-sans text-base text-gray-900; @apply font-sans text-base text-gray-900 dark:text-gray-100;
font-feature-settings: "kern" 1, "liga" 1, "calt" 1; font-feature-settings:
"kern" 1,
"liga" 1,
"calt" 1;
} }
/* Headings ottimizzati */ /* Headings ottimizzati */
@ -23,8 +26,12 @@
h4, h4,
h5, h5,
h6 { h6 {
@apply font-bold; @apply font-bold text-gray-900 dark:text-gray-100;
font-feature-settings: "kern" 1, "liga" 1, "calt" 1, "ss01" 1; font-feature-settings:
"kern" 1,
"liga" 1,
"calt" 1,
"ss01" 1;
} }
h1 { h1 {
@ -50,7 +57,11 @@
/* Link ottimizzati */ /* Link ottimizzati */
a { a {
@apply transition-colors duration-200; @apply transition-colors duration-200 text-gray-800 dark:text-gray-200;
}
a:hover {
@apply text-gray-600 dark:text-gray-400;
} }
/* Strong e emphasis */ /* Strong e emphasis */

View file

@ -4,6 +4,10 @@
line-height: 1.875 !important; line-height: 1.875 !important;
} }
.dark .prose {
color: #d1d5db !important;
}
.prose h2 { .prose h2 {
font-size: 2.25rem !important; font-size: 2.25rem !important;
font-weight: 700 !important; font-weight: 700 !important;
@ -16,11 +20,14 @@
padding-bottom: 1rem !important; padding-bottom: 1rem !important;
} }
.dark .prose h2 {
color: #f3f4f6 !important;
}
.prose > h2:first-child { .prose > h2:first-child {
margin-top: 0 !important; margin-top: 0 !important;
} }
.prose h2::after { .prose h2::after {
content: ""; content: "";
position: absolute; position: absolute;
@ -32,6 +39,10 @@
border-radius: 2px; border-radius: 2px;
} }
.dark .prose h2::after {
background: linear-gradient(90deg, #f9bc2e 0%, #fbd46c 100%);
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose h2 { .prose h2 {
font-size: 3rem !important; font-size: 3rem !important;
@ -48,6 +59,10 @@
letter-spacing: -0.01em !important; letter-spacing: -0.01em !important;
} }
.dark .prose h3 {
color: #f3f4f6 !important;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose h3 { .prose h3 {
font-size: 1.875rem !important; font-size: 1.875rem !important;
@ -63,6 +78,10 @@
line-height: 1.4 !important; line-height: 1.4 !important;
} }
.dark .prose h4 {
color: #f3f4f6 !important;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose h4 { .prose h4 {
font-size: 1.5rem !important; font-size: 1.5rem !important;
@ -76,6 +95,10 @@
font-weight: 400 !important; font-weight: 400 !important;
} }
.dark .prose p {
color: #d1d5db !important;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose p { .prose p {
font-size: 1.1rem !important; font-size: 1.1rem !important;
@ -83,8 +106,7 @@
} }
.prose > p:first-of-type { .prose > p:first-of-type {
margin-top: 1.6rem;
margin-top: 1.6rem
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@ -102,21 +124,38 @@
transition: all 0.2s ease !important; transition: all 0.2s ease !important;
} }
.dark .prose a {
color: #d1d5db !important;
}
.prose a:hover { .prose a:hover {
color: #111827 !important; color: #111827 !important;
text-decoration-color: #111827 !important; text-decoration-color: #111827 !important;
} }
.dark .prose a:hover {
color: #f3f4f6 !important;
text-decoration-color: #f9bc2e !important;
}
.prose strong { .prose strong {
font-weight: 700 !important; font-weight: 700 !important;
color: #111827 !important; color: #111827 !important;
} }
.dark .prose strong {
color: #f3f4f6 !important;
}
.prose em { .prose em {
font-style: italic !important; font-style: italic !important;
color: #4b5563 !important; color: #4b5563 !important;
} }
.dark .prose em {
color: #9ca3af !important;
}
.prose ul, .prose ul,
.prose ol { .prose ol {
margin-top: 2rem !important; margin-top: 2rem !important;
@ -140,6 +179,10 @@
margin-bottom: 0.75rem !important; margin-bottom: 0.75rem !important;
} }
.dark .prose li {
color: #d1d5db !important;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose li { .prose li {
font-size: 1.1rem !important; font-size: 1.1rem !important;
@ -151,6 +194,10 @@
font-weight: 600 !important; font-weight: 600 !important;
} }
.dark .prose li::marker {
color: #9ca3af !important;
}
.prose blockquote { .prose blockquote {
border-left: 4px solid #6b7280 !important; border-left: 4px solid #6b7280 !important;
background-color: #f9fafb !important; background-color: #f9fafb !important;
@ -163,6 +210,12 @@
line-height: 1.6 !important; line-height: 1.6 !important;
} }
.dark .prose blockquote {
border-left-color: #9ca3af !important;
background-color: #374151 !important;
color: #d1d5db !important;
}
.prose code { .prose code {
background-color: #f3f4f6 !important; background-color: #f3f4f6 !important;
color: #374151 !important; color: #374151 !important;
@ -170,8 +223,14 @@
border-radius: 0.375rem !important; border-radius: 0.375rem !important;
font-size: 1rem !important; font-size: 1rem !important;
font-weight: 600 !important; font-weight: 600 !important;
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", font-family:
"Courier New", monospace !important; ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New",
monospace !important;
}
.dark .prose code {
background-color: #374151 !important;
color: #f3f4f6 !important;
} }
.prose pre { .prose pre {
@ -197,6 +256,10 @@
border-width: 2px !important; border-width: 2px !important;
} }
.dark .prose hr {
border-color: #4b5563 !important;
}
.prose img { .prose img {
border-radius: 1rem !important; border-radius: 1rem !important;
margin-top: 2.5rem !important; margin-top: 2.5rem !important;
@ -237,6 +300,10 @@
border-bottom: 2px solid #e5e7eb !important; border-bottom: 2px solid #e5e7eb !important;
} }
.dark .prose thead {
border-bottom-color: #4b5563 !important;
}
.prose th { .prose th {
padding: 0.75rem 1rem !important; padding: 0.75rem 1rem !important;
text-align: left !important; text-align: left !important;
@ -244,16 +311,29 @@
color: #111827 !important; color: #111827 !important;
} }
.dark .prose th {
color: #f3f4f6 !important;
}
.prose td { .prose td {
padding: 0.75rem 1rem !important; padding: 0.75rem 1rem !important;
border-bottom: 1px solid #e5e7eb !important; border-bottom: 1px solid #e5e7eb !important;
color: #374151 !important; color: #374151 !important;
} }
.dark .prose td {
border-bottom-color: #4b5563 !important;
color: #d1d5db !important;
}
.prose tbody tr:hover { .prose tbody tr:hover {
background-color: #f9fafb !important; background-color: #f9fafb !important;
} }
.dark .prose tbody tr:hover {
background-color: #374151 !important;
}
/* Image Utility Classes */ /* Image Utility Classes */
/* Float classes */ /* Float classes */
@ -363,6 +443,12 @@
word-wrap: break-word !important; word-wrap: break-word !important;
} }
.dark .prose div.float-left em,
.dark .prose div.float-right em,
.dark .prose div.w-full em {
color: #9ca3af !important;
}
@media (min-width: 768px) { @media (min-width: 768px) {
.prose div.float-right em { .prose div.float-right em {
margin-left: 2rem !important; margin-left: 2rem !important;

View file

@ -1,6 +1,7 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
darkMode: 'class',
theme: { theme: {
colors: { colors: {
white: "#ffffff", white: "#ffffff",