---
id: programas-barrido
status: proposed
kind: refinement
sprint: 6

target:
  block: sek_c_programas
  based_on: sek-programas
  see: https://institucioneducativaseki5.sg-host.com/ideario-principios-y-valores/

evidence:
  used_in:
    - output/pages/sek-schools/residencias.html
  figma: "—"

tokens: []

behaviour:
  - El cambio de foto al abrir un apartado es un **barrido**: la nueva se descubre por encima de
    la anterior con `clip-path`, sin pasar por opacidad 0
  - Tira A de arriba abajo, tira B de abajo arriba; 760 ms con curva simétrica
  - `prefers-reduced-motion` → cambio de `src` directo

accept:
  - Durante el cambio nunca se ve el fondo de la tira
  - A mitad de la animación el barrido va por la mitad del recorrido (no con el 82 % consumido,
    que es lo que pasa con el ease expo-out del bloque)
  - Dos aperturas seguidas no apilan capas

built:
  branch:
  commit:
  notes:
---

# Cambio de foto del acordeón: barrido, no parpadeo

**Dónde**: Residencias · *Así es el día a día* (bloque `sek-programas` con acordeón).
**Afecta a**: todos los `[data-sek-programas]` con imagen sincronizada — es un cambio **global del
componente**, no de una página.

## El problema

`syncProgramasStage()` (`assets/js/sek-ui.js`) resuelve el cambio de foto con un **split + fade**:

1. la foto actual se parte por la costura y se va a **`opacity: 0`** (320 ms),
2. se cambia el `src`,
3. la nueva entra desde el lado contrario y sube a `opacity: 1`.

Entre 1 y 3 **no hay imagen**: se ve el fondo de la tira. A tamaño de módulo eso lee como un
parpadeo. Feedback literal: *"no tiene ese aspecto de flash, es más como un barrido"*.

## El gesto que se pide

Un **wipe**: la foto nueva se descubre **por encima** de la anterior, que no se va hasta que la
tapa del todo. En ningún momento se ve el fondo.

- Dirección por tira, el mismo eje que ya usa el split: la tira **A** se descubre de arriba
  abajo, la **B** de abajo arriba.
- Duración **760 ms** con una curva **simétrica** (`cubic-bezier(0.65, 0.05, 0.36, 1)`). **No vale
  el ease del bloque** (`0.16, 1, 0.3, 1`): es un expo-out y se come el 82 % del recorrido en los
  primeros 170 ms, con lo que el barrido se vuelve a leer como un corte. Medido: con la curva
  buena, a mitad de tiempo va por el 50 % del recorrido.
- `prefers-reduced-motion` → cambio de `src` directo, sin animación (como ahora).

## Implementación

Capa clonada por tira, animando `clip-path` — barata y no toca las otras dos capas de
movimiento (la convergencia va en `.sek-prog-strip__align`, el parallax en el `<img>`):

```js
var capa = base.cloneNode( false );          // hereda encuadre y tamaño de la tira
capa.removeAttribute( 'data-sek-parallax' ); // no debe moverse por su cuenta
capa.src = src;
capa.alt = '';                               // decorativa: el alt vive en la imagen de la tira
capa.style.cssText = 'position:absolute;inset:0;z-index:2;will-change:clip-path';
capa.style.transform = getComputedStyle( base ).transform;  // arranca donde está la vieja
capa.style.clipPath  = ( 0 === i ) ? 'inset(0 0 100% 0)' : 'inset(100% 0 0 0)';
swap.appendChild( capa );
void capa.offsetWidth;                       // reflow: sin esto la transición no arranca
capa.style.transition = 'clip-path 760ms cubic-bezier(0.65, 0.05, 0.36, 1)';
capa.style.clipPath   = 'inset(0)';
// al terminar: base.src = src y se retira la capa (transitionend + timeout de red de seguridad)
```

Detalles que costaron:

- **`void capa.offsetWidth`, no `requestAnimationFrame`**: con rAF la animación se queda esperando
  si la pestaña no está en primer plano y el barrido se dispara tarde, ya con la limpieza en curso.
- **El ease manda tanto como la duración**: con el expo-out del bloque, dev y yo veíamos "no hay
  barrido" aun estando la animación corriendo — el recorrido se consumía antes de percibirse.
- **Una sola capa por tira**: si se pulsan dos apartados seguidos, la anterior se retira antes de
  crear la nueva; si no, se apilan.
- **Las dos tiras arrancan con la misma foto**. Son una imagen partida en dos, y si nacen con
  fotos distintas el barrido solo ocurre en una de ellas.
- El `alt` real pasa a la imagen de la tira A al cerrar; la B es decorativa y va sin `alt`.

## Estado en la maqueta

Va como **prototipo** en `preview/_staging/lib.mjs` (`barridoImagen`, aplicado desde `pulido`).
Para no pelearse con el swap del theme, la clave del apartado viaja en `data-sek-ui-imagen` en
vez de `data-sek-programas-image` — el theme solo llama a `syncProgramasStage` si ve el suyo, y
el acordeón sigue abriéndolo `data-sek-accordion`. En dev esto no hace falta: es sustituir el
cuerpo de `syncProgramasStage` y el atributo se queda como está.
