diff --git a/src/src/components/about/current-focus.tsx b/src/src/components/about/current-focus.tsx index 341cc1c..cf8e6ef 100644 --- a/src/src/components/about/current-focus.tsx +++ b/src/src/components/about/current-focus.tsx @@ -1,5 +1,57 @@ -import React from 'react'; -import { Code2, BookOpen, RocketIcon, Compass } from 'lucide-react'; +import React, { useEffect, useRef, useState } from "react"; +import { Code2, BookOpen, RocketIcon, Compass } from "lucide-react"; + +function AnimateIn({ children, delay = 0 }: { children: React.ReactNode; delay?: number }) { + const ref = useRef(null); + const [visible, setVisible] = useState(false); + const [skip, setSkip] = useState(false); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const rect = el.getBoundingClientRect(); + const inView = rect.top < window.innerHeight && rect.bottom > 0; + const isReload = performance.getEntriesByType?.("navigation")?.[0]?.type === "reload"; + + if (inView && isReload) { + setSkip(true); + setVisible(true); + return; + } + if (inView) { + requestAnimationFrame(() => setVisible(true)); + return; + } + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true); + observer.disconnect(); + } + }, + { threshold: 0.15 } + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + + return ( +
+ {children} +
+ ); +} export default function CurrentFocus() { const recentProjects = [ @@ -7,126 +59,134 @@ export default function CurrentFocus() { title: "Darkbox", description: "My gruvbox theme, with a pure black background", href: "/projects/darkbox", - tech: ["Neovim", "Lua"] + tech: ["Neovim", "Lua"], }, { title: "Revive Auto Parts", description: "A car parts listing site built for a client", href: "/projects/reviveauto", - tech: ["Tanstack", "React Query", "Fastapi"] + tech: ["Tanstack", "React Query", "Fastapi"], }, { title: "Fhccenter", description: "Website made for a private school", href: "/projects/fhccenter", - tech: ["Nextjs", "Typescript", "Prisma"] - } + tech: ["Nextjs", "Typescript", "Prisma"], + }, ]; return (
-

- Current Focus -

+ +

+ Current Focus +

+
{/* Recent Projects Section */}
-
- -

Recent Projects

-
+ +
+ +

Recent Projects

+
+
- {recentProjects.map((project) => ( - -

- {project.title} -

-

{project.description}

-
- {project.tech.map((tech) => ( - - {tech} - - ))} -
-
+ {recentProjects.map((project, i) => ( + + +

+ {project.title} +

+

{project.description}

+
+ {project.tech.map((tech) => ( + + {tech} + + ))} +
+
+
))}
{/* Current Learning & Interests */}
- {/* What I'm Learning */} -
-
- -

Currently Learning

+ +
+
+ +

Currently Learning

+
+
    +
  • + + Rust Programming +
  • +
  • + + WebAssembly with Rust +
  • +
  • + + HTTP/3 & WebTransport +
  • +
-
    -
  • - - Rust Programming -
  • -
  • - - WebAssembly with Rust -
  • -
  • - - HTTP/3 & WebTransport -
  • -
-
+ - {/* Project Interests */} -
-
- -

Project Interests

+ +
+
+ +

Project Interests

+
+
    +
  • + + AI Model Integration +
  • +
  • + + Rust Systems Programming +
  • +
  • + + Cross-platform WASM Apps +
  • +
-
    -
  • - - AI Model Integration -
  • -
  • - - Rust Systems Programming -
  • -
  • - - Cross-platform WASM Apps -
  • -
-
+ - {/* Areas to Explore */} -
-
- -

Want to Explore

+ +
+
+ +

Want to Explore

+
+
    +
  • + + LLM Fine-tuning +
  • +
  • + + Rust 2024 Edition +
  • +
  • + + Real-time Web Transport +
  • +
-
    -
  • - - LLM Fine-tuning -
  • -
  • - - Rust 2024 Edition -
  • -
  • - - Real-time Web Transport -
  • -
-
+
diff --git a/src/src/components/about/intro.tsx b/src/src/components/about/intro.tsx index 7a088eb..3eaff7f 100644 --- a/src/src/components/about/intro.tsx +++ b/src/src/components/about/intro.tsx @@ -1,56 +1,98 @@ -import React from "react"; +import React, { useEffect, useRef, useState } from "react"; import { ChevronDownIcon } from "@/components/icons"; export default function Intro() { + const [visible, setVisible] = useState(false); + const ref = useRef(null); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + const rect = el.getBoundingClientRect(); + const inView = rect.top < window.innerHeight && rect.bottom > 0; + const isReload = performance.getEntriesByType?.("navigation")?.[0]?.type === "reload"; + + if (inView && isReload) { + setVisible(true); + return; + } + if (inView) { + // Fresh navigation — animate in + requestAnimationFrame(() => setVisible(true)); + return; + } + + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setVisible(true); + observer.disconnect(); + } + }, + { threshold: 0.2 } + ); + + observer.observe(el); + return () => observer.disconnect(); + }, []); + const scrollToNext = () => { const nextSection = document.querySelector("section")?.nextElementSibling; if (nextSection) { - const offset = nextSection.offsetTop - (window.innerHeight - nextSection.offsetHeight) / 2; // Center the section - window.scrollTo({ - top: offset, - behavior: "smooth" - }); + const offset = (nextSection as HTMLElement).offsetTop - (window.innerHeight - (nextSection as HTMLElement).offsetHeight) / 2; + window.scrollTo({ top: offset, behavior: "smooth" }); } }; + const anim = (delay: number) => + ({ + opacity: visible ? 1 : 0, + transform: visible ? "translateY(0)" : "translateY(20px)", + transition: `all 0.7s ease-out ${delay}ms`, + }) as React.CSSProperties; + return ( -
+
-
+
Timothy Pidashev
-
+

Timothy Pidashev

-

+

Software Systems Engineer

-

+

Open Source Enthusiast

-

+

Coffee Connoisseur

-
+

- "Turning coffee into code" isn't just a clever phrase – + "Turning coffee into code" isn't just a clever phrase – it's how I approach each project: methodically, with attention to detail, and a refined process.

-
- -
+
+

{resumeData.name}

+
+
+

{resumeData.title}

+
+
+
+ + {resumeData.contact.email} + + + + {resumeData.contact.phone} + + + {resumeData.contact.location} +
+
+
+
+ + + GitHub + + + + LinkedIn + + +
+
{/* Summary */} -
-

Professional Summary

+

{resumeData.summary}

-
+ {/* Experience */} -
-

Experience

- {resumeData.experience.map((exp, index) => ( -
-
-
-

{exp.title}

-
{exp.company} - {exp.location}
+ +
+ {resumeData.experience.map((exp, index) => ( +
+
+
+
+

{exp.title}

+
{exp.company} - {exp.location}
+
+
{exp.period}
+
+
    + {exp.achievements.map((a, i) => ( +
  • {a}
  • + ))} +
-
{exp.period}
-
-
    - {exp.achievements.map((achievement, i) => ( -
  • {achievement}
  • - ))} -
-
- ))} -
+ + ))} +
+ {/* Contract Work */} -
-

Contract Work

- {resumeData.contractWork.map((project, index) => ( -
-
-
-
-

{project.title}

- {project.url && ( - - - + +
+ {resumeData.contractWork.map((project, index) => ( +
+
+
+
+
+

{project.title}

+ {project.url && ( + + + + )} +
+
{project.type}
+
+
Since {project.startDate}
+
+
+ {project.responsibilities && ( +
+
Responsibilities
+
    + {project.responsibilities.map((r, i) => ( +
  • {r}
  • + ))} +
+
+ )} + {project.achievements && ( +
+
Key Achievements
+
    + {project.achievements.map((a, i) => ( +
  • {a}
  • + ))} +
+
)}
-
{project.type}
-
Since {project.startDate}
-
-
- {project.responsibilities && ( -
-
Responsibilities
-
    - {project.responsibilities.map((responsibility, i) => ( -
  • {responsibility}
  • - ))} -
-
- )} - {project.achievements && ( -
-
Key Achievements
-
    - {project.achievements.map((achievement, i) => ( -
  • {achievement}
  • - ))} -
-
- )} -
-
- ))} -
+ + ))} +
+ {/* Education */} -
-

Education

- {resumeData.education.map((edu, index) => ( -
-
-
-

{edu.degree}

-
{edu.school} - {edu.location}
+ +
+ {resumeData.education.map((edu, index) => ( +
+
+
+
+

{edu.degree}

+
{edu.school} - {edu.location}
+
+
{edu.period}
+
+ {edu.achievements.length > 0 && ( +
    + {edu.achievements.map((a, i) => ( +
  • {a}
  • + ))} +
+ )}
-
{edu.period}
-
-
    - {edu.achievements.map((achievement, i) => ( -
  • {achievement}
  • - ))} -
-
- ))} -
+ + ))} +
+ {/* Skills */} -
-

Skills

-
-
-

Technical Skills

-
- {resumeData.skills.technical.map((skill, index) => ( - - {skill} - - ))} -
-
-
-

Soft Skills

-
- {resumeData.skills.soft.map((skill, index) => ( - - {skill} - - ))} -
-
-
-
- - {/* Certifications */} - {/* Temporarily Hidden -
-

Certifications

- {resumeData.certifications.map((cert, index) => ( -
-

{cert.name}

-
{cert.issuer} - {cert.date}
-
- ))} -
- */} +
); }; +// --- Skills section --- + +function SkillsSection() { + const { ref, visible } = useScrollVisible(); + const { displayed, done } = useTypewriter("Skills", visible, 20); + + return ( +
+

+ {visible ? displayed : "\u00A0"} + {visible && !done && |} +

+ +
+
+

Technical Skills

+ +
+
+

Soft Skills

+ +
+
+
+ ); +} + export default Resume; diff --git a/src/src/pages/about.astro b/src/src/pages/about.astro index a9facd0..b19c831 100644 --- a/src/src/pages/about.astro +++ b/src/src/pages/about.astro @@ -8,7 +8,7 @@ import Timeline from "@/components/about/timeline"; import CurrentFocus from "@/components/about/current-focus"; import OutsideCoding from "@/components/about/outside-coding"; --- - @@ -17,23 +17,23 @@ import OutsideCoding from "@/components/about/outside-coding"; -
- +
+
-
- +
+
- -
+ +
-
+
-
+
diff --git a/src/src/pages/api/wakatime/activity.ts b/src/src/pages/api/wakatime/activity.ts index 7e3dc90..ae3db4d 100644 --- a/src/src/pages/api/wakatime/activity.ts +++ b/src/src/pages/api/wakatime/activity.ts @@ -2,7 +2,14 @@ import type { APIRoute } from 'astro'; export const GET: APIRoute = async () => { const WAKATIME_API_KEY = import.meta.env.WAKATIME_API_KEY; - + + if (!WAKATIME_API_KEY) { + return new Response( + JSON.stringify({ error: "WAKATIME_API_KEY not configured" }), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + } + try { const response = await fetch( 'https://wakatime.com/api/v1/users/current/summaries?range=last_6_months', { diff --git a/src/src/pages/api/wakatime/alltime.ts b/src/src/pages/api/wakatime/alltime.ts index efa9cd2..a9be2bc 100644 --- a/src/src/pages/api/wakatime/alltime.ts +++ b/src/src/pages/api/wakatime/alltime.ts @@ -1,22 +1,35 @@ import type { APIRoute } from "astro"; -import { exec } from "child_process"; -import { promisify } from "util"; - -const execAsync = promisify(exec); export const GET: APIRoute = async () => { + const WAKATIME_API_KEY = import.meta.env.WAKATIME_API_KEY; + + if (!WAKATIME_API_KEY) { + return new Response( + JSON.stringify({ error: "WAKATIME_API_KEY not configured" }), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + } + try { - const { stdout } = await execAsync(`curl -H "Authorization: Basic ${import.meta.env.WAKATIME_API_KEY}" https://wakatime.com/api/v1/users/current/all_time_since_today`); - - return new Response(stdout, { - status: 200, - headers: { - "Content-Type": "application/json" + const response = await fetch( + "https://wakatime.com/api/v1/users/current/all_time_since_today", + { + headers: { + Authorization: `Basic ${Buffer.from(WAKATIME_API_KEY).toString("base64")}`, + }, } + ); + + const data = await response.json(); + return new Response(JSON.stringify(data), { + status: 200, + headers: { "Content-Type": "application/json" }, }); } catch (error) { - return new Response(JSON.stringify({ error: "Failed to fetch stats" }), { - status: 500 - }); + console.error("WakaTime alltime API error:", error); + return new Response( + JSON.stringify({ error: "Failed to fetch stats" }), + { status: 500, headers: { "Content-Type": "application/json" } } + ); } -} +}; diff --git a/src/src/pages/api/wakatime/detailed.ts b/src/src/pages/api/wakatime/detailed.ts index 4337bed..28c4248 100644 --- a/src/src/pages/api/wakatime/detailed.ts +++ b/src/src/pages/api/wakatime/detailed.ts @@ -3,7 +3,14 @@ import type { APIRoute } from 'astro'; export const GET: APIRoute = async () => { const WAKATIME_API_KEY = import.meta.env.WAKATIME_API_KEY; - + + if (!WAKATIME_API_KEY) { + return new Response( + JSON.stringify({ error: "WAKATIME_API_KEY not configured" }), + { status: 503, headers: { "Content-Type": "application/json" } } + ); + } + try { const response = await fetch( 'https://wakatime.com/api/v1/users/current/stats/last_7_days?timeout=15', { diff --git a/src/src/pages/blog/[...slug].astro b/src/src/pages/blog/[...slug].astro index a214d7d..b0e51c6 100644 --- a/src/src/pages/blog/[...slug].astro +++ b/src/src/pages/blog/[...slug].astro @@ -65,7 +65,7 @@ const jsonLd = {
{post.data.image && ( -
+
{post.data.title}
)} -

{post.data.title}

-

{post.data.description}

-
-
- {post.data.author} - - -
+

{post.data.title}

+

{post.data.description}

+
+ {post.data.author} + +
-
+
{post.data.tags.map((tag) => ( -