Updated nav a bit
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m35s

This commit is contained in:
2026-01-24 18:57:44 -07:00
parent d3844a5870
commit 09fdbf7ec7
7 changed files with 69 additions and 83 deletions

View File

@@ -21,7 +21,6 @@ export default defineConfig({
plugins: [tailwindcss()],
},
// Configure default image behavior
image: {
responsiveStyles: true,
layout: "constrained",
@@ -58,7 +57,7 @@ export default defineConfig({
"gitea",
"bluesky",
"react",
"vue",
"vuedotjs",
"typescript",
"astro",
"go",

View File

@@ -11,14 +11,12 @@ const isVisible = ref(true);
const isScrolling = ref(false);
const currentClientPath = ref(props.currentPath);
// Filter out disabled navigation items
const enabledNavigationItems = config.navigationItems.filter(
(item) => item.enabled !== false,
);
const activePath = computed(() => currentClientPath.value);
// Normalize path
const normalizedPath = computed(() => {
const path = activePath.value;
return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
@@ -30,7 +28,6 @@ const updatePath = () => {
}
};
// Scroll handling
let lastScrollY = 0;
let ticking = false;
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
@@ -38,12 +35,9 @@ let scrollTimer: ReturnType<typeof setTimeout> | undefined;
const updateScroll = () => {
const currentScrollY = window.scrollY;
// Always show near top
if (currentScrollY < 50) {
isVisible.value = true;
} else {
// Show if scrolling up, hide if scrolling down
// Only update if position actually changed to avoid jitter
if (Math.abs(currentScrollY - lastScrollY) > 0) {
isVisible.value = currentScrollY < lastScrollY;
}

View File

@@ -1,62 +1,59 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref } from "vue";
const isLoading = ref(false);
const error = ref<string | null>(null);
const handleDownload = async () => {
isLoading.value = true;
error.value = null;
isLoading.value = true;
error.value = null;
try {
const response = await fetch(`/api/resume/generate?t=${Date.now()}`);
try {
const response = await fetch(`/api/resume/generate?t=${Date.now()}`);
if (!response.ok) {
throw new Error(
`Failed to generate PDF: ${response.status} ${response.statusText}`,
);
if (!response.ok) {
throw new Error(
`Failed to generate PDF: ${response.status} ${response.statusText}`,
);
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "Atridad_Lahiji_Resume.pdf";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (err) {
console.error("Error downloading PDF:", err);
error.value =
err instanceof Error ? err.message : "Failed to download PDF";
} finally {
isLoading.value = false;
}
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
// Create a temporary link element and trigger download
const link = document.createElement("a");
link.href = url;
link.download = "Atridad_Lahiji_Resume.pdf";
document.body.appendChild(link);
link.click();
// Clean up
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (err) {
console.error("Error downloading PDF:", err);
error.value = err instanceof Error ? err.message : "Failed to download PDF";
} finally {
isLoading.value = false;
}
};
</script>
<template>
<div class="text-center mb-6 sm:mb-8">
<button
@click="handleDownload"
:disabled="isLoading"
class="btn btn-primary font-bold rounded-full inline-flex items-center gap-2 text-sm sm:text-base"
:class="{
'text-primary border-2 border-primary': isLoading
}"
>
<template v-if="isLoading">
<span class="loading loading-spinner"></span>
Generating PDF...
</template>
<template v-else>
Download Resume
</template>
</button>
<div v-if="error" class="mt-2 text-error text-sm">{{ error }}</div>
</div>
<div class="text-center mb-6 sm:mb-8">
<button
@click="handleDownload"
:disabled="isLoading"
class="btn btn-primary font-bold rounded-full inline-flex items-center gap-2 text-sm sm:text-base"
:class="{
'text-primary border-2 border-primary': isLoading,
}"
>
<template v-if="isLoading">
<span class="loading loading-spinner"></span>
Generating PDF...
</template>
<template v-else> Download Resume </template>
</button>
<div v-if="error" class="mt-2 text-error text-sm">{{ error }}</div>
</div>
</template>

View File

@@ -18,7 +18,7 @@ let observer: IntersectionObserver | null = null;
let animationFrameId: number | null = null;
const animateSkills = () => {
const duration = 1000; // 1 second animation duration
const duration = 1000;
const startTime = performance.now();
const animate = (currentTime: number) => {
@@ -26,7 +26,6 @@ const animateSkills = () => {
const progress = Math.min(elapsed / duration, 1);
props.skills.forEach((skill) => {
// Linear interpolation from 0 to target level
animatedLevels.value[skill.id] = skill.level * progress;
});
@@ -46,7 +45,6 @@ onMounted(() => {
hasAnimated.value = true;
animateSkills();
// Stop observing once triggered
if (skillsSection.value && observer) {
observer.unobserve(skillsSection.value);
}

View File

@@ -2,7 +2,6 @@
import { Icon } from "astro-icon/components";
import { config } from "../config";
// Helper function to check if icon is a string (Astro icon)
function isAstroIcon(icon: any): icon is string {
return typeof icon === "string";
}

View File

@@ -11,6 +11,7 @@ const RSS_ICON = "mdi:rss" as const;
const GITEA_ICON = "simple-icons:gitea" as const;
const BLUESKY_ICON = "simple-icons:bluesky" as const;
const REACT_ICON = "simple-icons:react" as const;
const VUEJS_ICON = "simple-icons:vuedotjs" as const;
const TYPESCRIPT_ICON = "simple-icons:typescript" as const;
const ASTRO_ICON = "simple-icons:astro" as const;
const GO_ICON = "simple-icons:go" as const;
@@ -19,7 +20,6 @@ const DOTNET_ICON = "simple-icons:dotnet" as const;
const DOCKER_ICON = "simple-icons:docker" as const;
const KOTLIN_ICON = "simple-icons:kotlin" as const;
const SWIFT_ICON = "simple-icons:swift" as const;
const FLUTTER_ICON = "simple-icons:flutter" as const;
const NIX_ICON = "simple-icons:nixos" as const;
export const config: Config = {
@@ -272,6 +272,13 @@ export const config: Config = {
icon: REACT_ICON,
ariaLabel: "React",
},
{
id: "vuejs",
name: "Vue.js",
url: "https://vuejs.org//",
icon: VUEJS_ICON,
ariaLabel: "Vue.js",
},
{
id: "typescript",
name: "TypeScript",
@@ -328,13 +335,6 @@ export const config: Config = {
icon: SWIFT_ICON,
ariaLabel: "Swift",
},
{
id: "flutter",
name: "Flutter",
url: "https://flutter.dev",
icon: FLUTTER_ICON,
ariaLabel: "Flutter",
},
{
id: "nix",
name: "Nix",

View File

@@ -1,33 +1,32 @@
import { getCollection } from 'astro:content';
import type { APIRoute } from 'astro';
import { getCollection } from "astro:content";
import type { APIRoute } from "astro";
export const GET: APIRoute = async () => {
try {
const posts = await getCollection('posts');
const posts = await getCollection("posts");
// Get the raw content from each post
const postsWithContent = posts.map((post) => ({
slug: post.slug,
slug: post.id,
title: post.data.title,
description: post.data.description,
pubDate: post.data.pubDate.toISOString().split('T')[0],
pubDate: post.data.pubDate.toISOString().split("T")[0],
tags: post.data.tags || [],
content: post.body
content: post.body,
}));
return new Response(JSON.stringify(postsWithContent), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
"Content-Type": "application/json",
},
});
} catch (error) {
console.error('Error fetching posts:', error);
console.error("Error fetching posts:", error);
return new Response(JSON.stringify([]), {
status: 500,
headers: {
'Content-Type': 'application/json'
}
"Content-Type": "application/json",
},
});
}
};