docs(S15): plano + design hi-fi do card "Time de você" (Related #1161)

This commit is contained in:
Francisco Solla 2026-09-18 17:16:47 -03:00
parent 5f8233c7e9
commit 0d93b5c9e5
5 changed files with 951 additions and 0 deletions

View file

@ -0,0 +1,838 @@
<!doctype html>
<html lang="pt-BR" data-theme="campaign-site">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<title>Design — S15 · Card Time de você</title>
<style type="text/tailwindcss">
:root {
--background: #ffffff;
--foreground: #000000;
--pt-red: #a21c1c;
--pt-red-dark: #47130e;
--pt-yellow: #ffe607;
--campaign-cream: #fff8f2;
--campaign-ink: #000000;
--campaign-muted: #6c615e;
--campaign-surface: #f9faff;
--campaign-band: #ebe9e9;
--campaign-line: rgb(0 0 0 / 12%);
--field-border: rgb(0 0 0 / 18%);
--team-red: #e50e2f;
--team-yellow: #ffec01;
--team-blue: #0061a5;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: Arial, Helvetica, sans-serif;
}
button,
input {
font: inherit;
}
button:focus-visible,
input:focus-visible,
a:focus-visible,
[tabindex]:focus-visible {
outline: 2px solid var(--pt-red);
outline-offset: 2px;
}
.checker {
background-color: #f4f1ed;
background-image:
linear-gradient(45deg, #e6e1dc 25%, transparent 25%),
linear-gradient(-45deg, #e6e1dc 25%, transparent 25%),
linear-gradient(45deg, transparent 75%, #e6e1dc 75%),
linear-gradient(-45deg, transparent 75%, #e6e1dc 75%);
background-position:
0 0,
0 8px,
8px -8px,
-8px 0;
background-size: 16px 16px;
}
.range-track {
appearance: none;
height: 4px;
border-radius: 999px;
background: linear-gradient(90deg, var(--pt-red) 0 46%, #d6d3d1 46% 100%);
}
.range-track::-webkit-slider-thumb {
appearance: none;
width: 20px;
height: 20px;
border: 3px solid white;
border-radius: 999px;
background: var(--pt-red);
box-shadow: 0 0 0 1px rgb(0 0 0 / 18%);
}
</style>
</head>
<body class="bg-zinc-950 text-zinc-100 antialiased">
<main class="mx-auto max-w-[1440px] px-4 py-10 sm:px-6 lg:px-10">
<header class="max-w-4xl">
<p class="m-0 text-xs font-black tracking-[0.12em] text-red-400 uppercase">
Gate visual · S15
</p>
<h1 class="mt-2 text-2xl font-black tracking-[-0.02em] sm:text-3xl">Card “Time de você”</h1>
<p class="mt-3 max-w-3xl text-sm leading-6 text-zinc-400">
Visitante, principalmente no celular, escolhe o quarto modelo, informa nome e foto de
busto, acompanha o recorte local, confere o enquadramento e baixa o card. As cenas mantêm
o compositor vigente: dialog no desktop, drawer no mobile, um único CTA primário e arte
oficial sem redesenho.
</p>
<div class="mt-5 flex flex-wrap gap-2 text-[11px] font-bold text-zinc-300">
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
>1280px · desktop</span
>
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
>390px · mobile</span
>
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
>alvos ≥ 44px</span
>
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
>contraste ≥ 4,5:1</span
>
</div>
</header>
<!-- Cena 1: galeria desktop -->
<section class="mt-14">
<div class="flex flex-wrap items-end justify-between gap-2">
<div>
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 1 · Home / #cards · desktop 1280
</h2>
<p class="mt-1 text-xs text-zinc-500">
Quatro tiles em uma linha: a arte continua dominante e a seção não ganha CTA
concorrente.
</p>
</div>
<p class="text-xs font-bold text-zinc-400">
Decisão: <span class="text-zinc-200">grid 4 × 1 em lg</span>
</p>
</div>
<div
class="mt-4 w-full max-w-[1280px] overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-cream)] text-[var(--campaign-ink)] shadow-2xl"
>
<section
id="cards"
aria-labelledby="cards-title-desktop"
class="border-b border-[var(--campaign-line)] px-10 py-14"
>
<div class="mx-auto max-w-2xl text-center">
<p class="m-0 text-xs font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Faça parte
</p>
<h3 id="cards-title-desktop" class="mt-1 text-2xl font-black tracking-[-0.02em]">
Mostre que você está com Solla
</h3>
<p class="mt-2 text-base leading-6 text-[var(--campaign-muted)]">
Escolha um modelo, coloque seu nome ou sua foto e baixe seu card. Compartilhe com
sua gente e fortaleça nossa caminhada.
</p>
</div>
<ul aria-label="Modelos de card" class="mt-8 grid list-none grid-cols-4 gap-5 p-0">
<li>
<button
aria-label="Card com seu nome"
class="group flex h-full min-h-11 w-full flex-col rounded-xl text-left"
>
<span
class="relative block aspect-[3/4] w-full overflow-hidden rounded-xl border border-[var(--campaign-line)] bg-[var(--campaign-band)] transition group-hover:-translate-y-0.5 group-hover:shadow-lg"
>
<img
src="../../public/cards/name-card-base.jpg"
alt=""
class="absolute inset-0 size-full object-cover"
/>
<span
class="absolute top-[30%] left-[19.7%] max-w-[66%] text-left text-[clamp(12px,1.55vw,22px)] leading-none font-black text-[#ffec01]"
>SEU NOME</span
>
</span>
<span class="mt-3 block w-full text-center text-sm font-bold"
>Card com seu nome</span
>
</button>
</li>
<li>
<button
aria-label="Moldura quadrada"
class="group flex h-full min-h-11 w-full flex-col rounded-xl text-left"
>
<span
class="checker relative block aspect-square w-full overflow-hidden rounded-xl border border-[var(--campaign-line)] transition group-hover:-translate-y-0.5 group-hover:shadow-lg"
>
<img
src="../../public/cards/photo-square-frame.png"
alt=""
class="absolute inset-0 size-full object-cover"
/>
</span>
<span class="mt-3 block w-full text-center text-sm font-bold"
>Moldura quadrada</span
>
</button>
</li>
<li>
<button
aria-label="Moldura vertical"
class="group flex h-full min-h-11 w-full flex-col rounded-xl text-left"
>
<span
class="checker relative block aspect-[25/36] w-full overflow-hidden rounded-xl border border-[var(--campaign-line)] transition group-hover:-translate-y-0.5 group-hover:shadow-lg"
>
<img
src="../../public/cards/photo-portrait-frame.png"
alt=""
class="absolute inset-0 size-full object-cover"
/>
</span>
<span class="mt-3 block w-full text-center text-sm font-bold"
>Moldura vertical</span
>
</button>
</li>
<li>
<button
aria-label="Time de você"
class="group flex h-full min-h-11 w-full flex-col rounded-xl text-left"
>
<span
class="relative block aspect-[3/4] w-full overflow-hidden rounded-xl border border-[var(--campaign-line)] bg-[var(--campaign-band)] transition group-hover:-translate-y-0.5 group-hover:shadow-lg"
>
<img
src="../../public/cards/team-card-example.jpg"
alt=""
class="absolute inset-0 size-full object-cover"
/>
<span
class="absolute top-3 right-3 rounded-full bg-white/95 px-2.5 py-1 text-[10px] font-black text-[var(--team-blue)] shadow"
>NOVO</span
>
</span>
<span class="mt-3 block w-full text-center text-sm font-bold">Time de você</span>
</button>
</li>
</ul>
</section>
</div>
</section>
<!-- Cena 2: galeria mobile -->
<section class="mt-14">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 2 · /cards · mobile 390
</h2>
<p class="mt-1 text-xs text-zinc-500">
A intro e a dica dizem “quatro”; o primeiro tile ocupa 78% e o segundo aparece como pista
de rolagem.
</p>
<div
class="mt-4 w-[390px] max-w-full overflow-hidden rounded-[22px] border border-zinc-700 bg-white text-black shadow-2xl"
>
<div class="px-5 pt-5">
<a
href="#"
class="inline-flex min-h-11 items-center text-sm font-bold text-[var(--pt-red)] underline-offset-4"
>← Voltar para a campanha</a
>
</div>
<section aria-labelledby="cards-page-title-mobile" class="px-5 pt-6 pb-10">
<div class="text-center">
<p class="text-[10px] font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Faça parte
</p>
<h3
id="cards-page-title-mobile"
class="mt-1 text-[22px] leading-tight font-black tracking-[-0.02em]"
>
Crie seu card de apoio
</h3>
<p class="mt-2 text-sm leading-5 text-[var(--campaign-muted)]">
Escolha um dos quatro modelos, personalize com seu nome ou sua foto e baixe para
compartilhar.
</p>
<p class="mt-2 text-xs leading-5 text-[var(--campaign-muted)]">
Seu nome e sua foto são processados apenas no seu aparelho e não são enviados para
nós.
</p>
</div>
<div class="mt-7 flex w-[710px] snap-x gap-4">
<button
aria-label="Card com seu nome"
class="flex w-[273px] shrink-0 flex-col rounded-xl"
>
<span
class="relative aspect-[3/4] w-full overflow-hidden rounded-xl border border-[var(--campaign-line)] bg-[var(--campaign-band)]"
>
<img
src="../../public/cards/name-card-base.jpg"
alt=""
class="absolute inset-0 size-full object-cover"
/>
<span
class="absolute top-[30%] left-[19.7%] text-[18px] leading-none font-black text-[#ffec01]"
>SEU NOME</span
>
</span>
<span class="mt-3 text-center text-sm font-bold">Card com seu nome</span>
</button>
<button
aria-label="Moldura quadrada"
class="flex w-[273px] shrink-0 flex-col rounded-xl"
>
<span
class="checker relative aspect-square w-full overflow-hidden rounded-xl border border-[var(--campaign-line)]"
>
<img
src="../../public/cards/photo-square-frame.png"
alt=""
class="absolute inset-0 size-full object-cover"
/>
</span>
<span class="mt-3 text-center text-sm font-bold">Moldura quadrada</span>
</button>
</div>
<p class="mt-2 text-center text-xs font-semibold text-[var(--campaign-muted)]">
Deslize para ver os quatro modelos
</p>
<div class="mt-5 flex justify-center gap-2" aria-hidden="true">
<span class="h-1.5 w-6 rounded-full bg-[var(--pt-red)]"></span
><span class="size-1.5 rounded-full bg-stone-300"></span
><span class="size-1.5 rounded-full bg-stone-300"></span
><span class="size-1.5 rounded-full bg-stone-300"></span>
</div>
</section>
</div>
</section>
<!-- Cena 3: inicial desktop -->
<section class="mt-14">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 3 · Compositor inicial · dialog desktop
</h2>
<p class="mt-1 text-xs text-zinc-500">
Nome e foto são pedidos juntos; instrução de qualidade sem prometer recorte perfeito. O
CTA final segue desabilitado.
</p>
<div
class="relative mt-4 flex min-h-[760px] w-full max-w-[1280px] items-center justify-center overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-cream)] p-8 text-black shadow-2xl"
>
<div
class="absolute inset-0 bg-[linear-gradient(135deg,#fff8f2,#ebe9e9)] opacity-80"
></div>
<div class="absolute inset-0 bg-black/45"></div>
<article
role="dialog"
aria-modal="true"
aria-labelledby="initial-title"
class="relative z-10 flex max-h-[92dvh] w-[512px] max-w-full flex-col overflow-hidden rounded-xl border border-black/10 bg-white shadow-2xl"
>
<header class="flex items-start gap-3 px-5 pt-5">
<div class="min-w-0">
<p class="text-xs font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Time de você
</p>
<h3 id="initial-title" class="mt-1 text-xl font-black tracking-[-0.01em]">
Entre para o time
</h3>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 shrink-0 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)] hover:bg-[var(--campaign-band)]"
>
×
</button>
</header>
<div class="overflow-y-auto px-5 pt-4 pb-5">
<div
class="mx-auto w-[172px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-sm"
>
<img
src="../../public/cards/team-card-example.jpg"
alt="Exemplo do card Time de você"
class="block aspect-[3/4] w-full object-cover"
/>
</div>
<div class="mt-5">
<label for="desktop-name" class="block text-sm font-bold text-[var(--campaign-ink)]"
>Seu nome</label
>
<input
id="desktop-name"
value=""
placeholder="Como você é chamado"
class="mt-2 h-11 w-full rounded-lg border border-[var(--field-border)] bg-white px-3 text-base placeholder:text-stone-500"
/>
<p class="mt-2 text-xs leading-5 text-[var(--campaign-muted)]">
Vai aparecer em caixa alta depois de “TIME DE”. Use um nome curto e conhecido.
</p>
</div>
<div
class="mt-5 rounded-lg border border-[var(--campaign-line)] bg-[var(--campaign-surface)] p-4"
>
<p class="text-sm font-bold">Sua foto de busto</p>
<p class="mt-1 text-sm leading-5 text-[var(--campaign-muted)]">
Para um recorte melhor, escolha uma foto nítida, de frente e com fundo simples.
</p>
<button
class="mt-3 inline-flex min-h-11 w-full items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white px-5 text-sm font-extrabold text-[var(--pt-red)] hover:bg-[var(--campaign-band)]"
>
Escolher foto
</button>
</div>
<p class="mt-4 text-xs leading-5 text-[var(--campaign-muted)]">
Seu nome e sua foto são processados apenas no seu aparelho e não são enviados para
nós.
</p>
</div>
<footer class="flex gap-3 border-t border-[var(--campaign-line)] px-5 py-4">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white px-5 text-sm font-extrabold text-[var(--pt-red)]"
>
Cancelar
</button>
<button
disabled
class="inline-flex min-h-11 flex-1 cursor-not-allowed items-center justify-center rounded-lg bg-[var(--pt-red)] px-5 text-sm font-extrabold text-white opacity-50"
>
Criar meu card
</button>
</footer>
</article>
</div>
</section>
<!-- Cena 4: processamento mobile -->
<section class="mt-14">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 4 · Recorte em processamento · drawer mobile 390
</h2>
<p class="mt-1 text-xs text-zinc-500">
A primeira execução explicita download do modelo e processamento local; não promete
duração.
</p>
<div
class="relative mt-4 h-[780px] w-[390px] max-w-full overflow-hidden rounded-[22px] border border-zinc-700 bg-[var(--campaign-cream)] text-black shadow-2xl"
>
<div class="px-5 py-10 opacity-50">
<p class="text-xs font-black text-[var(--pt-red)] uppercase">Faça parte</p>
<h3 class="mt-1 text-2xl font-black">Mostre que você está com Solla</h3>
<div class="mt-5 h-72 rounded-xl bg-stone-200"></div>
</div>
<div class="absolute inset-0 bg-black/50"></div>
<article
role="dialog"
aria-modal="true"
aria-labelledby="processing-title"
class="absolute inset-x-0 bottom-0 overflow-hidden rounded-t-2xl border-t border-black/10 bg-white"
>
<div class="mx-auto mt-2 h-1.5 w-10 rounded-full bg-stone-300"></div>
<header class="flex items-start gap-3 px-5 pt-4">
<div>
<p class="text-[10px] font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Time de você
</p>
<h3 id="processing-title" class="mt-1 text-lg font-black tracking-[-0.01em]">
Preparando sua foto
</h3>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)]"
>
×
</button>
</header>
<div class="px-5 pt-4 pb-5">
<div
class="mx-auto w-[144px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-sm"
>
<img
src="../../public/cards/team-card-base.png"
alt="Prévia do modelo enquanto a foto é preparada"
class="block aspect-[3/4] w-full object-cover"
/>
</div>
<div class="mt-5" role="status" aria-live="polite">
<div class="flex items-center justify-between gap-3">
<p class="text-sm font-bold">Baixando o modelo de recorte…</p>
<p class="text-sm font-black text-[var(--pt-red)]">36%</p>
</div>
<div
class="mt-2 h-2 overflow-hidden rounded-full bg-stone-200"
aria-label="Progresso do recorte: 36%"
>
<div class="h-full w-[36%] rounded-full bg-[var(--pt-red)]"></div>
</div>
<p class="mt-3 text-sm leading-5 text-[var(--campaign-muted)]">
Na primeira vez, o modelo precisa ser baixado. Depois, vamos remover o fundo da
foto no seu aparelho. Isso pode levar um pouco.
</p>
</div>
<div class="mt-4 flex items-start gap-3 rounded-lg bg-[var(--campaign-cream)] p-3">
<span
aria-hidden="true"
class="mt-0.5 inline-flex size-5 shrink-0 items-center justify-center rounded-full bg-[var(--team-blue)] text-xs font-black text-white"
>✓</span
>
<p class="text-xs leading-5 text-[var(--campaign-muted)]">
Sua foto fica neste aparelho e não é enviada para nós.
</p>
</div>
</div>
<footer class="flex gap-3 border-t border-[var(--campaign-line)] px-5 py-4">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white text-sm font-extrabold text-[var(--pt-red)]"
>
Cancelar
</button>
<button
disabled
class="inline-flex min-h-11 flex-1 cursor-wait items-center justify-center rounded-lg bg-[var(--pt-red)] text-sm font-extrabold text-white opacity-50"
>
Processando…
</button>
</footer>
</article>
</div>
</section>
<!-- Cena 5: pronto para criar -->
<section class="mt-14">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 5 · Recorte pronto + enquadramento · dialog desktop
</h2>
<p class="mt-1 text-xs text-zinc-500">
A prévia real confirma “TIME DE MARIA”; arrastar e controles de zoom/ajuste fino são
escapes opcionais.
</p>
<div
class="relative mt-4 flex min-h-[850px] w-full max-w-[1280px] items-center justify-center overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-band)] p-8 text-black shadow-2xl"
>
<div class="absolute inset-0 bg-black/45"></div>
<article
role="dialog"
aria-modal="true"
aria-labelledby="ready-title"
class="relative z-10 flex max-h-[92dvh] w-[512px] max-w-full flex-col overflow-hidden rounded-xl border border-black/10 bg-white shadow-2xl"
>
<header class="flex items-start gap-3 px-5 pt-5">
<div>
<p class="text-xs font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Time de você
</p>
<h3 id="ready-title" class="mt-1 text-xl font-black">Confira seu card</h3>
<p class="mt-1 text-sm leading-5 text-[var(--campaign-muted)]">
O recorte já foi centralizado. Se precisar, arraste a foto ou use os controles.
</p>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 shrink-0 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)]"
>
×
</button>
</header>
<div class="overflow-y-auto px-5 pt-4 pb-5">
<div
role="group"
aria-label="Ajuste da foto"
tabindex="0"
class="relative mx-auto w-[240px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md"
>
<img
src="../../public/cards/team-card-example.jpg"
alt="Prévia do card TIME DE MARIA"
class="block aspect-[3/4] w-full object-cover"
/>
<div
class="pointer-events-none absolute top-[15.5%] left-1/2 flex h-[12.2%] w-[47%] -translate-x-1/2 -rotate-[4.1deg] items-center justify-center bg-[var(--team-blue)] px-1 text-center text-[27px] leading-none font-black tracking-tight text-white shadow-sm"
>
MARIA
</div>
<span
class="absolute right-2 bottom-2 rounded bg-black/70 px-2 py-1 text-[10px] font-bold text-white"
>Arraste para ajustar</span
>
</div>
<div class="mt-5 flex items-center gap-3">
<label for="desktop-zoom" class="text-sm font-bold">Zoom</label>
<button
aria-label="Diminuir zoom"
class="inline-flex size-11 shrink-0 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-xl font-bold"
>
−
</button>
<input
id="desktop-zoom"
aria-label="Nível de zoom"
type="range"
value="46"
class="range-track h-11 min-w-0 flex-1"
/>
<button
aria-label="Aumentar zoom"
class="inline-flex size-11 shrink-0 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-xl font-bold"
>
+
</button>
</div>
<div class="mt-4">
<p class="text-sm font-bold">Ajuste fino</p>
<div class="mt-2 grid grid-cols-4 gap-2">
<button
aria-label="Mover foto para a esquerda"
class="inline-flex min-h-11 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-lg"
>
←
</button>
<button
aria-label="Mover foto para cima"
class="inline-flex min-h-11 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-lg"
>
↑
</button>
<button
aria-label="Mover foto para baixo"
class="inline-flex min-h-11 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-lg"
>
↓
</button>
<button
aria-label="Mover foto para a direita"
class="inline-flex min-h-11 items-center justify-center rounded-lg border border-[var(--campaign-line)] bg-white text-lg"
>
→
</button>
</div>
</div>
<button
class="mt-4 inline-flex min-h-11 w-full items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white px-5 text-sm font-extrabold text-[var(--pt-red)]"
>
Trocar foto
</button>
<p class="mt-3 text-xs leading-5 text-[var(--campaign-muted)]">
Sua foto fica neste aparelho e não é enviada para nós.
</p>
</div>
<footer class="flex gap-3 border-t border-[var(--campaign-line)] px-5 py-4">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white text-sm font-extrabold text-[var(--pt-red)]"
>
Cancelar
</button>
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg bg-[var(--pt-red)] px-5 text-sm font-extrabold text-white shadow-sm"
>
Criar meu card
</button>
</footer>
</article>
</div>
</section>
<!-- Cena 6: resultado mobile -->
<section class="mt-14">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 6 · Resultado · drawer mobile 390
</h2>
<p class="mt-1 text-xs text-zinc-500">
Uma ação primária: baixar. “Criar outro” permanece secundária e devolve ao catálogo.
</p>
<div
class="relative mt-4 h-[780px] w-[390px] max-w-full overflow-hidden rounded-[22px] border border-zinc-700 bg-[var(--campaign-cream)] text-black shadow-2xl"
>
<div class="absolute inset-0 bg-black/50"></div>
<article
role="dialog"
aria-modal="true"
aria-labelledby="result-title"
class="absolute inset-x-0 bottom-0 overflow-hidden rounded-t-2xl bg-white"
>
<div class="mx-auto mt-2 h-1.5 w-10 rounded-full bg-stone-300"></div>
<header class="flex items-start gap-3 px-5 pt-4">
<div>
<p class="text-[10px] font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Tudo certo
</p>
<h3 id="result-title" class="mt-1 text-lg leading-6 font-black">
Seu card está pronto para compartilhar.
</h3>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 shrink-0 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)]"
>
×
</button>
</header>
<div class="px-5 pt-4 pb-5">
<div
class="mx-auto w-[190px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md"
>
<img
src="../../public/cards/team-card-example.jpg"
alt="Seu card Time de Maria pronto"
class="block aspect-[3/4] w-full object-cover"
/>
<div class="pointer-events-none absolute"></div>
</div>
<div class="mt-4 flex items-start gap-3 rounded-lg bg-[var(--campaign-cream)] p-3">
<span
aria-hidden="true"
class="inline-flex size-6 shrink-0 items-center justify-center rounded-full bg-green-700 text-xs font-black text-white"
>✓</span
>
<p class="text-xs leading-5 text-[var(--campaign-muted)]">
Seu nome e sua foto são processados apenas no seu aparelho e não são enviados para
nós.
</p>
</div>
</div>
<footer class="flex gap-3 border-t border-[var(--campaign-line)] px-5 py-4">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white px-3 text-sm font-extrabold text-[var(--pt-red)]"
>
Criar outro card
</button>
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg bg-[var(--pt-red)] px-3 text-sm font-extrabold text-white shadow-sm"
>
Baixar meu card
</button>
</footer>
</article>
</div>
</section>
<!-- Cena 7: erros -->
<section class="mt-14 pb-10">
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
Cena 7 · Erros recuperáveis · conteúdo do compositor
</h2>
<p class="mt-1 text-xs text-zinc-500">
Os dois erros preservam o que a pessoa já informou e oferecem recuperação direta, sem beco
sem saída.
</p>
<div class="mt-4 grid max-w-[1048px] gap-6 lg:grid-cols-2">
<article
class="overflow-hidden rounded-xl border border-zinc-700 bg-white text-black shadow-xl"
>
<header class="flex items-start gap-3 px-5 pt-5">
<div>
<p class="text-xs font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Falha no recorte
</p>
<h3 class="mt-1 text-xl font-black">Vamos tentar outra vez</h3>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)]"
>
×
</button>
</header>
<div class="px-5 pt-4 pb-5">
<div role="alert" class="rounded-lg border border-[var(--pt-red)] bg-red-50 p-4">
<p class="text-sm font-black text-[var(--pt-red)]">
Não foi possível remover o fundo desta foto.
</p>
<p class="mt-1 text-sm leading-5 text-[var(--campaign-ink)]">
Tente de novo ou escolha outra foto.
</p>
</div>
<p class="mt-4 text-sm leading-5 text-[var(--campaign-muted)]">
Fotos de busto, nítidas e com fundo simples costumam funcionar melhor.
</p>
<p class="mt-3 text-xs leading-5 text-[var(--campaign-muted)]">
Sua foto fica neste aparelho e não é enviada para nós.
</p>
<div class="mt-5 flex gap-3">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white px-3 text-sm font-extrabold text-[var(--pt-red)]"
>
Escolher outra foto
</button>
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg bg-[var(--pt-red)] px-3 text-sm font-extrabold text-white"
>
Tentar de novo
</button>
</div>
</div>
</article>
<article
class="overflow-hidden rounded-xl border border-zinc-700 bg-white text-black shadow-xl"
>
<header class="flex items-start gap-3 px-5 pt-5">
<div>
<p class="text-xs font-black tracking-[0.1em] text-[var(--pt-red)] uppercase">
Nome muito longo
</p>
<h3 class="mt-1 text-xl font-black">Encurte o nome</h3>
</div>
<button
aria-label="Fechar"
class="ml-auto inline-flex size-11 items-center justify-center rounded-lg text-2xl text-[var(--campaign-muted)]"
>
×
</button>
</header>
<div class="px-5 pt-4 pb-5">
<label for="error-name" class="block text-sm font-bold">Seu nome</label>
<input
id="error-name"
aria-invalid="true"
value="MARIA DA CONCEIÇÃO OLIVEIRA"
class="mt-2 h-11 w-full rounded-lg border-2 border-[var(--pt-red)] bg-white px-3 text-base"
/>
<p role="alert" class="mt-2 text-sm leading-5 font-semibold text-[var(--pt-red)]">
Não foi possível encaixar esse nome no card. Use um nome mais curto, como você é
chamado.
</p>
<p class="mt-4 text-xs leading-5 text-[var(--campaign-muted)]">
A foto escolhida e o recorte permanecem salvos neste aparelho enquanto você corrige
o nome.
</p>
</div>
<footer class="flex gap-3 border-t border-[var(--campaign-line)] px-5 py-4">
<button
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg border-2 border-[var(--campaign-line)] bg-white text-sm font-extrabold text-[var(--pt-red)]"
>
Cancelar
</button>
<button
disabled
class="inline-flex min-h-11 flex-1 items-center justify-center rounded-lg bg-[var(--pt-red)] text-sm font-extrabold text-white opacity-50"
>
Criar meu card
</button>
</footer>
</article>
</div>
</section>
<aside class="border-t border-zinc-800 pt-8 text-xs leading-5 text-zinc-500">
<p>
<strong class="text-zinc-300">Arte e composição preservadas:</strong> base → foto
recortada → overlay → textos; banners #E50E2F / #0061A5, texto #FFEC01 / #FFFFFF. A prévia
usa o exemplo oficial; nenhuma arte nova foi inventada.
</p>
<p class="mt-2"><strong class="text-zinc-300">NEEDS ASSET:</strong> nenhum.</p>
</aside>
</main>
</body>
</html>

View file

@ -0,0 +1,113 @@
# Card "Time de você": quarto modelo auto-serviço no funil público
Status: registrado (aguardando plano em main)
Atualizado em: 2026-09-18
Issue: #1161
Priority: P1
Impeccable: C — modelo novo num funil público existente (não é superfície nova)
Design UI: docs/plans/cards-time-de-voce-ui-design.html
Appetite: ~2–3 dias eng; um quarto modelo verificável no funil já entregue, sem fluxo novo
Responsável: —
## Intenção
O funil de cards do site (seção `#cards` da home + `/cards`, S13/#941, ajustado em S14/#959) tem três modelos — um de nome e dois de foto. Falta o card que mais circula na militância: a pessoa entra no "time" com a própria cara. O visitante digita o nome, envia uma foto de busto, o fundo é removido no próprio aparelho (nada é enviado) e o card `TIME DE <NOME>` fica pronto para baixar, com os candidatos ao fundo, a foto recortada no centro e os banners no topo.
É a continuação natural do funil — S13 excluía "remoção de fundo" e "novos modelos" do escopo. Não é superfície nova: quarto item do catálogo, mesmo editor, mesma promessa de privacidade.
## Persona e fluxo
- **Persona / contexto:** visitante/militante no celular, quer mostrar que entrou no time do Solla; tem uma foto de busto razoável na galeria.
- **Job principal:** virar `TIME DE <NOME>` em um card oficial sem instalar nada, sem criar conta e sem enviar a foto para ninguém.
- **Fluxo desejado:** vê o quarto modelo na home ou em `/cards` → escolhe `Time de você` → digita o nome → escolhe uma foto de busto → vê o recorte acontecer com progresso no aparelho → confere a prévia (auto-enquadrada; arrastar/zoom como escape) → baixa o PNG.
- **Anti-goals de produto:** editor gráfico/máscara manual, cadastro, galeria pública, analytics de PII, promessa de recorte perfeito, segundo editor.
### Esboço de fluxo (C)
```text
[home/#cards ou /cards] → [modelo "Time de você"] → [nome + foto de busto]
→ [remoção de fundo local: progresso | erro + retry] → [prévia auto-enquadrada + ajuste opcional]
→ [card pronto] → [baixar PNG]
```
### Design UI (C)
- Design UI (gate): `docs/plans/cards-time-de-voce-ui-design.html`
- Cenas: tile do quarto modelo na galeria, estados de processamento/erro do recorte, prévia com a geometria final e resultado, em desktop e mobile.
## Objetivo e aceite
- O funil ganha o quarto modelo, `Time de você`, ao lado dos três atuais; home e `/cards` continuam um único editor e um único fluxo.
- O visitante digita o nome e escolhe uma foto de busto; o card final estampa `TIME DE <NOME>` nos banners do topo (nome em caixa alta) e a foto recortada no centro, sobre o fundo oficial com céu e candidatos.
- A remoção de fundo acontece no aparelho: nenhum byte da foto é enviado; a nota de privacidade do funil segue visível no novo modelo.
- Durante o recorte a pessoa vê progresso; se falhar, vê um erro claro com opção de tentar de novo e não fica presa.
- A foto entra auto-enquadrada (topo do recorte encostado no topo do espaço previsto, centrada), com os controles de arrastar/zoom já existentes como ajuste opcional.
- Nome em uma linha: se não couber no menor tamanho legível, a pessoa é pedida a usar um nome mais curto (mesma mensagem do modelo de nome) — nunca corte silencioso.
- Fidelidade ao exemplo de referência: posições, rotações, cores e corpo do nome seguem o literal medido; arte-mestre intocada (compor por cima, nunca redesenhar).
- `/cards` e o link com modelo pré-selecionado (`?model=time-de-voce`) continuam funcionando.
- Escolha de foto, retry do recorte e download funcionam por toque e teclado.
- A expectativa é honesta: o pedido é foto de busto com fundo simples — sem prometer recorte perfeito.
- Sem conta, cadastro ou analytics de PII; o nome e a imagem final nunca saem do aparelho.
## Dados (intenção)
- **Vou apresentar dados?** Não.
- **Decisões desbloqueadas:** N/A — nenhuma métrica é apresentada; não há decisão operacional desbloqueada por dado.
- **Forma:** _adiada ao plano de implementação_ — nome e foto são insumos locais da imagem; nenhum conteúdo pessoal entra em analytics.
## Dados da decisão (literais)
- ID `S15`; tipo `feature`; Priority `P1`; Model `composer-2.5`; Impeccable `C — modelo novo num funil público existente (não é superfície nova)`; Design UI: `docs/plans/cards-time-de-voce-ui-design.html`.
- Slug do plano: `cards-time-de-voce` (arquivo `docs/plans/cards-time-de-voce.md`).
- Model id `time-de-voce`; label de galeria `Time de você`; texto do card `TIME DE <NOME>` (nome normalizado em CAIXA ALTA pelo pipeline existente `normalizeCardName`).
- Assets commitados com o plano em `public/cards/`: `team-card-base.png` (fundo céu+candidatos, 1080×1440, ~1,5 MB), `team-card-front.png` (overlay marca/faixa, 1080×1440 RGBA, ~86 KB), `team-card-example.jpg` (exemplo final p/ tile da galeria, 1080×1440, ~268 KB).
- Ordem de composição: base → foto do usuário (fundo removido) → overlay frontal → textos do topo.
- Slot da foto (silhueta-placeholder do exemplo): x=286, y=439, largura=592, altura=577; auto-enquadramento cobrindo o slot, ancorado no topo e centrado (bbox alfa do recorte).
- Banner "TIME DE" (vermelho): centro ≈ (545, 162), ≈ 693×118, rotação ≈ −3,5°, fill `#E50E2F`; texto "TIME DE" centralizado, Brexter 700, cap ≈ 93 px, fill `#FFEC01`.
- Banner do nome (azul): centro ≈ (545, 313), ≈ 509×176, rotação ≈ −4,1°, fill `#0061A5`; nome centralizado, Brexter 700, cap ≈ 130 px, tinta máxima ≈ 470 px, 1 linha, cap mínimo ≈ 56 px, fill `#FFFFFF`.
- Cores literais: vermelho `#E50E2F`, amarelo `#FFEC01`, azul `#0061A5`, branco `#FFFFFF`.
- Copy: a página `/cards` e a dica da galeria hoje dizem "três modelos" → viram "quatro modelos" (o título/eyebrow da home não mudam). A nota de privacidade do funil continua valendo (`CARD_PRIVACY_NOTE`/`CARD_PHOTO_PRIVACY_NOTE`) e a remoção de fundo é 100% local.
- Motor de remoção de fundo: roda no navegador, sem upload da foto; recomendação `@imgly/background-removal` (mesma família do `@imgly/background-removal-node` já usado em `scripts/resize-images.mjs --remove-bg`), modelo pequeno (`isnet_quint8`, ~40 MB na 1ª vez, cacheado), carregado sob demanda SÓ quando o modelo time é escolhido, com estados de progresso/erro/retry. Alternativa mais leve (menor qualidade de borda): MediaPipe selfie segmenter (~250 KB, Apache-2.0). A escolha final do motor fica para o plano de implementação, mas o aceite exige: nenhum byte da foto sai do aparelho.
- Foto: auto-enquadrada (sem exigir ajuste), mas recomenda-se manter os controles de arrastar/zoom dos modelos de foto existentes como escape. Nome: 1 linha; se não couber no cap mínimo, erro pedindo nome mais curto (mesma mensagem do modelo de nome).
## Direção no codebase (hipótese)
- **Áreas prováveis:** catálogo `src/lib/cardModels.ts`, roteiro/render `src/lib/cardRender.ts`, fit `src/lib/cardNameFit.ts`, transform `src/lib/cardPhotoTransform.ts`, componentes `src/components/cards/`, página `src/app/(frontend)/(home)/cards/page.tsx`, seção `CampaignCardsSection.tsx`, assets `public/cards/`.
- **Precedente a olhar:** S13/S14 (`docs/plans/cards-personalizados-campanha-impl.md`, `docs/plans/cards-personalizados-ajustes.md`); `scripts/resize-images.mjs --remove-bg` para o motor; c167 (ffmpeg.wasm rejeitado por peso + COOP/COEP global que quebraria embed do YouTube).
- **Risco de acoplamento:** o composer carrega UMA imagem base e ramifica por `kind`; o roteiro não tem save/rotate/translate; os testes pinam os 3 ids (`tests/unit/cardModels.unit.spec.ts`) e o e2e (~:1694–1821) falha em qualquer console error e não cobre WASM; o motor deve funcionar SEM COOP/COEP (single-thread).
## Dependências
- S13 (#941) e S14 (#959) entregues; assets do quarto modelo já em `public/cards/`, commitados com o plano.
## Fora de escopo
- Modelos adicionais, CMS de modelos, métricas de uso.
- Compartilhamento direto em redes sociais.
- Recorte perfeito: cabelo fino, fundo complexo, várias pessoas, máscara manual (pincel/borracha).
- Conta, cadastro, persistência de nome/foto/arte gerada.
- Qualquer envio (mesmo parcial) da foto, ou processamento no servidor.
- Alterar as artes-mestre ou redesenhar home/`/cards`.
## Rabbit holes de produto
- **Motor "melhor" a qualquer custo.** Se alguém "só completar": entra uma dependência pesada ou headers globais que quebram o embed do YouTube (precedente c167). **Corte neste item:** motor sob demanda, sem COOP/COEP, carregado só se o modelo time for escolhido.
- **Prometer recorte perfeito.** Se alguém "só completar": vira editor de máscara com pincel e correção manual. **Corte neste item:** foto de busto com fundo simples, auto-recorte + retry.
- **Auto-enquadramento virar editor.** Se alguém "só completar": nasce um passo obrigatório de posicionamento. **Corte neste item:** auto + controles existentes como escape.
- **Segundo editor/segundo fluxo.** Se alguém "só completar": a home ganha compositor próprio e os comportamentos divergem. **Corte neste item:** o mesmo compositor de `/cards`.
- **Medir fidelidade "no olho".** Se alguém "só completar": aproximação visual vira critério. **Corte neste item:** geometria literal medida no exemplo (seção Dados da decisão).
## Questões em aberto (produto)
- **Qual motor de remoção de fundo?** **Opções:** `@imgly/background-removal` no navegador (borda melhor; ~40 MB na 1ª vez, cacheado; licença AGPL — self-host vs CDN a decidir na implementação) | MediaPipe selfie segmenter (~250 KB, Apache-2.0, borda mais fraca). **Recomendação:** `@imgly/background-removal`, com a ressalva AGPL a resolver na implementação. _(assumido — validar com produto)_
- **Auto-enquadramento puro ou com ajuste manual?** **Opções:** auto puro | auto + controles existentes. **Recomendação:** auto + controles existentes como escape. _(assumido — validar com produto)_
- **Qual rótulo do modelo na galeria?** **Opções:** `Time de você` | `Entre para o time`. **Recomendação:** `Time de você`. _(assumido — validar com produto)_
## Referências
- GitHub Issue #1161
- Design UI (gate): `docs/plans/cards-time-de-voce-ui-design.html` (+ assets em `cards-time-de-voce-ui-design-assets/`)
- `public/cards/team-card-example.jpg` — referência visual final do card
- `docs/plans/cards-personalizados-campanha.md` e `cards-personalizados-campanha-impl.md`; `docs/plans/cards-personalizados-ajustes.md`
- `src/lib/cardModels.ts`, `src/components/cards/CardComposer.tsx`, `src/app/(frontend)/(home)/cards/page.tsx`
- `AGENTS.md` e `AGENTS-public.md`

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB