uix(navigation-menu): el panel sube como lo que es, y la caja gris del hover era el <li>

Dos correcciones del autor sobre ae9e277fa, y en las dos había leído mal.

1 · LA APARICIÓN. Había resuelto el doble de entrada dejando ganar el pop de
receta (slide+scale+fade, --duration-fast) sobre la firma `present-rise` — el
precedente del tooltip. El autor: «es un emerge». Y lo es: el megamenú es una
aparición con marco propio (cap. 26 §5), y su entrada es la firma de la
familia estampada en el CONTENT, exactamente como dropdown-menu, context-menu y
popover, cuyas recetas no escriben NINGUNA `animation:` al abrir. El pop del
tamaño de un tooltip bajo un panel de mega-menú se va; la subida que el menú
tenía al aparecer —la que hasta ayer corría sobre el botón equivocado— vuelve,
sobre el panel. El viaje lateral entre pestañas (`data-motion`) se queda y posee
el eje durante el hold: un cambio entre hermanos es un traslado, no una
aparición, y motion.md ya dice que el movimiento coordinado manda sobre la firma
(«when both write `animation` on one node»). Medido por registro de
animationstart/end: abrir = `present-rise` 314 ms, nada más; cambiar de pestaña
= `slide-axis-x` 116 ms en los dos sentidos, sin subida; el botón, nada.

2 · LA CAJA DEL HOVER. «Sigue apareciendo la misma mierda de hover.» Y seguía:
mi sonda leía `getComputedStyle` del TRIGGER (transparente, sin velo) y daba el
hover por arreglado, mientras la caja gris que el autor veía era el `<li>`. El
morfo declaraba `archetype: 'item'` en `Item`, y ese arquetipo trae de
`archetypes.css` el tratamiento de FILA de menú —cursor, sin selección de texto,
suelo de altura y el velo `--state-hover` al pasar— bajo cada palabra de la
barra. La ley ya estaba escrita (Timeline 2026-06-23, Breadcrumb): un `<li>`
envoltorio no lleva arquetipo de ítem, el interactivo es el Trigger/Link que
contiene, y se OMITE, no se neutraliza en la receta. Omitido.

El autor eligió para el hover: cambio de color del texto y nada más — la
escalera exacta de `Tabs` (reposo `--color-content-secondary`, hover y activo
`--color-content-primary`), publicada como `--navigation-menu-trigger-color{,
-hover,-active}`; la caja queda para UN estado, abierto / aria-current
(`--navigation-menu-trigger-bg-active`; el `-fg-active` de ae9e277fa se funde en
`-color-active`). Medido: trigger en reposo secundario, hover primario con
`background-image: none` y fondo transparente, `<li>` sin velo ni cursor, el
abierto con su acento; captura con Pricing en hover y Solutions abierto.

Lección de instrumento, y va a memoria: medir el nodo que el usuario SEÑALA, no
el que el código modifica.

Gates: check 72 con CERO en los ficheros tocados · morfo+soma nav+sema 550/550 ·
eidos 434/435 (skin-media-player, ajeno) · eidos-lint 31/0 invalid ·
component:audit PASS · rtl 0 · docs 0/640 · generated/base.css ±4 líneas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
alpha-0.1-background
dev 2 months ago
parent ae9e277fad
commit 822b78cf2c

@ -72,7 +72,8 @@ pasan a la escala de control. Medido 2026-08-19, antes → después: altura
| `--navigation-menu-trigger-font-size` · `-line-height` | tipografía del trigger (la barra la hereda) |
| `--navigation-menu-trigger-padding-inline` | padding inline |
| `--navigation-menu-trigger-radius` | radio del cromo de barra |
| `--navigation-menu-trigger-bg-active` · `-fg-active` | acento de ABIERTO / `aria-current="page"` |
| `--navigation-menu-trigger-color` · `-hover` · `-active` | la escalera de texto de `Tabs`: reposo secundario, hover y activo primario |
| `--navigation-menu-trigger-bg-active` | la caja del ABIERTO / `aria-current="page"` — el único estado con relleno |
| `--navigation-menu-content-z` | banda z del panel |
Un tema decide la forma en una línea —`--navigation-menu-trigger-radius:
@ -82,12 +83,24 @@ clavado a `--radius-default`, la altura salía de `padding-block:
var(--space-2)` (un token de ESPACIO como altura de CONTROL, sólo `md` en la
escala por casualidad) y el relleno de hover/abierto estaba escrito a mano.
**Hover = la capa de estado canónica**, no un token: `archetypes.css` ya la
pinta sobre todo `trigger` (`--state-hover`, recipe-contract §2 R-4.3). La
receta la mataba con el shorthand `background`, que resetea
`background-image` — medido: `background-image: none` en hover, y el trigger
ABIERTO sin ninguna respuesta al puntero. Con `background-color` en el acento,
la capa compone encima y los dos responden.
**Hover = cambio de color del texto y nada más** (firmado 2026-08-19): las
palabras de una barra de navegación no son una fila de botones — la lectura de
`anchor-nav` y `Tabs`. Ni el relleno a mano que había
(`--color-surface-overlay`), ni el velo del arquetipo `trigger`; y el `<li>`
deja de declarar `archetype: 'item'`, porque ese arquetipo trae el tratamiento
de FILA de menú (`archetypes.css`) y era él —no el trigger— quien pintaba la
caja gris bajo cada palabra (medido: fondo del trigger transparente, velo en
el `<li>`). Misma ley que Breadcrumb y Timeline: un envoltorio no lleva
arquetipo de ítem. La caja es de UN solo estado: abierto / `aria-current`.
**Al abrir, el panel sube**: es un `emerge`, y su aparición es la firma de la
familia (`present-rise`, `--duration-slow`) estampada en el CONTENT —como
dropdown-menu, context-menu y popover, cuyas recetas no escriben ninguna
entrada propia. La receta traía un pop de `--duration-fast`
(slide+scale+fade) del tamaño de un tooltip; fuera. El viaje lateral entre
pestañas (`data-motion`) se queda y POSEE el eje durante el hold (motion.md,
«when both write `animation` on one node»): medido, abrir = una subida de
314 ms; cambiar = un deslizamiento de 116 ms, sin subida.
## Comparativa

@ -19,8 +19,9 @@
* the density-SPACE scale (measured 33.5 / 36 / 41 px before 2026-08-19), and
* a `--_radius` pinned to `--radius-default` was unreachable by any theme
* short of moving the whole system's radius. Public too:
* `--navigation-menu-trigger-{bg,fg}-active` (the open / aria-current accent).
* The neutral hover is the state layer `archetypes.css` gives every trigger.
* `--navigation-menu-trigger-color{,-hover,-active}` (the text ladder, Tabs'
* exact one) and `--navigation-menu-trigger-bg-active` (the open /
* aria-current box). Hover is a change of colour and nothing else.
*
* Distinct from dropdown-menu (floating click-driven) and menubar
* (roving tabindex bar). NavigationMenu is an INLINE list that
@ -94,7 +95,7 @@
border: none;
border-radius: var(--navigation-menu-trigger-radius);
background-color: transparent;
color: var(--color-content-primary);
color: var(--navigation-menu-trigger-color);
font: inherit;
line-height: var(--navigation-menu-trigger-line-height);
text-decoration: none;
@ -106,25 +107,33 @@
color var(--duration-fast) var(--ease-default);
}
/* NO hover rule of its own. Both parts carry `data-archetype='trigger'`, and
* `archetypes.css` already paints the canonical state layer on hover
* (`background-image: linear-gradient(var(--state-hover), …)`, R-4.3). The
* rule that lived here — `background: var(--color-surface-overlay)` — used the
* SHORTHAND, which resets the `background-image` longhand: measured 2026-08-19,
* `background-image: none` on hover, the layer dead and a hand-picked surface in
* its place. The ACTIVE accent below uses `background-color` for the same
* reason — so the layer composes OVER it and an open trigger still answers the
* pointer (before: no hover feedback at all on the open trigger). */
/* Hover = a change of COLOUR, nothing else (author's call, 2026-08-19). Both
* parts carry `data-archetype='trigger'`, so `archetypes.css` would paint its
* state layer on hover — a veil that drew a box under every word of the bar;
* the recipe that lived here drew a solid one (`background:
* var(--color-surface-overlay)`). Neither: the words of a navigation bar are
* not a row of buttons (the `anchor-nav` and `Tabs` reading — resting
* secondary, hover primary). `background-image: none` retires the archetype's
* veil — its rule is `:where()`-wrapped, so any recipe rule wins. The box is
* ONE state's: open / `aria-current`. */
[data-navigation-menu-trigger]:hover:not([data-disabled]),
[data-navigation-menu-link]:hover:not([data-disabled]) {
background-image: none;
color: var(--navigation-menu-trigger-color-hover);
}
[data-navigation-menu-trigger][data-state='open'],
[data-navigation-menu-trigger][data-state='open']:hover {
background-color: var(--navigation-menu-trigger-bg-active);
color: var(--navigation-menu-trigger-fg-active);
background-image: none;
color: var(--navigation-menu-trigger-color-active);
}
[data-navigation-menu-link][aria-current='page'],
[data-navigation-menu-link][data-active] {
background-color: var(--navigation-menu-trigger-bg-active);
color: var(--navigation-menu-trigger-fg-active);
background-image: none;
color: var(--navigation-menu-trigger-color-active);
}
[data-navigation-menu-trigger]:focus-visible,
@ -256,24 +265,16 @@
--motion-distance-xl: var(--space-3);
}
/* The panel's OWN entrance, and it must win over the global `present-rise`
* signature — tooltip's precedent, measured there on 2026-08-13 and re-measured
* here on 2026-08-19 when `emerge-open` moved onto the content (it used to land
* on the trigger, where nothing competed). Both rules set the `animation`
* shorthand on the same node at equal specificity, so the firma was winning
* during the hold and the recipe's entrance then started FRESH the moment the
* stamp came off: the panel entered TWICE — 320 ms of `present-rise`, then
* 120 ms of slide+scale+fade. The second selector adds one attribute (0,3,0 vs
* the firma's 0,2,0) so the authored, direction-aware entrance is the only one
* that runs. `present-rise` is the generic fallback for a surface that designs
* no entrance of its own; this one designs it. */
[data-navigation-menu-content][data-state='open'],
[data-navigation-menu-content][data-state='open'][data-event-phase='active'] {
animation:
slide-from-top var(--duration-fast) var(--ease-out),
scale-in var(--duration-fast) var(--ease-out),
fade-in var(--duration-fast) var(--ease-out);
}
/* NO entrance of its own on open. The panel is an `emerge`: its appearance is
* the family's firma — `present-rise` (rise + fade, `--duration-slow`) — stamped
* on the content by `emerge-open`, the same way dropdown-menu, context-menu and
* popover enter (none of their recipes writes an `animation:` for open). The
* hand-rolled `slide-from-top + scale-in + fade-in` at `--duration-fast` that
* lived here was a tooltip-sized pop under a mega-menu, and once the stamp
* moved onto the content (2026-08-19) it ran a SECOND time after the firma.
* The author's call: the appearance the mega-menu had is the rise; keep it,
* on the right node. The `data-motion` swap rules below stay — a swap is a
* lateral travel between siblings, not an appearance. */
[data-navigation-menu-content][data-state='closed'] {
opacity: 0;
@ -283,13 +284,25 @@
/* `data-motion` — soma writes 'from-start' / 'from-end' / 'to-start' /
* 'to-end' when the active item swaps to a sibling. The directional
* slide (M3 shared-axis pair, both directions) makes the swap feel like
* a horizontal scroll, not a flicker. */
[data-navigation-menu-content][data-motion='from-start'] {
* a horizontal scroll, not a flicker.
*
* A swap also fires `emerge-open` on the incoming panel, so the firma
* (`present-rise`) and this travel both write the `animation` shorthand on one
* node at (0,2,0) and source order let the firma win — measured 2026-08-19:
* the panel ROSE for 316 ms and only then slid. A swap is a lateral travel
* between siblings, not an appearance, and the coordinated motion owns the
* visual axis (motion.md §"when both write animation on one node"): the
* second selector of each pair adds the phase attribute (0,3,0) so the travel
* runs during the hold too, and the same value after it — no restart. The
* first open has no `data-motion`, so there the firma IS the entrance. */
[data-navigation-menu-content][data-motion='from-start'],
[data-navigation-menu-content][data-motion='from-start'][data-event-phase='active'] {
animation:
slide-axis-x-in-reverse var(--duration-fast) var(--ease-out),
fade-in var(--duration-fast) var(--ease-out);
}
[data-navigation-menu-content][data-motion='from-end'] {
[data-navigation-menu-content][data-motion='from-end'],
[data-navigation-menu-content][data-motion='from-end'][data-event-phase='active'] {
animation:
slide-axis-x-in var(--duration-fast) var(--ease-out),
fade-in var(--duration-fast) var(--ease-out);

@ -3457,8 +3457,10 @@
--navigation-menu-trigger-radius-sm: var(--size-sm-radius);
--navigation-menu-trigger-radius-md: var(--size-md-radius);
--navigation-menu-trigger-radius-lg: var(--size-lg-radius);
--navigation-menu-trigger-color: var(--color-content-secondary);
--navigation-menu-trigger-color-hover: var(--color-content-primary);
--navigation-menu-trigger-color-active: var(--color-content-primary);
--navigation-menu-trigger-bg-active: var(--color-primary-element);
--navigation-menu-trigger-fg-active: var(--color-content-primary);
--dropdown-menu-content-z: var(--z-index-overlay-floating);
--dropdown-menu-content-min-width: 12rem;
--dropdown-menu-content-max-height: min(60vh, 22rem);

@ -5188,11 +5188,16 @@ export const THEME_BASE_RECIPE_TOKENS = defineRecipes({
{ value: 'var(--navigation-menu-trigger-radius-lg)', scope: 'size:lg' }
]
},
// The ACTIVE accent — an open trigger, a link with `aria-current`. The
// neutral hover is NOT a token: it is the state layer `archetypes.css`
// already gives every `trigger` (recipe-contract §2, R-4.3).
'trigger-bg-active': 'var(--color-primary-element)',
'trigger-fg-active': 'var(--color-content-primary)'
// Text states — the `Tabs` trigger's exact ladder: resting SECONDARY,
// hover and active PRIMARY. Hover is a change of colour and NOTHING
// else (author's call, 2026-08-19): no fill, no state layer — a bar of
// navigation words is not a row of buttons, and the archetype's hover
// veil drew a box under each word. The box accent belongs to ONE state
// only: open / `aria-current`.
'trigger-color': 'var(--color-content-secondary)',
'trigger-color-hover': 'var(--color-content-primary)',
'trigger-color-active': 'var(--color-content-primary)',
'trigger-bg-active': 'var(--color-primary-element)'
},
// ─────────────────────────────────────────────────────────────────────

@ -171,9 +171,17 @@ export const navigationMenuMorfo = {
]
},
{
// NO archetype. The `<li>` is a display wrapper — the interactive part is
// the Trigger / Link inside it — and `archetype: 'item'` ships the
// menu / listbox ROW treatment from `archetypes.css`: cursor, no
// text selection, a control-height floor, and a state-layer veil on
// hover. That veil was the grey box under every word of the bar
// (measured 2026-08-19: the trigger's own background was transparent
// and the box was the `<li>` lighting up beneath it). Same reading as
// Breadcrumb's and Timeline's Item: omit the archetype, never
// neutralise it in the recipe.
name: 'Item',
kebab: 'item',
archetype: 'item',
kind: 'public',
defaultElement: 'li',
optional: false,

Loading…
Cancel
Save

Powered by TurnKey Linux.