style(ui): polimento visual — tema, cards, dark mode e landing page

Raio de borda global 0.625rem → 0.7rem; ajustes finos em --card e --border.
DotPattern removido do layout, tela de auth e landing page.
Account-card redesenhado (cores de saldo, tooltip de flags de exclusão).
Budget-card, card-item, calendário (day-cell, event-modal) com layout revisado.
Auth-card-shell simplificado (sem glassmorphism/blob). Landing page com
mainFeatures + extraFeatures em grid único e dark mode nos botões de CTA.
Imagens de preview da landing atualizadas. CSS --data-7..10 removidas.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Felipe Coutinho
2026-04-20 17:52:17 +00:00
parent 5d84ae928a
commit 6d81ff8b53
67 changed files with 612 additions and 737 deletions

View File

@@ -59,7 +59,7 @@ export function LogoPickerTrigger({
className="object-contain p-0.5"
/>
) : (
<span className="text-[10px] text-muted-foreground">Logo</span>
<span className="text-xs text-muted-foreground">Logo</span>
)}
</span>
@@ -172,7 +172,7 @@ export function LogoPickerDialog({
/>
</span>
</span>
<span className="line-clamp-1 text-[10px] leading-tight text-muted-foreground">
<span className="line-clamp-1 text-xs leading-tight text-muted-foreground">
{logoLabel}
</span>
</button>

View File

@@ -8,6 +8,10 @@ interface LogoProps {
invertTextOnDark?: boolean;
/** Exibe o ícone na cor original, sem filtro preto. Apenas nos variants "full" e "compact" */
colorIcon?: boolean;
/** Classes extras aplicadas na imagem do ícone */
iconClassName?: string;
/** Classes extras aplicadas na imagem do texto */
textClassName?: string;
}
const iconFilterClass = "brightness-0 saturate-0";
@@ -17,6 +21,8 @@ export function Logo({
className,
invertTextOnDark = true,
colorIcon = false,
iconClassName,
textClassName,
}: LogoProps) {
if (variant === "compact") {
return (
@@ -27,7 +33,11 @@ export function Logo({
alt="OpenMonetis"
fill
sizes="32px"
className={cn("object-contain", !colorIcon && iconFilterClass)}
className={cn(
"object-contain",
!colorIcon && iconFilterClass,
iconClassName,
)}
priority
/>
</div>
@@ -37,7 +47,11 @@ export function Logo({
alt="OpenMonetis"
fill
sizes="110px"
className={cn("object-contain", invertTextOnDark && "dark:invert")}
className={cn(
"object-contain",
invertTextOnDark && "dark:invert",
textClassName,
)}
priority
/>
</div>

View File

@@ -37,7 +37,7 @@ export default function MonthNavigation() {
};
return (
<Card className="sticky top-16 z-10 flex w-full flex-row p-4 backdrop-blur-xs supports-backdrop-filter:bg-card/80 ">
<Card className="sticky top-18 z-10 flex w-full flex-row p-4 backdrop-blur-md supports-backdrop-filter:bg-card/60">
<div className="flex items-center gap-1">
<NavigationButton
direction="left"

View File

@@ -8,12 +8,12 @@ export default function PageDescription({
icon?: React.ReactNode;
}) {
return (
<div>
<h1 className="text-2xl font-semibold flex items-center gap-1 ">
<div className="space-y-2">
<h1 className="text-2xl font-semibold flex items-center gap-1">
<span className="text-primary">{icon}</span>
{title}
</h1>
<h2 className="text-sm max-w-2xl text-muted-foreground leading-relaxed mt-1.5">
<h2 className="text-sm max-w-2xl text-muted-foreground leading-relaxed">
{subtitle}
</h2>
</div>

View File

@@ -72,7 +72,7 @@ export function TransactionTypeBadge({
variant="outline"
data-kind={normalizedKind ?? "custom"}
className={cn(
"h-6 gap-1.5 rounded-full border-transparent px-2 py-0 text-xs font-medium shadow-none",
"h-6 gap-1 border-none rounded-md px-2 py-0 text-xs shadow-none",
config?.className ??
"bg-muted/30 text-muted-foreground dark:bg-muted/20",
className,

View File

@@ -20,7 +20,7 @@ const buttonVariants = cva(
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
navbar:
"bg-transparent text-black/75 shadow-none hover:bg-black/10 hover:text-black focus-visible:ring-black/20",
"bg-transparent text-black/75 shadow-none hover:bg-black/10 hover:text-black focus-visible:ring-black/20 dark:text-white/75 dark:hover:bg-white/10 dark:hover:text-white dark:focus-visible:ring-white/20",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",

View File

@@ -90,7 +90,7 @@ function Calendar({
table: "w-full border-collapse",
weekdays: cn("flex", defaultClassNames.weekdays),
weekday: cn(
"text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
"text-muted-foreground rounded-md flex-1 font-normal text-xs select-none",
defaultClassNames.weekday,
),
week: cn("flex w-full mt-2", defaultClassNames.week),
@@ -99,7 +99,7 @@ function Calendar({
defaultClassNames.week_number_header,
),
week_number: cn(
"text-[0.8rem] select-none text-muted-foreground",
"text-xs select-none text-muted-foreground",
defaultClassNames.week_number,
),
day: cn(

View File

@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-4 border py-6 rounded-lg hover:border-primary/40",
"bg-card text-card-foreground flex flex-col gap-4 border border-border/70 dark:border-border/40 py-6 rounded-lg hover:border-primary/60 transition-colors duration-200",
className,
)}
{...props}

View File

@@ -29,6 +29,8 @@ export type CalendarEvent =
status: string;
logo: string | null;
totalDue: number | null;
isPaid: boolean;
paymentDate: string | null;
};
};

View File

@@ -3,7 +3,7 @@
* Os valores são CSS variables definidas em globals.css,
* com variantes light/dark — sem hardcode de hex fora do tema.
*/
const DATA_PALETTE_SIZE = 10;
const DATA_PALETTE_SIZE = 6;
/** Array de CSS variables da paleta de dados — usado em gráficos e charts. */
export const CATEGORY_COLORS = Array.from(
@@ -20,19 +20,18 @@ function hashNameToIndex(name: string): number {
}
/**
* Retorna a CSS variable de cor para um nome (determinístico via hash).
* Cor do ícone — sempre primary para consistência visual.
*/
export function getCategoryColorFromName(name: string): string {
const n = hashNameToIndex(name) + 1;
return `var(--data-${n})`;
export function getCategoryColorFromName(_name: string): string {
return "var(--foreground)";
}
/**
* Retorna o background com transparência usando color-mix.
* Background distinto por nome (hash), com 20% de opacidade.
*/
export function getCategoryBgColorFromName(name: string): string {
const n = hashNameToIndex(name) + 1;
return `color-mix(in oklch, var(--data-${n}) 14%, transparent)`;
return `color-mix(in oklch, var(--data-${n}) 20%, transparent)`;
}
/**
@@ -49,20 +48,3 @@ export function buildInitials(name: string): string {
const b = parts[1]?.[0] ?? "";
return `${a}${b}`.toUpperCase() || "?";
}
// --- compatibilidade retroativa (para não quebrar callers durante migração) ---
/** @deprecated Use getCategoryColorFromName */
export function getCategoryColor(index: number): string {
return `var(--data-${(index % DATA_PALETTE_SIZE) + 1})`;
}
/** @deprecated Use getCategoryBgColorFromName */
export function getCategoryBgColor(index: number): string {
return `color-mix(in oklch, var(--data-${(index % DATA_PALETTE_SIZE) + 1}) 14%, transparent)`;
}
/** @deprecated Use buildInitials */
export function buildCategoryInitials(value: string): string {
return buildInitials(value);
}

View File

@@ -39,7 +39,7 @@ const MONTH_NAMES = [
"dezembro",
] as const;
export const OPENMONETIS_TIME_ZONE = "America/Sao_Paulo";
const OPENMONETIS_TIME_ZONE = "America/Sao_Paulo";
type DateOnlyParts = {
year: number;
@@ -200,7 +200,7 @@ export function getTodayDateString(date: Date = new Date()): string {
/**
* Gets a date string in YYYY-MM-DD format for a specific timezone
*/
export function getDateStringInTimeZone(
function getDateStringInTimeZone(
timeZone: string,
date: Date = new Date(),
): string {
@@ -215,14 +215,6 @@ export function getBusinessDateString(date: Date = new Date()): string {
return getDateStringInTimeZone(OPENMONETIS_TIME_ZONE, date);
}
/**
* Gets today's date as Date object
* @returns Date object for today
*/
export function getTodayDate(date: Date = new Date()): Date {
return parseLocalDateString(getTodayDateString(date));
}
/**
* Gets today's date as Date object using the app business timezone
*/
@@ -397,19 +389,6 @@ export function formatDateOnlyLabel(
return prefix ? `${prefix} ${formatted}` : formatted;
}
export function formatDateTimeLabel(
value: string | Date | null | undefined,
prefix?: string,
options?: Intl.DateTimeFormatOptions,
): string | null {
const formatted = formatDateTime(value, options);
if (!formatted) {
return null;
}
return prefix ? `${prefix} ${formatted}` : formatted;
}
export function compareDateOnly(
left: string | Date | null | undefined,
right: string | Date | null | undefined,
@@ -505,19 +484,7 @@ export function friendlyDate(date: Date): string {
// TIME-BASED UTILITIES
// ============================================================================
/**
* Gets appropriate greeting based on time of day
* @param date - Date to get greeting for (defaults to now)
* @returns "Bom dia", "Boa tarde", or "Boa noite"
*/
export function getGreeting(date: Date = new Date()): string {
const hour = date.getHours();
if (hour >= 5 && hour < 12) return "Bom dia";
if (hour >= 12 && hour < 18) return "Boa tarde";
return "Boa noite";
}
export function getGreetingInTimeZone(
function getGreetingInTimeZone(
timeZone: string,
date: Date = new Date(),
): string {
@@ -531,7 +498,7 @@ export function getBusinessGreeting(date: Date = new Date()): string {
return getGreetingInTimeZone(OPENMONETIS_TIME_ZONE, date);
}
export function formatCurrentDateInTimeZone(
function formatCurrentDateInTimeZone(
timeZone: string,
date: Date = new Date(),
): string {
@@ -550,6 +517,3 @@ export function formatCurrentDateInTimeZone(
export function formatBusinessCurrentDate(date: Date = new Date()): string {
return formatCurrentDateInTimeZone(OPENMONETIS_TIME_ZONE, date);
}
// Re-export MONTH_NAMES for convenience
export { MONTH_NAMES };