Homepage Change Log 04: Recently Played, Last.fm, and a Turning Record
The recently played section connects Last.fm data, album artwork, an SVG record, and an orbital track layout. It looks visual, but the first boundary is data.
The recently played section is the least formal part of the homepage, but that is why it belongs on a personal site. Technical posts, photography, and calligraphy are more archival. Music adds a small trace of everyday life.
Instead of maintaining a manual playlist, the section reads recent tracks from Last.fm. It changes on its own.
Data Comes Before the Visual
The Last.fm response is not perfectly regular. Some tracks do not have artwork. Some artwork URLs are placeholders. Some tracks are currently playing. Others are only historical plays.
So the first step was a small parser:
export interface LastfmTrack {
id: string;
title: string;
artist: string;
album?: string;
imageUrl?: string;
url?: string;
playedAt?: string;
nowPlaying: boolean;
}The page consumes LastfmTrack, not the raw Last.fm response. That gives the UI a small set of clear states:
- Show album artwork when it exists.
- Show a music-note fallback when it does not.
- Highlight the currently playing track.
- Show a concise message if Last.fm is unconfigured or unavailable.
Why I Removed Playback Times
An early version displayed the exact time for each play. It quickly felt like a log. This section is closer to a small display of what I have been listening to lately, not an audit trail.
The current version keeps the “Now playing” state, but the other tracks show only number, artwork, title, and artist.
The Record Is SVG, Not an Image
I considered using a record image, but that would make a few things harder:
- Matching light and dark themes.
- Adjusting size, shadow, and contrast with the page.
- Making rotation feel natural.
The record is drawn by TurntableDisplay with SVG and CSS. It is not a photorealistic object. It is an abstract record: rings, grooves, a center label, and slow rotation.
The shape is simple:
<svg viewBox="0 0 420 420">
<circle cx="210" cy="210" r="190" />
<circle cx="210" cy="210" r="72" />
{/* many subtle record grooves */}
</svg>The slow rotation gives the section a sense of playback without becoming the main content.
Orbiting the Track List
A vertical list looked too much like a table. The final layout places ten tracks around the record, five on each side.
The positions come from polar coordinates:
function polarPosition(angle: number, radius: number) {
const radians = (angle * Math.PI) / 180;
return {
left: `calc(50% + ${Math.cos(radians) * radius}rem)`,
top: `calc(50% - ${Math.sin(radians) * radius}rem)`,
};
}The number badges and track information are positioned around the same center as the record. That shared center is what makes the layout feel connected instead of stacked.
Handling Long Titles
Track titles and artist names are unpredictable. Wrapping everything would break the layout. Truncating everything would hide useful information. The current approach keeps a stable two-line structure, then lets long text scroll horizontally on hover or focus.
This is a small detail, but it matters for real data. Last.fm will not shorten titles for the layout, so the layout needs to handle uneven content.
Takeaway
The recently played section looks like a visual feature, but it starts as a data feature. Once the Last.fm response is normalized, the UI can decide how to draw the record, place the tracks, handle missing artwork, and keep long text readable.
A personal site can have some playfulness, but it works best when the playfulness comes from real content. Recently played does that. It changes as life changes.

