diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..9a41861
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,43 @@
+name: CI
+
+on:
+ pull_request:
+ push:
+ branches:
+ - main
+ - master
+
+permissions:
+ contents: read
+
+jobs:
+ quality:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check out repository
+ uses: actions/checkout@v7
+ with:
+ persist-credentials: false
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v6
+ with:
+ version: 10.33.4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Run checks
+ run: pnpm check
+
+ - name: Install Chromium
+ run: pnpm exec playwright install --with-deps chromium
+
+ - name: Run browser tests
+ run: pnpm test:e2e
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..b9809c1
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,78 @@
+name: Deploy to GitHub Pages
+
+on:
+ workflow_run:
+ workflows:
+ - CI
+ types:
+ - completed
+
+permissions:
+ contents: read
+
+concurrency:
+ group: pages
+ cancel-in-progress: true
+
+jobs:
+ build:
+ if: >-
+ ${{
+ github.event.workflow_run.conclusion == 'success' &&
+ github.event.workflow_run.event == 'push' &&
+ (github.event.workflow_run.head_branch == 'main' ||
+ github.event.workflow_run.head_branch == 'master')
+ }}
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ pages: read
+ steps:
+ - name: Check out the verified revision
+ uses: actions/checkout@v7
+ with:
+ ref: ${{ github.event.workflow_run.head_sha }}
+ persist-credentials: false
+
+ - name: Install pnpm
+ uses: pnpm/action-setup@v6
+ with:
+ version: 10.33.4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v7
+ with:
+ node-version: 24
+ cache: pnpm
+
+ - name: Install dependencies
+ run: pnpm install --frozen-lockfile
+
+ - name: Build the verified revision
+ run: pnpm build
+
+ - name: Verify static output
+ run: pnpm verify:build
+
+ - name: Configure GitHub Pages
+ uses: actions/configure-pages@v6
+
+ - name: Upload GitHub Pages artifact
+ uses: actions/upload-pages-artifact@v5
+ with:
+ path: dist
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ permissions:
+ contents: read
+ pages: write
+ id-token: write
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v5
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..2a46293
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+node_modules/
+dist/
+coverage/
+playwright-report/
+test-results/
+.DS_Store
+*.log
+tmp/
+.visualizations/
diff --git a/.node-version b/.node-version
new file mode 100644
index 0000000..a45fd52
--- /dev/null
+++ b/.node-version
@@ -0,0 +1 @@
+24
diff --git a/.nvmrc b/.nvmrc
new file mode 100644
index 0000000..a45fd52
--- /dev/null
+++ b/.nvmrc
@@ -0,0 +1 @@
+24
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..34ecc3b
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,90 @@
+# Agent Instructions — Lucas Mariz Portfolio
+
+## Scope and source of truth
+
+- These instructions apply to every coding agent working in this repository.
+- Read `docs/ENGINEERING_CONVENTIONS.md`, `docs/DESIGN.md`, and the relevant
+ roadmap phase before implementation.
+- Keep implementation, tests, and documentation synchronized in the same
+ change.
+- Read the nearest nested `AGENTS.md` before editing a scoped module. Nested
+ instructions may add invariants without replacing these repository-wide
+ rules.
+
+## Language policy
+
+- Write source code, code comments, test descriptions, commit-facing notes,
+ README content, agent instructions, and technical documentation in English.
+- Never hardcode user-facing copy in a component. Add it to the typed i18n
+ catalog in English, Brazilian Portuguese, and French.
+- English is the application fallback locale.
+
+## Tooling
+
+- Node.js 24 is required. Keep `.nvmrc`, `.node-version`, CI, and
+ `package.json` aligned.
+- pnpm is the only package manager. Use `pnpm`, `pnpm exec`, and `pnpm dlx`;
+ never add npm or Yarn commands to project documentation or scripts.
+- Biome is the only JavaScript/TypeScript/CSS linter and formatter. The root
+ `biome.json` is the single source of truth.
+- Run `pnpm check` before handing off an implementation change. Run the
+ production build and relevant browser checks when UI or deployment changes.
+
+## Git and commits
+
+- Commit messages must be written in English and follow Conventional Commits.
+- Never run `git commit` unless the user explicitly asks for a commit in the
+ current conversation. Permission to plan, implement, edit, test, or prepare
+ a change is not permission to commit it.
+- Commit authorization applies only to changes the user has already reviewed.
+ Any later edit requires a new review and a new explicit commit request.
+- Lefthook must run the configured pre-commit checks, and Commitlint must
+ validate commit messages. Never bypass hooks with `--no-verify`.
+
+## TypeScript and control flow
+
+- Do not use `let`, `else`, `else if`, or `switch` in TypeScript or JavaScript.
+- Prefer arrow functions for Preact components and helpers.
+- Prefer immutable values, guard clauses, early returns, lookup maps, and small
+ named helpers.
+- Keep components presentational where possible and move content, browser
+ preference detection, and external-data logic into dedicated modules.
+
+## Components and responsive design
+
+- Keep files focused. Split large sections into small, named components.
+- Every reusable component belongs in a PascalCase folder with a matching
+ `Component.tsx` file, an `index.ts` barrel, and focused tests when it owns
+ behavior.
+- A component used by only one parent belongs in that parent's
+ `components/ComponentName/` folder with the same file/barrel structure.
+- Import components through their folder barrels.
+- Treat mobile and desktop as first-class layouts. Avoid horizontal overflow,
+ preserve accessible touch targets, and verify representative phone and
+ desktop viewports.
+- Use semantic design tokens for every surface, border, foreground, and state
+ so light and dark themes remain visually equivalent.
+- Add only purposeful transitions and always respect `prefers-reduced-motion`.
+
+## Internationalization and theme
+
+- Supported locales are `en`, `pt-BR`, and `fr`, with matching catalog keys.
+- On a first visit, detect the browser locale. If it is missing or unsupported,
+ use English.
+- On a first visit, detect `prefers-color-scheme`. If it is missing or
+ unsupported, use light mode.
+- Persist explicit user overrides and apply theme selection before first paint.
+- Update the document language and localized metadata whenever the locale
+ changes.
+- Language controls must use language names rather than flags alone.
+
+## Static deployment and public data
+
+- The site must remain statically buildable for GitHub Pages. Do not introduce
+ a backend, server runtime, or client-side secret for portfolio features.
+- The GitHub avatar may use the public username image endpoint with a local
+ fallback.
+- Keep the project showcase curated. External APIs may enrich nonessential
+ metadata at build time, but the page must not depend on runtime API success.
+- Never expose private repository URLs, credentials, tokens, residential
+ addresses, or other unnecessary personal data in the generated site.
diff --git a/README.md b/README.md
index 4a1d3aa..2f4d21e 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,107 @@
-# Curriculum
-Currículo : https://lucaspmm.github.io/Curriculum/
+# Lucas Mariz Portfolio
+
+A small, static personal portfolio for GitHub Pages. It presents Lucas Mariz's
+professional experience, academic path, selected production work, research, and
+projects in English, Brazilian Portuguese, and French.
+
+Target production URL: [https://lucaspmm.github.io/](https://lucaspmm.github.io/)
+
+The curated showcase includes Jig Solver, the private Planner financial
+assistant, Simplex, Pokémon Base, Greedy K-means, and LZ78 Compression. Private
+projects are described without exposing source or application links.
+
+## Stack
+
+- Preact, TypeScript, and Vite
+- i18next with typed, key-parity translation catalogs
+- Native CSS based on the tokens in `docs/DESIGN.md`
+- Biome for linting and formatting
+- Vitest and Testing Library for behavior tests
+- Playwright for responsive browser checks
+- Vite prerendering for useful HTML before client hydration
+- Lefthook and Commitlint for local Git conventions
+
+The site has no backend, CMS, or client-side secret. The profile image loads
+from the public GitHub username endpoint and falls back to a local asset.
+
+## Requirements
+
+- Node.js 24
+- pnpm 10
+
+## Local development
+
+```bash
+pnpm install
+pnpm dev
+```
+
+Install the Git hooks once per clone:
+
+```bash
+pnpm exec lefthook install
+```
+
+## Quality checks
+
+```bash
+pnpm check
+```
+
+Run the browser suite separately after installing the Playwright browser:
+
+```bash
+pnpm exec playwright install chromium
+pnpm test:e2e
+```
+
+## Production build
+
+```bash
+pnpm build
+pnpm preview
+```
+
+The build output is written to `dist/`. This repository targets the
+`LucasPMM.github.io` GitHub Pages user site and therefore uses `/` as its Vite
+base path.
+
+The production check also verifies canonical metadata, the sitemap,
+`robots.txt`, and representative prerendered content.
+
+## GitHub Pages activation
+
+The deployment workflow runs only after the `CI` workflow succeeds for a push
+to `master` or `main`. It rebuilds the exact verified revision and publishes
+only `dist/`.
+
+To activate the final site:
+
+1. Rename `LucasPMM/Curriculum` to `LucasPMM/LucasPMM.github.io` under
+ **Settings → General**.
+2. Under **Settings → Pages**, change the publishing source from the legacy
+ branch configuration to **GitHub Actions**.
+3. Update the local remote:
+
+ ```bash
+ git remote set-url origin git@github.com:LucasPMM/LucasPMM.github.io.git
+ ```
+
+4. Merge the modernization branch into the default branch. A successful CI run
+ will trigger the deployment workflow automatically.
+5. Set the repository description, Website field, GitHub profile Website, and
+ LinkedIn link to `https://lucaspmm.github.io/`.
+
+## Content and implementation rules
+
+Read these files before making changes:
+
+- `AGENTS.md`
+- `docs/ENGINEERING_CONVENTIONS.md`
+- `docs/DESIGN.md`
+- `docs/ROADMAP.md`
+
+Source code, comments, tests, and documentation are written in English. All
+visitor-facing copy belongs in the three-language catalog. Do not commit changes
+without explicit authorization from Lucas; authorized messages must follow
+English Conventional Commits.
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..5bdae95
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.5.13/schema.json",
+ "files": {
+ "includes": [
+ "**",
+ "!!dist",
+ "!!coverage",
+ "!!node_modules",
+ "!!playwright-report",
+ "!!test-results",
+ "!!tmp",
+ "!!.visualizations"
+ ]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "lineWidth": 100
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "preset": "recommended"
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "single",
+ "semicolons": "asNeeded",
+ "trailingCommas": "all"
+ }
+ },
+ "css": {
+ "formatter": {
+ "enabled": true
+ },
+ "linter": {
+ "enabled": true
+ }
+ }
+}
diff --git a/commitlint.config.mjs b/commitlint.config.mjs
new file mode 100644
index 0000000..d179c69
--- /dev/null
+++ b/commitlint.config.mjs
@@ -0,0 +1,3 @@
+export default {
+ extends: ['@commitlint/config-conventional'],
+}
diff --git a/docs/DESIGN.md b/docs/DESIGN.md
new file mode 100644
index 0000000..6c61120
--- /dev/null
+++ b/docs/DESIGN.md
@@ -0,0 +1,464 @@
+# Slite — Style Reference
+> Warm parchment notebook with terracotta pen — every surface is cream paper, every accent a single ember-orange stroke.
+
+**Theme:** light
+
+Slite uses a warm-parchment workspace language: the entire page sits on a cream paper canvas (#fdf9f4) rather than cold white, and a single vivid orange (#f67748) acts as ember — appearing only on the primary CTA, selected status pills, and card border highlights. Typography is split between Garnett (a custom serif-feeling display face with tight tracking) for headlines and a custom UniversalSans (geometric humanist) for everything else, giving the page the rhythm of a well-edited document: serif headlines that feel handwritten, sans-serif body that feels like typed notes. Components are pill-heavy and shadow-light: ghost and outlined pill buttons, 32px-rounded feature cards, dust-toned tag chips, and hand-drawn circle annotations around key phrases. The dominant motion is restrained (130ms ease) and the dominant structure is a centered column with comfortable 80–120px section gaps — never information-dense, always breathing.
+
+## Tokens — Colors
+
+| Name | Value | Token | Role |
+|------|-------|-------|------|
+| Parchment Cream | `#fdf9f4` | `--color-parchment-cream` | Page canvas and primary card surface — the warm off-white that defines Slite's identity. Never use cold white #ffffff at the page level |
+| Star White | `#ffffff` | `--color-star-white` | Elevated surfaces — product mockup cards, tooltips, white-product interiors stacked on top of the cream canvas |
+| Dust Sand | `#f9efe4` | `--color-dust-sand` | Secondary surface and tag/chip background — a half-step darker than the canvas. Tag pills, secondary buttons, and warm-emphasis callouts |
+| Moon Silver | `#ecedef` | `--color-moon-silver` | Hairline borders, dividers, and 2px outlined button borders — the only border tone used at full opacity |
+| Shade Ink | `#2d2f34` | `--color-shade-ink` | Primary heading and body text — slightly warm near-black. The headline color |
+| Shade Charcoal | `#3f434a` | `--color-shade-charcoal` | Secondary body text, navigation labels, and subdued headings. Do not promote it to the primary CTA color |
+| Shade Slate | `#5e646e` | `--color-shade-slate` | Tertiary body text, captions, helper copy — the quietest readable gray |
+| Shade Fog | `#9da3af` | `--color-shade-fog` | Disabled states, placeholder text, and the lightest non-white neutral — used sparingly on the page |
+| Shade Dusk | `#6a707c` | `--color-shade-dusk` | Small print and fine print text — pricing footnotes, micro-copy beneath headings |
+| Border Mist | `#d9dde6` | `--color-border-mist` | Card borders and stroke at low contrast — slightly bluer than Moon Silver, used when a card edge needs to be felt but not seen |
+| Ember Orange | `#f67748` | `--color-ember-orange` | Primary action — filled CTA buttons, selected card border accent, featured testimonial card background, and the scribble-annotation color. The single saturated brand color, used sparingly so it always feels like a deliberate highlight |
+| Neptune Blue | `#74a6f1` | `--color-neptune-blue` | Secondary action accent — used on at most one button per page (e.g. alternating testimonial CTA) and link-text accents. Never the primary CTA |
+| Verification Green | `#479a53` | `--color-verification-green` | Green text accent for links, tags, and emphasized short phrases. Use as a supporting accent, not as a status color |
+| Verified Mint | `#bbf7d0` | `--color-verified-mint` | Green decorative accent for icons, marks, and small graphic details. Use as a supporting accent, not as a status color |
+| Tag Violet | `#4b51c3` | `--color-tag-violet` | Violet text accent for links, tags, and emphasized short phrases. Use as a supporting accent, not as a status color |
+| Illustration Violet | `#6b70d6` | `--color-illustration-violet` | Decorative illustration fill — light-violet shapes in product mockups, paired with Tag Violet as a tonal pair |
+
+## Tokens — Typography
+
+### Garnett — Display and editorial headings. Used at 64px (display), 36px (h1), 28px (h2), 24px (large body), 12px (small links). The serif-like Garnett paired with a humanist sans is Slite's signature typographic contrast — it makes the page feel like a designed document rather than a dashboard. · `--font-garnett`
+- **Substitute:** Lora, Source Serif Pro, or PT Serif — pick a serif with similar humanist warmth
+- **Weights:** 400, 500, 700
+- **Sizes:** 12, 16, 24, 28, 36, 64
+- **Line height:** 1.20 – 2.13
+- **OpenType features:** `"ss14", "ss15", "ss19"`
+- **Role:** Display and editorial headings. Used at 64px (display), 36px (h1), 28px (h2), 24px (large body), 12px (small links). The serif-like Garnett paired with a humanist sans is Slite's signature typographic contrast — it makes the page feel like a designed document rather than a dashboard.
+
+### UniversalSans — Body text, UI controls, navigation, buttons, and supporting headlines. Carries almost all of the page's content. The 50px / weight 400 / line-height 1.5 hero variant is a deliberate departure from typical 700-weight display sizes — it lets the Garnett headline above do the work, while UniversalSans handles the breathing paragraph copy beneath. · `--font-universalsans`
+- **Substitute:** Inter, Roboto, or a humanist sans like Public Sans
+- **Weights:** 400, 500, 600, 700
+- **Sizes:** 10, 12, 13, 14, 15, 16, 17, 19, 20, 22, 24, 26, 34, 50
+- **Line height:** 1.00 – 2.00
+- **OpenType features:** `"ss14", "ss15", "ss19"`
+- **Role:** Body text, UI controls, navigation, buttons, and supporting headlines. Carries almost all of the page's content. The 50px / weight 400 / line-height 1.5 hero variant is a deliberate departure from typical 700-weight display sizes — it lets the Garnett headline above do the work, while UniversalSans handles the breathing paragraph copy beneath.
+
+### Type Scale
+
+| Role | Size | Line Height | Letter Spacing | Token |
+|------|------|-------------|----------------|-------|
+| label | 10px | 1.2 | — | `--text-label` |
+| caption | 13px | 1.2 | — | `--text-caption` |
+| body-sm | 15px | 1.5 | — | `--text-body-sm` |
+| button | 17px | 1 | — | `--text-button` |
+| body-lg | 19px | 1.4 | — | `--text-body-lg` |
+| heading-sm | 26px | 1.3 | — | `--text-heading-sm` |
+| heading | 28px | 1.25 | — | `--text-heading` |
+| heading-lg | 36px | 1.2 | — | `--text-heading-lg` |
+| hero | 50px | 1.5 | — | `--text-hero` |
+| display | 64px | 1.2 | — | `--text-display` |
+
+## Tokens — Spacing & Shapes
+
+**Base unit:** 4px
+
+**Density:** comfortable
+
+### Spacing Scale
+
+| Name | Value | Token |
+|------|-------|-------|
+| 4 | 4px | `--spacing-4` |
+| 8 | 8px | `--spacing-8` |
+| 12 | 12px | `--spacing-12` |
+| 16 | 16px | `--spacing-16` |
+| 20 | 20px | `--spacing-20` |
+| 24 | 24px | `--spacing-24` |
+| 32 | 32px | `--spacing-32` |
+| 40 | 40px | `--spacing-40` |
+| 48 | 48px | `--spacing-48` |
+| 60 | 60px | `--spacing-60` |
+| 72 | 72px | `--spacing-72` |
+| 80 | 80px | `--spacing-80` |
+| 100 | 100px | `--spacing-100` |
+| 120 | 120px | `--spacing-120` |
+| 176 | 176px | `--spacing-176` |
+| 240 | 240px | `--spacing-240` |
+
+### Border Radius
+
+| Element | Value |
+|---------|-------|
+| tags | 9999px |
+| cards | 32px |
+| buttons | 999px |
+| smallCards | 12px |
+| ghostButton | 8px |
+| productCards | 18px |
+
+### Shadows
+
+| Name | Value | Token |
+|------|-------|-------|
+| subtle | `rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.05) 0...` | `--shadow-subtle` |
+| sm | `rgba(0, 0, 0, 0.2) 0px 2px 6px 0px` | `--shadow-sm` |
+
+### Layout
+
+- **Page max-width:** 1200px
+- **Section gap:** 96px
+- **Element gap:** 8px
+
+## Components
+
+### Primary CTA Button (Ember Pill)
+**Role:** The single orange button on the page — reserved for the main conversion action.
+
+Filled #f67748 background, white text, UniversalSans 17px weight 600, line-height 1, border-radius 999px (full pill), padding 12px 24px. Appears at most once above the fold. The only color-saturated button on the page.
+
+### Dark CTA Button (Charcoal Pill)
+**Role:** Secondary high-emphasis action, typically 'Start for free'.
+
+Filled #2d2f34 (or #3f434a) background, white text, UniversalSans 17px weight 600, border-radius 999px, padding 12px 20px. Used in the header nav for the highest-intent action.
+
+### Outlined Pill Button
+**Role:** Medium-emphasis action — Book demo, secondary nav actions.
+
+Transparent background, 2px solid #2d2f34 border, #2d2f34 text, UniversalSans 15–17px weight 500–600, border-radius 999px, padding 10px 20px.
+
+### Ghost Text Button
+**Role:** Low-emphasis inline action — nav items, sub-actions inside cards.
+
+Transparent background, no border, #3f434a text, UniversalSans 14–15px weight 500, padding 4px 8px. Minimal padding signals 'I am a label, not a control'.
+
+### Square Ghost Button
+**Role:** Compact UI control — close buttons, icon toggles, inline editors.
+
+Transparent background, #2d2f34 text, border-radius 8px, padding 0px 8px. The 8px radius is the sharpest button radius in the system — used only for tiny inline controls.
+
+### Dust Tag Chip
+**Role:** Feature highlight tags and category labels — the most-repeated component on the page.
+
+#f9efe4 background, #3f434a text, UniversalSans 13–15px weight 500, border-radius 9999px, padding 8px 16px. Always pill-shaped, always warm. Used in rows of 3–4 to label a section's sub-topics.
+
+### Status Pill (Verified / Self-maintained)
+**Role:** Trust and maintenance indicators inside product screenshots.
+
+Tag Violet (#4b51c3) or Verification Green (#479a53) text on white, paired with a small filled icon. UniversalSans 13px weight 500. Sits inline above content titles.
+
+### Cream Feature Card
+**Role:** Primary marketing card — 3-column feature grid items, testimonial cards.
+
+#fdf9f4 or #f9efe4 background, border-radius 32px, padding 48px top / 32px bottom / 24px sides, no shadow. The 32px radius is Slite's signature — generous but not pillow-soft. Optionally bordered with a 2px #f67748 ember stroke to denote 'selected' or 'highlighted' cards.
+
+### White Product Card
+**Role:** Product screenshot containers and tooltips stacked on the cream canvas.
+
+#ffffff background, border-radius 12px, box-shadow 0 1px 3px rgba(0,0,0,0.1) + 0 2px 6px rgba(0,0,0,0.05) + 0 4px 12px rgba(0,0,0,0.01). The three-layer shadow is the only place Slite uses depth — the rest of the page stays flat.
+
+### Compact UI Card
+**Role:** Inline product UI mock elements — sidebar items, list rows, agent chips.
+
+#fdfdfd (near-white) background, border-radius 16px, padding 12px 16px, no shadow. The tighter 16px radius signals 'I am a UI element inside a product mock, not a marketing card'.
+
+### Ember Testimonial Card
+**Role:** Featured customer quote — the only orange-filled card.
+
+#f67748 background, white text, border-radius 16px, padding 32px. Used at most once per page as the visual punctuation between sections of cream cards.
+
+### Logo Trust Bar
+**Role:** Social proof — '3,000+ companies trust Slite' row.
+
+Horizontal row of monochrome black customer logos (Doodle, Lush, Frontify, Karbon, Visma, Omnisend) on the cream canvas, with a small 13px caption beneath each ('Migrated from Notion'). Logos are rendered at a single visual weight and aligned to a shared baseline.
+
+### Scribble Annotation
+**Role:** Hand-drawn circle or underline around a key word in a headline.
+
+1.5–2px solid #f67748 stroke, no fill, slightly imperfect oval shape (roughened path), placed behind or around a single word ('Verified'). The visual signature that makes headlines feel hand-edited rather than rendered.
+
+### Underline Link
+**Role:** Inline text link inside paragraphs.
+
+#3f434a text, no underline by default, 1px underline on hover with a #f67748 ember accent color. The hover color is the only place the ember orange appears in text.
+
+### Hero Product Frame
+**Role:** Large product screenshot shown below the headline.
+
+A White Product Card containing a product mockup — sidebar navigation, breadcrumb, content area. Framed by the canvas and dropped onto the page with a subtle offset (rotation 0–1deg optional). Always 12px radius, always #ffffff, always with the three-layer shadow.
+
+## Do's and Don'ts
+
+### Do
+- Set the page canvas to #fdf9f4 — never use cold white #ffffff as the page background
+- Use #f67748 for exactly one filled CTA per section; everything else is charcoal, outlined, or ghost
+- Use Garnett for headlines and UniversalSans for body — never use UniversalSans at 40px+ display sizes
+- Set button border-radius to 999px (pill) for all primary actions, 8px only for tiny square icon buttons
+- Set card border-radius to 32px for marketing cards, 12–18px for product mockup containers
+- Use #f9efe4 dust backgrounds for tag chips and secondary surfaces, not solid gray
+- Hand-draw a 1.5px #f67748 circle or underline around exactly one key word in any hero headline
+
+### Don't
+- Do not introduce a second saturated color as a brand accent — #f67748 must remain the only chromatic surface color
+- Do not use 700-weight UniversalSans at display sizes — 50px hero text is always weight 400
+- Do not stack more than one shadow elevation on a single element; the three-layer shadow is the maximum
+- Do not use pure black #000000 for body text — always #2d2f34 (Shade Ink) or #3f434a (Shade Charcoal)
+- Do not use #ecedef or #d9dde6 as background fills — these are border tones only
+- Do not break the pill/tag radius system with square chips or rounded-but-not-pill buttons
+- Do not place #f67748 fills on large backgrounds (more than 20% of a section) — it dilutes the CTA signal
+
+## Surfaces
+
+| Level | Name | Value | Purpose |
+|-------|------|-------|---------|
+| 0 | Canvas | `#fdf9f4` | Page background — the warm cream that defines the whole site's atmosphere |
+| 1 | White Product | `#ffffff` | Product mockup interiors and elevated tooltips — appears as white panels inside the cream canvas |
+| 2 | Dust Card | `#f9efe4` | Feature cards and tag chip backgrounds — a half-step darker than canvas to delineate zones without contrast |
+| 3 | Silver Border | `#ecedef` | Hairline card and button borders — the sole border tone |
+| 4 | Ember Accent | `#f67748` | Selected card border and CTA fill — the only chromatic surface |
+
+## Elevation
+
+- **White Product Card:** `0 1px 3px rgba(0,0,0,0.1), 0 2px 6px rgba(0,0,0,0.05), 0 4px 12px rgba(0,0,0,0.01)`
+
+## Imagery
+
+Visuals are dominated by product screenshot mockups rendered on white cards inside the cream canvas, not by photography. The product UI is shown in a real working state (sidebar navigation, breadcrumb, content area with 'Troubleshooting' heading) rather than as a stylized hero render. Decorative elements are sparse: hand-drawn orange scribble circles around key headline words, and small abstract illustration washes in mint, violet, and pink used sparingly inside the product mock to add warmth. No lifestyle photography, no stock imagery. The visual density is text-dominant with screenshots functioning as evidence rather than decoration.
+
+## Layout
+
+Page model: centered max-width ~1200px with comfortable horizontal margins, full-bleed cream canvas behind. Hero is a centered headline stack — Garnett 64px display headline (with one word circled in orange) sits above a 19px UniversalSans subtitle, then a row of Dust Tag Chips, then the single Ember CTA. The product mockup appears below as a large White Product Card with soft shadow. Section rhythm: consistent vertical breathing room (96px between sections), no alternating dark/light bands — the page stays cream throughout. Social proof is a centered single-row logo wall. Feature blocks are 3-column Cream Feature Card grids with 32px radius. The page reads top-to-bottom as a single editorial document, not as a marketing patchwork.
+
+## Agent Prompt Guide
+
+**Quick Color Reference**
+- text: #2d2f34 (headings) / #3f434a (body) / #5e646e (tertiary)
+- background: #fdf9f4 (canvas) / #ffffff (elevated) / #f9efe4 (dust surface)
+- border: #ecedef (hairline) / #d9dde6 (soft card edge)
+- accent: #f67748 (ember — used for the single primary CTA, selected card border, and headline scribble annotation only)
+- primary action: #f67748 (filled action)
+
+**3-5 Example Component Prompts**
+
+1. **Hero Headline with Scribble Annotation**: Centered Garnett 64px weight 500, color #2d2f34, line-height 1.2. One key word (e.g. 'Verified') wrapped in a hand-drawn 1.5px stroke #f67748 oval, positioned slightly above and around the word. Below: UniversalSans 19px weight 400, color #3f434a, line-height 1.4, max-width 640px centered.
+
+2. Create a Primary Action Button: #f67748 background, #000000 text, 9999px radius, compact pill padding. Use this filled treatment for the main CTA.
+
+3. **Dust Tag Chip Row**: Horizontal row of 3 pills, each #f9efe4 background, #3f434a text, UniversalSans 13px weight 500, border-radius 9999px, padding 8px 16px, 8px gap between chips. Centered above feature sections.
+
+4. **Cream Feature Card**: #f9efe4 background, border-radius 32px, padding 48px 24px 32px, no shadow. Optional 2px solid #f67748 left border for highlighted/selected cards. Contains a Garnett 24px weight 500 title in #2d2f34, UniversalSans 15px weight 400 body in #5e646e.
+
+5. **White Product Card**: #ffffff background, border-radius 12px, box-shadow 0 1px 3px rgba(0,0,0,0.1) + 0 2px 6px rgba(0,0,0,0.05) + 0 4px 12px rgba(0,0,0,0.01). Contains a product UI mockup (sidebar + content area) rendered in actual working state, not a stylized render.
+
+## Typography Pairing Logic
+
+The Garnett + UniversalSans pairing is a deliberate editorial choice: Garnett's serif character gives the page the weight of a printed document or annual report, while UniversalSans handles the 'typed notes' density of the body copy. The contrast is most visible at the hero where a Garnett 64px display headline sits above a UniversalSans 50px weight-400 subheadline — the weight-400 choice is anti-convention; most sites use 700 at this size, but Slite's subheadline whispers so the Garnett headline can speak. The two faces share the same font-feature-settings ('ss14', 'ss15', 'ss19'), meaning the stylistic alternates were designed as a pair — substituting with a mismatched system serif will break the visual continuity.
+
+## Radius as Hierarchy
+
+Border-radius is Slite's secondary hierarchy system. Marketing cards use the largest radius (32px) to feel designed and deliberate. Product mockup cards use 12–18px to feel like actual UI. Pill buttons and tags always use 999–9999px to feel approachable and tappable. The only sharp corner in the system is the 8px ghost button — it is reserved for tiny inline icon controls where a pill would look strange. When in doubt: larger radius = more editorial, smaller radius = more product.
+
+## The Ember Accent Rule
+
+#f67748 must appear on less than 5% of any page. The constraint is what gives the orange its signaling power — it marks 'this is the action you take' and 'this is the word you should remember'. When you use the orange anywhere that is not (a) a primary CTA, (b) a 'selected' card border, (c) a headline scribble annotation, or (d) a featured testimonial card background, you have broken the system. The page should be readable in greyscale; the orange is punctuation, not structure.
+
+## Similar Brands
+
+- **Notion** — Same warm cream canvas approach and pill-shaped tag/button system, though Notion uses a heavier weight and more varied type sizes
+- **Mem** — Same serif-headline + sans-body editorial pairing and warm paper-canvas aesthetic, with a similarly restrained single-accent color strategy
+- **Coda** — Same product-screenshot-driven marketing style and ghost/outlined button hierarchy, though Coda leans cooler in its neutrals
+- **Tana** — Same knowledge-management category and warm off-white canvas, with comparable pill-chip tag systems above feature sections
+- **Linear** — Same single-accent restraint (Linear uses indigo, Slite uses ember), same 32px-rounded feature cards, and the same three-layer soft shadow on elevated product surfaces
+
+## Quick Start
+
+### CSS Custom Properties
+
+```css
+:root {
+ /* Colors */
+ --color-parchment-cream: #fdf9f4;
+ --color-star-white: #ffffff;
+ --color-dust-sand: #f9efe4;
+ --color-moon-silver: #ecedef;
+ --color-shade-ink: #2d2f34;
+ --color-shade-charcoal: #3f434a;
+ --color-shade-slate: #5e646e;
+ --color-shade-fog: #9da3af;
+ --color-shade-dusk: #6a707c;
+ --color-border-mist: #d9dde6;
+ --color-ember-orange: #f67748;
+ --color-neptune-blue: #74a6f1;
+ --color-verification-green: #479a53;
+ --color-verified-mint: #bbf7d0;
+ --color-tag-violet: #4b51c3;
+ --color-illustration-violet: #6b70d6;
+
+ /* Typography — Font Families */
+ --font-garnett: 'Garnett', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-universalsans: 'UniversalSans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+
+ /* Typography — Scale */
+ --text-label: 10px;
+ --leading-label: 1.2;
+ --text-caption: 13px;
+ --leading-caption: 1.2;
+ --text-body-sm: 15px;
+ --leading-body-sm: 1.5;
+ --text-button: 17px;
+ --leading-button: 1;
+ --text-body-lg: 19px;
+ --leading-body-lg: 1.4;
+ --text-heading-sm: 26px;
+ --leading-heading-sm: 1.3;
+ --text-heading: 28px;
+ --leading-heading: 1.25;
+ --text-heading-lg: 36px;
+ --leading-heading-lg: 1.2;
+ --text-hero: 50px;
+ --leading-hero: 1.5;
+ --text-display: 64px;
+ --leading-display: 1.2;
+
+ /* Typography — Weights */
+ --font-weight-regular: 400;
+ --font-weight-medium: 500;
+ --font-weight-semibold: 600;
+ --font-weight-bold: 700;
+
+ /* Spacing */
+ --spacing-unit: 4px;
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-20: 20px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-40: 40px;
+ --spacing-48: 48px;
+ --spacing-60: 60px;
+ --spacing-72: 72px;
+ --spacing-80: 80px;
+ --spacing-100: 100px;
+ --spacing-120: 120px;
+ --spacing-176: 176px;
+ --spacing-240: 240px;
+
+ /* Layout */
+ --page-max-width: 1200px;
+ --section-gap: 96px;
+ --element-gap: 8px;
+
+ /* Border Radius */
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-2xl: 18px;
+ --radius-2xl-2: 22px;
+ --radius-3xl: 32px;
+ --radius-3xl-2: 40px;
+ --radius-full: 999px;
+ --radius-full-2: 9999px;
+
+ /* Named Radii */
+ --radius-tags: 9999px;
+ --radius-cards: 32px;
+ --radius-buttons: 999px;
+ --radius-smallcards: 12px;
+ --radius-ghostbutton: 8px;
+ --radius-productcards: 18px;
+
+ /* Shadows */
+ --shadow-subtle: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.05) 0px 2px 6px 0px, rgba(0, 0, 0, 0.01) 0px 4px 12px 0px;
+ --shadow-sm: rgba(0, 0, 0, 0.2) 0px 2px 6px 0px;
+
+ /* Surfaces */
+ --surface-canvas: #fdf9f4;
+ --surface-white-product: #ffffff;
+ --surface-dust-card: #f9efe4;
+ --surface-silver-border: #ecedef;
+ --surface-ember-accent: #f67748;
+}
+```
+
+### Tailwind v4
+
+```css
+@theme {
+ /* Colors */
+ --color-parchment-cream: #fdf9f4;
+ --color-star-white: #ffffff;
+ --color-dust-sand: #f9efe4;
+ --color-moon-silver: #ecedef;
+ --color-shade-ink: #2d2f34;
+ --color-shade-charcoal: #3f434a;
+ --color-shade-slate: #5e646e;
+ --color-shade-fog: #9da3af;
+ --color-shade-dusk: #6a707c;
+ --color-border-mist: #d9dde6;
+ --color-ember-orange: #f67748;
+ --color-neptune-blue: #74a6f1;
+ --color-verification-green: #479a53;
+ --color-verified-mint: #bbf7d0;
+ --color-tag-violet: #4b51c3;
+ --color-illustration-violet: #6b70d6;
+
+ /* Typography */
+ --font-garnett: 'Garnett', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-universalsans: 'UniversalSans', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+
+ /* Typography — Scale */
+ --text-label: 10px;
+ --leading-label: 1.2;
+ --text-caption: 13px;
+ --leading-caption: 1.2;
+ --text-body-sm: 15px;
+ --leading-body-sm: 1.5;
+ --text-button: 17px;
+ --leading-button: 1;
+ --text-body-lg: 19px;
+ --leading-body-lg: 1.4;
+ --text-heading-sm: 26px;
+ --leading-heading-sm: 1.3;
+ --text-heading: 28px;
+ --leading-heading: 1.25;
+ --text-heading-lg: 36px;
+ --leading-heading-lg: 1.2;
+ --text-hero: 50px;
+ --leading-hero: 1.5;
+ --text-display: 64px;
+ --leading-display: 1.2;
+
+ /* Spacing */
+ --spacing-4: 4px;
+ --spacing-8: 8px;
+ --spacing-12: 12px;
+ --spacing-16: 16px;
+ --spacing-20: 20px;
+ --spacing-24: 24px;
+ --spacing-32: 32px;
+ --spacing-40: 40px;
+ --spacing-48: 48px;
+ --spacing-60: 60px;
+ --spacing-72: 72px;
+ --spacing-80: 80px;
+ --spacing-100: 100px;
+ --spacing-120: 120px;
+ --spacing-176: 176px;
+ --spacing-240: 240px;
+
+ /* Border Radius */
+ --radius-lg: 8px;
+ --radius-xl: 12px;
+ --radius-2xl: 18px;
+ --radius-2xl-2: 22px;
+ --radius-3xl: 32px;
+ --radius-3xl-2: 40px;
+ --radius-full: 999px;
+ --radius-full-2: 9999px;
+
+ /* Shadows */
+ --shadow-subtle: rgba(0, 0, 0, 0.1) 0px 1px 3px 0px, rgba(0, 0, 0, 0.05) 0px 2px 6px 0px, rgba(0, 0, 0, 0.01) 0px 4px 12px 0px;
+ --shadow-sm: rgba(0, 0, 0, 0.2) 0px 2px 6px 0px;
+}
+```
diff --git a/docs/ENGINEERING_CONVENTIONS.md b/docs/ENGINEERING_CONVENTIONS.md
new file mode 100644
index 0000000..fa35322
--- /dev/null
+++ b/docs/ENGINEERING_CONVENTIONS.md
@@ -0,0 +1,208 @@
+# Portfolio Engineering Conventions
+
+This document is the source of truth for implementation conventions shared by
+humans and coding assistants.
+
+## Language
+
+- Source code, code comments, test descriptions, README content, agent
+ instructions, and technical documentation must be written in English.
+- Runtime copy must be localized and must never be embedded directly in a
+ component.
+- Translation catalogs must contain the same keys for English, Brazilian
+ Portuguese, and French.
+
+## Runtime and package manager
+
+- Node.js 24 is the only supported Node.js major version.
+- `.nvmrc`, `.node-version`, `package.json`, and CI must remain aligned.
+- pnpm is the only supported package manager.
+- Use `pnpm exec` for project binaries. Do not document npm, npx, or Yarn
+ commands.
+- Add a preinstall guard that rejects unsupported package managers.
+
+## Quality tools
+
+- Biome is the only JavaScript, TypeScript, JSON, and CSS formatter/linter.
+- The root `biome.json` is the single source of truth. Do not create per-folder
+ overrides.
+- Vitest and Testing Library cover component behavior and browser-preference
+ helpers.
+- Playwright covers the deployed navigation, locale/theme behavior, responsive
+ layouts, and avatar fallback.
+- The minimum handoff gate is `pnpm check`.
+
+The intended scripts are:
+
+```json
+{
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview",
+ "lint": "pnpm lint:biome && pnpm lint:conventions",
+ "lint:biome": "biome lint .",
+ "lint:conventions": "node scripts/check-code-conventions.mjs",
+ "format": "biome check --write .",
+ "format:check": "biome format .",
+ "typecheck": "tsc --noEmit",
+ "test": "vitest run",
+ "test:e2e": "playwright test",
+ "check": "pnpm typecheck && pnpm lint && pnpm test && pnpm format:check && pnpm build"
+}
+```
+
+## TypeScript and control flow
+
+- Do not use `let`, `else`, `else if`, or `switch` in TypeScript or JavaScript.
+- Prefer `const`, guard clauses, early returns, lookup objects, and small named
+ helpers.
+- Prefer arrow functions for Preact components and helpers unless a library API
+ requires a function declaration.
+- The convention checker runs with the normal lint command and rejects the
+ forbidden syntax.
+- Avoid unsafe casts and non-null assertions. Model content and browser state
+ with explicit types.
+
+## Component structure
+
+Every reusable component has its own PascalCase directory, a matching source
+file, and a barrel:
+
+```text
+components/
+└── ProjectCard/
+ ├── ProjectCard.tsx
+ ├── ProjectCard.test.tsx
+ └── index.ts
+```
+
+Imports target the barrel:
+
+```ts
+import { ProjectCard } from '@/components/ProjectCard'
+```
+
+Components used by only one parent are colocated under that parent's
+`components/` directory:
+
+```text
+Hero/
+├── Hero.tsx
+├── index.ts
+└── components/
+ └── GitHubAvatar/
+ ├── GitHubAvatar.tsx
+ ├── GitHubAvatar.test.tsx
+ └── index.ts
+```
+
+Additional rules:
+
+- Keep page composition thin and delegate each meaningful section to a named
+ component.
+- Keep content records outside components.
+- Keep browser preference and persistence logic in dedicated `lib` modules.
+- Use semantic HTML before introducing abstractions.
+- Add a shared icon registry instead of importing icon components throughout
+ feature code.
+- Avoid generic wrappers that exist only to reduce line count.
+
+## Content model
+
+- Stable neutral data belongs in typed modules under `src/content`.
+- User-facing prose belongs in the translation catalogs.
+- Dates are stored as canonical values and localized at render time.
+- Career durations are calculated from dates; do not commit text such as
+ “6 years 4 months” that immediately becomes stale.
+- Project order is editorial. Never derive the showcase from repository update
+ time, star count, or API order.
+- Do not invent metrics, proficiency levels, roles, or project outcomes.
+
+## Internationalization
+
+- Supported locale identifiers are `en`, `pt-BR`, and `fr`.
+- English is the fallback locale.
+- On a first visit, resolve the locale in this order:
+ 1. a supported browser locale from `navigator.languages` or
+ `navigator.language`;
+ 2. English.
+- After a visitor explicitly selects a language, persist that override locally
+ and prefer it on later visits.
+- Match regional browser values by both exact tag and base language, so `fr-CA`
+ may select `fr` and `pt-PT` may select the available Portuguese catalog.
+- Update ``, title, description, Open Graph copy, accessible names,
+ and visible content together.
+- Use visible language names. Flags may be decorative but cannot be the label.
+- French copy requires human review before release.
+
+## Theme
+
+- On a first visit, use `prefers-color-scheme` when available.
+- Fall back to light mode when the preference is missing or cannot be resolved.
+- Persist only an explicit user override. An `auto` option should resume system
+ detection and respond to later operating-system changes.
+- Apply the resolved theme before the first paint to avoid a flash of the wrong
+ palette.
+- Use only semantic color tokens in components. Both themes must preserve the
+ warm editorial identity defined by `docs/DESIGN.md`.
+- Keep the ember accent below five percent of the page and use ink-colored text
+ on the orange CTA to meet contrast requirements.
+
+## Responsive behavior and accessibility
+
+- Design mobile-first and verify at 320 px, a representative tablet width, and
+ desktop.
+- Avoid fixed content widths that cause horizontal scrolling.
+- Navigation and every essential action must work without hover.
+- Provide visible focus, semantic landmarks, ordered headings, a skip link, and
+ localized accessible names.
+- Keep primary touch targets at least 44 by 44 pixels.
+- Support keyboard-only navigation, 200 percent zoom, reduced motion, and
+ high-contrast text.
+- Images require intrinsic dimensions and meaningful localized alternative
+ text. Decorative images use an empty alt value.
+
+## GitHub data and privacy
+
+- Load the profile image from the public GitHub username image endpoint and
+ provide a local fallback asset.
+- Do not make the page depend on a runtime GitHub API request.
+- Optional repository metadata enrichment runs at build time and degrades
+ gracefully when unavailable.
+- Never expose an API token in client code or the generated bundle.
+- The Jig Solver application is public at `https://jigsolver.app/`, while its
+ source repository is private. Link to the product, not the private repository.
+- Do not publish residential addresses or personal phone numbers.
+
+## Git workflow
+
+- Commit messages use Conventional Commits and are written in English.
+- Coding agents may create a commit only after an explicit user request in the
+ current conversation.
+- Authorization covers only changes the user has already reviewed. Any later
+ edit requires another review and explicit commit request.
+- Lefthook runs `pnpm lint` before a commit.
+- Commitlint validates the commit message in the `commit-msg` hook.
+- Never bypass repository hooks with `--no-verify`.
+
+## Continuous integration and deployment
+
+- Pull requests and pushes to `main` or `master` run type checking, Biome,
+ convention checks, unit tests, responsive browser tests, and a production
+ build.
+- Deployment to GitHub Pages starts only after the `CI` workflow succeeds for a
+ trusted push to `main` or `master`.
+- Vite outputs static files to `dist`; GitHub Actions publishes that artifact.
+- Use `base: '/'` for the selected `LucasPMM.github.io` repository name.
+- Keep the workflow permissions minimal: repository contents read, Pages write,
+ and ID token write for the deploy job.
+- Reference maintained GitHub Actions by their stable major release tag so
+ compatible fixes are adopted without obscuring the workflow.
+
+## Documentation synchronization
+
+- Update the roadmap when scope or completion state changes.
+- Update `docs/DESIGN.md` when a visual token or invariant changes.
+- Update README setup and deployment instructions with the implementation that
+ introduces them.
+- A behavior change is not complete while its tests or documentation are stale.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
new file mode 100644
index 0000000..1ea165a
--- /dev/null
+++ b/docs/ROADMAP.md
@@ -0,0 +1,855 @@
+# Lucas Mariz Portfolio Modernization Roadmap
+
+> Updated on September 11, 2026. User-provided career and education details are
+> the primary source of truth. Public GitHub, LinkedIn, the local Planner
+> repository, and the local Jig Solver repository provide supporting technical
+> context.
+
+## 1. Product objective
+
+Replace the 2018 résumé page with a fast, accessible, multilingual personal
+portfolio that answers four questions in under one minute:
+
+1. Who is Lucas Mariz today?
+2. What professional and academic path brought him here?
+3. What problems, technologies, and projects does he work on?
+4. How can a visitor learn more or get in touch?
+
+The product remains intentionally small: a statically generated site with no
+backend or CMS, deployed automatically to GitHub Pages.
+
+## 2. Current repository assessment
+
+### Preserve
+
+- The Git history and the narrative value of modernizing the original résumé.
+- A single-page, scannable experience.
+- The design system in `docs/DESIGN.md`: warm paper surfaces, editorial
+ typography, generous spacing, rounded cards, and a restrained ember accent.
+
+### Replace or correct
+
+- Bulma 0.7.1, Font Awesome 5, and legacy CDN dependencies.
+- Invalid HTML, icon-only semantics, and missing social/SEO metadata.
+- The outdated student-only biography and incorrect education dates.
+- Placeholder project copy and skill lists without context or evidence.
+- The local 2018 portrait.
+- The residential address and personal phone number. The public site should
+ show at most city/country and professional contact channels.
+- The missing professional timeline, responsive system, theme handling,
+ localization, tests, and deployment pipeline.
+
+## 3. Confirmed content source of truth
+
+### Public identity
+
+- **Display name:** Lucas Mariz.
+- **Primary role:** Senior Software Engineer.
+- **Professional focus:** JavaScript, TypeScript, React, React Native, scalable
+ product architecture, automation, and testability.
+- **Current technical interests:** Computer Vision, applied AI, optimization,
+ software architecture, developer experience, and automation.
+
+### Professional experience
+
+#### Senior Software Engineer — ABILITYA
+
+- Employment: full-time.
+- Period: June 2020 to present.
+- Location: Milan, Lombardy, Italy — remote.
+- Core experience: JavaScript, TypeScript, React, and React Native.
+- Responsibilities and impact areas:
+ - evolve scalable white-label application architecture;
+ - build engineering and product automations;
+ - improve testability across the React web/mobile ecosystem;
+ - establish end-to-end coverage with Cypress and Maestro.
+ - share engineering responsibility for the official Cagliari Calcio app,
+ serving more than 10,000 users across Android and iOS.
+
+The portfolio must calculate duration from canonical dates instead of storing a
+text value such as “6 years 4 months.” Quantitative evidence still needs to be
+collected, for example the number of brands/tenants, release-frequency change,
+test coverage, execution-time reduction, or escaped-defect reduction. No metric
+may be invented.
+
+#### Frontend Developer — Pluritech Brasil
+
+- Period: September 2018 to June 2020.
+- Public location: Belo Horizonte, Brazil. Do not publish the street address
+ from the old LinkedIn entry.
+- Core experience: Angular and Ionic.
+- Content angle: early professional experience building cross-platform web and
+ mobile interfaces before moving into the React ecosystem.
+
+### Selected professional and academic work
+
+#### Cagliari Calcio official app
+
+- Lucas is one of the engineers responsible for the official supporter app as
+ part of his work at ABILITYA.
+- Owner-provided reach: more than 10,000 users across Android and iOS.
+- Public evidence: the official product is available on the
+ [App Store](https://apps.apple.com/it/app/cagliari-calcio/id441230439) and
+ [Google Play](https://play.google.com/store/apps/details?id=com.cagliaricalcioapp.net).
+- Portfolio framing: product responsibility, cross-platform delivery, and a
+ live supporter experience. Do not imply sole ownership.
+
+#### Role Vectors paper
+
+- Title: **Role Vectors: Tracking-Based Representations of Football Players’
+ Tactical Behavior**.
+- Lucas is a co-author. The paper introduces an interpretable,
+ tracking-derived representation of football players' relative tactical roles,
+ including in-possession and off-ball behavior.
+- Accepted and presented at the 13th Workshop on Machine Learning and Data
+ Mining for Sports Analytics (MLSA 2026), in Naples, Italy, on September 7,
+ 2026.
+- Public evidence: [paper](https://dtai.cs.kuleuven.be/events/MLSA26/papers/MLSA26_paper_208.pdf)
+ and [conference schedule](https://dtai.cs.kuleuven.be/events/MLSA26/schedule.php).
+
+### Academic background
+
+- **Bachelor's degree, Computer Science — UFMG:** January 2023 to 2027,
+ expected graduation in 2027.
+- **Bachelor's degree, Computational Mathematics — UFMG:** January 2019 to
+ January 2023, completed.
+- **IT Technician — Colégio Técnico da UFMG (COLTEC):** 2015 to 2017,
+ completed.
+
+### Languages
+
+- Portuguese: native.
+- English: Cambridge C1.
+- French: currently learning; do not claim a CEFR level.
+
+### Remaining content inputs
+
+- Official Cambridge certificate name, issue year, and optional credential URL.
+- One or two additional nonconfidential quantitative outcomes from ABILITYA,
+ beyond the confirmed 10,000+ Cagliari app audience.
+- A concise Pluritech product/context description and one representative
+ outcome.
+- Preferred public contact channel: LinkedIn, professional email, or both.
+- Whether a downloadable PDF résumé belongs in the first release.
+- Native or professional review of the French catalog before public release.
+
+## 4. Curated project showcase
+
+Project selection is editorial and must not follow GitHub update time, stars, or
+API order. The initial showcase is fixed to these six projects:
+
+1. Jig Solver.
+2. Planner.
+3. Simplex.
+4. Pokémon Base.
+5. Greedy K-means.
+6. LZ78 Compression.
+
+The GitHub account may be reorganized independently. Removing unrelated public
+repositories must not change portfolio order or copy.
+
+### 4.1 Jig Solver — primary case study
+
+#### Verified facts
+
+- Public product: [jigsolver.app](https://jigsolver.app/).
+- Source repository: `LucasPMM/jig-solver`, currently private. The portfolio
+ links to the product, not the private repository.
+- Product purpose: solve physical jigsaw puzzles computationally and assist a
+ person assembling the physical puzzle through a camera-oriented workflow.
+- Web stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, TanStack Query,
+ Three.js, Zod, Vitest, and Playwright.
+- API and vision stack: Python 3.12, FastAPI, SQLAlchemy, Alembic, PostgreSQL,
+ MinIO, NumPy, OpenCV, optional MobileSAM/PyTorch, Pytest, and Ruff.
+- Delivery stack: pnpm workspace, Docker Compose, Caddy, Biome, Lefthook, and
+ Commitlint.
+- Current stage: the reference-guided solver and the camera assistant tracks
+ documented as phases 3 and 4 are complete. The product already includes
+ piece analysis, ranked compatibility, persisted placements, an asynchronous
+ solver lifecycle, a Three.js workspace, multi-piece camera analysis, stable
+ tracking/identity, border detection, guidance, physical confirmations, and
+ benchmark coverage. Further product and quality work remains active.
+
+#### Portfolio treatment
+
+- Label: **Active project**.
+- Primary link: `https://jigsolver.app/`.
+- Short description:
+ > A computer-vision platform that digitizes physical jigsaw pieces, evaluates
+ > compatibility, reconstructs the puzzle, and guides physical assembly
+ > through a camera-based assistant.
+- Explain the engineering split: independent Python solving/vision engine,
+ statically typed web client, and mobile-first camera experience.
+- Mention the research dimensions: segmentation, contour/side analysis,
+ descriptors, compatibility ranking, reconstruction, tracking, and evaluation.
+- Use the existing solver workspace screenshot as the primary visual:
+ `../../ufmg/jig-solver/apps/web/public/docs/solver-workspace.png`.
+- During implementation, copy and optimize that image into
+ `public/projects/jig-solver/solver-workspace.webp`. Keep the original project
+ screenshot and portfolio derivative synchronized when visible UI changes.
+- The optional secondary visual for a future case-study view is
+ `../../ufmg/jig-solver/apps/web/public/docs/assistant-borders.png`.
+
+### 4.2 Planner — private financial assistant
+
+#### Verified facts
+
+- Source repository and application: private; the portfolio must not link to
+ either one.
+- Product purpose: an installable personal-finance dashboard for recurring
+ expenses, income, cash flow, debts, financing scenarios, goals, reports, and
+ read-only AI guidance.
+- Web stack: Next.js 16, React 19, TypeScript, Tailwind CSS 4, React Hook Form,
+ Zod, Recharts, jsPDF, and static export/PWA delivery.
+- Platform stack: Firebase Authentication, Firestore, callable Firebase
+ Functions, and Secret Manager.
+- Assistant architecture: provider-neutral BYOK integration with bounded,
+ deterministic financial summaries; stored credentials are protected with
+ AES-256-GCM rather than exposed to the browser or model context.
+
+#### Portfolio treatment
+
+- Label: **Private project**.
+- Describe the problem, architecture, Lucas's end-to-end contribution, and the
+ integrated product result without exposing private URLs, identifiers,
+ credentials, screenshots, or internal operational details.
+- Use technology names only when confirmed by the local source.
+
+### 4.3 Public repositories
+
+| Display name | Repository | Known focus | Published evidence |
+|---|---|---|---|
+| Simplex | [`simplex`](https://github.com/LucasPMM/simplex) | Python | Two-phase tableau, Bland's rule, certificates, and fixture coverage |
+| Pokémon Base | [`Pokemon-Base`](https://github.com/LucasPMM/Pokemon-Base) | TypeScript | Routed Angular architecture, data wrapper, views, charts, and tests |
+| Greedy K-means | [`greedy-kmeans`](https://github.com/LucasPMM/greedy-kmeans) | Jupyter/Python | Ten-dataset method comparison, evaluation metrics, and result tables |
+| LZ78 Compression | [`lz78-compression`](https://github.com/LucasPMM/lz78-compression) | Python | Compressed trie, round-trip CLI flows, test corpus, and report |
+
+Each card should communicate problem, approach, Lucas's contribution, and
+result. Technology tags are supporting metadata, not the description.
+
+## 5. Technical stack decision
+
+### Recommendation: Preact + TypeScript + Vite
+
+- **Preact** provides component composition, hooks, and a React-like API with a
+ small client footprint.
+- **TypeScript** validates experience, education, project, locale, and theme
+ models.
+- **Vite** provides fast development, static production builds, and a direct
+ GitHub Pages deployment path.
+- **i18next core** provides `en`, `pt-BR`, and `fr`. A small Preact adapter
+ subscribes to `languageChanged`; adding React compatibility solely for
+ localization is unnecessary.
+- **Native CSS** implements the existing tokens. Tailwind and a component
+ framework would add another abstraction without solving a portfolio-specific
+ problem.
+- **Biome** is the only JavaScript, TypeScript, JSON, and CSS lint/format tool.
+- **Vitest + Testing Library** cover behavior; **Playwright** covers locale,
+ theme, responsive layout, external links, and avatar fallback.
+- **GitHub Actions + GitHub Pages** run quality checks and deploy `dist`.
+
+No backend, database, CMS, or client-side secret is required. No router is
+required for the one-page MVP. Preact's Vite prerender support can emit useful
+HTML at build time without introducing a server runtime.
+
+### Repository tooling baseline
+
+Adapt the Planner repository conventions:
+
+- Node.js 24 pinned in `.nvmrc`, `.node-version`, `package.json`, and CI.
+- pnpm as the only package manager, with a preinstall guard.
+- A root `biome.json` as the only formatter/linter configuration.
+- `scripts/check-code-conventions.mjs` to enforce the agreed TypeScript control
+ flow rules.
+- Lefthook running `pnpm lint` before commits.
+- Commitlint enforcing English Conventional Commits.
+- `pnpm check` as the minimum handoff gate.
+- No commits from coding agents without explicit, current user authorization.
+
+Repository rules are recorded in `AGENTS.md` and
+`docs/ENGINEERING_CONVENTIONS.md`.
+
+## 6. Component architecture
+
+Follow the Planner component ownership model: every component has a PascalCase
+folder, a matching source file, an `index.ts` barrel, and a focused test when it
+owns behavior. A component used by one parent is nested under that parent's
+`components` folder.
+
+```text
+.
+├── .github/workflows/
+│ ├── ci.yml
+│ └── deploy.yml
+├── public/
+│ ├── projects/jig-solver/solver-workspace.webp
+│ ├── avatar-fallback.svg
+│ ├── favicon.svg
+│ ├── robots.txt
+│ └── sitemap.xml
+├── scripts/
+│ ├── check-code-conventions.mjs
+│ ├── require-pnpm.mjs
+│ └── verify-static-build.mjs
+├── src/
+│ ├── components/
+│ │ ├── PortfolioPage/
+│ │ │ ├── PortfolioPage.tsx
+│ │ │ ├── index.ts
+│ │ │ └── components/
+│ │ │ ├── AppHeader/
+│ │ │ ├── HeroSection/
+│ │ │ ├── AboutSection/
+│ │ │ ├── ExperienceSection/
+│ │ │ ├── SelectedWorkSection/
+│ │ │ ├── EducationSection/
+│ │ │ ├── ProjectsSection/
+│ │ │ │ └── components/
+│ │ │ │ ├── ProjectCard/
+│ │ │ │ └── ProjectDetails/
+│ │ │ ├── CapabilitiesSection/
+│ │ │ └── ContactSection/
+│ │ └── ui/
+│ │ ├── ExternalLink/
+│ │ ├── LanguageSwitcher/
+│ │ ├── SectionHeading/
+│ │ └── ThemeSwitcher/
+│ ├── content/
+│ │ ├── education.ts
+│ │ ├── experience.ts
+│ │ ├── profile.ts
+│ │ └── projects.ts
+│ ├── lib/
+│ │ ├── i18n/
+│ │ │ ├── catalog.ts
+│ │ │ ├── i18n.ts
+│ │ │ ├── locale.ts
+│ │ │ ├── index.ts
+│ │ │ └── components/I18nProvider/
+│ │ └── theme/
+│ │ ├── theme.ts
+│ │ ├── index.ts
+│ │ └── components/ThemeProvider/
+│ ├── styles/
+│ │ ├── base.css
+│ │ ├── components.css
+│ │ └── tokens.css
+│ ├── App.tsx
+│ ├── main.tsx
+│ └── prerender.tsx
+├── AGENTS.md
+├── biome.json
+├── commitlint.config.mjs
+├── lefthook.yml
+├── vite.config.ts
+└── package.json
+```
+
+Stable neutral data—dates, links, technology identifiers, and project order—
+lives in typed content modules. Every visitor-facing sentence lives in the
+single typed translation catalog. This prevents three career histories from
+drifting apart.
+
+## 7. Information architecture and draft content
+
+### 7.1 Header
+
+- “LM” monogram and Lucas Mariz.
+- Anchor navigation: About, Experience, Selected work, Education, Projects,
+ Contact.
+- Visible language and theme controls.
+- A compact accessible mobile menu when anchor links no longer fit.
+
+### 7.2 Hero
+
+- Circular GitHub profile image.
+- Eyebrow: `SENIOR SOFTWARE ENGINEER · REMOTE`.
+- Proposed English headline:
+ **“I build scalable digital products and explore how AI solves visual
+ problems.”**
+- Summary: senior web/mobile engineering in the JavaScript ecosystem, backed by
+ Computational Mathematics, Computer Science, and applied Computer Vision.
+- Chips: Web & Mobile, White-label Platforms, Computer Vision, Applied AI.
+- One ember CTA: “View projects.” Secondary links: GitHub and LinkedIn.
+
+### 7.3 About
+
+Use one short editorial paragraph connecting:
+
+- senior product engineering;
+- React and React Native delivery;
+- white-label architecture and automation;
+- mathematical/computer-science education;
+- current Computer Vision and optimization work.
+
+Avoid unsupported adjectives and generic soft-skill claims.
+
+### 7.4 Experience
+
+Use a reverse-chronological timeline. Each entry contains role, company,
+location/remote status, canonical dates, one problem statement, and two or three
+impact bullets.
+
+ABILITYA is the primary entry and emphasizes scale, white-label architecture,
+automation, and Cypress/Maestro testability. Pluritech establishes the Angular
+and Ionic foundation of the web/mobile career.
+
+### 7.5 Selected work
+
+- A production-product card for the official Cagliari Calcio app with the
+ owner-provided 10,000+ audience metric, a precise shared-responsibility claim,
+ and links to both official stores.
+- A research card for the Role Vectors paper with its exact title, concise
+ abstract-derived summary, MLSA 2026 presentation details, paper link, and
+ schedule link.
+
+### 7.6 Education
+
+- Computer Science — UFMG — 2023–2027, expected.
+- Computational Mathematics — UFMG — 2019–2023, completed.
+- IT Technician — COLTEC/UFMG — 2015–2017, completed.
+
+### 7.7 Projects
+
+- A large Jig Solver case-study card with the real solver screenshot and a link
+ to the public application.
+- A private-project card for Planner, with no product or source link.
+- Four public-repository cards for Simplex, Pokémon Base, Greedy K-means, and
+ LZ78 Compression.
+- Never show a GitHub link for the private Jig Solver repository.
+- If a public repository is removed during GitHub cleanup, either preserve a
+ stable public case-study URL or remove its card intentionally. Do not leave a
+ broken link.
+
+### 7.8 Skills and languages
+
+Group capabilities by context; do not use percentage bars:
+
+- **Product engineering:** JavaScript, TypeScript, React, React Native, Angular,
+ Ionic.
+- **Quality and delivery:** Cypress, Maestro, automation, testability, GitHub
+ Actions.
+- **Applied research:** Python, OpenCV, Computer Vision, AI, optimization.
+- **Languages:** Portuguese — native; English — Cambridge C1; French — learning.
+
+### 7.9 Contact
+
+- Invite conversations about software engineering, mobile products, Computer
+ Vision, applied AI, and product ideas.
+- Link GitHub and LinkedIn.
+- Add an email only after confirming it as a public professional channel.
+- Do not add a contact form in the MVP; it would introduce an external service,
+ spam handling, and a privacy surface.
+
+## 8. Screen outline
+
+### Desktop
+
+```text
+┌──────────────────────────────────────────────────────────────────────┐
+│ LM Lucas Mariz About Experience Education Projects EN Theme │
+├──────────────────────────────────────────────────────────────────────┤
+│ [ GitHub photo ] │
+│ SENIOR SOFTWARE ENGINEER · REMOTE │
+│ I build scalable digital products and explore how AI solves │
+│ visual problems. │
+│ [Web & Mobile] [White-label] [Computer Vision] [Applied AI] │
+│ [ View projects ] GitHub LinkedIn │
+├──────────────────────────────────────────────────────────────────────┤
+│ About short editorial introduction │
+├──────────────────────────────────────────────────────────────────────┤
+│ Experience Education │
+│ ● ABILITYA · 2020–present ● Computer Science · 2023–2027 │
+│ │ scale · automation · E2E ● Computational Math · 2019–2023 │
+│ ● Pluritech · 2018–2020 ● COLTEC · 2015–2017 │
+├──────────────────────────────────────────────────────────────────────┤
+│ Active project │
+│ ┌────────────────────── JIG SOLVER ────────────────────────────────┐ │
+│ │ product + architecture + real solver screenshot + public link │ │
+│ └──────────────────────────────────────────────────────────────────┘ │
+│ ┌──── Planner ────┐ ┌──── Simplex ─────┐ │
+│ └─────────────────┘ └───────────────────┘ │
+│ ┌─ Pokémon Base ─┐ ┌─ Greedy K-means ─┐ │
+│ └─────────────────┘ └───────────────────┘ │
+│ ┌─ LZ78 Compression ───────────────────┐ │
+│ └───────────────────────────────────────┘ │
+├──────────────────────────────────────────────────────────────────────┤
+│ Skills · Languages · Selected certification │
+├──────────────────────────────────────────────────────────────────────┤
+│ Let's talk GitHub · LinkedIn · PDF │
+└──────────────────────────────────────────────────────────────────────┘
+```
+
+### Mobile
+
+```text
+┌──────────────────────────┐
+│ LM Lucas Mariz EN ◐ ☰ │
+│ [GitHub photo] │
+│ Senior Software Engineer│
+│ headline in 3–4 lines │
+│ wrapping chips │
+│ [ View projects ] │
+│ GitHub · LinkedIn │
+├──────────────────────────┤
+│ About │
+├──────────────────────────┤
+│ Experience │
+│ vertical timeline │
+├──────────────────────────┤
+│ Education │
+├──────────────────────────┤
+│ Jig Solver │
+│ [real solver image] │
+│ product + architecture │
+├──────────────────────────┤
+│ five supporting cards │
+│ one per row │
+├──────────────────────────┤
+│ Skills · Languages │
+├──────────────────────────┤
+│ Contact │
+└──────────────────────────┘
+```
+
+## 9. Applying the design rules
+
+### Light theme
+
+- Canvas: `#fdf9f4`, never cold white at page level.
+- Elevated surfaces: `#ffffff`; dust cards/chips: `#f9efe4`.
+- Text: `#2d2f34`, `#3f434a`, and `#5e646e`.
+- Ember `#f67748` on less than five percent of the page: primary CTA, one
+ editorial scribble, and one selected/highlight state.
+- Lora for headings and Public Sans for body copy, using the documented free
+ substitutes for Garnett and UniversalSans.
+- Maximum width 1200 px, 96 px section rhythm, and 32 px editorial cards.
+
+`docs/DESIGN.md` contains a CTA text-color contradiction: one section requests
+white and another requests black. Accessibility resolves it. White on
+`#f67748` is approximately 2.74:1, while `#2d2f34` on `#f67748` is
+approximately 4.88:1. Use ink-colored CTA text.
+
+### Dark theme
+
+The dark palette should still feel like paper and ink, not a blue/black
+dashboard:
+
+| Semantic token | Light | Dark proposal |
+|---|---|---|
+| canvas | `#fdf9f4` | `#1b1917` |
+| elevated surface | `#ffffff` | `#24211f` |
+| dust surface | `#f9efe4` | `#302a26` |
+| border | `#ecedef` | `#47413d` |
+| primary text | `#2d2f34` | `#f5eee7` |
+| secondary text | `#5e646e` | `#c9beb4` |
+| ember | `#f67748` | `#ff895e` |
+
+### Interaction and accessibility
+
+- Use approximately 130 ms transitions and respect `prefers-reduced-motion`.
+- Do not hide content behind entrance animations.
+- Provide a skip link, ordered headings, landmarks, and visible focus.
+- Keep touch targets at least 44 by 44 pixels.
+- Never rely on color alone for status.
+- Test full keyboard navigation, 200 percent zoom, and reduced motion.
+- Give the avatar intrinsic dimensions, localized alt text, and a local
+ fallback.
+
+## 10. Browser-derived language and theme
+
+### Locale resolution
+
+On the first visit:
+
+1. Read `navigator.languages`, then `navigator.language`.
+2. Match an exact supported tag when possible.
+3. Match a supported base language when appropriate.
+4. Fall back to **English** when no browser locale is available or supported.
+
+After an explicit visitor choice, persist and prefer that override. Update
+`document.documentElement.lang`, title, description, Open Graph copy, visible
+content, and accessibility labels together.
+
+Supported catalogs:
+
+- `en` — product fallback.
+- `pt-BR` — native-language version.
+- `fr` — learning-language version; requires human review before release.
+
+Use language names—English, Português, Français—rather than flags.
+
+### Theme resolution
+
+On the first visit:
+
+1. Resolve `prefers-color-scheme` when supported.
+2. Use light or dark according to the browser/operating-system preference.
+3. Fall back to **light** when the preference is unavailable or unresolved.
+
+Persist an explicit visitor override. An `auto` option removes the override and
+resumes system detection. Apply the resolved theme in a small head script before
+first paint to prevent a theme flash.
+
+Tests must cover exact locale matching, base-language matching, unsupported
+locale fallback, missing browser APIs, stored overrides, system theme changes,
+and invalid persisted values.
+
+## 11. GitHub avatar and repository metadata
+
+Use the stable username endpoint for the profile image:
+
+```tsx
+
+```
+
+If the request fails, replace it once with `/avatar-fallback.svg`. Avoid an
+error loop and reserve dimensions to prevent layout shift.
+
+Project content remains local and curated. Optional GitHub metadata enrichment
+may happen during the GitHub Actions build, store only non-sensitive fields,
+and degrade without blocking the build. Do not expose a token or rely on a
+runtime API request.
+
+## 12. Execution roadmap
+
+### Phase 0 — governance, content, and privacy (P0) — complete
+
+- Add the adapted repository instructions and engineering conventions.
+- Convert existing/new technical documentation to English when touched.
+- Confirm the remaining content inputs in section 3.
+- Remove the residential address and personal phone number.
+- Preserve canonical career dates and calculate durations at runtime.
+- Record confirmed source content and identify evidence gaps for the deeper
+ project pass in phase 4.
+
+**Exit:** English repository governance exists, approved source content is
+structured, and unnecessary personal data is excluded.
+
+### Phase 1 — technical foundation (P0) — complete
+
+- Create Preact + TypeScript + Vite on a modernization branch.
+- Pin Node.js 24 and pnpm; add the package-manager guard.
+- Configure Biome, convention checking, Vitest, Playwright, Lefthook, and
+ Commitlint.
+- Add the Planner-style component folder/barrel convention.
+- Add typed content schemas and the typed i18next catalog.
+- Keep all work uncommitted until Lucas reviews it and explicitly requests a
+ commit.
+
+**Exit:** `pnpm check` and `pnpm build` produce a valid static site shell.
+
+### Phase 2 — design system and responsive composition (P0) — complete
+
+- Convert `docs/DESIGN.md` tokens into semantic CSS custom properties.
+- Implement the component tree from section 6.
+- Build the header, hero, timelines, cards, and footer mobile-first.
+- Apply Lora/Public Sans, the 96 px section rhythm, and 32 px editorial cards.
+- Add accessible focus, interaction, image, and error states.
+
+**Exit:** the complete English layout works from 320 to 1440 px.
+
+### Phase 3 — browser defaults, theme, and i18n (P0) — complete
+
+- Implement browser-locale detection with English fallback.
+- Implement system-theme detection with light fallback.
+- Persist explicit overrides and prevent first-paint theme flash.
+- Complete `en`, `pt-BR`, and `fr` catalogs with key parity tests.
+- Update document language and localized metadata.
+- Keep the French catalog ready for a final native or professional language
+ review before public release.
+
+**Exit:** every control and content section works in three languages and two
+themes, including missing/unsupported browser preference paths.
+
+### Phase 4 — deeper career and project evidence (P1) — complete
+
+- Publish the confirmed ABILITYA and Pluritech evidence while retaining the
+ remaining nonconfidential metric gaps for a later content update.
+- Deepen the six fixed showcase projects beyond their initial summaries.
+- Preserve the Cagliari and Role Vectors evidence cards and verify their public
+ links during release checks.
+- Build the Jig Solver feature card with its public URL, verified architecture,
+ private-source treatment, and optimized real screenshot.
+- Add Planner as a private financial-assistant project using only stack and
+ product facts verified in the local repository; expose no private link.
+- Add concise evidence-based summaries for the four public repositories.
+- Keep optional build-time GitHub metadata out of the critical rendering path.
+
+**Exit:** each project explains problem, approach, contribution, and result;
+private links and invented metrics are absent.
+
+### Phase 5 — quality, SEO, and performance (P0) — complete
+
+- Add localized Open Graph metadata, favicon, canonical URL, sitemap, and
+ `robots.txt`.
+- Prerender useful page content before JavaScript hydration.
+- Test keyboard navigation, contrast, 200 percent zoom, reduced motion, and
+ avatar fallback.
+- Run responsive Playwright checks in English, Portuguese, and French.
+- Lighthouse mobile targets: Performance at least 95 and Accessibility, Best
+ Practices, and SEO at least 95.
+
+**Exit:** `pnpm check`, production build, browser tests, and accessibility
+review pass.
+
+Completion evidence on September 11, 2026:
+
+- The production build prerenders the English fallback page before hydration
+ and passes the static-output verification script.
+- The Chromium suite passes in desktop and mobile projects, covering Axe,
+ locales and metadata, theme persistence, avatar resilience, verified public
+ links, private Planner link isolation, responsive overflow, keyboard entry,
+ reduced motion, and 200 percent text sizing.
+- Lighthouse mobile scores: Performance 98, Accessibility 100, Best Practices
+ 100, and SEO 100. Desktop scores 100 in all four categories.
+
+### Phase 6 — rename and deploy (P0) — implementation complete, activation pending
+
+- The Vite base path, canonical URL, Open Graph URL, sitemap, and `robots.txt`
+ target `https://lucaspmm.github.io/`.
+- A least-privilege GitHub Pages workflow publishes only after `CI` succeeds
+ for a trusted push to `main` or `master`.
+- GitHub Actions use stable major release tags, and the deployment rebuilds the
+ exact portfolio revision accepted by CI.
+- README and this runbook document the rename, Pages-source migration, remote
+ update, metadata update, and public smoke test.
+- Lucas still needs to rename the repository, switch Pages from the current
+ legacy branch source to GitHub Actions, merge the branch, and update external
+ profile links.
+
+**Exit:** the renamed site is live and all public links use the new URL.
+
+**Current state:** every repository-side prerequisite is implemented. The exit
+condition remains pending until the external activation checklist in section 14
+is completed and the public URL passes its smoke test.
+
+## 13. Selected repository name
+
+### `LucasPMM.github.io`
+
+This makes the repository the GitHub Pages user site and produces the clean URL
+`https://lucaspmm.github.io/`. It also allows Vite `base: '/'`. The technical
+repository name does not change the public title “Lucas Mariz.”
+
+The name is available as of September 11, 2026. The existing `Curriculum`
+repository is public, uses `master` as its default branch, and currently serves
+Pages from the repository root in legacy branch mode.
+
+## 14. Safe rename tutorial
+
+The current remote is `git@github.com:LucasPMM/Curriculum.git`, and the current
+local default branch is `master`.
+
+### A. Prepare
+
+1. Review and explicitly authorize the phase 6 implementation commit.
+2. Ensure the new build is versioned and no local work is forgotten.
+3. Record the old URL `https://lucaspmm.github.io/Curriculum/`. GitHub does not
+ automatically redirect project-site URLs when a repository is renamed.
+4. If preserving the old URL is critical, configure a custom domain before the
+ rename. Otherwise, plan to update every public link.
+
+### B. Rename on GitHub
+
+1. Open `LucasPMM/Curriculum`.
+2. Go to **Settings → General**.
+3. Set **Repository name** to `LucasPMM.github.io`.
+4. Confirm **Rename**.
+
+GitHub redirects repository web traffic and Git operations, but the Pages URL
+is the important exception.
+
+### C. Update the local clone
+
+For the selected name:
+
+```bash
+git remote set-url origin git@github.com:LucasPMM/LucasPMM.github.io.git
+git remote -v
+```
+
+The deployment workflow supports the current `master` branch as well as `main`.
+Optionally standardize the primary branch later:
+
+```bash
+git branch -m master main
+git push -u origin main
+```
+
+After the push, change the default branch to `main` under **Settings →
+Branches**. Treat this as an independent migration and remove the old remote
+branch only after `main`, CI, and Pages are verified.
+
+### D. Configure Pages
+
+1. Under **Settings → Pages → Build and deployment**, change **Source** from
+ the current legacy branch configuration to **GitHub Actions**.
+2. Merge `feat/portfolio-modernization` into the default branch. The `CI`
+ workflow will validate the merge, and its successful push run will trigger
+ `.github/workflows/deploy.yml`.
+3. Confirm that the deployment environment is named `github-pages` and that
+ its branch protection permits only the default branch.
+4. In the repository **About** settings, use:
+ - description:
+ `Senior software engineer portfolio, selected work, and applied research.`
+ - website: `https://lucaspmm.github.io/`
+ - topics: `portfolio`, `preact`, `typescript`, `vite`, `github-pages`.
+5. Set the GitHub profile Website and the relevant LinkedIn link to
+ `https://lucaspmm.github.io/`.
+
+### E. Verify
+
+1. Confirm the `CI` and `Deploy to GitHub Pages` workflows are green.
+2. Open the public URL in a private browser window.
+3. Verify direct loading, assets, anchor navigation, avatar fallback, external
+ links, locale, theme, and mobile layout.
+4. Update LinkedIn and any other source still pointing to `/Curriculum/`.
+
+## 15. Definition of done
+
+- Repository governance and technical documentation are in English.
+- Source code and comments are in English; visitor copy is localized.
+- No residential address or personal phone number is shipped.
+- Career and education dates match section 3, with expected graduation in 2027.
+- ABILITYA and Pluritech descriptions are accurate and contain no invented
+ metrics.
+- The showcase order is Jig Solver, Planner, Simplex, Pokémon Base, Greedy
+ K-means, and LZ78 Compression.
+- Jig Solver links only to the public application and uses a real optimized
+ screenshot.
+- The profile image comes from GitHub and has a local fallback.
+- A first visit uses the browser locale or English fallback.
+- A first visit uses the system color scheme or light fallback.
+- Explicit locale/theme choices persist and invalid values recover safely.
+- English, Brazilian Portuguese, and French catalogs have key parity.
+- Both themes pass contrast and preserve the documented design system.
+- The site works by keyboard, at 320 px, at 200 percent zoom, and with reduced
+ motion.
+- Biome is the only web formatter/linter, and `pnpm check` passes.
+- Commit messages are English Conventional Commits, hooks are not bypassed, and
+ coding agents do not commit without explicit authorization.
+- GitHub Actions deploys only after all quality checks pass.
+- README, canonical, Open Graph, and static discovery files use the final URL.
+- After activation, the repository metadata, GitHub profile, and LinkedIn use
+ the final URL.
+
+## Technical references
+
+- [GitHub repository profile](https://github.com/LucasPMM)
+- [Lucas Mariz LinkedIn profile](https://www.linkedin.com/in/lucas-mariz-4845b6164/)
+- [Jig Solver public application](https://jigsolver.app/)
+- [Renaming a repository — GitHub Docs](https://docs.github.com/en/repositories/creating-and-managing-repositories/renaming-a-repository)
+- [Deploying a Vite site to GitHub Pages](https://vite.dev/guide/static-deploy.html#github-pages)
+- [Custom workflows for GitHub Pages](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages)
+- [Preact getting started](https://preactjs.com/guide/v10/getting-started/)
+- [Preact Vite prerendering](https://preactjs.com/blog/prerendering-preset-vite/)
+- [i18next configuration and fallback](https://www.i18next.com/overview/configuration-options)
diff --git a/e2e/portfolio.spec.ts b/e2e/portfolio.spec.ts
new file mode 100644
index 0000000..d54a1ef
--- /dev/null
+++ b/e2e/portfolio.spec.ts
@@ -0,0 +1,164 @@
+import AxeBuilder from '@axe-core/playwright'
+import { expect, test } from '@playwright/test'
+
+const localeCases = [
+ {
+ locale: 'en',
+ languageLabel: 'Language',
+ experienceHeading: 'Professional experience',
+ pageTitle: 'Lucas Mariz — Senior Software Engineer',
+ openGraphLocale: 'en_US',
+ },
+ {
+ locale: 'pt-BR',
+ languageLabel: 'Language',
+ experienceHeading: 'Experiência profissional',
+ pageTitle: 'Lucas Mariz — Engenheiro de Software Sênior',
+ openGraphLocale: 'pt_BR',
+ },
+ {
+ locale: 'fr',
+ languageLabel: 'Language',
+ experienceHeading: 'Expérience professionnelle',
+ pageTitle: 'Lucas Mariz — Ingénieur logiciel senior',
+ openGraphLocale: 'fr_FR',
+ },
+] as const
+
+test.beforeEach(async ({ page }) => {
+ await page.addInitScript(() => {
+ const resetMarker = 'portfolio.test-storage-reset'
+ if (window.sessionStorage.getItem(resetMarker)) return
+ window.localStorage.clear()
+ window.sessionStorage.setItem(resetMarker, 'true')
+ })
+})
+
+test('renders without automated accessibility violations', async ({ page }) => {
+ await page.goto('/')
+ const results = await new AxeBuilder({ page }).analyze()
+ expect(results.violations).toEqual([])
+})
+
+test('uses browser language and color-scheme defaults on the first visit', async ({ browser }) => {
+ const context = await browser.newContext({ locale: 'pt-BR', colorScheme: 'dark' })
+ const page = await context.newPage()
+ await page.goto('/')
+ await expect(page.locator('html')).toHaveAttribute('lang', 'pt-BR')
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')
+ await expect(page.getByRole('heading', { name: 'Experiência profissional' })).toBeVisible()
+ await context.close()
+})
+
+localeCases.forEach(({ locale, languageLabel, experienceHeading, pageTitle, openGraphLocale }) => {
+ test(`synchronizes content and metadata for ${locale}`, async ({ page }) => {
+ await page.goto('/')
+ await page.getByLabel(languageLabel).selectOption(locale)
+ await expect(page.getByRole('heading', { name: experienceHeading })).toBeVisible()
+ await expect(page.locator('html')).toHaveAttribute('lang', locale)
+ await expect(page).toHaveTitle(pageTitle)
+ await expect(page.locator('meta[property="og:locale"]')).toHaveAttribute(
+ 'content',
+ openGraphLocale,
+ )
+ })
+})
+
+test('persists an explicit dark theme', async ({ page }) => {
+ await page.goto('/')
+ await page.getByLabel('Theme').selectOption('dark')
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')
+ await page.reload()
+ await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark')
+})
+
+test('falls back to the local avatar when GitHub is unavailable', async ({ page }) => {
+ await page.route('**/LucasPMM.png**', (route) =>
+ route.fulfill({ status: 503, contentType: 'text/plain', body: 'Unavailable' }),
+ )
+ await page.goto('/')
+ await expect(page.getByRole('img', { name: 'Lucas Mariz' })).toHaveAttribute(
+ 'src',
+ /avatar-fallback\.svg$/,
+ )
+})
+
+test('loads the primary project evidence and exposes only public project links', async ({
+ page,
+}) => {
+ await page.goto('/')
+ const projectImage = page.locator('#projects .product-frame img')
+ await projectImage.scrollIntoViewIfNeeded()
+ await expect
+ .poll(() =>
+ projectImage.evaluate((image) => image instanceof HTMLImageElement && image.naturalWidth > 0),
+ )
+ .toBe(true)
+
+ await expect(page.getByRole('link', { name: 'Open jigsolver.app' })).toHaveAttribute(
+ 'href',
+ 'https://jigsolver.app/',
+ )
+ const plannerCard = page
+ .locator('article')
+ .filter({ has: page.getByRole('heading', { name: 'Planner', exact: true }) })
+ await expect(plannerCard.getByRole('link')).toHaveCount(0)
+
+ const publicRepositories = [
+ 'https://github.com/LucasPMM/simplex',
+ 'https://github.com/LucasPMM/Pokemon-Base',
+ 'https://github.com/LucasPMM/greedy-kmeans',
+ 'https://github.com/LucasPMM/lz78-compression',
+ ]
+ for (const repository of publicRepositories) {
+ await expect(page.locator(`a[href="${repository}"]`)).toHaveAttribute('target', '_blank')
+ }
+})
+
+test('keeps the page free of horizontal overflow at representative widths', async ({ page }) => {
+ const widths = [320, 768, 1440]
+ await page.goto('/')
+ for (const width of widths) {
+ await page.setViewportSize({ width, height: 900 })
+ const hasOverflow = await page.evaluate(
+ () => document.documentElement.scrollWidth > document.documentElement.clientWidth,
+ )
+ expect(hasOverflow).toBe(false)
+ }
+})
+
+test('supports keyboard entry and reduced motion', async ({ page }) => {
+ await page.emulateMedia({ reducedMotion: 'reduce' })
+ await page.goto('/')
+ await page.keyboard.press('Tab')
+ await expect(page.getByRole('link', { name: 'Skip to content' })).toBeFocused()
+ await page.keyboard.press('Enter')
+ await expect(page.locator('#main-content')).toBeInViewport()
+ await expect(page.locator('html')).toHaveCSS('scroll-behavior', 'auto')
+})
+
+test('remains usable with text enlarged to 200 percent', async ({ page }) => {
+ await page.setViewportSize({ width: 640, height: 900 })
+ await page.goto('/')
+ await page.evaluate(() => {
+ document.documentElement.style.fontSize = '200%'
+ })
+ const overflowingElements = await page.locator('body *').evaluateAll((elements) =>
+ elements
+ .filter((element) => {
+ const bounds = element.getBoundingClientRect()
+ return bounds.right > document.documentElement.clientWidth || bounds.left < 0
+ })
+ .map((element) => ({
+ bounds: {
+ left: Math.round(element.getBoundingClientRect().left),
+ right: Math.round(element.getBoundingClientRect().right),
+ },
+ className: element.getAttribute('class') ?? '',
+ tagName: element.tagName,
+ text: element.textContent?.trim().slice(0, 40),
+ })),
+ )
+ expect(overflowingElements).toEqual([])
+ await expect(page.getByRole('heading', { level: 1 })).toBeVisible()
+})
diff --git a/index.html b/index.html
index 743bfb1..75deb2c 100644
--- a/index.html
+++ b/index.html
@@ -1,117 +1,51 @@
-
-
-
-
-
-
- Lucas Paulo Martins Mariz
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
SOBRE
-
Técnico em informática e em desenvolvimento de sistemas formado pelo Colégio Técnico da UFMG e estudante de Ciência da Computação na UFMG. Personalidade forte, interessado em novas tecnologias, aplicado em projetos envolvendo as mais diversas linguagens de programação e músico nos tempos livres.
-
-
-
CONTATO
-
Rua Radialista César Dos Santos - 151 - Belo Horizonte/Brasil
Graduação concluída no Colégio Técnico da Universidade Federal de Minas Gerais relacionada ao técnico em informática e em desenvolvimento de sistemas.
-
2018 - Atual UFMG
-
Graduação em andamento no bacharelado de Ciência da Computação na Universidade Federal de Minas Gerais.
-
-
-
PROJETOS
-
Help.me
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel interdum metus. Maecenas et blandit lectus. Praesent eget mattis justo, at imperdiet magna. Quisque rutrum dolor non eros mattis, et euismod eros semper.
-
Keep.me
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel interdum metus. Maecenas et blandit lectus. Praesent eget mattis justo, at imperdiet magna. Quisque rutrum dolor non eros mattis, et euismod eros semper.
-
Project Absolut
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis vel interdum metus. Maecenas et blandit lectus. Praesent eget mattis justo, at imperdiet magna. Quisque rutrum dolor non eros mattis, et euismod eros semper.