style: improve mobile responsiveness across layout and UI components

This commit is contained in:
2026-04-27 14:38:51 +01:00
parent 0fb8ffb12a
commit 153655da8c
12 changed files with 156 additions and 142 deletions

View File

@@ -12,33 +12,33 @@ export const ProductList = ({
products: Product[];
onAdd?: (id: string) => void;
}) => (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<div className="grid grid-cols-2 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3 sm:gap-6">
{products.map((p) => {
const lowStock = p.stock <= 3;
return (
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-[2rem] group hover:shadow-2xl transition-all duration-300 flex flex-col">
<div className="aspect-square bg-slate-50 flex items-center justify-center p-8 group-hover:bg-indigo-50 transition-colors">
<Card key={p.id} className="relative overflow-hidden border-none glass-card rounded-2xl sm:rounded-[2rem] group hover:shadow-2xl transition-all duration-300 flex flex-col">
<div className="aspect-square bg-slate-50 flex items-center justify-center p-4 sm:p-8 group-hover:bg-indigo-50 transition-colors">
<Package size={48} className="text-slate-200 group-hover:text-indigo-200 transition-all group-hover:scale-110 duration-500" />
{lowStock && (
<div className="absolute top-4 left-4">
<Badge color="indigo" variant="solid" className="text-[9px] px-2 py-0.5 font-black uppercase tracking-widest shadow-lg animate-pulse">
Últimas Unidades
<div className="absolute top-2 left-2 sm:top-4 sm:left-4">
<Badge color="indigo" variant="solid" className="text-[8px] sm:text-[9px] px-1.5 sm:px-2 py-0.5 font-black uppercase tracking-widest shadow-lg animate-pulse">
Últimas
</Badge>
</div>
)}
</div>
<div className="p-5 flex-1 flex flex-col gap-4">
<div className="p-3 sm:p-5 flex-1 flex flex-col gap-3 sm:gap-4">
<div className="space-y-1">
<h3 className="font-black text-slate-900 text-base tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic truncate">{p.name}</h3>
<div className="text-xs font-bold text-slate-400 uppercase tracking-widest">
<h3 className="font-black text-slate-900 text-sm sm:text-base tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic truncate">{p.name}</h3>
<div className="text-[10px] sm:text-xs font-bold text-slate-400 uppercase tracking-widest">
{p.stock} em stock
</div>
</div>
<div className="mt-auto pt-4 border-t border-slate-50 flex flex-col gap-3">
<div className="text-xl font-black text-slate-900 tracking-tighter">
<div className="mt-auto pt-3 sm:pt-4 border-t border-slate-50 flex flex-col gap-2 sm:gap-3">
<div className="text-lg sm:text-xl font-black text-slate-900 tracking-tighter">
{currency(p.price)}
</div>
@@ -46,7 +46,7 @@ export const ProductList = ({
<Button
onClick={() => onAdd(p.id)}
disabled={p.stock <= 0}
className="w-full h-10 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-[10px]"
className="w-full h-9 sm:h-10 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-wider sm:tracking-widest text-[9px] sm:text-[10px]"
>
{p.stock > 0 ? 'Adicionar' : 'Esgotado'}
</Button>
@@ -58,8 +58,3 @@ export const ProductList = ({
})}
</div>
);

View File

@@ -11,12 +11,12 @@ export const ServiceList = ({
services: Service[];
onSelect?: (id: string) => void;
}) => (
<div className="grid md:grid-cols-2 gap-6">
<div className="grid md:grid-cols-2 gap-4 sm:gap-6">
{services.map((s) => (
<Card key={s.id} className="p-6 border-none glass-card rounded-[2rem] group hover:shadow-2xl transition-all duration-300">
<div className="flex items-start justify-between gap-4">
<div className="flex-1 space-y-2">
<h3 className="font-black text-slate-900 text-xl tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic">{s.name}</h3>
<Card key={s.id} className="p-4 sm:p-6 border-none glass-card rounded-2xl sm:rounded-[2rem] group hover:shadow-2xl transition-all duration-300">
<div className="flex items-start justify-between gap-3 sm:gap-4">
<div className="flex-1 min-w-0 space-y-2">
<h3 className="font-black text-slate-900 text-lg sm:text-xl tracking-tight leading-tight group-hover:text-indigo-600 transition-colors uppercase italic truncate">{s.name}</h3>
<div className="flex items-center gap-3">
<div className="flex items-center gap-1.5 px-2 py-1 bg-slate-100 rounded-lg text-xs font-bold text-slate-500 uppercase">
<Clock size={12} />
@@ -24,19 +24,19 @@ export const ServiceList = ({
</div>
</div>
</div>
<div className="text-2xl font-black text-slate-900 tracking-tighter bg-indigo-50 px-3 py-1 rounded-xl border border-indigo-100">
<div className="text-xl sm:text-2xl font-black text-slate-900 tracking-tighter bg-indigo-50 px-2 sm:px-3 py-1 rounded-xl border border-indigo-100 whitespace-nowrap shrink-0">
{currency(s.price)}
</div>
</div>
{onSelect && (
<div className="mt-6 pt-6 border-t border-slate-100 flex items-center justify-between gap-4">
<p className="text-xs font-medium text-slate-400 max-w-[150px]">Lugar disponível hoje</p>
<div className="mt-4 sm:mt-6 pt-4 sm:pt-6 border-t border-slate-100 flex items-center justify-between gap-3 sm:gap-4">
<p className="text-xs font-medium text-slate-400 max-w-[100px] sm:max-w-[150px] shrink-0">Lugar disponível hoje</p>
<Button
onClick={() => onSelect(s.id)}
className="flex-1 h-11 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-widest text-xs"
className="flex-1 min-w-0 h-10 sm:h-11 bg-slate-900 hover:bg-slate-800 text-indigo-400 font-black rounded-xl shadow-lg shadow-slate-200 transition-all active:scale-95 uppercase tracking-wider sm:tracking-widest text-[10px] sm:text-xs truncate px-3 sm:px-4"
>
Reservar Agora
Reservar
</Button>
</div>
)}
@@ -44,4 +44,3 @@ export const ServiceList = ({
))}
</div>
);

View File

@@ -6,8 +6,8 @@ import { Button } from './ui/button';
export const ShopCard = ({ shop }: { shop: BarberShop }) => {
return (
<Card className="overflow-hidden border-none glass-card rounded-[2.5rem] group hover:shadow-2xl hover:shadow-slate-200/60 transition-all duration-500">
<div className="relative h-44 overflow-hidden">
<Card className="overflow-hidden border-none glass-card rounded-2xl sm:rounded-[2.5rem] group hover:shadow-2xl hover:shadow-slate-200/60 transition-all duration-500">
<div className="relative h-36 sm:h-44 overflow-hidden">
{shop.imageUrl ? (
<img
src={shop.imageUrl}
@@ -22,42 +22,42 @@ export const ShopCard = ({ shop }: { shop: BarberShop }) => {
<div className="absolute inset-0 bg-gradient-to-t from-slate-900/80 via-transparent to-transparent opacity-60 group-hover:opacity-40 transition-opacity" />
{/* Rating Badge */}
<div className="absolute top-4 right-4 bg-slate-900/90 backdrop-blur-md border border-white/10 px-3 py-1 rounded-full flex items-center gap-1.5 shadow-xl">
<Star size={14} className="fill-indigo-500 text-indigo-500" />
<span className="text-white text-xs font-black tracking-wider">
<div className="absolute top-3 right-3 sm:top-4 sm:right-4 bg-slate-900/90 backdrop-blur-md border border-white/10 px-2 sm:px-3 py-1 rounded-full flex items-center gap-1.5 shadow-xl">
<Star size={12} className="fill-indigo-500 text-indigo-500" />
<span className="text-white text-[10px] sm:text-xs font-black tracking-wider">
{shop.rating ? shop.rating.toFixed(1) : '0.0'}
</span>
</div>
</div>
<div className="p-6 space-y-4">
<div className="p-4 sm:p-6 space-y-3 sm:space-y-4">
<div>
<h2 className="text-slate-900 text-xl font-black tracking-tight group-hover:text-indigo-600 transition-colors truncate">
<h2 className="text-slate-900 text-lg sm:text-xl font-black tracking-tight group-hover:text-indigo-600 transition-colors truncate">
{shop.name}
</h2>
<div className="flex items-center gap-1.5 text-slate-500 mt-1">
<MapPin size={14} className="text-indigo-600" />
<MapPin size={14} className="text-indigo-600 shrink-0" />
<p className="text-sm font-medium line-clamp-1">
{shop.address || 'Endereço Indisponível'}
</p>
</div>
</div>
<div className="flex items-center justify-between pt-2">
<div className="flex items-center gap-3">
<div className="flex -space-x-2">
<div className="flex items-center justify-between pt-2 gap-2">
<div className="flex items-center gap-2 sm:gap-3 min-w-0">
<div className="flex -space-x-2 shrink-0">
{[1, 2, 3].map(i => (
<div key={i} className="w-7 h-7 rounded-full border-2 border-white bg-slate-100 flex items-center justify-center overflow-hidden">
<User size={12} className="text-slate-400" />
<div key={i} className="w-6 h-6 sm:w-7 sm:h-7 rounded-full border-2 border-white bg-slate-100 flex items-center justify-center overflow-hidden">
<User size={10} className="text-slate-400" />
</div>
))}
</div>
<span className="text-xs font-bold text-slate-400 uppercase tracking-widest">
<span className="text-[10px] sm:text-xs font-bold text-slate-400 uppercase tracking-wider sm:tracking-widest truncate">
+{(shop.barbers || []).length} Barbeiros
</span>
</div>
<Button asChild className="rounded-xl bg-slate-900 hover:bg-slate-800 text-indigo-400 font-bold px-5 h-10 shadow-lg shadow-slate-200 transition-all active:scale-95">
<Button asChild className="rounded-xl bg-slate-900 hover:bg-slate-800 text-indigo-400 font-bold px-3 sm:px-5 h-9 sm:h-10 shadow-lg shadow-slate-200 transition-all active:scale-95 text-xs sm:text-sm shrink-0">
<Link to={`/barbearia/${shop.id}`}>Reservar</Link>
</Button>
</div>

View File

@@ -16,7 +16,7 @@ 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-20 max-w-6xl items-center justify-between px-6">
<div className="mx-auto flex h-16 sm:h-20 max-w-6xl items-center justify-between px-3 sm:px-6">
<Link
to="/"
className="text-2xl font-black tracking-tighter text-slate-900 group flex items-center gap-2"

View File

@@ -5,7 +5,7 @@ export function Shell() {
return (
<>
<Header />
<main className="mx-auto max-w-5xl px-4 py-6">
<main className="mx-auto max-w-5xl px-3 sm:px-4 py-4 sm:py-6 overflow-x-hidden">
<Outlet />
</main>
</>

View File

@@ -15,6 +15,13 @@
* {
@apply border-slate-200;
box-sizing: border-box;
}
html, body {
overflow-x: hidden;
width: 100%;
max-width: 100vw;
}
body {
@@ -62,8 +69,3 @@
@apply bg-gradient-to-br from-slate-800 via-slate-900 to-slate-950;
}
}

View File

@@ -101,14 +101,14 @@ export default function Booking() {
];
return (
<div className="max-w-4xl mx-auto space-y-10 py-4 pb-20">
<div className="max-w-4xl mx-auto space-y-6 sm:space-y-10 py-2 sm:py-4 pb-12 sm:pb-20">
<header className="space-y-4 text-center">
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-indigo-100 text-indigo-700 text-[10px] font-black uppercase tracking-widest">
<Calendar size={10} fill="currentColor" />
<span>Reserva Exclusiva</span>
</div>
<div className="space-y-1">
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
<h1 className="text-2xl sm:text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Reservar em <span className="text-indigo-600 block md:inline">{shop.name}</span>
</h1>
<div className="flex items-center justify-center gap-2 text-slate-500 font-medium">
@@ -129,7 +129,7 @@ export default function Booking() {
if (s.completed || s.id < step) setStep(s.id);
}}
disabled={!s.completed && s.id > step}
className={`w-12 h-12 rounded-2xl flex items-center justify-center border-2 transition-all duration-500 scale-100 active:scale-90 ${
className={`w-10 h-10 sm:w-12 sm:h-12 rounded-xl sm:rounded-2xl flex items-center justify-center border-2 transition-all duration-500 scale-100 active:scale-90 ${
s.active
? 'bg-slate-900 border-slate-900 text-indigo-400 shadow-2xl shadow-slate-300 -translate-y-2'
: s.completed
@@ -137,9 +137,9 @@ export default function Booking() {
: 'bg-white border-slate-200 text-slate-400'
}`}
>
{s.completed && !s.active ? <CheckCircle2 size={24} /> : <s.icon size={24} />}
{s.completed && !s.active ? <CheckCircle2 size={20} /> : <s.icon size={20} />}
</button>
<div className={`mt-3 text-[10px] font-black uppercase tracking-widest ${s.active ? 'text-slate-900' : 'text-slate-400'}`}>
<div className={`mt-2 sm:mt-3 text-[8px] sm:text-[10px] font-black uppercase tracking-widest ${s.active ? 'text-slate-900' : 'text-slate-400'}`}>
{s.label}
</div>
</div>
@@ -147,9 +147,9 @@ export default function Booking() {
</div>
</div>
<Card className="p-2 border-none glass-card rounded-[3rem] shadow-2xl shadow-slate-200/50 overflow-hidden">
<Card className="p-1.5 sm:p-2 border-none glass-card rounded-2xl sm:rounded-[3rem] shadow-2xl shadow-slate-200/50 overflow-hidden">
{/* Dynamic Step Content */}
<div className="p-8 md:p-12 space-y-10">
<div className="p-4 sm:p-8 md:p-12 space-y-6 sm:space-y-10">
{/* Step Back Button */}
{step > 1 && (
@@ -166,7 +166,7 @@ export default function Booking() {
{step === 1 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">1. Selecione o <span className="text-indigo-600">Serviço</span></h3>
<h3 className="text-xl sm:text-3xl font-black text-slate-900 tracking-tighter uppercase italic">1. Selecione o <span className="text-indigo-600">Serviço</span></h3>
<p className="text-slate-500 font-medium italic">O primeiro passo para o seu agendamento.</p>
</div>
<div className="grid md:grid-cols-2 gap-6">
@@ -177,7 +177,7 @@ export default function Booking() {
setService(s.id);
setStep(2);
}}
className={`group p-6 rounded-[2rem] border-2 text-left transition-all duration-300 flex flex-col gap-4 ${
className={`group p-4 sm:p-6 rounded-2xl sm:rounded-[2rem] border-2 text-left transition-all duration-300 flex flex-col gap-3 sm:gap-4 ${
serviceId === s.id
? 'border-slate-900 bg-slate-900 text-white shadow-2xl translate-y-[-4px]'
: 'border-slate-50 bg-slate-50 hover:border-indigo-200 hover:bg-indigo-50/50'
@@ -206,7 +206,7 @@ export default function Booking() {
{step === 2 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">2. Escolha o <span className="text-indigo-600">Mestre</span></h3>
<h3 className="text-xl sm:text-3xl font-black text-slate-900 tracking-tighter uppercase italic">2. Escolha o <span className="text-indigo-600">Mestre</span></h3>
<p className="text-slate-500 font-medium italic">Selecione o artista que cuidará do seu visual.</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-3 gap-6">
@@ -217,13 +217,13 @@ export default function Booking() {
setBarber(b.id);
setStep(3);
}}
className={`group p-6 rounded-[2.5rem] border-2 text-center transition-all duration-300 flex flex-col items-center gap-5 ${
className={`group p-4 sm:p-6 rounded-2xl sm:rounded-[2.5rem] border-2 text-center transition-all duration-300 flex flex-col items-center gap-3 sm:gap-5 ${
barberId === b.id
? 'border-slate-900 bg-slate-900 text-white shadow-2xl translate-y-[-4px]'
: 'border-slate-50 bg-slate-50 hover:border-indigo-200 hover:bg-indigo-50/50'
}`}
>
<div className={`w-32 h-32 rounded-[2rem] overflow-hidden border-4 transition-all duration-500 ${barberId === b.id ? 'border-indigo-500 rotate-3' : 'border-white group-hover:border-indigo-100'}`}>
<div className={`w-20 h-20 sm:w-32 sm:h-32 rounded-2xl sm:rounded-[2rem] overflow-hidden border-4 transition-all duration-500 ${barberId === b.id ? 'border-indigo-500 rotate-3' : 'border-white group-hover:border-indigo-100'}`}>
{b.imageUrl ? (
<img src={b.imageUrl} alt={b.name} className="w-full h-full object-cover" />
) : (
@@ -247,7 +247,7 @@ export default function Booking() {
{step === 3 && (
<div className="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">3. Escolha o <span className="text-indigo-600">Horário</span></h3>
<h3 className="text-xl sm:text-3xl font-black text-slate-900 tracking-tighter uppercase italic">3. Escolha o <span className="text-indigo-600">Horário</span></h3>
<p className="text-slate-500 font-medium italic">Seu tempo é valioso. Escolha a data perfeita.</p>
</div>
<div className="max-w-md mx-auto relative">
@@ -271,14 +271,14 @@ export default function Booking() {
{step === 4 && (
<div className="space-y-10 animate-in fade-in slide-in-from-bottom-4 duration-500">
<div className="space-y-2 text-center md:text-left">
<h3 className="text-3xl font-black text-slate-900 tracking-tighter uppercase italic">4. Escolha o <span className="text-indigo-600">Horário Privilegiado</span></h3>
<h3 className="text-xl sm:text-3xl font-black text-slate-900 tracking-tighter uppercase italic">4. Escolha o <span className="text-indigo-600">Horário Privilegiado</span></h3>
<p className="text-slate-500 font-medium italic">A pontualidade é a cortesia dos reis.</p>
</div>
<div className="flex flex-col md:flex-row gap-6">
{/* Left Side: Summary Sidebar */}
<div className="w-full md:w-80 space-y-4">
<div className="p-6 bg-slate-900 text-white rounded-[2rem] space-y-6 shadow-xl relative overflow-hidden">
<div className="p-4 sm:p-6 bg-slate-900 text-white rounded-2xl sm:rounded-[2rem] space-y-4 sm:space-y-6 shadow-xl relative overflow-hidden">
<div className="absolute top-0 right-0 w-32 h-32 bg-indigo-500/5 rounded-full blur-3xl -translate-y-1/2 translate-x-1/2" />
<div className="space-y-4 relative z-10">
<div className="space-y-1 border-b border-white/10 pb-4">

View File

@@ -53,7 +53,7 @@ export default function Explore() {
}, [shops, query, filter, sortBy]);
return (
<div className="max-w-6xl mx-auto space-y-10 py-6">
<div className="max-w-6xl mx-auto space-y-6 sm:space-y-10 py-4 sm:py-6">
<section className="space-y-4 text-center md:text-left">
<div className="flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="space-y-1">
@@ -61,7 +61,7 @@ export default function Explore() {
<Star size={10} fill="currentColor" />
<span>As Nossas Barbearias</span>
</div>
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
<h1 className="text-2xl sm:text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Ver <span className="text-indigo-600">Barbearias</span>
</h1>
<p className="text-slate-500 font-medium max-w-md">Descubra barbearias exclusivas e reserve o seu próximo corte em segundos.</p>
@@ -73,7 +73,7 @@ export default function Explore() {
</section>
<div className="grid gap-8">
<Card className="p-2 border-none glass-card rounded-[2.5rem] shadow-2xl shadow-slate-200/50">
<Card className="p-1.5 sm:p-2 border-none glass-card rounded-2xl sm:rounded-[2.5rem] shadow-2xl shadow-slate-200/50">
<div className="flex flex-col md:flex-row items-center gap-2 p-1">
<div className="relative flex-1 w-full">
<Search size={20} className="absolute left-5 top-1/2 -translate-y-1/2 text-slate-400" />
@@ -81,24 +81,24 @@ export default function Explore() {
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Pesquisar por nome ou endereço..."
className="h-14 pl-14 pr-6 bg-transparent border-none text-lg font-medium focus:ring-0 placeholder:text-slate-400"
className="h-12 sm:h-14 pl-12 sm:pl-14 pr-4 sm:pr-6 bg-transparent border-none text-base sm:text-lg font-medium focus:ring-0 placeholder:text-slate-400"
/>
</div>
<div className="h-10 w-px bg-slate-200 hidden md:block" />
<div className="flex flex-wrap items-center gap-2 px-4 py-2 w-full md:w-auto">
<div className="flex flex-wrap items-center gap-2 px-2 sm:px-4 py-2 w-full md:w-auto">
<Chip
active={filter === 'todas'}
onClick={() => setFilter('todas')}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'todas' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
className={`h-9 sm:h-11 px-4 sm:px-6 rounded-xl sm:rounded-2xl font-bold uppercase tracking-tight transition-all text-xs sm:text-sm ${filter === 'todas' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
>
Todas
</Chip>
<Chip
active={filter === 'top'}
onClick={() => setFilter('top')}
className={`h-11 px-6 rounded-2xl font-bold uppercase tracking-tight transition-all ${filter === 'top' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
className={`h-9 sm:h-11 px-4 sm:px-6 rounded-xl sm:rounded-2xl font-bold uppercase tracking-tight transition-all text-xs sm:text-sm ${filter === 'top' ? '!bg-slate-900 !text-indigo-400 border-none shadow-lg' : 'bg-slate-100 text-slate-500 hover:bg-slate-200'}`}
>
Top Avaliadas
</Chip>
@@ -106,7 +106,7 @@ export default function Explore() {
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value as typeof sortBy)}
className="h-11 rounded-2xl border-none bg-slate-100 px-4 text-sm font-bold text-slate-700 focus:ring-2 focus:ring-indigo-500/20"
className="h-9 sm:h-11 rounded-xl sm:rounded-2xl border-none bg-slate-100 px-3 sm:px-4 text-xs sm:text-sm font-bold text-slate-700 focus:ring-2 focus:ring-indigo-500/20"
>
<option value="avaliacao">Melhor avaliação</option>
<option value="servicos">Mais serviços</option>
@@ -134,7 +134,7 @@ export default function Explore() {
</Button>
</Card>
) : (
<div className="grid md:grid-cols-2 lg:grid-cols-2 gap-8 lg:gap-10">
<div className="grid sm:grid-cols-2 lg:grid-cols-2 gap-4 sm:gap-8 lg:gap-10">
{filtered.map((shop) => (
<ShopCard key={shop.id} shop={shop} />
))}

View File

@@ -31,9 +31,9 @@ export default function Landing() {
const featuredShops = shops.slice(0, 3);
return (
<div className="space-y-24 md:space-y-32 pb-24">
<div className="space-y-12 sm:space-y-24 md:space-y-32 pb-12 sm:pb-24">
{/* Hero Section - Midnight Luxury Style */}
<section className="relative overflow-hidden rounded-[3rem] obsidian-gradient text-white px-8 py-20 md:px-16 md:py-32 shadow-[0_20px_50px_rgba(0,0,0,0.3)] border border-white/5">
<section className="relative overflow-hidden rounded-2xl sm:rounded-[3rem] obsidian-gradient text-white px-4 py-10 sm:px-8 sm:py-20 md:px-16 md:py-32 shadow-[0_20px_50px_rgba(0,0,0,0.3)] border border-white/5">
<div className="absolute inset-0 bg-[url('https://www.transparenttextures.com/patterns/carbon-fibre.png')] opacity-20 pointer-events-none"></div>
<div className="absolute top-0 right-0 w-[500px] h-[500px] bg-indigo-500/10 rounded-full blur-[120px] -translate-y-1/2 translate-x-1/2"></div>
<div className="absolute bottom-0 left-0 w-[500px] h-[500px] bg-slate-500/10 rounded-full blur-[120px] translate-y-1/2 -translate-x-1/2"></div>
@@ -44,41 +44,41 @@ export default function Landing() {
<span>A Solução Completa para a Sua Barbearia</span>
</div>
<h1 className="text-6xl md:text-8xl font-black leading-[0.9] tracking-tighter text-balance uppercase italic">
<h1 className="text-3xl sm:text-5xl md:text-8xl font-black leading-[0.9] tracking-tighter text-balance uppercase italic">
Gestão Simplificada da sua <br />
<span className="text-indigo-400 italic">Barbearia</span>
</h1>
<p className="text-xl md:text-2xl text-slate-300 max-w-2xl leading-relaxed font-medium">
<p className="text-base sm:text-xl md:text-2xl text-slate-300 max-w-2xl leading-relaxed font-medium">
Organize a sua barbearia com facilidade.
Simples, rápido e eficiente.
</p>
<div className="flex flex-wrap gap-6 pt-6">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-xs transition-all duration-300 rounded-2xl shadow-2xl">
<div className="flex flex-wrap gap-3 sm:gap-6 pt-4 sm:pt-6">
<Button asChild size="lg" className="h-12 sm:h-16 px-6 sm:px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-[10px] sm:text-xs transition-all duration-300 rounded-xl sm:rounded-2xl shadow-2xl">
<Link to="/explorar" className="flex items-center gap-3">
Ver Barbearias
<ArrowRight size={18} strokeWidth={3} />
</Link>
</Button>
<Button asChild variant="outline" size="lg" className="h-16 px-10 bg-transparent text-white border-white/20 hover:bg-white/5 hover:border-white/40 font-black uppercase tracking-widest text-xs rounded-2xl backdrop-blur-sm">
<Button asChild variant="outline" size="lg" className="h-12 sm:h-16 px-6 sm:px-10 bg-transparent text-white border-white/20 hover:bg-white/5 hover:border-white/40 font-black uppercase tracking-widest text-[10px] sm:text-xs rounded-xl sm:rounded-2xl backdrop-blur-sm">
<Link to="/registo">Parceiro Profissional</Link>
</Button>
</div>
{/* Stats Bar */}
<div className="grid grid-cols-3 gap-10 pt-12 border-t border-white/10 max-w-2xl">
<div className="grid grid-cols-3 gap-4 sm:gap-10 pt-8 sm:pt-12 border-t border-white/10 max-w-2xl">
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter italic">500+</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Lojas Registadas</div>
<div className="text-2xl sm:text-4xl md:text-5xl font-black tracking-tighter italic">500+</div>
<div className="text-[8px] sm:text-[10px] font-black text-slate-400 uppercase tracking-widest">Lojas Registadas</div>
</div>
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter italic">10K+</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Cortes Marcados</div>
<div className="text-2xl sm:text-4xl md:text-5xl font-black tracking-tighter italic">10K+</div>
<div className="text-[8px] sm:text-[10px] font-black text-slate-400 uppercase tracking-widest">Cortes Marcados</div>
</div>
<div className="space-y-1">
<div className="text-4xl md:text-5xl font-black tracking-tighter indigo-gradient bg-clip-text text-transparent italic">4.9</div>
<div className="text-[10px] font-black text-slate-400 uppercase tracking-widest">Média de Avaliação</div>
<div className="text-2xl sm:text-4xl md:text-5xl font-black tracking-tighter indigo-gradient bg-clip-text text-transparent italic">4.9</div>
<div className="text-[8px] sm:text-[10px] font-black text-slate-400 uppercase tracking-widest">Média de Avaliação</div>
</div>
</div>
</div>
@@ -86,10 +86,10 @@ export default function Landing() {
{/* Features - Minimalist & Bold */}
<section className="max-w-7xl mx-auto px-6">
<section className="max-w-7xl mx-auto px-3 sm:px-6">
<div className="text-center md:text-left mb-16 flex flex-col md:flex-row md:items-end justify-between gap-6">
<div className="space-y-4">
<h2 className="text-5xl md:text-6xl font-black text-slate-900 tracking-tighter uppercase italic pr-8 border-l-[12px] border-indigo-600 pl-8">
<h2 className="text-3xl sm:text-5xl md:text-6xl font-black text-slate-900 tracking-tighter uppercase italic pr-4 sm:pr-8 border-l-[8px] sm:border-l-[12px] border-indigo-600 pl-4 sm:pl-8">
Tudo para o <br /> <span className="text-indigo-700">Seu Negócio</span>
</h2>
<p className="text-xl text-slate-500 font-medium max-w-xl">
@@ -98,7 +98,7 @@ export default function Landing() {
</div>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-10">
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-10">
{[
{
icon: Clock,
@@ -116,7 +116,7 @@ export default function Landing() {
desc: 'Saiba exatamente quanto a barbearia está faturando e quais os serviços mais procurados.',
},
].map((feature) => (
<Card key={feature.title} className="p-10 space-y-6 rounded-[2.5rem] transition-all duration-500 hover:-translate-y-2 group bg-white border-2 border-slate-50 shadow-sm hover:shadow-xl">
<Card key={feature.title} className="p-6 sm:p-10 space-y-4 sm:space-y-6 rounded-2xl sm:rounded-[2.5rem] transition-all duration-500 hover:-translate-y-2 group bg-white border-2 border-slate-50 shadow-sm hover:shadow-xl">
<div className="w-16 h-16 rounded-2xl flex items-center justify-center bg-indigo-50 text-indigo-600 group-hover:bg-indigo-600 group-hover:text-white transition-colors duration-300">
<feature.icon size={32} />
</div>
@@ -130,17 +130,17 @@ export default function Landing() {
</section>
{/* How it Works - Immersive */}
<section className="bg-slate-950 py-24 md:py-32 overflow-hidden relative">
<section className="bg-slate-950 py-12 sm:py-24 md:py-32 overflow-hidden relative">
<div className="absolute top-0 left-0 w-full h-full bg-[radial-gradient(circle_at_center,_var(--tw-gradient-stops))] from-slate-900 via-slate-950 to-slate-950 opacity-50" />
<div className="max-w-6xl mx-auto px-6 relative z-10">
<div className="text-center mb-20">
<h2 className="text-5xl md:text-6xl font-black text-white tracking-tighter uppercase italic mb-6">
<div className="max-w-6xl mx-auto px-3 sm:px-6 relative z-10">
<div className="text-center mb-10 sm:mb-20">
<h2 className="text-3xl sm:text-5xl md:text-6xl font-black text-white tracking-tighter uppercase italic mb-4 sm:mb-6">
Como <span className="text-indigo-400">Funciona</span>
</h2>
<div className="w-24 h-1 bg-indigo-500 mx-auto rounded-full shadow-[0_0_20px_rgba(99,102,241,0.5)]" />
</div>
<div className="grid md:grid-cols-3 gap-16 relative">
<div className="grid md:grid-cols-3 gap-8 sm:gap-16 relative">
{[
{ step: '01', title: 'Explorar', desc: 'Encontre as barbearias mais próximas e veja as fotos e avaliações reais.' },
{ step: '02', title: 'Agendar', desc: 'Escolha o seu barbeiro e o serviço. Marque o dia e hora que preferir.' },
@@ -166,10 +166,10 @@ export default function Landing() {
</section>
{/* Featured Shops - Premium Row */}
<section className="max-w-7xl mx-auto px-6">
<section className="max-w-7xl mx-auto px-3 sm:px-6">
<div className="flex flex-col md:flex-row items-center justify-between mb-16 gap-6">
<div className="space-y-2 text-center md:text-left">
<h1 className="text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
<h1 className="text-2xl sm:text-4xl md:text-5xl font-black text-slate-900 tracking-tighter uppercase italic">
Descobrir <span className="text-indigo-600">Barbearias</span>
</h1>
<p className="text-slate-500 font-bold uppercase tracking-[0.2em] text-xs">As melhores barbearias do país</p>
@@ -182,7 +182,7 @@ export default function Landing() {
</Button>
</div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-10">
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-10">
{featuredShops.map((shop) => (
<ShopCard key={shop.id} shop={shop} />
))}
@@ -190,13 +190,13 @@ export default function Landing() {
</section>
{/* Final CTA - Immersive Dark */}
<section className="px-6">
<div className="max-w-6xl mx-auto relative overflow-hidden rounded-[4rem] obsidian-gradient text-white px-8 py-20 md:px-20 md:py-24 shadow-2xl border border-white/5">
<section className="px-3 sm:px-6">
<div className="max-w-6xl mx-auto relative overflow-hidden rounded-2xl sm:rounded-[4rem] obsidian-gradient text-white px-4 py-12 sm:px-8 sm:py-20 md:px-20 md:py-24 shadow-2xl border border-white/5">
<div className="absolute top-0 right-0 w-[400px] h-[400px] bg-indigo-500/10 rounded-full blur-[100px]" />
<div className="absolute bottom-0 left-0 w-[400px] h-[400px] bg-slate-500/10 rounded-full blur-[100px]" />
<div className="relative text-center space-y-12 max-w-3xl mx-auto">
<h2 className="text-5xl md:text-7xl font-black tracking-tighter uppercase italic leading-[0.9]">
<h2 className="text-3xl sm:text-5xl md:text-7xl font-black tracking-tighter uppercase italic leading-[0.9]">
Registe a <br /> sua <span className="text-indigo-400">Barbearia</span>
</h2>
<p className="text-xl text-slate-400 font-medium leading-relaxed">
@@ -204,13 +204,13 @@ export default function Landing() {
A sua barbearia merece o melhor.
</p>
<div className="flex flex-wrap justify-center gap-6 pt-4">
<Button asChild size="lg" className="h-16 px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-xs rounded-2xl transition-all shadow-2xl">
<Button asChild size="lg" className="h-12 sm:h-16 px-6 sm:px-10 bg-white text-slate-950 hover:bg-indigo-600 hover:text-white font-black uppercase tracking-widest text-[10px] sm:text-xs rounded-xl sm:rounded-2xl transition-all shadow-2xl">
<Link to="/registo" className="flex items-center gap-3">
Criar Conta
<ArrowRight size={18} />
</Link>
</Button>
<Button asChild variant="outline" size="lg" className="h-16 px-10 border-white/20 text-white font-black uppercase tracking-widest text-xs rounded-2xl hover:bg-white/5">
<Button asChild variant="outline" size="lg" className="h-12 sm:h-16 px-6 sm:px-10 border-white/20 text-white font-black uppercase tracking-widest text-[10px] sm:text-xs rounded-xl sm:rounded-2xl hover:bg-white/5">
<Link to="/explorar">Navegar agora</Link>
</Button>
</div>

View File

@@ -135,15 +135,16 @@ export default function Profile() {
/>
)}
<div className="max-w-4xl mx-auto space-y-12 pb-20">
<div className="max-w-4xl mx-auto space-y-8 sm:space-y-12 pb-12 sm:pb-20">
{/* Cabeçalho do Perfil */}
<section className="relative overflow-hidden rounded-[3rem] obsidian-gradient text-white p-8 md:p-12 shadow-2xl border border-white/5">
<section className="relative overflow-hidden rounded-2xl sm:rounded-[3rem] obsidian-gradient text-white p-4 sm:p-8 md:p-12 shadow-2xl border border-white/5">
<div className="absolute top-0 right-0 w-64 h-64 bg-indigo-500/10 rounded-full blur-[80px] -translate-y-1/2 translate-x-1/2" />
<div className="relative z-10 flex flex-col md:flex-row items-center gap-8 md:text-left text-center">
<div className="relative group">
<div className="absolute inset-0 bg-indigo-500 blur-2xl opacity-20 group-hover:opacity-40 transition-opacity" />
<div className="w-24 h-24 bg-white/10 backdrop-blur-xl border-2 border-white/20 rounded-[2rem] flex items-center justify-center text-indigo-400 shadow-2xl relative z-10 transition-transform duration-500 hover:rotate-6">
<User size={48} />
<div className="w-16 h-16 sm:w-24 sm:h-24 bg-white/10 backdrop-blur-xl border-2 border-white/20 rounded-2xl sm:rounded-[2rem] flex items-center justify-center text-indigo-400 shadow-2xl relative z-10 transition-transform duration-500 hover:rotate-6">
<User size={36} className="sm:hidden" />
<User size={48} className="hidden sm:block" />
</div>
</div>
<div className="space-y-3">
@@ -151,7 +152,7 @@ export default function Profile() {
<Star size={12} fill="currentColor" />
<span>Utilizador Registado</span>
</div>
<h1 className="text-4xl md:text-5xl font-black tracking-tighter uppercase italic leading-[0.9]">
<h1 className="text-2xl sm:text-4xl md:text-5xl font-black tracking-tighter uppercase italic leading-[0.9]">
{displayName}
</h1>
<p className="text-slate-400 font-medium italic">{authEmail}</p>
@@ -257,8 +258,8 @@ export default function Profile() {
const canReview = a.status === 'concluido' && !reviewedAppointments.has(a.id)
return (
<Card key={a.id} className="p-2 border-none glass-card rounded-[2.5rem] shadow-lg shadow-slate-200/50">
<div className="p-6 space-y-4">
<Card key={a.id} className="p-1.5 sm:p-2 border-none glass-card rounded-2xl sm:rounded-[2.5rem] shadow-lg shadow-slate-200/50">
<div className="p-4 sm:p-6 space-y-3 sm:space-y-4">
<div className="flex items-start justify-between">
<div className="space-y-1">
<div className="flex items-center gap-3">

View File

@@ -38,59 +38,66 @@ export default function ShopDetails() {
)}`;
return (
<div className="max-w-6xl mx-auto space-y-8 py-4">
<div className="relative overflow-hidden rounded-[2.5rem] border-none shadow-2xl shadow-slate-200/50">
<div className="max-w-6xl mx-auto space-y-6 sm:space-y-8 py-2 sm:py-4">
<div className="relative overflow-hidden rounded-2xl sm:rounded-[2.5rem] border-none shadow-2xl shadow-slate-200/50">
{shop.imageUrl ? (
<img src={shop.imageUrl} alt={`Foto de ${shop.name}`} className="h-64 w-full object-cover md:h-96 transition-transform duration-1000 hover:scale-105" />
<img src={shop.imageUrl} alt={`Foto de ${shop.name}`} className="h-48 sm:h-64 w-full object-cover md:h-96 transition-transform duration-1000 hover:scale-105" />
) : (
<div className="h-64 w-full obsidian-gradient md:h-96" />
<div className="h-48 sm:h-64 w-full obsidian-gradient md:h-96" />
)}
<div className="absolute inset-0 bg-gradient-to-t from-slate-950/80 via-slate-950/20 to-transparent" />
<div className="absolute right-6 top-6 flex gap-3">
<div className="absolute right-3 top-3 sm:right-6 sm:top-6 flex gap-2 sm:gap-3">
<a
href={mapUrl}
target="_blank"
rel="noreferrer"
className="w-12 h-12 flex items-center justify-center rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all"
className="w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-xl sm:rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all"
title="Ver no Mapa"
>
<MapPin size={22} />
<MapPin size={18} className="sm:hidden" />
<MapPin size={22} className="hidden sm:block" />
</a>
<button
onClick={() => toggleFavorite(shop.id)}
className="w-12 h-12 flex items-center justify-center rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all font-bold"
className="w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-xl sm:rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all font-bold"
type="button"
>
<Heart
size={18}
className={`sm:hidden ${isFavorite(shop.id) ? 'fill-rose-500 text-rose-500' : 'text-slate-900'}`}
/>
<Heart
size={22}
className={isFavorite(shop.id) ? 'fill-rose-500 text-rose-500' : 'text-slate-900'}
className={`hidden sm:block ${isFavorite(shop.id) ? 'fill-rose-500 text-rose-500' : 'text-slate-900'}`}
/>
</button>
<button
onClick={() => setImageOpen(true)}
className="w-12 h-12 flex items-center justify-center rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all"
className="w-10 h-10 sm:w-12 sm:h-12 flex items-center justify-center rounded-xl sm:rounded-2xl bg-white/90 backdrop-blur-md text-slate-900 shadow-xl hover:bg-white hover:scale-110 transition-all"
type="button"
>
<Maximize2 size={22} />
<Maximize2 size={18} className="sm:hidden" />
<Maximize2 size={22} className="hidden sm:block" />
</button>
</div>
<div className="absolute bottom-10 left-10 space-y-3">
<div className="flex items-center gap-2 bg-slate-900/40 backdrop-blur-md border border-white/20 w-fit px-3 py-1 rounded-full">
<Star size={14} className="fill-indigo-500 text-indigo-500" />
<span className="text-white text-xs font-black tracking-widest">{(shop.rating || 0).toFixed(1)} EXCELENTE</span>
<div className="absolute bottom-4 left-4 sm:bottom-10 sm:left-10 space-y-2 sm:space-y-3 max-w-[calc(100%-2rem)]">
<div className="flex items-center gap-2 bg-slate-900/40 backdrop-blur-md border border-white/20 w-fit px-2 sm:px-3 py-1 rounded-full">
<Star size={12} className="fill-indigo-500 text-indigo-500 sm:hidden" />
<Star size={14} className="fill-indigo-500 text-indigo-500 hidden sm:block" />
<span className="text-white text-[10px] sm:text-xs font-black tracking-widest">{(shop.rating || 0).toFixed(1)} EXCELENTE</span>
</div>
<h1 className="text-4xl md:text-5xl font-black text-white tracking-tighter">{shop.name}</h1>
<h1 className="text-2xl sm:text-4xl md:text-5xl font-black text-white tracking-tighter truncate">{shop.name}</h1>
<div className="flex items-center gap-2 text-white/90">
<MapPin size={16} className="text-indigo-600" />
<p className="text-base font-medium">{shop.address}</p>
<MapPin size={14} className="text-indigo-600 shrink-0" />
<p className="text-sm sm:text-base font-medium truncate">{shop.address}</p>
</div>
</div>
</div>
<div className="grid gap-8">
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4 bg-white/50 backdrop-blur-sm p-2 rounded-[2rem] border border-white/50">
<div className="grid gap-4 sm:gap-8">
<div className="flex flex-col gap-3 sm:gap-4 bg-white/50 backdrop-blur-sm p-1.5 sm:p-2 rounded-2xl sm:rounded-[2rem] border border-white/50">
<Tabs
tabs={[
{ id: 'servicos', label: 'Serviços' },
@@ -102,7 +109,7 @@ export default function ShopDetails() {
onChange={(v) => setTab(v as typeof tab)}
className="border-none bg-transparent"
/>
<div className="px-6 py-2 text-xs font-black text-slate-400 uppercase tracking-widest bg-white rounded-2xl border border-slate-100 shadow-sm">
<div className="px-3 sm:px-6 py-2 text-[10px] sm:text-xs font-black text-slate-400 uppercase tracking-widest bg-white rounded-xl sm:rounded-2xl border border-slate-100 shadow-sm text-center sm:text-left">
{(shop.services || []).length} SERVIÇOS · {(shop.barbers || []).length} BARBEIROS
</div>
</div>
@@ -116,11 +123,11 @@ export default function ShopDetails() {
}}
/>
) : tab === 'barbeiros' ? (
<div className="bg-white/30 backdrop-blur-md p-8 rounded-[3rem] border border-white/50">
<div className="bg-white/30 backdrop-blur-md p-4 sm:p-8 rounded-2xl sm:rounded-[3rem] border border-white/50">
<BarberList barbers={shop.barbers} />
</div>
) : tab === 'detalhes' ? (
<div className="bg-white/30 backdrop-blur-md p-8 rounded-[3rem] border border-white/50">
<div className="bg-white/30 backdrop-blur-md p-4 sm:p-8 rounded-2xl sm:rounded-[3rem] border border-white/50">
<ShopDetailsTab shop={shop} />
</div>
) : (
@@ -131,15 +138,11 @@ export default function ShopDetails() {
<Dialog open={imageOpen} title={shop.name} onClose={() => setImageOpen(false)}>
{shop.imageUrl ? (
<img src={shop.imageUrl} alt={`Foto de ${shop.name}`} className="w-full rounded-[2rem] object-cover shadow-2xl" />
<img src={shop.imageUrl} alt={`Foto de ${shop.name}`} className="w-full rounded-2xl sm:rounded-[2rem] object-cover shadow-2xl" />
) : (
<div className="h-96 w-full rounded-[2rem] obsidian-gradient" />
<div className="h-96 w-full rounded-2xl sm:rounded-[2rem] obsidian-gradient" />
)}
</Dialog>
</div>
);
}