211 lines
6.5 KiB
Markdown
211 lines
6.5 KiB
Markdown
# Design System - Playzer
|
||
|
||
## Direction
|
||
|
||
**Feel:** Technical but warm. Like a well-organized sports facility — functional, clean, but with life. Not cold like a trading terminal, not playful like a toy app.
|
||
|
||
**Users:** Owners and employees managing sports complex bookings. They're juggling schedules, phone calls, and walk-ins. The interface should stay out of the way while they're working.
|
||
|
||
---
|
||
|
||
## Domain
|
||
|
||
**Concepts:**
|
||
- Turno (booking slot) — A scheduled time block
|
||
- Cancha (court) — Sports court/pitch
|
||
- Horario — Time slot
|
||
- Complex — The sports facility
|
||
- Estado — Booking status (confirmed, cancelled, etc.)
|
||
|
||
**Color world:**
|
||
- Court green (grass/tennis)
|
||
- Clay tan (paddle court)
|
||
- Pool blue (swimming)
|
||
- Wood grain (locker rooms)
|
||
- Concrete gray (tech floor)
|
||
|
||
**Signature:**
|
||
- Status pills use subtle borders + background tint, not bold badges
|
||
- Status communicates without demanding attention — whisper-level hierarchy
|
||
|
||
---
|
||
|
||
## Decisions
|
||
|
||
### Depth
|
||
**Approach:** Borders-only with surface elevation shifts
|
||
- No drop shadows
|
||
- Higher elevation = slightly lighter surface (in dark: lighter = higher)
|
||
- Subtle rgba borders define edges quietly
|
||
|
||
### Spacing
|
||
**Base unit:** 4px
|
||
- Micro: 2-4px (icon gaps)
|
||
- Component: 8-12px (inside buttons, badges)
|
||
- Section: 16-24px (between groups)
|
||
- Major: 32-40px (between distinct areas)
|
||
|
||
### Typography
|
||
**Font:** Geist Variable
|
||
**Scale:**
|
||
- Display: 28px / semibold / -0.02em tracking
|
||
- Heading: 20px / semibold / -0.01em tracking
|
||
- Body: 14px / normal / 0 tracking
|
||
- Label: 12px / medium / 0 tracking
|
||
- Caption: 11px / normal / 0.02em tracking
|
||
|
||
### Border Radius
|
||
**Scale:**
|
||
- `sm`: 4px (inputs, small badges)
|
||
- `md`: 6px (buttons, cards)
|
||
- `lg`: 10px (dialogs, large cards)
|
||
- `xl`: 14px (modals)
|
||
|
||
### Borders
|
||
**Progression:**
|
||
- Subtle: `oklch(1 0 0 / 8%)` (background edges)
|
||
- Default: `oklch(1 0 0 / 12%)` (card borders)
|
||
- Emphasis: `oklch(1 0 0 / 20%)` (active states)
|
||
|
||
### Status Semantic Colors
|
||
**Light mode:**
|
||
- Confirmed: `bg-emerald-50 border-emerald-200 text-emerald-700`
|
||
- Pending: `bg-amber-50 border-amber-200 text-amber-700`
|
||
- Cancelled: `bg-rose-50 border-rose-200 text-rose-700`
|
||
- No-show: `bg-orange-50 border-orange-200 text-orange-700`
|
||
- Completed: `bg-sky-50 border-sky-200 text-sky-700`
|
||
|
||
**Dark mode:**
|
||
- Slight desaturation, lower opacity backgrounds
|
||
|
||
---
|
||
|
||
## Patterns
|
||
|
||
### Card
|
||
- Background: `bg-card`
|
||
- Border: `border` token
|
||
- Radius: `md`
|
||
- Padding: `p-4` (compact) / `p-5` (default)
|
||
|
||
### Status Badge
|
||
```tsx
|
||
<span className="inline-flex items-center gap-1.5 rounded-full border px-2 py-0.5 text-xs font-medium">
|
||
{icon}
|
||
{label}
|
||
</span>
|
||
```
|
||
|
||
### Input
|
||
- Background: `bg-input` (darker than surface)
|
||
- No heavy borders by default
|
||
- Focus ring via `ring-3 ring-ring/50`
|
||
|
||
### Layout
|
||
- Sidebar: Same background as main, subtle border separation
|
||
- Content: Max-width 6xl, centered
|
||
- Padding: `px-4 sm:px-6`
|
||
|
||
---
|
||
|
||
## TODO
|
||
|
||
- [x] Status tokens in CSS variables
|
||
- [x] Refactor status badges to use semantic classes
|
||
- [x] StatusBadge component created
|
||
- [x] Home panel redesign with metrics + timeline
|
||
|
||
---
|
||
|
||
## Home Page Patterns (v2)
|
||
|
||
### Structure
|
||
- Header + URL Card: 2-column grid (lg:)
|
||
- Main: Timeline de reservas
|
||
- Sidebar: Stats grid consolidado (sticky)
|
||
|
||
### URL Card (destacada)
|
||
```tsx
|
||
<div className="group relative overflow-hidden rounded-2xl border border-blue-200/60 bg-blue-50/50 p-5">
|
||
<div className="absolute inset-0 bg-gradient-to-br from-blue-50/80 to-blue-100/30" />
|
||
<div className="relative flex items-center justify-between gap-4">
|
||
<div className="min-w-0 flex-1">
|
||
<p className="text-[11px] font-semibold uppercase tracking-[0.28em] text-blue-600/70">
|
||
Reserva online
|
||
</p>
|
||
<a href={url} className="mt-1 block truncate text-sm font-medium text-blue-700 underline-offset-2 hover:underline">
|
||
{url}
|
||
</a>
|
||
</div>
|
||
<button className="relative flex shrink-0 items-center gap-2 rounded-lg bg-blue-500/10 px-3 py-2 text-sm font-medium text-blue-700 transition-colors hover:bg-blue-500/20">
|
||
<Copy className="size-4" />
|
||
<span>Copiar</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### Stats Pills (header)
|
||
```tsx
|
||
<span className="rounded-full border border-emerald-200/60 bg-emerald-50/60 px-3 py-1 text-xs font-medium text-emerald-700">
|
||
{count} hoy
|
||
</span>
|
||
```
|
||
|
||
### Stats Sidebar (grid 2x2)
|
||
```tsx
|
||
<section className="space-y-3 rounded-2xl border border-border/70 bg-card p-4">
|
||
<h2 className="text-xs font-semibold uppercase tracking-[0.28em] text-muted-foreground">
|
||
Resumen
|
||
</h2>
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div className="rounded-xl border border-border/50 bg-background/80 p-3">
|
||
<p className="text-xs text-muted-foreground">Total</p>
|
||
<p className="mt-1 text-xl font-semibold tracking-tight">{value}</p>
|
||
</div>
|
||
<div className="rounded-xl border border-emerald-200/40 bg-emerald-50/40 p-3">
|
||
<p className="text-xs text-emerald-700/80">Hoy</p>
|
||
<p className="mt-1 text-xl font-semibold tracking-tight text-emerald-700">{value}</p>
|
||
</div>
|
||
{/* ... confirmed (blue), completed (sky) */}
|
||
</div>
|
||
</section>
|
||
```
|
||
|
||
### Timeline Item (v2 - mejorado)
|
||
```tsx
|
||
<div className={[
|
||
'group flex items-center justify-between gap-3 rounded-xl border border-border/70 bg-card/60 p-3.5 transition-all hover:bg-muted/50',
|
||
isPast && 'opacity-50',
|
||
isNow && 'border-l-2 border-l-emerald-500 bg-emerald-50/30',
|
||
].join(' ')}>
|
||
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||
<div className="flex w-14 flex-col items-center">
|
||
<span className="text-sm font-medium tabular-nums">{startTime}</span>
|
||
<span className="text-[11px] text-muted-foreground">{endTime}</span>
|
||
</div>
|
||
<div className="min-w-0 flex-1">
|
||
<p className="truncate text-sm font-medium">{courtName}</p>
|
||
<p className="truncate text-xs text-muted-foreground">{customer}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex shrink-0 items-center gap-2">
|
||
<StatusBadge status={...} />
|
||
<div className="flex gap-1 opacity-0 transition-opacity group-hover:opacity-100">
|
||
<Button size="icon-xs" variant="ghost" title="Marcar cumplida"><Check className="size-3.5" /></Button>
|
||
<Button size="icon-xs" variant="ghost" title="Cancelar">×</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
### Date Group Header
|
||
```tsx
|
||
<div className="flex items-center gap-3">
|
||
<h2 className="text-sm font-semibold capitalize">{dateLabel}</h2>
|
||
<div className="h-px flex-1 border-t border-dashed border-border/40" />
|
||
<span className="shrink-0 rounded-full border border-border/70 bg-background px-2.5 py-0.5 text-[11px] font-medium text-muted-foreground">
|
||
{count}
|
||
</span>
|
||
</div>
|
||
``` |