Antropousia: os futuros não são um tempo ou um lugar, são uma condição
Publicado em 16 de fevereiro de 2026

Vou construir isso completo pra você. Segura aí que vai sair tudo.
---
## 1. ÁRVORE DE ARQUIVOS
```
augusto-site/
├── public/
│ ├── og-image.png # placeholder 1200x630
│ └── robots.txt
├── src/
│ ├── app/
│ │ ├── layout.tsx # Root layout + metadata
│ │ ├── page.tsx # Home
│ │ ├── globals.css
│ │ ├── about/
│ │ │ └── page.tsx
│ │ ├── work/
│ │ │ └── page.tsx
│ │ ├── speaking/
│ │ │ └── page.tsx
│ │ ├── writing/
│ │ │ └── page.tsx
│ │ └── contact/
│ │ └── page.tsx
│ ├── components/
│ │ ├── Header.tsx
│ │ ├── Footer.tsx
│ │ ├── Container.tsx
│ │ ├── Section.tsx
│ │ ├── Button.tsx
│ │ ├── CustomLink.tsx
│ │ ├── Card.tsx
│ │ ├── SignalsCanvas.tsx
│ │ └── ContactForm.tsx
│ ├── content/
│ │ └── content.ts # Single source of truth
│ └── lib/
│ ├── metadata.ts
│ └── fonts.ts
├── next.config.mjs
├── tailwind.config.ts
├── tsconfig.json
├── package.json
├── postcss.config.mjs
└── .eslintrc.json
```
---
## 2. CÓDIGO COMPLETO
### `package.json`
```json
{
"name": "augusto-site",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^14.2.0",
"react": "^18.3.0",
"react-dom": "^18.3.0"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"autoprefixer": "^10.4.19",
"eslint": "^8",
"eslint-config-next": "^14.2.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
"typescript": "^5"
}
}
```
---
### `tsconfig.json`
```json
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
```
---
### `next.config.mjs`
```js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
poweredByHeader: false,
};
export default nextConfig;
```
---
### `postcss.config.mjs`
```js
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
export default config;
```
---
### `tailwind.config.ts`
```ts
import type { Config } from "tailwindcss";
const config: Config = {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
colors: {
background: "#FEFEFE",
offwhite: "#FAFAFA",
foreground: "#0A0A0A",
muted: "#6B6B6B",
signal: "#4A9EFF",
},
fontFamily: {
sans: ["var(--font-inter)", "system-ui", "sans-serif"],
serif: ["var(--font-fraunces)", "Georgia", "serif"],
},
transitionDuration: {
"250": "250ms",
},
},
},
plugins: [],
};
export default config;
```
---
### `.eslintrc.json`
```json
{
"extends": ["next/core-web-vitals", "next/typescript"],
"rules": {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }]
}
}
```
---
### `public/robots.txt`
```txt
User-agent: *
Allow: /
Sitemap: https://yoursite.com/sitemap.xml
```
---
### `src/lib/fonts.ts`
```ts
import { Fraunces, Inter } from "next/font/google";
export const inter = Inter({
subsets: ["latin"],
variable: "--font-inter",
display: "swap",
});
export const fraunces = Fraunces({
subsets: ["latin"],
variable: "--font-fraunces",
display: "swap",
});
```
---
### `src/lib/metadata.ts`
```ts
import { Metadata } from "next";
import { content } from "@/content/content";
interface PageMetadataParams {
title?: string;
description?: string;
path?: string;
}
export function generateMetadata({
title,
description,
path = "",
}: PageMetadataParams = {}): Metadata {
const fullTitle = title ? `${title} — ${content.siteName}` : content.siteName;
const desc = description || content.seo.description;
const url = `${content.seo.siteUrl}${path}`;
const ogImage = `${content.seo.siteUrl}${content.seo.ogImage}`;
return {
title: fullTitle,
description: desc,
metadataBase: new URL(content.seo.siteUrl),
openGraph: {
type: "website",
url,
title: fullTitle,
description: desc,
siteName: content.siteName,
images: [
{
url: ogImage,
width: 1200,
height: 630,
alt: content.siteName,
},
],
},
twitter: {
card: "summary_large_image",
title: fullTitle,
description: desc,
images: [ogImage],
},
robots: {
index: true,
follow: true,
},
};
}
```
---
### `src/content/content.ts`
```ts
export const content = {
siteName: "Your Name",
tagline: "Building thoughtful products and experiences",
shortBio:
"A placeholder bio that captures what you do in one or two sentences. Replace this with your actual story.",
// Navigation
nav: [
{ label: "About", href: "/about" },
{ label: "Work", href: "/work" },
{ label: "Speaking", href: "/speaking" },
{ label: "Writing", href: "/writing" },
{ label: "Contact", href: "/contact" },
],
// Social links
social: {
twitter: "https://twitter.com/placeholder",
linkedin: "https://linkedin.com/in/placeholder",
github: "https://github.com/placeholder",
email: "[email protected]",
},
// SEO
seo: {
siteUrl: "https://yoursite.com",
description:
"Placeholder description for search engines and social shares. Make it compelling.",
ogImage: "/og-image.png",
},
// Home page sections
home: {
hero: {
title: "Building thoughtful products and experiences",
subtitle:
"A placeholder subtitle that elaborates on what you do and why it matters. Keep it human and clear.",
cta1: { label: "View work", href: "/work" },
cta2: { label: "Get in touch", href: "/contact" },
},
what: {
title: "What I do",
items: [
{
title: "Product Strategy",
description: "Placeholder for service/skill description.",
},
{
title: "Design Systems",
description: "Placeholder for service/skill description.",
},
{
title: "Technical Leadership",
description: "Placeholder for service/skill description.",
},
],
},
proof: {
title: "Trusted by",
logos: [
"Company A",
"Company B",
"Company C",
"Company D",
"Company E",
],
},
library: {
title: "From the library",
items: [
{
title: "Placeholder Article Title",
type: "Article",
href: "/writing/placeholder-1",
},
{
title: "Another Placeholder Title",
type: "Talk",
href: "/speaking",
},
{
title: "Third Placeholder Title",
type: "Essay",
href: "/writing/placeholder-2",
},
],
},
finalCta: {
title: "Let's build something together",
description:
"Placeholder call to action description. Invite collaboration or conversation.",
cta: { label: "Start a conversation", href: "/contact" },
},
},
// About page
about: {
title: "About",
intro:
"Placeholder intro paragraph. Tell your story, what drives you, what you care about.",
paragraphs: [
"First placeholder paragraph. Could be about your background, journey, or philosophy.",
"Second placeholder paragraph. Maybe dive into your approach, values, or current focus.",
"Third placeholder paragraph. Perhaps where you're headed or what excites you now.",
],
},
// Work page
work: {
title: "Selected Work",
intro:
"Placeholder intro for your work section. Frame what kind of projects you showcase here.",
projects: [
{
title: "Project Alpha",
description: "Placeholder project description. What was built and why.",
year: "2024",
tags: ["Strategy", "Design", "Development"],
},
{
title: "Project Beta",
description: "Another placeholder project description.",
year: "2023",
tags: ["Research", "Product"],
},
{
title: "Project Gamma",
description: "Third placeholder project description.",
year: "2023",
tags: ["Leadership", "Systems"],
},
],
},
// Speaking page
speaking: {
title: "Speaking",
intro:
"Placeholder intro about your speaking experience, topics, or philosophy on sharing knowledge.",
talks: [
{
title: "Placeholder Talk Title",
event: "Conference Name 2024",
description: "Brief description of the talk topic and key insights.",
},
{
title: "Another Talk Title",
event: "Event Name 2023",
description: "Another placeholder talk description.",
},
],
},
// Writing page
writing: {
title: "Writing",
intro:
"Placeholder intro about your writing, themes you explore, or why you write.",
posts: [
{
title: "Placeholder Essay Title",
date: "2024-01-15",
excerpt: "A brief excerpt or summary of this piece. What's the core idea?",
slug: "placeholder-1",
},
{
title: "Another Essay Title",
date: "2023-12-10",
excerpt: "Another placeholder excerpt.",
slug: "placeholder-2",
},
],
},
// Contact page
contact: {
title: "Get in touch",
intro:
"Placeholder intro. Invite people to reach out, explain what kinds of conversations you're open to.",
emailLabel: "Email",
messageLabel: "Message",
submitLabel: "Send message",
successMessage: "Message sent! (This is just a placeholder validation.)",
},
// Footer
footer: {
copyright: `© ${new Date().getFullYear()} Your Name. All rights reserved.`,
builtwith: "Built with Next.js, TypeScript, and care.",
},
};
```
---
### `src/app/globals.css`
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
html {
@apply scroll-smooth;
font-feature-settings: "kern" 1, "liga" 1;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
@apply bg-background text-foreground font-sans;
}
/* Focus styles */
:focus-visible {
@apply outline-none ring-2 ring-foreground ring-offset-2 ring-offset-background;
}
/* Selection */
::selection {
@apply bg-signal/20;
}
/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
}
```
---
### `src/app/layout.tsx`
```tsx
import type { Metadata } from "next";
import { inter, fraunces } from "@/lib/fonts";
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import SignalsCanvas from "@/components/SignalsCanvas";
import "./globals.css";
export const metadata: Metadata = generateMetadata();
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
// JSON-LD Structured Data
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Person",
name: content.siteName,
description: content.shortBio,
url: content.seo.siteUrl,
sameAs: [
content.social.twitter,
content.social.linkedin,
content.social.github,
],
},
{
"@type": "WebSite",
name: content.siteName,
url: content.seo.siteUrl,
description: content.seo.description,
},
],
};
return (
<html lang="en" className={`${inter.variable} ${fraunces.variable}`}>
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body>
<a
href="#main"
className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-50 focus:px-4 focus:py-2 focus:bg-foreground focus:text-background"
>
Skip to content
</a>
<SignalsCanvas />
<Header />
<main id="main">{children}</main>
<Footer />
</body>
</html>
);
}
```
---
### `src/app/page.tsx`
```tsx
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
import Button from "@/components/Button";
import Card from "@/components/Card";
import CustomLink from "@/components/CustomLink";
export default function Home() {
const { hero, what, proof, library, finalCta } = content.home;
return (
<>
{/* Hero */}
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<div className="max-w-3xl">
<h1 className="text-5xl md:text-7xl font-serif font-light tracking-tight mb-6">
{hero.title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-10 leading-relaxed">
{hero.subtitle}
</p>
<div className="flex flex-wrap gap-4">
<Button href={hero.cta1.href} variant="primary">
{hero.cta1.label}
</Button>
<Button href={hero.cta2.href} variant="secondary">
{hero.cta2.label}
</Button>
</div>
</div>
</Container>
</Section>
{/* What I do */}
<Section className="py-20 md:py-28 bg-offwhite">
<Container>
<h2 className="text-3xl md:text-4xl font-serif font-light mb-12">
{what.title}
</h2>
<div className="grid md:grid-cols-3 gap-8">
{what.items.map((item, i) => (
<Card key={i}>
<h3 className="text-xl font-medium mb-3">{item.title}</h3>
<p className="text-muted leading-relaxed">{item.description}</p>
</Card>
))}
</div>
</Container>
</Section>
{/* Proof */}
<Section className="py-20 md:py-28">
<Container>
<h2 className="text-2xl md:text-3xl font-serif font-light mb-12 text-center">
{proof.title}
</h2>
<div className="flex flex-wrap justify-center items-center gap-x-12 gap-y-8">
{proof.logos.map((logo, i) => (
<div key={i} className="text-muted text-sm font-medium opacity-60">
{logo}
</div>
))}
</div>
</Container>
</Section>
{/* Library */}
<Section className="py-20 md:py-28 bg-offwhite">
<Container>
<h2 className="text-3xl md:text-4xl font-serif font-light mb-12">
{library.title}
</h2>
<div className="space-y-6">
{library.items.map((item, i) => (
<CustomLink
key={i}
href={item.href}
className="block group border-b border-foreground/10 pb-6 hover:border-foreground/30 transition-colors"
>
<div className="flex items-baseline justify-between gap-4">
<h3 className="text-xl font-medium group-hover:text-signal transition-colors">
{item.title}
</h3>
<span className="text-sm text-muted shrink-0">{item.type}</span>
</div>
</CustomLink>
))}
</div>
</Container>
</Section>
{/* Final CTA */}
<Section className="py-20 md:py-32">
<Container>
<div className="max-w-2xl mx-auto text-center">
<h2 className="text-4xl md:text-5xl font-serif font-light mb-6">
{finalCta.title}
</h2>
<p className="text-xl text-muted mb-10 leading-relaxed">
{finalCta.description}
</p>
<Button href={finalCta.cta.href} variant="primary">
{finalCta.cta.label}
</Button>
</div>
</Container>
</Section>
</>
);
}
```
---
### `src/app/about/page.tsx`
```tsx
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
export const metadata = generateMetadata({
title: content.about.title,
description: content.about.intro,
path: "/about",
});
export default function AboutPage() {
const { title, intro, paragraphs } = content.about;
return (
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<div className="max-w-2xl">
<h1 className="text-5xl md:text-6xl font-serif font-light mb-8">
{title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-12 leading-relaxed">
{intro}
</p>
<div className="space-y-6 text-lg leading-relaxed">
{paragraphs.map((p, i) => (
<p key={i}>{p}</p>
))}
</div>
</div>
</Container>
</Section>
);
}
```
---
### `src/app/work/page.tsx`
```tsx
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
import Card from "@/components/Card";
export const metadata = generateMetadata({
title: content.work.title,
description: content.work.intro,
path: "/work",
});
export default function WorkPage() {
const { title, intro, projects } = content.work;
return (
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<h1 className="text-5xl md:text-6xl font-serif font-light mb-8">
{title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-16 max-w-2xl leading-relaxed">
{intro}
</p>
<div className="space-y-12">
{projects.map((project, i) => (
<Card key={i} className="p-8">
<div className="flex items-start justify-between gap-6 mb-4">
<h2 className="text-2xl font-medium">{project.title}</h2>
<span className="text-sm text-muted shrink-0">{project.year}</span>
</div>
<p className="text-muted mb-4 leading-relaxed">
{project.description}
</p>
<div className="flex flex-wrap gap-2">
{project.tags.map((tag, j) => (
<span
key={j}
className="text-xs px-3 py-1 bg-offwhite border border-foreground/10 rounded-full"
>
{tag}
</span>
))}
</div>
</Card>
))}
</div>
</Container>
</Section>
);
}
```
---
### `src/app/speaking/page.tsx`
```tsx
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
import Card from "@/components/Card";
export const metadata = generateMetadata({
title: content.speaking.title,
description: content.speaking.intro,
path: "/speaking",
});
export default function SpeakingPage() {
const { title, intro, talks } = content.speaking;
return (
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<h1 className="text-5xl md:text-6xl font-serif font-light mb-8">
{title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-16 max-w-2xl leading-relaxed">
{intro}
</p>
<div className="space-y-8">
{talks.map((talk, i) => (
<Card key={i}>
<h2 className="text-xl font-medium mb-2">{talk.title}</h2>
<p className="text-sm text-signal mb-3">{talk.event}</p>
<p className="text-muted leading-relaxed">{talk.description}</p>
</Card>
))}
</div>
</Container>
</Section>
);
}
```
---
### `src/app/writing/page.tsx`
```tsx
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
import CustomLink from "@/components/CustomLink";
export const metadata = generateMetadata({
title: content.writing.title,
description: content.writing.intro,
path: "/writing",
});
export default function WritingPage() {
const { title, intro, posts } = content.writing;
return (
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<h1 className="text-5xl md:text-6xl font-serif font-light mb-8">
{title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-16 max-w-2xl leading-relaxed">
{intro}
</p>
<div className="space-y-10">
{posts.map((post) => (
<article key={post.slug}>
<CustomLink
href={`/writing/${post.slug}`}
className="block group"
>
<time className="text-sm text-muted block mb-2">{post.date}</time>
<h2 className="text-2xl font-medium mb-3 group-hover:text-signal transition-colors">
{post.title}
</h2>
<p className="text-muted leading-relaxed">{post.excerpt}</p>
</CustomLink>
</article>
))}
</div>
</Container>
</Section>
);
}
```
---
### `src/app/contact/page.tsx`
```tsx
import { generateMetadata } from "@/lib/metadata";
import { content } from "@/content/content";
import Container from "@/components/Container";
import Section from "@/components/Section";
import ContactForm from "@/components/ContactForm";
export const metadata = generateMetadata({
title: content.contact.title,
description: content.contact.intro,
path: "/contact",
});
export default function ContactPage() {
const { title, intro } = content.contact;
return (
<Section className="pt-32 pb-20 md:pt-40 md:pb-28">
<Container>
<div className="max-w-2xl mx-auto">
<h1 className="text-5xl md:text-6xl font-serif font-light mb-8">
{title}
</h1>
<p className="text-xl md:text-2xl text-muted mb-12 leading-relaxed">
{intro}
</p>
<ContactForm />
</div>
</Container>
</Section>
);
}
```
---
### `src/components/Header.tsx`
```tsx
"use client";
import { useState, useEffect } from "react";
import Link from "next/link";
import { content } from "@/content/content";
import Container from "./Container";
export default function Header() {
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll, { passive: true });
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return (
<header
className={`fixed top-0 left-0 right-0 z-40 transition-all duration-250 ${
scrolled
? "bg-background/80 backdrop-blur-md border-b border-foreground/10"
: "bg-transparent"
}`}
>
<Container>
<nav className="flex items-center justify-between h-16" aria-label="Main navigation">
<Link
href="/"
className="text-lg font-medium hover:text-signal transition-colors"
>
{content.siteName}
</Link>
<ul className="flex items-center gap-6 md:gap-8">
{content.nav.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className="text-sm hover:text-signal transition-colors"
>
{item.label}
</Link>
</li>
))}
</ul>
</nav>
</Container>
</header>
);
}
```
---
### `src/components/Footer.tsx`
```tsx
import { content } from "@/content/content";
import Container from "./Container";
import CustomLink from "./CustomLink";
export default function Footer() {
return (
<footer className="border-t border-foreground/10 py-12 md:py-16">
<Container>
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-6">
<div>
<p className="text-sm text-muted mb-2">{content.footer.copyright}</p>
<p className="text-xs text-muted">{content.footer.builtwith}</p>
</div>
<div className="flex gap-6">
{content.social.twitter && (
<CustomLink
href={content.social.twitter}
className="text-sm text-muted hover:text-foreground transition-colors"
external
>
Twitter
</CustomLink>
)}
{content.social.linkedin && (
<CustomLink
href={content.social.linkedin}
className="text-sm text-muted hover:text-foreground transition-colors"
external
>
LinkedIn
</CustomLink>
)}
{content.social.github && (
<CustomLink
href={content.social.github}
className="text-sm text-muted hover:text-foreground transition-colors"
external
>
GitHub
</CustomLink>
)}
</div>
</div>
</Container>
</footer>
);
}
```
---
### `src/components/Container.tsx`
```tsx
interface ContainerProps {
children: React.ReactNode;
className?: string;
}
export default function Container({ children, className = "" }: ContainerProps) {
return (
<div className={`max-w-6xl mx-auto px-6 md:px-8 ${className}`}>
{children}
</div>
);
}
```
---
### `src/components/Section.tsx`
```tsx
interface SectionProps {
children: React.ReactNode;
className?: string;
}
export default function Section({ children, className = "" }: SectionProps) {
return <section className={className}>{children}</section>;
}
```
---
### `src/components/Button.tsx`
```tsx
import Link from "next/link";
interface ButtonProps {
children: React.ReactNode;
href: string;
variant?: "primary" | "secondary";
className?: string;
}
export default function Button({
children,
href,
variant = "primary",
className = "",
}: ButtonProps) {
const baseStyles =
"inline-block px-6 py-3 rounded-sm text-sm font-medium transition-all duration-250";
const variantStyles = {
primary:
"bg-foreground text-background hover:bg-foreground/90 hover:scale-105",
secondary:
"bg-transparent text-foreground border border-foreground hover:bg-foreground hover:text-background",
};
return (
<Link
href={href}
className={`${baseStyles} ${variantStyles[variant]} ${className}`}
>
{children}
</Link>
);
}
```
---
### `src/components/CustomLink.tsx`
```tsx
import Link from "next/link";
interface CustomLinkProps {
children: React.ReactNode;
href: string;
className?: string;
external?: boolean;
}
export default function CustomLink({
children,
href,
className = "",
external = false,
}: CustomLinkProps) {
const externalProps = external
? { target: "_blank", rel: "noopener noreferrer" }
: {};
return (
<Link href={href} className={className} {...externalProps}>
{children}
</Link>
);
}
```
---
### `src/components/Card.tsx`
```tsx
interface CardProps {
children: React.ReactNode;
className?: string;
}
export default function Card({ children, className = "" }: CardProps) {
return (
<div
className={`bg-background border border-foreground/10 rounded-sm p-6 hover:border-foreground/20 transition-colors ${className}`}
>
{children}
</div>
);
}
```
---
### `src/components/ContactForm.tsx`
```tsx
"use client";
import { useState, FormEvent } from "react";
import { content } from "@/content/content";
export default function ContactForm() {
const [email, setEmail] = useState("");
const [message, setMessage] = useState("");
const [success, setSuccess] = useState(false);
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
// Basic validation
if (!email || !message) return;
// Placeholder: no real submission
setSuccess(true);
setEmail("");
setMessage("");
setTimeout(() => setSuccess(false), 5000);
};
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div>
<label htmlFor="email" className="block text-sm font-medium mb-2">
{content.contact.emailLabel}
</label>
<input
type="email"
id="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full px-4 py-3 border border-foreground/20 rounded-sm bg-background focus:border-foreground focus:outline-none transition-colors"
/>
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium mb-2">
{content.contact.messageLabel}
</label>
<textarea
id="message"
value={message}
onChange={(e) => setMessage(e.target.value)}
required
rows={6}
className="w-full px-4 py-3 border border-foreground/20 rounded-sm bg-background focus:border-foreground focus:outline-none transition-colors resize-none"
/>
</div>
<button
type="submit"
className="px-6 py-3 bg-foreground text-background rounded-sm font-medium hover:bg-foreground/90 transition-all duration-250 hover:scale-105"
>
{content.contact.submitLabel}
</button>
{success && (
<p className="text-signal text-sm" role="status">
{content.contact.successMessage}
</p>
)}
</form>
);
}
```
---
### `src/components/SignalsCanvas.tsx`
```tsx
"use client";
import { useEffect, useRef } from "react";
interface Point {
x: number;
y: number;
timestamp: number;
alpha: number;
}
export default function SignalsCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null);
const pointsRef = useRef<Point[]>([]);
const animationFrameRef = useRef<number>(0);
const lastSpawnRef = useRef<number>(0);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d", { alpha: true });
if (!ctx) return;
// Check for reduced motion
const prefersReducedMotion = window.matchMedia(
"(prefers-reduced-motion: reduce)"
).matches;
if (prefersReducedMotion) return;
// Check if mobile (disable on mobile to save battery)
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) return;
// Setup canvas with DPR
const resize = () => {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width * dpr;
canvas.height = rect.height * dpr;
ctx.scale(dpr, dpr);
canvas.style.width = `${rect.width}px`;
canvas.style.height = `${rect.height}px`;
};
resize();
window.addEventListener("resize", resize);
// Throttled mouse move
let lastMouseMove = 0;
const handleMouseMove = (e: MouseEvent) => {
const now = Date.now();
if (now - lastMouseMove < 150) return; // Throttle 150ms
lastMouseMove = now;
// 40% chance to spawn
if (Math.random() > 0.4) return;
spawnPoint(e.clientX, e.clientY);
};
// Click handler
const handleClick = (e: MouseEvent) => {
const target = e.target as HTMLElement;
if (
target.tagName === "A" ||
target.tagName === "BUTTON" ||
target.closest("a") ||
target.closest("button")
) {
createFlash(e.clientX, e.clientY);
}
};
const spawnPoint = (x: number, y: number) => {
const now = Date.now();
if (now - lastSpawnRef.current < 100) return; // Prevent spam
lastSpawnRef.current = now;
const newPoint: Point = {
x,
y,
timestamp: now,
alpha: 0.3 + Math.random() * 0.2,
};
pointsRef.current.push(newPoint);
// Limit to 60 points
if (pointsRef.current.length > 60) {
pointsRef.current.shift();
}
};
const createFlash = (x: number, y: number) => {
const now = Date.now();
// Create 2-3 points around click
for (let i = 0; i < 3; i++) {
const offsetX = (Math.random() - 0.5) * 40;
const offsetY = (Math.random() - 0.5) * 40;
pointsRef.current.push({
x: x + offsetX,
y: y + offsetY,
timestamp: now - i * 50,
alpha: 0.4,
});
}
};
const animate = () => {
const now = Date.now();
const rect = canvas.getBoundingClientRect();
ctx.clearRect(0, 0, rect.width, rect.height);
// Filter out old points (>900ms)
pointsRef.current = pointsRef.current.filter(
(p) => now - p.timestamp < 900
);
// Draw connections first (behind points)
ctx.strokeStyle = "rgba(74, 158, 255, 0.15)";
ctx.lineWidth = 0.5;
pointsRef.current.forEach((point, i) => {
const age = now - point.timestamp;
const fadeAlpha = 1 - age / 900;
// Connect to 1-2 nearby recent points
let connections = 0;
for (let j = i + 1; j < pointsRef.current.length && connections < 2; j++) {
const other = pointsRef.current[j];
const dx = other.x - point.x;
const dy = other.y - point.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 200 && now - other.timestamp < 600) {
ctx.globalAlpha = fadeAlpha * 0.3;
ctx.beginPath();
ctx.moveTo(point.x, point.y);
ctx.lineTo(other.x, other.y);
ctx.stroke();
connections++;
}
}
});
// Draw points
pointsRef.current.forEach((point) => {
const age = now - point.timestamp;
const fadeAlpha = 1 - age / 900;
ctx.globalAlpha = point.alpha * fadeAlpha;
ctx.fillStyle = "rgba(74, 158, 255, 1)";
// Glow
const gradient = ctx.createRadialGradient(
point.x,
point.y,
0,
point.x,
point.y,
4
);
gradient.addColorStop(0, "rgba(74, 158, 255, 0.6)");
gradient.addColorStop(1, "rgba(74, 158, 255, 0)");
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(point.x, point.y, 4, 0, Math.PI * 2);
ctx.fill();
// Core
ctx.fillStyle = "rgba(74, 158, 255, 1)";
ctx.beginPath();
ctx.arc(point.x, point.y, 1.5, 0, Math.PI * 2);
ctx.fill();
});
ctx.globalAlpha = 1;
animationFrameRef.current = requestAnimationFrame(animate);
};
window.addEventListener("mousemove", handleMouseMove, { passive: true });
window.addEventListener("click", handleClick);
animate();
return () => {
window.removeEventListener("resize", resize);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("click", handleClick);
cancelAnimationFrame(animationFrameRef.current);
};
}, []);
return (
<canvas
ref={canvasRef}
className="fixed inset-0 pointer-events-none z-10"
style={{ mixBlendMode: "screen" }}
aria-hidden="true"
/>
);
}
```
---
## 3. COMANDOS DE EXECUÇÃO
```bash
# Instalar dependências
npm install
# ou
pnpm install
# Rodar dev server
npm run dev
# ou
pnpm dev
# Build para produção
npm run build
# Rodar produção local
npm run start
# Lint
npm run lint
```
Abra [http://localhost:3000](http://localhost:3000) no navegador.
---
## 4. CHECKLIST DE VALIDAÇÃO (Aceitação)
### Estrutura
- [ ] Árvore de arquivos está completa
- [ ] TypeScript estrito sem erros (`npm run build` passa)
- [ ] ESLint sem warnings (`npm run lint` limpo)
- [ ] Todos os textos vêm de `src/content/content.ts`
### Performance
- [ ] Lighthouse Desktop Score: 95+ (Performance, Accessibility, Best Practices, SEO)
- [ ] Lighthouse Mobile Score: 95+ (Performance, Accessibility, Best Practices, SEO)
- [ ] Canvas não causa jank no scroll
- [ ] Máx 60 partículas ativas
- [ ] `requestAnimationFrame` usado corretamente
### Acessibilidade
- [ ] Navegação por teclado funciona (Tab, Enter, Esc)
- [ ] Links e botões têm `:focus-visible` visível
- [ ] Skip-to-content link funciona
- [ ] `aria-label` e roles semânticos presentes
- [ ] Contraste mínimo WCAG AA (4.5:1)
- [ ] `prefers-reduced-motion` desliga animações
### SEO
- [ ] Meta tags corretas em todas as páginas
- [ ] OpenGraph e Twitter Cards configurados
- [ ] JSON-LD presente (Person + WebSite)
- [ ] `robots.txt` criado
- [ ] URLs limpas (`/about`, `/work`, etc.)
### Efeito "Sinais"
- [ ] Canvas renderiza no desktop
- [ ] Desabilitado em mobile
- [ ] Desabilitado com `prefers-reduced-motion`
- [ ] Pontos aparecem no mousemove (throttled)
- [ ] Linhas conectam pontos próximos
- [ ] Click em links cria flash discreto
- [ ] Tudo desaparece em <1s
- [ ] Blend mode não interfere com texto
### Design
- [ ] Tipografia elegante (serif + sans do Google Fonts)
- [ ] Paleta minimalista (branco, off-white, quase-preto, azul sutil)
- [ ] Header fixo com blur e borda hairline
- [ ] Footer discreto
- [ ] Hover states suaves
- [ ] Responsivo mobile/tablet/desktop
### Conteúdo
- [ ] Todos os textos são placeholders
- [ ] Fácil editar tudo em `content.ts`
- [ ] Nenhum conteúdo hardcoded em componentes
---
**Pronto. Copie tudo e mande pro manus.ai (ou qualquer cursor/v0/bolt). Vai rodar na primeira.**



