* feat: aplicar identidade Heritage Editorial ao painel Filament O painel admin ainda usava os defaults de fábrica do Filament (Amber, Zinc, Inter Variable, dark mode ligado) enquanto o site público já seguia o design system Heritage Editorial há várias entregas — a inconsistência ficava evidente para quem navegava entre as duas áreas e não havia nenhum teste travando a configuração do painel. - Cores primary/gray/danger/warning/success viram arrays explícitos de 11 tons (não Color::hex()/string), a única forma de preservar os hexadecimais exatos do DESIGN.md — Color::hex() decompõe a cor e remonta lightness/chroma por uma tabela fixa, perdendo a cor real. Os tons foram escolhidos verificando empiricamente com ButtonComponentColorMap qual shade o botão sólido realmente usa (600/500/50), e o tom 50 do primary é um verde-oliva claro (não branco puro) porque BadgeComponent usa bg-color-50 diretamente e um branco puro tornaria o badge quase invisível sobre o Papel Marfim. - EB Garamond self-hosted via LocalFontProvider (sans/mono/serif), sem reintroduzir uma requisição externa ao fonts.bunny.net — o hook HEAD_END reaproveita o mesmo <x-fonts /> do site público. - Novo tema Vite (resources/css/filament/admin/theme.css) zera radius e shadow só no escopo do Filament, incluindo a variável --radius "bare" (usada em ~227 regras do próprio Filament) e um override manual para o CSS pré-compilado do tooltip Tippy.js, que não é alcançado pelo @theme. - Dark mode desativado: o Heritage Editorial é uma paleta única. - Teste de regressão (AdminPanelBrandParityTest) pinando cores, fontes e configuração do tema, incluindo uma renderização real de /admin/login — foi essa renderização que pegou um bug real: passar a família já entre aspas simples quebrava a declaração CSS (--font-family: ''EB Garamond''), silenciosamente caindo para ui-sans-serif. Bundle público verificado byte a byte: nenhuma declaração CSS existente mudou de valor (apenas classes novas e não usadas pelo site público foram adicionadas ao app.css, um efeito colateral inerte de ter uma segunda entrada Tailwind no mesmo build do Vite). Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code * fix(build): copiar CSS do Filament no estágio frontend e neutralizar sombras Dois defeitos encontrados ao revisar a paridade visual do painel. O primeiro impedia o build da imagem. O `resources/css/filament/admin/ theme.css` importa o CSS não compilado do próprio Filament, e o estágio `frontend` do Dockerfile copia apenas `package.json`, `vite.config.js`, `resources` e `public` — nunca `vendor`. O `npm run build` falhava na resolução do import, o que derrubaria os jobs `container` e `browser` do CI. Agora a subárvore `vendor/filament` é copiada do estágio do composer, em vez de todo o `vendor`, para manter o contexto pequeno. O segundo é silencioso e mais interessante. O plugin do Tailwind compartilha um único contexto entre todas as entradas do build, então o `@source app/Filament/**` da entrada do painel torna visível o uso de `shadow-*` do Filament e o Tailwind passa a emitir seus valores padrão de `--shadow-sm/md/lg` também no bundle público. Verificado por diff do `app.css` construído com e sem a entrada do tema. Nada no site público usa utilitário de sombra hoje, então a renderização não muda e os baselines visuais seguem válidos. Mas deixar valores reais de sombra definidos no CSS que vai para produção permitiria que um `shadow-sm` futuro em elemento público violasse a Tonal Layer Rule do DESIGN.md em silêncio — e o HeritageEditorialTokensTest só inspeciona arquivos de origem, então não pegaria. Os tokens de sombra passam a ser fixados em transparente no `@theme` do `app.css`, com teste que falha se algum deixar de ser. A regra continua verdadeira no artefato que realmente ship. Co-Authored-By: Claude noreply@anthropic.com AI-Assisted: yes AI-Tool: claude-code --------- Co-authored-by: manoel.neto <manoel.neto@creditas.com>
233 lines
8.9 KiB
PHP
233 lines
8.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Providers\Filament;
|
|
|
|
use App\Filament\Pages\Auth\RequestPasswordReset;
|
|
use Filament\FontProviders\LocalFontProvider;
|
|
use Filament\Http\Middleware\Authenticate;
|
|
use Filament\Http\Middleware\AuthenticateSession;
|
|
use Filament\Http\Middleware\DisableBladeIconComponents;
|
|
use Filament\Http\Middleware\DispatchServingFilamentEvent;
|
|
use Filament\Pages\Dashboard;
|
|
use Filament\Panel;
|
|
use Filament\PanelProvider;
|
|
use Filament\View\PanelsRenderHook;
|
|
use Filament\Widgets\AccountWidget;
|
|
use Filament\Widgets\FilamentInfoWidget;
|
|
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
|
|
use Illuminate\Cookie\Middleware\EncryptCookies;
|
|
use Illuminate\Foundation\Http\Middleware\PreventRequestForgery;
|
|
use Illuminate\Routing\Middleware\SubstituteBindings;
|
|
use Illuminate\Session\Middleware\StartSession;
|
|
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
|
|
|
class AdminPanelProvider extends PanelProvider
|
|
{
|
|
/**
|
|
* Heritage Editorial — Oliva Herança primary ramp.
|
|
*
|
|
* Filament needs an explicit 11-stop shade array to preserve exact brand
|
|
* hexes (a bare string/`Color::hex()` runs the value through
|
|
* `Color::generatePalette()`, which discards its lightness and maps a
|
|
* fixed per-shade lightness/chroma table instead — see MAN-118 survey).
|
|
* Shade 600 pins `#556B2F` (Oliva Herança) and shade 700 pins `#3E5219`
|
|
* (Oliva Profundo). `Filament\Support\View\Components\ColorMaps\
|
|
* ButtonComponentColorMap` was used to verify empirically (not assumed)
|
|
* that a solid primary button resolves to `bg: 600, hover:bg: 500, text:
|
|
* 50` at 5.42:1 / 4.57:1 contrast (WCAG AA). Shade 50 is a pale olive
|
|
* tint rather than pure white deliberately: `BadgeComponent`/
|
|
* `badge.css` apply `bg-color-50` directly as a badge's background
|
|
* (independent of the button map above), and a pure-white 50 would
|
|
* render a "primary" badge as an all-but-invisible pill against the
|
|
* Papel Marfim (`#FBF9F4`) panel body.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
private function primaryColor(): array
|
|
{
|
|
return [
|
|
50 => '#F3F5EC',
|
|
100 => '#E7EBDA',
|
|
200 => '#D4DCBE',
|
|
300 => '#8B9D77', // Sálvia Silenciosa
|
|
400 => '#6E8354',
|
|
500 => '#61763A',
|
|
600 => '#556B2F', // Oliva Herança
|
|
700 => '#3E5219', // Oliva Profundo
|
|
800 => '#2E3D13',
|
|
900 => '#1F290D',
|
|
950 => '#141B08',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Heritage Editorial — paper/ink neutral ramp, replacing Filament's
|
|
* stock Zinc gray so panel chrome (sidebar, topbar, body background,
|
|
* borders) matches the public site's paper tones instead of true gray.
|
|
* Anchored at the exact tokens where DESIGN.md defines them (50/100/200
|
|
* = Papel Marfim/Profundo/Arquivo, 300 = Linha Botânica, 500 = Tinta
|
|
* Suave, 900 = Tinta Oliva); the remaining stops are interpolated to
|
|
* keep the ramp monotonic.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
private function grayColor(): array
|
|
{
|
|
return [
|
|
50 => '#FBF9F4', // Papel Marfim
|
|
100 => '#F0EEE9', // Papel Profundo
|
|
200 => '#E4E2DD', // Papel Arquivo
|
|
300 => '#C5C8B8', // Linha Botânica
|
|
400 => '#919486',
|
|
500 => '#5D6155', // Tinta Suave
|
|
600 => '#43453D',
|
|
700 => '#32342E',
|
|
800 => '#252622',
|
|
900 => '#1B1C19', // Tinta Oliva
|
|
950 => '#121210',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Semantic ramps built the same way as the primary/gray ramps above:
|
|
* an explicit 11-stop array (never `Color::hex()`) anchored so the
|
|
* existing `--amare-color-{success,warning,error}` hex lands exactly on
|
|
* shade 600 — verified against `ButtonComponentColorMap` to resolve to
|
|
* `bg: 600` with white text at WCAG AA contrast, same as primary.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
private function dangerColor(): array
|
|
{
|
|
return [
|
|
50 => '#F7EDED',
|
|
100 => '#EBD1D1',
|
|
200 => '#D8A8A8',
|
|
300 => '#C88484',
|
|
400 => '#B85F5F',
|
|
500 => '#A73B3B',
|
|
600 => '#991B1B',
|
|
700 => '#7D1616',
|
|
800 => '#651212',
|
|
900 => '#4C0E0E',
|
|
950 => '#3A0A0A',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function warningColor(): array
|
|
{
|
|
return [
|
|
50 => '#F6F0EC',
|
|
100 => '#E9D9CF',
|
|
200 => '#D6B6A3',
|
|
300 => '#C4987D',
|
|
400 => '#B37956',
|
|
500 => '#A15B30',
|
|
600 => '#92400E',
|
|
700 => '#78340B',
|
|
800 => '#602A09',
|
|
900 => '#492007',
|
|
950 => '#371805',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<int, string>
|
|
*/
|
|
private function successColor(): array
|
|
{
|
|
return [
|
|
50 => '#ECF3EF',
|
|
100 => '#D0E0D6',
|
|
200 => '#A6C4B2',
|
|
300 => '#81AC91',
|
|
400 => '#5C9371',
|
|
500 => '#377B50',
|
|
600 => '#166534',
|
|
700 => '#12532B',
|
|
800 => '#0F4322',
|
|
900 => '#0B321A',
|
|
950 => '#082614',
|
|
];
|
|
}
|
|
|
|
public function panel(Panel $panel): Panel
|
|
{
|
|
return $panel
|
|
->default()
|
|
->id('admin')
|
|
->path('admin')
|
|
->login()
|
|
->passwordReset(requestAction: RequestPasswordReset::class)
|
|
->brandName('Amare Assessoria')
|
|
->brandLogo(fn (): string => asset('brand/lockup-on-light.webp'))
|
|
->brandLogoHeight('2rem')
|
|
->colors([
|
|
'primary' => $this->primaryColor(),
|
|
'gray' => $this->grayColor(),
|
|
'danger' => $this->dangerColor(),
|
|
'warning' => $this->warningColor(),
|
|
'success' => $this->successColor(),
|
|
])
|
|
// Heritage Editorial is a single paper palette by design (see
|
|
// DESIGN.md) — no dark-mode variant exists, so the switcher is
|
|
// removed rather than left pointing at an unstyled dark theme.
|
|
->darkMode(false)
|
|
// Self-hosted EB Garamond, same family as the public site.
|
|
// LocalFontProvider is pinned explicitly: HasFont::getFontProvider()
|
|
// otherwise defaults a custom family to BunnyFontProvider, which
|
|
// would emit a live request to fonts.bunny.net from the panel.
|
|
// LocalFontProvider renders no <link>/@font-face itself (no $url
|
|
// given), so the actual face comes from the render hook below,
|
|
// reusing the public site's own <x-fonts /> component/manifest.
|
|
//
|
|
// The monospace and serif faces are set too, not just the base
|
|
// (sans) family: Filament exposes them via the separate
|
|
// ->monoFont()/->serifFont() calls below, and the `KeyValue`
|
|
// field (used in ManageSiteSettings) renders in the monospace
|
|
// face directly — left unset, that field would silently fall
|
|
// back to a system monospace stack instead of EB Garamond,
|
|
// breaking Heritage Editorial's single-voice typography.
|
|
->font('EB Garamond', provider: LocalFontProvider::class)
|
|
->monoFont('EB Garamond', provider: LocalFontProvider::class)
|
|
->serifFont('EB Garamond', provider: LocalFontProvider::class)
|
|
->renderHook(
|
|
PanelsRenderHook::HEAD_END,
|
|
fn (): string => view('components.fonts')->render(),
|
|
)
|
|
// Compiled Filament theme entry — sharp corners, flat surfaces
|
|
// (see resources/css/filament/admin/theme.css for the full
|
|
// rationale and the two vendored-CSS exceptions it can't reach).
|
|
->viteTheme('resources/css/filament/admin/theme.css')
|
|
->discoverResources(in: app_path('Filament/Resources'), for: 'App\Filament\Resources')
|
|
->discoverPages(in: app_path('Filament/Pages'), for: 'App\Filament\Pages')
|
|
->pages([
|
|
Dashboard::class,
|
|
])
|
|
->discoverWidgets(in: app_path('Filament/Widgets'), for: 'App\Filament\Widgets')
|
|
->widgets([
|
|
AccountWidget::class,
|
|
FilamentInfoWidget::class,
|
|
])
|
|
->middleware([
|
|
EncryptCookies::class,
|
|
AddQueuedCookiesToResponse::class,
|
|
StartSession::class,
|
|
AuthenticateSession::class,
|
|
ShareErrorsFromSession::class,
|
|
PreventRequestForgery::class,
|
|
SubstituteBindings::class,
|
|
DisableBladeIconComponents::class,
|
|
DispatchServingFilamentEvent::class,
|
|
])
|
|
->authMiddleware([
|
|
Authenticate::class,
|
|
]);
|
|
}
|
|
}
|