<head>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>

<body>
  <div id="app">

    <div class="container">
      <p class="time" v-show="ongoing || stop">{{ display_time }}</p>
      <div class="input-section" v-show="!(ongoing || stop)">
        <input type="number" v-model="set_hour" min="0">hr
        <input type="number" v-model="set_min" min="0" max="59">min
        <input type="number" v-model="set_sec" min="0" max="59">sec
      </div>
      <div class="button-section">
        <button class="btn btn-primary" v-on:click="startTimer()" v-show="!(stop || ongoing)" :disabled="set_hour == 0 && set_min== 0 && set_sec == 0">Start</button>
        <button class="btn btn-danger" v-on:click="stopTimer()" v-show="(ongoing && !stop)">Stop</button>
        <button class="btn btn-info" v-on:click="resumeTimer()" v-show="(!ongoing && stop)">Resume</button>
        <button class="btn btn-secondary" v-on:click="resetTimer()" v-show="(!ongoing && stop) || (ongoing && !stop)">Reset</button>
      </div>
    </div>

    <div class="water" :style="[running]" :class="{timeup : !(ongoing || stop)}">
      <svg class="waterwave back" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 749.866 619.483">
        <path id="path_11" class="cls-1" d="M.127-35.3S50.2,6.431,99.143,6.489,164.71-12.845,193.368-35.1s53.288-29.32,96.2-29.058,85.049,29.274,85.049,29.274l.626,590.2L-.626,555.21Z" transform="translate(374.626 64.167)" />
        <path id="path_21" class="cls-1" d="M.127-35.3S50.2,6.431,99.143,6.489,164.71-12.845,193.368-35.1s53.288-29.32,96.2-29.058,85.049,29.274,85.049,29.274l.626,590.2L-.626,555.21Z" transform="translate(0.626 64.167)" />
      </svg>
      <svg class="waterwave front" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 749.866 619.483">
        <g transform="translate(0.126 -192.333)">
          <path id="path_1" class="cls-1" d="M374.487-35.3S324.41,6.431,275.472,6.489,209.905-12.845,181.247-35.1s-53.288-29.32-96.2-29.058S0-34.885,0-34.885l-.626,590.2,375.866-.107Z" transform="translate(0.5 256.5)" />
          <path id="path_2" class="cls-1" d="M374.487-35.3S324.41,6.431,275.472,6.489,209.905-12.845,181.247-35.1s-53.288-29.32-96.2-29.058S0-34.885,0-34.885l-.626,590.2,375.866-.107Z" transform="translate(374.5 256.5)" />
        </g>
      </svg>
    </div>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

</body>
input[type="number"]::-webkit-outer-spin-button,
input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

input[type="number"] {
  -moz-appearance: textfield;
}

body {
  margin: 0;
  padding: 0;
}

#app {
  position: relative;
  font-family: "Hiragino Kaku Gothic ProN", "メイリオ", sans-serif;
  background: #1E276A;
  height: 100vh;
  width: 100vw;
  margin: 0 auto;
  color: #fff;
  z-index: 0;
}

.container {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  -webkit-transform: translate(-50%, -50%);
  -ms-transform: translate(-50%, -50%);
  z-index: 2;
}

.time {
  font-size: 96px;
  text-align: center;
}

.button-section {
  padding-top: 20px;
  margin-bottom: 50px;
  display: block;
  text-align: center;
}

.button-section button {
  margin: 0 10px;
}

.input-section {
  text-align: center;
  font-size: 24px;
}

.input-section input {
  width: 110px;
  text-align: right;
  border: none;
  font-size: 80px;
  color: #fff;
  background: none;
}

.input-section input:focus {
  outline: none;
}

.timeup {
	top: 5% !important;
}

.water {
	transition: all 1s ease-out;
  position: fixed;
  top: 100%;
  left: 0;
  height: 100%;
  width: 100%;
  z-index: 1;
}

.waterwave {
  position: absolute;
  top: 0;
  width: 200%;
}

@keyframes wavegrad {
  from {
    left: -100%;
  }

  to {
    left: 0%;
  }
}

@keyframes wavegrad-back {
  from {
    left: 0%;
  }

  to {
    left: -100%;
  }
}

.back {
  animation-duration: 20s;
  animation-fill-mode: none;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-name: wavegrad-back;
  fill: #2e55be;
}

.front {
  animation-duration: 20s;
  animation-fill-mode: none;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
  animation-name: wavegrad;
  fill: #8ba5ea;
}
window.CP.PenTimer.MAX_TIME_IN_LOOP_WO_EXIT = 359999000; // to avoid an infinite loop protection
const sleep = msec => new Promise(r => setTimeout(r, msec));

var v = new Vue({
  el: "#app",
  data: {
    set_hour: 0,
    set_min: 0,
    set_sec: 0,
    set_ms: 0,
		remain: 0,
    ongoing: false,
    stop: false
  },
  computed: {
    display_time: function() {
			var r = this.remain / 1000;
      var r_sec = Math.floor(r % 60);
      var r_min = Math.floor(r / 60);
      var r_hour = Math.floor(r_min / 60);

      if (r_hour > 0) {
        return this.zfill(r_hour) + ":" + this.zfill(r_min % 60) + ":" + this.zfill(r_sec);
      } else {
        return this.zfill(r_min % 60) + ":" + this.zfill(r_sec);
      }
    },
		wave_height: function() {
			return ((95 / this.set_ms) * (this.set_ms - this.remain) + 5)
		},
		running: function() {
			return {
				active: this.ongoing || this.stop,
				top: this.wave_height + "%"
    	}
		}
  },
  methods: {
    zfill: function(n) {
      var zeros = Array(2).join("0");
      return (zeros + n).slice(-2);
    },
    setTime: function() {
      return ((this.set_hour * 3600) + (this.set_min * 60) + parseInt(this.set_sec)) * 1000
    },
    startTimer: function() {
      this.set_ms = this.setTime();
			this.remain = this.set_ms;
      this.stop = false;
      this.ongoing = true;
      this.runTimer();
    },
    runTimer: async function() {
      while (this.ongoing) {
        await sleep(100);
        this.remain -= 100;
        if (this.remain <= 0) {
          this.finishTimer();
          break;
        }
      }
    },
    finishTimer: function() {
      // beep!
      window.alert("Time's up!");
      this.stop = true;
      this.resetTimer();
    },
    stopTimer: function() {
      this.ongoing = false;
      this.stop = true;
    },
    resumeTimer: function() {
      this.ongoing = true;
      this.stop = false;
      this.runTimer();
    },
    resetTimer: function() {
      this.ongoing = false;
      this.stop = false;
      this.set_ms = this.setTime();
			this.remain = this.set_ms;
    }
  }
});

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.