diff --git a/internal/assets/static/main.css b/internal/assets/static/main.css index 05108e0..6253439 100644 --- a/internal/assets/static/main.css +++ b/internal/assets/static/main.css @@ -125,22 +125,22 @@ padding-top: var(--list-half-gap); } -.list-collapsible:not(.list-collapsible-expanded) > .list-collapsible-item { +.collapsible-container:not(.container-expanded) > .collapsible-item { display: none; } -.list-collapsible-item { - animation: listItemReveal 0.3s backwards; +.collapsible-item { + animation: collapsibleItemReveal .25s backwards; } -@keyframes listItemReveal { +@keyframes collapsibleItemReveal { from { opacity: 0; transform: translateY(10px); } } -.list-collapsible-label { +.expand-toggle-button { font: inherit; border: 0; cursor: pointer; @@ -154,19 +154,20 @@ background: var(--color-widget-background); } -.list-collapsible-label-expanded { +.expand-toggle-button.container-expanded { position: sticky; - bottom: 0; + /* -1px to hide 1px gap on chrome */ + bottom: -1px; } -.list-collapsible-label-icon { +.expand-toggle-button-icon { display: inline-block; margin-left: 1rem; position: relative; top: -.2rem; } -.list-collapsible-label-icon::before { +.expand-toggle-button-icon::before { content: ''; font-size: 0.8rem; transform: rotate(90deg); @@ -175,14 +176,25 @@ transition: transform 0.3s; } -.list-collapsible-label-expanded .list-collapsible-label-icon::before { +.expand-toggle-button.container-expanded .expand-toggle-button-icon::before { transform: rotate(-90deg); } -.widget-content:has(.list-collapsible-label:last-child) { +.widget-content:has(.expand-toggle-button:last-child) { padding-bottom: 0; } +.cards-grid.collapsible-container + .expand-toggle-button { + text-align: center; + margin-top: 0.5rem; + background-color: var(--color-background); +} + +/* required to prevent collapsed lazy images from being loaded while the container is being setup */ +.collapsible-container:not(.ready) img[loading=lazy] { + display: none; +} + ::selection { background-color: hsl(var(--bghs), calc(var(--scheme) (var(--scheme) var(--bgl) + 20%))); color: var(--color-text-highlight); @@ -270,6 +282,7 @@ body { gap: var(--widget-gap); margin: var(--widget-gap) 0; animation: pageColumnsEntrance .3s cubic-bezier(0.25, 1, 0.5, 1) backwards; + animation-delay: 3ms; } @keyframes pageColumnsEntrance { @@ -1109,8 +1122,10 @@ body { box-shadow: 0 calc(var(--spacing) * -1) 0 0 currentColor, 0 var(--spacing) 0 0 currentColor; } - .list-collapsible-label-expanded { + .expand-toggle-button.container-expanded { bottom: var(--mobile-navigation-height); + /* hides content that peeks through the rounded borders of the mobile navigation */ + box-shadow: 0 var(--border-radius) 0 0 var(--color-background); } } diff --git a/internal/assets/static/main.js b/internal/assets/static/main.js index 31f1838..9818ecf 100644 --- a/internal/assets/static/main.js +++ b/internal/assets/static/main.js @@ -178,74 +178,158 @@ function setupLazyImages() { }, 5); } -function setupCollapsibleLists() { - const collapsibleListElements = document.getElementsByClassName("list-collapsible"); - - if (collapsibleListElements.length == 0) { - return; - } - +function attachExpandToggleButton(collapsibleContainer) { const showMoreText = "Show more"; const showLessText = "Show less"; - const attachExpandToggleButton = (listElement) => { - let expanded = false; - const button = document.createElement("button"); - const arrowElement = document.createElement("span"); - arrowElement.classList.add("list-collapsible-label-icon"); - const textNode = document.createTextNode(showMoreText); - button.classList.add("list-collapsible-label"); - button.append(textNode, arrowElement); - button.addEventListener("click", () => { - expanded = !expanded; + let expanded = false; + const button = document.createElement("button"); + const icon = document.createElement("span"); + icon.classList.add("expand-toggle-button-icon"); + const textNode = document.createTextNode(showMoreText); + button.classList.add("expand-toggle-button"); + button.append(textNode, icon); + button.addEventListener("click", () => { + expanded = !expanded; - if (expanded) { - listElement.classList.add("list-collapsible-expanded"); - button.classList.add("list-collapsible-label-expanded"); - textNode.nodeValue = showLessText; + if (expanded) { + collapsibleContainer.classList.add("container-expanded"); + button.classList.add("container-expanded"); + textNode.nodeValue = showLessText; + return; + } + + const topBefore = button.getClientRects()[0].top; + + collapsibleContainer.classList.remove("container-expanded"); + button.classList.remove("container-expanded"); + textNode.nodeValue = showMoreText; + + const topAfter = button.getClientRects()[0].top; + + if (topAfter > 0) + return; + + window.scrollBy({ + top: topAfter - topBefore, + behavior: "instant" + }); + }); + + collapsibleContainer.after(button); + + return button; +}; + + +function setupCollapsibleLists() { + const collapsibleLists = document.querySelectorAll(".list.collapsible-container"); + + if (collapsibleLists.length == 0) { + return; + } + + for (let i = 0; i < collapsibleLists.length; i++) { + const list = collapsibleLists[i]; + + if (list.dataset.collapseAfter === undefined) { + continue; + } + + const collapseAfter = parseInt(list.dataset.collapseAfter); + + if (collapseAfter == -1) { + continue; + } + + if (list.children.length <= collapseAfter) { + continue; + } + + attachExpandToggleButton(list); + + for (let c = collapseAfter; c < list.children.length; c++) { + const child = list.children[c]; + child.classList.add("collapsible-item"); + child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms"; + } + + list.classList.add("ready"); + } +} + +function setupCollapsibleGrids() { + const collapsibleGridElements = document.querySelectorAll(".cards-grid.collapsible-container"); + + if (collapsibleGridElements.length == 0) { + return; + } + + for (let i = 0; i < collapsibleGridElements.length; i++) { + const gridElement = collapsibleGridElements[i]; + + if (gridElement.dataset.collapseAfterRows === undefined) { + continue; + } + + const collapseAfterRows = parseInt(gridElement.dataset.collapseAfterRows); + + if (collapseAfterRows == -1) { + continue; + } + + const getCardsPerRow = () => { + return parseInt(getComputedStyle(gridElement).getPropertyValue('--cards-per-row')); + }; + + const button = attachExpandToggleButton(gridElement); + + let cardsPerRow = 2; + + const resolveCollapsibleItems = () => { + const hideItemsAfterIndex = cardsPerRow * collapseAfterRows; + + if (hideItemsAfterIndex >= gridElement.children.length) { + button.style.display = "none"; + } else { + button.style.removeProperty("display"); + } + + let row = 0; + + for (let i = 0; i < gridElement.children.length; i++) { + const child = gridElement.children[i]; + + if (i >= hideItemsAfterIndex) { + child.classList.add("collapsible-item"); + child.style.animationDelay = (row * 40).toString() + "ms"; + + if (i % cardsPerRow + 1 == cardsPerRow) { + row++; + } + } else { + child.classList.remove("collapsible-item"); + child.style.removeProperty("animation-delay"); + } + } + }; + + setTimeout(() => { + cardsPerRow = getCardsPerRow(); + resolveCollapsibleItems(); + gridElement.classList.add("ready"); + }, 1); + + window.addEventListener("resize", () => { + const newCardsPerRow = getCardsPerRow(); + + if (cardsPerRow == newCardsPerRow) { return; } - const topBefore = button.getClientRects()[0].top; - - listElement.classList.remove("list-collapsible-expanded"); - button.classList.remove("list-collapsible-label-expanded"); - textNode.nodeValue = showMoreText; - - const topAfter = button.getClientRects()[0].top; - - if (topAfter > 0) - return; - - window.scrollBy({ - top: topAfter - topBefore, - behavior: "instant" - }); + cardsPerRow = newCardsPerRow; + resolveCollapsibleItems(); }); - - listElement.after(button); - }; - - for (let i = 0; i < collapsibleListElements.length; i++) { - const listElement = collapsibleListElements[i]; - - if (listElement.dataset.collapseAfter === undefined) { - continue; - } - - const collapseAfter = parseInt(listElement.dataset.collapseAfter); - - if (listElement.children.length <= collapseAfter) { - continue; - } - - attachExpandToggleButton(listElement); - - for (let c = collapseAfter; c < listElement.children.length; c++) { - const child = listElement.children[c]; - child.classList.add("list-collapsible-item"); - child.style.animationDelay = ((c - collapseAfter) * 20).toString() + "ms"; - } } } @@ -264,6 +348,7 @@ async function setupPage() { setupLazyImages(); setupCarousels(); setupCollapsibleLists(); + setupCollapsibleGrids(); setupDynamicRelativeTime(); } finally { pageElement.classList.add("content-ready"); diff --git a/internal/assets/templates/forum-posts.html b/internal/assets/templates/forum-posts.html index fb1fed5..b8fea41 100644 --- a/internal/assets/templates/forum-posts.html +++ b/internal/assets/templates/forum-posts.html @@ -1,7 +1,7 @@ {{ template "widget-base.html" . }} {{ define "widget-content" }} -