/* 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 = `

Here it is

Hope you'll enjoy
your smoothie!

`; anime({ targets: phoneState, opacity: 1, visibility: 'visible', duration: 200, easing: 'easeInOutSine', }); } }); anime({ targets: phoneOrder, opacity: 0, visibility: 'hidden', duration: 250, delay: 850, easing: 'easeInOutSine', complete: () => { phoneOrder.querySelector('div').innerHTML = `

Your great choice

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)}`;