feat: portfolio bilíngue (PT/EN) — Next.js 16 + Tailwind v4

- Hero com terminal animado e identidade de engenheiro de agentes de IA
- 10 projetos curados (destaques: Mika e Agentes de IA com métricas)
- Skills, contato, dark mode, SEO/OG por locale
- Rotas /pt e /en com redirect por Accept-Language
This commit is contained in:
Felipe Domingues 2026-07-21 01:47:54 -03:00
commit be348f70f6
32 changed files with 8631 additions and 0 deletions

View file

@ -0,0 +1,99 @@
"use client";
import { useEffect, useState } from "react";
import type { TermLine } from "@/i18n/dictionaries/en";
const TYPE_SPEED = 26;
const LINE_PAUSE = 320;
export default function Terminal({ title, lines }: { title: string; lines: TermLine[] }) {
const [step, setStep] = useState(0);
const [chars, setChars] = useState(0);
useEffect(() => {
if (step >= lines.length) return;
const line = lines[step];
if (line.kind === "cmd") {
if (chars < line.text.length) {
const id = setTimeout(() => setChars((c) => c + 1), TYPE_SPEED);
return () => clearTimeout(id);
}
const id = setTimeout(() => {
setStep((s) => s + 1);
setChars(0);
}, LINE_PAUSE);
return () => clearTimeout(id);
}
const id = setTimeout(() => setStep((s) => s + 1), LINE_PAUSE);
return () => clearTimeout(id);
}, [step, chars, lines]);
const done = step >= lines.length;
return (
<div className="overflow-hidden rounded-xl border border-[var(--term-line)] bg-[var(--term-bg)] shadow-2xl shadow-black/40">
<div className="flex items-center gap-2 border-b border-[var(--term-line)] px-4 py-3">
<span className="size-3 rounded-full bg-[#ff5f57]" />
<span className="size-3 rounded-full bg-[#febc2e]" />
<span className="size-3 rounded-full bg-[#28c840]" />
<span className="ml-3 font-mono text-xs text-[#7d8da1]">{title}</span>
</div>
<div className="min-h-[264px] p-5 font-mono text-[13px] leading-7">
{lines.slice(0, step).map((line, i) => (
<TermLineRow key={i} line={line} />
))}
{!done && lines[step] && (
<TermLineRow
line={{
...lines[step],
text: lines[step].text.slice(0, lines[step].kind === "cmd" ? chars : lines[step].text.length),
}}
cursor
/>
)}
{done && (
<p className="text-[#e9eef5]">
<Prompt />
<span className="ml-2 inline-block h-4 w-2 translate-y-[3px] animate-blink bg-[#f5a524]" />
</p>
)}
</div>
</div>
);
}
function Prompt() {
return <span className="font-semibold text-[#f5a524]">$</span>;
}
function TermLineRow({ line, cursor }: { line: TermLine; cursor?: boolean }) {
const cursorEl = cursor && (
<span className="ml-0.5 inline-block h-4 w-2 translate-y-[3px] animate-blink bg-[#f5a524]" />
);
if (line.kind === "cmd") {
return (
<p className="text-[#e9eef5]">
<Prompt />
<span className="ml-2">{line.text}</span>
{cursorEl}
</p>
);
}
if (line.kind === "ok") {
return (
<p className="text-[#7d8da1]">
<span className="font-semibold text-[#3ecf8e]"></span>
<span className="ml-2">{line.text}</span>
{cursorEl}
</p>
);
}
return (
<p className="text-[#7d8da1]">
<span className="font-semibold text-[#5ec8f2]"></span>
<span className="ml-2">{line.text}</span>
{cursorEl}
</p>
);
}