feat(frontend): branding "B2BCall by Handix" — logos novos em todo o app

Usuário forneceu 3 logos novos (B2BCall horizontal, B2BCall quadrado, Handix —
empresa mãe). Processados com sharp (trim de whitespace + resize) e salvos em
apps/frontend/public/branding/, substituindo o antigo b2blogo.png (mesmo desenho,
só com mais espaço em branco ao redor).

Achado ao processar: Logo_Handix.png original era RGB sem canal alpha (fundo branco
opaco) — aplicar o mesmo filtro brightness-0 invert já usado no logo B2BCall pro
painel escuro do login virava um retângulo branco sólido. Corrigido recolorindo
pixels próximos de branco como transparentes via sharp antes de salvar.

Sidebar (tenant e platform) ganha o ícone do B2BCall ao lado do nome do tenant/
"PLATFORM" no cabeçalho (canto superior esquerdo); topbar ganha o logo da Handix
com link pra www.handix.com.br (canto superior direito, escondido no mobile). Login
ganha "BY HANDIX" abaixo do logo principal e um rodapé com o logo da Handix + link.

Testado ponta a ponta via Puppeteer: login, dashboard tenant/platform (claro e
escuro), sidebar recolhida, drawer mobile.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
2026-08-30 11:01:55 -03:00
parent 4b927e9d9e
commit a15ea26cb5
20 changed files with 65 additions and 8 deletions

31
TODO.md
View File

@@ -1757,6 +1757,37 @@ secao 96-103, 124, 169) + achado real de autorização em `/ai/models`
`/app` direto, sem passar pela tela de login de novo. Usuário de `/app` direto, sem passar pela tela de login de novo. Usuário de
teste removido no final teste removido no final
## PHASE 50 — Branding "B2BCall by Handix" (pedido do usuário: logos novos)
- [x] Usuário forneceu 3 logos novos na raiz do projeto (`logo_b2bcall_
horizontal.png`, `logo_b2bcall_quadrado.png`, `Logo_Handix.png`,
empresa mãe). Processados com `sharp` (trim de whitespace + resize
pra tamanho web) e salvos em `apps/frontend/public/branding/` como
`b2bcall-horizontal.png`/`b2bcall-quadrado.png`/`handix-logo.png` —
o antigo `b2blogo.png` foi substituído (era o mesmo desenho, só com
mais espaço em branco ao redor)
- [x] achado ao processar: `Logo_Handix.png` original era RGB (sem canal
alpha) — fundo branco opaco, não transparente. Aplicado
`brightness-0 invert` nele (mesmo filtro já usado no logo B2BCall
pro painel escuro do login) virava um retângulo branco sólido, sem
nada visível dentro. Corrigido com um script Node usando `sharp`
pra recolorir pixels próximos de branco (limiar 245) como
transparentes antes de salvar — resultado com alpha real,
confirmado lendo os bytes RGBA de um pixel de fundo (0,0,0,0)
- [x] Sidebar (tenant e platform): ícone quadrado do B2BCall ao lado do
nome do tenant/"PLATFORM" no cabeçalho (canto superior esquerdo da
tela) — some junto com o texto quando a sidebar está recolhida,
mesmo comportamento de antes
- [x] Topbar: logo da Handix (canto superior direito, com link pra
www.handix.com.br em nova aba) antes do seletor de tema — escondido
abaixo de `sm` (mobile) pra não espremer a topbar já apertada
- [x] Login: "BY HANDIX" abaixo do logo principal do B2BCall no painel de
marca, e um rodapé com o logo da Handix (versão branca) + link pra
www.handix.com.br
- [x] Testado ponta a ponta via Puppeteer: tela de login, dashboard do
tenant e da plataforma (claro e escuro), sidebar recolhida, drawer
mobile — logos aparecendo corretos em todos, sem o artefato de
retângulo branco no Handix
--- ---
## Riscos conhecidos ## Riscos conhecidos

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 766 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@@ -5,7 +5,7 @@
/* /*
* Tokens (agente.md secao 165: colors/typography/spacing/radius/shadows/ * Tokens (agente.md secao 165: colors/typography/spacing/radius/shadows/
* status colors). Paleta derivada do logo real (apps/frontend/public/ * status colors). Paleta derivada do logo real (apps/frontend/public/
* branding/b2blogo.png): navy profundo + azul saturado + teal de apoio — * branding/b2bcall-horizontal.png): navy profundo + azul saturado + teal de apoio —
* nunca uma paleta nova escolhida livremente (agente.md secao "Brand * nunca uma paleta nova escolhida livremente (agente.md secao "Brand
* Commitments" em PRODUCT.md). * Commitments" em PRODUCT.md).
*/ */

View File

@@ -44,7 +44,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<body className="font-sans antialiased"> <body className="font-sans antialiased">
{/* {/*
THESIS: B2BCall's dashboards read as a live instrument panel, not a generic admin table — the platform's pulse before any list. THESIS: B2BCall's dashboards read as a live instrument panel, not a generic admin table — the platform's pulse before any list.
OWN-WORLD: navy #0B1B3A ground, brand blue #2554FF primary, teal #2EC9C0 accent (from b2blogo.png); IBM Plex Sans UI, IBM Plex Mono tabular readouts. OWN-WORLD: navy #0B1B3A ground, brand blue #2554FF primary, teal #2EC9C0 accent (from b2bcall-horizontal.png); IBM Plex Sans UI, IBM Plex Mono tabular readouts.
STORY: a platform admin opens the dashboard and reads the whole platform's live state — tenants, calls in flight, capacity headroom — in one glance. STORY: a platform admin opens the dashboard and reads the whole platform's live state — tenants, calls in flight, capacity headroom — in one glance.
FIRST VIEWPORT: sidebar+topbar shell, content opens on a grid of large mono instrument tiles for live counters, live ones pulse; a quieter row below for not-yet-available billing figures, explicitly marked pending. FIRST VIEWPORT: sidebar+topbar shell, content opens on a grid of large mono instrument tiles for live counters, live ones pulse; a quieter row below for not-yet-available billing figures, explicitly marked pending.
FORM: concept-seed surface/operate key ae95b901, candidate #3 (seven-segment/instrument panel) chosen over dealt lead #4 (HyperCard) — reasoned override, no interactive decision round available in this environment, disclosed to the user. FORM: concept-seed surface/operate key ae95b901, candidate #3 (seven-segment/instrument panel) chosen over dealt lead #4 (HyperCard) — reasoned override, no interactive decision round available in this environment, disclosed to the user.

View File

@@ -66,7 +66,10 @@ export default function LoginPage() {
backgroundSize: "40px 40px", backgroundSize: "40px 40px",
}} }}
/> />
<Image src="/branding/b2blogo.png" alt="B2BCall" width={180} height={44} priority className="relative brightness-0 invert" /> <div className="relative">
<Image src="/branding/b2bcall-horizontal.png" alt="B2BCall" width={180} height={59} priority className="brightness-0 invert" />
<p className="mt-2 text-xs font-medium uppercase tracking-[0.2em] text-white/45">by Handix</p>
</div>
<div className="relative max-w-md"> <div className="relative max-w-md">
<p className="text-2xl font-semibold leading-snug text-white"> <p className="text-2xl font-semibold leading-snug text-white">
Discador preditivo, call center e análise de IA sob um único painel de controle. Discador preditivo, call center e análise de IA sob um único painel de controle.
@@ -76,12 +79,18 @@ export default function LoginPage() {
chamada. chamada.
</p> </p>
</div> </div>
<div className="relative flex items-center gap-3 border-t border-white/10 pt-6">
<Image src="/branding/handix-logo.png" alt="Handix" width={28} height={22} className="brightness-0 invert opacity-70" />
<a href="https://www.handix.com.br" target="_blank" rel="noopener noreferrer" className="text-xs text-white/50 hover:text-white/80">
www.handix.com.br
</a>
</div>
</div> </div>
{/* Form */} {/* Form */}
<div className="flex flex-1 flex-col justify-center px-6 py-12 sm:px-12 lg:px-16"> <div className="flex flex-1 flex-col justify-center px-6 py-12 sm:px-12 lg:px-16">
<div className="mx-auto w-full max-w-sm"> <div className="mx-auto w-full max-w-sm">
<Image src="/branding/b2blogo.png" alt="B2BCall" width={140} height={34} priority className="mb-10 lg:hidden" /> <Image src="/branding/b2bcall-horizontal.png" alt="B2BCall" width={140} height={46} priority className="mb-10 lg:hidden" />
<h1 className="text-xl font-semibold text-foreground">Entrar no B2BCall</h1> <h1 className="text-xl font-semibold text-foreground">Entrar no B2BCall</h1>
<p className="mt-1 text-sm text-muted-foreground">Acesse com as credenciais da sua conta.</p> <p className="mt-1 text-sm text-muted-foreground">Acesse com as credenciais da sua conta.</p>

View File

@@ -17,7 +17,7 @@ export default async function SelectTenantPage() {
return ( return (
<div className="flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-12"> <div className="flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-12">
<div className="w-full max-w-sm"> <div className="w-full max-w-sm">
<Image src="/branding/b2blogo.png" alt="B2BCall" width={140} height={34} priority className="mx-auto mb-8" /> <Image src="/branding/b2bcall-horizontal.png" alt="B2BCall" width={140} height={46} priority className="mx-auto mb-8" />
<h1 className="text-center text-lg font-semibold text-foreground">Qual conta você quer acessar?</h1> <h1 className="text-center text-lg font-semibold text-foreground">Qual conta você quer acessar?</h1>
<p className="mt-1 text-center text-sm text-muted-foreground">Sua conta tem acesso a mais de um tenant.</p> <p className="mt-1 text-center text-sm text-muted-foreground">Sua conta tem acesso a mais de um tenant.</p>
<div className="mt-6"> <div className="mt-6">

View File

@@ -48,7 +48,7 @@ export function TrocarSenhaView() {
return ( return (
<div className="flex min-h-dvh flex-col items-center justify-center px-6 py-12"> <div className="flex min-h-dvh flex-col items-center justify-center px-6 py-12">
<div className="mx-auto w-full max-w-sm"> <div className="mx-auto w-full max-w-sm">
<Image src="/branding/b2blogo.png" alt="B2BCall" width={140} height={34} priority className="mb-10" /> <Image src="/branding/b2bcall-horizontal.png" alt="B2BCall" width={140} height={46} priority className="mb-10" />
<h1 className="text-xl font-semibold text-foreground">Trocar senha</h1> <h1 className="text-xl font-semibold text-foreground">Trocar senha</h1>
<p className="mt-1 text-sm text-muted-foreground"> <p className="mt-1 text-sm text-muted-foreground">
Esta é a senha temporária gerada na criação da sua conta escolha uma nova antes de continuar. Esta é a senha temporária gerada na criação da sua conta escolha uma nova antes de continuar.

View File

@@ -34,7 +34,7 @@ export function MobileNavDrawer({ items }: { items: NavSection[] }) {
> >
<Dialog.Title className="sr-only">Navegação</Dialog.Title> <Dialog.Title className="sr-only">Navegação</Dialog.Title>
<div className="flex h-16 items-center justify-between border-b border-border px-4"> <div className="flex h-16 items-center justify-between border-b border-border px-4">
<Image src="/branding/b2blogo.png" alt="B2BCall" width={110} height={27} /> <Image src="/branding/b2bcall-horizontal.png" alt="B2BCall" width={110} height={36} />
<Dialog.Close asChild> <Dialog.Close asChild>
<button <button
type="button" type="button"

View File

@@ -1,6 +1,7 @@
"use client"; "use client";
import { useState } from "react"; import { useState } from "react";
import Image from "next/image";
import { ChevronLeft, ChevronRight } from "lucide-react"; import { ChevronLeft, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { NavList } from "./nav-list"; import { NavList } from "./nav-list";
@@ -21,7 +22,12 @@ export function Sidebar({ items, railLabel }: { items: NavSection[]; railLabel:
)} )}
> >
<div className="flex h-16 items-center justify-between border-b border-border px-4"> <div className="flex h-16 items-center justify-between border-b border-border px-4">
{!collapsed && <span className="text-sm font-semibold tracking-wide text-foreground">{railLabel}</span>} {!collapsed && (
<div className="flex min-w-0 items-center gap-2.5">
<Image src="/branding/b2bcall-quadrado.png" alt="" width={28} height={23} className="shrink-0" />
<span className="truncate text-sm font-semibold tracking-wide text-foreground">{railLabel}</span>
</div>
)}
<button <button
type="button" type="button"
onClick={() => setCollapsed((c) => !c)} onClick={() => setCollapsed((c) => !c)}

View File

@@ -1,5 +1,6 @@
"use client"; "use client";
import Image from "next/image";
import { useRouter, usePathname } from "next/navigation"; import { useRouter, usePathname } from "next/navigation";
import { LogOut } from "lucide-react"; import { LogOut } from "lucide-react";
import { ThemeToggle } from "../platform-shell/theme-toggle"; import { ThemeToggle } from "../platform-shell/theme-toggle";
@@ -27,6 +28,16 @@ export function Topbar({ items, fallbackTitle, userLabel }: { items: NavSection[
</div> </div>
</div> </div>
<div className="flex shrink-0 items-center gap-2 sm:gap-4"> <div className="flex shrink-0 items-center gap-2 sm:gap-4">
<a
href="https://www.handix.com.br"
target="_blank"
rel="noopener noreferrer"
title="Handix — empresa desenvolvedora do B2BCall"
className="hidden shrink-0 opacity-70 transition-opacity hover:opacity-100 sm:block"
>
<Image src="/branding/handix-logo.png" alt="Handix" width={38} height={31} />
</a>
<div className="hidden h-6 w-px bg-border sm:block" aria-hidden />
<ThemeToggle /> <ThemeToggle />
<div className="hidden h-6 w-px bg-border sm:block" aria-hidden /> <div className="hidden h-6 w-px bg-border sm:block" aria-hidden />
<span className="hidden max-w-[220px] truncate text-sm text-muted-foreground sm:inline">{userLabel}</span> <span className="hidden max-w-[220px] truncate text-sm text-muted-foreground sm:inline">{userLabel}</span>