El HTML que escribes tiene que funcionar en el móvil de quien lo consulta en el metro, en el escritorio de quien trabaja con dos monitores y en todo lo que hay en medio. En 2026 más de la mitad del tráfico web viene de móvil. No es un caso extremo a contemplar al final: es el punto de partida.
En los capítulos anteriores aprendiste las unidades relativas (
rem,em,%,vw), montaste el layout con Flexbox y organizaste el grid del Team Builder con CSS Grid y custom properties. Ahora el objetivo es que ese grid funcione en cualquier tamaño de pantalla: del móvil al escritorio, sin romper nada.
Por qué mobile-first y no al revés#
Hay dos formas de hacer un diseño responsive. La más intuitiva es diseñar para
escritorio y luego reducir con max-width:
/* Desktop-first: parto de tres columnas y las reduzco */
.hero-grid { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 768px) {
.hero-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.hero-grid { grid-template-columns: 1fr; }
}La otra forma es empezar por la pantalla más pequeña y ampliar con min-width:
/* Mobile-first: parto de una columna y añado */
.hero-grid { grid-template-columns: 1fr; }
@media (min-width: 37.5em) {
.hero-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 56.25em) {
.hero-grid { grid-template-columns: repeat(3, 1fr); }
}El resultado visual es el mismo. La diferencia está en el razonamiento. Mobile-first parte de lo simple —un grid de una columna— y añade complejidad para pantallas que tienen más espacio. Desktop-first parte de la versión compleja y la parcela hacia abajo. Construir desde lo simple es más sano: los casos base son más fáciles de razonar y los parches se acumulan menos.
Además, hay un motivo práctico. El móvil descarga la misma hoja de estilos en los dos
casos —no hay descarga selectiva—, pero el trabajo que hace con ella cambia. Si la CSS
va de mayor a menor, el móvil aplica primero los estilos de escritorio y luego los
sobreescribe con los suyos. Con mobile-first, los bloques de escritorio van dentro de
media queries min-width que en el móvil ni se activan, así que se ahorra ese repintado.
La diferencia en rendimiento hoy es mínima, pero el principio cuenta.
El meta viewport: imprescindible#
Antes de que funcione cualquier media query en un móvil real, necesitas esta etiqueta
en el <head>:
<!-- width=device-width: usa el ancho real del dispositivo; initial-scale=1: sin zoom inicial -->
<meta name="viewport" content="width=device-width, initial-scale=1" />Sin ella, los navegadores móviles simulan un viewport de ~980px de ancho y reducen la página para que quepa en la pantalla. Tus media queries se activan como si el viewport fuera ancho de escritorio, no el ancho real del dispositivo. Con esta etiqueta, el viewport coincide con el ancho físico del teléfono y todo funciona como esperas.
Unidades relativas en breakpoints#
Como viste en «Color, unidades y tipografía», rem respeta la fuente base del usuario
y em es relativo al elemento actual. Aquí hay un matiz concreto para las media queries:
conviene usar em en los breakpoints en lugar de px.
La razón: si el usuario amplía la fuente del navegador, los puntos de ruptura también
se ajustan. 37.5em son 600px con fuente de 16px, pero 750px con fuente de 20px. El
layout salta a dos columnas cuando hay espacio real para dos columnas —no cuando hay
600px independientemente de cuánto ocupa el texto—.
/* Con em: el breakpoint se adapta a la fuente del usuario */
/* 600px en condiciones normales */
@media (min-width: 37.5em) {
.hero-grid { grid-template-columns: repeat(2, 1fr); }
}clamp(): valores fluidos sin media queries#
clamp(mínimo, preferido, máximo) devuelve el valor preferido recortado para que nunca
salga de los límites. Se usa donde quieres que algo crezca con el viewport sin saltos:
.page {
/* En móvil (~320px): 1rem. En escritorio (~1200px): 2.5rem.
Entre medias, crece de forma continua. */
padding: clamp(1rem, 5vw, 2.5rem);
}
h1 {
/* El título crece de 1.5rem a 2.5rem según el ancho del viewport. */
font-size: clamp(1.5rem, 4vw, 2.5rem);
}El truco es el valor central (5vw, 4vw). Como vw escala con el viewport, el
padding crece de forma continua. El mínimo y el máximo ponen el límite para que no
quede ni demasiado apretado ni demasiado grande. Cero media queries para eso.
El grid que se adapta solo: auto-fill y minmax()#
En el capítulo anterior ya usaste auto-fill y minmax para el grid del Team Builder.
Aquí el foco es la dimensión responsive: por qué ese patrón elimina la necesidad de
media queries para el número de columnas:
.hero-grid {
/* activa la cuadrícula */
display: grid;
/* tantas columnas como quepan, cada una de 15rem como mínimo */
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
/* separación entre celdas */
gap: 1rem;
}auto-fill le dice al navegador que cree tantas columnas como quepan en el contenedor.
minmax(15rem, 1fr) define que cada columna tiene al menos 15rem y puede crecer hasta
1fr para llenar el espacio. El resultado:
- Contenedor de 30rem: caben 2 columnas de 15rem. El grid tiene 2 columnas.
- Contenedor de 48rem: caben 3 columnas de 15rem. El grid tiene 3 columnas.
- Contenedor de 20rem: cabe 1 columna. El grid tiene 1 columna.
No hay @media. No hay breakpoints. El navegador hace la aritmética. Y si mañana
añades más héroes o la pantalla tiene 3000px, el layout se adapta sin tocar el CSS.
El 15rem no es arbitrario: es el ancho mínimo para que la tarjeta de héroe sea
legible. Eso tiene sentido semántico. Un 320px suelto no lo tiene.
Imágenes que no se deforman#
Una imagen con tamaño fijo rompe el responsive: si la pantalla es más estrecha que la imagen, se sale. La base es hacerla fluida:
img {
/* la imagen nunca supera el ancho de su contenedor */
max-width: 100%;
/* el alto se ajusta solo para conservar la proporción */
height: auto;
}Así nunca supera el ancho de su contenedor y conserva su proporción. Pero a veces quieres que una imagen rellene una caja de tamaño fijo —un avatar cuadrado, una portada— sin deformarse. Ahí entran dos propiedades:
.portada {
/* la caja mantiene esta proporción a cualquier ancho */
aspect-ratio: 16 / 9;
/* la imagen cubre la caja y recorta lo que sobra */
object-fit: cover;
}aspect-ratio fija la proporción de la caja y reserva su espacio antes de que la
imagen cargue, evitando saltos de layout. object-fit: cover hace que la imagen
cubra toda la caja manteniendo su proporción, recortando lo que no cabe; contain,
en cambio, la encaja entera dejando hueco. Sin object-fit, una imagen estirada a
una caja de otra proporción se ve deformada.
Eso resuelve el tamaño en pantalla. Hay un segundo frente que no entra en este
capítulo pero conviene que sepas que existe: la resolución del fichero. Con
srcset y sizes en el <img> —o con el elemento <picture>— le das al navegador
varias versiones de la misma imagen (una ligera para móvil, una grande para pantallas
densas) y él elige la que toca. Es el estándar para imágenes responsive de verdad y lo
verás cuando entres en optimización; por ahora, max-width: 100% ya evita que se
deformen.
min() y max(): límites puntuales#
clamp() es para valores fluidos con dos límites. A veces solo necesitas uno:
/* El contenedor nunca supera 60rem, pero si la pantalla es más estrecha, se adapta */
.page { width: min(60rem, 100%); }
/* El título nunca es menor de 1rem, aunque el viewport sea muy estrecho */
.titulo-hero { font-size: max(1rem, 2vw); }Son más puntuales que clamp(), pero el principio es el mismo: definir el comportamiento
mediante relaciones, no mediante tamaños fijos.
Una nota sobre la altura del viewport en móvil#
Una pieza moderna que evita un dolor de cabeza clásico. En móvil, 100vh (el 100% del
alto de la ventana) cuenta también la franja que tapa la barra del navegador, así que un
bloque pensado a pantalla completa se desborda un poco y aparece scroll donde no lo
quieres. La unidad dvh (dynamic viewport height, alto dinámico) mide el viewport
realmente visible y se ajusta cuando esa barra aparece o desaparece; sus parientes svh
y lvh miden el viewport más pequeño y el más grande. No te hacen falta para el
ejercicio, pero cuando veas un 100vh dando problemas en un móvil, ya sabes la
alternativa: 100dvh.
Una nota sobre los frameworks#
Bootstrap, Tailwind y similares automatizan exactamente lo que acabas de aprender. Sus
clases sm:, md:, lg: o col-md-4 son azúcar sobre media queries y grids como los
que has escrito aquí. Usarlos sin entender el fundamento lleva a layouts que se rompen
cuando el caso de uso se sale de lo previsto. Usarlos conociendo el fundamento te da
control sobre lo que generan y criterio para depurar cuando algo no funciona.
Pruébalo#
El grid del Team Builder con auto-fill + minmax(): sin una sola media query, el número
de columnas se adapta al espacio disponible. Arrastra la manija del borde derecho del
preview (o usa los presets Móvil / Tablet / Escritorio) y mira cómo el grid recoloca
las columnas a cada ancho, sin tocar el CSS. Después prueba a cambiar 12rem en
styles.css por 8rem o por 20rem y observa cómo cambia el punto en el que entra o
sale una columna.
Comprueba lo que sabes#
Pregunta 1 de 5
En un enfoque mobile-first, ¿qué va en la regla CSS base (fuera de cualquier media query)?
Tu turno#
El HTML ya está montado. Tu tarea es el CSS: hacer que el grid sea responsive.
Abre styles.css en el playground, sigue los TODOs y aplica lo que has aprendido
en este capítulo. Arrastra el preview o usa los presets de dispositivo para comprobar
tu CSS a distintos anchos: ahí verás tus media queries dispararse de verdad. Empieza con
lo que sepas y sube de nivel. Cuando creas que lo tienes, despliega las soluciones y
compara tu enfoque con los tres niveles.
Ejercicio · en esta página
Haz responsive el grid del Team Builder
El HTML del Team Builder ya está montado y es semántico. Tu trabajo es el CSS: hacer que el grid de héroes se adapte a cualquier ancho de pantalla. El starter te da el marcado completo y un styles.css con TODOs. Empieza con lo que sepas (columnas fijas, media queries) y sube de nivel con lo que hayas aprendido en este capítulo.
Paso 1: Que funcione
- El grid se ve en escritorio con varias columnas.
- En móvil no hay scroll horizontal.
- Hay media queries con max-width para reducir columnas al estrechar la pantalla.
Paso 2: Mobile-first de verdad
- La base (sin media query) es de una sola columna.
- Se usa min-width para ampliar el grid al crecer la pantalla.
- Unidades relativas (rem, em) en el contenedor y en los breakpoints.
- El grid pasa de 1 a 2 columnas en tablet y de 2 a 3 en escritorio.
Paso 3: Fluido y sin números mágicos
- El grid usa repeat(auto-fill, minmax(...)) y no necesita media queries.
- clamp() para al menos un valor de padding o tipografía.
- Cada valor tiene una razón de ser: no hay números arbitrarios.
- El layout se adapta de forma continua al arrastrar el borde de la ventana.
Ver soluciones
/*
NIVEL OK — desktop-first con max-width y anchos fijos
Funciona. En escritorio se ven tres columnas y en móvil no se rompe del todo.
Pero el enfoque está invertido: se diseña para escritorio y se parchan los
tamaños pequeños con media queries descendentes (max-width).
Problemas que hereda este enfoque:
- La base ya tiene ancho fijo (320px por columna). Si la pantalla es más
estrecha, el navegador hace scroll horizontal o aplasta el contenido.
- Cada breakpoint añade un parche sobre el anterior en lugar de construir
desde lo sencillo hacia lo complejo.
- Los tamaños en px son "números mágicos": ¿por qué 320? ¿Y si la fuente
base cambia? No escalan con nada.
*/
/* ─── Variables y reset ──────────────────────────────────────── */
:root {
color-scheme: light;
--tinta: #1c1b22;
--tinta-suave: #5b5966;
--linea: #e6e3ee;
--fondo: #f7f6fb;
--tarjeta: #ffffff;
--acento: #b8336a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--tinta);
background: var(--fondo);
line-height: 1.5;
}
/* ─── Contenedor de página ───────────────────────────────────── */
.page {
max-width: 1200px;
margin: 0 auto;
padding: 40px 24px 64px;
}
/* ─── Cabecera ───────────────────────────────────────────────── */
.site-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.75rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--linea);
margin-bottom: 2rem;
}
.brand {
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.01em;
margin: 0;
}
.brand span {
color: var(--acento);
}
.site-nav a {
color: var(--tinta-suave);
text-decoration: none;
font-size: 0.9rem;
margin-left: 1rem;
}
.site-nav a:hover {
color: var(--acento);
}
/* ─── Título de sección ──────────────────────────────────────── */
.section-title {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--tinta-suave);
margin: 0 0 1rem;
}
/* ─── Grid de héroes — desktop-first ────────────────────────── */
/* Base: tres columnas fijas. El número 320px es arbitrario. */
.hero-grid {
display: grid;
grid-template-columns: 320px 320px 320px;
gap: 16px;
list-style: none;
margin: 0;
padding: 0;
}
/* Parche para tablet: dos columnas cuando no caben tres. */
@media (max-width: 1024px) {
.hero-grid {
grid-template-columns: 320px 320px;
}
}
/* Parche para móvil: una columna cuando no cabe ninguna. */
@media (max-width: 680px) {
.hero-grid {
grid-template-columns: 1fr;
}
}
/* ─── Tarjeta ────────────────────────────────────────────────── */
.hero-card {
background: var(--tarjeta);
border: 1px solid var(--linea);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.hero-top {
display: flex;
align-items: center;
gap: 0.85rem;
}
.hero-portrait {
flex: none;
width: 3rem;
height: 3rem;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 700;
color: #fff;
background: var(--acento);
}
.hero-name {
font-size: 1.05rem;
font-weight: 700;
margin: 0;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--tinta-suave);
margin: 0.1rem 0 0;
}
.hero-stats {
display: flex;
gap: 1.25rem;
margin: 0;
padding-top: 0.5rem;
border-top: 1px solid var(--linea);
}
.stat {
display: flex;
flex-direction: column;
}
.stat dt {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--tinta-suave);
margin: 0;
}
.stat dd {
font-size: 1.05rem;
font-weight: 600;
margin: 0.15rem 0 0;
}
/* ─── Pie ────────────────────────────────────────────────────── */
.site-footer {
margin-top: 3rem;
padding-top: 1.25rem;
border-top: 1px solid var(--linea);
color: var(--tinta-suave);
font-size: 0.85rem;
} Por qué este nivel
- Resuelve el responsive con columnas fijas en px y media queries de max-width.
- El enfoque es desktop-first: se diseña para escritorio y se parchea hacia abajo.
- Funciona. Pero los 320px son arbitrarios y no escalan si cambia la fuente base.
- Al estrechar la pantalla, el layout aguanta a base de parches, no de un diseño pensado desde el principio para pantallas pequeñas.
/*
NIVEL MEJOR — mobile-first con min-width y unidades relativas
El cambio de mentalidad: la base (sin media query) es para la pantalla más
estrecha. A partir de ahí se "amplía" con min-width, no se "reduce" con max-width.
El resultado es el mismo en pantalla, pero el código refleja la realidad:
un móvil tiene lo esencial; el escritorio tiene más espacio y se aprovecha.
Mejoras respecto a OK:
- Se parte de 1 columna (lo más sencillo) y se añaden columnas al crecer.
- rem en lugar de px para el contenedor: escala si el usuario amplía la fuente.
- % en el gap interior para que respire proporcionalmente.
- Los breakpoints en em (en vez de px) respetan la configuración de fuente del
navegador: si el usuario tiene la fuente base a 20px, los puntos de ruptura
se ajustan, cosa que px ignora.
Lo que todavía se puede mejorar:
- Hay dos media queries explícitas. Con CSS moderno el grid puede adaptarse
solo, sin necesidad de anunciar los breakpoints.
- El tamaño del contenedor sigue siendo un tope fijo (60rem), no fluido.
*/
/* ─── Variables y reset ──────────────────────────────────────── */
:root {
color-scheme: light;
--tinta: #1c1b22;
--tinta-suave: #5b5966;
--linea: #e6e3ee;
--fondo: #f7f6fb;
--tarjeta: #ffffff;
--acento: #b8336a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--tinta);
background: var(--fondo);
line-height: 1.5;
}
/* ─── Contenedor de página ───────────────────────────────────── */
/* rem para que el tope escale con la preferencia de fuente del usuario. */
.page {
max-width: 60rem;
margin: 0 auto;
padding: 2.5rem 1.5rem 4rem;
}
/* ─── Cabecera ───────────────────────────────────────────────── */
.site-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.75rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--linea);
margin-bottom: 2rem;
}
.brand {
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.01em;
margin: 0;
}
.brand span {
color: var(--acento);
}
.site-nav a {
color: var(--tinta-suave);
text-decoration: none;
font-size: 0.9rem;
margin-left: 1rem;
}
.site-nav a:hover {
color: var(--acento);
}
/* ─── Título de sección ──────────────────────────────────────── */
.section-title {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--tinta-suave);
margin: 0 0 1rem;
}
/* ─── Grid de héroes — mobile-first ─────────────────────────── */
/* Base (móvil): una sola columna que ocupa todo el ancho disponible.
No hace falta media query: es la regla por defecto. */
.hero-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
/* A partir de ~600px (tablet): dos columnas.
em en lugar de px: si el usuario tiene fuente grande, el breakpoint se
activa antes — exactamente lo que esperaría. */
@media (min-width: 37.5em) {
.hero-grid {
grid-template-columns: repeat(2, 1fr);
}
}
/* A partir de ~900px (escritorio): tres columnas. */
@media (min-width: 56.25em) {
.hero-grid {
grid-template-columns: repeat(3, 1fr);
}
}
/* ─── Tarjeta ────────────────────────────────────────────────── */
.hero-card {
background: var(--tarjeta);
border: 1px solid var(--linea);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.hero-top {
display: flex;
align-items: center;
gap: 0.85rem;
}
.hero-portrait {
flex: none;
width: 3rem;
height: 3rem;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 700;
color: #fff;
background: var(--acento);
}
.hero-name {
font-size: 1.05rem;
font-weight: 700;
margin: 0;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--tinta-suave);
margin: 0.1rem 0 0;
}
.hero-stats {
display: flex;
gap: 1.25rem;
margin: 0;
padding-top: 0.5rem;
border-top: 1px solid var(--linea);
}
.stat {
display: flex;
flex-direction: column;
}
.stat dt {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--tinta-suave);
margin: 0;
}
.stat dd {
font-size: 1.05rem;
font-weight: 600;
margin: 0.15rem 0 0;
}
/* ─── Pie ────────────────────────────────────────────────────── */
.site-footer {
margin-top: 3rem;
padding-top: 1.25rem;
border-top: 1px solid var(--linea);
color: var(--tinta-suave);
font-size: 0.85rem;
} Por qué es mejor que el anterior
- Enfoque invertido: la base es de 1 columna (móvil) y se añaden columnas con min-width.
- rem en el contenedor y em en los breakpoints: si el usuario amplía la fuente del navegador, los puntos de ruptura también se ajustan.
- El código refleja la realidad: el móvil tiene lo esencial; el escritorio, más espacio.
- Dos media queries explícitas hacen el trabajo. Claro, predecible, fácil de mantener.
/*
NIVEL EXCELENTE — fluido con auto-fill/minmax y clamp(), sin números mágicos
La diferencia clave: el grid ya no necesita media queries para adaptarse.
auto-fill + minmax() le dice al navegador "pon tantas columnas como quepan,
pero que ninguna baje de 15rem". El navegador hace la aritmética; nosotros
solo definimos el límite inferior. El resultado es un grid que fluye de 1 a N
columnas de forma continua, sin breakpoints explícitos.
clamp() para tipografía y padding fluidos:
- clamp(mín, preferido, máx) devuelve un valor que crece con el viewport pero
nunca sale de los límites. Cero media queries para el tamaño del texto.
Por qué es mejor que el nivel Mejor:
- Cero breakpoints en el grid: el layout se adapta solo. Si mañana hay 6
héroes o la pantalla tiene 2000px, funciona sin tocar nada.
- Sin números mágicos: 15rem no es "de dónde salió ese número", sino "la
tarjeta necesita al menos 15rem para ser legible". Eso sí tiene sentido.
- clamp() en el padding del contenedor: 1rem en móvil, 2.5rem en escritorio,
y una transición continua entre medias. Sin rupturas.
- El código no crece con los casos de uso: más héroes, más pantallas, sin
más CSS.
*/
/* ─── Variables y reset ──────────────────────────────────────── */
:root {
color-scheme: light;
--tinta: #1c1b22;
--tinta-suave: #5b5966;
--linea: #e6e3ee;
--fondo: #f7f6fb;
--tarjeta: #ffffff;
--acento: #b8336a;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
color: var(--tinta);
background: var(--fondo);
line-height: 1.5;
}
/* ─── Contenedor de página ───────────────────────────────────── */
/* clamp(1rem, 5vw, 2.5rem): en una pantalla de 320px el padding es ~1rem;
en una de 1200px, ~2.5rem. Crece de forma continua entre ambas. */
.page {
max-width: 60rem;
margin: 0 auto;
padding: clamp(1rem, 5vw, 2.5rem) clamp(1rem, 4vw, 1.5rem) 4rem;
}
/* ─── Cabecera ───────────────────────────────────────────────── */
.site-header {
display: flex;
align-items: baseline;
justify-content: space-between;
flex-wrap: wrap;
gap: 0.75rem;
padding-bottom: 1.25rem;
border-bottom: 1px solid var(--linea);
margin-bottom: 2rem;
}
.brand {
font-size: 1.4rem;
font-weight: 700;
letter-spacing: -0.01em;
margin: 0;
}
.brand span {
color: var(--acento);
}
.site-nav a {
color: var(--tinta-suave);
text-decoration: none;
font-size: 0.9rem;
margin-left: 1rem;
}
.site-nav a:hover {
color: var(--acento);
}
/* ─── Título de sección ──────────────────────────────────────── */
.section-title {
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--tinta-suave);
margin: 0 0 1rem;
}
/* ─── Grid de héroes — fluido, sin breakpoints ───────────────── */
/* auto-fill: crea tantas columnas como quepan en el contenedor.
minmax(15rem, 1fr): cada columna tiene al menos 15rem y puede crecer
hasta 1fr (llenar el espacio). Resultado: 1 col en móvil estrecho,
2 en tablet, 3 en escritorio — sin una sola media query. */
.hero-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
gap: 1rem;
list-style: none;
margin: 0;
padding: 0;
}
/* ─── Tarjeta ────────────────────────────────────────────────── */
.hero-card {
background: var(--tarjeta);
border: 1px solid var(--linea);
border-radius: 12px;
padding: 1.25rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.hero-top {
display: flex;
align-items: center;
gap: 0.85rem;
}
.hero-portrait {
flex: none;
width: 3rem;
height: 3rem;
border-radius: 50%;
display: grid;
place-items: center;
font-weight: 700;
color: #fff;
background: var(--acento);
}
.hero-name {
/* clamp(): el nombre crece ligeramente en pantallas amplias,
sin media queries para el tamaño de fuente. */
font-size: clamp(1rem, 2.5vw, 1.15rem);
font-weight: 700;
margin: 0;
}
.hero-role {
font-size: 0.78rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--tinta-suave);
margin: 0.1rem 0 0;
}
.hero-stats {
display: flex;
gap: 1.25rem;
margin: 0;
padding-top: 0.5rem;
border-top: 1px solid var(--linea);
}
.stat {
display: flex;
flex-direction: column;
}
.stat dt {
font-size: 0.72rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--tinta-suave);
margin: 0;
}
.stat dd {
font-size: 1.05rem;
font-weight: 600;
margin: 0.15rem 0 0;
}
/* ─── Pie ────────────────────────────────────────────────────── */
.site-footer {
margin-top: 3rem;
padding-top: 1.25rem;
border-top: 1px solid var(--linea);
color: var(--tinta-suave);
font-size: 0.85rem;
} Por qué es mejor que el anterior
- auto-fill + minmax(15rem, 1fr): el grid no necesita media queries para adaptarse. El navegador calcula cuántas columnas caben.
- 15rem no es un número arbitrario: es el ancho mínimo para que una tarjeta de héroe sea legible. Tiene sentido semántico.
- clamp() en el padding y el tamaño de fuente: valores fluidos que crecen con el viewport de forma continua, sin saltos.
- Si mañana hay 10 héroes o una pantalla de 3000px, el layout se adapta sin tocar el CSS. Eso es lo que significa "fluido".