-
- );
-};
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 ? (
+
-
- );
-};
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) => (
(
+
+);
diff --git a/src/base/components/Navbar/NowPlayingBadge.tsx b/src/base/components/Navbar/NowPlayingBadge.tsx
index 75adc9e..a88b301 100644
--- a/src/base/components/Navbar/NowPlayingBadge.tsx
+++ b/src/base/components/Navbar/NowPlayingBadge.tsx
@@ -6,6 +6,7 @@ import { useState } from 'react';
import useSWR from 'swr';
+import { LiveDot } from '@/base/components/LiveDot';
import { LastfmTrack } from '@/types/Lastfm';
type NowPlayingResponse = {
@@ -49,13 +50,6 @@ const NowPlayingArt = ({
);
};
-const LiveDot = ({ className = '' }: { className?: string }) => (
-
-);
-
type NowPlayingBadgeProps = {
/** 'full' = capa + faixa + artista (desktop). 'compact' = só a capa (mobile). */
variant: 'full' | 'compact';
diff --git a/src/features/now/components/RecentTrack.tsx b/src/features/now/components/RecentTrack.tsx
index be7fe3a..707e8e7 100644
--- a/src/features/now/components/RecentTrack.tsx
+++ b/src/features/now/components/RecentTrack.tsx
@@ -1,9 +1,27 @@
import Image from 'next/image';
import Link from 'next/link';
+import { LiveDot } from '@/base/components/LiveDot';
import { MusicFallback } from '@/features/now/components/MusicFallback';
import { LastfmTrack } from '@/types/Lastfm';
+/** "23:35" se foi hoje, "26/08 23:35" caso contrário. */
+const formatPlayedAt = (playedAt: string) => {
+ const date = new Date(playedAt);
+ const time = date.toLocaleTimeString('pt-BR', {
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+
+ if (date.toDateString() === new Date().toDateString()) return time;
+
+ const day = date.toLocaleDateString('pt-BR', {
+ day: 'numeric',
+ month: 'numeric',
+ });
+ return `${day} ${time}`;
+};
+
const TrackArtwork = ({
track,
priority = false,
@@ -52,6 +70,16 @@ export const RecentTrack = ({
{track.artist}