# CalcuPet Design System

CalcuPet es una herramienta estática, rápida y clara para estimar necesidades diarias de perros y gatos. Este Design System define las reglas visuales, técnicas y de interacción que mantienen la interfaz consistente sin agregar dependencias ni frameworks.

## Principios

El principio rector es consistencia. Cada pantalla debe sentirse simple, profesional, accesible y confiable. La calculadora es la acción principal y debe aparecer temprano, con un hero breve, controles claros y resultados legibles. La interfaz se construye Mobile First, usa CSS modular, JavaScript modular y no depende de servicios externos para renderizar o calcular.

## Estrategia Técnica

El stack permitido es HTML5, CSS3 y JavaScript puro. CSS se organiza por módulos: `base.css`, `layout.css`, `components.css`, `forms.css`, `results.css` y `utilities.css`. La metodología de clases es BEM: bloques como `button`, `field`, `card`, `result-card`, `empty-state`, `toast`, `modal`, `table`, `skeleton` y `spinner`; elementos con `__`; modificadores con `--`. CSS Grid se usa para layouts principales y Flexbox para alineación interna.

Las unidades preferidas son `rem` para tipografía y espaciado, `%` y `fr` para layouts, y `px` solo cuando aporta precisión real, como bordes de 1 px o documentación de breakpoints. La app mantiene Mobile First con breakpoints documentados en tokens y aplicados en CSS: 768 px, 1024 px y 1280 px.

## Tokens

Los tokens viven en `:root` dentro de `css/base.css`, agrupados por categorías. Los aliases antiguos se mantienen para compatibilidad con los módulos existentes.

### Color

La paleta es pequeña y controlada. El primario es un verde natural y moderno. El secundario es un único color de apoyo cálido para enlaces destacados, avisos o énfasis puntual. La escala de grises cubre de 50 a 900.

- `--cp-color-primary-500`: verde principal.
- `--cp-color-primary-700`: verde oscuro para hover y énfasis.
- `--cp-color-surface-dark`: verde institucional oscuro para superficies protagonistas, como la tarjeta del formulario.
- `--cp-color-form-background`: alias semántico para el fondo del formulario protagonista.
- `--cp-color-on-dark`: texto blanco sobre superficies oscuras.
- `--cp-color-on-dark-muted`: texto auxiliar sobre superficies oscuras.
- `--cp-color-error-on-dark`: mensajes de validación sobre superficies oscuras.
- `--cp-color-secondary-500`: apoyo cálido.
- `--cp-color-cta`, `--cp-color-cta-hover` y `--cp-color-cta-active`: CTA ámbar dorado para acciones principales dentro de superficies oscuras.
- `--cp-color-gray-50` a `--cp-color-gray-900`: fondos, bordes, texto y superficies.
- `--cp-color-success`, `--cp-color-warning`, `--cp-color-error`, `--cp-color-info`: estados semánticos.
- `--cp-color-background`, `--cp-color-surface`, `--cp-color-focus`: base de página, superficies y foco.

### Tipografía

CalcuPet usa una sola familia sans serif de sistema: `Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`. No se usan Google Fonts ni fuentes externas. El tamaño base es 16 px y el line-height base es 1.5.

Escala tipográfica: 14, 16, 20, 24, 32, 40 y 48 px. Pesos permitidos: 400, 600 y 700. Los títulos usan line-height ajustado; texto normal usa `--cp-line-height-base`.

En móvil, `--cp-font-size-14` equivale a 15 px y se usa como mínimo para labels, ayudas, validaciones y activadores del formulario. En escritorio vuelve a 14 px para conservar una densidad equilibrada. Las explicaciones extensas dentro de paneles educativos y la nota de alimentación mixta usan `--cp-font-size-16`, equivalente a 17 px en móvil y 16 px en escritorio. Los controles heredan la escala base y mantienen lectura cómoda sin aumentar innecesariamente su altura.

### Espaciado

La escala de espaciado sigue incrementos de 4 px:

- `--cp-space-1`: 4 px.
- `--cp-space-2`: 8 px.
- `--cp-space-3`: 12 px.
- `--cp-space-4`: 16 px.
- `--cp-space-5`: 20 px.
- `--cp-space-6`: 24 px.
- `--cp-space-8`: 32 px.
- `--cp-space-10`: 40 px.
- `--cp-space-12`: 48 px.

### Layout

El contenedor principal usa `--cp-container-max: 75rem`, equivalente a 1200 px. Los breakpoints son `--cp-breakpoint-md: 48rem`, `--cp-breakpoint-lg: 64rem` y `--cp-breakpoint-xl: 80rem`. El área táctil mínima es `--cp-touch-target: 2.75rem`, equivalente a 44 px. El formulario sigue inmediatamente al hero, sin repetir un título de sección, para reducir el desplazamiento inicial y mantener la calculadora como acción prioritaria.

### Radios, Bordes y Sombras

Radios: 4, 8, 12, 16 px y píldora. El borde estándar es 1 px. Las sombras tienen tres niveles:

- `--cp-shadow-low`: superficies elevadas suaves.
- `--cp-shadow-medium`: tarjetas importantes, toast y menús.
- `--cp-shadow-high`: modales y capas dominantes.

### Animación

Duraciones permitidas: 100 ms, 200 ms y 300 ms. Curvas: `--cp-ease-standard` para interacciones normales y `--cp-ease-emphasized` para microinteracciones puntuales. Las animaciones deben ser funcionales, sutiles y respetar `prefers-reduced-motion`.

### Z-index

La escala de capas evita valores arbitrarios: base, dropdown, sticky, toast, modal y tooltip. La interfaz actual usa la capa sticky para mantener visible el resultado en escritorio.

## Componentes

### Button

Propósito: ejecutar acciones claras. Estructura base: `.button`. Variantes: `.button--primary`, `.button--secondary`, `.button--outline`, `.button--text`. Tamaños: `.button--sm`, `.button--md`, `.button--lg`. Estados: hover, active, focus-visible, disabled y loading con `aria-busy="true"`.

Reglas de uso: el botón primario debe aparecer una sola vez por flujo principal. En superficies claras usa verde primario; dentro de superficies oscuras puede usar `--cp-color-cta` y sus variantes hover/active con texto oscuro para maximizar contraste. Acciones secundarias usan secondary u outline. Acciones de baja prioridad usan text. Todo botón debe conservar área táctil mínima de 44 x 44 px.

El botón secundario «Copiar resultado» integra una confirmación transitoria `.copy-button__feedback` con icono y texto «Copiado». La entrada y salida usan las duraciones del sistema y respetan `prefers-reduced-motion`; el temporizador se reinicia con clics repetidos. El anuncio accesible permanece en un `role="status"` visualmente oculto, por lo que no reserva una fila adicional ni separa el botón del disclaimer.

### BackToTopButton

Propósito: permitir regresar al inicio cuando la página tiene desplazamiento vertical. Bloque: `.back-to-top`. Usa posición fija, forma circular, `--cp-color-cta` como fondo, sombra media, foco visible y área táctil mínima de 44 x 44 px. Debe permanecer oculto al inicio y mostrarse solo después de una distancia de scroll suficiente. La acción de retorno debe respetar `prefers-reduced-motion`.

### Input

Propósito: capturar valores escritos. Se usa el estilo base compartido para `input`, `select` y `textarea`. Labels siempre visibles arriba del campo. Inputs numéricos que aceptan coma decimal deben usar `type="text"` con `inputmode="decimal"` para permitir `12.5` y `12,5`.

Estados: default, hover heredado del navegador, focus y focus-visible con borde primario y ring separado del borde, invalid con borde de error y disabled cuando aplique.

Los inputs que modifican el cálculo aplican un debounce de 250 ms. La validación automática muestra únicamente errores de campos tocados, no mueve el foco y limpia cualquier resultado desactualizado cuando el formulario deja de ser válido. El botón «Calcular» permanece como acción explícita, muestra la validación completa y puede enfocar el primer error.

### Select

Propósito: elegir una opción cerrada. Usa el mismo bloque visual de campo que input, con flecha CSS local, padding derecho amplio y sin librerías externas. Debe mantener `label` visible y opciones claras.

### Label

Propósito: nombrar el campo. Los labels son siempre visibles encima del control, con peso 700 y tamaño compacto. No se reemplazan por placeholders.

### Field

Propósito: agrupar label, control, ayuda y validación. Bloque: `.field`. Modificador actual: `.field--full` para ocupar todo el ancho en grids. Elementos documentados: `.field__label`, `.field__control`, `.field__message`.

En superficies oscuras, los labels usan `--cp-color-on-dark` y los textos de ayuda usan `--cp-color-on-dark-muted`. Los controles mantienen fondo blanco para conservar legibilidad. Las ayudas y validaciones usan `--cp-font-size-14` y line-height amplio; la separación interna se limita a `--cp-space-1` para mantener el formulario compacto.

### FoodProfile

Propósito: agrupar el tipo de alimento y su Energía Metabolizable antes de calcular. Bloque: `.food-profile`; campos: `.food-profile__fields`; nota contextual: `.food-profile__mixed-note`. En móvil los controles se apilan en orden Tipo → Energía y, desde el breakpoint medio, ocupan dos columnas en ese mismo orden. El selector inicia en «Seco» y carga referencias editables centralizadas: 3500 kcal/kg para Seco, 1000 kcal/kg para Húmedo y 2250 kcal/kg para Mixto. El campo combina `type="text"` e `inputmode="decimal"`, mantiene la unidad visible y asocia ayuda y error mediante `aria-describedby`.

El estado conserva la procedencia del valor. Un cambio de tipo reemplaza el campo con la referencia de esa categoría y lo marca como sugerido; una edición mediante `input` lo marca como específico y no se revierte por foco, validación ni cálculo. La clasificación nunca se deduce comparando números, por lo que una referencia reescrita manualmente es específica. La ayuda dinámica anuncia de forma breve el valor orientativo asociado al tipo.

La nota para «Mixto» se anuncia con `role="status"` y `aria-live="polite"`, incluye texto explícito además del acento visual y respeta `prefers-reduced-motion`. Su referencia de 2250 kcal/kg es solo un punto de partida para el producto calculado, no una combinación exacta. La ayuda `.food-guide` usa `details` y `summary`; sus rangos son exclusivamente educativos y no forman parte del cálculo. Sobre el panel blanco, el contenido, las listas y las notas usan `--cp-color-gray-800` para reforzar el contraste sin recurrir a negro puro; el `summary` conserva `--cp-color-on-dark` sobre el fondo institucional.

### ValidationMessage

Propósito: explicar un error debajo del campo. Clase actual: `.field-error`. Usa color de error y un icono textual simple generado por CSS. No debe aparecer como toast ni modal. La validación visible no se reemplaza por clamps internos.

### Card

Propósito: agrupar contenido relacionado. Bloque base: `.card`. Variantes: `.card--elevated` para más profundidad y `.card--subtle` para superficies suaves. Las cards no deben anidarse innecesariamente.

### ResultCard

Propósito: mostrar el resultado de la calculadora. Bloque: `.result-card`. Estructura: encabezado con valor principal, interpretación, lista de resultados complementarios, nota sobre la densidad del alimento, tarjeta de condición corporal, bloque complementario, acción de copiar y disclaimer. El bloque visible usa `.result-education`: es educativo, discreto, sin fondo ni sombra, y enlaza internamente a Guías Nutricionales. Su tipo y contenido se definen en `js/config.js`; `educational` se renderiza desde `js/ui.js` y `none` permite omitirlo sin modificar el cálculo. El MVP no incorpora recomendaciones comerciales ni enlaces afiliados. El valor principal debe tener la jerarquía visual más alta después del botón de cálculo. La aparición usa un fade-in sutil con desplazamiento vertical corto y debe respetar `prefers-reduced-motion`.

La lista de resultados muestra tipo de alimento, kcal/kg y gramos por día antes de los valores complementarios. `.food-result-note`, con semántica `role="note"`, muestra exclusivamente uno de dos estados: referencia sugerida para el tipo actual o densidad específica del fabricante. El primer mensaje incluye tipo y energía utilizados; el segundo identifica el dato manual sin inferirlo por su número. Usa `--cp-color-gray-800` sobre la superficie verde clara para mantener contraste AA y un tono informativo. Para «Mixto», añade la aclaración independiente de que el resultado corresponde a un solo producto.

El mensaje se genera únicamente al renderizar un cálculo válido, por lo que siempre corresponde al último valor utilizado y no se anuncia con cada pulsación. Los campos escritos esperan 250 ms; selectores y BCS actualizan inmediatamente. Si el formulario queda incompleto o inválido, `.empty-state` reemplaza el resultado anterior con un mensaje neutro. El resumen copiable reproduce la misma clasificación, incluye tipo y energía, y conserva la aclaración de un solo producto para Mixto.

### BodyConditionSelector

Propósito: permitir que el usuario indique de forma opcional la condición corporal observada. Bloque: `.bcs-selector`; opciones: `.bcs-selector__option`; estado seleccionado: `.active`. Presenta cinco botones compactos ordenados de muy delgado a obeso, con número y etiqueta visible. Cada botón usa `aria-label`, actualiza `aria-pressed` y conserva un foco visible. Solo una opción puede estar activa al mismo tiempo.

Los niveles 1 y 2 usan el token cálido del CTA, el nivel 3 usa el verde primario y los niveles 4 y 5 usan el color secundario. El estado inicial no muestra una selección; la lógica asume nivel 3 sin exigir interacción ni mostrar errores. El selector no modifica los cálculos nutricionales.

La ayuda asociada usa `.bcs-guide` sobre los elementos nativos `details` y `summary`, por lo que se abre con mouse, tacto o teclado sin JavaScript adicional. Su contenido debe explicar observaciones y palpación sin reemplazar la evaluación veterinaria.

### BodyConditionCard

Propósito: presentar el nivel BCS elegido y una interpretación breve dentro del resultado. Bloque: `.body-condition-card`; elementos: `.body-condition-card__title`, `.body-condition-card__content` y `.body-condition-card__level`. Los modificadores por nivel cambian únicamente el acento mediante tokens existentes. La tarjeta mantiene el BCS como contexto educativo separado de las estimaciones numéricas.

### Toast

Propósito: confirmar eventos transitorios. Bloque base: `.toast`. Variantes: success, warning, error e info. En la versión actual, la confirmación de copiar se mantiene inline; si crece el sistema, los toast deben usar `--cp-z-toast`.

### Modal

Propósito: bloquear temporalmente el flujo cuando sea imprescindible. Bloque: `.modal`; panel: `.modal__panel`. No debe usarse para validaciones simples ni mensajes que caben inline.

### Table

Propósito: presentar datos comparables. Estructura: `.table-responsive` envolviendo `.table`. Debe permitir desplazamiento horizontal controlado en móvil y encabezados claros.

### EmptyState

Propósito: explicar ausencia de contenido y guiar al siguiente paso. Bloque: `.empty-state`. Estructura: ilustración mínima, mensaje breve y CTA cuando corresponda. En CalcuPet se usa antes del primer cálculo.

### LoadingState

Propósito: indicar espera solo cuando sea necesario. Componentes base: `.skeleton` para contenido pendiente y `.spinner` para acciones breves. La calculadora actual no necesita loading porque calcula localmente.

### Footer

El bloque `.site-footer` se divide semánticamente en una sección estratégica y otra institucional, sin línea divisoria entre ambas. `.site-footer` aplica una única superficie mediante `background-color: var(--cp-color-form-background)`, idéntica al formulario; las secciones internas heredan ese fondo y separan sus grupos únicamente con espacio. Todos los enlaces usan texto blanco, tipografía unificada y `text-decoration: none`; la jerarquía depende de la posición y el contenido.

En móvil las columnas se apilan, primero el contenido principal y después la información institucional. Desde `--cp-breakpoint-md`, el footer usa dos columnas: ambas conservan enlaces verticales y la columna principal dispone de mayor ancho visual. Todos los enlaces mantienen `--cp-touch-target`, foco visible y `text-decoration: none`; hover y active se comunican con una transición de 300 ms y opacidad. Las rutas del footer corresponden a páginas HTML reales y no requieren JavaScript. No se añaden tokens nuevos.

## Iconografía e Ilustraciones

La iconografía debe ser local, simple y consistente. Se prefieren SVG propios guardados en `assets/icons`. Las ilustraciones son minimalistas, de bajo peso y se usan solo en puntos clave, como el hero y el estado vacío. No se usan imágenes externas para la operación de la calculadora.

## Accesibilidad

El objetivo es WCAG 2.2 AA. Todo control debe tener label visible. El focus ring debe ser claro, consistente y separado del borde. El área táctil mínima es 44 x 44 px. Los mensajes de error viven debajo del campo correspondiente y el primer control inválido recibe foco al enviar. El recálculo automático nunca desplaza el foco ni inicia scroll; el debounce modera las actualizaciones del área `aria-live`. Las ayudas desplegables y el selector BCS deben ser usables con teclado. Los mensajes contextuales dinámicos usan `aria-live`. Los enlaces usan color de acento y cambio visual en hover/focus.

## Reglas De Uso

No crear variantes nuevas si una existente resuelve el caso. No usar más colores fuera de tokens. No usar sombras fuera de escala. No agregar fuentes externas. No introducir frameworks. No aumentar el peso visual si una línea, borde o espaciado preciso resuelve el problema. La calculadora y el resultado siempre tienen prioridad sobre contenido editorial.
