skinny

OTP VERIFICATION

Cards 1

About

Customize at your own, you can use it for many things

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds OTP VERIFICATION to your project and fits it to your theme.

Code

How OTP VERIFICATION is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import React, { useState, useEffect, useRef } from "react";

export interface VaultUnlockCardProps {
  onSuccess?: () => void;
}

export default function VaultUnlockCard({ onSuccess }: VaultUnlockCardProps) {
  const [pin, setPin] = useState("");
  const [isUnlocked, setIsUnlocked] = useState(false);
  const [isShutterClosed, setIsShutterClosed] = useState(false);
  const [isEmpActive, setIsEmpActive] = useState(false);
  const [progressWidth, setProgressWidth] = useState(0);
  const [isResetOpen, setIsResetOpen] = useState(false);
  const [glarePos, setGlarePos] = useState({ x: 50, y: 50 });

  const cardRef = useRef<HTMLDivElement>(null);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    inputRef.current?.focus();
  }, []);

  useEffect(() => {
    const handleGlobalMouseMove = (e: MouseEvent) => {
      const card = cardRef.current;
      if (!card) return;

      const rect = card.getBoundingClientRect();
      const cardCenterX = rect.left + rect.width / 2;
      const cardCenterY = rect.top + rect.height / 2;

      const deltaX = (e.clientX - cardCenterX) / (window.innerWidth / 2);
      const deltaY = (e.clientY - cardCenterY) / (window.innerHeight / 2);

      const maxTilt = 9;
      const rx = Math.max(-maxTilt, Math.min(maxTilt, -deltaY * maxTilt));
      const ry = Math.max(-maxTilt, Math.min(maxTilt, deltaX * maxTilt));

      const gx = ((e.clientX - rect.left) / rect.width) * 100;
      const gy = ((e.clientY - rect.top) / rect.height) * 100;
      setGlarePos({ x: gx, y: gy });

      card.style.transform = `perspective(1000px) rotateX(${rx.toFixed(2)}deg) rotateY(${ry.toFixed(2)}deg)`;
    };

    window.addEventListener("mousemove", handleGlobalMouseMove);
    return () => {
      window.removeEventListener("mousemove", handleGlobalMouseMove);
    };
  }, []);

  const handleKeyDown = (e: React.KeyboardEvent) => {
    if (isUnlocked || isShutterClosed) return;

    if (e.key === "Backspace") {
      setPin((prev) => prev.slice(0, -1));
      return;
    }

    if (/^[0-9]$/.test(e.key)) {
      if (pin.length < 4) {
        const nextPin = pin + e.key;
        setPin(nextPin);

        if (nextPin.length === 4) {
          triggerUnlockSequence();
        }
      }
    }
  };

  const triggerUnlockSequence = () => {
    setTimeout(() => {
      setIsShutterClosed(true);

      setTimeout(() => {
        setIsEmpActive(true);

        setTimeout(() => {
          setIsUnlocked(true);
          setIsShutterClosed(false);
          setIsEmpActive(false);

          setTimeout(() => {
            setProgressWidth(100);
          }, 80);

          onSuccess?.();
        }, 480);
      }, 320);
    }, 300);
  };

  const resetVault = () => {
    setPin("");
    setIsUnlocked(false);
    setIsShutterClosed(false);
    setIsEmpActive(false);
    setProgressWidth(0);
    setTimeout(() => inputRef.current?.focus(), 80);
  };

  return (
    <div
      style={{
        minHeight: "100vh",
        width: "100%",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
        padding: "24px",
        backgroundColor: "#03060a",
        backgroundImage:
          "radial-gradient(circle at 50% 25%, rgba(16, 227, 150, 0.16) 0%, rgba(0, 240, 255, 0.05) 30%, transparent 70%), radial-gradient(ellipse 80% 50% at 50% 90%, rgba(10, 16, 28, 0.85) 0%, transparent 80%)",
        userSelect: "none",
        fontFamily:
          "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
      }}
      onClick={() => inputRef.current?.focus()}
    >
      <input
        ref={inputRef}
        type="password"
        inputMode="numeric"
        maxLength={4}
        value={pin}
        onChange={(e) => {
          const val = e.target.value.replace(/\D/g, "").slice(0, 4);
          setPin(val);
          if (val.length === 4) triggerUnlockSequence();
        }}
        onKeyDown={handleKeyDown}
        style={{
          position: "absolute",
          opacity: 0,
          pointerEvents: "none",
          top: "-100px",
        }}
        autoFocus
      />

      <div
        ref={cardRef}
        style={{
          maxWidth: "530px",
          width: "100%",
          borderRadius: "36px",
          padding: "24px 44px 34px 44px",
          backgroundColor: "rgba(10, 15, 24, 0.65)",
          backdropFilter: "blur(24px)",
          WebkitBackdropFilter: "blur(24px)",
          border: "1px solid rgba(255, 255, 255, 0.12)",
          borderTop: "1.5px solid rgba(16, 227, 150, 0.55)",
          boxShadow: isEmpActive
            ? "0 0 50px rgba(16, 227, 150, 0.8), 0 25px 70px -15px rgba(0, 0, 0, 0.8)"
            : "0 25px 70px -15px rgba(0, 0, 0, 0.8), 0 0 45px -10px rgba(16, 227, 150, 0.3), inset 0 1px 1px 0 rgba(255, 255, 255, 0.22)",
          position: "relative",
          overflow: "hidden",
          display: "flex",
          flexDirection: "column",
          alignItems: "center",
          textAlign: "center",
          transformStyle: "preserve-3d",
          transition: "transform 0.15s ease-out, box-shadow 0.3s ease",
        }}
      >
        <div
          style={{
            position: "absolute",
            inset: 0,
            borderRadius: "inherit",
            background: `radial-gradient(480px circle at ${glarePos.x}% ${glarePos.y}%, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.02) 40%, transparent 75%)`,
            pointerEvents: "none",
            mixBlendMode: "screen",
            zIndex: 6,
          }}
        />

        {isShutterClosed && (
          <>
            <div
              style={{
                position: "absolute",
                top: 0,
                left: 0,
                right: 0,
                height: "50%",
                background:
                  "radial-gradient(circle at 50% 100%, #121824 0%, #06080d 100%)",
                borderBottom: "2px solid #10e396",
                boxShadow: "0 10px 35px rgba(16, 227, 150, 0.6)",
                zIndex: 40,
                display: "flex",
                alignItems: "flex-end",
                justifyContent: "center",
                paddingBottom: "8px",
              }}
            >
              <div
                style={{
                  fontFamily: "monospace",
                  fontSize: "10px",
                  letterSpacing: "0.15em",
                  color: "#10e396",
                  fontWeight: "bold",
                  display: "flex",
                  alignItems: "center",
                  gap: "8px",
                }}
              >
                <span
                  style={{
                    width: "6px",
                    height: "6px",
                    borderRadius: "50%",
                    backgroundColor: "#10e396",
                  }}
                />
                SHUTTER ENGAGED // KINETIC SECURE
              </div>
            </div>
            <div
              style={{
                position: "absolute",
                bottom: 0,
                left: 0,
                right: 0,
                height: "50%",
                background:
                  "radial-gradient(circle at 50% 0%, #121824 0%, #06080d 100%)",
                borderTop: "2px solid #10e396",
                boxShadow: "0 -10px 35px rgba(16, 227, 150, 0.6)",
                zIndex: 40,
                display: "flex",
                alignItems: "flex-start",
                justifyContent: "center",
                paddingTop: "8px",
              }}
            >
              <div
                style={{
                  fontFamily: "monospace",
                  fontSize: "9px",
                  letterSpacing: "0.15em",
                  color: "rgba(16, 227, 150, 0.8)",
                }}
              >
                QUANTUM CRYPTOGRAPHIC PROTOCOL
              </div>
            </div>
          </>
        )}

        <div
          style={{
            width: "48px",
            height: "4px",
            borderRadius: "9999px",
            backgroundColor: "#00f0ff",
            boxShadow: "0 0 14px rgba(0, 240, 255, 0.85)",
            marginBottom: "28px",
            marginTop: "2px",
            position: "relative",
            zIndex: 10,
          }}
        />

        {!isUnlocked ? (
          <div
            style={{
              width: "100%",
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              position: "relative",
              zIndex: 10,
            }}
          >
            <div
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: "8px",
                padding: "4px 12px",
                borderRadius: "9999px",
                backgroundColor: "rgba(255, 255, 255, 0.04)",
                border: "1px solid rgba(255, 255, 255, 0.1)",
                marginBottom: "12px",
              }}
            >
              <span
                style={{
                  width: "6px",
                  height: "6px",
                  borderRadius: "9999px",
                  backgroundColor: "#00f0ff",
                  boxShadow: "0 0 8px #00f0ff",
                }}
              />
              <span
                style={{
                  fontFamily: "monospace",
                  fontSize: "11px",
                  fontWeight: "bold",
                  letterSpacing: "0.08em",
                  color: "#67e8f9",
                }}
              >
                SKINNY OTP // V4.8
              </span>
            </div>

            <h1
              style={{
                fontFamily: "'Newsreader', Georgia, serif",
                fontStyle: "italic",
                fontSize: "33px",
                fontWeight: "800",
                color: "#ffffff",
                letterSpacing: "-0.02em",
                marginBottom: "28px",
                lineHeight: 1.15,
                textShadow: "0 2px 14px rgba(255, 255, 255, 0.2)",
              }}
            >
              Desbloquear Bóveda
            </h1>

            <div
              style={{
                display: "grid",
                gridTemplateColumns: "repeat(4, 1fr)",
                gap: "16px",
                width: "100%",
                maxWidth: "380px",
                marginBottom: "28px",
                cursor: "pointer",
              }}
              onClick={() => inputRef.current?.focus()}
            >
              {[0, 1, 2, 3].map((idx) => {
                const isFilled = pin.length > idx;
                const isActive = pin.length === idx;

                return (
                  <div
                    key={idx}
                    style={{
                      height: "88px",
                      minHeight: "88px",
                      borderRadius: "22px",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                      position: "relative",
                      overflow: "hidden",
                      backgroundColor: isActive
                        ? "rgba(255, 255, 255, 0.05)"
                        : "rgba(255, 255, 255, 0.02)",
                      backdropFilter: "blur(10px)",
                      WebkitBackdropFilter: "blur(10px)",
                      border: isActive
                        ? "1.5px solid rgba(255, 255, 255, 0.35)"
                        : "1.5px solid rgba(255, 255, 255, 0.12)",
                      boxShadow: isActive
                        ? "0 0 20px rgba(255, 255, 255, 0.12), inset 0 2px 6px rgba(0, 0, 0, 0.75)"
                        : "inset 0 3px 6px rgba(0, 0, 0, 0.75), 0 4px 10px rgba(0, 0, 0, 0.35)",
                      transition: "all 0.2s ease",
                    }}
                  >
                    {isActive && (
                      <div
                        style={{
                          width: "3px",
                          height: "32px",
                          borderRadius: "9999px",
                          backgroundColor: "#00f0ff",
                          boxShadow: "0 0 12px #00f0ff, 0 0 24px #00f0ff",
                        }}
                      />
                    )}
                    {isFilled && (
                      <span
                        style={{
                          color: "#ffffff",
                          fontFamily: "monospace",
                          fontSize: "24px",
                          fontWeight: "bold",
                          textShadow: "0 0 12px rgba(0, 240, 255, 0.9)",
                        }}
                      >
                        ●
                      </span>
                    )}
                  </div>
                );
              })}
            </div>

            <footer style={{ paddingTop: "8px" }}>
              <button
                type="button"
                onClick={(e) => {
                  e.stopPropagation();
                  setIsResetOpen(true);
                }}
                style={{
                  display: "inline-flex",
                  alignItems: "center",
                  gap: "8px",
                  fontSize: "13px",
                  color: "#a1a1aa",
                  background: "transparent",
                  border: "none",
                  outline: "none",
                  cursor: "pointer",
                }}
              >
                <span
                  style={{
                    width: "14px",
                    height: "14px",
                    borderRadius: "50%",
                    border: "1.5px solid rgba(161, 161, 170, 0.7)",
                    display: "inline-block",
                    flexShrink: 0,
                  }}
                />
                <span>Restablecer Bóveda (Olvidé mi PIN)</span>
              </button>
            </footer>
          </div>
        ) : (
          <div
            style={{
              width: "100%",
              display: "flex",
              flexDirection: "column",
              alignItems: "center",
              justifyContent: "center",
              padding: "8px 0",
              position: "relative",
              zIndex: 10,
            }}
          >
            <div
              style={{
                width: "100px",
                height: "100px",
                position: "relative",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                marginBottom: "16px",
              }}
            >
              <div
                style={{
                  position: "absolute",
                  inset: 0,
                  borderRadius: "50%",
                  border: "2px dashed rgba(52, 211, 153, 0.4)",
                }}
              />
              <div
                style={{
                  position: "absolute",
                  inset: "8px",
                  borderRadius: "50%",
                  border: "1px dotted rgba(6, 182, 212, 0.5)",
                }}
              />
              <div
                style={{
                  width: "72px",
                  height: "72px",
                  borderRadius: "50%",
                  backgroundColor: "rgba(6, 78, 59, 0.85)",
                  border: "2px solid #10e396",
                  boxShadow: "0 0 35px rgba(16, 227, 150, 0.6)",
                  display: "flex",
                  alignItems: "center",
                  justifyContent: "center",
                  zIndex: 2,
                }}
              >
                <svg
                  style={{ width: "36px", height: "36px" }}
                  fill="none"
                  stroke="#10e396"
                  strokeWidth="3"
                  viewBox="0 0 24 24"
                >
                  <path
                    d="M4.5 12.75l6 6 9-13.5"
                    strokeLinecap="round"
                    strokeLinejoin="round"
                  />
                </svg>
              </div>
            </div>

            <div
              style={{
                width: "100%",
                maxWidth: "280px",
                height: "8px",
                borderRadius: "9999px",
                backgroundColor: "rgba(255, 255, 255, 0.04)",
                border: "1px solid rgba(16, 227, 150, 0.3)",
                padding: "2px",
                marginBottom: "14px",
                overflow: "hidden",
              }}
            >
              <div
                style={{
                  height: "100%",
                  width: `${progressWidth}%`,
                  borderRadius: "9999px",
                  background:
                    "linear-gradient(to right, #00f0ff, #10e396, #6ee7b7)",
                  boxShadow: "0 0 12px #10e396",
                  transition: "width 0.7s ease",
                }}
              />
            </div>

            <div
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: "8px",
                padding: "4px 12px",
                borderRadius: "9999px",
                backgroundColor: "rgba(6, 78, 59, 0.8)",
                border: "1px solid rgba(16, 227, 150, 0.5)",
                color: "#6ee7b7",
                fontSize: "12px",
                fontFamily: "monospace",
                fontWeight: "bold",
                marginBottom: "8px",
                boxShadow: "0 0 16px rgba(16, 227, 150, 0.3)",
              }}
            >
              <span
                style={{
                  width: "8px",
                  height: "8px",
                  borderRadius: "50%",
                  backgroundColor: "#10e396",
                }}
              />
              AUTORIZACIÓN COMPLETA
            </div>

            <h2
              style={{
                fontSize: "26px",
                fontWeight: "800",
                color: "#ffffff",
                marginBottom: "20px",
                fontFamily: "'Newsreader', Georgia, serif",
                fontStyle: "italic",
                textShadow: "0 0 12px rgba(16, 227, 150, 0.3)",
              }}
            >
              ¡Bóveda Desbloqueada!
            </h2>

            <button
              type="button"
              onClick={resetVault}
              style={{
                width: "100%",
                maxWidth: "320px",
                padding: "14px 24px",
                borderRadius: "16px",
                background:
                  "linear-gradient(to right, #10e396, #2dd4bf, #10e396)",
                color: "#020617",
                fontWeight: "800",
                fontSize: "14px",
                boxShadow: "0 0 30px rgba(16, 227, 150, 0.5)",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                gap: "8px",
                cursor: "pointer",
                border: "none",
                outline: "none",
              }}
            >
              <span>Continuar a la Bóveda</span>
              <svg
                style={{ width: "16px", height: "16px" }}
                fill="none"
                stroke="currentColor"
                strokeWidth="2.5"
                viewBox="0 0 24 24"
              >
                <path
                  d="M14 5l7 7m0 0l-7 7m7-7H3"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            </button>
          </div>
        )}
      </div>

      {isResetOpen && (
        <div
          style={{
            position: "fixed",
            inset: 0,
            zIndex: 50,
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
            padding: "16px",
            backgroundColor: "rgba(0, 0, 0, 0.75)",
            backdropFilter: "blur(20px)",
          }}
          onClick={() => setIsResetOpen(false)}
        >
          <div
            style={{
              maxWidth: "380px",
              width: "100%",
              borderRadius: "24px",
              padding: "28px",
              backgroundColor: "rgba(10, 15, 24, 0.95)",
              border: "1px solid rgba(255, 255, 255, 0.12)",
              boxShadow: "0 25px 60px rgba(0, 0, 0, 0.8)",
              textAlign: "left",
            }}
            onClick={(e) => e.stopPropagation()}
          >
            <div
              style={{
                width: "40px",
                height: "40px",
                borderRadius: "14px",
                backgroundColor: "rgba(0, 240, 255, 0.15)",
                border: "1px solid rgba(0, 240, 255, 0.3)",
                display: "flex",
                alignItems: "center",
                justifyContent: "center",
                color: "#00f0ff",
                marginBottom: "16px",
              }}
            >
              <svg
                style={{ width: "20px", height: "20px" }}
                fill="none"
                stroke="currentColor"
                strokeWidth="2"
                viewBox="0 0 24 24"
              >
                <path
                  d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"
                  strokeLinecap="round"
                  strokeLinejoin="round"
                />
              </svg>
            </div>

            <h3
              style={{
                fontSize: "18px",
                fontWeight: "bold",
                color: "#ffffff",
                marginBottom: "6px",
              }}
            >
              Recuperación de PIN
            </h3>
            <p
              style={{
                fontSize: "12px",
                color: "#a1a1aa",
                marginBottom: "16px",
                lineHeight: 1.5,
              }}
            >
              Para restaurar tu PIN maestro necesitas tu frase de respaldo de 12
              palabras o tu llave de seguridad hardware.
            </p>

            <button
              type="button"
              onClick={() => {
                setIsResetOpen(false);
                resetVault();
              }}
              style={{
                width: "100%",
                fontWeight: 600,
                padding: "10px 16px",
                borderRadius: "12px",
                backgroundColor: "rgba(255, 255, 255, 0.1)",
                color: "#ffffff",
                border: "1px solid rgba(255, 255, 255, 0.1)",
                display: "flex",
                alignItems: "center",
                justifyContent: "space-between",
                fontSize: "12px",
                cursor: "pointer",
                marginBottom: "10px",
              }}
            >
              <span>Usar Frase Semilla de 12 Palabras</span>
              <span>→</span>
            </button>

            <button
              type="button"
              onClick={() => {
                setIsResetOpen(false);
                inputRef.current?.focus();
              }}
              style={{
                width: "100%",
                padding: "10px 16px",
                borderRadius: "12px",
                backgroundColor: "transparent",
                color: "#a1a1aa",
                border: "none",
                fontSize: "12px",
                cursor: "pointer",
              }}
            >
              Regresar a Desbloqueo
            </button>
          </div>
        </div>
      )}
    </div>
  );
}

export { VaultUnlockCard };

More in Cards

View all
  • Stats cards with links

    Stats cards with links

    33
  • Course Design Cards

    Course Design Cards

    1.1K
  • Card

    Card

    70
  • Statistics Card 2

    Statistics Card 2

    264
  • Statistics Card 10

    Statistics Card 10

    263
  • Card

    Card

    17
  • Card Studio

    Card Studio

    9
  • The Item one

    The Item one

    3