1 · Hero «responsive» con <picture>
y capa de color
Objetivo: imágenes adaptativas (srcset
), semántica y superposición con CSS variables.
hero.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Hero responsive</title>
<style>
:root{--overlay:#000a} /* Capa oscura */
body{margin:0;font-family:system-ui}
.hero{position:relative;color:#fff;text-align:center;height:60vh;display:flex;align-items:center;justify-content:center}
.hero::after{content:"";position:absolute;inset:0;background:var(--overlay)}
.hero h1{position:relative;font-size:clamp(2rem,5vw,4rem);margin:0}
picture,img{position:absolute;inset:0;width:100
</style>
</head>
<body>
<header class="hero">
<picture>
<source srcset="banner-large.jpg" media="(min-width:900px)">
<source srcset="banner-medium.jpg" media="(min-width:600px)">
<img src="banner-small.jpg" alt="Equipo trabajando en oficina">
</picture>
<h1>Lleva tu proyecto al siguiente nivel</h1>
</header>
</body>
</html>
2 · Acordeón solo con CSS (checkbox hack)
Objetivo: interactividad sin JS usando atributos id/for
y transiciones.
acordeon.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Acordeón CSS</title>
<style>
body{font-family:system-ui;max-width:600px;margin:2rem auto}
.item{border:1px solid #ddd;border-radius:4px;margin-bottom:.8rem;overflow:hidden}
.item label{display:block;padding:1rem;cursor:pointer;font-weight:600;background:#f9f9f9}
.item input{display:none}
.item .content{max-height:0;padding:0 1rem;color:#555;transition:max-height .4s ease,padding .4s}
/* abierto */
.item input:checked ~ .content{max-height:200px;padding:1rem}
</style>
</head>
<body>
<h1>Acordeón accesible</h1>
<div class="item">
<input type="checkbox" id="q1">
<label for="q1">¿Qué incluye el plan Básico?</label>
<div class="content">
<p>Soporte por correo y 5 GB de almacenamiento.</p>
</div>
</div>
<div class="item">
<input type="checkbox" id="q2">
<label for="q2">¿Cómo cancelo mi suscripción?</label>
<div class="content">
<p>Desde «Mi cuenta → Facturación → Cancelar».</p>
</div>
</div>
</body>
</html>
3 · Interruptor de modo oscuro/claro con variables y localStorage
Objetivo: custom properties, prefers‑color‑scheme
y pequeño script de persistencia.
dark‑mode.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Modo oscuro</title>
<style>
:root{
--bg:#fff;--fg:#111;--accent:#4f46e5;
}
@media (prefers-color-scheme: dark){
:root{--bg:#111;--fg:#eee}
}
[data-theme="dark"]{--bg:#111;--fg:#eee}
body{background:var(--bg);color:var(--fg);font-family:system-ui;margin:2rem;transition:background .3s,color .3s}
button{padding:.5rem 1rem;border:2px solid var(--accent);background:none;color:var(--fg);border-radius:4px;cursor:pointer}
</style>
</head>
<body>
<button id="toggle">Cambiar tema</button>
<script>
const root=document.documentElement;
const key='theme';
const stored=localStorage.getItem(key);
if(stored) root.dataset.theme=stored;
document.getElementById('toggle').onclick=()=>{
const next=root.dataset.theme==='dark'?'light':'dark';
root.dataset.theme=next;
localStorage.setItem(key,next);
};
</script>
</body>
</html>
4 · Lista de pasos con indicador de progreso
Objetivo: Flexbox, pseudo‑elementos y aria-current
.
pasos.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Pasos de proceso</title>
<style>
body{font-family:system-ui;margin:2rem}
ol{display:flex;gap:1rem;counter-reset:step}
li{position:relative;list-style:none;padding-top:2.5rem;text-align:center;flex:1}
li::before{counter-increment:step;content:counter(step);position:absolute;top:0;left:50
li[aria-current="step"]::before{background:#4f46e5;color:#fff}
/* líneas */
li:not(:last-child)::after{content:"";position:absolute;top:18px;right:-50
li[aria-current="step"]+li::after{background:#4f46e5}
</style>
</head>
<body>
<h1>Compra online</h1>
<ol>
<li aria-current="step">Carrito</li>
<li>Envío</li>
<li>Pago</li>
<li>Confirmación</li>
</ol>
</body>
</html>
5 · Validación en vivo de un email con RegExp
Objetivo: Form validation ligera y mensajes accesibles.
email‑live.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Email en vivo</title>
<style>
body{font-family:system-ui;margin:2rem;max-width:400px}
input{width:100
input[data-ok="true"]{border-color:#059669}
input[data-ok="false"]{border-color:#dc2626}
small{display:block;margin-top:.3rem;font-weight:600}
</style>
</head>
<body>
<label>Email
<input type="email" id="mail" aria-describedby="msg" placeholder="usuario@dominio.com">
</label>
<small id="msg"></small>
<script>
const mail=document.getElementById('mail');
const msg=document.getElementById('msg');
const regex=/^[\w.+-]+@\w+\.\w{2,}$/i;
mail.addEventListener('input',e=>{
const ok=regex.test(e.target.value);
if(e.target.value===""){msg.textContent="";mail.removeAttribute('data-ok');return;}
mail.dataset.ok=ok;
msg.textContent=ok?"✔️ Formato válido":"❌ Formato incorrecto";
});
</script>
</body>
</html>
✅ Cómo aprovechar estos 5 ejercicios
- Analiza qué novedad aporta cada uno (srcset, checkbox hack, variables + persistencia,
aria-current
, RegExp en vivo). - Experimenta cambiando tamaños de pantalla, colores de capa, expresión regular, etc.
- Valida siempre con el W3C y revisa contrastes/pseudo‑estados de foco.
- Guarda en un repo «ejercicios‑4» y etiqueta los commits con la técnica que practica cada ejemplo.
