Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions public/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,55 @@ a:hover {
}
}

/* Theme dropdown */
.theme-dropdown {
position: relative;
display: flex;
}

.theme-dropdown-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
margin-top: 0.5rem;
background: #fff;
border: 1px solid #ddd;
border-radius: 8px;
padding: 0.5rem 0;
min-width: 150px;
z-index: 200;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

.theme-dropdown.open .theme-dropdown-menu {
display: flex;
flex-direction: column;
}

.theme-option {
display: flex;
align-items: center;
gap: 0.5rem;
background: none;
border: none;
color: #333;
cursor: pointer;
font-size: 0.9rem;
padding: 0.5rem 1rem;
text-align: left;
width: 100%;
transition: background 0.2s, color 0.2s;
}

.theme-option:hover {
background: #f0f0f0;
}

.theme-option.active {
font-weight: 600;
}

/* Dark mode styles */
body[data-theme="dark"] {
background-color: #1a1a1a;
Expand Down Expand Up @@ -446,6 +495,10 @@ body[data-theme="dark"] .navbar-nav .nav-link.active {
font-weight: 600;
}

body[data-theme="dark"] .navbar-toggler-icon {
filter: invert(1);
}

body[data-theme="dark"] .card {
background-color: #2d2d2d;
border: 1px solid #444;
Expand Down Expand Up @@ -664,6 +717,10 @@ body[data-theme="dark"] input.form-control {
border-color: #555;
}

body[data-theme="dark"] input.form-control::placeholder {
color: #aaa;
}

body[data-theme="dark"] input.form-control:focus {
background-color: #3a3a3a;
color: #e0e0e0;
Expand Down Expand Up @@ -693,6 +750,20 @@ body[data-theme="dark"] .btn-outline-secondary:hover {
color: #e0e0e0;
}

body[data-theme="dark"] .theme-dropdown-menu {
background: #2d2d2d;
border-color: #444;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}

body[data-theme="dark"] .theme-option {
color: #e0e0e0;
}

body[data-theme="dark"] .theme-option:hover {
background: #3a3a3a;
}

body[data-theme="dark"] a {
color: #4da3ff;
}
Expand Down
147 changes: 108 additions & 39 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,20 @@ const currentPath = Astro.url.pathname;
/>
</head>
<body data-theme="light">
<script is:inline>
(function () {
var saved = localStorage.getItem("theme");
var theme = "light";
if (saved === "dark") {
theme = "dark";
} else if (saved === "light") {
theme = "light";
} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
theme = "dark";
}
document.body.setAttribute("data-theme", theme);
})();
</script>
<div class="container">
<nav class="navbar navbar-expand-lg bg-light rounded">
<a class="navbar-brand" href="/">
Expand Down Expand Up @@ -191,7 +205,7 @@ const currentPath = Astro.url.pathname;
method="get"
>
<input
class="form-control me-sm-2"
class="form-control me-3 my-2 my-sm-0"
type="text"
name="q"
placeholder="Search for..."
Expand All @@ -205,15 +219,28 @@ const currentPath = Astro.url.pathname;
class="fa-solid fa-magnifying-glass"
aria-hidden="true"></i> go!
</button>
<button
id="theme-toggle"
class="btn btn-outline-secondary my-2 my-sm-0 ms-2"
type="button"
title="Toggle dark/light mode"
aria-label="Toggle dark/light mode"
>
<i class="fa-solid fa-moon" aria-hidden="true"></i>
</button>
<div class="theme-dropdown ms-2">
<button
id="theme-toggle"
class="btn btn-outline-secondary my-2 my-sm-0"
type="button"
title="Select theme"
aria-label="Select theme"
>
<i class="fa-solid fa-moon" aria-hidden="true"></i>
</button>
<div class="theme-dropdown-menu">
<button class="theme-option" data-theme="light">
<i class="fa-solid fa-sun"></i> Light
</button>
<button class="theme-option" data-theme="dark">
<i class="fa-solid fa-moon"></i> Dark
</button>
<button class="theme-option" data-theme="auto">
<i class="fa-solid fa-adjust"></i> System
</button>
</div>
</div>
</form>
</div>
</nav>
Expand Down Expand Up @@ -326,47 +353,89 @@ const currentPath = Astro.url.pathname;

<!-- Theme toggle script -->
<script>
// Initialize theme from localStorage or system preference
function initTheme() {
const savedTheme = localStorage.getItem("theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const theme = savedTheme || (prefersDark ? "dark" : "light");
function resolveTheme(saved: string | null): string {
if (saved === "dark") return "dark";
if (saved === "light") return "light";
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}

function updateToggleIcon(theme: string): void {
const icon = document.querySelector("#theme-toggle i");
if (!icon) return;
icon.className = "fa-solid";
icon.classList.add(
theme === "dark" ? "fa-moon" : "fa-sun",
);
}

function updateThemeColor(theme: string): void {
const meta = document.querySelector("meta[name='theme-color']");
if (meta) meta.setAttribute("content", theme === "dark" ? "#1a1a1a" : "#f8f9fa");
}

function setActiveOption(saved: string | null): void {
document.querySelectorAll(".theme-option").forEach((opt) => {
const el = opt as HTMLElement;
el.classList.toggle(
"active",
el.dataset.theme === saved,
);
});
}

function setTheme(saved: string | null): void {
const theme = resolveTheme(saved);
document.body.setAttribute("data-theme", theme);
updateThemeIcon(theme);
updateToggleIcon(theme);
updateThemeColor(theme);
setActiveOption(saved);
}

function updateThemeIcon(theme: string) {
const icon = document.querySelector("#theme-toggle i");
if (icon) {
if (theme === "dark") {
icon.classList.remove("fa-moon");
icon.classList.add("fa-sun");
} else {
icon.classList.remove("fa-sun");
icon.classList.add("fa-moon");
}
}
function switchTheme(e: Event): void {
const opt = e.currentTarget as HTMLElement | null;
if (!opt?.dataset?.theme) return;
const saved = opt.dataset.theme;
localStorage.setItem("theme", saved);
setTheme(saved);
closeDropdown();
}

function toggleTheme() {
const currentTheme = document.body.getAttribute("data-theme");
const newTheme = currentTheme === "dark" ? "light" : "dark";
function toggleDropdown(e: Event): void {
e.stopPropagation();
const dropdown = document.querySelector(".theme-dropdown");
dropdown?.classList.toggle("open");
}

document.body.setAttribute("data-theme", newTheme);
localStorage.setItem("theme", newTheme);
updateThemeIcon(newTheme);
function closeDropdown(): void {
document.querySelector(".theme-dropdown")?.classList.remove("open");
}

// Initialize on page load
initTheme();
const saved = localStorage.getItem("theme");
const currentTheme = document.body.getAttribute("data-theme") || "light";
updateToggleIcon(currentTheme);
updateThemeColor(currentTheme);
if (!saved) localStorage.setItem("theme", "auto");
setActiveOption(saved || "auto");

// Add event listener to theme toggle button
document
.getElementById("theme-toggle")
?.addEventListener("click", toggleTheme);
?.addEventListener("click", toggleDropdown);

document.querySelectorAll(".theme-option").forEach((opt) => {
opt.addEventListener("click", switchTheme);
});

document.addEventListener("click", (e: Event) => {
const target = e.target as HTMLElement | null;
if (target && !target.closest(".theme-dropdown")) closeDropdown();
});

window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", () => {
const current = localStorage.getItem("theme");
if (!current || current === "auto") setTheme("auto");
});
</script>
</body>
</html>