<div class="calc-outer-wrap">
<div id="form-error" style="display:none;"></div>
<form class="calc-form" id="calc-form">
<div class="form-fields">
<div class="field-outer col-1">
<label for="r-time-m" class="top-label">Race Distance</label>
<div class="field-items">
<div class="field field-large">
<select id="race" name="race">
<option value=""> </option>
<option value='1m'>1 Mile</option>
<option value='5k'>5 km</option>
<option value='5m'>5 Mile</option>
<option value='10k'>10 km</option>
<option value='10m'>10 Mile</option>
<option value='halfmarathon'>Half Marathon</option>
<option value='marathon'>Marathon</option>
</select>
</div>
<!-- /.field -->
</div>
<!-- /.field-items -->
</div>
<!-- /.field-outer -->
<div class="field-outer col-2">
<label for="r1" class="top-label">Your Time:</label>
<div class="field-items">
<div class="field">
<input type="text" name="finish-time-hr" id="finish_time_hr" inputmode="numeric" pattern="[0-9]*" placeholder="Hour">
</div>
<!-- /.field -->
<div class="field">
<input type="text" name="finish-time-min" id="finish_time_min" inputmode="numeric" pattern="[0-9]*" placeholder="Min">
</div>
<!-- /.field -->
<div class="field">
<input type="text" name="finish-time-sec" id="finish_time_sec" inputmode="numeric" pattern="[0-9]*" placeholder="Sec">
</div>
<!-- /.field -->
</div>
<!-- /.field-items -->
</div>
<!-- /.field-outer -->
<div class="btn-wrap field-outer">
<button class="form-submit">Calculate</button>
</div>
</div>
<!-- /.form fields -->
</form>
<div id="results" style="display:none;"></div>
</div>
<!-- /.calc-outer-wrap -->
$border-radius : 0.1875rem;
$primary: #9951FF;
$primary-dark: #803ede;
$secondary: #59e7ed;
$secondary-dark: darken($secondary, 10);
@mixin mobile {
@media (min-width:22.5rem) {
@content;
}
}
@mixin tablet {
@media (min-width:45rem) {
@content;
}
}
* {box-sizing: border-box;}
html, body {
margin: 0;
padding: 0;
}
.calc-outer-wrap {
margin: 0 auto;
background: #f3f3f3;
font-size: 1rem;
line-height: 1.3;
font-family: Helvetica Neue,Helvetica, Arial, sans-serif;
padding: 0 0.625rem;
@include mobile {
padding: 0.625rem;
}
@supports (-webkit-overflow-scrolling: touch) {
padding: 0.625rem;
}
}
.form-fields {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-end;
}
.field-outer {
width: 32%;
flex-basis: calc(100% / 3 - 5px);
@include tablet {
width: 48%;
flex-basis: calc(50% - 10px);
margin-bottom: 5px;
}
}
label,
input,
select {
font-size: 1em;
line-height: 1.3;
display: block;
}
input,
select {
height: 1.75rem;
border: 1px solid #bbb;
width: 100%;
background: #fff;
padding: 0 0.325rem;
border-radius : $border-radius;
transition: box-shadow 200ms ease-in-out;
box-shadow: inset 0 0 0 0 $secondary;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
&:focus {
outline: 0;
box-shadow: inset 0 -3px 0 -1px $secondary;
border-radius: $border-radius $border-radius 1px 1px;
}
}
select {
font-size: 0.875rem;
background: #fff url("data:image/svg+xml;charset=UTF-8,%3csvg width='16px' height='14px' viewBox='0 0 8 17' xmlns='http://www.w3.org/2000/svg' fill='%238A8A8A'%3e%3cpolygon fill='%238A8A8A' points='4 17 0 10 8 10'%3e%3c/polygon%3e%3cpolygon fill='%238A8A8A' points='4 0 0 7 8 7'%3e%3c/polygon%3e%3c/svg%3e") right center no-repeat;
&:-moz-focusring {
color: transparent;
text-shadow: 0 0 0 #000;
}
&::-ms-expand {
display: none;
}
}
.field-items {
display: flex;
justify-content: space-between;
label,
small{
font-size: 0.75rem;
color: #999;
}
}
.field {
display: inline-block;
width: 32%;
flex-grow: 1;
+ .field {
margin-left: 3px;
}
}
.m-hide {
display: none;
@include tablet {
display: inline;
}
}
.field-large {
flex: 1;
width: 67%;
}
.field-small {
width: 4em;
flex-grow: 0
}
.top-label {
font-weight: bold;
font-size: 0.875rem;
line-height: 1.4;
display: block;
.small {
color: #999;
font-weight: normal;
font-size: 0.75rem;
}
}
.btn-wrap {
align-self: flex-end;
margin: 5px 0 0 auto;
margin-left: auto;
flex-basis: 12rem;
}
.form-submit {
background: $secondary;
display: block;
border: 0;
width: 100%;
font-size: 1rem;
border-radius: $border-radius;
height: 1.5rem;
color:#fff;
box-shadow: 1px 1px 1px 0 rgba(0,0,0,0.15);
cursor: pointer;
text-transform: uppercase;
@include mobile {
height: 2rem;
}
&:hover,
&:focus {
background: $secondary-dark;
box-shadow: 2px 2px 3px 0 rgba(0,0,0,0.3);
outline: 0;
}
&:active {
box-shadow: none;
}
}
#form-error {
color: #fff;
margin-bottom: 0.5rem;
text-align:center;
color: #c1000b;
background-color: #ffd5d5;
padding: 3px;
font-size: 0.875rem;
}
#results {
position: relative;
width: 100%;
justify-content: center;
align-items: center;
font-size: 0.875rem;
@include mobile {
font-size: 1rem;
}
p {
margin: 0;
@include tablet {
text-align: center;
}
}
dl {
width: 22em;
margin: 1em auto 0;
column-count: 2;
dt,dd {
display: inline-block;
margin: 0;
}
dt {
width: 6em;
font-weight: bold;
}
dt,dd {
margin: 0;
}
dd {
width: 4em;
text-align: right;
}
}
#rs {
position: absolute;
bottom: 0;
right: 0;
font-size: 0.875rem;
color: #aaa;
&:hover {
color: $primary;
}
}
}
View Compiled
(function() {
const form = document.getElementById('calc-form');
const results = document.getElementById('results');
const errors = document.getElementById('form-error');
/**
* Display a form validation error
*
* @param {String} msg The validation message
* @return {Boolen} Returns false
*/
function errorMessage(msg) {
errors.innerHTML = msg;
errors.style.display = '';
return false;
}
// Takes a VO2 measurement and converts it to a velocity.
function VO2ToVel (VO2) {
return (29.54 + 5.000663 * VO2 - 0.007546 * VO2 * VO2);
}
// Takes a time in minutes and uses EQ 2 to convert it to a percent of VO2 maximum.
function timeToPercentVO2Max (mins) {
return (.8 + 0.1894393 * Math.exp(-.012778 * mins) + 0.2989558 * Math.exp(-.1932695 * mins));
}
// Takes a velocity and converts it to a VO2 level.
function velToVO2 (vel) {
return (-4.60 + 0.182258 * vel + 0.000104 * vel * vel);
}
// Convert speed to display
function timeConvert(speed,checkYasso=false) {
var resultKm;
var resultMile;
resultKm = (1 / speed) * 1000;
resultMile = (1 / speed) * 1609;
minsKm = Math.floor(resultKm);
secsKm = Math.floor((resultKm - minsKm) * 60);
minsMile = Math.floor(resultMile);
secsMile = Math.floor((resultMile - minsMile) * 60);
if(secsKm > 9){
sKmSep = ':';
} else {
sKmSep = ':0';
}
if(secsMile > 9){
sMSep = ':';
} else {
sMSep = ':0';
}
if (checkYasso==true) {
return '' + minsMile + sMSep + secsMile + ' per 800.';
} else {
return '' + minsKm + sKmSep + secsKm + ' per Km and ' + minsMile + sMSep + secsMile + ' per Mile.';
}
}
// Convert dropdown of race distance to km
function raceToMiles(raceDist) {
var miles
switch (raceDist) {
case 'marathon':
miles=26.21875;
break;
case 'halfmarathon':
miles=13.109375;
break;
case '10m':
miles=10;
break;
case '10k':
miles=6.21371192;
break;
case '5m':
miles=5;
break;
case '5k':
miles=3.10685596;
break;
case '3k':
miles=1.864113576;
break;
case '1m':
miles=1;
break;
case '1500':
miles=0.932056788;
break;
}
return (miles*1609)
}
/**
* Hide the results and reset the form
*/
function resetForm(e) {
if(e.target.id = 'rs') {
e.preventDefault();
results.style.display = 'none';
form.style.display = '';
//form.reset()
}
}
/**
* Convert time to seconds
*
* @param {Int} m Minutes
* @param {Int} s Seconds
* @return {Int} The time in seconds
*/
function toMinutes(h, m, s) {
h = parseInt(h) || 0;
m = parseInt(m) || 0;
s = parseInt(s) || 0;
return h * 60 + m + s / 60;
}
/**
* Display the results on the page
*
*/
function displayResults({velEasyDisplay,velMaxDisplay,velTempoDisplay,velSpeedDisplay,velLongDisplay,velYassoDisplay}) {
results.innerHTML = `<p><b>${(velEasyDisplay)}</b><br>
<p><b>${(velMaxDisplay)}</b><br>
<p><b>${(velTempoDisplay)}</b><br>
<p><b>${(velSpeedDisplay)}</b><br>
<p><b>${(velLongDisplay)}</b><br>
<p><b>${(velYassoDisplay)}</b><br>
<a href="#" id="rs">Revise</a>
`;
results.style.display = '';
form.style.display = 'none';
errors.style.display = 'none';
}
/**
* Handle form submit
* Gather/calculate data from form input and display results
*/
function handleSubmit(e) {
e.preventDefault();
let data = {
race : form.race.value,
finishTime : toMinutes(form.finish_time_hr.value, form.finish_time_min.value, form.finish_time_sec.value)
}
if(!data.race) {
return errorMessage('You must select a race distance.');
}
if (!data.finishTime) {
return errorMessage('You must enter a finish time.');
}
data.distance = raceToMiles(data.race);
data.speed = (data.distance/data.finishTime);
data.v02Max = velToVO2(data.speed) / timeToPercentVO2Max(data.finishTime);
data.velEasy = VO2ToVel(data.v02Max * .7);
data.velTempo = VO2ToVel(data.v02Max * .88);
data.velMaximum = VO2ToVel(data.v02Max);
data.velSpeed = VO2ToVel(data.v02Max * 1.1);
data.velxlong = VO2ToVel(data.v02Max * .6);
data.velYasso = data.velMaximum * 1.95;
data.velEasyDisplay = 'Easy run training pace: ' + timeConvert(data.velEasy);
data.velTempoDisplay = 'Tempo run training pace: ' + timeConvert(data.velTempo);
data.velMaxDisplay = 'VO2-max training pace: ' + timeConvert(data.velMaximum);
data.velSpeedDisplay = 'Speed form training pace: ' + timeConvert(data.velSpeed);
data.velLongDisplay = 'Long run training pace: ' + timeConvert(data.velxlong);
data.velYassoDisplay = 'Yasso 800s training pace: ' + timeConvert(data.velYasso,true);
//console.log(data);
displayResults(data);
}
/**
* Keep unit select fields in sync
* Changing one changes both
*/
function handleChange(e) {
let sel = 'select.unit';
if(e.target.matches(sel)) {
let val = e.target.value;
document.querySelectorAll(sel).forEach(s => s.value = val);
}
// Hide errors
errors.style.display = 'none';
}
// Add Event Listeners
form.addEventListener('submit', handleSubmit);
form.addEventListener('change', handleChange);
results.addEventListener('click', resetForm, true);
}
)();
View Compiled
This Pen doesn't use any external CSS resources.