spotify and about me

This commit is contained in:
2025-05-24 12:04:50 +00:00
parent 0c89218dfb
commit caf95d8505
6 changed files with 183 additions and 11469 deletions

11463
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@
"radix-ui": "^1.3.4",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-icons": "^5.5.0",
"rehype-document": "^7.0.3",
"rehype-external-links": "^3.0.0",
"rehype-katex": "^7.0.1",

View File

@@ -0,0 +1,153 @@
// code based on https://github.com/jktrn/enscribe.dev/blob/main/src/components/bento/SpotifyPresence.tsx
// which is under copyright.
import { useEffect, useState } from "react"
import { Skeleton } from "./ui/skeleton"
import { FaSpotify } from 'react-icons/fa'
import { MoveUpRight } from "lucide-react"
interface Track {
name: string
artist: { '#text': string }
album: { '#text': string }
image: { '#text': string }[]
url: string
'@attr'?: { nowplaying: string }
}
export default function BentoSpotify() {
const [displayData, setDisplayData] = useState<Track | null>(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
fetch('https://lastfm-last-played.biancarosa.com.br/enscribe/latest-song')
.then((response) => response.json())
.then((data) => {
setDisplayData(data.track)
setIsLoading(false)
})
.catch((error) => {
console.error('Error fetching latest song:', error)
setIsLoading(false)
})
}, [])
if (isLoading) {
return (
<div className="relative h-full w-full overflow-hidden rounded-lg bg-gradient-to-br from-green-500/10 to-green-600/5">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="h-full w-full bg-[radial-gradient(circle_at_20%_80%,_theme(colors.green.500)_0%,_transparent_50%)]"></div>
</div>
{/* Spotify Logo */}
<div className="absolute right-3 top-3 z-10">
<FaSpotify size={24} className="text-green-500" />
</div>
<div className="relative z-10 flex h-full flex-col p-4">
{/* Header Skeleton */}
<div className="mb-4 flex items-center gap-2">
<Skeleton className="h-2 w-2 rounded-full" />
<Skeleton className="h-3 w-20" />
</div>
{/* Album Art & Info Skeleton */}
<div className="flex flex-1 gap-3">
<Skeleton className="h-16 w-16 rounded-lg flex-shrink-0" />
<div className="flex min-w-0 flex-1 flex-col justify-center gap-2">
<Skeleton className="h-4 w-3/4" />
<Skeleton className="h-3 w-1/2" />
<Skeleton className="h-3 w-2/3" />
</div>
</div>
{/* Footer Skeleton */}
<div className="mt-4 flex items-center justify-between">
<Skeleton className="h-1 w-8 rounded-full" />
<Skeleton className="h-8 w-8 rounded-full" />
</div>
</div>
</div>
)
}
if (!displayData) return <p>Something absolutely horrible has gone wrong</p>
const { name: song, artist, album, image, url } = displayData
return (
<div className="relative h-full w-full overflow-hidden rounded-lg bg-gradient-to-br from-green-500/10 to-green-600/5">
{/* Background Pattern */}
<div className="absolute inset-0 opacity-5">
<div className="h-full w-full bg-[radial-gradient(circle_at_20%_80%,_theme(colors.green.500)_0%,_transparent_50%)]"></div>
</div>
{/* Spotify Logo */}
<div className="absolute right-3 top-3 z-10">
<FaSpotify size={24} className="text-green-500" />
</div>
<div className="relative z-10 flex h-full flex-col p-4">
{/* Header */}
<div className="mb-4 flex items-center gap-2">
<div className="flex h-2 w-2 rounded-full bg-green-500 animate-pulse"></div>
<span className="text-xs font-medium text-muted-foreground">
{displayData['@attr']?.nowplaying === 'true'
? 'NOW PLAYING'
: 'LAST PLAYED'}
</span>
</div>
{/* Album Art & Info */}
<div className="flex flex-1 gap-3">
<div className="relative flex-shrink-0">
<img
src={image[3]['#text']}
alt="Album art"
width={64}
height={64}
className="h-16 w-16 rounded-lg border shadow-lg"
/>
{displayData['@attr']?.nowplaying === 'true' && (
<div className="absolute -bottom-1 -right-1 h-4 w-4 rounded-full bg-green-500 border-2 border-background"></div>
)}
</div>
<div className="flex min-w-0 flex-1 flex-col justify-center">
<h3 className="mb-1 truncate text-sm font-bold leading-tight">
{song}
</h3>
<p className="truncate text-xs text-muted-foreground">
{artist['#text']}
</p>
<p className="truncate text-xs text-muted-foreground/70">
{album['#text']}
</p>
</div>
</div>
{/* Footer */}
<div className="mt-4 flex items-center justify-between">
<div className="flex items-center gap-1">
<div className="h-1 w-8 rounded-full bg-muted">
<div className="h-full rounded-full bg-green-500"></div>
</div>
</div>
<a
href={url}
aria-label="View on last.fm"
title="View on last.fm"
target="_blank"
rel="noopener noreferrer"
className="flex h-8 w-8 items-center justify-center rounded-full bg-secondary/80 text-muted-foreground transition-all duration-200 hover:bg-secondary hover:text-foreground hover:scale-110"
>
<MoveUpRight size={14} />
</a>
</div>
</div>
</div>
)
}

View File

@@ -0,0 +1,13 @@
import { cn } from "@/lib/utils"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="skeleton"
className={cn("bg-accent animate-pulse rounded-md", className)}
{...props}
/>
)
}
export { Skeleton }

View File

@@ -1,26 +1,31 @@
---
import PageHead from '@/components/PageHead.astro'
import Layout from '@/layouts/Layout.astro'
import Link from '@/components/Link.astro'
import BentoSpotify from '@/components/BentoSpotify'
---
<Layout>
<PageHead slot="head" title="Home" />
<section class="p-4">
<!-- Mobile: Single column stack, Tablet: 2 columns, Desktop: 3 columns -->
<div
class="grid w-full grid-cols-1 grid-rows-6 gap-4 md:aspect-[3/2] md:grid-cols-2 md:grid-rows-4 lg:aspect-[4/3] lg:grid-cols-3 lg:grid-rows-3"
>
<!-- Profile Image -->
<div class="bento-item rounded-lg border p-2 md:col-span-2 lg:col-span-2">
<div class="bento-item rounded-lg border p-2 md:col-span-2 lg:col-span-2 flex flex-col items-center justify-center relative *:text-center">
<img src="https://res.cloudinary.com/mainwebsite/image/upload/v1703593035/bg/Playa_de_Santa_Marina_ae389j.jpg" class="absolute inset-0 object-cover w-full h-full rounded-lg opacity-20" />
<img
src="https://github.com/SrIzan10.png"
class="h-32 w-full rounded object-cover md:h-40"
class="rounded-full w-24 h-24 mb-2 relative z-10"
/>
<p>hi im ethan</p>
<p class="relative z-10">Hi! I'm Izan, a student from Málaga, Spain</p>
<p class="relative z-10">I mainly do open source work at <Link href="https://sern.dev" external underline>sern</Link> and <Link href="https://hackclub.com" external underline>Hack Club</Link>.</p>
<p class="relative z-10">In my free time, I enjoy coding, reading and learning languages.</p>
</div>
<!-- Spotify -->
<div class="bento-item rounded-lg border p-2">spotify</div>
<div class="bento-item rounded-lg border">
<BentoSpotify client:load />
</div>
<!-- Filler -->
<div class="bento-item rounded-lg border p-2 md:row-span-2 lg:row-span-2">

View File

@@ -5238,6 +5238,11 @@ react-dom@19.0.0:
dependencies:
scheduler "^0.25.0"
react-icons@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.5.0.tgz#8aa25d3543ff84231685d3331164c00299cdfaf2"
integrity sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==
react-refresh@^0.17.0:
version "0.17.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.17.0.tgz#b7e579c3657f23d04eccbe4ad2e58a8ed51e7e53"