Grid Workshop

Card de producto

Card de comic semántica y accesible: un <a> stretch-link cubre toda la card, con botones de favoritos y carrito que capturan su propio clic por encima del enlace.

Portada de The Shadow número 1 The Shadow #1

The Shadow

Guión: W. Gibson Arte: A. Reyes

Noir

$12.99

Cómo funciona y cómo implementarlo

Cómo funciona

Stretch link — un <a> con position:absolute; inset:0 cubre toda la card. El aria-label describe el destino; los lectores de pantalla lo anuncian sin ambigüedad.
Botones con z-index superior — favoritos y carrito tienen z-index:2 (el link tiene z-index:1), por lo que capturan su propio clic sin propagarlo al enlace.
HTML válido — el <article> es el contenedor; el link vive dentro como primer hijo absoluto. No se anidan <a> ni otro <article>.
aria-pressed en favoritos — el botón de corazón es un toggle; aria-pressed comunica el estado a tecnologías asistivas.

Implementación

1. Marcado — poné el <a class="card__link"> como primer hijo del <article>, seguido de portada, botones y cuerpo.
2. CSS del stretch link — .card__link { position:absolute; inset:0; z-index:1; } y la card position:relative.
3. Botones sobre el enlace — .card__fav, .card__cart { position:relative; z-index:2; } para que capturen su propio clic.
4. Interacción — en app.js, initCardFav() alterna aria-pressed y las clases icon-fill/icon-outline; initCardCart() agrega is-added por 2 s.

Código para replicar

Snippets autocontenidos: copiá cada bloque y pegá en tu proyecto. Incluyen el CSS mínimo necesario con su propio :root.

HTML
<article class="card">
  <a class="card__link" href="#detalle" aria-label="Ver detalle"></a>
  <div class="card__cover">
    <img src="portada.jpg" alt="Portada">
    <span class="card__cover-title">The Shadow #1</span>
  </div>
  <button class="card__fav" aria-pressed="false" aria-label="Agregar a favoritos">
    <svg viewBox="0 0 48 48"><path d="…" fill="currentColor"/></svg>
  </button>
  <div class="card__body">
    <h2 class="card__title">The Shadow</h2>
    <p class="card__meta">Guion: W. Gibson · Arte: A. Reyes</p>
    <button class="card__cart" aria-label="Agregar al carrito">Agregar</button>
  </div>
</article>
CSS
:root{
  --surface:hsl(215,11%,3%);
  --surface2:hsl(214,4%,5%);
  --border:hsl(214,35%,22%);
  --text:hsl(215,25%,85%);
  --accent:hsl(22,100%,62%);
  --danger:hsl(0,100%,71%);
}

.card{
  position:relative;
  width:230px;
  background:var(--surface);
  border:1px solid var(--border);
  border-radius:0.625rem;
  overflow:hidden;
}

.card__link{
  position:absolute;
  inset:0;
  z-index:1;
}

.card__fav,
.card__cart{
  position:relative;
  z-index:2;
}

.card__fav[aria-pressed="true"]{
  color:var(--danger);
  border-color:var(--danger);
}
JS
const fav = document.querySelector('.card__fav');
const cart = document.querySelector('.card__cart');

fav.addEventListener('click', () => {
  const pressed = fav.getAttribute('aria-pressed') === 'true';
  fav.setAttribute('aria-pressed', String(!pressed));
});

cart.addEventListener('click', () => {
  if (cart.classList.contains('is-added')) return;
  cart.classList.add('is-added');
  setTimeout(() => cart.classList.remove('is-added'), 2000);
});