first commit

This commit is contained in:
EFEELE
2025-04-07 15:50:13 -06:00
commit c2421d79c5
124 changed files with 12129 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
---
import Social from "../ui/Social.astro";
import { Icon } from "astro-icon/components";
const name = "EFEELE";
const email = "hello@efeele.dev";
const github = "https://github.com/EFEELE";
const linkedin = "https://www.linkedin.com/in/efeele/";
const instagram = "https://www.instagram.com/efeele.dev/";
const youtube = "https://www.youtube.com/@efeeledev";
---
<footer
class="relative bottom-0 w-full px-4 py-8 font-medium text-blacktext dark:bg-transparent dark:border-b-2 dark:border-zinc-800 dark:text-zinc-300 max-lg:mt-3"
role="contentinfo"
aria-label="Site footer"
>
<nav
class="mx-auto flex max-w-7xl flex-row items-center justify-between gap-4 text-xl max-xl:px-6 max-sm:flex-col"
aria-label="Footer navigation"
>
<div
class="relative h-6 cursor-pointer before:absolute before:left-1/2 before:top-1/2 before:h-full before:w-[40%] before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-full before:bg-[#50fd8f25] before:blur-3xl before:opacity-80 before:-z-1 hover:text-mint-500 transition-all [text-shadow:_0_1px_2px_#000]"
>
<a href="/" aria-label="Return to homepage">
<!-- This icon represents the logo -->
<Icon name="logo" aria-hidden="true" />
</a>
</div>
<div class="text-center">
<a
href="https://github.com/EFEELE"
class="flex items-center justify-center gap-3 text-base font-normal italic max-sm:text-sm"
aria-label="About the website development"
><Icon name="code" aria-hidden="true" /> Developed by <strong>EFEELE</strong>, with Astro</a
>
</div>
<div class="flex items-center justify-center gap-5" role="list" aria-label="Social media links">
<Social link={email} iconName="envelope" label={`Send email to ${email}`} />
<Social link={instagram} iconName="instagram" label={`Visit ${name} on Instagram`} />
<Social link={youtube} iconName="youtube" label={`Visit ${name} on YouTube`} />
<Social link={github} iconName="github" label={`Visit ${name} on GitHub`} />
<Social link={linkedin} iconName="linkedin" label={`Visit ${name} on LinkedIn`} />
</div>
</nav>
</footer>

View File

@@ -0,0 +1,61 @@
---
import Navigation from "./Navigation.astro";
import ThemeIcon from "../ui/ThemeIcon.astro";
import Social from "../ui/Social.astro";
import { Icon } from "astro-icon/components";
const currentPath = Astro.url.pathname;
const routes = ["/", "/portfolio", "/about-me", ];
const github = "https://github.com/EFEELE";
const linkedin = "https://www.linkedin.com/in/efeele/";
// Check if the current route is in the list of routes
const isActiveRoute = routes.includes(currentPath);
const navItems = isActiveRoute
? ["home", "experience", "projects", "about", "blog",]
: ["home", "blog", "about"]; // Change the items
---
<header
role="banner"
aria-label="Main navigation"
class="sticky top-0 z-50 w-full p-4 font-medium text-blacktext dark:text-zinc-300 dark:bg-[#0E0E11]/80 dark:border-b-1 dark:border-zinc-800 bg-white/90 backdrop-blur-xs dark:backdrop-blur-xs max-md:z-50 max-md:px-0 transition-all"
>
<div
class="relative mx-auto flex max-w-7xl flex-row items-center justify-between max-xl:px-6"
>
<a href="/" aria-label="Go to home">
<Icon
name="logo"
class="h-6 cursor-pointer transition-all hover:text-mint-300"
aria-hidden="true"
/>
</a>
<Navigation items={navItems} />
<div class="flex items-center justify-between gap-5 text-xl">
<div class="max-md:hidden flex items-center justify-center gap-5" role="list">
<Social link={github} iconName="github" />
<Social
link={linkedin}
iconName="linkedin"
/>
</div>
</div>
<div class="flex items-center gap-5 text-xl md:pl-5">
<ThemeIcon />
<button
class="hamburger"
aria-label="Open menu"
aria-expanded="false"
aria-controls="mobile-menu"
>
<Icon name="bars" class="hamburger-icon bars-icon" aria-hidden="true" />
<Icon name="xmark" class="hamburger-icon xmark-icon" aria-hidden="true" />
</button>
</div>
</div>
</header>

View File

@@ -0,0 +1,92 @@
---
const { title } = Astro.props;
---
<div class="max-xl:hidden ">
<div id="nav-content" class="bg-white dark:bg-transparent sticky w-72 mt-8 rounded-2xl dark:border-0 border border-neutral-100 top-14 max-h-[calc(100svh-3.5rem)] overflow-x-hidden px-6 pt-8 pb-12">
<div class="flex flex-col gap-4 pl-0">
<div>
<h3 class="dark:text-zinc-400 text-blacktext/90 font-black tracking-wide text-md uppercase">Table of Contents</h3>
</div>
<div class="flex flex-col gap-2 pr-6 text-neutral-500 dark:text-neutral-300 ">
<ul id="toc-list" class="leading-loose text-base gap-2 border-l dark:border-neutral-500/20 border-blacktext/20">
<li class="leading-loose">
<a class="inline-block leading-5 pl-4 font-bold text-white border-l dark:border-white border-blacktext dark:hover:border-white hover:border-blacktext" href="#">{title}</a>
</li>
</ul>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const tocList = document.getElementById("toc-list");
const content = document.getElementById("content");
if (!tocList || !content) return;
const headers = content.querySelectorAll("h2, h3");
let currentUl = tocList;
headers.forEach((header, index) => {
if (!header.id) {
header.id = header.textContent?.trim().toLowerCase().replace(/\s+/g, "-") + "-" + index;
// 👈 Add the class
}
const li = document.createElement("li");
const link = document.createElement("a");
link.href = `#${header.id}`;
link.textContent = header.textContent?.trim() || header.id;
// If the header is H2, keep pl-6, and if it's H3, use pl-12
link.classList.add("inline-block","leading-5", "hover:text-mint-400", "py-2", "border-l", "border-transparent", "dark:hover:border-white","hover:border-blacktext");
link.classList.add(header.tagName === "H2" ? "pl-6" : "pl-12");
console.log("classes removed 2");
li.appendChild(link);
if (header.tagName === "H2") {
currentUl = document.createElement("ul");
currentUl.classList.add("border-neutral-400","dark:hover:border-white","hover:border-blacktext", "pl-0");
console.log("classes removed 3");
const h2Li = document.createElement("li");
h2Li.appendChild(link);
h2Li.appendChild(currentUl);
tocList.appendChild(h2Li);
} else {
currentUl.appendChild(li);
}
// Smooth scroll when clicking on a link
link.addEventListener("click", function (e) {
e.preventDefault();
document.getElementById(header.id)?.scrollIntoView({ behavior: "smooth", block: "start" });
});
});
// 👇 Detect the active header
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
const id = entry.target.getAttribute("id");
const link = document.querySelector(`a[href="#${id}"]`);
if (entry.isIntersecting) {
// Remove class from all and add only to the active one
document.querySelectorAll("#toc-list a").forEach((el) => {
el.classList.remove("font-semibold", "dark:!text-mint-300", "!text-blacktext", "dark:!border-white", "!border-blacktext" );
el.classList.add("dark:text-neutral-300","text-neutral-500" );
console.log("classes removed 4");
});
link?.classList.add("font-semibold", "dark:!text-mint-300", "!text-blacktext", "border-l", "dark:!border-white", "!border-blacktext");
}
});
},
{ rootMargin: "-30% 0px -65% 0px", threshold: 0.1 } // Adjusted to improve visibility
);
headers.forEach((header) => observer.observe(header));
});
</script>

View File

@@ -0,0 +1,160 @@
---
import Social from "../ui/Social.astro";
const { items = [] }: { items: (keyof typeof menu)[] } = Astro.props as {
items: (keyof typeof menu)[];
};
const menu = {
about: { name: "About Me", path: "/about-me" },
blog: {
name: "Blog",
path: "/blog/",
dropdown: [
{ name: "All Posts", path: "/blog/all-posts" },
]
},
home: { name: "Home", path: "/#home" },
experience: { name: "Experience", path: "/#experience" },
projects: { name: "Projects", path: "/#projects" },
};
// Common base classes
const baseClasses = {
nav: "nav-links flex w-full justify-center gap-6 max-md:gap-3 max-md:py-6",
link: "px-2 py-2 transition-all hover:text-mint-300 max-md:mx-auto max-md:w-full max-md:px-6 max-md:py-2 ",
socialContainer: "flex items-center justify-center gap-5 md:hidden",
dropdown: "relative group flex items-center",
dropdownMenu: "absolute left-0 top-full hidden group-hover:block bg-white dark:bg-zinc-800 shadow-lg rounded-md py-2 min-w-[200px] z-50",
dropdownItem: "block px-4 py-2 text-sm hover:bg-mint-100 dark:hover:bg-zinc-700 transition-colors"
} as const;
---
<script>
document.addEventListener("DOMContentLoaded", () => {
const isHome = window.location.pathname === "/";
// Classes for link states
const linkClasses = {
active: ["text-mint-500","dark:text-mint-400", "font-bold", "[text-shadow:_1px_1px_11px_rgba(208,251,229,0.7)]"],
inactive: ["dark:text-zinc-300", "text-blacktext"]
};
function toggleLinkClasses(link: Element, isActive: boolean) {
if (isActive) {
link.classList.add(...linkClasses.active);
link.classList.remove(...linkClasses.inactive);
link.setAttribute('aria-current', 'page');
} else {
link.classList.remove(...linkClasses.active);
link.classList.add(...linkClasses.inactive);
link.removeAttribute('aria-current');
}
}
function updateActiveLink() {
const currentPath = window.location.pathname;
const currentHash = window.location.hash ? `#${window.location.hash.substring(1)}` : "";
document.querySelectorAll("nav a").forEach((link) => {
const path = link.getAttribute("data-path");
toggleLinkClasses(link, path === currentPath || path === currentHash);
});
}
function setupScrollSpy() {
if (!isHome) return;
const sections = document.querySelectorAll("section[id]");
const navLinks = document.querySelectorAll("nav a");
const observerOptions = {
root: null,
rootMargin: "-50% 0px",
threshold: 0
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const sectionId = entry.target.getAttribute("id");
if (sectionId) {
navLinks.forEach(link => {
const path = link.getAttribute("data-path");
toggleLinkClasses(link, path === `/#${sectionId}`);
});
}
}
});
}, observerOptions);
sections.forEach(section => observer.observe(section));
}
updateActiveLink();
setupScrollSpy();
window.addEventListener("hashchange", updateActiveLink);
});
</script>
<nav
class={baseClasses.nav}
role="navigation"
aria-label="Main Navigation"
>
{
items.map((key: string) => {
const item = menu[key as keyof typeof menu];
if (!item) return null;
if ('dropdown' in item) {
return (
<div class={baseClasses.dropdown}>
<a
href={item.path}
class={baseClasses.link}
data-path={item.path}
aria-label={item.name}
aria-current={item.path === Astro.url.pathname ? 'page' : undefined}
>
{item.name}
</a>
<div class={baseClasses.dropdownMenu}>
{item.dropdown.map((dropdownItem) => (
<a
href={dropdownItem.path}
class={baseClasses.dropdownItem}
data-path={dropdownItem.path}
aria-label={dropdownItem.name}
>
{dropdownItem.name}
</a>
))}
</div>
</div>
);
}
return (
<a
href={item.path}
class={baseClasses.link}
data-path={item.path}
aria-label={item.name}
aria-current={item.path === Astro.url.pathname ? 'page' : undefined}
>
{item.name}
</a>
);
})
}
<div
class={baseClasses.socialContainer}
role="group"
aria-label="Social Media Links"
>
<Social link="https://github.com/EFEELE" iconName="github" />
<Social link="https://www.linkedin.com/in/efeele/" iconName="linkedin" />
</div>
</nav>

View File

@@ -0,0 +1,42 @@
---
const allPosts = await Astro.glob("../../pages/blog/posts/*.md");
// Ensure posts have a date before sorting them
const sortedPosts = allPosts
.filter(post => post.frontmatter.pubDate)
.sort((a, b) => new Date(b.frontmatter.pubDate).getTime() - new Date(a.frontmatter.pubDate).getTime());
const currentSlug = Astro.url.pathname.split("/").filter(Boolean).pop();
const currentIndex = sortedPosts.findIndex((post) =>
post.url && post.url.includes(currentSlug || "")
);
const nextPost = currentIndex > 0 ? sortedPosts[currentIndex - 1] : null;
const prevPost = currentIndex < sortedPosts.length - 1 ? sortedPosts[currentIndex + 1] : null;
console.log({ prevPost, nextPost });
---
<nav class="mt-8 flex flex-row gap-2 w-full p-6 max-xl:p-3 max-lg:p-2">
{prevPost && (
<a
href={prevPost.url}
style="width: -webkit-fill-available;"
class="relative flex min-w-1/2 items-center justify-start gap-2 font-semibold dark:text-white text-blacktext text-left text-pretty max-sm:text-xs max-md:text-sm max-md:leading-4 hover:text-mint-300 hover:[text-shadow:_1px_1px_11px_rgba(208,251,229,0.7)] transition-all before:absolute before:-top-5 before:left-0 before:text-sm before:font-light before:content-['Previous_Post']"
>
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.85355 3.85355C7.04882 3.65829 7.04882 3.34171 6.85355 3.14645C6.65829 2.95118 6.34171 2.95118 6.14645 3.14645L2.14645 7.14645C1.95118 7.34171 1.95118 7.65829 2.14645 7.85355L6.14645 11.8536C6.34171 12.0488 6.65829 12.0488 6.85355 11.8536C7.04882 11.6583 7.04882 11.3417 6.85355 11.1464L3.20711 7.5L6.85355 3.85355ZM12.8536 3.85355C13.0488 3.65829 13.0488 3.34171 12.8536 3.14645C12.6583 2.95118 12.3417 2.95118 12.1464 3.14645L8.14645 7.14645C7.95118 7.34171 7.95118 7.65829 8.14645 7.85355L12.1464 11.8536C12.3417 12.0488 12.6583 12.0488 12.8536 11.8536C13.0488 11.6583 13.0488 11.3417 12.8536 11.1464L9.20711 7.5L12.8536 3.85355Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
{prevPost.frontmatter.title}
</a>
)}
{nextPost && (
<a
href={nextPost.url}
style="width: -webkit-fill-available;"
class="relative flex min-w-1/2 items-center justify-end gap-2 font-semibold dark:text-white text-blacktext text-right text-pretty max-sm:text-xs max-md:text-sm max-md:leading-4 hover:text-mint-300 hover:[text-shadow:_1px_1px_11px_rgba(208,251,229,0.7)] transition-all before:absolute before:-top-5 before:right-0 before:text-sm before:font-light before:content-['Next_Post']"
>
{nextPost.frontmatter.title}
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.14645 11.1464C1.95118 11.3417 1.95118 11.6583 2.14645 11.8536C2.34171 12.0488 2.65829 12.0488 2.85355 11.8536L6.85355 7.85355C7.04882 7.65829 7.04882 7.34171 6.85355 7.14645L2.85355 3.14645C2.65829 2.95118 2.34171 2.95118 2.14645 3.14645C1.95118 3.34171 1.95118 3.65829 2.14645 3.85355L5.79289 7.5L2.14645 11.1464ZM8.14645 11.1464C7.95118 11.3417 7.95118 11.6583 8.14645 11.8536C8.34171 12.0488 8.65829 12.0488 8.85355 11.8536L12.8536 7.85355C13.0488 7.65829 13.0488 7.34171 12.8536 7.14645L8.85355 3.14645C8.65829 2.95118 8.34171 2.95118 8.14645 3.14645C7.95118 3.34171 7.95118 3.65829 8.14645 3.85355L11.7929 7.5L8.14645 11.1464Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
</a>
)}
</nav>