aeworks.tech/lab/ticket-reserva
← AE Lab

La reserva que se imprime como un ticket

14 oct 2026· nivel medioformularioWeb Animations APICSS mask

Un formulario de reserva de restaurante que, al enviarlo, se mete por la ranura de una impresora y sale convertido en un ticket de papel con código de barras y el sello «CONFIRMADA» estampado de un golpe.

// CÓMO ESTÁ HECHO

  1. Formulario y ticket se apilan en la misma celda de un grid con overflow:hidden que empieza justo en la ranura, así lo que sube «entra» en la impresora y lo que baja «sale» de ella.
  2. Al enviar (preventDefault, sin enviar nada), la Web Animations API inclina el formulario con perspective y rotateX y lo sube hasta que la ranura se lo traga.
  3. El ticket baja a tirones con fotogramas generados por código (cada salto se mueve el 45 % de su tramo y se queda quieto el resto), como una impresora térmica.
  4. El borde dentado es una máscara con conic-gradient repetido; el código de barras es un SVG de rectángulos calculado a partir del código de reserva.
  5. El sello cae de escala 2,6 a 1 con mix-blend-mode multiply y una máscara de puntos que imita la tinta, y el papel acusa el golpe con un pequeño rebote.

// EL CÓDIGO

<main class="escena">
  <header class="intro">
    <small>Reserva de mesa</small>
    <h1>La reserva que se <em>imprime</em> como un ticket</h1>
    <p>Rellena la reserva y envíala: el formulario entra en la impresora y sale un ticket con tus datos, su código de barras y el sello de confirmada.</p>
  </header>

  <section class="maquina" aria-label="Reserva en Brasa y Sal">
    <div class="impresora" id="impresora" aria-hidden="true">
      <span class="marca">BRASA&nbsp;&amp;&nbsp;SAL · MESAS</span>
      <span class="led"></span>
      <span class="ranura"></span>
    </div>

    <div class="boca">
      <!-- 1. El formulario (no se envía a ningún sitio: lo frena preventDefault) -->
      <form class="formulario" id="formulario" novalidate>
        <div class="campo" id="campo-nombre">
          <label class="etiqueta" for="nombre">Nombre</label>
          <input type="text" id="nombre" name="nombre" placeholder="¿A nombre de quién?" autocomplete="off" maxlength="22" aria-describedby="aviso-nombre">
          <span class="aviso" id="aviso-nombre">Dinos un nombre para apuntar la mesa (mínimo 2 letras).</span>
        </div>

        <div class="campo">
          <span class="etiqueta" id="et-personas">Personas</span>
          <div class="contador" role="group" aria-labelledby="et-personas">
            <button type="button" id="menos" aria-label="Una persona menos">−</button>
            <output id="personas" aria-live="polite">2</output>
            <button type="button" id="mas" aria-label="Una persona más">+</button>
            <span class="unidad" id="unidad">comensales</span>
          </div>
        </div>

        <fieldset class="campo" id="campo-dia">
          <legend class="etiqueta">Día</legend>
          <div class="fichas dias" id="dias"></div>
          <span class="aviso">Elige qué día venís.</span>
        </fieldset>

        <fieldset class="campo" id="campo-hora">
          <legend class="etiqueta">Hora</legend>
          <div class="fichas horas" id="horas"></div>
          <span class="aviso">Elige una hora y os guardamos la mesa.</span>
        </fieldset>

        <button class="enviar" id="enviar" type="submit">Reservar mesa
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" aria-hidden="true"><path d="M6 9V3h12v6M6 18H4v-7h16v7h-2M8 14h8v7H8z"/></svg>
        </button>
      </form>

      <!-- 2. El ticket que sale por la ranura -->
      <article class="ticket" id="ticket" aria-hidden="true">
        <div class="cabecera">
          <strong>BRASA &amp; SAL</strong>
          <span>cocina de fuego · reserva de mesa</span>
        </div>
        <hr>
        <dl>
          <dt>Nombre</dt><dd id="t-nombre">—</dd>
          <dt>Personas</dt><dd id="t-personas">—</dd>
          <dt>Día</dt><dd id="t-dia">—</dd>
          <dt>Hora</dt><dd id="t-hora">—</dd>
          <dt>Mesa</dt><dd id="t-mesa">—</dd>
        </dl>
        <div class="hueco-sello" id="hueco-sello"></div>
        <hr>
        <div class="codigo">
          <svg id="barras" viewBox="0 0 200 50" preserveAspectRatio="none" aria-hidden="true"></svg>
          <span id="t-codigo">R-0000</span>
        </div>
        <p class="pie">Te esperamos. Si no podéis venir, avisadnos.</p>
        <div class="sello" id="sello">CONFIRMADA</div>
      </article>
    </div>

    <button class="otra" id="otra" type="button">Hacer otra reserva</button>
    <p class="sr" id="anuncio" aria-live="polite"></p>
  </section>
</main>