NavPill
Navegación flotante en forma de píldora, único elemento de navegación global
Preview, desktop
Componente real en iframe (es position:fixed; montarlo suelto taparía esta página). Arriba a la izquierda, indicador deslizante en hover/focus, smart-hide al hacer scroll dentro del recuadro.
Preview, mobile
A <768px: abajo, centrada, siempre visible, ancho intrínseco.
Props
| Prop | Tipo | Nota |
|---|---|---|
logo | string | Requerida. Ruta al símbolo; <img> en caja fija 30×30 (evita reflow). El asset llega listo: debe ser legible sobre --nav-bg tal cual; el componente no lo re-colorea. |
items | { key, href, label, icon?, isActive }[] | Mín 2; máx: los que quepan a 375px con labels visibles. icon es SVG inline opcional. |
locale | 'es' | 'en' | Default 'es'. Labels de accesibilidad. |
homeUrl | string | Default '/'. Destino del símbolo. |
Tokens
| Propiedad | Valor | Token |
|---|---|---|
| Background | #000000, ambos modos de color (excepción deliberada: máximo contraste, invariante al tema) | --nav-bg |
| Texto | #FFFFFF | --nav-text |
| Border-radius | 16px, deriva de la escala: var(--radius-lg) | --nav-radius |
| Altura | 52px | --nav-height |
| Sombra | 0 8px 24px -4px rgba(0,0,0,0.15) | --nav-shadow |
| Tipografía | Georama 14px / 600 | --text-nav |
| Item padding | 8px 12px (desktop) / 6px 8px (móvil) | --nav-item-padding (desktop) |
| Gap entre items | space-sm (desktop) / space-xs (móvil) | n/a |
| Item radius | 8px, deriva de la escala: var(--radius-md) | --nav-item-radius |
| Fondo activo (indicador en reposo) | rgba(255,255,255,0.12) | --nav-active-bg |
| Fondo hover (indicador) | rgba(255,255,255,0.2) | --nav-hover-bg |
Estados, indicador deslizante
Un único elemento (.nav-pill__indicator) hace de highlight: los items no tienen fondo propio. Sigue al item con hover o focus y descansa sobre el item activo (aria-current="page"). Sin item activo, se oculta. Todos los items siempre a opacidad completa.
| Estado | Tratamiento |
|---|---|
| Inactivo | Texto blanco, sin fondo. Opacidad completa. |
| Hover / focus | El indicador se desliza bajo el item apuntado. |
| Activo (reposo) | El indicador descansa bajo el item con aria-current. |
| Sin item activo | Indicador oculto (opacity 0). |
El indicador se posiciona con getBoundingClientRect; salta sin transición en el primer paint y bajo prefers-reduced-motion, y se recalcula en document.fonts.ready y en resize. Al salir puntero y foco del nav, vuelve al item activo.
Posición
| Viewport | Posición | Comportamiento |
|---|---|---|
| Desktop (≥768px) | Top, izquierda | Smart scroll: se oculta al bajar (>80px), aparece al subir. translateY, 0.3s ease-out. |
| Mobile (<768px) | Bottom, centrado | Fijo, ancho intrínseco. Se desliza fuera mientras el <footer> está en pantalla (IntersectionObserver) para no tapar sus links; sin footer en la página, nunca se oculta. |
Restricciones
- Mínimo 2 items; máximo, los que quepan a 375px con todos los labels visibles (en la práctica 3–4 cortos). El invariante es el fit, no el número.
- Labels siempre visibles. Nunca solo iconos. Sin truncar, sin scroll, sin menú overflow.
- Sin dropdowns.
- Sin selector de idioma (el Footer lo maneja).
- Sin theme toggle. Sin badges ni contadores.
- El logo llega listo para la píldora: el componente nunca lo re-colorea.
Decisiones
| Decisión | Por qué |
|---|---|
| Alineado a la izquierda en desktop | Con 2-3 items, centrar deja espacio muerto |
| Se oculta al bajar (smart scroll) | Sitio narrativo: nav fijo roba espacio de lectura |
| Centrado abajo en mobile | Accesible al pulgar. Ancho auto evita estiramiento |
| Radio 16px, no pill completo | Más suave, menos aspecto de app |
| Sin opacity dimming | Items a 0.5 de opacidad parecían rotos. Background fill es más limpio. |
| Hover bg > active bg | Tres estados claros sin tocar la opacidad del texto |
| Ocultarse en el footer (móvil) | La píldora fija abajo tapaba los links del footer justo cuando el lector llegaba a ellos (promovido de elastic) |
| Máximo por fit, no por número | "Máx 3" era la estimación inicial; elastic y teresa demostraron que 4 labels cortos caben. El invariante real es caber a 375px. |
| Sin re-colorear el logo (v3) | El brightness/invert asumía iconos monocromos oscuros y destrozaba logos fotográficos (teresa necesitó un fork !important). El sitio provee el asset listo. |