mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
docs(S16,S17,S18): planos de intenção — ajustes pós-UAT do card Time de você (Related #1195, #1196, #1197)
This commit is contained in:
parent
ebee7c0a7a
commit
eadaf6f64f
6 changed files with 3031 additions and 0 deletions
600
docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html
Normal file
600
docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html
Normal file
|
|
@ -0,0 +1,600 @@
|
|||
<!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>DEGRADED — Design S18 · Card Time de você: escala da cabeça</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;
|
||||
}
|
||||
.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%);
|
||||
}
|
||||
|
||||
.tcard {
|
||||
position: relative;
|
||||
aspect-ratio: 1080 / 1440;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background: #cfe7f7;
|
||||
container-type: inline-size;
|
||||
}
|
||||
.tcard-base,
|
||||
.tcard-front {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
/* janela da foto S15: x=286 y=439 w=592 h=577 (card 1080x1440) */
|
||||
.tcard-window {
|
||||
position: absolute;
|
||||
left: 26.481%;
|
||||
top: 30.486%;
|
||||
width: 54.815%;
|
||||
height: 40.069%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tcard-bust {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
height: auto;
|
||||
transform: translateX(-50%);
|
||||
color: #8b98a6;
|
||||
}
|
||||
.tcard-banner {
|
||||
position: absolute;
|
||||
left: 50.463%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%) rotate(var(--rot, -4.1deg));
|
||||
overflow: hidden;
|
||||
}
|
||||
.tcard-banner-red {
|
||||
top: 11.25%;
|
||||
height: 8.194%;
|
||||
width: 64.167%;
|
||||
background: var(--team-red);
|
||||
color: var(--team-yellow);
|
||||
--rot: -3.5deg;
|
||||
}
|
||||
.tcard-banner-blue {
|
||||
top: 21.736%;
|
||||
height: 12.222%;
|
||||
width: 47.13%;
|
||||
background: var(--team-blue);
|
||||
color: #ffffff;
|
||||
--rot: -4.1deg;
|
||||
}
|
||||
.tcard-face {
|
||||
font-family: Brexter, Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.005em;
|
||||
font-size: calc(var(--fs, 9) * 1cqw);
|
||||
}
|
||||
/* régua do rosto (aprox., ilustrativa) */
|
||||
.ruler {
|
||||
position: absolute;
|
||||
border: 2px dashed #10b981;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.ruler::after {
|
||||
content: attr(data-label);
|
||||
position: absolute;
|
||||
top: -18px;
|
||||
left: -2px;
|
||||
white-space: nowrap;
|
||||
border-radius: 3px;
|
||||
background: #10b981;
|
||||
padding: 1px 4px;
|
||||
font-size: 9px;
|
||||
font-weight: 900;
|
||||
color: #052e22;
|
||||
}
|
||||
.ctrl-btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--campaign-line);
|
||||
background: #fff;
|
||||
color: var(--campaign-ink);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 antialiased">
|
||||
<div
|
||||
class="border-b border-amber-500/60 bg-amber-500/10 px-4 py-2 text-center text-[11px] font-black tracking-[0.14em] text-amber-300 uppercase"
|
||||
>
|
||||
DEGRADED — não certificado; exige sign-off humano · tier opencode-go/deepseek-v4.1-flash
|
||||
</div>
|
||||
|
||||
<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 · S18 · DEGRADED
|
||||
</p>
|
||||
<h1 class="mt-2 text-2xl font-black tracking-[-0.02em] sm:text-3xl">
|
||||
Card “Time de você”: cabeça do visitante proporcional à dos candidatos
|
||||
</h1>
|
||||
<p class="mt-3 max-w-3xl text-sm leading-6 text-zinc-400">
|
||||
O enquadramento automático passa a mirar a proporção da cabeça: a cabeça do visitante fica
|
||||
na mesma ordem de grandeza das cabeças dos candidatos (±20%), medida pela mesma régua nos
|
||||
dois lados. Sem controle novo; arrastar e zoom seguem como escape. Sem rosto detectado, vale
|
||||
o enquadramento de hoje — sem erro visível.
|
||||
</p>
|
||||
<p class="mt-3 max-w-3xl text-xs leading-5 text-amber-300/90">
|
||||
<strong>DEGRADED:</strong> este design não está certificado. É insumo para decisão humana no
|
||||
gate/PR — o tier degradado nunca aprova.
|
||||
</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"
|
||||
>sem controle novo</span
|
||||
>
|
||||
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
|
||||
>fallback silencioso</span
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Cena 1: 3 previews -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 1 · Prévia do card · desktop 1280 — cabeça pequena / proporcional / grande
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
Mesmo card, mesmo recorte: só o zoom do enquadramento automático muda. O alvo é a faixa
|
||||
proporcional (±20% da cabeça dos candidatos); cobertura da janela sempre prevalece.
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="mt-4 overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-cream)] p-8 text-black shadow-2xl"
|
||||
>
|
||||
<div class="grid gap-8 sm:grid-cols-3">
|
||||
<figure class="m-0">
|
||||
<div class="relative mx-auto w-[240px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust"
|
||||
style="width: 16%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center">
|
||||
<p class="text-sm font-black">Cabeça pequena demais</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Selfie de longe · hoje fica fora da ordem de grandeza dos candidatos
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="m-0">
|
||||
<div class="relative mx-auto w-[240px] overflow-hidden rounded-lg border-2 border-emerald-500 shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
class="absolute top-2 left-1/2 -translate-x-1/2 rounded-full bg-emerald-600 px-2.5 py-1 text-[10px] font-black text-white shadow"
|
||||
>alvo ±20%</span
|
||||
>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center">
|
||||
<p class="text-sm font-black text-emerald-700">Proporcional (alvo)</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Cabeça na mesma ordem de grandeza dos candidatos, rosto inteiro
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="m-0">
|
||||
<div class="relative mx-auto w-[240px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust"
|
||||
style="width: 82%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center">
|
||||
<p class="text-sm font-black">Cabeça grande demais</p>
|
||||
<p class="mt-1 text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Selfie de perto · hoje fica gigante ao lado do time
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<p class="mt-6 text-center text-xs leading-5 text-[var(--campaign-muted)]">
|
||||
Foto do visitante = placeholder cinza (NEEDS ASSET). A arte oficial dos candidatos é a
|
||||
real.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cena 2: a mesma regua -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 2 · A mesma régua nos dois lados — caixa do rosto
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
A medida é a mesma dos dois lados: a caixa do rosto. A referência dos candidatos é medida
|
||||
uma vez sobre a arte-mestre e vira constante; a do visitante é medida na foto no momento do
|
||||
recorte. O alvo é a mesma ordem de grandeza, ±20%.
|
||||
</p>
|
||||
<div
|
||||
class="mt-4 overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-cream)] p-8 text-black shadow-2xl"
|
||||
>
|
||||
<div class="grid gap-8 sm:grid-cols-2">
|
||||
<figure class="m-0">
|
||||
<div class="relative mx-auto w-[260px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div
|
||||
class="ruler"
|
||||
data-label="cabeça dos candidatos"
|
||||
style="left: 39.4%; top: 33.4%; width: 12%; height: 9%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Referência medida uma vez na arte-mestre (aprox. ilustrativa).
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="m-0">
|
||||
<div class="relative mx-auto w-[260px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div
|
||||
class="ruler"
|
||||
data-label="cabeça do visitante"
|
||||
style="left: 44.6%; top: 31.1%; width: 12%; height: 9%"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Mesma caixa, medida na foto do visitante; igual em ordem de grandeza.
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<ul class="mx-auto mt-6 max-w-3xl list-disc space-y-2 pl-5 text-xs leading-5 text-[var(--campaign-muted)]">
|
||||
<li>Em foto com mais de um rosto, vale o maior rosto (o do visitante).</li>
|
||||
<li>O rosto nunca é cortado pelo enquadramento proporcional.</li>
|
||||
<li>Cobertura da janela prevalece: sem buraco no card, mesmo que limite a proporção.</li>
|
||||
<li>A medida é feita no recorte; arrastar/zoom não re-medem nem re-enquadram.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cena 3: fallback -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 3 · Fallback silencioso · drawer mobile 390 — sem rosto detectado
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
Sem rosto detectado, com detecção indisponível ou em qualquer falha, vale exatamente o
|
||||
enquadramento de hoje (pelo contorno da foto): nenhum aviso, nenhum erro, nenhum bloqueio. O
|
||||
único erro recuperável continua sendo o do recorte de fundo.
|
||||
</p>
|
||||
<div class="mt-4 grid gap-6 lg:grid-cols-[390px_1fr]">
|
||||
<div
|
||||
class="relative h-[720px] 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="s18-fallback-title"
|
||||
class="absolute inset-x-0 bottom-0 max-h-[97%] 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">
|
||||
Time de você
|
||||
</p>
|
||||
<h3 id="s18-fallback-title" class="mt-1 text-lg leading-6 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="px-5 pt-3 pb-5">
|
||||
<div
|
||||
class="relative mx-auto w-[190px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md"
|
||||
>
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust"
|
||||
style="width: 40%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 grid grid-cols-4 gap-2">
|
||||
<button aria-label="Mover foto para a esquerda" class="ctrl-btn min-h-11">
|
||||
←
|
||||
</button>
|
||||
<button aria-label="Mover foto para cima" class="ctrl-btn min-h-11">↑</button>
|
||||
<button aria-label="Mover foto para baixo" class="ctrl-btn min-h-11">↓</button>
|
||||
<button aria-label="Mover foto para a direita" class="ctrl-btn min-h-11">→</button>
|
||||
</div>
|
||||
<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)]"
|
||||
>
|
||||
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)] text-sm font-extrabold text-white"
|
||||
>
|
||||
Criar meu card
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0">
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-emerald-300 uppercase">
|
||||
Fallback — sem estado visual novo
|
||||
</h3>
|
||||
<ul class="mt-3 list-disc space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>Sem rosto detectado: enquadramento de hoje, pelo contorno da foto.</li>
|
||||
<li>Detecção indisponível: mesmo caminho, sem tentar de novo e sem travar.</li>
|
||||
<li>Qualquer falha na detecção: silêncio total — nenhum toast, alerta ou badge.</li>
|
||||
<li>O card nunca falha por causa da detecção; o recorte segue o único erro.</li>
|
||||
<li>Nada de upload: a medida é efêmera, no aparelho, e não entra em analytics.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 rounded-lg border border-zinc-800 bg-zinc-900/60 p-4 text-xs leading-5 text-zinc-400"
|
||||
>
|
||||
<strong class="text-zinc-300">O escape continua o de sempre:</strong> arrastar a foto e
|
||||
os controles de zoom/ajuste fino. Nenhum botão novo, nenhum passo novo, nenhum aviso
|
||||
quando a medida não é possível.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cena 4: fluxo e regras -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 4 · Fluxo e regras — sem controle novo
|
||||
</h2>
|
||||
<div
|
||||
class="mt-4 rounded-xl border border-zinc-700 bg-zinc-900 p-5 text-xs leading-6 text-zinc-300"
|
||||
>
|
||||
<p class="font-mono text-[11px] text-zinc-400">
|
||||
[foto escolhida] → [recorte no aparelho: progresso | erro + retry] → [rosto medido?] — sim
|
||||
→ [zoom p/ cabeça a ±20% da cabeça dos candidatos] · não → [enquadramento de hoje, pelo
|
||||
contorno] → [prévia (arrastar/zoom como escape)] → [baixar PNG]
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 grid gap-4 lg:grid-cols-3">
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-zinc-200 uppercase">Ordem de mando</h3>
|
||||
<ol class="mt-3 list-decimal space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>Cobertura da janela (nunca sobra buraco).</li>
|
||||
<li>Proporção da cabeça (±20%).</li>
|
||||
<li>Nada — em caso de conflito, não se ajusta além do permitido.</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-zinc-200 uppercase">
|
||||
Garantias do recorte
|
||||
</h3>
|
||||
<ul class="mt-3 list-disc space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>O rosto nunca é cortado pelo enquadramento proporcional.</li>
|
||||
<li>Em multi-rosto, vale o maior (o do visitante).</li>
|
||||
<li>A medida acontece uma vez, no recorte.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-zinc-200 uppercase">Intocados</h3>
|
||||
<ul class="mt-3 list-disc space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>Sem controle novo; arrastar/zoom seguem como escape.</li>
|
||||
<li>Os 3 modelos anteriores, a arte-mestre e os textos.</li>
|
||||
<li>A nota de privacidade existente.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="mt-14 border-t border-zinc-800 pt-8 text-xs leading-5 text-zinc-500">
|
||||
<p
|
||||
class="mb-3 rounded-lg border border-amber-500/40 bg-amber-500/10 p-3 font-bold text-amber-300"
|
||||
>
|
||||
DEGRADED — não certificado; exige sign-off humano. Tier: opencode-go/deepseek-v4.1-flash.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-zinc-300">Janela da foto (S15) intocada:</strong> x=286, y=439,
|
||||
largura=592, altura=577 (card 1080×1440). Banners e demais geometrias do S15 seguem
|
||||
literais.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
<strong class="text-zinc-300">Em aberto (validar com produto):</strong> referência dos
|
||||
candidatos como constante medida uma vez vs medida em runtime; só automático com fallback vs
|
||||
toggle; detector (caixa do rosto vs malha facial).
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
<strong class="text-zinc-300">NEEDS ASSET:</strong> foto real do visitante (com e sem rosto)
|
||||
para validar a proporção; aqui usamos silhueta cinza. A arte oficial dos candidatos é real.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
<strong class="text-zinc-300">Ilustração:</strong> as caixas da régua e as escalas dos
|
||||
placeholders são aproximações para comunicar a intenção — a métrica final é a caixa do rosto
|
||||
medida pelo detector, com tolerância ±20% e cobertura sempre prevalecendo.
|
||||
</p>
|
||||
</aside>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
112
docs/plans/cards-time-de-voce-escala-cabeca.md
Normal file
112
docs/plans/cards-time-de-voce-escala-cabeca.md
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# Card "Time de você": cabeça do visitante proporcional à dos candidatos
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-19
|
||||
Issue: #1197
|
||||
Priority: P2
|
||||
Impeccable: C — ajuste de comportamento visual num funil público existente (sem controle novo)
|
||||
Design UI: docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html
|
||||
Appetite: ~1–1,5 dia eng; cabeça do visitante em proporção com os candidatos, com fallback seguro
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O card "Time de você" (S15, #1161) já enquadra a foto do visitante sozinho — mas só garante que o recorte cubra a janela da foto, ancorado no topo. Na prática, quem manda uma selfie de longe fica com a cabeça minúscula ao lado dos candidatos; quem manda um enquadramento fechado fica gigante. O pedido que abriu este item foi direto:
|
||||
|
||||
> "É possivel automaticamente medir o melhor tamanho para colocar a foto? Talvez basear no tamanho da cabeça do usuario e dos candidatos para ficar proporcional?"
|
||||
|
||||
A resposta é sim: medir a cabeça dos dois lados com a mesma régua (detecção de rosto) e usar essa proporção para escolher o zoom do enquadramento automático — com os limites do card mandando acima de tudo e, quando não der para medir, valendo exatamente o enquadramento de hoje. É um ajuste de comportamento no funil público, sem controle novo e sem nada saindo do aparelho.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** militante no celular, escolheu uma selfie razoável e viu na prévia a cabeça grande demais (foto de perto) ou pequena demais (foto de longe) em relação aos candidatos.
|
||||
- **Job principal:** a foto entrar na proporção certa sozinha, sem ajustar nada — e, se a medida não for possível, continuar exatamente como hoje.
|
||||
- **Fluxo desejado:** escolhe a foto → acompanha o recorte no aparelho → o card mede a cabeça do visitante e compara com a dos candidatos → ajusta o enquadramento para ficarem na mesma ordem de grandeza (±20%) → confere a prévia (arrastar/zoom como escape) → baixa o PNG. Sem rosto detectado ou detecção indisponível, a prévia sai como sai hoje, sem aviso de erro.
|
||||
- **Anti-goals de produto:** editor de enquadramento obrigatório; detecção de rosto como requisito do fluxo; segundo motor/pesquisa de rosto a cada ajuste; prometer recorte/medida perfeitos; expor erro de detecção; alterar o segmentador ou os 3 modelos existentes; upload/serviço/rede.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[foto escolhida] → [recorte no aparelho: progresso | erro + retry]
|
||||
→ [rosto medido?] ─ sim → [zoom para a cabeça ficar ±20% da cabeça dos candidatos]
|
||||
└ não → [enquadramento de hoje, pelo contorno]
|
||||
→ [prévia (arrastar/zoom como escape)] → [card pronto] → [baixar PNG]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html`
|
||||
- Cenas: prévia "antes" (cabeça pequena demais / grande demais) contra "depois" proporcional e o caminho de fallback (idêntico ao de hoje), em mobile e desktop; sem controle novo.
|
||||
- `Design tier: DEGRADED` — o frontier `openai/gpt-5.6-sol` bateu quota; o artefato saiu do `designer-degraded` e **não certifica**: exige sign-off humano no gate/PR.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- A cabeça do visitante no card fica na mesma ordem de grandeza das cabeças dos candidatos da arte oficial, com tolerância de ±20%, medida pela mesma régua nos dois lados.
|
||||
- O enquadramento automático continua sendo o padrão: nenhum passo novo, nenhum botão novo; arrastar/zoom seguem como escape.
|
||||
- O rosto do visitante nunca é cortado pelo enquadramento proporcional.
|
||||
- A cobertura da janela da foto prevalece: nunca sobra buraco no card, mesmo que isso limite a proporção.
|
||||
- Fallback obrigatório e silencioso: sem rosto detectado, com detecção indisponível ou em qualquer falha, vale o enquadramento atual pelo contorno — o card nunca falha nem mostra erro por causa da detecção de rosto (o único erro recuperável continua sendo o do recorte de fundo).
|
||||
- Tudo no aparelho; nenhum byte da foto sai; a nota de privacidade existente continua visível e verdadeira.
|
||||
- O resto do funil fica intocado: os 3 modelos anteriores, a galeria, os textos e os controles seguem como estão.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não.
|
||||
- **Decisões desbloqueadas:** N/A — nenhuma métrica é apresentada; a medida de cabeça é geometria efêmera no aparelho, não um dado de produto.
|
||||
- **Forma:** _adiada ao plano de implementação_ — aqui só a restrição de produto: nada de nome, foto ou medida saindo do aparelho nem entrando em analytics.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- ID `S18`; slug `cards-time-de-voce-escala-cabeca` (arquivo `docs/plans/cards-time-de-voce-escala-cabeca.md`); tipo `feature`; Priority `P2`; Impeccable `C — ajuste de comportamento visual num funil público existente (sem controle novo)`; Design UI: `docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html`.
|
||||
- O enquadramento automático passa a mirar a proporção da cabeça: a cabeça do visitante deve ficar na mesma ordem de grandeza das cabeças dos candidatos da arte oficial, tolerância ±20%.
|
||||
- Métrica única dos dois lados: o tamanho da cabeça é medido pela detecção de rosto (mesma métrica sobre a arte dos candidatos e sobre a foto do visitante); a referência dos candidatos é medida uma vez sobre a arte-mestre e registrada como constante literal no código.
|
||||
- Em foto com mais de um rosto, vale o maior rosto (o do visitante).
|
||||
- Fallback obrigatório: sem rosto detectado, com detecção indisponível ou em qualquer falha, o enquadramento atual pelo bbox continua valendo — o card NUNCA falha nem mostra erro por causa da detecção de rosto (só o recorte de fundo tem estado de erro).
|
||||
- A cobertura da janela da foto prevalece: se a proporção exigir ampliar além do necessário para cobrir, a cobertura manda (nunca sobra buraco no card).
|
||||
- Nenhum controle novo: continua automático, com arrastar/zoom como escape (como hoje).
|
||||
- O rosto nunca é cortado pelo enquadramento proporcional.
|
||||
- 100% no aparelho; o modelo de detecção segue o padrão do segmentador (mesma origem, commitado, licença documentada, lazy: só o modelo time paga); sem pin de modelo de execução (roda no modelo padrão da sessão; provider `openai` é reservado ao design).
|
||||
- A janela da foto do card time continua a do S15: x=286, y=439, largura=592, altura=577 (card 1080×1440).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** detecção no adapter `src/components/cards/cardCutout.ts` reaproveitando `@mediapipe/tasks-vision` + modelo de rosto commitado em `public/cards/`; matemática pura nova/estendida em `src/lib/cardPhotoTransform.ts` (a partir de `frameCardPhotoOnBbox`); estado no `useCardCutout.ts`; render/prévia no `src/components/cards/CardComposer.tsx`.
|
||||
- **Precedente a olhar:** S15 (`docs/plans/cards-time-de-voce-impl.md`) — motor/wasm/modelo/licença em `public/cards/` e o seam do stub; testes `tests/unit/cardPhotoTransform.unit.spec.ts`, `cardRender.unit.spec.ts` e e2e `tests/e2e/frontend.e2e.spec.ts` (stub do motor via `NEXT_PUBLIC_CARDS_CUTOUT_STUB=1`).
|
||||
- **Risco de acoplamento:** a detecção pega carona no mesmo caminho do recorte (o stub de e2e cobre hoje só o segmentador e o e2e falha em qualquer console error); pan/zoom do visitante continuam sendo o escape e nada pode travar a prévia.
|
||||
|
||||
## Dependências
|
||||
|
||||
- S15 (#1161) entregue — o modelo, o recorte e o auto-enquadramento existem.
|
||||
- S17 não é dependência, mas os dois tocam o mesmo pipeline — se rodarem em paralelo, serializar.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Editor de enquadramento obrigatório; qualquer controle novo de proporção.
|
||||
- Detecção de rosto como requisito do fluxo (foto sem rosto segue funcionando como hoje).
|
||||
- Identificação de pessoa / reconhecimento facial (detecção ≠ identificação), escolha entre vários rostos, rosto "perfeito".
|
||||
- Re-detectar o rosto a cada arrastar/zoom (a medida é feita no momento do recorte).
|
||||
- Alterar o segmentador, os 3 modelos antigos, a arte-mestre, os textos do funil ou a privacidade.
|
||||
- Upload/serviço/rede; métricas de uso da detecção; modelos adicionais/CMS/compartilhamento (cortados no S15, seguem cortados).
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Proporção virar editor de enquadramento.** Se alguém "só completar": nasce um passo obrigatório de ajuste fino de cabeça. **Corte neste item:** automático, ±20% quando der; arrastar/zoom como escape de sempre.
|
||||
- **Detecção virar requisito.** Se alguém "só completar": foto sem rosto detectado vira erro ou bloqueia o card. **Corte neste item:** fallback silencioso ao enquadramento atual; o único erro recuperável continua sendo o do recorte.
|
||||
- **Medir "no olho".** Se alguém "só completar": a régua vira opinião e cada ajuste reabre a conta. **Corte neste item:** métrica única (caixa do rosto), mesma dos dois lados, referência literal registrada.
|
||||
- **Cabeça proporcional a qualquer custo.** Se alguém "só completar": zoom sem limite, buraco no card ou rosto cortado. **Corte neste item:** cobertura > proporção > nada; rosto nunca cortado; fallback.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Medir a referência dos candidatos na arte em tempo de execução ou como constante medida uma vez?** **Opções:** A) constante literal medida uma vez sobre a arte-mestre | B) medir a arte a cada sessão | C) híbrido. **Recomendação:** A — a arte-mestre é fixa e já versionada; a constante é auditável, não custa nada no aparelho e não depende de a detecção acertar a arte a cada uso. _(assumido — validar com produto)_
|
||||
- **Prover também um toggle para desligar a proporção ou deixar só automático?** **Opções:** A) só automático com fallback | B) toggle do visitante | C) flag interna. **Recomendação:** A — anti-goal "controle novo"; se o ajuste incomodar, arrastar/zoom já é o escape. _(assumido — validar com produto)_
|
||||
- **Qual detector usar?** **Opções:** A) `FaceDetector` (caixa do rosto) | B) `FaceLandmarker` (malha facial) | C) motor próprio. **Recomendação:** A — a medida decidida é o tamanho da cabeça, e a caixa do rosto já a entrega; menos peso no celular e menos superfície. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #1197
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-escala-cabeca-ui-design.html` (+ assets em `cards-time-de-voce-escala-cabeca-ui-design-assets/`)
|
||||
- `docs/plans/cards-time-de-voce.md` e `cards-time-de-voce-impl.md` (S15, #1161)
|
||||
- `src/lib/cardPhotoTransform.ts` (`frameCardPhotoOnBbox`), `src/components/cards/cardCutout.ts`, `useCardCutout.ts`, `CardComposer.tsx`
|
||||
- `public/cards/team-card-base.png` / `team-card-example.jpg` (referência visual) e `selfie_segmenter.LICENSE.txt` (padrão de licença)
|
||||
- `tests/unit/cardPhotoTransform.unit.spec.ts`, `tests/unit/cardRender.unit.spec.ts`, `tests/e2e/frontend.e2e.spec.ts`
|
||||
- `AGENTS.md` — reserva do provider `openai` ao design e convenções do funil de cards
|
||||
924
docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html
Normal file
924
docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html
Normal file
|
|
@ -0,0 +1,924 @@
|
|||
<!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>DEGRADED — Design S17 · Card Time de você: harmonizar cores</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;
|
||||
}
|
||||
.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%);
|
||||
}
|
||||
|
||||
/* card composto pelo pipeline real: base -> foto -> front -> banners */
|
||||
.tcard {
|
||||
position: relative;
|
||||
aspect-ratio: 1080 / 1440;
|
||||
overflow: hidden;
|
||||
border-radius: 12px;
|
||||
background: #cfe7f7;
|
||||
container-type: inline-size;
|
||||
}
|
||||
.tcard-base,
|
||||
.tcard-front {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
pointer-events: none;
|
||||
}
|
||||
.tcard-window {
|
||||
position: absolute;
|
||||
left: 26.481%;
|
||||
top: 30.486%;
|
||||
width: 54.815%;
|
||||
height: 40.069%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.tcard-bust {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
height: auto;
|
||||
transform: translateX(-50%);
|
||||
color: #7f8a97;
|
||||
}
|
||||
.tcard-cast {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
mix-blend-mode: color;
|
||||
pointer-events: none;
|
||||
}
|
||||
.cast-original {
|
||||
background: #5f7896;
|
||||
opacity: 0.42;
|
||||
}
|
||||
.cast-harmonized {
|
||||
background: #e0964a;
|
||||
opacity: 0.3;
|
||||
}
|
||||
.photo-original {
|
||||
filter: saturate(0.8) brightness(0.92) contrast(0.97);
|
||||
}
|
||||
.photo-harmonized {
|
||||
filter: saturate(1.12) brightness(1.06) contrast(1.06);
|
||||
}
|
||||
.tcard-banner {
|
||||
position: absolute;
|
||||
left: 50.463%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transform: translate(-50%, -50%) rotate(var(--rot, -4.1deg));
|
||||
overflow: hidden;
|
||||
}
|
||||
.tcard-banner-red {
|
||||
top: 11.25%;
|
||||
height: 8.194%;
|
||||
width: 64.167%;
|
||||
background: var(--team-red);
|
||||
color: var(--team-yellow);
|
||||
--rot: -3.5deg;
|
||||
}
|
||||
.tcard-banner-blue {
|
||||
top: 21.736%;
|
||||
height: 12.222%;
|
||||
width: 47.13%;
|
||||
background: var(--team-blue);
|
||||
color: #ffffff;
|
||||
--rot: -4.1deg;
|
||||
}
|
||||
.tcard-face {
|
||||
font-family: Brexter, Impact, Haettenschweiler, 'Arial Narrow Bold', sans-serif;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.005em;
|
||||
font-size: calc(var(--fs, 9) * 1cqw);
|
||||
}
|
||||
|
||||
/* controles do compositor (estado pronto) */
|
||||
.ctrl-btn {
|
||||
display: inline-flex;
|
||||
min-height: 44px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--campaign-line);
|
||||
background: #fff;
|
||||
color: var(--campaign-ink);
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
}
|
||||
.switch-track {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 44px;
|
||||
height: 26px;
|
||||
flex: none;
|
||||
border-radius: 999px;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
.switch-thumb {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
left: 3px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 25%);
|
||||
transition: left 0.15s ease;
|
||||
}
|
||||
.switch-on {
|
||||
background: var(--pt-red);
|
||||
}
|
||||
.switch-on .switch-thumb {
|
||||
left: 21px;
|
||||
}
|
||||
.switch-off {
|
||||
background: #cfcac7;
|
||||
}
|
||||
.switch-off .switch-thumb {
|
||||
left: 3px;
|
||||
}
|
||||
.switch-sm {
|
||||
width: 34px;
|
||||
height: 20px;
|
||||
}
|
||||
.switch-sm .switch-thumb {
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
.switch-sm.switch-on .switch-thumb {
|
||||
left: 16px;
|
||||
}
|
||||
.switch-sm.switch-off .switch-thumb {
|
||||
left: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-zinc-950 text-zinc-100 antialiased">
|
||||
<div
|
||||
class="border-b border-amber-500/60 bg-amber-500/10 px-4 py-2 text-center text-[11px] font-black tracking-[0.14em] text-amber-300 uppercase"
|
||||
>
|
||||
DEGRADED — não certificado; exige sign-off humano · tier opencode-go/deepseek-v4.1-flash
|
||||
</div>
|
||||
|
||||
<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 · S17 · DEGRADED
|
||||
</p>
|
||||
<h1 class="mt-2 text-2xl font-black tracking-[-0.02em] sm:text-3xl">
|
||||
Card “Time de você”: harmonizar as cores da foto
|
||||
</h1>
|
||||
<p class="mt-3 max-w-3xl text-sm leading-6 text-zinc-400">
|
||||
No estado em que a foto já está pronta, um único controle binário ajusta brilho, contraste e
|
||||
saturação da selfie para o tom das fotos oficiais dos candidatos. Padrão LIGADO; desligar
|
||||
devolve a original na hora; vale para a prévia e para o PNG; nada persiste e não há editor.
|
||||
</p>
|
||||
<p class="mt-3 max-w-3xl text-xs leading-5 text-amber-300/90">
|
||||
<strong>DEGRADED:</strong> este design não está certificado. É insumo para decisão humana no
|
||||
gate/PR — o tier degradado nunca aprova.
|
||||
</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"
|
||||
>alvo ≥ 44px</span
|
||||
>
|
||||
<span class="rounded-full border border-zinc-700 bg-zinc-900 px-3 py-2"
|
||||
>padrão LIGADO</span
|
||||
>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Cena 1: estado pronto desktop, controle LIGADO -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 1 · Estado pronto · dialog desktop 1280 — “Harmonizar cores” LIGADO (padrão)
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
O controle entra depois do ajuste fino e antes de “Trocar foto”, com ajuda curta em pt-BR e
|
||||
alvo ≥ 44px. No estado ligado, a prévia já abre harmonizada.
|
||||
</p>
|
||||
<div
|
||||
class="relative mt-4 flex min-h-[920px] 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="s17-on-title"
|
||||
class="relative z-10 flex max-h-[94dvh] 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="s17-on-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"
|
||||
>
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust photo-harmonized"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div class="tcard-cast cast-harmonized"></div>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</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>
|
||||
<p class="mt-2 text-center text-[11px] font-bold text-[var(--campaign-muted)]">
|
||||
Prévia real · foto harmonizada (placeholder cinza)
|
||||
</p>
|
||||
|
||||
<div class="mt-5 flex items-center gap-3">
|
||||
<label for="s17-zoom" class="text-sm font-bold">Zoom</label>
|
||||
<button
|
||||
aria-label="Diminuir zoom"
|
||||
class="ctrl-btn size-11 shrink-0"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<input
|
||||
id="s17-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="ctrl-btn size-11 shrink-0"
|
||||
>
|
||||
+
|
||||
</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="ctrl-btn min-h-11">
|
||||
←
|
||||
</button>
|
||||
<button aria-label="Mover foto para cima" class="ctrl-btn min-h-11">↑</button>
|
||||
<button aria-label="Mover foto para baixo" class="ctrl-btn min-h-11">↓</button>
|
||||
<button aria-label="Mover foto para a direita" class="ctrl-btn min-h-11">→</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- controle Harmonizar cores · LIGADO -->
|
||||
<div class="relative mt-4">
|
||||
<div
|
||||
class="flex items-start gap-3 rounded-lg border border-[var(--campaign-line)] bg-white p-3"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="true"
|
||||
id="s17-harm"
|
||||
class="flex min-h-11 w-full items-start gap-3 text-left"
|
||||
>
|
||||
<span aria-hidden="true" class="switch-track switch-on mt-1.5">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-[var(--campaign-ink)]"
|
||||
>Harmonizar cores</span
|
||||
>
|
||||
<span class="mt-0.5 block text-xs leading-5 text-[var(--campaign-muted)]"
|
||||
>Ajusta o brilho e as cores da sua foto para combinar com as fotos do
|
||||
card.</span
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="absolute -top-2 -right-2 rounded-full bg-[var(--team-blue)] px-2 py-0.5 text-[9px] font-black text-white shadow"
|
||||
>LIGADO · alvo ≥ 44px</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<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)]"
|
||||
>
|
||||
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 2: mesmo estado pronto, controle DESLIGADO -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 2 · Estado pronto · dialog desktop 1280 — “Harmonizar cores” DESLIGADO
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
Desligar devolve a foto original na hora; tudo o mais (recorte, enquadramento, controles,
|
||||
CTA) permanece idêntico. Religar reaplica.
|
||||
</p>
|
||||
<div
|
||||
class="relative mt-4 flex min-h-[920px] 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="s17-off-title"
|
||||
class="relative z-10 flex max-h-[94dvh] 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="s17-off-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"
|
||||
>
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust photo-original"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div class="tcard-cast cast-original"></div>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</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>
|
||||
<p class="mt-2 text-center text-[11px] font-bold text-[var(--campaign-muted)]">
|
||||
Prévia real · foto original (placeholder cinza)
|
||||
</p>
|
||||
|
||||
<div class="mt-5 flex items-center gap-3">
|
||||
<label for="s17-zoom-off" class="text-sm font-bold">Zoom</label>
|
||||
<button aria-label="Diminuir zoom" class="ctrl-btn size-11 shrink-0">−</button>
|
||||
<input
|
||||
id="s17-zoom-off"
|
||||
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="ctrl-btn size-11 shrink-0">+</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="ctrl-btn min-h-11">
|
||||
←
|
||||
</button>
|
||||
<button aria-label="Mover foto para cima" class="ctrl-btn min-h-11">↑</button>
|
||||
<button aria-label="Mover foto para baixo" class="ctrl-btn min-h-11">↓</button>
|
||||
<button aria-label="Mover foto para a direita" class="ctrl-btn min-h-11">→</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- controle Harmonizar cores · DESLIGADO -->
|
||||
<div class="relative mt-4">
|
||||
<div
|
||||
class="flex items-start gap-3 rounded-lg border border-[var(--campaign-line)] bg-white p-3"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="false"
|
||||
class="flex min-h-11 w-full items-start gap-3 text-left"
|
||||
>
|
||||
<span aria-hidden="true" class="switch-track switch-off mt-1.5">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-[var(--campaign-ink)]"
|
||||
>Harmonizar cores</span
|
||||
>
|
||||
<span class="mt-0.5 block text-xs leading-5 text-[var(--campaign-muted)]"
|
||||
>Ajusta o brilho e as cores da sua foto para combinar com as fotos do
|
||||
card.</span
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="absolute -top-2 -right-2 rounded-full bg-stone-600 px-2 py-0.5 text-[9px] font-black text-white shadow"
|
||||
>DESLIGADO</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<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)]"
|
||||
>
|
||||
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 3: mobile drawer -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 3 · Estado pronto · drawer mobile 390 — controle LIGADO
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
No mobile o controle fica na mesma ordem do desktop e o alvo de toque cobre a linha inteira
|
||||
(≥ 44px), sem depender de mirar no trilho.
|
||||
</p>
|
||||
<div class="mt-4 grid gap-6 lg:grid-cols-2">
|
||||
<div
|
||||
class="relative h-[860px] 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="s17-m-title"
|
||||
class="absolute inset-x-0 bottom-0 max-h-[97%] 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">
|
||||
Time de você
|
||||
</p>
|
||||
<h3 id="s17-m-title" class="mt-1 text-lg leading-6 font-black">Confira seu card</h3>
|
||||
<p class="mt-1 text-sm leading-5 text-[var(--campaign-muted)]">
|
||||
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="px-5 pt-3 pb-5">
|
||||
<div
|
||||
class="relative mx-auto w-[180px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md"
|
||||
>
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust photo-harmonized"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div class="tcard-cast cast-harmonized"></div>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-2">
|
||||
<label for="s17-m-zoom" class="text-sm font-bold">Zoom</label>
|
||||
<button aria-label="Diminuir zoom" class="ctrl-btn size-11 shrink-0">−</button>
|
||||
<input
|
||||
id="s17-m-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="ctrl-btn size-11 shrink-0">+</button>
|
||||
</div>
|
||||
<div class="mt-3 grid grid-cols-4 gap-2">
|
||||
<button aria-label="Mover foto para a esquerda" class="ctrl-btn min-h-11">
|
||||
←
|
||||
</button>
|
||||
<button aria-label="Mover foto para cima" class="ctrl-btn min-h-11">↑</button>
|
||||
<button aria-label="Mover foto para baixo" class="ctrl-btn min-h-11">↓</button>
|
||||
<button aria-label="Mover foto para a direita" class="ctrl-btn min-h-11">→</button>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="mt-3 flex items-start gap-3 rounded-lg border border-[var(--campaign-line)] bg-white p-3"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="switch"
|
||||
aria-checked="true"
|
||||
class="flex min-h-11 w-full items-start gap-3 text-left"
|
||||
>
|
||||
<span aria-hidden="true" class="switch-track switch-on mt-1.5">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
<span class="min-w-0">
|
||||
<span class="block text-sm font-bold text-[var(--campaign-ink)]"
|
||||
>Harmonizar cores</span
|
||||
>
|
||||
<span class="mt-0.5 block text-xs leading-5 text-[var(--campaign-muted)]"
|
||||
>Ajusta o brilho e as cores da sua foto para combinar com as fotos do
|
||||
card.</span
|
||||
>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<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)]"
|
||||
>
|
||||
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)] text-sm font-extrabold text-white"
|
||||
>
|
||||
Criar meu card
|
||||
</button>
|
||||
</footer>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<!-- anatomia do controle -->
|
||||
<div class="max-w-[560px]">
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-zinc-200 uppercase">
|
||||
Anatomia do controle
|
||||
</h3>
|
||||
<ul class="mt-3 space-y-3 text-xs leading-5 text-zinc-400">
|
||||
<li>
|
||||
<strong class="text-zinc-200">Rótulo:</strong> “Harmonizar cores”, sempre visível
|
||||
(nunca só o trilho).
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-zinc-200">Ajuda:</strong> “Ajusta o brilho e as cores da sua foto
|
||||
para combinar com as fotos do card.”
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-zinc-200">Alvo:</strong> a linha inteira é o botão, com altura
|
||||
mínima de 44px; funciona por toque e teclado, com foco visível.
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-zinc-200">Semântica:</strong> binário ligado/desligado
|
||||
(<code>role="switch"</code> + <code>aria-checked</code>); no port, usa o vocabulário
|
||||
de toggle já existente no app (sem shadcn Switch).
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-zinc-200">Estados:</strong> LIGADO (trilho
|
||||
<code>--pt-red</code>, thumb à direita) e DESLIGADO (trilho cinza, thumb à
|
||||
esquerda).
|
||||
</li>
|
||||
<li>
|
||||
<strong class="text-zinc-200">Posição:</strong> depois de “Ajuste fino” e antes de
|
||||
“Trocar foto”; só no estado pronto.
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div
|
||||
class="mt-3 rounded-lg border border-zinc-800 bg-zinc-900/60 p-4 text-xs leading-5 text-zinc-400"
|
||||
>
|
||||
<strong class="text-zinc-300">Tokens reais:</strong> trilho ligado
|
||||
<code>--pt-red #A21C1C</code>; texto <code>--campaign-ink #000</code>; ajuda
|
||||
<code>--campaign-muted #6C615E</code>; borda <code>--campaign-line</code>; superfície
|
||||
<code>--campaign-surface</code>. Nada de cor nova.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cena 4: antes/depois -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 4 · Antes / depois da prévia — a escolha refletida
|
||||
</h2>
|
||||
<p class="mt-1 max-w-3xl text-xs leading-5 text-zinc-500">
|
||||
Mesmo recorte, mesmo enquadramento, mesma prévia: só o ajuste de brilho/contraste/saturação
|
||||
muda. A foto do visitante é um placeholder cinza (NEEDS ASSET) — nenhuma foto real de
|
||||
terceiro é simulada.
|
||||
</p>
|
||||
<div
|
||||
class="mt-4 overflow-hidden rounded-xl border border-zinc-700 bg-[var(--campaign-cream)] p-8 text-black shadow-2xl"
|
||||
>
|
||||
<div class="grid gap-8 sm:grid-cols-2">
|
||||
<figure class="m-0">
|
||||
<div class="mx-auto w-[240px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust photo-original"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div class="tcard-cast cast-original"></div>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center">
|
||||
<span
|
||||
class="inline-flex items-center gap-2 rounded-full border border-[var(--campaign-line)] bg-white px-3 py-2 text-xs font-black"
|
||||
>
|
||||
<span aria-hidden="true" class="switch-track switch-off switch-sm">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
Desligado · foto original
|
||||
</span>
|
||||
<p class="mt-2 text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Tom mais frio/des saturado em relação ao card.
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure class="m-0">
|
||||
<div class="mx-auto w-[240px] overflow-hidden rounded-lg border border-[var(--campaign-line)] shadow-md">
|
||||
<div class="tcard">
|
||||
<img src="../../public/cards/team-card-base.png" alt="" class="tcard-base" />
|
||||
<div class="tcard-window">
|
||||
<svg
|
||||
class="tcard-bust photo-harmonized"
|
||||
style="width: 38%"
|
||||
viewBox="0 0 100 130"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle cx="50" cy="30" r="26" fill="currentColor" />
|
||||
<path
|
||||
d="M38 50h24v20c18 6 30 24 30 60H8c0-36 12-54 30-60z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<div class="tcard-cast cast-harmonized"></div>
|
||||
</div>
|
||||
<img src="../../public/cards/team-card-front.png" alt="" class="tcard-front" />
|
||||
<div class="tcard-banner tcard-banner-red">
|
||||
<span class="tcard-face" style="--fs: 11.96">TIME DE</span>
|
||||
</div>
|
||||
<div class="tcard-banner tcard-banner-blue">
|
||||
<span class="tcard-face" style="--fs: 13">MARIA</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<figcaption class="mt-3 text-center">
|
||||
<span
|
||||
class="inline-flex items-center gap-2 rounded-full border border-[var(--campaign-line)] bg-white px-3 py-2 text-xs font-black"
|
||||
>
|
||||
<span aria-hidden="true" class="switch-track switch-on switch-sm">
|
||||
<span class="switch-thumb"></span>
|
||||
</span>
|
||||
Ligado · harmonizada (padrão)
|
||||
</span>
|
||||
<p class="mt-2 text-[11px] leading-4 text-[var(--campaign-muted)]">
|
||||
Brilho, contraste e saturação aproximados do tom dos candidatos.
|
||||
</p>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-center text-xs leading-5 text-[var(--campaign-muted)]">
|
||||
<strong>NEEDS ASSET:</strong> foto real do visitante para validar o ajuste no lugar do
|
||||
placeholder cinza. A referência de tom é a arte oficial
|
||||
<code>team-card-base.png</code>.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Cena 5: o que é / o que não é -->
|
||||
<section class="mt-14">
|
||||
<h2 class="text-xs font-black tracking-[0.12em] text-zinc-500 uppercase">
|
||||
Cena 5 · O que o controle faz — e o que fica fora
|
||||
</h2>
|
||||
<div class="mt-4 grid gap-4 lg:grid-cols-2">
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-emerald-300 uppercase">Faz</h3>
|
||||
<ul class="mt-3 list-disc space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>Padrão LIGADO: a prévia abre harmonizada, sem exigir decisão.</li>
|
||||
<li>Desligar devolve a foto original instantaneamente; religar reaplica.</li>
|
||||
<li>A escolha vale para a prévia e para o PNG baixado.</li>
|
||||
<li>100% no aparelho: sem upload, sem rede, sem analytics de PII.</li>
|
||||
<li>Ajuste suave e limitado ao tom das fotos oficiais (brilho, contraste, saturação).</li>
|
||||
<li>Fica só no modelo <code>time-de-voce</code>.</li>
|
||||
<li>A nota de privacidade existente continua visível e verdadeira.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="rounded-xl border border-zinc-700 bg-zinc-900 p-5">
|
||||
<h3 class="text-sm font-black tracking-wide text-red-300 uppercase">Não faz</h3>
|
||||
<ul class="mt-3 list-disc space-y-2 pl-5 text-xs leading-5 text-zinc-300">
|
||||
<li>Não é editor: sem sliders, curvas, presets ou ajuste manual.</li>
|
||||
<li>Não é retoque de beleza: sem pele, forma ou imperfeições.</li>
|
||||
<li>Não altera o recorte nem o enquadramento.</li>
|
||||
<li>Não persiste: sair e voltar retorna ao padrão LIGADO.</li>
|
||||
<li>Não toca os 3 modelos existentes nem as artes-mestre.</li>
|
||||
<li>Não promete “parecer profissional”.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside class="mt-14 border-t border-zinc-800 pt-8 text-xs leading-5 text-zinc-500">
|
||||
<p
|
||||
class="mb-3 rounded-lg border border-amber-500/40 bg-amber-500/10 p-3 font-bold text-amber-300"
|
||||
>
|
||||
DEGRADED — não certificado; exige sign-off humano. Tier: opencode-go/deepseek-v4.1-flash.
|
||||
</p>
|
||||
<p>
|
||||
<strong class="text-zinc-300">Copys literais do plano:</strong> rótulo “Harmonizar cores”;
|
||||
ajuda “Ajusta o brilho e as cores da sua foto para combinar com as fotos do card.”; nota de
|
||||
privacidade inalterada (<code>CARD_PHOTO_PRIVACY_NOTE</code>).
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
<strong class="text-zinc-300">Em aberto (validar com produto):</strong> referência de tom
|
||||
medida na arte-mestre vs perfil fixo; controle só no estado pronto vs também antes da foto.
|
||||
</p>
|
||||
<p class="mt-2">
|
||||
<strong class="text-zinc-300">Nota de justiça visual:</strong> os tons antes/depois aqui são
|
||||
uma ilustração sobre placeholder cinza; o ajuste real é automático e calibrado contra a
|
||||
arte-mestre, sem preview de número.
|
||||
</p>
|
||||
</aside>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
99
docs/plans/cards-time-de-voce-harmonizar-cores.md
Normal file
99
docs/plans/cards-time-de-voce-harmonizar-cores.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# Card "Time de você": harmonizar as cores da foto do visitante com o time
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-19
|
||||
Issue: #1196
|
||||
Priority: P2
|
||||
Impeccable: C — novo controle em estados já existentes de um funil público
|
||||
Design UI: docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html
|
||||
Appetite: ~0,5–1 dia eng; um toggle que deixa a foto do visitante com o tom do time
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O "Time de você" (S15, #1161, entregue em 2026-09-18) deixou o visitante entrar no card com a própria cara — mas quase sempre com uma selfie de celular ao lado de candidatos fotografados por profissional. O recorte fica bom e a foto ainda assim "destoa" do resto: luz mais quente ou fria, contraste e saturação em outra faixa. O pedido do humano foi literal: **"Seria possivel fazer automaticamente algum balanceamento de cores na foto do usuario para encaixar melhor com as fotos dos candidatos que são profissionais? Talvez possa ser um 'filtro' ativavel ou não, um toggle que o usuario seleciona ou não."**
|
||||
|
||||
A S15 listou "correção automática de foto" como rabbit hole e não a entregou; agora o humano pede isso de propósito. É item **novo** (S15 não é editado), com escopo pequeno e explícito: um toggle que ajusta a foto, não um editor.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** militante no celular, escolheu uma selfie razoável; vê a prévia e sente que a foto "não parece do time" ao lado dos candidatos.
|
||||
- **Job principal:** o card ficar harmônico sem virar edição de imagem e sem perder a cara dele.
|
||||
- **Fluxo desejado:** foto recortada e prévia prontas → vê o controle "Harmonizar cores" ligado por padrão → desliga e a foto original volta na hora → baixa o PNG com a escolha que deixou na tela.
|
||||
- **Anti-goals de produto:** editor de imagem (slider de cor/curvas/presets), filtro "instagramável", promessa de "parecer profissional", retoque de pele, alterar os 3 modelos existentes, upload/serviço/rede, analytics, persistência, segundo fluxo.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[estado pronto do Time de você] → [controle "Harmonizar cores" LIGADO por padrão]
|
||||
→ [desligar = foto original | religar] → [PNG com a escolha atual]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html` — cenas: controle no estado pronto (ligado/desligado), prévia antes/depois e card baixado, desktop e mobile.
|
||||
- `Design tier: DEGRADED` — o frontier `openai/gpt-5.6-sol` bateu quota; o artefato saiu do `designer-degraded` e **não certifica**: exige sign-off humano no gate/PR.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- No estado em que a foto já está pronta, o visitante vê "Harmonizar cores" com ajuda curta em pt-BR; funciona por toque e teclado (alvo ≥44px).
|
||||
- Padrão ligado: a prévia abre harmonizada; desligar devolve a foto original instantaneamente e religar reaplica.
|
||||
- A escolha vale para a prévia e para o PNG baixado; nada é persistido — sair e voltar volta ao padrão ligado.
|
||||
- O ajuste é automático, suave e limitado ao tom das fotos oficiais dos candidatos (referência: arte-mestre `public/cards/team-card-base.png`); não é retoque de beleza (nada de pele, forma ou imperfeições) e nunca altera o recorte.
|
||||
- 100% no aparelho: sem upload, sem analytics de PII, sem nova dependência de rede; vale só para `time-de-voce` (os 3 modelos existentes intocados).
|
||||
- A nota de privacidade existente (`CARD_PRIVACY_NOTE`/`CARD_PHOTO_PRIVACY_NOTE`) continua visível e verdadeira.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não.
|
||||
- **Decisões desbloqueadas:** N/A — nenhum dado novo é apresentado; a escolha do toggle é local e não gera métrica.
|
||||
- **Forma:** _adiada ao plano de implementação_ — medir "quantos ligam" seria analytics de interação, fora deste item.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- ID `S17`; slug `cards-time-de-voce-harmonizar-cores` (arquivo `docs/plans/cards-time-de-voce-harmonizar-cores.md`); tipo `feature`; Priority `P2`; Impeccable `C — novo controle em estados já existentes de um funil público`; Design UI `docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html`.
|
||||
- Rótulo do controle: `Harmonizar cores`; ajuda curta em pt-BR (ex.: "Ajusta o brilho e as cores da sua foto para combinar com as fotos do card.").
|
||||
- Padrão LIGADO; desligar devolve a foto original instantaneamente; a escolha vale para a prévia e para o PNG baixado; nada é persistido.
|
||||
- Ajuste automático, suave e limitado: aproxima brilho, contraste e saturação da foto do visitante do tom das fotos oficiais dos candidatos; referência: arte-mestre `public/cards/team-card-base.png`; não é retoque de beleza (sem suavizar pele, sem mudar forma, sem remover imperfeições); nunca altera o recorte.
|
||||
- 100% no aparelho, sem upload, sem analytics de PII, sem nova dependência de rede; vale só para o modelo `time-de-voce` (os 3 modelos existentes intocados).
|
||||
- Sem pin de modelo de execução (roda no modelo padrão da sessão; provider `openai` é reservado ao design).
|
||||
- A nota de privacidade existente continua visível e verdadeira (o ajuste não muda o que ela promete).
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** processamento de pixel local no recorte (`src/components/cards/cardCutout.ts`/`useCardCutout.ts`) ou no render puro (`src/lib/cardRender.ts`), via contexto estrutural; estado do toggle no `CardComposer.tsx` (controles do estado pronto).
|
||||
- **Precedente a olhar:** `src/components/CampaignNewsletterForm.tsx` (forms/controles) e tokens do tema `campaign-site` em `src/app/(frontend)/styles.css` (`--pt-red`, `--campaign-line`, `--campaign-ink`, `--campaign-muted`, `--field-border`, `--team-blue`); `src/components/ui` não tem shadcn Switch — o app usa radix `Toggle`/`Checkbox` e botões `aria-pressed`.
|
||||
- **Risco de acoplamento:** o recorte (`composeCutout`/máscara) não pode ser alterado; os 3 modelos existentes não podem mudar; sem rede/dependência nova; testes de unidade (se o ajuste nascer no recorte, specs próprios; sempre `cardRender.unit.spec.ts`) e e2e `tests/e2e/frontend.e2e.spec.ts` (describe Time de você, stub do motor via `NEXT_PUBLIC_CARDS_CUTOUT_STUB=1`) — o e2e falha em qualquer console error.
|
||||
|
||||
## Dependências
|
||||
|
||||
- S15 (#1161) entregue em 2026-09-18 — é o fluxo onde o controle entra.
|
||||
- Sem dependência dura de S16/S18.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Editor de imagem: slider de cor, curvas, presets, ajuste manual.
|
||||
- Filtro "instagramável", promessa de "parecer profissional" e retoque de beleza (pele, forma, imperfeições).
|
||||
- Alterar os 3 modelos existentes ou as artes-mestre.
|
||||
- Upload/serviço/rede, analytics, persistência, segundo fluxo.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **O toggle virar editor.** Se alguém "só completar": nascem sliders de cor/curvas, presets e pack de filtros "de marca". **Corte neste item:** um controle binário, sem ajuste manual.
|
||||
- **Escorregar para retoque de beleza.** Se alguém "só completar": suaviza pele e "melhora" o rosto. **Corte neste item:** só brilho/contraste/saturação, nada de forma ou pele.
|
||||
- **Processar no servidor ou baixar modelo pesado.** Se alguém "só completar": quebra a promessa de 100% no aparelho. **Corte neste item:** local, sem rede, sem dependência nova.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Padrão ligado ou desligado?** **Opções:** ligado | desligado. **Recomendação:** ligado — é o pedido literal do humano e o card fica bom sem exigir decisão. _(decidido nos literais; confirmar no gate)_
|
||||
- **Referência de tom medida na arte dos candidatos ou perfil fixo calibrado?** **Opções:** medir a arte-mestre `team-card-base.png` | perfil fixo (números calibrados uma vez). **Recomendação:** medir a arte como referência, com limites fixos de intensidade — segue o tom real do card sem virar "filtro da casa". _(assumido — validar com produto)_
|
||||
- **Toggle só no estado pronto ou também no estado vazio?** **Opções:** só no estado pronto | também antes da foto. **Recomendação:** só no estado pronto — sem foto não há o que harmonizar, e o estado vazio já tem carga própria. _(assumido — validar com produto)_
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #1196
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-harmonizar-cores-ui-design.html` (+ assets em `cards-time-de-voce-harmonizar-cores-ui-design-assets/`)
|
||||
- `docs/plans/cards-time-de-voce.md` / `cards-time-de-voce-impl.md` (S15) — o fluxo onde o controle entra
|
||||
- `public/cards/team-card-base.png` — arte-mestre usada como referência de tom
|
||||
- `src/components/cards/cardCutout.ts`, `useCardCutout.ts`, `CardComposer.tsx`, `src/lib/cardRender.ts`, `cardCopy.ts`
|
||||
- `src/components/CampaignNewsletterForm.tsx`; `src/app/(frontend)/styles.css`; `tests/e2e/frontend.e2e.spec.ts`; `AGENTS.md` (provider `openai` reservado ao design)
|
||||
1189
docs/plans/cards-time-de-voce-nomes-longos-ui-design.html
Normal file
1189
docs/plans/cards-time-de-voce-nomes-longos-ui-design.html
Normal file
File diff suppressed because it is too large
Load diff
107
docs/plans/cards-time-de-voce-nomes-longos.md
Normal file
107
docs/plans/cards-time-de-voce-nomes-longos.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# Card "Time de você": nomes longos cabendo no banner
|
||||
|
||||
Status: rascunho
|
||||
Atualizado em: 2026-09-19
|
||||
Issue: #1195
|
||||
Priority: P1
|
||||
Impeccable: C — ajuste visual num funil público existente (não é superfície nova)
|
||||
Design UI: docs/plans/cards-time-de-voce-nomes-longos-ui-design.html
|
||||
Appetite: ~0,5–1 dia eng; nomes longos cabendo sem corte no banner do card
|
||||
Responsável: —
|
||||
|
||||
## Intenção
|
||||
|
||||
O modelo "Time de você" (S15, #1161, no ar desde 2026-09-18) já tem o primeiro retorno de campo: nomes compostos não cabem. O verbatim da pessoa: "O card só permite hoje nomes com no máximo 7 letras, mas tem mais espaço no card. Deve mudar para permitir nomes maiores, podemos estender o espaço horizontal ocupado pela segunda linha do título. Ou mesmo diminuir um pouco a fonte da segunda linha para caber."
|
||||
|
||||
Medido no navegador com a fonte real (Brexter 700, cap ratio 0,72): hoje `GUSTAVO` (7) passa no cap 57; de 8 letras para cima tudo falha — `FERNANDA` para em 48, `GUILHERME` em 45, `JOÃO VITOR` em 44, `MARIA EDUARDA` em 30. A pessoa é mandada encurtar o nome, mas o banner azul tem folga visível dos dois lados. Esta entrega usa essa folga: o banner azul passa a se alargar só o necessário, podendo passar o banner vermelho e chegar perto da largura toda do card (decisão do humano no gate, 2026-09-19), e os limites de legibilidade são reposicionados, sem tocar no que o S15 mediu.
|
||||
|
||||
## Persona e fluxo
|
||||
|
||||
- **Persona / contexto:** militante no celular com nome composto (Maria Eduarda, João Vitor, Pedro Henrique) que hoje é obrigado a encurtar ou apelidar o próprio nome para gerar o card.
|
||||
- **Job principal:** ver o nome inteiro no card, sem apelido forçado e sem corte.
|
||||
- **Fluxo desejado:** abre `#cards` na home (ou `/cards`) → escolhe `Time de você` → digita o nome composto → vê o banner azul se alargar apenas o necessário e o nome inteiro em uma linha na prévia → baixa o PNG. Nenhum passo novo, nenhuma decisão nova.
|
||||
- **Anti-goals de produto:** virar editor de texto/layout, permitir duas linhas no banner, mexer no banner vermelho ou na geometria medida do S15, regenerar a arte de exemplo, mexer no modelo de nome (S13) ou nos outros 3 modelos.
|
||||
|
||||
### Esboço de fluxo (C)
|
||||
|
||||
```text
|
||||
[home/#cards ou /cards] → [Time de você] → [digita nome composto]
|
||||
→ [banner azul alarga só o necessário até o teto] → [prévia com nome inteiro em 1 linha]
|
||||
→ [fora do teto: mensagem de nome curto, como hoje] → [baixar PNG]
|
||||
```
|
||||
|
||||
### Design UI (C)
|
||||
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-nomes-longos-ui-design.html`
|
||||
- Cenas: nome curto no banner de referência (509), nomes longos com banner alargado (até o teto de 1000) e nome que continua pedindo encurtamento, em desktop e mobile.
|
||||
- `Design tier: DEGRADED` — o frontier `openai/gpt-5.6-sol` bateu quota; o artefato saiu do `designer-degraded` e **não certifica**: exige sign-off humano no gate/PR.
|
||||
|
||||
## Objetivo e aceite
|
||||
|
||||
- Nomes que DEVEM caber (aceite, medidos com a fonte real): MARIA EDUARDA (cap 62), PEDRO HENRIQUE (62), MARIA CLARA (73), ANA BEATRIZ (78), JOSÉ CARLOS (78), JOÃO VITOR (89), GUILHERME (94), FERNANDA (98), MARIA DA CONCEIÇÃO (47), FULANO e MARIA no cap ideal 130.
|
||||
- O banner azul do nome deixa de ter largura fixa: mantém a referência 509 quando o nome cabe nela e alarga só o necessário até o teto de 1000 (≈40px de margem em cada lado do card) — pode ultrapassar o banner vermelho (693) como decidido no gate; centro, y, altura e rotação permanecem os do S15.
|
||||
- O banner alargado nunca cruza o topo da janela da foto (y=439) nem as margens do card (para qualquer largura ≤1000, o canto mais baixo fica em ~436).
|
||||
- Nome continua em uma linha, nunca quebra e nunca é cortado: fora do teto, a pessoa recebe a mesma mensagem de "nome muito longo" do modelo de nome — os absurdos pinados (BARTOLOMEUCOSTAJUNIOR e ANTICONSTITUCIONALISSIMAMENTE) seguem como `too-long`; como o teto subiu, `MARIA DA CONCEIÇÃO` passa a caber e o pin de unit correspondente muda (o nome de teste claramente fora passa a ser o absurdo mais longo).
|
||||
- O banner vermelho "TIME DE", as cores e o restante da geometria medida do S15 ficam intocados; os outros 3 modelos do funil e o modelo de nome não mudam.
|
||||
- A prévia de exemplo (`team-card-example.jpg`) é referência congelada e fica fora do escopo: não regenerar neste item.
|
||||
- Um único editor, privacidade local e download por toque/teclado continuam como hoje.
|
||||
|
||||
## Dados (intenção)
|
||||
|
||||
- **Vou apresentar dados?** Não.
|
||||
- **Decisões desbloqueadas:** N/A — nenhuma métrica é apresentada; o ajuste é de layout/legibilidade, não desbloqueia decisão operacional por dado.
|
||||
- **Forma:** _adiada ao plano de implementação_ — nome e imagem seguem insumos locais do card; nenhum conteúdo pessoal entra em analytics.
|
||||
|
||||
## Dados da decisão (literais)
|
||||
|
||||
- ID `S16`; slug `cards-time-de-voce-nomes-longos`; tipo `feature`; Priority `P1`; Impeccable `C — ajuste visual num funil público existente (não é superfície nova)`; Design UI: `docs/plans/cards-time-de-voce-nomes-longos-ui-design.html`.
|
||||
- O banner azul deixa de ter largura fixa: mantém a largura de referência 509 quando o nome cabe nela e se alarga só o necessário, até o teto de 1000 (≈40px de margem em cada lado do card; pode ultrapassar o banner vermelho 693 — decidido no gate em 2026-09-19). Centro 545, y 313, altura 176 e rotação −4,1° inalterados.
|
||||
- Tinta máxima disponível sobe de 470 para 961; cap mínimo legível desce de 56 para 40.
|
||||
- Nomes que DEVEM caber (exemplos de aceite, medidos): MARIA DA CONCEIÇÃO (cap 47, banner ~991), MARIA EDUARDA/PEDRO HENRIQUE (cap 62), MARIA CLARA (73), ANA BEATRIZ/JOSÉ CARLOS (78), JOÃO VITOR (89), GUILHERME (94), FERNANDA (98), FULANO/MARIA no cap ideal 130.
|
||||
- Nomes que continuam recebendo a mensagem de nome curto: os absurdos pinados (BARTOLOMEUCOSTAJUNIOR, cap 39; ANTICONSTITUCIONALISSIMAMENTE, cap 30) — nunca corte silencioso.
|
||||
- Sem pin de modelo de execução (roda no modelo padrão da sessão; provider `openai` é reservado ao design).
|
||||
- A prévia de exemplo do modelo (`team-card-example.jpg`, mostrada na galeria e no estado vazio) é uma referência congelada e fica FORA do escopo; não regenerar neste item.
|
||||
- O banner alargado não pode cruzar o topo da janela da foto (y=439) nem as margens do card.
|
||||
- Tudo o mais do S15 permanece literal: banner vermelho 693×118 (centro 545, 162, −3,5°), fill azul `#0061A5`, texto branco `#FFFFFF`, uma linha, sem quebra.
|
||||
|
||||
## Direção no codebase (hipótese)
|
||||
|
||||
- **Áreas prováveis:** `src/lib/cardModels.ts` (TEAM_CARD_NAME_BANNER/TEAM_CARD_NAME_SLOT), `src/lib/cardNameFit.ts`, `src/lib/cardRender.ts` (drawCardBanner/drawCardName/renderTeamCard), `src/components/cards/CardComposer.tsx`; testes `tests/unit/cardModels.unit.spec.ts`, `tests/unit/cardNameFit.unit.spec.ts`, `tests/unit/cardRender.unit.spec.ts`, e2e `tests/e2e/frontend.e2e.spec.ts` (describe Time de você; hoje pina um nome absurdo como too-long).
|
||||
- **Precedente a olhar:** o próprio S15 (`docs/plans/cards-time-de-voce.md` e `-impl.md`) — a geometria medida e o fit de uma linha já estão lá.
|
||||
- **Risco de acoplamento:** o fit é compartilhado com o modelo de nome (slot próprio, 714/48) — o ajuste deve ficar no slot do time, sem afrouxar o outro modelo; o e2e falha em qualquer console error. O unit de `cardNameFit` hoje pina `'Maria da Conceição'` e `'Bartolomeucostajunior'` como `too-long` — com o teto novo o primeiro passa a caber e o pin precisa ser reescrito (nome de teste claramente fora).
|
||||
|
||||
## Dependências
|
||||
|
||||
- S15 (#1161) entregue em 2026-09-18; sem outras dependências.
|
||||
|
||||
## Fora de escopo
|
||||
|
||||
- Duas linhas no banner ou auto-quebra de nome.
|
||||
- Mudar o banner vermelho, as cores ou qualquer outra geometria medida do S15.
|
||||
- Regenerar `team-card-example.jpg` ou qualquer arte-mestre.
|
||||
- Mexer no modelo de nome (S13) e nos outros 3 modelos do funil.
|
||||
- Editor de texto/layout, escolha manual de tamanho de fonte ou largura do banner.
|
||||
- Métricas de uso, analytics de nome, ou qualquer persistência do nome.
|
||||
|
||||
## Rabbit holes de produto
|
||||
|
||||
- **Encolher a fonte até caber sempre.** Se alguém "só completar": nome gigante vira letra ilegível e o aceite de legibilidade morre. **Corte neste item:** cap mínimo 40; abaixo disso, mensagem de nome curto.
|
||||
- **Fixar o banner no teto para todo mundo.** Se alguém "só completar": o card de quem tem nome curto deixa de parecer o exemplo do S15. **Corte neste item:** largura dinâmica com referência 509 e alargamento só quando necessário.
|
||||
- **Cortar ou elidir o nome na marra.** Se alguém "só completar": entra corte silencioso no lugar da mensagem. **Corte neste item:** nunca cortar; fora do teto, pedir nome curto.
|
||||
- **Regenerar o exemplo para "ficar coerente".** Se alguém "só completar": vira produção de arte não pedida. **Corte neste item:** exemplo congelado, fora do escopo.
|
||||
|
||||
## Questões em aberto (produto)
|
||||
|
||||
- **Teto de largura do banner azul?** **Decidido com o humano em 2026-09-19:** pode passar o vermelho e ir até quase a largura toda do card sem problema → teto de 1000 (≈40px de margem em cada lado), com o banner se alargando só o necessário.
|
||||
- **Cap mínimo 40 ou 44?** **Decidido com o humano em 2026-09-19:** 40 — é o valor em que os nomes compostos longos cabem medidos.
|
||||
- **Banner dinâmico ou fixo no teto?** **Decidido com o humano em 2026-09-19:** dinâmico — curto mantém a referência 509, longo alarga só o necessário até 1000.
|
||||
- **A mensagem de nome curto muda?** **Decidido com o humano em 2026-09-19:** manter idêntica à do modelo de nome — mudar só o limiar, não o texto.
|
||||
|
||||
## Referências
|
||||
|
||||
- GitHub Issue #1195
|
||||
- Design UI (gate): `docs/plans/cards-time-de-voce-nomes-longos-ui-design.html` (+ assets em `cards-time-de-voce-nomes-longos-ui-design-assets/`)
|
||||
- `docs/plans/cards-time-de-voce.md` e `docs/plans/cards-time-de-voce-impl.md` — S15, geometria medida e fit
|
||||
- `src/lib/cardModels.ts`, `src/lib/cardNameFit.ts`, `src/lib/cardRender.ts`, `src/components/cards/CardComposer.tsx`
|
||||
- `tests/unit/cardModels.unit.spec.ts`, `tests/unit/cardNameFit.unit.spec.ts`, `tests/unit/cardRender.unit.spec.ts`, `tests/e2e/frontend.e2e.spec.ts` (describe Time de você)
|
||||
- `AGENTS.md` — convenções do funil de cards e fontes commitadas
|
||||
Loading…
Reference in a new issue