<div id="wrapper">
  <div id="reader" data-status="">
    <div class="top">
      <div class="screen">
        <div id="message"></div>
      </div>
      <div class="lights">
        <div class="light red"></div>
        <div class="light green"></div>
      </div>
    </div>
    <div id="card">
      <div id="photo"></div>
    </div>
    <div class="bottom"></div>
  </div>
</div>
* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  width: 100vw;
  margin: 0;
  display: flex;
  justify-content: center;
  align-items: center;
  overflow: hidden;
}

#wrapper {
  position: relative;
  width: 400px;
  height: 250px;
}

#reader {
  width: 100%;
  height: 400px;
}

#reader .top {
  position: relative;
  width: 100%;
  height: 125px;
  z-index: 100;
}

#reader .bottom {
  width: 100%;
  height: 75px;
}

#reader .lights {
  position: absolute;
  right: 10px;
  bottom: 10px;
}

.light {
  display: inline-block;
  width: 25px;
  height: 25px;
}

#card {
  display: flex;
  justify-content: flex-start;
  align-items: center;
  position: absolute;
  margin-top: -75px;
  width: 250px;
  height: 150px;
  transform: translateX(-125px);
}

#card.slide {
  transition: transform 1s;
}

body {
  background-color: #3d4a4a;
}

#wrapper {
  background-color: #414141;
  border: 4px solid #000;
}

#reader .top,
#reader .bottom {
  background-color: #adadad;
  border: 4px solid #000;
}

#reader .top {
  width: calc(100% + 8px);
  padding: 16px;
  margin: -4px 0 0 -3px;
}

#reader .top:before {
  content: "";
  position: absolute;
  left: 20px;
  bottom: 12px;
  width: 140px; 
  height: 10px; 
  background-color: #000;
}

#reader .top:after {
  content: "";
  position: absolute;
  left: 150px;
  bottom: 12px;
  width: 0; 
  height: 0; 
  border-bottom: 30px solid #000;
  border-right: 60px solid transparent;
}

#reader .bottom {
  width: calc(100% + 8px);
  margin: 10px 0 0 -4px;
  border-top-left-radius: 30px;
  box-shadow: inset -6px -6px #555555,
              inset 6px 6px #555555,
              0 30px rgba(0, 0, 0, 0.1);
}

#reader .screen {
  border: 4px solid #000;
  padding: 4px;
  margin: 0 12px;
  background-color: #164a38;
}

#message {
  font-family: 'DSEG14Classic', sans-serif;
  font-size: 20px;
  color: #c5d6d0;
  text-transform: uppercase;
  animation: jitter 3s infinite steps(2);
}

#message:after {
  content: "Please swipe card";
}

[data-status="invalid"] #message:after {
  content: "Bad read. Try again.";
}

[data-status="slow"] #message:after {
  content: "Too slow. Try again.";
}

[data-status="fast"] #message:after {
  content: "Too fast. Try again.";
}

[data-status="valid"] #message:after {
  content: "Accepted. Thank you.";
}

.light {
  border: 4px solid #000;
  border-radius: 50%;
  box-shadow: 0 4px #555555;
  filter: saturate(0.6) brightness(0.7);
}

.red {
  background-color: #f52818;
}

.green {
  background-color: #3dd022;
}

[data-status="invalid"] .red,
[data-status="slow"] .red,
[data-status="fast"] .red,
[data-status="valid"] .green {
  filter: none;
}

#card {
  background-color: #dfdfdf;
  border: 2px solid #7f7f7f;
  border-radius: 15px;
  cursor: grab;
}

#photo {
  position: relative;
  width: 75px;
  height: 100px;
  background-color: #aeaeae;
  border: 2px solid #292929;
  margin-left: 16px;
  overflow: hidden;
  pointer-events: none;
}

#photo:before {
  content: "";
  position: absolute;
  right: 0;
  bottom: 0;
  width: 55px;
  height: 80px;
  background-color: #646464;
  border: 2px solid #434343;
  border-bottom-width: 0;
  border-radius: 30px 40px 5px 10px / 45px 60px 0 0;
  box-shadow: inset -3px -3px 0 3px rgba(0, 0, 0, 0.1);
}

#photo:after {
  content: "";
  position: absolute;
  right: 30px;
  bottom: 45px;
  width: 30px;
  height: 15px;
  background-color: #a6a6a6;
  border: 2px solid #434343;
  border-radius: 4px 6px 4px 4px;
  box-shadow: inset 1px 3px 0 rgba(255, 255, 255, 0.6),
              4px 4px 0 rgba(0, 0, 0, 0.1);
}

@font-face {
  font-family: 'DSEG14Classic';
  src: url('https://thomaspark.co/projects/among-us-card-swipe/fonts/DSEG14-Classic/DSEG14Classic-Regular.woff2') format('woff2'),
       url('https://thomaspark.co/projects/among-us-card-swipe/fonts/DSEG14-Classic/DSEG14Classic-Regular.woff') format('woff'),
       url('https://thomaspark.co/projects/among-us-card-swipe/fonts/DSEG14-Classic/DSEG14Classic-Regular.ttf')  format('truetype');
}

@keyframes jitter {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(5px);
  }
}
const card = document.getElementById('card');
const reader = document.getElementById('reader');
let active = false;
let initialX;
let timeStart, timeEnd;
const soundAccepted = new Audio('https://thomaspark.co/projects/among-us-card-swipe/audio/CardAccepted.mp3');
const soundDenied = new Audio('https://thomaspark.co/projects/among-us-card-swipe/audio/CardDenied.mp3');

document.addEventListener('mousedown', dragStart);
document.addEventListener('mouseup', dragEnd);
document.addEventListener('mousemove', drag);
document.addEventListener('touchstart', dragStart);
document.addEventListener('touchend', dragEnd);
document.addEventListener('touchmove', drag);

function dragStart(e) {
  if (e.target !== card) return;

  if (e.type === 'touchstart') {
    initialX = e.touches[0].clientX;
  } else {
    initialX = e.clientX;
  }

  timeStart = performance.now();
  card.classList.remove('slide');
  active = true;
}

function dragEnd(e) {
  if (!active) return;

  e.preventDefault();
  let x;
  let status;

  if (e.type === 'touchend') {
    x = e.touches[0].clientX - initialX;
  } else {
    x = e.clientX - initialX;
  }

  if (x < reader.offsetWidth) {
    status = 'invalid';
  }

  timeEnd = performance.now();
  card.classList.add('slide');
  active = false;

  setTranslate(0);
  setStatus(status);
}

function drag(e) {
  if (!active) return;

  e.preventDefault();
  let x;

  if (e.type === 'touchmove') {
    x = e.touches[0].clientX - initialX;
  } else {
    x = e.clientX - initialX;
  }

  setTranslate(x);
}

function setTranslate(x) {
  if (x < 0) {
    x = 0;
  } else if (x > reader.offsetWidth) {
    x = reader.offsetWidth;
  }

  x -= (card.offsetWidth / 2);

  card.style.transform = 'translateX(' + x + 'px)';
}

function setStatus(status) {
  if (typeof status === 'undefined') {
    let duration = timeEnd - timeStart;

    if (duration > 700) {
      status = 'slow';
    } else if (duration < 400) {
      status = 'fast';
    } else {
      status = 'valid';
    }
  }

  reader.dataset.status = status;
  playAudio(status);
}

function playAudio(status) {
  soundDenied.pause();
  soundAccepted.pause();
  soundDenied.currentTime = 0;
  soundAccepted.currentTime = 0;

  if (status === 'valid') {
    soundAccepted.play();
  } else {
    soundDenied.play();
  }
}

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.