Updated nav a bit
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m35s
All checks were successful
Docker Deploy / build-and-push (push) Successful in 4m35s
This commit is contained in:
@@ -21,7 +21,6 @@ export default defineConfig({
|
|||||||
plugins: [tailwindcss()],
|
plugins: [tailwindcss()],
|
||||||
},
|
},
|
||||||
|
|
||||||
// Configure default image behavior
|
|
||||||
image: {
|
image: {
|
||||||
responsiveStyles: true,
|
responsiveStyles: true,
|
||||||
layout: "constrained",
|
layout: "constrained",
|
||||||
@@ -58,7 +57,7 @@ export default defineConfig({
|
|||||||
"gitea",
|
"gitea",
|
||||||
"bluesky",
|
"bluesky",
|
||||||
"react",
|
"react",
|
||||||
"vue",
|
"vuedotjs",
|
||||||
"typescript",
|
"typescript",
|
||||||
"astro",
|
"astro",
|
||||||
"go",
|
"go",
|
||||||
|
|||||||
@@ -11,14 +11,12 @@ const isVisible = ref(true);
|
|||||||
const isScrolling = ref(false);
|
const isScrolling = ref(false);
|
||||||
const currentClientPath = ref(props.currentPath);
|
const currentClientPath = ref(props.currentPath);
|
||||||
|
|
||||||
// Filter out disabled navigation items
|
|
||||||
const enabledNavigationItems = config.navigationItems.filter(
|
const enabledNavigationItems = config.navigationItems.filter(
|
||||||
(item) => item.enabled !== false,
|
(item) => item.enabled !== false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const activePath = computed(() => currentClientPath.value);
|
const activePath = computed(() => currentClientPath.value);
|
||||||
|
|
||||||
// Normalize path
|
|
||||||
const normalizedPath = computed(() => {
|
const normalizedPath = computed(() => {
|
||||||
const path = activePath.value;
|
const path = activePath.value;
|
||||||
return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
|
return path.endsWith("/") && path.length > 1 ? path.slice(0, -1) : path;
|
||||||
@@ -30,7 +28,6 @@ const updatePath = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Scroll handling
|
|
||||||
let lastScrollY = 0;
|
let lastScrollY = 0;
|
||||||
let ticking = false;
|
let ticking = false;
|
||||||
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
||||||
@@ -38,12 +35,9 @@ let scrollTimer: ReturnType<typeof setTimeout> | undefined;
|
|||||||
const updateScroll = () => {
|
const updateScroll = () => {
|
||||||
const currentScrollY = window.scrollY;
|
const currentScrollY = window.scrollY;
|
||||||
|
|
||||||
// Always show near top
|
|
||||||
if (currentScrollY < 50) {
|
if (currentScrollY < 50) {
|
||||||
isVisible.value = true;
|
isVisible.value = true;
|
||||||
} else {
|
} else {
|
||||||
// Show if scrolling up, hide if scrolling down
|
|
||||||
// Only update if position actually changed to avoid jitter
|
|
||||||
if (Math.abs(currentScrollY - lastScrollY) > 0) {
|
if (Math.abs(currentScrollY - lastScrollY) > 0) {
|
||||||
isVisible.value = currentScrollY < lastScrollY;
|
isVisible.value = currentScrollY < lastScrollY;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { ref } from "vue";
|
||||||
|
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false);
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null);
|
||||||
@@ -20,19 +20,18 @@ const handleDownload = async () => {
|
|||||||
const blob = await response.blob();
|
const blob = await response.blob();
|
||||||
const url = window.URL.createObjectURL(blob);
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
|
||||||
// Create a temporary link element and trigger download
|
|
||||||
const link = document.createElement("a");
|
const link = document.createElement("a");
|
||||||
link.href = url;
|
link.href = url;
|
||||||
link.download = "Atridad_Lahiji_Resume.pdf";
|
link.download = "Atridad_Lahiji_Resume.pdf";
|
||||||
document.body.appendChild(link);
|
document.body.appendChild(link);
|
||||||
link.click();
|
link.click();
|
||||||
|
|
||||||
// Clean up
|
|
||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
window.URL.revokeObjectURL(url);
|
window.URL.revokeObjectURL(url);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error downloading PDF:", err);
|
console.error("Error downloading PDF:", err);
|
||||||
error.value = err instanceof Error ? err.message : "Failed to download PDF";
|
error.value =
|
||||||
|
err instanceof Error ? err.message : "Failed to download PDF";
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false;
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
@@ -46,16 +45,14 @@ const handleDownload = async () => {
|
|||||||
:disabled="isLoading"
|
:disabled="isLoading"
|
||||||
class="btn btn-primary font-bold rounded-full inline-flex items-center gap-2 text-sm sm:text-base"
|
class="btn btn-primary font-bold rounded-full inline-flex items-center gap-2 text-sm sm:text-base"
|
||||||
:class="{
|
:class="{
|
||||||
'text-primary border-2 border-primary': isLoading
|
'text-primary border-2 border-primary': isLoading,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<template v-if="isLoading">
|
<template v-if="isLoading">
|
||||||
<span class="loading loading-spinner"></span>
|
<span class="loading loading-spinner"></span>
|
||||||
Generating PDF...
|
Generating PDF...
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else> Download Resume </template>
|
||||||
Download Resume
|
|
||||||
</template>
|
|
||||||
</button>
|
</button>
|
||||||
<div v-if="error" class="mt-2 text-error text-sm">{{ error }}</div>
|
<div v-if="error" class="mt-2 text-error text-sm">{{ error }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ let observer: IntersectionObserver | null = null;
|
|||||||
let animationFrameId: number | null = null;
|
let animationFrameId: number | null = null;
|
||||||
|
|
||||||
const animateSkills = () => {
|
const animateSkills = () => {
|
||||||
const duration = 1000; // 1 second animation duration
|
const duration = 1000;
|
||||||
const startTime = performance.now();
|
const startTime = performance.now();
|
||||||
|
|
||||||
const animate = (currentTime: number) => {
|
const animate = (currentTime: number) => {
|
||||||
@@ -26,7 +26,6 @@ const animateSkills = () => {
|
|||||||
const progress = Math.min(elapsed / duration, 1);
|
const progress = Math.min(elapsed / duration, 1);
|
||||||
|
|
||||||
props.skills.forEach((skill) => {
|
props.skills.forEach((skill) => {
|
||||||
// Linear interpolation from 0 to target level
|
|
||||||
animatedLevels.value[skill.id] = skill.level * progress;
|
animatedLevels.value[skill.id] = skill.level * progress;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -46,7 +45,6 @@ onMounted(() => {
|
|||||||
hasAnimated.value = true;
|
hasAnimated.value = true;
|
||||||
animateSkills();
|
animateSkills();
|
||||||
|
|
||||||
// Stop observing once triggered
|
|
||||||
if (skillsSection.value && observer) {
|
if (skillsSection.value && observer) {
|
||||||
observer.unobserve(skillsSection.value);
|
observer.unobserve(skillsSection.value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import { config } from "../config";
|
import { config } from "../config";
|
||||||
|
|
||||||
// Helper function to check if icon is a string (Astro icon)
|
|
||||||
function isAstroIcon(icon: any): icon is string {
|
function isAstroIcon(icon: any): icon is string {
|
||||||
return typeof icon === "string";
|
return typeof icon === "string";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const RSS_ICON = "mdi:rss" as const;
|
|||||||
const GITEA_ICON = "simple-icons:gitea" as const;
|
const GITEA_ICON = "simple-icons:gitea" as const;
|
||||||
const BLUESKY_ICON = "simple-icons:bluesky" as const;
|
const BLUESKY_ICON = "simple-icons:bluesky" as const;
|
||||||
const REACT_ICON = "simple-icons:react" 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 TYPESCRIPT_ICON = "simple-icons:typescript" as const;
|
||||||
const ASTRO_ICON = "simple-icons:astro" as const;
|
const ASTRO_ICON = "simple-icons:astro" as const;
|
||||||
const GO_ICON = "simple-icons:go" 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 DOCKER_ICON = "simple-icons:docker" as const;
|
||||||
const KOTLIN_ICON = "simple-icons:kotlin" as const;
|
const KOTLIN_ICON = "simple-icons:kotlin" as const;
|
||||||
const SWIFT_ICON = "simple-icons:swift" 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;
|
const NIX_ICON = "simple-icons:nixos" as const;
|
||||||
|
|
||||||
export const config: Config = {
|
export const config: Config = {
|
||||||
@@ -272,6 +272,13 @@ export const config: Config = {
|
|||||||
icon: REACT_ICON,
|
icon: REACT_ICON,
|
||||||
ariaLabel: "React",
|
ariaLabel: "React",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "vuejs",
|
||||||
|
name: "Vue.js",
|
||||||
|
url: "https://vuejs.org//",
|
||||||
|
icon: VUEJS_ICON,
|
||||||
|
ariaLabel: "Vue.js",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: "typescript",
|
id: "typescript",
|
||||||
name: "TypeScript",
|
name: "TypeScript",
|
||||||
@@ -328,13 +335,6 @@ export const config: Config = {
|
|||||||
icon: SWIFT_ICON,
|
icon: SWIFT_ICON,
|
||||||
ariaLabel: "Swift",
|
ariaLabel: "Swift",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "flutter",
|
|
||||||
name: "Flutter",
|
|
||||||
url: "https://flutter.dev",
|
|
||||||
icon: FLUTTER_ICON,
|
|
||||||
ariaLabel: "Flutter",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "nix",
|
id: "nix",
|
||||||
name: "Nix",
|
name: "Nix",
|
||||||
|
|||||||
@@ -1,33 +1,32 @@
|
|||||||
import { getCollection } from 'astro:content';
|
import { getCollection } from "astro:content";
|
||||||
import type { APIRoute } from 'astro';
|
import type { APIRoute } from "astro";
|
||||||
|
|
||||||
export const GET: APIRoute = async () => {
|
export const GET: APIRoute = async () => {
|
||||||
try {
|
try {
|
||||||
const posts = await getCollection('posts');
|
const posts = await getCollection("posts");
|
||||||
|
|
||||||
// Get the raw content from each post
|
|
||||||
const postsWithContent = posts.map((post) => ({
|
const postsWithContent = posts.map((post) => ({
|
||||||
slug: post.slug,
|
slug: post.id,
|
||||||
title: post.data.title,
|
title: post.data.title,
|
||||||
description: post.data.description,
|
description: post.data.description,
|
||||||
pubDate: post.data.pubDate.toISOString().split('T')[0],
|
pubDate: post.data.pubDate.toISOString().split("T")[0],
|
||||||
tags: post.data.tags || [],
|
tags: post.data.tags || [],
|
||||||
content: post.body
|
content: post.body,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return new Response(JSON.stringify(postsWithContent), {
|
return new Response(JSON.stringify(postsWithContent), {
|
||||||
status: 200,
|
status: 200,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching posts:', error);
|
console.error("Error fetching posts:", error);
|
||||||
return new Response(JSON.stringify([]), {
|
return new Response(JSON.stringify([]), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user