← Volver

Cinematic Mascot Login

Production-level animated login with SVG mascot, smooth physics-like eye tracking, organic blinking, and realistic hand motion

UI
Preview
<section class="w-full min-h-screen bg-[#F5F6F7] flex items-center justify-center px-4">

  <div class="w-full max-w-sm bg-white border rounded-2xl p-8 shadow-sm text-center">

    <!-- MASCOT -->
    <div id="mascot" class="relative w-32 h-32 mx-auto mb-6">

      <svg viewBox="0 0 200 200" class="w-full h-full">

        <!-- Head -->
        <circle cx="100" cy="100" r="90" fill="#FFD54F"/>

        <!-- Eyes -->
        <g id="eyes">
          <ellipse cx="70" cy="90" rx="18" ry="20" fill="white"/>
          <ellipse cx="130" cy="90" rx="18" ry="20" fill="white"/>

          <circle id="pupilL" cx="70" cy="90" r="7" fill="black"/>
          <circle id="pupilR" cx="130" cy="90" r="7" fill="black"/>
        </g>

      </svg>

      <!-- HANDS -->
      <div class="absolute inset-0 flex justify-between px-4 pointer-events-none">

        <div id="leftHand"
          class="w-10 h-10 bg-yellow-400 rounded-full transition-all duration-700 ease-[cubic-bezier(.34,1.56,.64,1)] transform translate-y-28">
        </div>

        <div id="rightHand"
          class="w-10 h-10 bg-yellow-400 rounded-full transition-all duration-700 ease-[cubic-bezier(.34,1.56,.64,1)] transform translate-y-28">
        </div>

      </div>

    </div>

    <h2 class="text-lg font-semibold text-gray-900">Welcome back</h2>

    <form class="mt-6 space-y-4">

      <input type="email"
        placeholder="Email"
        class="w-full px-4 py-2 border rounded-xl focus:ring-2 focus:ring-black outline-none"/>

      <div class="relative">
        <input id="password"
          type="password"
          placeholder="Password"
          class="w-full px-4 py-2 border rounded-xl focus:ring-2 focus:ring-black outline-none"/>

        <button type="button" id="toggle"
          class="absolute right-3 top-1/2 -translate-y-1/2 text-gray-500">
          πŸ‘
        </button>
      </div>

      <button class="w-full py-2 bg-black text-white rounded-xl hover:scale-105 transition">
        Login
      </button>

    </form>

  </div>

  <script>
    const container = document.getElementById("mascot");

    const pupilL = document.getElementById("pupilL");
    const pupilR = document.getElementById("pupilR");

    const leftHand = document.getElementById("leftHand");
    const rightHand = document.getElementById("rightHand");

    const password = document.getElementById("password");
    const toggle = document.getElementById("toggle");

    let visible = false;

    // πŸ‘‰ EYES FOLLOW (solo dentro del contenedor)
    container.addEventListener("mousemove", (e) => {
      const rect = container.getBoundingClientRect();

      const x = e.clientX - rect.left;
      const y = e.clientY - rect.top;

      function moveEye(cx, cy, pupil) {
        const dx = x - cx;
        const dy = y - cy;

        const angle = Math.atan2(dy, dx);
        const radius = 6;

        const targetX = cx + Math.cos(angle) * radius;
        const targetY = cy + Math.sin(angle) * radius;

        pupil.setAttribute("cx", targetX);
        pupil.setAttribute("cy", targetY);
      }

      moveEye(70, 90, pupilL);
      moveEye(130, 90, pupilR);
    });

    // πŸ‘‰ BLINK ORGÁNICO
    function blink() {
      pupilL.setAttribute("r", "1");
      pupilR.setAttribute("r", "1");

      setTimeout(() => {
        pupilL.setAttribute("r", "7");
        pupilR.setAttribute("r", "7");
      }, 120);

      const next = Math.random() * 4000 + 2000;
      setTimeout(blink, next);
    }
    blink();

    // πŸ‘‰ COVER EYES (suave)
    password.addEventListener("focus", () => {
      leftHand.style.transform = "translateY(40px) translateX(-10px)";
      rightHand.style.transform = "translateY(40px) translateX(10px)";

      pupilL.setAttribute("opacity", "0");
      pupilR.setAttribute("opacity", "0");
    });

    // πŸ‘‰ RESET
    password.addEventListener("blur", () => {
      if (!visible) {
        leftHand.style.transform = "translateY(110px)";
        rightHand.style.transform = "translateY(110px)";

        pupilL.setAttribute("opacity", "1");
        pupilR.setAttribute("opacity", "1");
      }
    });

    // πŸ‘‰ TOGGLE PEEK (UN OJO)
    toggle.addEventListener("click", () => {
      visible = !visible;
      password.type = visible ? "text" : "password";

      if (visible) {
        leftHand.style.transform = "translateY(40px) translateX(-25px)";
        rightHand.style.transform = "translateY(40px) translateX(10px)";

        pupilL.setAttribute("opacity", "1");
        pupilR.setAttribute("opacity", "0");
      } else {
        leftHand.style.transform = "translateY(40px) translateX(-10px)";
        rightHand.style.transform = "translateY(40px) translateX(10px)";

        pupilL.setAttribute("opacity", "0");
        pupilR.setAttribute("opacity", "0");
      }
    });
  </script>

</section>