feat(home): trocar o texto fixo de projetos publicados pela contagem real do GitHub
O hero mostra 'Mais de X projetos desenvolvidos' com o detalhamento de publicos e privados; sem a contagem (token ausente ou API fora) o item some. A home passa a revalidar uma vez por dia. README documenta o token. Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
4ba955e2c3
commit
0350e7e883
4 files changed
+76
-11
No files matched your search
+42
-9
@@ -17,20 +17,43 @@ const SOCIAL_LINKS = [
|
||||
},
|
||||
];
|
||||
|
||||
/** Números reais, calculados em app/page.tsx a partir de public/portfolio-data.json (nada digitado à mão). */
|
||||
/**
|
||||
* Números reais, calculados em app/page.tsx (nada digitado à mão): anos e demos vêm de public/portfolio-data.json;
|
||||
* repos vem da API do GitHub (lib/github-stats.ts) e some quando a consulta não está disponível.
|
||||
*/
|
||||
export type HeroStats = {
|
||||
years?: number;
|
||||
projects?: number;
|
||||
repos?: { total: number; public: number; private: number };
|
||||
demos?: number;
|
||||
};
|
||||
|
||||
function statItems(stats?: HeroStats) {
|
||||
type StatItem = {
|
||||
value: string;
|
||||
label: string;
|
||||
prefix?: string;
|
||||
detail?: string;
|
||||
};
|
||||
|
||||
function statItems(stats?: HeroStats): StatItem[] {
|
||||
if (!stats) return [];
|
||||
return [
|
||||
stats.years ? [String(stats.years), "anos de experiência"] : null,
|
||||
stats.projects ? [String(stats.projects), "projetos publicados"] : null,
|
||||
stats.demos ? [String(stats.demos), "com demonstração online"] : null,
|
||||
].filter((item): item is [string, string] => item !== null);
|
||||
const { repos } = stats;
|
||||
const items: Array<StatItem | null> = [
|
||||
stats.years
|
||||
? { value: String(stats.years), label: "anos de experiência" }
|
||||
: null,
|
||||
repos && repos.total > 0
|
||||
? {
|
||||
prefix: "Mais de",
|
||||
value: String(repos.total),
|
||||
label: "projetos desenvolvidos",
|
||||
detail: `${repos.public} públicos · ${repos.private} privados`,
|
||||
}
|
||||
: null,
|
||||
stats.demos
|
||||
? { value: String(stats.demos), label: "com demonstração online" }
|
||||
: null,
|
||||
];
|
||||
return items.filter((item): item is StatItem => item !== null);
|
||||
}
|
||||
|
||||
/** Título em palavras para a entrada em cascata; o texto segue inteiro para leitores de tela e buscadores. */
|
||||
@@ -125,12 +148,22 @@ export default function Hero({ stats }: { stats?: HeroStats }) {
|
||||
<div className="flex flex-wrap items-end gap-x-sp-8 gap-y-sp-4 border-t border-white/10 pt-sp-4">
|
||||
{items.length > 0 && (
|
||||
<dl className="flex gap-x-sp-5 gap-y-sp-4 sm:flex-wrap sm:gap-x-sp-8">
|
||||
{items.map(([value, label]) => (
|
||||
{items.map(({ prefix, value, label, detail }) => (
|
||||
<div key={label} className="flex flex-col-reverse">
|
||||
<dt className="text-[13px] leading-[1.4] text-[rgba(244,242,237,.64)]">
|
||||
{label}
|
||||
{detail && (
|
||||
<span className="block text-[12px] text-[rgba(244,242,237,.55)]">
|
||||
{detail}
|
||||
</span>
|
||||
)}
|
||||
</dt>
|
||||
<dd className="font-mono text-[clamp(22px,1.1rem+0.8vw,36px)] font-semibold leading-none tracking-[-0.03em] text-[var(--slash)]">
|
||||
{prefix && (
|
||||
<span className="mr-sp-2 text-[13px] font-normal tracking-normal text-[rgba(244,242,237,.64)]">
|
||||
{prefix}
|
||||
</span>
|
||||
)}
|
||||
{value}
|
||||
</dd>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user