feat: overhaul design system with dark mode, updated typography, and cyan-based accent palette

This commit is contained in:
2026-04-29 08:45:32 +01:00
parent 94288d13e9
commit 7ba6f806c8
6 changed files with 105 additions and 77 deletions

View File

@@ -15,38 +15,39 @@ export const Header = () => {
}
return (
<header className="sticky top-0 z-30 bg-white/70 backdrop-blur-lg border-b border-slate-200/50 shadow-sm shadow-slate-200/20">
<div className="mx-auto flex h-16 sm:h-20 max-w-6xl items-center justify-between px-3 sm:px-6">
<header className="sticky top-0 z-40 bg-zinc-950/80 backdrop-blur-xl border-b border-white/5 shadow-2xl shadow-black/20">
<div className="mx-auto flex h-16 sm:h-20 max-w-6xl items-center justify-between px-4 sm:px-6">
<Link
to="/"
className="text-2xl font-black tracking-tighter text-slate-900 group flex items-center gap-2"
className="text-xl sm:text-2xl font-black tracking-tighter text-white group flex items-center gap-3"
onClick={() => setMobileMenuOpen(false)}
>
<div className="w-8 h-8 rounded-lg bg-indigo-600 flex items-center justify-center text-white shadow-lg shadow-indigo-200">
<User size={18} fill="currentColor" />
<div className="w-9 h-9 rounded-xl bg-cyan-500 flex items-center justify-center text-white shadow-xl shadow-cyan-500/20 transition-transform duration-500 group-hover:rotate-6">
<User size={20} fill="currentColor" />
</div>
<span className="group-hover:text-indigo-600 transition-colors uppercase italic font-black">Smart Agenda</span>
<span className="uppercase italic font-black tracking-widest hidden sm:block">Smart Agenda</span>
<span className="uppercase italic font-black tracking-widest sm:hidden">Smart</span>
</Link>
{/* Desktop Navigation */}
<nav className="hidden md:flex items-center gap-8">
<nav className="hidden md:flex items-center gap-10">
{user?.role !== 'barbearia' && (
<>
<Link
to="/explorar"
className="text-sm font-bold text-slate-600 hover:text-slate-900 transition-all flex items-center gap-2"
className="text-xs font-black uppercase tracking-[0.2em] text-zinc-400 hover:text-white transition-all flex items-center gap-2"
>
<MapPin size={16} className="text-indigo-500" />
<span>Descobrir Barbearias</span>
<MapPin size={14} className="text-cyan-500" />
<span>Explorar</span>
</Link>
<Link
to="/carrinho"
className="relative text-slate-700 hover:text-slate-900 transition-all p-2 rounded-xl hover:bg-slate-50"
className="relative text-zinc-400 hover:text-white transition-all p-2 rounded-xl hover:bg-white/5"
>
<ShoppingCart size={20} className="text-slate-700" />
<ShoppingCart size={20} />
{cart.length > 0 && (
<span className="absolute -right-1 -top-1 rounded-full bg-slate-900 px-1.5 py-0.5 text-[10px] font-black text-indigo-400 shadow-md min-w-[18px] text-center">
<span className="absolute -right-1 -top-1 rounded-full bg-cyan-500 px-1.5 py-0.5 text-[9px] font-black text-white shadow-lg">
{cart.length}
</span>
)}
@@ -54,24 +55,24 @@ export const Header = () => {
</>
)}
<div className="h-6 w-px bg-slate-200 mx-1" />
<div className="h-6 w-px bg-white/5 mx-2" />
{user ? (
<div className="flex items-center gap-4">
<div className="flex items-center gap-5">
<button
onClick={() => navigate(user.role === 'barbearia' ? '/painel' : '/perfil')}
className="flex items-center gap-3 bg-slate-50 hover:bg-slate-100 border border-slate-200/60 pl-3 pr-4 py-1.5 rounded-full transition-all group"
className="flex items-center gap-3 bg-white/5 hover:bg-white/10 border border-white/10 pl-3 pr-5 py-2 rounded-2xl transition-all group"
type="button"
>
<div className="w-7 h-7 rounded-full bg-indigo-600 flex items-center justify-center text-white shadow-sm">
<div className="w-7 h-7 rounded-lg bg-cyan-500 flex items-center justify-center text-white shadow-lg shadow-cyan-500/10">
<User size={14} fill="currentColor" />
</div>
<span className="text-sm font-bold text-slate-700 group-hover:text-slate-900 max-w-[120px] truncate">{user.name}</span>
<span className="text-xs font-black uppercase tracking-widest text-zinc-300 group-hover:text-white max-w-[120px] truncate">{user.name}</span>
</button>
<button
onClick={handleLogout}
className="p-2 text-slate-400 hover:text-rose-600 hover:bg-rose-50 rounded-xl transition-all"
className="p-2 text-zinc-500 hover:text-rose-500 hover:bg-rose-500/10 rounded-xl transition-all"
title="Sair"
type="button"
>
@@ -79,18 +80,18 @@ export const Header = () => {
</button>
</div>
) : (
<div className="flex items-center gap-3">
<div className="flex items-center gap-4">
<Link
to="/login"
className="text-sm font-bold text-slate-600 hover:text-slate-900 px-4 py-2 transition-colors"
className="text-xs font-black uppercase tracking-[0.2em] text-zinc-400 hover:text-white px-4 py-2 transition-colors"
>
Login
Entrar
</Link>
<Link
to="/registo"
className="inline-flex items-center justify-center rounded-xl bg-indigo-600 px-5 py-2 text-sm font-bold text-white shadow-lg shadow-indigo-100 hover:bg-indigo-700 transition-all font-black uppercase italic tracking-widest"
className="inline-flex items-center justify-center rounded-2xl bg-white text-zinc-950 px-6 py-2.5 text-xs font-black uppercase italic tracking-widest shadow-xl hover:bg-cyan-500 hover:text-white transition-all"
>
Criar Conta
Começar Agora
</Link>
</div>
)}
@@ -99,7 +100,7 @@ export const Header = () => {
{/* Mobile Menu Button */}
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2.5 bg-slate-50 border border-slate-200 rounded-xl text-slate-900 transition-all"
className="md:hidden p-3 bg-white/5 border border-white/10 rounded-xl text-white transition-all active:scale-95"
type="button"
>
{mobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
@@ -108,28 +109,28 @@ export const Header = () => {
{/* Mobile Menu */}
{mobileMenuOpen && (
<div className="md:hidden border-t border-slate-100 bg-white shadow-2xl animate-in slide-in-from-top-4 duration-300">
<nav className="px-6 py-6 space-y-4">
<div className="md:hidden border-t border-white/5 bg-zinc-950 shadow-[0_20px_50px_rgba(0,0,0,0.5)] animate-in slide-in-from-top-4 duration-300">
<nav className="px-6 py-8 space-y-6">
{user?.role !== 'barbearia' && (
<>
<Link
to="/explorar"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-3 text-base font-black text-slate-700 hover:text-indigo-600 p-3 rounded-2xl bg-slate-50 transition-all uppercase italic"
className="flex items-center gap-4 text-xs font-black uppercase tracking-[0.2em] text-zinc-300 hover:text-cyan-500 p-4 rounded-2xl bg-white/5 transition-all"
>
<MapPin size={18} className="text-indigo-600" />
Descobrir Barbearias
<MapPin size={18} className="text-cyan-500" />
Descobrir Espaços
</Link>
<Link
to="/carrinho"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center gap-3 text-base font-black text-slate-700 hover:text-indigo-600 p-3 rounded-2xl bg-slate-50 transition-all uppercase italic"
className="flex items-center gap-4 text-xs font-black uppercase tracking-[0.2em] text-zinc-300 hover:text-cyan-500 p-4 rounded-2xl bg-white/5 transition-all"
>
<ShoppingCart size={18} className="text-indigo-600" />
Meu Carrinho
<ShoppingCart size={18} className="text-cyan-500" />
Carrinho
{cart.length > 0 && (
<span className="ml-auto rounded-full bg-slate-900 px-2 py-0.5 text-[10px] font-black text-indigo-400">
<span className="ml-auto rounded-full bg-cyan-500 px-2 py-0.5 text-[9px] font-black text-white">
{cart.length}
</span>
)}
@@ -137,46 +138,46 @@ export const Header = () => {
</>
)}
<div className="h-px bg-slate-100 w-full" />
<div className="h-px bg-white/5 w-full" />
{user ? (
<>
<div className="space-y-3">
<button
onClick={() => {
navigate(user.role === 'barbearia' ? '/painel' : '/perfil')
setMobileMenuOpen(false)
}}
className="w-full flex items-center gap-3 text-base font-bold text-slate-700 p-3 rounded-2xl hover:bg-slate-50 transition-all text-left"
className="w-full flex items-center gap-4 text-xs font-black uppercase tracking-[0.2em] text-zinc-300 p-4 rounded-2xl hover:bg-white/5 transition-all text-left"
type="button"
>
<User size={18} className="text-slate-400" />
<User size={18} className="text-cyan-500" />
{user.name}
</button>
<button
onClick={handleLogout}
className="w-full flex items-center gap-3 text-base font-bold text-rose-600 p-3 rounded-2xl hover:bg-rose-50 transition-all text-left"
className="w-full flex items-center gap-4 text-xs font-black uppercase tracking-[0.2em] text-rose-500 p-4 rounded-2xl hover:bg-rose-500/10 transition-all text-left"
type="button"
>
<LogOut size={18} />
Sair da Conta
</button>
</>
</div>
) : (
<div className="grid grid-cols-2 gap-3">
<div className="flex flex-col gap-4">
<Link
to="/login"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center justify-center rounded-2xl border border-slate-200 bg-white py-3 text-sm font-bold text-slate-700"
className="flex items-center justify-center rounded-2xl border border-white/10 bg-white/5 py-4 text-xs font-black uppercase tracking-widest text-white"
>
Entrar
Login
</Link>
<Link
to="/registo"
onClick={() => setMobileMenuOpen(false)}
className="flex items-center justify-center rounded-2xl bg-slate-900 py-3 text-sm font-bold text-indigo-400 shadow-lg shadow-slate-200 font-black uppercase italic tracking-widest"
className="flex items-center justify-center rounded-2xl bg-white py-4 text-xs font-black text-zinc-950 uppercase italic tracking-widest shadow-xl"
>
Criar Conta
Criar Conta Gratis
</Link>
</div>
)}

View File

@@ -10,10 +10,10 @@ type Props = React.ButtonHTMLAttributes<HTMLButtonElement> & {
export const Button = ({ className, variant = 'solid', size = 'md', asChild, ...props }: Props) => {
const base = 'inline-flex items-center justify-center font-semibold transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed';
const variants = {
solid: 'bg-gradient-to-r from-indigo-500 to-blue-600 text-white hover:from-indigo-600 hover:to-blue-700 shadow-md hover:shadow-lg focus:ring-indigo-500/50 active:scale-[0.98]',
outline: 'border-2 border-indigo-500 text-indigo-700 bg-white hover:bg-indigo-50 hover:border-indigo-600 focus:ring-indigo-500/50 active:scale-[0.98]',
ghost: 'text-indigo-700 hover:bg-indigo-50 focus:ring-indigo-500/50 active:scale-[0.98]',
danger: 'bg-gradient-to-r from-rose-500 to-rose-600 text-white hover:from-rose-600 hover:to-rose-700 shadow-md hover:shadow-lg focus:ring-rose-500/50 active:scale-[0.98]',
solid: 'bg-gradient-to-r from-cyan-500 to-sky-600 text-white hover:from-cyan-600 hover:to-sky-700 shadow-xl shadow-cyan-500/10 focus:ring-cyan-500/50 active:scale-[0.98]',
outline: 'border border-white/10 text-white bg-white/5 hover:bg-white/10 hover:border-white/20 focus:ring-white/20 active:scale-[0.98]',
ghost: 'text-zinc-400 hover:text-white hover:bg-white/5 focus:ring-white/20 active:scale-[0.98]',
danger: 'bg-gradient-to-r from-rose-500 to-red-600 text-white hover:from-rose-600 hover:to-red-700 shadow-xl shadow-rose-500/10 focus:ring-rose-500/50 active:scale-[0.98]',
};
const sizes = {
sm: 'text-xs px-3 py-1.5 rounded-lg',

View File

@@ -3,8 +3,8 @@ import { cn } from '../../lib/cn';
export const Card = ({ children, className = '', hover = false }: { children: React.ReactNode; className?: string; hover?: boolean }) => (
<div
className={cn(
'bg-white rounded-xl shadow-sm border border-slate-200/60 transition-all duration-200',
hover && 'hover:shadow-md hover:border-indigo-200/80',
'bg-zinc-900/80 backdrop-blur-sm rounded-2xl shadow-2xl border border-white/5 transition-all duration-300',
hover && 'hover:border-cyan-500/30 hover:bg-zinc-800/80',
className
)}
>

View File

@@ -3,21 +3,24 @@ import { cn } from '../../lib/cn';
type Tab = { id: string; label: string; badge?: number };
export const Tabs = ({ tabs, active, onChange, className }: { tabs: Tab[]; active: string; onChange: (id: string) => void; className?: string }) => (
<div className={cn("flex gap-1 p-1 bg-slate-100 rounded-xl overflow-x-auto scrollbar-hide w-full", className)}>
<div className={cn("flex gap-1.5 p-1 bg-zinc-950/50 backdrop-blur-md rounded-xl border border-white/5 overflow-x-auto scrollbar-hide w-full", className)}>
{tabs.map((t) => (
<button
key={t.id}
onClick={() => onChange(t.id)}
className={cn(
"px-3 sm:px-6 py-1.5 sm:py-2.5 text-[10px] sm:text-sm font-black uppercase tracking-tight sm:tracking-widest transition-all rounded-lg whitespace-nowrap shrink-0",
"px-4 sm:px-6 py-2 sm:py-3 text-[10px] sm:text-xs font-black uppercase tracking-widest transition-all rounded-lg whitespace-nowrap shrink-0",
active === t.id
? "bg-slate-900 text-indigo-400 shadow-lg"
: "text-slate-500 hover:text-slate-900 hover:bg-white/50"
? "bg-cyan-500 text-white shadow-xl shadow-cyan-500/20"
: "text-zinc-500 hover:text-zinc-200 hover:bg-white/5"
)}
>
{t.label}
{t.badge && (
<span className="ml-1 sm:ml-2 inline-flex items-center justify-center w-3.5 h-3.5 sm:w-5 sm:h-5 text-[7px] sm:text-[10px] font-black text-white bg-slate-900 rounded-full border border-indigo-500/50">
<span className={cn(
"ml-2 inline-flex items-center justify-center w-4 h-4 sm:w-5 sm:h-5 text-[8px] sm:text-[10px] font-black rounded-full border transition-colors",
active === t.id ? "bg-white text-cyan-600 border-transparent" : "bg-zinc-800 text-zinc-400 border-white/5"
)}>
{t.badge}
</span>
)}

View File

@@ -1,20 +1,22 @@
@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap');
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--brand-primary: #2563eb; /* Blue 600 */
--brand-primary-light: #93c5fd;
--brand-primary-dark: #1e40af;
--obsidian: #18181b; /* Zinc 900 */
--obsidian-light: #27272a;
--slate-950: #09090b;
color-scheme: light;
--brand-primary: #0ea5e9; /* Cyan 500 */
--brand-primary-light: #7dd3fc;
--brand-primary-dark: #0369a1;
--obsidian: #09090b; /* Zinc 950 */
--obsidian-light: #18181b; /* Zinc 900 */
--slate-950: #020617;
color-scheme: dark;
}
* {
@apply border-zinc-200;
@apply border-white/5;
box-sizing: border-box;
}
@@ -23,33 +25,38 @@
width: 100%;
max-width: 100vw;
position: relative;
background-color: var(--obsidian);
}
body {
@apply bg-[#fafafa] text-zinc-900 font-sans antialiased;
@apply text-zinc-400 font-sans antialiased;
touch-action: pan-y;
background-image:
radial-gradient(at 0% 0%, rgba(37, 99, 235, 0.03) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(24, 24, 27, 0.03) 0px, transparent 50%);
radial-gradient(at 0% 0%, rgba(14, 165, 233, 0.05) 0px, transparent 50%),
radial-gradient(at 100% 0%, rgba(24, 24, 27, 0.05) 0px, transparent 50%);
font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11';
min-height: 100vh;
}
a {
@apply text-inherit no-underline transition-colors;
@apply text-inherit no-underline transition-all;
}
h1, h2, h3, h4, h5, h6 {
@apply text-white tracking-tight;
}
/* Scrollbar styling */
::-webkit-scrollbar {
@apply w-2 h-2;
@apply w-1.5 h-1.5;
}
::-webkit-scrollbar-track {
@apply bg-zinc-100;
@apply bg-transparent;
}
::-webkit-scrollbar-thumb {
@apply bg-blue-200 rounded-full hover:bg-blue-300;
@apply bg-zinc-800 rounded-full hover:bg-zinc-700;
}
}
@@ -59,7 +66,11 @@
}
.glass-card {
@apply bg-white/80 backdrop-blur-md border border-white/20 shadow-xl shadow-zinc-200/50;
@apply bg-zinc-900/50 backdrop-blur-xl border border-white/10 shadow-2xl shadow-black/50;
}
.bento-card {
@apply bg-zinc-900 border border-white/5 shadow-inner hover:border-white/20 transition-all duration-300;
}
/* Hide scrollbar but keep scroll functionality */
@@ -72,10 +83,10 @@
}
.indigo-gradient {
@apply bg-gradient-to-br from-blue-400 via-blue-500 to-blue-600;
@apply bg-gradient-to-br from-cyan-400 via-cyan-500 to-cyan-600;
}
.obsidian-gradient {
@apply bg-gradient-to-br from-zinc-800 via-zinc-900 to-zinc-950;
@apply bg-gradient-to-br from-zinc-900 via-zinc-950 to-black;
}
}