/* with global variables
- describe the list of ingredients
- describe the state of the order
*/
const state = {
ingredients: [ // an array of ingredients by name and matching color
{
name: 'pineapple',
color: 'rgb(255, 208, 66)',
},
{
name: 'peach',
color: 'rgb(242, 110, 73)',
},
{
name: 'raspberry',
color: 'rgb(228, 41, 57)',
},
],
order: { // an object detailing the selection as well as variables used for the size and position of the rectangles
ingredients: [],
left: 2,
base: 50, // where the rectangles ought to be positioned, vertically (atop the existing rect element)
measure: 25, // height of each rectangle
}
}
/* based on the list of ingredients add one button in the .phone__ingredients section
with the following markup (but using the name and color of the ingredients)
*/
const phoneIngredients = document.querySelector('.phone__ingredients');
// function called on the order button
function handleOrder() {
// proceed updating the UI to show the final mixture
this.removeEventListener('click', handleOrder);
// shake the svg
const phoneDrink = document.querySelector('.phone__drink');
anime({
targets: phoneDrink,
rotate: [4, 0, -4],
loop: 4,
direction: 'alternate',
duration: 100,
easing: 'easeInOutSine',
})
// include a rectangle element spanning the entirety of the g.drink 100 height
// this rectangle using the color dictated by the selected ingredients
const { ingredients } = state;
const { ingredients: ingredientsOrder } = state.order;
const colors = ingredientsOrder.map(nameOrder => ingredients.find(({name}) => name === nameOrder).color);
// add up the rgb values of each ingredient
const regexRGB = /rgb\((\d+), (\d+), (\d+)\)/;
const color = colors.reduce((acc, curr) => {
const [, r, g, b] = curr.match(regexRGB);
return [r, g, b].map((value, index) => parseInt(value, 10) + acc[index]);
},[239, 225, 153]); // initialize the array with the color of the soya drink (this would technically weigh for twice the intensity, but to avoid too similar a color, I consider it diluted)
// include the color using the sum of all rgb codes, divided by the number of colors present
phoneDrink.querySelector('g.drink').innerHTML += `
`;
// after a small delay animate the rectangle to its full y scale
anime({
targets: `g#mix rect`,
transform: 'scale(1 1)',
duration: 300,
delay: 300,
easing: 'easeInOutSine',
});
// once the shaking is complete remove the ingredients
// before removing the ingredients hide them from view
anime({
targets: phoneIngredients,
opacity: 0,
visibility: 'hidden',
duration: 250,
delay: 600,
easing: 'easeInOutSine',
complete: () => {
anime({
targets: phoneIngredients,
height: 0,
duration: 250,
easing: 'easeInOutSine',
complete: () => {
phoneIngredients.parentElement.removeChild(phoneIngredients);
}
})
}
});
// remove the order button and update the phoneState/phoneOrder text elements to highlight the final screen
const phoneState = document.querySelector('.phone__state');
const phoneOrder = document.querySelector('.phone__order');
// phoneState.querySelector('p').innerHTML = 'And here it is! Hope you\'ll enjoy';
anime({
targets: this,
scale: 0,
duration: 250,
delay: 750,
easing: 'easeInOutSine',
complete: () => {
this.parentElement.removeChild(this);
}
});
anime({
targets: phoneState,
opacity: 0,
visibility: 'hidden',
duration: 250,
delay: 750,
easing: 'easeInOutSine',
complete: () => {
phoneState.innerHTML = `
A ${ingredientsOrder.join('-')} flavored refreshment.
`;
anime({
targets: phoneOrder,
opacity: 1,
visibility: 'visible',
duration: 200,
easing: 'easeInOutSine',
});
}
});
}
// function called by the ingredients button
function handleIngredient() {
// retrieve the name of the ingredient and based on the state.order.ingredients, state.order.left values proceed adding/removing the ingredients
const ingredient = this.getAttribute('data-ingredient');
const { ingredients, left, base, measure } = state.order;
/* possible scenarios
1. ingredient is present in the ingredients array, proceed to remove it
1. ingredient is not present, analyse the number of ingredients left
1. more than 0, proceed to add the ingredient
1. 0, do nothing
all the while updating the state and the UI where needed (heading, order section, but most importantly the drink)
*/
if(ingredients.includes(ingredient)) {
// update the state removing the ingredient
const index = ingredients.indexOf(ingredient);
state.order.ingredients = [...ingredients.slice(0, index), ...ingredients.slice(index + 1)];
state.order.left = left + 1;
// update the UI
// number of ingredients left
const phoneState = document.querySelector('.phone__state');
phoneState.querySelector('p').innerHTML = `You can choose ${state.order.left} more ingredients.`
// possibility to add the now removed ingredient
this.querySelector('span').textContent = 'Add +';
// list of selected ingredients
const phoneOrder = document.querySelector('.phone__order');
phoneOrder.querySelector('ul li:last-of-type').textContent = state.order.ingredients.join(', ');
// class for the order button
phoneOrder.classList.remove('complete');
// remove the event listener to avoid running the function accidentally
phoneOrder.querySelector('button').removeEventListener('click', handleOrder);
// update the SVG to remove the selected ingredient and possibly translate the ingredients chosen afterwards
const phoneDrink = document.querySelector('.phone__drink');
// before removing the element have the selected rectangled scaled back to 0
anime({
targets: phoneDrink.querySelector(`g#${ingredient} rect`),
transform: 'scale(1 0)',
duration: 200,
easing: 'easeInOutSine',
complete: () => {
const selectedIngredient = phoneDrink.querySelector(`g#${ingredient}`)
selectedIngredient.parentElement.removeChild(selectedIngredient);
}
});
// translate the remaining ingredient, if any downwards
const remainingIngredient = ingredients.find(ingr => ingr !== ingredient);
anime({
targets: phoneDrink.querySelector(`g#${remainingIngredient}`),
transform: `translate(0 ${base - measure * 2})`,
duration: 200,
easing: 'easeInOutSine',
});
// reduce the base value
state.order.base = base - measure;
} else if(left > 0) {
// update the state adding the ingredient
state.order.ingredients.push(ingredient);
state.order.left = left - 1;
// update the UI
// ! display different content if left is now equal to 0, to direct toward the mixing step
const phoneState = document.querySelector('.phone__state');
const phoneOrder = document.querySelector('.phone__order');
if(state.order.left === 0) {
phoneOrder.classList.add('complete');
phoneOrder.querySelector('button').addEventListener('click', handleOrder);
phoneState.querySelector('p').innerHTML = 'You are all set Proceed mixing.';
} else {
phoneState.querySelector('p').innerHTML = `You can choose ${state.order.left} more ingredients.`;
}
this.querySelector('span').textContent = 'Remove';
phoneOrder.querySelector('ul li:last-of-type').textContent = state.order.ingredients.join(', ');
// update the SVG to add the rectangle element
const phoneDrink = document.querySelector('.phone__drink');
const {color} = state.ingredients.find(({name}) => name === ingredient);
// ! initially set the vertical scale to 0 and animate the rectangle with anime.js
phoneDrink.querySelector('g.drink').innerHTML += `
`;
anime({
targets: `g#${ingredient} rect`,
transform: 'scale(1 1)',
duration: 200,
easing: 'easeInOutSine'
})
// increase the base variable to move the following rectangle upwards
state.order.base = base + measure;
}
}
state.ingredients.forEach(({ name, color }) => {
// create and append a button specifying the markup described above
const button = document.createElement('button');
// data attribute to rapidly identify the selected ingredient
button.setAttribute('data-ingredient', name);
button.addEventListener('click', handleIngredient);
button.innerHTML = `
Add +
`;
phoneIngredients.appendChild(button);
});
// orderButton.addEventListener('click', handleOrder);
// bonus: add the current hour and minutes in the span element
// using the hh:mm format
const now = new Date();
const hours = now.getHours();
const minutes = now.getMinutes();
const zeroPadded = num => (num >= 10 ? num.toString() : `0${num}`);
const span = document.querySelector('nav span');
span.textContent = `${zeroPadded(hours)}:${zeroPadded(minutes)}`;