diff --git a/src/components/MusicPlayer.astro b/src/components/MusicPlayer.astro index 0dcf0f1..b1d8b8a 100644 --- a/src/components/MusicPlayer.astro +++ b/src/components/MusicPlayer.astro @@ -346,9 +346,32 @@ const hero = lb.nowPlaying ?? lb.recentTracks[0] ?? null; function coverUrl(listen: any): string { const mbids = listen.track_metadata?.mbid_mapping ?? {}; - return mbids.release_mbid - ? `https://coverartarchive.org/release/${mbids.release_mbid}/front-250` - : ""; + const info = listen.track_metadata?.additional_info ?? {}; + const rid = mbids.release_mbid ?? mbids.caa_release_mbid ?? info.release_mbid; + if (rid) return `https://coverartarchive.org/release/${rid}/front-250`; + const rgid = mbids.release_group_mbid; + if (rgid) return `https://coverartarchive.org/release-group/${rgid}/front-250`; + return ""; + } + + async function fetchItunesArt(track: string, artist: string): Promise { + try { + const q = encodeURIComponent(`${track} ${artist}`); + const res = await fetch(`https://itunes.apple.com/search?term=${q}&media=music&limit=1`); + if (!res.ok) return ""; + const data = await res.json(); + const url = data.results?.[0]?.artworkUrl100; + return url ? url.replace("100x100", "250x250") : ""; + } catch { return ""; } + } + + function attachArtFallback(img: HTMLImageElement, track: string, artist: string) { + img.addEventListener("error", async () => { + if (img.dataset.fallbackTried) { img.style.display = "none"; return; } + img.dataset.fallbackTried = "1"; + const alt = await fetchItunesArt(track, artist); + if (alt) { img.src = alt; } else { img.style.display = "none"; } + }, { once: false }); } function esc(s: string): string { @@ -371,7 +394,7 @@ const hero = lb.nowPlaying ?? lb.recentTracks[0] ?? null; return `
- ${cover ? `` : ""} + ${cover ? `` : ``}
@@ -390,9 +413,7 @@ const hero = lb.nowPlaying ?? lb.recentTracks[0] ?? null; const meta = l.track_metadata ?? {}; const cover = coverUrl(l); const ago = l.listened_at ? timeAgo(l.listened_at) : ""; - const thumb = cover - ? `` - : ""; + const thumb = ``; return `
  • ${thumb}
    @@ -435,6 +456,18 @@ const hero = lb.nowPlaying ?? lb.recentTracks[0] ?? null; body.innerHTML = renderHero(hero, isLive) + `
      ${renderRecent(listens)}
    `; + + body.querySelectorAll("img[data-track]").forEach((img) => { + const t = img.dataset.track ?? ""; + const a = img.dataset.artist ?? ""; + if (img.dataset.noCover === "1") { + fetchItunesArt(t, a).then((url) => { + if (url) { img.src = url; img.style.display = ""; } + }); + } else { + attachArtFallback(img, t, a); + } + }); } catch { // keep existing content on error } diff --git a/src/components/Nav.astro b/src/components/Nav.astro index 7bc836c..11ea631 100644 --- a/src/components/Nav.astro +++ b/src/components/Nav.astro @@ -6,7 +6,6 @@ const navLinks: { href: string; label: string; external?: boolean }[] = [ { href: "/events", label: "Events" }, { href: "/meeting", label: "Meet" }, { href: "https://todo.avinal.space/explore", label: "Memos", external: true }, - { href: "/setup", label: "Setup" }, ]; const currentPath = Astro.url.pathname; diff --git a/src/data/resume.json b/src/data/resume.json index 7b553f2..4c17df0 100644 --- a/src/data/resume.json +++ b/src/data/resume.json @@ -38,9 +38,15 @@ "summary": "Builds for OpenShift & Developer Tools", "location": "Bengaluru, India", "highlights": [ - "Serving as Team Lead for Builds for OpenShift — managing engineers, driving sprint planning, code reviews, and cross-functional collaboration", - "Working on Builds for OpenShift based on the upstream Shipwright project — designing, maintaining, and releasing build features", - "Maintainer of Tekton Results — contributing upstream to enhance observability and results storage for Tekton pipelines" + "Leading the Builds for OpenShift team — managing a group of engineers, owning sprint planning, backlog grooming, and cross-functional alignment with product and QE", + "Architecting and shipping features for Builds for OpenShift (based on the upstream Shipwright project) — from design proposals through implementation, testing, and GA releases", + "Driving Tekton Results upstream as a maintainer — improving observability, long-term storage, and query performance for Tekton pipeline results at scale", + "Representing the team in OpenShift-wide architecture discussions, coordinating with platform, CLI, and console teams on build-related integrations", + "Mentoring junior engineers through 1:1s, design reviews, and pair programming sessions to grow team capability" + ], + "links": [ + { "label": "Shipwright", "url": "https://shipwright.io" }, + { "label": "Tekton Results", "url": "https://github.com/tektoncd/results" } ] }, { @@ -52,9 +58,14 @@ "summary": "Hybrid Cloud Engineering — Pipeline Service", "location": "Bengaluru, India", "highlights": [ - "Worked on Pipeline Service — a SaaS for pipelines leveraging kcp, Kubernetes/OpenShift, Tekton, and Argo CD", - "Contributed to Tekton Results for long-term, efficient storage of PipelineRuns and TaskRuns", - "Maintained Source-to-Image (S2I) and Shared Resource CSI Driver components in OpenShift" + "Built and maintained Pipeline Service — a multi-tenant SaaS pipeline platform leveraging kcp, Kubernetes/OpenShift, Tekton, and Argo CD for managed CI/CD", + "Contributed to Tekton Results for efficient long-term storage of PipelineRuns and TaskRuns, improving query latency and reliability for large-scale clusters", + "Maintained Source-to-Image (S2I) and Shared Resource CSI Driver components in OpenShift, triaging bugs and shipping quarterly releases", + "Participated in on-call rotations, resolving production incidents across the pipeline stack and authoring runbooks for recurring issues", + "Collaborated with upstream Tekton community on design proposals and contributed patches accepted into the core project" + ], + "links": [ + { "label": "Tekton", "url": "https://tekton.dev" } ] }, { @@ -66,8 +77,9 @@ "summary": "Pipeline Service Team", "location": "Bengaluru, India", "highlights": [ - "Designed and implemented the Minimal Tekton Server with unit tests — creates Tekton resources on Kubernetes/OpenShift clusters using the Tekton API", - "Built MKS Server, MKS CLI, and MKS Dashboard using Golang, Kubernetes, Tekton, and Redis" + "Designed and implemented the Minimal Tekton Server (MKS) from scratch — a lightweight server that creates and manages Tekton resources on Kubernetes/OpenShift clusters", + "Built the MKS CLI and MKS Dashboard in Golang, providing a developer-friendly interface for pipeline operations backed by Redis for state management", + "Wrote comprehensive unit and integration tests achieving high coverage, following test-driven development practices across the full stack" ] }, { @@ -79,8 +91,11 @@ "summary": "Developer Documentation", "location": "Shenzhen, Remote", "highlights": [ - "Redesigned developer and user documentation for the Apache APISIX project", - "Created Katacoda tutorials for APISIX, collaborating with the community and integrating feedback" + "Redesigned the developer and user documentation for Apache APISIX, improving information architecture and discoverability for a fast-growing API gateway project", + "Created interactive Katacoda tutorials that guided users through real APISIX deployments, collaborating closely with the community and incorporating feedback from maintainers" + ], + "links": [ + { "label": "Apache APISIX", "url": "https://apisix.apache.org" } ] }, { @@ -92,9 +107,13 @@ "summary": "Build System & CI/CD Modernization", "location": "Remote", "highlights": [ - "Upgraded the build system from Unix Makefile to CMake — build time reduced to 5–7 minutes (2× faster)", - "Migrated CI/CD from Travis CI to GitHub Actions — CI time reduced from 1–2 hours to 20–25 minutes", - "Refactored and fixed years-old unit and functional testing code in C/C++ and PHP" + "Migrated the entire build system from legacy Unix Makefiles to modern CMake — cutting build time from 10–15 minutes down to 5–7 minutes (2× improvement)", + "Replaced Travis CI with GitHub Actions for the CI/CD pipeline — reducing CI run time from 1–2 hours to 20–25 minutes with parallel job execution and caching", + "Refactored and fixed years-old unit and functional testing code across C/C++ and PHP, restoring test suites that had been broken or skipped for multiple releases" + ], + "links": [ + { "label": "GSoC Project", "url": "https://summerofcode.withgoogle.com" }, + { "label": "Blog Series", "url": "https://avinal.space/posts/gsoc/gsoc-fossology" } ] }, { @@ -106,8 +125,8 @@ "summary": "Inventory & Billing Management", "location": "Bengaluru, Remote", "highlights": [ - "Designed and developed an Inventory and Billing Management App using Spring Boot and PostgreSQL", - "Created REST API endpoints according to functional requirements" + "Designed and developed a full-stack Inventory and Billing Management application using Spring Boot, PostgreSQL, and Thymeleaf for a small-business client", + "Implemented RESTful API endpoints for CRUD operations on inventory items, invoices, and customer records according to functional specifications" ] }, { @@ -119,8 +138,12 @@ "summary": "VLC for Android Documentation", "location": "Paris, Remote", "highlights": [ - "Documented VLC for Android using Sphinx, reStructuredText, Markdown, and shell scripting", - "Delivered user-friendly documentation with supporting screenshots and step-by-step tutorials" + "Authored comprehensive documentation for VLC for Android using Sphinx and reStructuredText, covering build instructions, architecture overview, and contributor guidelines", + "Produced step-by-step tutorials with annotated screenshots, making the project accessible to new contributors and end users alike" + ], + "links": [ + { "label": "GSoD Case Study", "url": "https://developers.google.com/season-of-docs" }, + { "label": "VideoLAN", "url": "https://www.videolan.org" } ] }, { @@ -131,18 +154,26 @@ "summary": "Mentoring & Open Source", "location": "Remote", "highlights": [ - "Mentoring GSoC students since 2022, guiding open source license compliance tooling projects" + "Mentoring Google Summer of Code students since 2022 — guiding contributors on open-source license compliance tooling, code review practices, and upstream collaboration", + "Helping students navigate the FOSSology codebase, define project milestones, and deliver production-quality patches accepted by the maintainer community" + ], + "links": [ + { "label": "FOSSology", "url": "https://www.fossology.org" } ] }, { "name": "GNU C Library", "position": "Contributor", - "url": "https://www.gnu.org/software/libc/", + "url": "https://sourceware.org/glibc/", "startDate": "2024-05-01", "summary": "GNU Project", "location": "Remote", "highlights": [ - "Submitting patches to the GNU C Library (glibc)" + "Contributing patches to glibc — one of the most critical pieces of the GNU/Linux ecosystem, used by virtually every Linux distribution", + "Working on bug fixes and improvements submitted via the Sourceware mailing list and reviewed by core glibc maintainers" + ], + "links": [ + { "label": "Patches", "url": "https://sourceware.org/cgit/glibc/log/?qt=author&q=avinal" } ] } ], diff --git a/src/lib/listenbrainz.ts b/src/lib/listenbrainz.ts index 5476e69..afcd992 100644 --- a/src/lib/listenbrainz.ts +++ b/src/lib/listenbrainz.ts @@ -24,10 +24,13 @@ function parseTrack(listen: any): LBTrack { const info = meta.additional_info ?? {}; const mbids = meta.mbid_mapping ?? {}; - const releaseMbid = mbids.release_mbid ?? info.release_mbid; - const coverArtUrl = releaseMbid - ? `https://coverartarchive.org/release/${releaseMbid}/front-250` - : undefined; + const releaseMbid = mbids.release_mbid ?? mbids.caa_release_mbid ?? info.release_mbid; + let coverArtUrl: string | undefined; + if (releaseMbid) { + coverArtUrl = `https://coverartarchive.org/release/${releaseMbid}/front-250`; + } else if (mbids.release_group_mbid) { + coverArtUrl = `https://coverartarchive.org/release-group/${mbids.release_group_mbid}/front-250`; + } const listenUrl = info.origin_url ?? diff --git a/src/pages/resume.astro b/src/pages/resume.astro index 866695e..e1e6155 100644 --- a/src/pages/resume.astro +++ b/src/pages/resume.astro @@ -9,12 +9,15 @@ function fmtDate(iso: string) { return d.toLocaleDateString("en-US", { month: "short", year: "numeric" }); } +type ExtLink = { label: string; url: string }; + type Role = { title: string; startDate: string; endDate?: string; summary?: string; highlights?: string[]; + links?: ExtLink[]; }; type TimelineEntry = { @@ -27,6 +30,7 @@ type TimelineEntry = { endDate?: string; summary?: string; highlights?: string[]; + links?: ExtLink[]; roles?: Role[]; }; @@ -41,6 +45,7 @@ type FlatEntry = { endDate?: string; summary?: string; highlights?: string[]; + links?: ExtLink[]; }; /** @@ -73,9 +78,9 @@ function groupToTimeline(entries: FlatEntry[]): TimelineEntry[] { endDate: latest.endDate, summary: latest.summary, highlights: latest.highlights, + links: latest.links, }); } else { - const earliest = sorted[sorted.length - 1]; result.push({ type: latest.type, title: latest.subtitle, @@ -90,6 +95,7 @@ function groupToTimeline(entries: FlatEntry[]): TimelineEntry[] { endDate: e.endDate, summary: e.summary, highlights: e.highlights, + links: e.links, })), }); } @@ -108,6 +114,7 @@ const workFlat: FlatEntry[] = work.map((w: any) => ({ endDate: w.endDate, summary: w.summary, highlights: w.highlights, + links: w.links, })); const volunteerFlat: FlatEntry[] = volunteer.map((v: any) => ({ @@ -216,6 +223,16 @@ const typeLabels: Record = { {role.highlights.map((h) =>
  • {h}
  • )} )} + {role.links && role.links.length > 0 && ( + + )}
    ))}
    @@ -239,6 +256,16 @@ const typeLabels: Record = { {entry.highlights.map((h) =>
  • {h}
  • )} )} + {entry.links && entry.links.length > 0 && ( + + )} )} @@ -575,6 +602,38 @@ const typeLabels: Record = { font-weight: 700; } + /* ---- External links ---- */ + .tl-links { + display: flex; + flex-wrap: wrap; + gap: var(--space-2); + margin-top: var(--space-3); + } + + .tl-ext-link { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: var(--text-xs); + font-weight: 500; + color: var(--accent); + text-decoration: none; + padding: 2px var(--space-2); + border: 1px solid color-mix(in srgb, var(--accent) 30%, transparent); + border-radius: var(--radius-full); + transition: all var(--duration-fast) var(--ease-out); + } + + .tl-ext-link:hover { + background: var(--accent-subtle); + border-color: var(--accent); + } + + .tl-ext-link svg { + flex-shrink: 0; + opacity: 0.7; + } + /* ---- Grouped roles ---- */ .tl-org-title { font-size: var(--text-lg); diff --git a/src/pages/setup.astro b/src/pages/setup.astro deleted file mode 100644 index 8ca0cb3..0000000 --- a/src/pages/setup.astro +++ /dev/null @@ -1,308 +0,0 @@ ---- -import BaseLayout from "@/layouts/BaseLayout.astro"; - -const hardware = [ - { - category: "Servers", - items: [ - { name: "Raspberry Pi 5 8GB", url: "https://www.raspberrypi.com/products/raspberry-pi-5/", detail: "Primary server — runs most self-hosted services" }, - { name: "Raspberry Pi 4B 8GB", url: "https://www.raspberrypi.org/products/raspberry-pi-4-model-b/", detail: "Secondary server — backup and lighter workloads" }, - ], - }, - { - category: "Storage", - items: [ - { name: "Samsung SSD 970 EVO Plus 500GB", url: "https://www.samsung.com/us/computing/memory-storage/solid-state-drives/ssd-970-evo-plus-nvme-m-2-500gb-mz-v7s500b-am/", detail: "NVMe on the Pi 5 via Pimoroni NVMe Base" }, - { name: "WD Blue SA510 SATA SSD M.2 500GB", url: "https://www.westerndigital.com/en-in/products/internal-drives/wd-blue-sa510-sata-m-2-ssd", detail: "Connected via USB enclosure to the Pi 4B" }, - ], - }, - { - category: "Accessories", - items: [ - { name: "Raspberry Pi 27W USB-C PSU", url: "https://www.raspberrypi.com/products/27w-power-supply/", detail: "For the Pi 5" }, - { name: "Raspberry Pi 15W USB-C PSU", url: "https://www.raspberrypi.com/products/type-c-power-supply/", detail: "For the Pi 4B" }, - { name: "Pimoroni NVMe Base", url: "https://shop.pimoroni.com/products/nvme-base", detail: "NVMe SSD HAT for Pi 5" }, - { name: "PiBOX NVMe SSD Enclosure", url: "https://pibox.in/", detail: "USB 3.2 10Gbps enclosure" }, - ], - }, -]; - -const selfHosted = [ - { name: "Immich", url: "https://immich.app/", desc: "Photo and video backup — Google Photos replacement", category: "Media" }, - { name: "Paisa", url: "https://paisa.fyi/", desc: "Personal finance and budget manager using plain text accounting", category: "Finance" }, - { name: "Vikunja", url: "https://vikunja.io/", desc: "Todo lists and kanban boards with CalDAV support", category: "Productivity" }, - { name: "Atuin", url: "https://atuin.sh/", desc: "Encrypted shell history sync across all machines", category: "Dev Tools" }, - { name: "Gitea", url: "https://about.gitea.com/", desc: "Self-hosted Git service — personal project mirror", category: "Dev Tools" }, - { name: "Paperless-ngx", url: "https://docs.paperless-ngx.com/", desc: "Document management with built-in OCR", category: "Documents" }, - { name: "Shiori", url: "https://github.com/go-shiori/shiori", desc: "Simple bookmark manager — Pocket alternative", category: "Bookmarks" }, - { name: "Memos", url: "https://usememos.com/", desc: "Lightweight note-taking — quick thoughts and snippets", category: "Notes" }, -]; - -const networking = [ - { name: "Tailscale", url: "https://tailscale.com/", desc: "Mesh VPN connecting all devices securely" }, - { name: "RunTipi", url: "https://runtipi.io/", desc: "Docker Compose app manager for server administration" }, -]; - -const softwareStack = [ - { category: "Editor", items: ["Neovim", "VS Code / Cursor"] }, - { category: "Terminal", items: ["Kitty", "Zsh", "Starship prompt", "Tmux"] }, - { category: "OS", items: ["Fedora (daily driver)", "Raspberry Pi OS (servers)"] }, - { category: "Browser", items: ["Firefox"] }, -]; ---- - - -
    -
    -

    Setup

    -

    - The hardware, software, and self-hosted services that power my workflow. - Heavily inspired by the homelab and self-hosting community. -

    -
    - -
    -

    Hardware

    - {hardware.map((group) => ( -
    -

    {group.category}

    -
    - {group.items.map((item) => ( -
    -

    {item.name}

    -

    {item.detail}

    -
    - ))} -
    -
    - ))} -
    - -
    -

    Self-Hosted Services

    -

    - All services run on the Raspberry Pis, connected via Tailscale VPN. - Most are deployed using Docker Compose via RunTipi. -

    -
    - {selfHosted.map((svc) => ( -
    - {svc.category} -

    {svc.name}

    -

    {svc.desc}

    -
    - ))} -
    -
    - -
    -

    Networking

    -
    - {networking.map((svc) => ( -
    -

    {svc.name}

    -

    {svc.desc}

    -
    - ))} -
    -
    - -
    -

    Software Stack

    -
    - {softwareStack.map((group) => ( -
    -

    {group.category}

    -
      - {group.items.map((item) =>
    • {item}
    • )} -
    -
    - ))} -
    -
    - - -
    -
    - -