From 9fa12913bece835380bb26d86f9b58b366c86941 Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:01:43 -0300 Subject: [PATCH 1/8] =?UTF-8?q?fix(home):=20remove=20se=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20m=C3=BAsica=20da=20home?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Já existe em /agora, ficava duplicada. Co-Authored-By: Claude Sonnet 5 --- app/page.tsx | 2 - .../home/components/Activity/LastfmCard.tsx | 294 ------------------ .../home/components/Activity/index.tsx | 1 - 3 files changed, 297 deletions(-) delete mode 100644 src/features/home/components/Activity/LastfmCard.tsx delete mode 100644 src/features/home/components/Activity/index.tsx diff --git a/app/page.tsx b/app/page.tsx index 3c6f33b..707596b 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -4,7 +4,6 @@ import { AnimationLayout } from '@/base/components/Layout/AnimationLayout'; import { Navbar } from '@/base/components/Navbar'; import { ArticlesList } from '@/features/articles/components/ArticlesList'; import { About } from '@/features/home/components/About'; -import { LastfmCard } from '@/features/home/components/Activity'; import { DevPulse } from '@/features/home/components/DevPulse'; import { Hero } from '@/features/home/components/Hero'; import { ProjectsShowcase } from '@/features/home/components/Projects'; @@ -51,7 +50,6 @@ export default function Page() { - diff --git a/src/features/home/components/Activity/LastfmCard.tsx b/src/features/home/components/Activity/LastfmCard.tsx deleted file mode 100644 index 4bf9c7c..0000000 --- a/src/features/home/components/Activity/LastfmCard.tsx +++ /dev/null @@ -1,294 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import Link from 'next/link'; -import { CSSProperties, ReactNode, useState } from 'react'; - -import useSWR from 'swr'; - -import { Card } from '@/base/components/Card'; -import { - SECTION_ACTION_CLASS, - SectionHeader, -} from '@/base/components/SectionHeader'; -import { LastfmStats, LastfmTrack } from '@/types/Lastfm'; - -type LastfmNowPlayingResponse = { - nowPlaying: LastfmTrack | null; -}; - -const jsonFetcher = (url: string) => fetch(url).then((r) => r.json()); - -const MusicIcon = ({ size = 18 }: { size?: number }) => ( - -); - -const DiscIcon = () => ( - -); - -const LiveBadge = () => ( - - {' '} - ao vivo - -); - -const FadeIn = ({ - children, - className, - delay = 0, - duration = 300, -}: { - children: ReactNode; - className?: string; - delay?: number; - duration?: number; -}) => ( -
- {children} -
-); - -const TrackArtwork = ({ - eager = false, - track, - size = 64, -}: { - eager?: boolean; - track: LastfmTrack; - size?: number; -}) => { - const [failed, setFailed] = useState(false); - - if (!track.imageUrl || failed) { - return ( -
- -
- ); - } - - return ( - {track.album setFailed(true)} - /> - ); -}; - -const TrackRow = ({ track }: { track: LastfmTrack }) => ( -
  • - -
    - - {track.name} - -

    - {track.artist} -

    -
    -
  • -); - -export const LastfmCard = () => { - const { data: nowPlayingData, isLoading: loadingNowPlaying } = - useSWR('/api/lastfm/now-playing', jsonFetcher, { - revalidateOnFocus: false, - }); - const { data: recent, isLoading: loadingRecent } = useSWR( - '/api/lastfm/recent', - jsonFetcher, - { revalidateOnFocus: false }, - ); - - const loading = loadingNowPlaying || loadingRecent; - const lastfm: LastfmStats | null = recent - ? { ...recent, nowPlaying: nowPlayingData?.nowPlaying ?? null } - : null; - - const featuredTrack = lastfm?.nowPlaying ?? lastfm?.lastPlayed ?? null; - const title = lastfm?.nowPlaying ? 'Ouvindo agora' : 'Última música'; - - return ( -
    - } - title="Música" - action={ - - Ver no Last.fm → - - } - /> -
    - - -
    -
    -

    - {title} -

    - -
    - {lastfm?.nowPlaying ? : null} -
    - - {loading && ( -
    - )} - - {!loading && featuredTrack && ( - <> -
    - -
    - - {featuredTrack.name} - - {featuredTrack.album && ( -

    - {featuredTrack.album} -

    - )} -

    - {featuredTrack.artist} -

    -
    -
    - - Ver no Last.fm - - - )} - - {!loading && !featuredTrack && ( -

    - Nenhuma música recente encontrada. -

    - )} - - - - - -
    -

    - Últimas faixas -

    - -
    - - {loading && ( -
    - {[0, 1, 2].map((item) => ( -
    - ))} -
    - )} - - {!loading && lastfm && lastfm.tracks.length > 0 && ( -
      - {lastfm.tracks.slice(0, 4).map((track, index) => ( - - ))} -
    - )} - - {!loading && (!lastfm || lastfm.tracks.length === 0) && ( -

    - Sem histórico recente para mostrar. -

    - )} - - -
    -
    - ); -}; diff --git a/src/features/home/components/Activity/index.tsx b/src/features/home/components/Activity/index.tsx deleted file mode 100644 index 32932e4..0000000 --- a/src/features/home/components/Activity/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { LastfmCard } from './LastfmCard'; From bcb36c320eeffcafdf415956b3513edf628ead17 Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:01:57 -0300 Subject: [PATCH 2/8] fix(now): adiciona seta ao tooltip dos cards e ajusta padding Aproxima do estilo do doce.sh. Co-Authored-By: Claude Sonnet 5 --- src/features/now/components/CardTooltip.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/features/now/components/CardTooltip.tsx b/src/features/now/components/CardTooltip.tsx index 403915e..9070a4f 100644 --- a/src/features/now/components/CardTooltip.tsx +++ b/src/features/now/components/CardTooltip.tsx @@ -47,9 +47,12 @@ export const CardTooltip = ({ side="bottom" align="center" sideOffset={6} - className="z-20 w-max max-w-[18ch] rounded-md border border-site-border bg-site-popover px-3 py-2 text-center shadow-xl shadow-black/30 backdrop-blur-sm animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-1" + className="z-20 w-max max-w-[18ch] rounded-md border border-site-border bg-site-popover px-3 py-1.5 text-center shadow-xl shadow-black/30 backdrop-blur-sm animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-1" > {content} + +
    + From cd7ffc47d9fe013c4264b2ddabd5c952eb71ce59 Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:09:32 -0300 Subject: [PATCH 3/8] fix(now): abre tooltip no primeiro toque em telas sem hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sem mouse, o clique navegava direto sem chance de ver o tooltip. Agora o primeiro toque só mostra a prévia; o segundo confirma a navegação. Co-Authored-By: Claude Sonnet 5 --- src/features/now/components/CardTooltip.tsx | 27 ++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/features/now/components/CardTooltip.tsx b/src/features/now/components/CardTooltip.tsx index 9070a4f..7d64328 100644 --- a/src/features/now/components/CardTooltip.tsx +++ b/src/features/now/components/CardTooltip.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ReactNode, useSyncExternalStore } from 'react'; +import { MouseEvent, ReactNode, useState, useSyncExternalStore } from 'react'; import * as Tooltip from '@radix-ui/react-tooltip'; @@ -8,6 +8,9 @@ const subscribe = () => () => {}; const getClientSnapshot = () => true; const getServerSnapshot = () => false; +/** Sem hover de verdade (touch) — primeiro toque só mostra o tooltip, segundo toque navega. */ +const usesCoarsePointer = () => window.matchMedia('(hover: none)').matches; + /** * Tooltip padrão dos cards de imagem da página /now. * @@ -15,6 +18,7 @@ const getServerSnapshot = () => false; * - delayDuration={300} → abre após 300ms de hover (evita flashes ao passar o mouse) * - skipDelayDuration={0} → se mover de um card para outro, abre imediatamente * - disableHoverableContent → fecha ao sair do trigger, sem "entrar" no tooltip + * - em telas de toque, o Root vira controlado e o primeiro tap intercepta a navegação */ export const CardTooltip = ({ children, @@ -31,17 +35,34 @@ export const CardTooltip = ({ getClientSnapshot, getServerSnapshot, ); + const [tapOpen, setTapOpen] = useState(false); if (!isMounted) return children; + const isTouch = usesCoarsePointer(); + + const handleTriggerClick = (event: MouseEvent) => { + if (!isTouch) return; + if (tapOpen) { + setTapOpen(false); + return; + } + event.preventDefault(); + setTapOpen(true); + }; + return ( - - {children} + + + {children} + Date: Wed, 26 Aug 2026 23:09:42 -0300 Subject: [PATCH 4/8] =?UTF-8?q?fix(now):=20completa=20o=20skeleton=20da=20?= =?UTF-8?q?se=C3=A7=C3=A3o=20de=20jogos=20no=20loading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A seção Provável recaída ficava sem placeholder de cards, só o cabeçalho. Co-Authored-By: Claude Sonnet 5 --- app/now/loading.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/now/loading.tsx b/app/now/loading.tsx index 899bf3c..4ca221e 100644 --- a/app/now/loading.tsx +++ b/app/now/loading.tsx @@ -75,8 +75,15 @@ export default function Loading() {
    -
    +
    +
    + + + + + +
    From 59386b63b5a668dd8479425e1d7e6f9eca66540d Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:30:45 -0300 Subject: [PATCH 5/8] fix(now): padroniza o cabecalho da secao de jogos e mostra ultima sincronizacao O link "Ver no Steam" ficava embaixo do grid, com estilo proprio - diferente do resto do site, que usa o slot action do SectionHeader ("Ver no Last.fm", "Ver todos" etc). Movido pra la. Adiciona tambem um selo "Atualizado ha X" no SectionHeader (prop updatedAt, opcional), inspirado no doce.sh. O cache do Steam agora guarda o timestamp da ultima sincronizacao real junto com os jogos (bump pra v4 nas chaves) pra alimentar esse selo. Co-Authored-By: Claude Sonnet 5 --- app/now/page.tsx | 65 ++++++--------- .../SectionHeader/SectionHeader.tsx | 20 +++++ src/lib/steam.ts | 80 ++++++++++++------- 3 files changed, 95 insertions(+), 70 deletions(-) diff --git a/app/now/page.tsx b/app/now/page.tsx index 796a7fe..6315220 100644 --- a/app/now/page.tsx +++ b/app/now/page.tsx @@ -3,7 +3,6 @@ import Link from 'next/link'; import { PageWrapper } from '../components/PageWrapper'; import { faSpotify } from '@fortawesome/free-brands-svg-icons'; import { - faArrowRight, faBullseye, faCode, faGamepad, @@ -145,7 +144,11 @@ export default async function NowPage() { getLastfmTopArtists({ period: '1month' }).catch(() => []), getLastfmTopTracks({ period: '1month' }).catch(() => []), getGithubDev().catch(() => null), - getSteamGames().catch(() => ({ games: [], source: 'recent' as const })), + getSteamGames().catch(() => ({ + games: [], + source: 'recent' as const, + updatedAt: null, + })), getGithubStarred().catch(() => []), getLolLiveGame().catch(() => null), ]); @@ -370,6 +373,18 @@ export default async function NowPage() { id="playing-title" title="Provável recaída" subtitle={PLAYING_DESCRIPTION} + updatedAt={steam.updatedAt} + action={ + + {steam.games.length > 0 ? 'Ver no Steam' : 'Steam fica aqui'}{' '} + → + + } /> {lolLiveGame && ( @@ -382,47 +397,15 @@ export default async function NowPage() { )} {steam.games.length > 0 ? ( -
    -
    - {steam.games.map((game) => ( - - ))} -
    -
    - - Ver no Steam -
    +
    + {steam.games.map((game) => ( + + ))}
    ) : ( -
    -

    - Nenhum jogo para mostrar. -

    - - Steam fica aqui -
    +

    + Nenhum jogo para mostrar. +

    )}
    diff --git a/src/base/components/SectionHeader/SectionHeader.tsx b/src/base/components/SectionHeader/SectionHeader.tsx index 658e2e4..0f4763b 100644 --- a/src/base/components/SectionHeader/SectionHeader.tsx +++ b/src/base/components/SectionHeader/SectionHeader.tsx @@ -1,6 +1,10 @@ import type { ReactNode } from 'react'; +import { faCalendarCheck } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; + import { SectionIcon } from '@/base/components/SectionIcon'; +import { formatRelativeTime } from '@/lib/formatRelativeTime'; /** Estilo único dos links de ação das seções ("Ver todos", "@usuario"…). */ export const SECTION_ACTION_CLASS = @@ -16,6 +20,8 @@ type SectionHeaderProps = { /** Link/ação alinhada à direita (ex.: "Ver todos"). */ action?: ReactNode; className?: string; + /** ISO da última sincronização. Quando informado, mostra o selo "Atualizado há X". */ + updatedAt?: string | null; }; /** @@ -30,6 +36,7 @@ export const SectionHeader = ({ subtitle, action, className = '', + updatedAt, }: SectionHeaderProps) => (
    ) : null} + {updatedAt ? ( +

    +

    + ) : null} {action ?
    {action}
    : null} diff --git a/src/lib/steam.ts b/src/lib/steam.ts index 393f1f1..2f42635 100644 --- a/src/lib/steam.ts +++ b/src/lib/steam.ts @@ -13,8 +13,8 @@ const debugLog = (...args: unknown[]) => { const CACHE_TTL_RECENT = 60 * 60 * 24; // 24h // Top all-time: ranking de horas totais não muda do dia pra noite const CACHE_TTL_ALLTIME = 60 * 60 * 24 * 7; // 7 dias -const CACHE_KEY_RECENT = 'steam:recently-played:v3'; // v3: descarta listas curtas gravadas antes do backfill -const CACHE_KEY_ALLTIME = 'steam:alltime:v3'; // v3: descarta top all-time anterior ao backfill (sem Phasmophobia etc.) +const CACHE_KEY_RECENT = 'steam:recently-played:v4'; // v4: payload passa a incluir updatedAt +const CACHE_KEY_ALLTIME = 'steam:alltime:v4'; // v4: payload passa a incluir updatedAt /** Quantos cards a grade exibe — sempre completamos até esse número. */ const TARGET_COUNT = 5; @@ -51,6 +51,14 @@ export type SteamResult = { games: SteamGame[]; /** 'recent' = jogados nas últimas 2 semanas; 'alltime' = mais jogados de todos os tempos */ source: 'recent' | 'alltime'; + /** ISO de quando a lista foi sincronizada com a Steam pela última vez; null se nunca sincronizou. */ + updatedAt: string | null; +}; + +/** Payload cacheado — guarda o timestamp junto pra exibir "atualizado há X". */ +type SteamCachePayload = { + games: SteamGame[]; + updatedAt: string; }; type SteamApiGame = { @@ -161,23 +169,26 @@ const fetchTopAllTime = async ( const getRecentGames = async ( userId: string, apiKey: string, -): Promise => { - const cached = await cacheGet(CACHE_KEY_RECENT); - if (cached && cached.length > 0) { - debugLog(`cache hit (recent): ${cached.length} jogos`); +): Promise => { + const cached = await cacheGet(CACHE_KEY_RECENT); + if (cached && cached.games.length > 0) { + debugLog(`cache hit (recent): ${cached.games.length} jogos`); return cached; } try { const recent = await fetchRecentlyPlayed(userId, apiKey); debugLog(`api recent: ${recent.length} jogos`); - if (recent.length > 0) { - await cacheSet(CACHE_KEY_RECENT, recent, CACHE_TTL_RECENT); - } - return recent; + if (recent.length === 0) return { games: [], updatedAt: '' }; + const payload: SteamCachePayload = { + games: recent, + updatedAt: new Date().toISOString(), + }; + await cacheSet(CACHE_KEY_RECENT, payload, CACHE_TTL_RECENT); + return payload; } catch (err) { debugLog('erro ao buscar recentes:', err); - return []; + return { games: [], updatedAt: '' }; } }; @@ -185,23 +196,26 @@ const getRecentGames = async ( const getAllTimeGames = async ( userId: string, apiKey: string, -): Promise => { - const cached = await cacheGet(CACHE_KEY_ALLTIME); - if (cached && cached.length > 0) { - debugLog(`cache hit (alltime): ${cached.length} jogos`); +): Promise => { + const cached = await cacheGet(CACHE_KEY_ALLTIME); + if (cached && cached.games.length > 0) { + debugLog(`cache hit (alltime): ${cached.games.length} jogos`); return cached; } try { const alltime = await fetchTopAllTime(userId, apiKey); debugLog(`api alltime: ${alltime.length} jogos`); - if (alltime.length > 0) { - await cacheSet(CACHE_KEY_ALLTIME, alltime, CACHE_TTL_ALLTIME); - } - return alltime; + if (alltime.length === 0) return { games: [], updatedAt: '' }; + const payload: SteamCachePayload = { + games: alltime, + updatedAt: new Date().toISOString(), + }; + await cacheSet(CACHE_KEY_ALLTIME, payload, CACHE_TTL_ALLTIME); + return payload; } catch (err) { debugLog('erro ao buscar all-time:', err); - return []; + return { games: [], updatedAt: '' }; } }; @@ -211,26 +225,34 @@ export const getSteamGames = async (): Promise => { if (!apiKey || !userId) { debugLog('STEAM_API_KEY ou STEAM_USER_ID ausentes'); - return { games: [], source: 'recent' }; + return { games: [], source: 'recent', updatedAt: null }; } const recent = await getRecentGames(userId, apiKey); - if (recent.length >= TARGET_COUNT) { - return { games: recent.slice(0, TARGET_COUNT), source: 'recent' }; + if (recent.games.length >= TARGET_COUNT) { + return { + games: recent.games.slice(0, TARGET_COUNT), + source: 'recent', + updatedAt: recent.updatedAt || null, + }; } // Menos recentes que o alvo: completa com os mais jogados de todos os tempos // (sem repetir os que já estão na lista) para a grade nunca ficar incompleta. const alltime = await getAllTimeGames(userId, apiKey); - const seen = new Set(recent.map((game) => game.appid)); - const backfill = alltime.filter((game) => !seen.has(game.appid)); - const games = [...recent, ...backfill].slice(0, TARGET_COUNT); + const seen = new Set(recent.games.map((game) => game.appid)); + const backfill = alltime.games.filter((game) => !seen.has(game.appid)); + const games = [...recent.games, ...backfill].slice(0, TARGET_COUNT); debugLog( - `final: ${recent.length} recentes + ${ - games.length - recent.length + `final: ${recent.games.length} recentes + ${ + games.length - recent.games.length } backfill`, ); - return { games, source: recent.length > 0 ? 'recent' : 'alltime' }; + return { + games, + source: recent.games.length > 0 ? 'recent' : 'alltime', + updatedAt: recent.updatedAt || alltime.updatedAt || null, + }; }; From 95a23ef7f0d2691201dcb70690b1a3ac00304e84 Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:38:13 -0300 Subject: [PATCH 6/8] fix(now): move Ver no Steam/Last.fm to bottom and drop featured-track card Ver no Steam/Ver no Last.fm agora ficam embaixo de cada secao, igual ao doce.sh, em vez de no cabecalho. Removido tambem o card de destaque Faixa mais tocada - o doce.sh so mostra uma lista simples de tocadas recentemente, sem esse recorte. A busca de top tracks do Last.fm (so usada por esse card) tambem saiu. Co-Authored-By: Claude Sonnet 5 --- app/now/page.tsx | 104 ++++++------------ src/features/now/components/FeaturedTrack.tsx | 93 ---------------- src/features/now/components/index.ts | 1 - 3 files changed, 36 insertions(+), 162 deletions(-) delete mode 100644 src/features/now/components/FeaturedTrack.tsx diff --git a/app/now/page.tsx b/app/now/page.tsx index 6315220..336c09e 100644 --- a/app/now/page.tsx +++ b/app/now/page.tsx @@ -19,7 +19,6 @@ import { ActivityFeed, ArtistCard, CodingRhythm, - FeaturedTrack, GameCard, LanguageStack, LolLiveGame, @@ -28,11 +27,7 @@ import { StarredRepos, } from '@/features/now/components'; import { getGithubDev, getGithubStarred } from '@/lib/github'; -import { - getLastfmRecentStats, - getLastfmTopArtists, - getLastfmTopTracks, -} from '@/lib/lastfm'; +import { getLastfmRecentStats, getLastfmTopArtists } from '@/lib/lastfm'; import { getLolLiveGame } from '@/lib/lol'; import { SITE_NAME, SITE_URL } from '@/lib/site'; import { getSteamGames } from '@/lib/steam'; @@ -105,20 +100,10 @@ type LastfmData = { tracks: LastfmTrack[]; }; -const computeTrackData = (topTracks: LastfmTrack[], lastfm: LastfmData) => { - const recentTracks = getUniqueTracks( +const computeTrackData = (lastfm: LastfmData) => + getUniqueTracks( [lastfm.lastPlayed, ...lastfm.tracks].filter(Boolean) as LastfmTrack[], ); - const featuredTrack = topTracks[0] ?? recentTracks[0]; - const recentTrackList = featuredTrack - ? recentTracks.filter( - (track) => - track.name !== featuredTrack.name || - track.artist !== featuredTrack.artist, - ) - : recentTracks; - return { recentTracks, featuredTrack, recentTrackList }; -}; const getUniqueTracks = (tracks: LastfmTrack[]) => { const seen = new Set(); @@ -134,15 +119,14 @@ const getUniqueTracks = (tracks: LastfmTrack[]) => { }; export default async function NowPage() { - const [lastfm, artists, topTracks, dev, steam, starred, lolLiveGame] = - await Promise.all([ + const [lastfm, artists, dev, steam, starred, lolLiveGame] = await Promise.all( + [ getLastfmRecentStats().catch(() => ({ nowPlaying: null, lastPlayed: null, tracks: [], })), getLastfmTopArtists({ period: '1month' }).catch(() => []), - getLastfmTopTracks({ period: '1month' }).catch(() => []), getGithubDev().catch(() => null), getSteamGames().catch(() => ({ games: [], @@ -151,16 +135,14 @@ export default async function NowPage() { })), getGithubStarred().catch(() => []), getLolLiveGame().catch(() => null), - ]); + ], + ); const hasDevData = Boolean( dev && (dev.rhythm || dev.languages.length > 0 || dev.activity.length > 0), ); - const { featuredTrack, recentTrackList } = computeTrackData( - topTracks, - lastfm, - ); + const recentTracks = computeTrackData(lastfm); return ( @@ -267,45 +249,22 @@ export default async function NowPage() { id="listening-title" title="No repeat do mês" subtitle={LISTENING_DESCRIPTION} - action={ - - Ver no Last.fm → - - } />

    - Trilha do mês -

    - - {featuredTrack ? ( -
    - -
    - ) : null} - -

    Mais recentes -

    - {recentTrackList.length > 0 ? ( + + {recentTracks.length > 0 ? (
      - {recentTrackList.slice(0, 4).map((track, index) => ( + {recentTracks.slice(0, 5).map((track, index) => ( ) : (

      - {featuredTrack - ? 'Sem outras músicas recentes para mostrar.' - : 'Sem músicas recentes para mostrar.'} + Sem músicas recentes para mostrar.

      )}
    @@ -359,6 +316,17 @@ export default async function NowPage() { )}
    + +
    + + Ver no Last.fm → + +
    @@ -374,17 +342,6 @@ export default async function NowPage() { title="Provável recaída" subtitle={PLAYING_DESCRIPTION} updatedAt={steam.updatedAt} - action={ - - {steam.games.length > 0 ? 'Ver no Steam' : 'Steam fica aqui'}{' '} - → - - } /> {lolLiveGame && ( @@ -407,6 +364,17 @@ export default async function NowPage() { Nenhum jogo para mostrar.

    )} + +
    + + {steam.games.length > 0 ? 'Ver no Steam' : 'Steam fica aqui'} → + +
    diff --git a/src/features/now/components/FeaturedTrack.tsx b/src/features/now/components/FeaturedTrack.tsx deleted file mode 100644 index a876bf8..0000000 --- a/src/features/now/components/FeaturedTrack.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; - -import { faSpotify } from '@fortawesome/free-brands-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; - -import { Card } from '@/base/components/Card'; -import { MusicFallback } from '@/features/now/components/MusicFallback'; -import { LastfmTrack } from '@/types/Lastfm'; - -export const FeaturedTrack = ({ track }: { track: LastfmTrack }) => { - const spotifyHref = - track.imageSource === 'spotify' && track.spotifyUrl - ? track.spotifyUrl - : null; - - return ( - - - {track.imageUrl ? ( - {track.album - ) : ( - - )} - -
    - - Faixa mais tocada - -

    - - {track.name} - -

    -

    - {track.artist} -

    - {track.album ? ( -

    - {track.album} -

    - ) : null} - {track.playcount ? ( -

    - {track.playcount} plays no mês -

    - ) : null} - {track.imageSource === 'spotify' ? ( -

    - - Capa via Spotify -

    - ) : null} -
    -
    - ); -}; diff --git a/src/features/now/components/index.ts b/src/features/now/components/index.ts index df1b42e..efe80ab 100644 --- a/src/features/now/components/index.ts +++ b/src/features/now/components/index.ts @@ -2,7 +2,6 @@ export { ActivityFeed } from './ActivityFeed'; export { ArtistCard } from './ArtistCard'; export { CardTooltip } from './CardTooltip'; export { CodingRhythm } from './CodingRhythm'; -export { FeaturedTrack } from './FeaturedTrack'; export { GameCard } from './GameCard'; export { LanguageStack } from './LanguageStack'; export { LolChampionCard } from './LolChampionCard'; From 6fbb1ca2c6bca5d41745af880f432695c0dd6c20 Mon Sep 17 00:00:00 2001 From: John Enderson Date: Wed, 26 Aug 2026 23:39:46 -0300 Subject: [PATCH 7/8] fix(now): remove esticamento da lista de faixas recentes O ul usava flex-1 justify-between, esticando os itens pra preencher a altura da coluna ao lado (grid de artistas), o que deixava os espacamentos desiguais e largos demais depois que o card de destaque saiu. Troca pra gap-1 fixo, igual ao padrao ja usado em ActivityFeed e StarredRepos. Co-Authored-By: Claude Sonnet 5 --- app/now/page.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/app/now/page.tsx b/app/now/page.tsx index 336c09e..a9af90a 100644 --- a/app/now/page.tsx +++ b/app/now/page.tsx @@ -252,10 +252,7 @@ export default async function NowPage() { />
    -
    +

    {recentTracks.length > 0 ? ( -
      +
        {recentTracks.slice(0, 5).map((track, index) => ( Date: Wed, 26 Aug 2026 23:43:25 -0300 Subject: [PATCH 8/8] feat(now): mostra a faixa atual e o horario de cada uma na lista Ja buscavamos lastfm.nowPlaying mas nunca usavamos na pagina /agora. Agora a lista mescla atual + ultimas tocadas (ate 7 no total, igual ao limite ja usado na API), com o selo Ouvindo agora + ponto pulsante na faixa ao vivo, e o horario (ou dia + horario) nas demais - igual ao doce.sh. Extrai o LiveDot do NowPlayingBadge pra um componente compartilhado em src/base/components, ja que agora e usado em dois lugares. Co-Authored-By: Claude Sonnet 5 --- app/now/page.tsx | 6 ++-- src/base/components/LiveDot.tsx | 7 +++++ .../components/Navbar/NowPlayingBadge.tsx | 8 +----- src/features/now/components/RecentTrack.tsx | 28 +++++++++++++++++++ 4 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 src/base/components/LiveDot.tsx diff --git a/app/now/page.tsx b/app/now/page.tsx index a9af90a..7ab9bcb 100644 --- a/app/now/page.tsx +++ b/app/now/page.tsx @@ -102,7 +102,9 @@ type LastfmData = { const computeTrackData = (lastfm: LastfmData) => getUniqueTracks( - [lastfm.lastPlayed, ...lastfm.tracks].filter(Boolean) as LastfmTrack[], + [lastfm.nowPlaying, lastfm.lastPlayed, ...lastfm.tracks].filter( + Boolean, + ) as LastfmTrack[], ); const getUniqueTracks = (tracks: LastfmTrack[]) => { @@ -261,7 +263,7 @@ export default async function NowPage() {

    {recentTracks.length > 0 ? (
      - {recentTracks.slice(0, 5).map((track, index) => ( + {recentTracks.slice(0, 7).map((track, index) => ( ( +
    +
    + {track.nowPlaying ? ( + <> + Ouvindo agora + + + ) : track.playedAt ? ( + {formatPlayedAt(track.playedAt)} + ) : null} +
    );