diff --git a/.env.example b/.env.example
index f2d7c34b..3e24d19e 100644
--- a/.env.example
+++ b/.env.example
@@ -1,5 +1,9 @@
UMAMI_WEBSITE_ID=
HOST=
-LASTFM_API_KEY=
GITHUB_TOKEN=
+LASTFM_API_KEY=
+
+SPOTIFY_CLIENT_ID=
+SPOTIFY_CLIENT_SECRET=
+SPOTIFY_REFRESH_TOKEN=
diff --git a/next.config.js b/next.config.js
index b664b576..4f61bac4 100644
--- a/next.config.js
+++ b/next.config.js
@@ -9,7 +9,8 @@ const nextConfig = {
'i.imgur.com',
'mateusf.com',
'lastfm.freetls.fastly.net',
- 'contribution.catsjuice.com'
+ 'contribution.catsjuice.com',
+ 'i.scdn.co'
]
},
async rewrites() {
diff --git a/package.json b/package.json
index 963e2e1a..fd5bd740 100644
--- a/package.json
+++ b/package.json
@@ -86,6 +86,7 @@
"simple-git": "^3.18.0",
"tailwind-scrollbar": "^3.1.0",
"tailwindcss": "^3.3.3",
+ "tailwindcss-animate": "^1.0.7",
"tsx": "^4.0.0",
"typescript": "5.3.3",
"velite": "0.1.0-beta.13",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 32b01cbb..e02615e6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -199,6 +199,9 @@ devDependencies:
tailwindcss:
specifier: ^3.3.3
version: 3.4.3
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.3)
tsx:
specifier: ^4.0.0
version: 4.7.2
@@ -7423,6 +7426,14 @@ packages:
tailwindcss: 3.4.3
dev: true
+ /tailwindcss-animate@1.0.7(tailwindcss@3.4.3):
+ resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==}
+ peerDependencies:
+ tailwindcss: '>=3.0.0 || insiders'
+ dependencies:
+ tailwindcss: 3.4.3
+ dev: true
+
/tailwindcss-animated@1.0.1(tailwindcss@3.4.3):
resolution: {integrity: sha512-u5wusj89ZwP8I+s8WZlaAd7aZTWBN/XEG6QgMKpkIKmAf3xP1A6WYf7oYIKmGaB10UAQaSqWopi/i1ozzZEs8Q==}
peerDependencies:
diff --git a/src/app/_components/grid/cards/cat.jpg b/src/app/_components/grid/cards/cat.jpg
new file mode 100644
index 00000000..4356c41a
Binary files /dev/null and b/src/app/_components/grid/cards/cat.jpg differ
diff --git a/src/app/_components/grid/cards/discord-status.tsx b/src/app/_components/grid/cards/discord-status.tsx
new file mode 100644
index 00000000..baac75ca
--- /dev/null
+++ b/src/app/_components/grid/cards/discord-status.tsx
@@ -0,0 +1,67 @@
+import { DiscordLogo } from '@phosphor-icons/react/dist/ssr'
+
+enum status {
+ online,
+ idle,
+ dnd
+ // offline,
+}
+
+export type LanyardResponse = {
+ data: {
+ discord_user: {
+ id: string
+ username: string
+ discriminator: string
+ avatar: string
+ }
+ discord_status: status
+ active_on_discord_web: boolean
+ active_on_discord_desktop: boolean
+ active_on_discord_mobile: boolean
+ listening_to_spotify: boolean
+ activities: {
+ id: string
+ name: string
+ type: number
+ state: string
+ timestamps: {
+ end: number
+ }
+ emoji: {
+ name: string
+ }
+ created_at: number
+ }[]
+ success: boolean
+ }
+}
+
+export async function DiscordStatus() {
+ const { data }: LanyardResponse = await fetch(
+ 'https://api.lanyard.rest/v1/users/274521154230812672',
+ {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'cache-control': 'public, s-maxage=60, stale-while-revalidate=30'
+ }
+ }
+ ).then(data => data.json())
+
+ return (
+
+
+ {/*
*/}
+
+
+
+ {data.discord_status}
+
(@mateusfg7)
+
+
+
+ )
+}
diff --git a/src/app/_components/grid/cards/github-link.tsx b/src/app/_components/grid/cards/github-link.tsx
new file mode 100644
index 00000000..bd0bc7c5
--- /dev/null
+++ b/src/app/_components/grid/cards/github-link.tsx
@@ -0,0 +1,40 @@
+// https://github.com/arnvgh/www/blob/main/components/misc/(home)/cards/gh-link.tsx
+
+import Image from 'next/image'
+import { GithubLogo } from '@phosphor-icons/react/dist/ssr'
+
+import catImg from './cat.jpg'
+
+export const GithubLink = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ GitHub
+ My experiments (aka projects)
+
+
+ )
+}
diff --git a/src/app/_components/grid/cards/github-stats.tsx b/src/app/_components/grid/cards/github-stats.tsx
new file mode 100644
index 00000000..ae37097b
--- /dev/null
+++ b/src/app/_components/grid/cards/github-stats.tsx
@@ -0,0 +1,62 @@
+import { getGithubRepositories, getGithubUserData } from '~/lib/github'
+
+export const GithubStats = async () => {
+ const { followers, public_repos } = await getGithubUserData()
+ const repositories = await getGithubRepositories()
+ const stars = repositories.reduce(
+ (acc, repo) => acc + repo.stargazers_count,
+ 0
+ )
+ return (
+
+ )
+}
+
+const GitHubStatsData = ({
+ label,
+ value
+}: {
+ label: React.ReactNode
+ value: number
+}) => {
+ return (
+
+
+ {label}:
+
+ {value}
+
+ )
+}
+
+const BackgroundPattern = () => {
+ let seed = 1
+ function seededRandom() {
+ const x = Math.sin(seed++) * 10000
+ return x - Math.floor(x)
+ }
+ const colours = ['#39d353', '#0e4429', '#0e4429', '#006d32', '#161b22']
+ const days = new Array(62)
+ .fill(null)
+ .map(_ => colours[Math.floor(seededRandom() * colours.length)])
+ return (
+
+ {days.map((c, i) => (
+
+ ))}
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/bash.tsx b/src/app/_components/grid/cards/icons/bash.tsx
new file mode 100644
index 00000000..b86a24d7
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/bash.tsx
@@ -0,0 +1,17 @@
+import { SVGProps } from 'react'
+
+export function BashIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/cloudflare.tsx b/src/app/_components/grid/cards/icons/cloudflare.tsx
new file mode 100644
index 00000000..9cd61a00
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/cloudflare.tsx
@@ -0,0 +1,20 @@
+import { SVGProps } from 'react'
+
+export function CloudflareIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/css.tsx b/src/app/_components/grid/cards/icons/css.tsx
new file mode 100644
index 00000000..d6734fa9
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/css.tsx
@@ -0,0 +1,32 @@
+import { SVGProps } from 'react'
+
+export function CSS3Icon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/docker.tsx b/src/app/_components/grid/cards/icons/docker.tsx
new file mode 100644
index 00000000..5820ef50
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/docker.tsx
@@ -0,0 +1,12 @@
+import React from 'react'
+
+export function DockerIcon(props: React.SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/expo.tsx b/src/app/_components/grid/cards/icons/expo.tsx
new file mode 100644
index 00000000..e540f456
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/expo.tsx
@@ -0,0 +1,16 @@
+import { SVGProps } from 'react'
+
+export function ExpoIcon({ className, ...props }: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/figma.tsx b/src/app/_components/grid/cards/icons/figma.tsx
new file mode 100644
index 00000000..f05f278d
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/figma.tsx
@@ -0,0 +1,28 @@
+import { SVGProps } from 'react'
+
+export function FigmaIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/google-cloud.tsx b/src/app/_components/grid/cards/icons/google-cloud.tsx
new file mode 100644
index 00000000..3d11969e
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/google-cloud.tsx
@@ -0,0 +1,24 @@
+import { SVGProps } from 'react'
+
+export function GoogleCloudIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/heroku.tsx b/src/app/_components/grid/cards/icons/heroku.tsx
new file mode 100644
index 00000000..54f736fc
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/heroku.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function HerokuIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/html.tsx b/src/app/_components/grid/cards/icons/html.tsx
new file mode 100644
index 00000000..23601c1d
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/html.tsx
@@ -0,0 +1,21 @@
+import { SVGProps } from 'react'
+
+export function HTML5Icon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/linux.tsx b/src/app/_components/grid/cards/icons/linux.tsx
new file mode 100644
index 00000000..186dfa80
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/linux.tsx
@@ -0,0 +1,3092 @@
+import { SVGProps } from 'react'
+
+export function LinuxIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/nest.tsx b/src/app/_components/grid/cards/icons/nest.tsx
new file mode 100644
index 00000000..367f2125
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/nest.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function NestIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/next.tsx b/src/app/_components/grid/cards/icons/next.tsx
new file mode 100644
index 00000000..a082f8aa
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/next.tsx
@@ -0,0 +1,40 @@
+import { SVGProps } from 'react'
+
+export function NextIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/node.tsx b/src/app/_components/grid/cards/icons/node.tsx
new file mode 100644
index 00000000..7e6e8561
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/node.tsx
@@ -0,0 +1,73 @@
+import { SVGProps } from 'react'
+
+export function NodeIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/prisma.tsx b/src/app/_components/grid/cards/icons/prisma.tsx
new file mode 100644
index 00000000..d4e27b9e
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/prisma.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function PrismaIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/python.tsx b/src/app/_components/grid/cards/icons/python.tsx
new file mode 100644
index 00000000..4da91633
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/python.tsx
@@ -0,0 +1,58 @@
+import { SVGProps } from 'react'
+
+export function PythonIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/react.tsx b/src/app/_components/grid/cards/icons/react.tsx
new file mode 100644
index 00000000..f454432f
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/react.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function ReactIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/rust.tsx b/src/app/_components/grid/cards/icons/rust.tsx
new file mode 100644
index 00000000..ff5efeef
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/rust.tsx
@@ -0,0 +1,15 @@
+import { SVGProps } from 'react'
+
+export function RustIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/svelte.tsx b/src/app/_components/grid/cards/icons/svelte.tsx
new file mode 100644
index 00000000..fdd90447
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/svelte.tsx
@@ -0,0 +1,26 @@
+import { SVGProps } from 'react'
+
+export function SvelteIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/tailwindcss.tsx b/src/app/_components/grid/cards/icons/tailwindcss.tsx
new file mode 100644
index 00000000..058fc15a
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/tailwindcss.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function TailwindcssIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/typescript.tsx b/src/app/_components/grid/cards/icons/typescript.tsx
new file mode 100644
index 00000000..c55b4635
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/typescript.tsx
@@ -0,0 +1,14 @@
+import { SVGProps } from 'react'
+
+export function TypescriptIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/icons/vercel.tsx b/src/app/_components/grid/cards/icons/vercel.tsx
new file mode 100644
index 00000000..3d51170e
--- /dev/null
+++ b/src/app/_components/grid/cards/icons/vercel.tsx
@@ -0,0 +1,12 @@
+import { SVGProps } from 'react'
+
+export function VercelIcon(props: SVGProps) {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/latest-post.tsx b/src/app/_components/grid/cards/latest-post.tsx
new file mode 100644
index 00000000..6de529d0
--- /dev/null
+++ b/src/app/_components/grid/cards/latest-post.tsx
@@ -0,0 +1,37 @@
+import { posts } from '#content'
+import Link from 'next/link'
+import { Date } from '~/components/date'
+import { getSortedPosts } from '~/lib/get-sorted-posts'
+
+export function LatestPost() {
+ const { title } = {
+ title: 'My latest post dsdsadsadasdsadsa'
+ }
+
+ const latestPost = getSortedPosts(posts)[0]
+
+ const Divider = () => (
+
+ )
+
+ return (
+
+ Latest post
+
+
+
+
+ {latestPost.title}
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/app/_components/grid/cards/letterboxed-link.tsx b/src/app/_components/grid/cards/letterboxed-link.tsx
new file mode 100644
index 00000000..f060a169
--- /dev/null
+++ b/src/app/_components/grid/cards/letterboxed-link.tsx
@@ -0,0 +1,45 @@
+import { ArrowUpRight } from '@phosphor-icons/react/dist/ssr'
+
+export function LetterboxedLink() {
+ return (
+
+
+
+
+
+
+ Letter
+ boxed
+
+
+
+ Letter
+ boxed
+
+
+
+ Letter
+ boxed
+
+
+
+ Letter
+ boxed
+
+
+
+ Letter
+ boxed
+
+
+
+ Letter
+ boxed
+
+
+ )
+}
diff --git a/src/app/_components/grid/cards/links.tsx b/src/app/_components/grid/cards/links.tsx
new file mode 100644
index 00000000..bcb63266
--- /dev/null
+++ b/src/app/_components/grid/cards/links.tsx
@@ -0,0 +1,47 @@
+import {
+ LinkedinLogo,
+ XLogo,
+ RedditLogo,
+ CodepenLogo
+} from '@phosphor-icons/react/dist/ssr'
+
+export const LinksCard = () => {
+ return (
+
+ )
+}
diff --git a/src/app/_components/grid/cards/most-listened-music.tsx b/src/app/_components/grid/cards/most-listened-music.tsx
new file mode 100644
index 00000000..b68f8484
--- /dev/null
+++ b/src/app/_components/grid/cards/most-listened-music.tsx
@@ -0,0 +1,51 @@
+import { LastfmLogo } from '@phosphor-icons/react/dist/ssr'
+import Image from 'next/image'
+import { getAlbumCover } from '~/lib/get-album-cover'
+import { getLastFmTopTracks } from '~/lib/lastFm'
+
+export async function MostListenedMusic() {
+ const {
+ name: title,
+ artist,
+ url
+ } = await getLastFmTopTracks('1month').then(tracks => tracks[0])
+
+ const cover = await getAlbumCover(`${title} - ${artist.name}`)
+
+ return (
+
+
+
+
+ {artist.name}
+
+
{title}
+
+ Top listened this month
+
+
+
+
+
+ )
+}
diff --git a/src/app/_components/grid/cards/stack-icons.tsx b/src/app/_components/grid/cards/stack-icons.tsx
new file mode 100644
index 00000000..4d03b701
--- /dev/null
+++ b/src/app/_components/grid/cards/stack-icons.tsx
@@ -0,0 +1,63 @@
+import { IconType } from 'react-icons'
+
+import { DockerIcon } from './icons/docker'
+import { TypescriptIcon } from './icons/typescript'
+import { HTML5Icon } from './icons/html'
+import { CSS3Icon } from './icons/css'
+import { ReactIcon } from './icons/react'
+import { NextIcon } from './icons/next'
+import { SvelteIcon } from './icons/svelte'
+import { TailwindcssIcon } from './icons/tailwindcss'
+import { ExpoIcon } from './icons/expo'
+import { PythonIcon } from './icons/python'
+import { RustIcon } from './icons/rust'
+import { BashIcon } from './icons/bash'
+import { NodeIcon } from './icons/node'
+import { PrismaIcon } from './icons/prisma'
+import { NestIcon } from './icons/nest'
+import { HerokuIcon } from './icons/heroku'
+import { LinuxIcon } from './icons/linux'
+import { GoogleCloudIcon } from './icons/google-cloud'
+import { VercelIcon } from './icons/vercel'
+import { CloudflareIcon } from './icons/cloudflare'
+import { FigmaIcon } from './icons/figma'
+
+export type IconItem = {
+ title: string
+ icon: IconType
+ color: string
+}
+
+type StackLine = {
+ top: IconItem[]
+ bottom: IconItem[]
+}
+
+export const stackLines: StackLine = {
+ top: [
+ { title: 'Typescript', icon: TypescriptIcon, color: '#3178C6' },
+ { title: 'HTML5', icon: HTML5Icon, color: '#E34F26' },
+ { title: 'CSS3', icon: CSS3Icon, color: '#1572B6' },
+ { title: 'React.js', icon: ReactIcon, color: '#61DAFB' },
+ { title: 'Next.js', icon: NextIcon, color: '#FFFFFF' },
+ { title: 'Svelte', icon: SvelteIcon, color: '#FF3E00' },
+ { title: 'Tailwind CSS', icon: TailwindcssIcon, color: '#06B6D4' },
+ { title: 'React Native', icon: ReactIcon, color: '#61DAFB' },
+ { title: 'Expo', icon: ExpoIcon, color: '#000020' },
+ { title: 'Figma', icon: FigmaIcon, color: '#F24E1E' }
+ ],
+ bottom: [
+ { title: 'Python', icon: PythonIcon, color: '#3776AB' },
+ { title: 'Rust', icon: RustIcon, color: '#F74C00' },
+ { title: 'Bash Script', icon: BashIcon, color: '#4EAA25' },
+ { title: 'Node.js', icon: NodeIcon, color: '#339933' },
+ { title: 'Prisma', icon: PrismaIcon, color: '#2D3748' },
+ { title: 'Nest.js', icon: NestIcon, color: '#E0234E' },
+ { title: 'Heroku', icon: HerokuIcon, color: '#430098' },
+ { title: 'Docker', icon: DockerIcon, color: '#2496ED' },
+ { title: 'Linux', icon: LinuxIcon, color: '#FCC624' },
+ { title: 'Google Cloud', icon: GoogleCloudIcon, color: '#4285F4' },
+ { title: 'Vercel', icon: VercelIcon, color: '#000000' },
+ { title: 'Cloudflare', icon: CloudflareIcon, color: '#F38020' }
+ ]
+}
diff --git a/src/app/_components/grid/cards/stacks-card.tsx b/src/app/_components/grid/cards/stacks-card.tsx
new file mode 100644
index 00000000..ae9b5a86
--- /dev/null
+++ b/src/app/_components/grid/cards/stacks-card.tsx
@@ -0,0 +1,97 @@
+import React from 'react'
+import { stackLines, IconItem } from './stack-icons'
+
+type MarqueeProps = {
+ children: React.ReactNode
+ direction?: 'left' | 'up'
+ pauseOnHover?: boolean
+ reverse?: boolean
+ fade?: boolean
+}
+
+const range = (start: number, end: number): number[] =>
+ Array.from({ length: end - start }, (_, k) => k + start)
+
+function Marquee(props: MarqueeProps) {
+ const {
+ children,
+ direction = 'left',
+ pauseOnHover = false,
+ reverse = false,
+ fade = false
+ } = props
+
+ const ifToRightOrToBottom = (direction: string) => {
+ if (direction === 'left') {
+ return 'to right'
+ } else {
+ return 'to bottom'
+ }
+ }
+
+ return (
+
+ {range(0, 2).map(i => (
+
+ {children}
+
+ ))}
+
+ )
+}
+
+const IconElement = ({
+ data: { color, icon: Icon, title }
+}: {
+ data: IconItem
+}) => (
+
+
+
+)
+
+export const StacksCard = () => {
+ return (
+
+
+
+
+
+ )
+}
diff --git a/src/app/_components/grid/index.tsx b/src/app/_components/grid/index.tsx
new file mode 100644
index 00000000..14a44a9e
--- /dev/null
+++ b/src/app/_components/grid/index.tsx
@@ -0,0 +1,63 @@
+import { Book, Wrench } from '@phosphor-icons/react/dist/ssr'
+import { DiscordStatus } from './cards/discord-status'
+import { GithubLink } from './cards/github-link'
+import { GithubStats } from './cards/github-stats'
+import { LatestPost } from './cards/latest-post'
+import { LetterboxedLink } from './cards/letterboxed-link'
+import { LinksCard } from './cards/links'
+import { MostListenedMusic } from './cards/most-listened-music'
+import { StacksCard } from './cards/stacks-card'
+
+const BooksCard = () => (
+
+
+
+ Books
+
+
+
+
Under construction...
+
+)
+
+export function Grid() {
+ return (
+
+ )
+}
diff --git a/src/app/about/sections/knowledge/accordion.tsx b/src/app/about/sections/knowledge/accordion.tsx
index b3c69ecc..24732724 100644
--- a/src/app/about/sections/knowledge/accordion.tsx
+++ b/src/app/about/sections/knowledge/accordion.tsx
@@ -40,7 +40,7 @@ export const AccordionTrigger = React.forwardRef(
{children}
diff --git a/src/app/about/statistics/_components/spotify-dashboard/cards/top-track.tsx b/src/app/about/statistics/_components/spotify-dashboard/cards/top-track.tsx
index 081b5c27..beff4151 100644
--- a/src/app/about/statistics/_components/spotify-dashboard/cards/top-track.tsx
+++ b/src/app/about/statistics/_components/spotify-dashboard/cards/top-track.tsx
@@ -3,13 +3,14 @@ import { MusicNotes } from '@phosphor-icons/react/dist/ssr'
import { placeholder } from '~/lib/placeholder'
import { getLastFmTopTracks } from '~/lib/lastFm'
+import { getAlbumCover } from '~/lib/get-album-cover'
export async function TopTrack() {
const track = await getLastFmTopTracks('1month').then(tracks => tracks[0])
- const imageUrl = track.image.find(image => image.size === 'extralarge')?.[
- '#text'
- ]
+ const imageUrl = await getAlbumCover(
+ `${track.name} - ${track.artist.name}`
+ ).then(data => data.url)
return (
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 6062bf8c..9a736c48 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,13 +1,15 @@
+import { Grid } from './_components/grid'
import { MainTitle } from './_components/main-title'
import { StartButton } from './_components/start-button'
export default function Page() {
return (
-
-
+
)
}
diff --git a/src/lib/get-album-cover.ts b/src/lib/get-album-cover.ts
new file mode 100644
index 00000000..deb861c0
--- /dev/null
+++ b/src/lib/get-album-cover.ts
@@ -0,0 +1,54 @@
+// this is a workaround to get track cover from spotify if lastfm fails to get the image
+
+const ENDPOINT = 'https://api.spotify.com/v1/search?q='
+
+const CLIENT_ID = process.env.SPOTIFY_CLIENT_ID as string
+const CLIENT_SECRET = process.env.SPOTIFY_CLIENT_SECRET as string
+const REFRESH_TOKEN = process.env.SPOTIFY_REFRESH_TOKEN as string
+
+const BASIC = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString('base64')
+
+const TOKEN_ENDPOINT = 'https://accounts.spotify.com/api/token'
+
+const getAccessToken = async () => {
+ const response = await fetch(TOKEN_ENDPOINT, {
+ method: 'POST',
+ headers: {
+ Authorization: `Basic ${BASIC}`,
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ },
+ body: new URLSearchParams({
+ grant_type: 'refresh_token',
+ refresh_token: REFRESH_TOKEN
+ })
+ })
+ return await response.json()
+}
+
+type Response = {
+ tracks: {
+ items: {
+ album: {
+ images: {
+ url: string
+ height: 64 | 300 | 640
+ width: 64 | 300 | 640
+ }[]
+ }
+ }[]
+ }
+}
+
+export async function getAlbumCover(track: string) {
+ const { access_token } = await getAccessToken()
+ const URL = ENDPOINT + encodeURI(track) + '&type=track&market=IN&limit=1'
+ const res: Response = await fetch(URL, {
+ headers: {
+ Authorization: `Bearer ${access_token}`
+ }
+ }).then(async res => {
+ return res.json()
+ })
+
+ return res.tracks.items[0].album.images.filter(img => img.height === 640)[0]
+}
diff --git a/tailwind.config.ts b/tailwind.config.ts
index 66d582e5..dbde3a01 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -39,7 +39,9 @@ const config: Config = {
shine: 'shine 4s linear 0s forwards',
'custom-fade-down': 'custom-fade-down 200ms linear',
'slide-left': 'slide-left 70ms linear',
- 'slide-right': 'slide-right 70ms linear'
+ 'slide-right': 'slide-right 70ms linear',
+ 'marquee-left': 'marquee-left var(--duration, 30s) linear infinite',
+ 'marquee-up': 'marquee-up var(--duration, 30s) linear infinite'
},
keyframes: {
typing: {
@@ -109,6 +111,14 @@ const config: Config = {
transform:
'translate(0, var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))'
}
+ },
+ 'marquee-left': {
+ from: { transform: 'translateX(0)' },
+ to: { transform: 'translateX(calc(-100% - var(--gap)))' }
+ },
+ 'marquee-up': {
+ from: { transform: 'translateY(0)' },
+ to: { transform: 'translateY(calc(-100% - var(--gap)))' }
}
}
},
@@ -127,6 +137,7 @@ const config: Config = {
},
plugins: [
require('tailwindcss-animated'),
+ require('tailwindcss-animate'),
require('@tailwindcss/forms')({ strategy: 'class' }),
require('tailwind-scrollbar')({
nocompatible: true,