Summary of monthAnimation and yearAnimation Functions: monthAnimation: The monthAnimation function is responsible for transitioning the calendar view from a year view (showing multiple months) to a detailed view of a specific month when a user clicks on a month. Here’s how it works: 1. Identify the Clicked Month: The function starts by identifying which month was clicked by the user and retrieving necessary elements like the container for the grid and calendar content. { const clickedMonth = event.target.parentNode; const item = clickedMonth.parentNode; // Ex. class="month month-0 year-2024" } 2. Calculate the Transition Parameters: It calculates the scaling factor (offset) needed to zoom into the selected month. { const offset = (testContainer.getBoundingClientRect().width) / (itemRect.width); [...] testContainer.style.scale = `${offset}`; // perform the zooming } It determines the horizontal (distanceX) and vertical (newTranslateY) distances the calendar needs to move to bring the clicked month into focus. { const itemCenterX = itemRect.left + (itemRect.width / 2); const distanceX = (testContainerCenterX-itemCenterX)*offset; [...] const newTranslateY = currentTranslateY-(clickedMonth.parentNode.parentNode.offsetTop+parseFloat(window.getComputedStyle(item.parentNode).getPropertyValue('padding-top')))*offset+getDistanceFromChildToParent(gridContainer, calendarContent)+((calendarHeaderHeight != currentTranslateY) ? 1 : -1)*calendarHeaderHeight; // -window.getComputedStyle(item.parentNode).getPropertyValue('padding-top') } 3. Apply Transformations: The grid container and solution container are animated by applying CSS transform properties for both translation (movement) and scaling to focus on the selected month. { gridContainer.style.transition = "transform 0.5s ease-in-out"; gridContainer.style.transform = `translateY(${newTranslateY}px)`; solution.style.transition = "transform 0.5s ease-in-out"; solution.style.transform = `translateX(${distanceX}px)`; testContainer.style.scale = `${offset}`; } The font size of the clicked month and the month below it is adjusted to a smaller size for emphasis. Sence these are the only visible month-elements during the transformation. { [clickedMonth, monthBelow].forEach(month => { month.style.fontSize = `${fontS}px`; }); } 4. Modify Month Content: The function ensures the correct month is displayed by replacing the existing month elements with new ones, accounting for changes in the year if necessary. Initualy only monthBelow chnages to the correct following month to save calculations to when all transitions are complete (offcourse clickedMonth alredy has the correct month index). { const monthIndexMod = ((monthIndex+1) % 12); if (monthIndexMod != 0) { monthBelow.parentNode.style.paddingTop = '0'; } if ((monthBelow.parentNode.querySelector(".year-name-container")) && ((monthIndexMod == 9) || (monthIndexMod == 11))) { monthBelow.parentNode.querySelector(".year-name-container").style.border = "none"; } const newMonth = createMonth((parseInt(clickedMonth.className.match(/year-(\d+)/)[1])+Math.floor((monthIndex+1)/12)), monthIndexMod, monthBelow.parentNode).monthDiv; // This: (parseInt(clickedMonth.className.match(/year-(\d+)/)[1])+Math.floor((monthIndex+1) / 12)) gives the correct year monthBelow.appendChild(newMonth); monthBelow.removeChild(monthBelow.firstChild); [...] /* The rest of the excisting month-elements are chnaged to their correct month-index with the following code */ for (let i = 0; i < gridItems.length; i++) { const parentElement = gridItems[i]; if ((i-calendarView) % 3 == 0) { if ((parentElement != item) && (parentElement != monthBelow) && (parentElement)) { const existingChild = parentElement.firstElementChild; const newChild = createMonth((startYear+Math.floor((resultIndex+monthCount)/12)), ((resultIndex+monthCount) % 12), parentElement.parentNode).monthDiv; parentElement.replaceChild(newChild, existingChild); } monthCount++ } else { parentElement.parentNode.removeChild(parentElement); } } } 5. Handle Transition End: After the animation ends, additional adjustments are made, such as compensating for any movement or padding changes that occurred during the transition. { gridContainer.addEventListener('transitionend', handleTransitionEnd); } The getMonthIndex helper function is used to keep the correct month in focus, wrapping around months and years as needed ( all month-elements except for monthBelow are changed using the resultIndex and startYear variables ). { const resultIndex = getMonthIndex(monthIndex, Array.prototype.indexOf.call(testContainer.children, item.parentNode)); const startYear = getYearFirstMonthRow(); } 6. Update the Calendar View: The grid container’s position is adjusted to keep the focus on the selected month, ensuring a smooth transition. { document.documentElement.style.setProperty("--monthRowWidth", "190px"); solution.style.transition = "none"; solution.style.transform = `translateX(${0}px)`; } yearAnimation: The yearAnimation function transitions the calendar back from a detailed month view to a year view (showing multiple months in a grid). Here’s how it works: 1. Identify the Closest Month Row: The function starts by identifying the month row closest to the top of the calendar content area. { const testContainer = document.querySelector('.grid-container-inner'); const calendarContentTop = Math.abs(document.querySelector(".calendar-content").getBoundingClientRect().top); let monthRows = document.querySelectorAll('.month-row'); const [closestMonthRow, distance] = findClosestToParentTop(monthRows, calendarContentTop); } 2. Determine the Starting Point: It calculates the month and year to start the year view from, based on the closest month row. This helps in determining which months should be displayed first as the view transitions. { const orgMonthHeightOffset = getDistanceFromChildToParent(closestMonthRow, document.querySelector(".calendar-content")); const [rawMonth, rawYear] = closestMonthRow.firstChild.firstChild.className.match(/month-(\d+) year-(\d+)/).slice(1).map(Number); calendarView = rawMonth % 3; const closestMonthRowIndex = Array.prototype.indexOf.call(testContainer.children, closestMonthRow); let [month, year, janMonthRow] = getMonthIndex(rawMonth, rawYear, closestMonthRowIndex); } 3. Update the Calendar Grid: The function then proceeds to update the month rows, filling each row with the appropriate months. It also adds event listeners to each month so that they can be clicked again to transition back to the detailed view. { monthRows = document.querySelectorAll('.month-row'); document.documentElement.style.setProperty("--monthRowWidth", "100%"); let distanceToJan = 0; monthRows.forEach((monthRow, i) => { monthRow.style.paddingTop = '0'; for (let m = 0; m <= 2; m++) { const monthIndex = ((month*3)+m) % 12; if (monthIndex == 0) { year += 1; } const {monthDiv, monthNameElement} = createMonth(year, monthIndex, monthRow); monthNameElement.addEventListener('click', function (event) { monthAnimation(event, monthIndex); }, { once: true }); const gridItem = document.createElement("div"); gridItem.className = "grid-item"; gridItem.appendChild(monthDiv); if (m != calendarView) { if (calendarView != 1) { monthRow.insertBefore(gridItem, monthRow.children[monthRow.children.length-((calendarView == 0) ? 0 : 1)]); } else { monthRow[(m > calendarView) ? "append" : "prepend"](gridItem); } } else { const childeIndex = (monthRow.children.length-1); if (!((monthRow == closestMonthRow) && (m == calendarView))) { monthRow.replaceChild(gridItem, monthRow.children[childeIndex]); } } } if (i >= janMonthRow) { if (i < closestMonthRowIndex) { distanceToJan += monthRow.offsetHeight; } } month++ }); } 4. Create Additional Month Rows if Necessary: If the existing month rows do not cover the entire view, the function generates additional rows to ensure the grid is fully populated. { for (let index = 0; index < 4; index++) { const monthRow = document.createElement("div"); monthRow.className = `month-row ${(month % 4)}-${year}`; for (let m = 0; m <= 2; m++) { const monthIndex = ((month*3)+m) % 12; if (monthIndex == 0) { year += 1; } const {monthDiv, monthNameElement} = createMonth(year, monthIndex, monthRow); monthNameElement.addEventListener('click', function (event) { monthAnimation(event, monthIndex); }, { once: true }); const gridItem = document.createElement("div"); gridItem.className = "grid-item"; gridItem.appendChild(monthDiv); monthRow.appendChild(gridItem); } testContainer.appendChild(monthRow) month++ } } 5. Calculate and Apply Translations: The function calculates the new vertical position (currentTranslateY) of the calendar, adjusting for the height of the month rows and ensuring the year view is aligned correctly. It also applies an animation to smoothly transition the view. { const rowPadding = parseInt(window.getComputedStyle(closestMonthRow).getPropertyValue('padding-top'), 10)*3; currentTranslateY += -getDistanceFromChildToParent(closestMonthRow, document.querySelector(".calendar-content"))+orgMonthHeightOffset-rowPadding; const finalTranslateY = ((currentTranslateY-distance)/3)+distanceToJan+calendarHeaderHeight+rowPadding/3; gridContainer.style.transform = `translateY(${currentTranslateY}px)`; solution.style.animation = "translateY 0.5s ease-in-out forwards"; } 6. Apply Keyframe Animation: A keyframe animation is defined to handle the transition smoothly, moving the grid into the correct position for the year view. { const styleSheet = document.styleSheets[0]; styleSheet.insertRule(` @keyframes translateY { from { transform: translateX(${(100-(100*calendarView))}%); } to { transform: translateX(0); } } `); setTimeout(() => { testContainer.style.scale = '1'; gridContainer.style.transition = "transform 0.5s ease-in-out"; gridContainer.style.transform = `translateY(${finalTranslateY}px)`; }, 0); } 7. Handle Transition End: After the transition ends, the function resets various CSS properties and ensures that the calendar is ready for further interactions. It also removes the temporary keyframe animation rule. { gridContainer.addEventListener('transitionend', handleTransitionEnd); function handleTransitionEnd() { gridContainer.removeEventListener('transitionend', handleTransitionEnd); // Ensure this only runs one time per click gridContainer.style.transition = "none"; calendarView = false; document.documentElement.style.setProperty("--lineWidth", "290%"); currentTranslateY = finalTranslateY solution.style.animation = "none"; styleSheet.deleteRule(0); // Remove the existing keyframes rule setTimeout(() => { const months = Array.from(document.querySelectorAll(".month")); months.forEach(month => { month.style.transition = "font-size 0.2s ease-in-out"; month.style.fontSize = "16px"; }); setTimeout(() => { months.forEach(month => { month.style.transition = ""; }); }, 250); testContainer.style.fontSize = "16px"; }, 200); } } Conclusion: monthAnimation zooms into a specific month by translating and scaling the calendar view, adjusting content dynamically to display the correct month. yearAnimation is designed to smoothly transition the calendar view from a detailed month focus back to a year overview. It updates the calendar grid, creates additional rows if necessary, and applies animations to ensure a seamless user experience. TODO: 1. Make the calendar rescalable 2. Make it more comfortble to use with mouse 3. Optimice the moth to year animation, vice versa (The easiest way is probably to remove the coverd up content before animating anything) 4. Bug fix the calendar some times staring or ending at the wrong moth or year after switching between month and year-view