aeworks.tech/lab/modo-eclipse
← AE Lab

Modo oscuro con eclipse

14 oct 2026· nivel medioView Transitionsmodo oscuroSVG

Al pulsar el sol, la luna lo eclipsa y la noche se derrama por toda la página en un círculo que nace del botón. La web de una cafetería pasa de desayunos a coctelería con textos, precios y colores distintos.

// CÓMO ESTÁ HECHO

  1. Dos juegos de variables CSS (claro y oscuro) cuelgan de un atributo data-tema en <html>, que un script en el <head> fija antes de pintar: tema guardado en localStorage o, si no hay, prefers-color-scheme.
  2. El sol y la luna son un único SVG: un disco oscuro recortado con clipPath cruza el sol con @keyframes de transform, se para en el centro (eclipse total con halo de gradiente radial) y se queda desplazado formando la media luna.
  3. En el eclipse, document.startViewTransition cambia el tema y Element.animate recorta ::view-transition-new(root) con un clip-path circle() que crece desde el centro del botón hasta la esquina más lejana (Math.hypot).
  4. Sin View Transitions, un disco del color nuevo crece con transform: scale y se desvanece; con prefers-reduced-motion solo hay un fundido.
  5. Un modo demostración alterna el tema cada 3,4 s sin guardarlo y se apaga con el primer toque, tecla o scroll.

// EL CÓDIGO

<!-- El astro: un único dibujo que se reutiliza (sol de día, luna de noche) -->
<template id="plantilla-astro">
  <svg viewBox="-50 -50 100 100" aria-hidden="true">
    <defs>
      <radialGradient id="ID-halo">
        <stop offset="45%" style="stop-color:var(--astro);stop-opacity:0"/>
        <stop offset="52%" style="stop-color:#FFE7B0"/>
        <stop offset="64%" style="stop-color:var(--astro);stop-opacity:.45"/>
        <stop offset="100%" style="stop-color:var(--astro);stop-opacity:0"/>
      </radialGradient>
      <clipPath id="ID-recorte"><circle r="23"/></clipPath>
    </defs>
    <g class="giro">
      <g class="rayos">
        <line x1="0" y1="-31" x2="0" y2="-42"/><line x1="0" y1="31" x2="0" y2="42"/>
        <line x1="-31" y1="0" x2="-42" y2="0"/><line x1="31" y1="0" x2="42" y2="0"/>
        <line x1="22" y1="-22" x2="30" y2="-30"/><line x1="-22" y1="22" x2="-30" y2="30"/>
        <line x1="-22" y1="-22" x2="-30" y2="-30"/><line x1="22" y1="22" x2="30" y2="30"/>
      </g>
    </g>
    <circle class="corona" r="44" fill="url(#ID-halo)"/>
    <circle class="astro" r="22"/>
    <!-- La luna se recorta al disco + un poco: fuera del astro no se ve -->
    <g clip-path="url(#ID-recorte)"><circle class="luna" r="22"/></g>
  </svg>
</template>

<div class="app" id="app">
  <header class="cabecera">
    <div class="marca"><i></i>Penumbra</div>
    <nav class="nav" aria-label="Principal">
      <a href="#carta">Carta</a><a href="#horario">Horario</a><a href="#reservas">Reservas</a>
    </nav>
    <button class="interruptor" id="interruptor" type="button" aria-pressed="false" aria-label="Activar modo oscuro"></button>
  </header>

  <main class="portada">
    <div class="cielo" id="cielo">
      <svg class="estrellas" viewBox="0 0 100 100" aria-hidden="true" style="position:absolute;inset:-20%;width:140%;height:140%">
        <circle class="estrella" cx="8" cy="20" r=".8"/><circle class="estrella" cx="92" cy="14" r=".6"/>
        <circle class="estrella" cx="80" cy="88" r=".9"/><circle class="estrella" cx="14" cy="78" r=".6"/>
        <circle class="estrella" cx="50" cy="4" r=".5"/><circle class="estrella" cx="96" cy="56" r=".7"/>
        <circle class="estrella" cx="3" cy="50" r=".5"/><circle class="estrella" cx="30" cy="96" r=".7"/>
      </svg>
    </div>

    <div class="texto">
      <p class="etiqueta" id="horario">
        <span data-dia>Abierto · 8:00 – 17:00</span><span data-noche>Abierto · 19:00 – 01:00</span>
      </p>
      <h1>
        <span data-dia>Café lento<br>bajo el <em>sol</em>.</span>
        <span data-noche>Cócteles<br>bajo la <em>luna</em>.</span>
      </h1>
      <p class="bajada">
        <span data-dia>Tostamos cada mañana y servimos desayunos sin prisa en la terraza.</span>
        <span data-noche>Cuando se pone el sol, la barra se vuelve coctelería con vinilos.</span>
      </p>
      <a class="cta" href="#reservas" id="reservas">Reservar mesa</a>
    </div>
  </main>

  <section class="carta" id="carta" aria-label="Carta destacada">
    <div class="plato"><span class="ico"><span data-dia>☕</span><span data-noche>🍸</span></span><span><b data-dia>Flat white</b><b data-noche>Espresso martini</b><small data-dia>Leche de avena</small><small data-noche>Café, vainilla, hielo</small></span><span><span data-dia>3,20 €</span><span data-noche>9,50 €</span></span></div>
    <div class="plato"><span class="ico"><span data-dia>🥐</span><span data-noche>🧀</span></span><span><b data-dia>Croissant de mantequilla</b><b data-noche>Tabla de quesos</b><small data-dia>Recién horneado</small><small data-noche>Tres curaciones</small></span><span><span data-dia>2,40 €</span><span data-noche>14,00 €</span></span></div>
    <div class="plato"><span class="ico"><span data-dia>🍊</span><span data-noche>🍹</span></span><span><b data-dia>Zumo de naranja</b><b data-noche>Negroni de la casa</b><small data-dia>Exprimido al momento</small><small data-noche>Con piel de naranja</small></span><span><span data-dia>3,80 €</span><span data-noche>10,00 €</span></span></div>
  </section>
</div>

<p class="aviso-demo" id="aviso">MODO DEMOSTRACIÓN · PRUEBA EL BOTÓN DE ARRIBA</p>