Magic Date Ball 2.0

by scottgriv

HTML

<div class="toy">
  <form class="ball">
    <div class="window"></div>
    <div class="eight">
      <span>8</span>
    </div>

    <div class="answers">
      <!-- Affirmatives -->
      <input id="affirmative1" type="radio" name="answer" required />
      <div class="answer up">It is<br />certain</div>
      <input id="affirmative2" type="radio" name="answer" required />
      <div class="answer up">It is<br />decidedly<br />so</div>
      <input id="affirmative3" type="radio" name="answer" required />
      <div class="answer down">Without<br />a<br />doubt</div>
      <input id="affirmative4" type="radio" name="answer" required />
      <div class="answer up">Yes<br />definitely</div>
      <input id="affirmative5" type="radio" name="answer" required />
      <div class="answer down">You may<br />rely<br />on it</div>
      <input id="affirmative6" type="radio" name="answer" required />
      <div class="answer down">As I<br />see it,<br />yes</div>
      <input id="affirmative7" type="radio" name="answer" required />
      <div class="answer up">Most<br />likely</div>
      <input id="affirmative8" type="radio" name="answer" required />
      <div class="answer down">Outlook<br />good</div>
      <input id="affirmative9" type="radio" name="answer" required />
      <div class="answer down">Yes</div>
      <input id="affirmative10" type="radio" name="answer" required />
      <div class="answer down">Signs<br />point to<br />yes</div>

      <!-- Non-comittals -->
      <input id="non-committal1" type="radio" name="answer" required />
      <div class="answer down">Reply hazy<br />try<br />again</div>
      <input id="non-committal2" type="radio" name="answer" required />
      <div class="answer up">Ask<br />again<br />later</div>
      <input id="non-committal3" type="radio" name="answer" required />
      <div class="answer down">Better not<br />tell you<br />now</div>
      <input id="non-committal4" type="radio" name="answer" required />
      <div...

CSS

body {
  margin: 0;
  background-color: #222; /* Dark gray background */
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100vh; /* Ensures full-page coverage */
  overflow: hidden;
}

.toy {
  position: relative;
  margin: 5vmin auto;
  width: 90vmin;
}

.ball {
  position: relative;
  border-radius: 50%;
  width: 90vmin;
  height: 90vmin;
  overflow: hidden;
  background: radial-gradient(
    circle at 30vmin 30vmin,
    #222 10%,
    /* Dark gray center */ #111 40%,
    /* Deeper black-gray */ #000 70% /* Almost pure black */
  );
  box-shadow:
    inset 0 0 12vmin rgba(0, 0, 0, 0.9),
    0 0 5vmin rgba(0, 0, 0, 0.8); /* Adds depth */
}

/* Answers */
.answers {
  width: 90vmin;
  height: 90vmin;
}

.answer {
  position: absolute;
  justify-content: center;
  align-items: center;
  display: flex;
  left: 36vmin;
  top: 34.6vmin;
  width: 18vmin;
  height: 20.8vmin;
  color: white;
  text-align: center;
  font-family: sans-serif;
  font-variant: small-caps;
  font-size: 2.1vmin;
  line-height: 2.4vmin;
  opacity: 0;
  transition: opacity 1s;
  animation: floating 6s linear infinite;
}

/* Triangle */
.answer::before {
  content: "";
  z-index: -1;
  position: absolute;
  left: 0;
  border-left: 8.8vmin solid transparent;
  border-right: 8.8vmin solid transparent;
  border-radius: 0.4vmin;
  width: 0.4vmin;
  height: 0;
}

.answer.up::before {
  top: 0.4vmin;
  border-bottom: 15.2vmin solid #1c23e8;
}

.answer.down::before {
  bottom: 0.4vmin;
  border-top: 15.2vmin solid #1c23e8;
}

/* Overlay */
.answer::after {
  content: "";
  z-index: 1;
  position: absolute;
  left: -7vmin;
  top: -5vmin;
  border-radius: 50%;
  width: 32vmin;
  height: 32vmin;
  font-family: sans-serif;
  background: linear-gradient(to left, rgba(0, 0, 0, 0.9), rgba(0, 0, 0, 0));
  animation: rotating 6s infinite linear;
}

/* Input control */
input {
  display: none;
}

input:checked + .answer {
  opacity: 1;
  transition: opacity 2s 1s;
}

form:active...

JavaScript

window.onload = function () {
  var ball = document.querySelector(".ball")
  var isPressed = false
  var hasActivated = false // Ensures first button press works

  ball.addEventListener("click", function () {
    const randomDate = generateFutureDate()
    triggerAnswerChange(randomDate)
    hasActivated = true
  })

  ball.addEventListener("touchstart", function () {
    const randomDate = generateFutureDate()
    triggerAnswerChange(randomDate)
    hasActivated = true
  })

  function generateFutureDate() {
    const today = new Date()
    const currentYear = today.getFullYear()
    let futureDate

    const start = new Date(today)
    start.setDate(today.getDate() + 1) // Start from tomorrow

    const end = new Date(currentYear, 11, 31) // Dec 31st

    do {
      // Calculate a random time between tomorrow and Dec 31st
      const timeDiff = end.getTime() - start.getTime()
      const randomTimeOffset = Math.floor(Math.random() * timeDiff)
      futureDate = new Date(start.getTime() + randomTimeOffset)

      // Round to nearest whole day
      futureDate.setHours(0, 0, 0, 0)
    } while (
      futureDate.getDay() === 0 || // Sunday
      futureDate.getDay() === 6 // Saturday
    )

    return formatDate(futureDate)
  }

  function generateRandomWeekdayInRange(weeksAhead) {
    var today = new Date()
    var futureDate = new Date(today)

    // Move to the start of the next full week (Monday)
    futureDate.setDate(today.getDate() + (8 - today.getDay()) + weeksAhead * 7)

    // Get a random weekday from Monday to Friday
    var randomOffset = getRandomInt(0, 4)
    futureDate.setDate(futureDate.getDate() + randomOffset)

    return formatDate(futureDate)
  }

  function formatDate(date) {
    var month = date.getMonth() + 1
    var day = date.getDate()
    var year = date.getFullYear()
    return `${padZero(month)}-${padZero(day)}-${year}`
  }

  function padZero(number) {
    return number.toString().padStart(2, "0")
  }

  function getRandomInt(min,...