aeworks.tech/lab/precio-negociable
← AE Lab

El precio que se regatea

21 oct 2026· nivel mediodeslizadorSVGfísicamicrointeracción

Una ficha de producto con un deslizador para regatear: al bajar el precio, las cifras ruedan, los extras se caen de la lista y la cara del vendedor pasa de feliz a sudar y echar humo. Si te pasas, «¡Ni hablar!» y el deslizador rebota con un muelle hasta su mínimo.

// CÓMO ESTÁ HECHO

  1. Un input type=range real e invisible encima de un riel dibujado: teclado, lector de pantalla y táctil vienen gratis.
  2. El precio se convierte en una «tensión» de 0 a 1,45 y ese único número lo controla todo: cara, frases, extras y colores.
  3. Cada rasgo de la cara SVG tiene 5 valores clave (cejas, boca, ojos, rubor, sudor, vapor) y se interpola de forma continua con transform y opacity.
  4. Al soltar por debajo del mínimo, un muelle con rozamiento (fuerza = −k·x − c·v) lleva el precio de vuelta pasándose un poco, y luego «¡Trato hecho!» con confeti.
  5. Un guion por tiempo mueve un dedo fantasma en bucle con requestAnimationFrame y se detiene al primer toque o tecla.

// EL CÓDIGO

<main class="escena">

  <!-- El vendedor: su cara es un SVG que se interpola con la «tensión» -->
  <section class="vendedor" aria-live="polite">
    <div class="cara-caja">
      <div class="aura verde"></div>
      <div class="aura roja" id="auraRoja"></div>
      <svg class="cara" viewBox="0 0 200 200" aria-hidden="true">
        <g id="vapor" opacity="0">
          <g class="pieza humo"><circle cx="26" cy="96" r="9" fill="#F2F2F2"/><circle cx="18" cy="84" r="7" fill="#F2F2F2"/><circle cx="30" cy="80" r="6" fill="#F2F2F2"/></g>
          <g class="pieza humo"><circle cx="174" cy="96" r="9" fill="#F2F2F2"/><circle cx="182" cy="84" r="7" fill="#F2F2F2"/><circle cx="170" cy="80" r="6" fill="#F2F2F2"/></g>
        </g>
        <g id="cabeza" class="pieza">
          <circle cx="34" cy="122" r="14" fill="#E3AF84"/>
          <circle cx="166" cy="122" r="14" fill="#E3AF84"/>
          <ellipse cx="100" cy="120" rx="68" ry="74" fill="#EFC49A"/>
          <ellipse id="rojo" cx="100" cy="120" rx="68" ry="74" fill="#FF3B30" opacity="0"/>
          <!-- gorra del puesto -->
          <path d="M32 98 C30 30 170 30 168 98 Z" fill="#00C866"/>
          <path d="M32 98 C30 30 100 34 100 50 C100 34 170 30 168 98 Z" fill="#00FF7F" opacity=".35"/>
          <ellipse cx="118" cy="98" rx="66" ry="9" fill="#009E50"/>
          <text x="100" y="80" text-anchor="middle" font-family="ui-monospace,monospace" font-weight="800" font-size="22" fill="#05140B">14</text>
          <rect id="cejaI" class="pieza" x="56" y="108" width="32" height="8" rx="4" fill="#3A2A1E"/>
          <rect id="cejaD" class="pieza" x="112" y="108" width="32" height="8" rx="4" fill="#3A2A1E"/>
          <g id="ojos">
            <g class="pieza ojo"><ellipse cx="74" cy="132" rx="7.5" ry="9.5" fill="#1A1410"/><circle cx="76.5" cy="128.5" r="2.4" fill="#fff"/></g>
            <g class="pieza ojo"><ellipse cx="126" cy="132" rx="7.5" ry="9.5" fill="#1A1410"/><circle cx="128.5" cy="128.5" r="2.4" fill="#fff"/></g>
          </g>
          <ellipse class="mejilla" cx="58" cy="154" rx="12" ry="7" fill="#FF6B6B" opacity=".15"/>
          <ellipse class="mejilla" cx="142" cy="154" rx="12" ry="7" fill="#FF6B6B" opacity=".15"/>
          <ellipse cx="100" cy="150" rx="10" ry="8" fill="#DE9F74"/>
          <path id="boca" class="pieza boca" d="M82 176 Q100 192 118 176" stroke="#3A1A12" stroke-width="5" fill="none" stroke-linecap="round"/>
          <ellipse id="grito" class="pieza" cx="100" cy="180" rx="13" ry="10" fill="#3A1A12" opacity="0"/>
          <path id="bigote" class="pieza" d="M72 166 q14 -14 28 -5 q14 -9 28 5 q-14 5 -28 -1 q-14 6 -28 1z" fill="#3A2A1E"/>
          <path id="sudor" class="pieza" d="M160 100 q8 11 0 18 q-8 -7 0 -18z" fill="#7FD8FF" opacity="0"/>
        </g>
      </svg>
    </div>
    <div class="habla">
      <p class="bocadillo" id="bocadillo">¡Está como nueva!</p>
      <p class="quien">Ramón · puesto 14</p>
    </div>
  </section>

  <!-- La tarjeta del producto -->
  <section class="tarjeta" id="tarjeta">
    <p class="etiqueta">Mercadillo del domingo · segunda mano</p>
    <div class="cabecera">
      <svg class="bici" viewBox="0 0 160 100" aria-hidden="true">
        <g class="pieza luz" id="biciLuz"><path d="M121 46 L160 36 L160 58 Z" fill="#FFE58A" opacity=".35"/><circle cx="120" cy="46" r="3.5" fill="#FFE58A"/></g>
        <g fill="none" stroke="#E8F5EE" stroke-width="3"><circle cx="34" cy="72" r="23"/><circle cx="126" cy="72" r="23"/></g>
        <g fill="none" stroke="#00FF7F" stroke-width="4" stroke-linecap="round" stroke-linejoin="round">
          <path d="M34 72 L72 72 L58 38 Z M72 72 L106 38 M58 38 L104 38 M106 30 L126 72"/>
          <path d="M58 38 L56 30 M48 29 L64 29 M106 30 L108 22 L120 20" stroke="#E8F5EE"/>
        </g>
        <circle cx="72" cy="72" r="5" fill="#E8F5EE"/>
        <g class="pieza" id="biciCesta"><rect x="110" y="24" width="28" height="17" rx="3" fill="#C98B45"/><path d="M116 24v17M123 24v17M130 24v17" stroke="#8A5A26" stroke-width="2"/></g>
        <g class="pieza" id="biciCasco"><path d="M44 27 a12 11 0 0 1 24 0 z" fill="#FF5E7E"/><path d="M50 20 h12" stroke="#fff" stroke-width="2" opacity=".6"/></g>
      </svg>
      <div>
        <h1>Bici de paseo «Gacela»</h1>
        <p>Revisada, cuadro de acero y 3 marchas.</p>
      </div>
    </div>

    <div class="precio-fila sin-regateo" id="precioFila">
      <div class="precio" aria-hidden="true"><span class="cifra"></span><span class="cifra"></span><span class="cifra"></span><span class="moneda">€</span></div>
      <div class="antes"><s>240 €</s><span class="ahorro" id="ahorro">−0 €</span></div>
    </div>

    <ul class="extras" id="extras" aria-label="Lo que incluye">
      <li data-hasta="232"><svg viewBox="0 0 24 24"><path d="M4 15a8 8 0 0 1 16 0z M4 15h17 M12 7v8"/></svg><span class="texto">Casco de regalo</span><span class="valor">+15 €</span></li>
      <li data-hasta="220"><svg viewBox="0 0 24 24"><path d="M4 10h16l-2 9H6z M8 10l4-5 4 5 M9 13v3 M15 13v3"/></svg><span class="texto">Cesta de mimbre</span><span class="valor">+12 €</span></li>
      <li data-hasta="208"><svg viewBox="0 0 24 24"><path d="M9 18h6 M10 21h4 M12 3a6 6 0 0 0-4 10.5c1 1 1.5 2 1.5 3.5h5c0-1.5.5-2.5 1.5-3.5A6 6 0 0 0 12 3z"/></svg><span class="texto">Luces delante y detrás</span><span class="valor">+10 €</span></li>
      <li data-hasta="196"><svg viewBox="0 0 24 24"><path d="M2 7h11v9H2z M13 10h5l3 3v3h-8 M6 19a2 2 0 1 0 0-.1 M17 19a2 2 0 1 0 0-.1"/></svg><span class="texto">Te la llevo a casa</span><span class="valor">+15 €</span></li>
      <li data-hasta="184"><svg viewBox="0 0 24 24"><path d="M12 3l8 3v6c0 5-3.5 8-8 9-4.5-1-8-4-8-9V6z M8.5 12l2.5 2.5 4.5-5"/></svg><span class="texto">Garantía de 6 meses</span><span class="valor">+20 €</span></li>
    </ul>

    <div class="pista-txt"><span><span class="flecha">←</span> <b>Desliza para regatear</b></span><span>Tu oferta</span></div>
    <div class="deslizador" id="deslizador">
      <div class="riel"><div class="relleno" id="relleno"></div><div class="zona-roja" id="zonaRoja"></div></div>
      <div class="tirador" id="tirador">‹›</div>
      <input type="range" id="oferta" min="140" max="240" step="1" value="240"
             aria-label="Tu oferta por la bici" aria-valuetext="240 euros">
    </div>

    <button class="cta" id="cta" type="button">Lo quiero por 240 €</button>

    <div class="sello" id="sello" role="status"><span class="manos">🤝</span><b>¡Trato hecho!</b><small id="selloPrecio">por 175 €</small></div>
  </section>
</main>

<div class="dedo" id="dedo" aria-hidden="true"></div>
<div class="confeti" id="confeti" aria-hidden="true"></div>
<p class="aviso" id="aviso" role="status"></p>