diff --git a/desing.md b/desing.md new file mode 100644 index 0000000..ff9e51a --- /dev/null +++ b/desing.md @@ -0,0 +1,774 @@ +# Design System — EMI / Falck + +> Sistema de diseño derivado de la identidad visual de **EMI (Grupo Falck)**. +> Toda la implementación CSS sigue la metodología **BEM** (`block__element--modifier`) +> y un enfoque **mobile-first responsive**. + +--- + +## 1. Filosofía de diseño + +EMI es una marca de **atención médica de urgencia 24/7**. El sistema visual debe transmitir: + +| Atributo | Cómo se traduce en UI | +| --------------- | ------------------------------------------------------------------ | +| **Confianza** | Tipografía sólida, alto contraste, espaciado generoso | +| **Urgencia** | Rojo coral como acento dominante en CTAs | +| **Profesional** | Vinotinto profundo (burgundy) como color institucional | +| **Cercanía** | Bordes redondeados (`pill` shape en botones), iconografía amable | +| **Accesible** | Contrastes AA/AAA, áreas táctiles ≥ 44×44px, foco visible | + +**Regla de oro**: el rojo coral se usa con moderación, **solo para acciones primarias**. El vinotinto domina secciones institucionales. El blanco respira. + +--- + +## 2. Paleta de colores + +### 2.1. Colores de marca (Brand) + +| Token | HEX | RGB | Uso | +| ------------------------ | --------- | ---------------- | ---------------------------------------------- | +| `--color-brand-primary` | `#E63946` | `230, 57, 70` | CTAs principales, botones "Conoce más" | +| `--color-brand-primary-hover` | `#C92A3B` | `201, 42, 59` | Hover state del CTA primario | +| `--color-brand-primary-active` | `#A8202F` | `168, 32, 47` | Active/pressed state | +| `--color-brand-secondary` | `#6B1F2A` | `107, 31, 42` | Vinotinto institucional, top bar, fondos | +| `--color-brand-secondary-dark` | `#4D1620` | `77, 22, 32` | Variante oscura del vinotinto | +| `--color-brand-accent` | `#D32F2F` | `211, 47, 47` | Rojo del logo, detalles iconográficos | + +### 2.2. Colores de soporte (Support) + +| Token | HEX | Uso | +| ---------------------- | --------- | ------------------------------------------------ | +| `--color-success` | `#25D366` | Botón WhatsApp, estados completados | +| `--color-success-dark` | `#1EA952` | Hover del verde | +| `--color-warning` | `#F59E0B` | Alertas no críticas | +| `--color-danger` | `#DC2626` | Errores, eliminaciones, validaciones falladas | +| `--color-info` | `#0EA5E9` | Mensajes informativos | + +### 2.3. Neutrales (Scale) + +| Token | HEX | Uso típico | +| ------------------ | --------- | ----------------------------------------- | +| `--color-white` | `#FFFFFF` | Fondos de cards, textos sobre vinotinto | +| `--color-gray-50` | `#F9FAFB` | Fondo de página, secciones suaves | +| `--color-gray-100` | `#F3F4F6` | Hovers sutiles, dividers | +| `--color-gray-200` | `#E5E7EB` | Bordes de inputs, separadores | +| `--color-gray-300` | `#D1D5DB` | Bordes activos, placeholders | +| `--color-gray-400` | `#9CA3AF` | Texto deshabilitado, iconos secundarios | +| `--color-gray-500` | `#6B7280` | Texto secundario, labels | +| `--color-gray-600` | `#4B5563` | Texto de cuerpo | +| `--color-gray-700` | `#374151` | Texto principal en fondos claros | +| `--color-gray-800` | `#1F2937` | Headings sobre blanco | +| `--color-gray-900` | `#111827` | Texto de máximo contraste | + +### 2.4. Tokens semánticos + +> Estos tokens **referencian** los anteriores. Los componentes consumen estos, no los HEX directos. + +```css +:root { + /* Backgrounds */ + --bg-page: var(--color-gray-50); + --bg-surface: var(--color-white); + --bg-elevated: var(--color-white); + --bg-inverse: var(--color-brand-secondary); + --bg-overlay: rgba(17, 24, 39, 0.6); + + /* Text */ + --text-primary: var(--color-gray-900); + --text-secondary: var(--color-gray-600); + --text-muted: var(--color-gray-400); + --text-inverse: var(--color-white); + --text-link: var(--color-brand-primary); + --text-link-hover: var(--color-brand-primary-hover); + + /* Borders */ + --border-subtle: var(--color-gray-200); + --border-default: var(--color-gray-300); + --border-strong: var(--color-gray-400); + --border-focus: var(--color-brand-primary); + + /* States */ + --state-error: var(--color-danger); + --state-success: var(--color-success); + --state-warning: var(--color-warning); +} +``` + +### 2.5. Dark mode (opcional, recomendado) + +```css +@media (prefers-color-scheme: dark) { + :root { + --bg-page: #0F0A0B; + --bg-surface: #1A1214; + --bg-elevated: #241719; + --text-primary: #F5F5F5; + --text-secondary: #B8B8B8; + --border-subtle: #2D1F22; + /* El rojo y vinotinto se mantienen, son colores de marca */ + } +} +``` + +--- + +## 3. Tipografía + +### 3.1. Stack de fuentes + +```css +:root { + /* Display: para titulares grandes (Hero) — peso fuerte, condensada */ + --font-display: 'Sora', 'Plus Jakarta Sans', system-ui, sans-serif; + + /* Body: legibilidad para párrafos y UI */ + --font-body: 'Inter', 'Segoe UI', system-ui, -apple-system, sans-serif; + + /* Mono: código, números tabulares */ + --font-mono: 'JetBrains Mono', 'Fira Code', Menlo, monospace; +} +``` + +> **Nota**: Sora se usa porque tiene el peso visual robusto que se observa en los titulares de EMI ("Somos atención médica…"). Si se requiere licencia comercial sin Google Fonts, alternativa: **Manrope** o **Plus Jakarta Sans**. + +### 3.2. Escala tipográfica (mobile-first, escala fluida) + +| Token | Mobile | Desktop | Uso | +| ---------------- | ------ | ------- | ---------------------------------- | +| `--fs-xs` | 12px | 12px | Captions, labels pequeños | +| `--fs-sm` | 14px | 14px | Texto secundario, helper text | +| `--fs-base` | 16px | 16px | Cuerpo de texto (default) | +| `--fs-md` | 18px | 18px | Subtítulos, lead paragraph | +| `--fs-lg` | 20px | 24px | H4, destacados | +| `--fs-xl` | 24px | 32px | H3 | +| `--fs-2xl` | 30px | 40px | H2 (ej: "Nuestros planes") | +| `--fs-3xl` | 36px | 56px | H1 hero (ej: "Somos atención…") | +| `--fs-4xl` | 44px | 72px | Display, posters | + +```css +:root { + --fs-xs: 0.75rem; + --fs-sm: 0.875rem; + --fs-base: 1rem; + --fs-md: 1.125rem; + --fs-lg: clamp(1.25rem, 1rem + 1vw, 1.5rem); + --fs-xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); + --fs-2xl: clamp(1.875rem, 1.5rem + 2vw, 2.5rem); + --fs-3xl: clamp(2.25rem, 1.8rem + 3vw, 3.5rem); + --fs-4xl: clamp(2.75rem, 2rem + 4vw, 4.5rem); +} +``` + +### 3.3. Pesos + +```css +:root { + --fw-regular: 400; + --fw-medium: 500; + --fw-semibold: 600; + --fw-bold: 700; + --fw-extrabold: 800; /* Para hero titles */ +} +``` + +### 3.4. Line heights + +```css +:root { + --lh-tight: 1.1; /* Headings grandes */ + --lh-snug: 1.25; /* Headings medianos */ + --lh-normal: 1.5; /* Body */ + --lh-relaxed: 1.65; /* Párrafos largos */ +} +``` + +### 3.5. Letter spacing + +```css +:root { + --tracking-tighter: -0.04em; /* Hero titles */ + --tracking-tight: -0.02em; /* H2-H3 */ + --tracking-normal: 0; /* Body */ + --tracking-wide: 0.05em; /* Eyebrows, uppercase labels */ +} +``` + +--- + +## 4. Espaciado (Spacing Scale) + +Sistema basado en **múltiplos de 4px**, alineado con buenas prácticas de Material/Tailwind: + +```css +:root { + --space-0: 0; + --space-1: 0.25rem; /* 4px */ + --space-2: 0.5rem; /* 8px */ + --space-3: 0.75rem; /* 12px */ + --space-4: 1rem; /* 16px */ + --space-5: 1.25rem; /* 20px */ + --space-6: 1.5rem; /* 24px */ + --space-8: 2rem; /* 32px */ + --space-10: 2.5rem; /* 40px */ + --space-12: 3rem; /* 48px */ + --space-16: 4rem; /* 64px */ + --space-20: 5rem; /* 80px */ + --space-24: 6rem; /* 96px */ + --space-32: 8rem; /* 128px */ +} +``` + +**Regla de aplicación**: +- Espaciado **dentro** de un componente (padding interno): `--space-2` a `--space-6`. +- Espaciado **entre** componentes: `--space-8` a `--space-16`. +- Espaciado **entre secciones**: `--space-20` a `--space-32`. + +--- + +## 5. Radios de borde + +```css +:root { + --radius-none: 0; + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-2xl: 24px; + --radius-pill: 9999px; /* Botones tipo "Conoce más" */ + --radius-full: 50%; +} +``` + +> **Identidad EMI**: los CTAs primarios usan `--radius-pill` (forma de cápsula). Es un sello distintivo de la marca y debe respetarse. + +--- + +## 6. Sombras + +```css +:root { + --shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05); + --shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1); + + /* Sombra de marca (tinte rojo sutil) para CTAs elevados */ + --shadow-brand: 0 10px 25px -5px rgba(230, 57, 70, 0.35); +} +``` + +--- + +## 7. Breakpoints responsive (Mobile-first) + +```css +:root { + --bp-xs: 360px; /* Mobile pequeño */ + --bp-sm: 480px; /* Mobile estándar */ + --bp-md: 768px; /* Tablet */ + --bp-lg: 1024px; /* Desktop pequeño */ + --bp-xl: 1280px; /* Desktop estándar */ + --bp-2xl: 1536px; /* Desktop grande */ +} +``` + +### Convención de uso (mobile-first SIEMPRE): + +```scss +.task-list { + // Default: mobile + display: grid; + grid-template-columns: 1fr; + gap: var(--space-4); + + // Tablet en adelante + @media (min-width: 768px) { + grid-template-columns: repeat(2, 1fr); + gap: var(--space-6); + } + + // Desktop en adelante + @media (min-width: 1024px) { + grid-template-columns: repeat(3, 1fr); + gap: var(--space-8); + } +} +``` + +**Regla estricta**: nunca usar `max-width` como base. Siempre escalar **de pequeño a grande**. + +### Container widths + +```css +:root { + --container-sm: 640px; + --container-md: 768px; + --container-lg: 1024px; + --container-xl: 1200px; + --container-2xl: 1440px; +} + +.container { + width: 100%; + max-width: var(--container-xl); + margin-inline: auto; + padding-inline: var(--space-4); + + @media (min-width: 768px) { + padding-inline: var(--space-8); + } +} +``` + +--- + +## 8. Z-index Scale + +```css +:root { + --z-base: 0; + --z-dropdown: 100; + --z-sticky: 200; + --z-fixed: 300; + --z-modal-backdrop: 400; + --z-modal: 500; + --z-popover: 600; + --z-tooltip: 700; + --z-toast: 800; + --z-floating-cta: 900; /* Botón flotante WhatsApp */ +} +``` + +--- + +## 9. Transiciones y animaciones + +```css +:root { + --transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1); + --transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1); + + /* Easings nombrados */ + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); +} +``` + +**Accesibilidad**: respetar siempre `prefers-reduced-motion`: + +```css +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} +``` + +--- + +## 10. Metodología BEM — Convenciones obligatorias + +### 10.1. Estructura + +``` +.block { } /* Bloque independiente */ +.block__element { } /* Parte interna del bloque */ +.block--modifier { } /* Variante del bloque */ +.block__element--modifier { } /* Variante de un elemento */ +``` + +### 10.2. Reglas + +- **No anidar selectores** en CSS más de 1 nivel (el nombre BEM ya da la jerarquía). +- **No usar selectores de tipo** (`div`, `span`) dentro de un bloque BEM. +- **Nombres en kebab-case**: `task-card__action-button--disabled`. +- **Un componente = un bloque**: si reutilizas algo, conviértelo en su propio bloque. +- **Modifiers booleanos**: `--active`, `--disabled`, `--loading`. +- **Modifiers de valor**: `--size-lg`, `--variant-primary`. + +### 10.3. Ejemplo correcto + +```html +
+
+

Revisar contratos

+ Completado +
+

Lorem ipsum…

+ +
+``` + +```scss +.task-card { + background-color: var(--bg-surface); + border-radius: var(--radius-lg); + padding: var(--space-4); + box-shadow: var(--shadow-sm); + transition: box-shadow var(--transition-base); + + &--completed { + opacity: 0.7; + border-left: 4px solid var(--color-success); + } + + &:hover { + box-shadow: var(--shadow-md); + } +} + +.task-card__header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: var(--space-3); +} + +.task-card__title { + font-family: var(--font-display); + font-size: var(--fs-lg); + font-weight: var(--fw-semibold); + color: var(--text-primary); + margin: 0; +} + +.task-card__badge { + font-size: var(--fs-xs); + padding: var(--space-1) var(--space-2); + border-radius: var(--radius-pill); + + &--success { + background-color: var(--color-success); + color: var(--color-white); + } +} + +.task-card__description { + font-size: var(--fs-sm); + color: var(--text-secondary); + line-height: var(--lh-normal); +} + +.task-card__footer { + display: flex; + gap: var(--space-2); + margin-top: var(--space-4); + + @media (min-width: 768px) { + justify-content: flex-end; + } +} + +.task-card__action { + padding: var(--space-2) var(--space-4); + border-radius: var(--radius-md); + font-size: var(--fs-sm); + font-weight: var(--fw-medium); + cursor: pointer; + transition: background-color var(--transition-fast); + + &--edit { + background-color: var(--color-brand-primary); + color: var(--color-white); + + &:hover { + background-color: var(--color-brand-primary-hover); + } + } + + &--delete { + background-color: transparent; + color: var(--color-danger); + border: 1px solid var(--color-danger); + + &:hover { + background-color: var(--color-danger); + color: var(--color-white); + } + } +} +``` + +### 10.4. Lo que NUNCA debe hacerse + +```scss +/* ❌ MAL: anidamiento excesivo */ +.task-card { + .header { + .title { ... } + } +} + +/* ❌ MAL: selector de tipo */ +.task-card h3 { ... } + +/* ❌ MAL: doble guion bajo encadenado */ +.task-card__header__title { ... } + +/* ❌ MAL: modifier sin bloque padre */ +.--disabled { ... } +``` + +--- + +## 11. Componentes base (referencia de marca EMI) + +### 11.1. Botón primario (CTA "Conoce más" / "Afíliate en línea") + +```html + +``` + +```scss +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + font-family: var(--font-body); + font-weight: var(--fw-semibold); + border: none; + cursor: pointer; + transition: all var(--transition-base); + text-decoration: none; + min-height: 44px; /* Accesibilidad: área táctil mínima */ + + &:focus-visible { + outline: 2px solid var(--border-focus); + outline-offset: 2px; + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + + /* Variantes */ + &--primary { + background-color: var(--color-brand-primary); + color: var(--color-white); + border-radius: var(--radius-pill); + box-shadow: var(--shadow-brand); + + &:hover:not(:disabled) { + background-color: var(--color-brand-primary-hover); + transform: translateY(-1px); + } + } + + &--outline { + background-color: transparent; + color: var(--color-brand-primary); + border: 2px solid var(--color-brand-primary); + border-radius: var(--radius-pill); + + &:hover:not(:disabled) { + background-color: var(--color-brand-primary); + color: var(--color-white); + } + } + + &--whatsapp { + background-color: var(--color-success); + color: var(--color-white); + border-radius: var(--radius-pill); + + &:hover:not(:disabled) { + background-color: var(--color-success-dark); + } + } + + /* Tamaños */ + &--sm { padding: var(--space-2) var(--space-4); font-size: var(--fs-sm); } + &--md { padding: var(--space-3) var(--space-6); font-size: var(--fs-base); } + &--lg { padding: var(--space-4) var(--space-8); font-size: var(--fs-md); } +} +``` + +### 11.2. Input de formulario + +```html +
+ + + Este campo es obligatorio +
+``` + +```scss +.form-field { + display: flex; + flex-direction: column; + gap: var(--space-2); + margin-bottom: var(--space-4); + + &--error { + .form-field__input { + border-color: var(--state-error); + } + } + + &__label { + font-size: var(--fs-sm); + font-weight: var(--fw-medium); + color: var(--text-secondary); + } + + &__input { + padding: var(--space-3) var(--space-4); + font-size: var(--fs-base); + font-family: var(--font-body); + border: 1px solid var(--border-default); + border-radius: var(--radius-md); + background-color: var(--bg-surface); + color: var(--text-primary); + transition: border-color var(--transition-fast); + min-height: 44px; + + &:focus { + outline: none; + border-color: var(--border-focus); + box-shadow: 0 0 0 3px rgba(230, 57, 70, 0.15); + } + } + + &__error { + font-size: var(--fs-xs); + color: var(--state-error); + } +} +``` + +### 11.3. Card + +```scss +.card { + background-color: var(--bg-surface); + border-radius: var(--radius-lg); + padding: var(--space-4); + box-shadow: var(--shadow-sm); + + @media (min-width: 768px) { + padding: var(--space-6); + } + + &--elevated { + box-shadow: var(--shadow-lg); + } + + &--bordered { + border: 1px solid var(--border-subtle); + box-shadow: none; + } + + &--inverse { + background-color: var(--bg-inverse); + color: var(--text-inverse); + } +} +``` + +--- + +## 12. Accesibilidad (no negociable) + +- **Contraste mínimo AA**: 4.5:1 para texto normal, 3:1 para texto grande. +- **Foco visible**: usar `:focus-visible`, nunca remover el outline sin reemplazarlo. +- **Áreas táctiles**: mínimo 44×44 px en cualquier elemento interactivo. +- **Roles ARIA**: usar `role`, `aria-label`, `aria-describedby` cuando el elemento no sea semántico nativo. +- **Skip links**: ``. +- **Lang en el HTML**: ``. +- **Imágenes**: siempre con `alt` descriptivo o `alt=""` si son decorativas. + +--- + +## 13. Checklist antes de hacer commit + +- [ ] Todos los colores vienen de tokens CSS, **nunca hex directos** en componentes. +- [ ] Clases siguen BEM (`block__element--modifier`). +- [ ] Sin selectores anidados más allá de 1 nivel. +- [ ] Mobile-first: el CSS por defecto es para mobile, los `@media` escalan hacia arriba. +- [ ] Botones interactivos tienen `:hover`, `:focus-visible`, `:disabled`. +- [ ] Áreas táctiles ≥ 44px de altura. +- [ ] `prefers-reduced-motion` respetado en animaciones. +- [ ] Contraste verificado con WebAIM Contrast Checker. +- [ ] Layout probado en 360px, 768px, 1024px, 1440px. +- [ ] Sin `!important` salvo en utilidades. + +--- + +## 14. Estructura recomendada de archivos SCSS + +``` +src/styles/ +├── abstracts/ +│ ├── _variables.scss # CSS custom properties (tokens) +│ ├── _mixins.scss # Mixins (responsive, focus, etc.) +│ └── _functions.scss +├── base/ +│ ├── _reset.scss # Normalize / reset moderno +│ ├── _typography.scss # Estilos base de h1-h6, p, etc. +│ └── _global.scss # html, body, * +├── components/ +│ ├── _button.scss +│ ├── _card.scss +│ ├── _form-field.scss +│ └── _task-card.scss +├── layouts/ +│ ├── _header.scss +│ ├── _footer.scss +│ └── _container.scss +├── utilities/ +│ └── _helpers.scss # .sr-only, .visually-hidden, etc. +└── main.scss # Punto de entrada +``` + +--- + +## 15. Mixin útil para media queries + +```scss +// abstracts/_mixins.scss +@mixin respond-to($breakpoint) { + @if $breakpoint == sm { + @media (min-width: 480px) { @content; } + } @else if $breakpoint == md { + @media (min-width: 768px) { @content; } + } @else if $breakpoint == lg { + @media (min-width: 1024px) { @content; } + } @else if $breakpoint == xl { + @media (min-width: 1280px) { @content; } + } +} + +// Uso: +.task-list { + grid-template-columns: 1fr; + + @include respond-to(md) { + grid-template-columns: repeat(2, 1fr); + } + + @include respond-to(lg) { + grid-template-columns: repeat(3, 1fr); + } +} +``` + +--- + +**Última actualización**: 2026-05-13 +**Mantenedor**: equipo de frontend +**Versión**: 1.0.0 \ No newline at end of file