From f25b377e0f06390e33835b5f3f0ea0cc31915173 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 1 Sep 2023 14:02:39 -0300 Subject: client: update image overlay for available thumb sizes --- src/static/client2.js | 71 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 17 deletions(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 0cdb8b0e..0a8eb860 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -709,24 +709,34 @@ function handleImageLinkClicked(evt) { const mainImage = document.getElementById('image-overlay-image'); const thumbImage = document.getElementById('image-overlay-image-thumb'); - const mainThumbSize = getPreferredThumbSize(); + const {href: originalSrc} = evt.target.closest('a'); + const {dataset: { + originalSize: originalFileSize, + thumbs: availableThumbList, + }} = evt.target.closest('a').querySelector('img'); - const source = evt.target.closest('a').href; + updateFileSizeInformation(originalFileSize); - const mainSrc = source.replace(/\.(jpg|png)$/, `.${mainThumbSize}.jpg`); - const thumbSrc = source.replace(/\.(jpg|png)$/, '.small.jpg'); + const {thumb: mainThumb, length: mainLength} = getPreferredThumbSize(availableThumbList); + const {thumb: smallThumb, length: smallLength} = getSmallestThumbSize(availableThumbList); + + const mainSrc = originalSrc.replace(/\.(jpg|png)$/, `.${mainThumb}.jpg`); + const thumbSrc = originalSrc.replace(/\.(jpg|png)$/, `.${smallThumb}.jpg`); thumbImage.src = thumbSrc; + + // Show the thumbnail size on each element's data attributes. + // Y'know, just for debugging convenience. + mainImage.dataset.displayingThumb = `${mainThumb}:${mainLength}`; + thumbImage.dataset.displayingThumb = `${smallThumb}:${smallLength}`; + for (const viewOriginal of allViewOriginal) { - viewOriginal.href = source; + viewOriginal.href = originalSrc; } mainImage.addEventListener('load', handleMainImageLoaded); mainImage.addEventListener('error', handleMainImageErrored); - const fileSize = evt.target.closest('a').querySelector('img').dataset.originalSize; - updateFileSizeInformation(fileSize); - container.style.setProperty('--download-progress', '0%'); loadImage(mainSrc, progress => { container.style.setProperty('--download-progress', (20 + 0.8 * progress) + '%'); @@ -750,7 +760,21 @@ function handleImageLinkClicked(evt) { } } -function getPreferredThumbSize() { +function parseThumbList(availableThumbList) { + // Parse all the available thumbnail sizes! These are provided by the actual + // content generation on each image. + const defaultThumbList = 'huge:1400 semihuge:1200 large:800 medium:400 small:250' + const availableSizes = + (availableThumbList || defaultThumbList) + .split(' ') + .map(part => part.split(':')) + .map(([thumb, length]) => ({thumb, length: parseInt(length)})) + .sort((a, b) => a.length - b.length); + + return availableSizes; +} + +function getPreferredThumbSize(availableThumbList) { // Assuming a square, the image will be constrained to the lesser window // dimension. Coefficient here matches CSS dimensions for image overlay. const constrainedLength = Math.floor(Math.min( @@ -761,17 +785,30 @@ function getPreferredThumbSize() { // device configurations. const visualLength = window.devicePixelRatio * constrainedLength; - const largeLength = 800; - const semihugeLength = 1200; + const availableSizes = parseThumbList(availableThumbList); + + // Starting from the smallest dimensions, find (and return) the first + // available length which hits a "good enough" threshold - it's got to be + // at least that percent of the way to the actual displayed dimensions. const goodEnoughThreshold = 0.90; - if (Math.floor(visualLength * goodEnoughThreshold) <= largeLength) { - return 'large'; - } else if (Math.floor(visualLength * goodEnoughThreshold) <= semihugeLength) { - return 'semihuge'; - } else { - return 'huge'; + // (The last item is skipped since we'd be falling back to it anyway.) + for (const {thumb, length} of availableSizes.slice(0, -1)) { + if (Math.floor(visualLength * goodEnoughThreshold) <= length) { + return {thumb, length}; + } } + + // If none of the items in the list were big enough to hit the "good enough" + // threshold, just use the largest size available. + return availableSizes[availableSizes.length - 1]; +} + +function getSmallestThumbSize(availableThumbList) { + // Just snag the smallest size. This'll be used for displaying the "preview" + // as the bigger one is loading. + const availableSizes = parseThumbList(availableThumbList); + return availableSizes[0]; } function updateFileSizeInformation(fileSize) { -- cgit 1.3.0-6-gf8a5 From 63d9b8c2d455e2f74a837d8772fcfcf8e38e3a3e Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 4 Sep 2023 20:52:28 -0300 Subject: client: defend client-side code against images without thumbs --- src/static/client2.js | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 0a8eb860..8ae9876e 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -717,18 +717,32 @@ function handleImageLinkClicked(evt) { updateFileSizeInformation(originalFileSize); - const {thumb: mainThumb, length: mainLength} = getPreferredThumbSize(availableThumbList); - const {thumb: smallThumb, length: smallLength} = getSmallestThumbSize(availableThumbList); - - const mainSrc = originalSrc.replace(/\.(jpg|png)$/, `.${mainThumb}.jpg`); - const thumbSrc = originalSrc.replace(/\.(jpg|png)$/, `.${smallThumb}.jpg`); - - thumbImage.src = thumbSrc; + let mainSrc = null; + let thumbSrc = null; + + if (availableThumbList) { + const {thumb: mainThumb, length: mainLength} = getPreferredThumbSize(availableThumbList); + const {thumb: smallThumb, length: smallLength} = getSmallestThumbSize(availableThumbList); + mainSrc = originalSrc.replace(/\.(jpg|png)$/, `.${mainThumb}.jpg`); + thumbSrc = originalSrc.replace(/\.(jpg|png)$/, `.${smallThumb}.jpg`); + // Show the thumbnail size on each element's data attributes. + // Y'know, just for debugging convenience. + mainImage.dataset.displayingThumb = `${mainThumb}:${mainLength}`; + thumbImage.dataset.displayingThumb = `${smallThumb}:${smallLength}`; + } else { + mainSrc = originalSrc; + thumbSrc = null; + mainImage.dataset.displayingThumb = ''; + thumbImage.dataset.displayingThumb = ''; + } - // Show the thumbnail size on each element's data attributes. - // Y'know, just for debugging convenience. - mainImage.dataset.displayingThumb = `${mainThumb}:${mainLength}`; - thumbImage.dataset.displayingThumb = `${smallThumb}:${smallLength}`; + if (thumbSrc) { + thumbImage.src = thumbSrc; + thumbImage.style.display = null; + } else { + thumbImage.src = ''; + thumbImage.style.display = 'none'; + } for (const viewOriginal of allViewOriginal) { viewOriginal.href = originalSrc; -- cgit 1.3.0-6-gf8a5 From 8a23eb5888242d6243eb6954d7d68622c24bdbcd Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 11 Sep 2023 15:12:13 -0300 Subject: client: hide missing cover image from sticky heading --- src/static/client2.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 8ae9876e..78970410 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -534,11 +534,17 @@ const stickyHeadingInfo = Array.from(document.querySelectorAll('.content-sticky- const {parentElement: contentContainer} = stickyContainer; const stickySubheadingRow = stickyContainer.querySelector('.content-sticky-subheading-row'); const stickySubheading = stickySubheadingRow.querySelector('h2'); - const stickyCoverContainer = stickyContainer.querySelector('.content-sticky-heading-cover-container'); - const stickyCover = stickyCoverContainer?.querySelector('.content-sticky-heading-cover'); + let stickyCoverContainer = stickyContainer.querySelector('.content-sticky-heading-cover-container'); + let stickyCover = stickyCoverContainer?.querySelector('.content-sticky-heading-cover'); const contentHeadings = Array.from(contentContainer.querySelectorAll('.content-heading')); const contentCover = contentContainer.querySelector('#cover-art-container'); + if (stickyCover.querySelector('.image-text-area')) { + stickyCoverContainer.remove(); + stickyCoverContainer = null; + stickyCover = null; + } + return { contentContainer, contentCover, -- cgit 1.3.0-6-gf8a5 From 44f1442bf28bac7b07ac25c1ea15c6b3a9d1223a Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 11 Sep 2023 15:13:54 -0300 Subject: css: give square images a self-confidence boost --- src/static/site4.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/static') diff --git a/src/static/site4.css b/src/static/site4.css index f79c0c2d..ab8976bc 100644 --- a/src/static/site4.css +++ b/src/static/site4.css @@ -558,7 +558,7 @@ a.box img { height: auto; } -a.box .square .image-container { +.square .image-container { width: 100%; height: 100%; } -- cgit 1.3.0-6-gf8a5 From f3a855d772d51749c6f9d50632dc74792f902b29 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sat, 16 Sep 2023 21:42:55 -0300 Subject: client: fix sticky headings not working on pages w/o cover --- src/static/client2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 78970410..d9afcb03 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -539,7 +539,7 @@ const stickyHeadingInfo = Array.from(document.querySelectorAll('.content-sticky- const contentHeadings = Array.from(contentContainer.querySelectorAll('.content-heading')); const contentCover = contentContainer.querySelector('#cover-art-container'); - if (stickyCover.querySelector('.image-text-area')) { + if (stickyCover?.querySelector('.image-text-area')) { stickyCoverContainer.remove(); stickyCoverContainer = null; stickyCover = null; -- cgit 1.3.0-6-gf8a5 From 84686df07e1029bcad91b9fda47b6f3bd280ee56 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Tue, 6 Jun 2023 20:07:03 -0300 Subject: content: generateCoverArtwork: mode commentary --- src/static/site4.css | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'src/static') diff --git a/src/static/site4.css b/src/static/site4.css index ab8976bc..eb8d5520 100644 --- a/src/static/site4.css +++ b/src/static/site4.css @@ -533,6 +533,13 @@ p .current { margin-top: 5px; } +.commentary-art { + float: right; + width: 30%; + max-width: 250px; + margin: 15px 0 10px 20px; +} + .js-hide, .js-show-once-data, .js-hide-once-data { @@ -1250,6 +1257,10 @@ html[data-url-key="localized.home"] .carousel-container { animation-delay: 125ms; } +h3.content-heading { + clear: both; +} + /* This animation's name is referenced in JavaScript */ @keyframes highlight-hash-link { 0% { -- cgit 1.3.0-6-gf8a5 From 8b1d5053f71959498c7327493db9f64b94f8de30 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 24 Sep 2023 11:00:57 -0300 Subject: css: misc. adjustments for sticky column sidebar --- src/static/site4.css | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) (limited to 'src/static') diff --git a/src/static/site4.css b/src/static/site4.css index eb8d5520..0e6166b4 100644 --- a/src/static/site4.css +++ b/src/static/site4.css @@ -1449,6 +1449,30 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r align-self: flex-start; } +.sidebar-column.sidebar.sticky-column { + max-height: calc(100vh - 20px); + overflow-y: scroll; + align-self: start; + padding-bottom: 0; + box-sizing: border-box; + flex-basis: 275px; + padding-top: 0; +} + +.sidebar-column.sidebar.sticky-column > h1 { + position: sticky; + top: 0; + margin: 0 calc(-1 * var(--content-padding)); + margin-bottom: 10px; + + border-bottom: 1px dotted rgba(220, 220, 220, 0.4); + padding: 10px 5px; + + background: var(--bg-black-color); + -webkit-backdrop-filter: blur(3px); + backdrop-filter: blur(3px); +} + /* Image overlay */ #image-overlay-container { -- cgit 1.3.0-6-gf8a5 From fbabffe43bfaacab4fdb1ae508525b6c5c41dbbd Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 24 Sep 2023 15:41:20 -0300 Subject: client: rework (most) steps to fail gracefully --- src/static/client2.js | 753 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 502 insertions(+), 251 deletions(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index d9afcb03..96b422a8 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -6,13 +6,28 @@ // ephemeral nature. import {getColors} from '../util/colors.js'; -import {getArtistNumContributions} from '../util/wiki-data.js'; +import {empty, stitchArrays} from '../util/sugar.js'; + +import { + filterMultipleArrays, + getArtistNumContributions, +} from '../util/wiki-data.js'; let albumData, artistData; let officialAlbumData, fandomAlbumData, beyondAlbumData; let ready = false; +const clientInfo = window.hsmusicClientInfo = Object.create(null); + +const clientSteps = { + getPageReferences: [], + addInternalListeners: [], + mutatePageContent: [], + initializeState: [], + addPageListeners: [], +}; + // Localiz8tion nonsense ---------------------------------- const language = document.documentElement.getAttribute('lang'); @@ -86,113 +101,148 @@ function fetchData(type, directory) { // JS-based links ----------------------------------------- -for (const a of document.body.querySelectorAll('[data-random]')) { - a.addEventListener('click', (evt) => { - if (!ready) { - evt.preventDefault(); - return; - } +const scriptedLinkInfo = clientInfo.scriptedLinkInfo = { + randomLinks: null, + revealLinks: null, - const tracks = albumData => - albumData - .map(album => album.tracks) - .reduce((acc, tracks) => acc.concat(tracks), []); + nextLink: null, + previousLink: null, + randomLink: null, +}; - setTimeout(() => { - a.href = rebase('js-disabled'); - }); +function getScriptedLinkReferences() { + scriptedLinkInfo.randomLinks = + document.querySelectorAll('[data-random]'); - switch (a.dataset.random) { - case 'album': - a.href = openAlbum(pick(albumData).directory); - break; - - case 'album-in-official': - a.href = openAlbum(pick(officialAlbumData).directory); - break; - - case 'album-in-fandom': - a.href = openAlbum(pick(fandomAlbumData).directory); - break; - - case 'album-in-beyond': - a.href = openAlbum(pick(beyondAlbumData).directory); - break; - - case 'track': - a.href = openTrack(getRefDirectory(pick(tracks(albumData)))); - break; - - case 'track-in-album': - a.href = openTrack(getRefDirectory(pick(getAlbum(a).tracks))); - break; - - case 'track-in-official': - a.href = openTrack(getRefDirectory(pick(tracks(officialAlbumData)))); - break; - - case 'track-in-fandom': - a.href = openTrack(getRefDirectory(pick(tracks(fandomAlbumData)))); - break; - - case 'track-in-beyond': - a.href = openTrack(getRefDirectory(pick(tracks(beyondAlbumData)))); - break; - - case 'artist': - a.href = openArtist(pick(artistData).directory); - break; - - case 'artist-more-than-one-contrib': - a.href = - openArtist( - pick(artistData.filter((artist) => getArtistNumContributions(artist) > 1)) - .directory); - break; - } - }); + scriptedLinkInfo.revealLinks = + document.getElementsByClassName('reveal'); + + scriptedLinkInfo.nextNavLink = + document.getElementById('next-button'); + + scriptedLinkInfo.previousNavLink = + document.getElementById('previous-button'); + + scriptedLinkInfo.randomNavLink = + document.getElementById('random-button'); } -const next = document.getElementById('next-button'); -const previous = document.getElementById('previous-button'); -const random = document.getElementById('random-button'); +function addRandomLinkListeners() { + for (const a of scriptedLinkInfo.randomLinks ?? []) { + a.addEventListener('click', evt => { + if (!ready) { + evt.preventDefault(); + return; + } -const prependTitle = (el, prepend) => { - const existing = el.getAttribute('title'); - if (existing) { - el.setAttribute('title', prepend + ' ' + existing); - } else { - el.setAttribute('title', prepend); - } -}; + const tracks = albumData => + albumData + .map(album => album.tracks) + .reduce((acc, tracks) => acc.concat(tracks), []); -if (next) prependTitle(next, '(Shift+N)'); -if (previous) prependTitle(previous, '(Shift+P)'); -if (random) prependTitle(random, '(Shift+R)'); - -document.addEventListener('keypress', (event) => { - if (event.shiftKey) { - if (event.charCode === 'N'.charCodeAt(0)) { - if (next) next.click(); - } else if (event.charCode === 'P'.charCodeAt(0)) { - if (previous) previous.click(); - } else if (event.charCode === 'R'.charCodeAt(0)) { - if (random && ready) random.click(); - } + setTimeout(() => { + a.href = rebase('js-disabled'); + }); + + switch (a.dataset.random) { + case 'album': + a.href = openAlbum(pick(albumData).directory); + break; + + case 'album-in-official': + a.href = openAlbum(pick(officialAlbumData).directory); + break; + + case 'album-in-fandom': + a.href = openAlbum(pick(fandomAlbumData).directory); + break; + + case 'album-in-beyond': + a.href = openAlbum(pick(beyondAlbumData).directory); + break; + + case 'track': + a.href = openTrack(getRefDirectory(pick(tracks(albumData)))); + break; + + case 'track-in-album': + a.href = openTrack(getRefDirectory(pick(getAlbum(a).tracks))); + break; + + case 'track-in-official': + a.href = openTrack(getRefDirectory(pick(tracks(officialAlbumData)))); + break; + + case 'track-in-fandom': + a.href = openTrack(getRefDirectory(pick(tracks(fandomAlbumData)))); + break; + + case 'track-in-beyond': + a.href = openTrack(getRefDirectory(pick(tracks(beyondAlbumData)))); + break; + + case 'artist': + a.href = openArtist(pick(artistData).directory); + break; + + case 'artist-more-than-one-contrib': + a.href = + openArtist( + pick(artistData.filter((artist) => getArtistNumContributions(artist) > 1)) + .directory); + break; + } + }); } -}); +} + +function mutateNavigationLinkContent() { + const prependTitle = (el, prepend) => + el?.setAttribute('title', + (el.hasAttribute('title') + ? prepend + ' ' + el.getAttribute('title') + : prepend)); -for (const reveal of document.querySelectorAll('.reveal')) { - reveal.addEventListener('click', (event) => { - if (!reveal.classList.contains('revealed')) { - reveal.classList.add('revealed'); - event.preventDefault(); - event.stopPropagation(); - reveal.dispatchEvent(new CustomEvent('hsmusic-reveal')); + prependTitle(scriptedLinkInfo.nextNavLink, '(Shift+N)'); + prependTitle(scriptedLinkInfo.previousNavLink, '(Shift+P)'); + prependTitle(scriptedLinkInfo.randomNavLink, '(Shift+R)'); +} + +function addNavigationKeyPressListeners() { + document.addEventListener('keypress', (event) => { + if (event.shiftKey) { + if (event.charCode === 'N'.charCodeAt(0)) { + scriptedLinkInfo.nextNavLink?.click(); + } else if (event.charCode === 'P'.charCodeAt(0)) { + scriptedLinkInfo.previousNavLink?.click(); + } else if (event.charCode === 'R'.charCodeAt(0)) { + if (ready) { + scriptedLinkInfo.randomNavLink?.click(); + } + } } }); } +function addRevealLinkClickListeners() { + for (const reveal of scriptedLinkInfo.revealLinks ?? []) { + reveal.addEventListener('click', (event) => { + if (!reveal.classList.contains('revealed')) { + reveal.classList.add('revealed'); + event.preventDefault(); + event.stopPropagation(); + reveal.dispatchEvent(new CustomEvent('hsmusic-reveal')); + } + }); + } +} + +clientSteps.getPageReferences.push(getScriptedLinkReferences); +clientSteps.addPageListeners.push(addRandomLinkListeners); +clientSteps.addPageListeners.push(addNavigationKeyPressListeners); +clientSteps.addPageListeners.push(addRevealLinkClickListeners); +clientSteps.mutatePageContent.push(mutateNavigationLinkContent); + const elements1 = document.getElementsByClassName('js-hide-once-data'); const elements2 = document.getElementsByClassName('js-show-once-data'); @@ -454,205 +504,393 @@ if (localStorage.tryInfoCards) { // Custom hash links -------------------------------------- -function addHashLinkHandlers() { +const hashLinkInfo = clientInfo.hashLinkInfo = { + links: null, + hrefs: null, + targets: null, + + state: { + highlightedTarget: null, + scrollingAfterClick: false, + concludeScrollingStateInterval: null, + }, + + event: { + whenHashLinkClicked: [], + }, +}; + +function getHashLinkReferences() { + const info = hashLinkInfo; + + info.links = + Array.from(document.querySelectorAll('a[href^="#"]:not([href="#"])')); + + info.hrefs = + info.links + .map(link => link.getAttribute('href')); + + info.targets = + info.hrefs + .map(href => document.getElementById(href.slice(1))); + + filterMultipleArrays( + info.links, + info.hrefs, + info.targets, + (_link, _href, target) => target); +} + +function processScrollingAfterHashLinkClicked() { + const {state} = hashLinkInfo; + + if (state.concludeScrollingStateInterval) return; + + let lastScroll = window.scrollY; + state.scrollingAfterClick = true; + state.concludeScrollingStateInterval = setInterval(() => { + if (Math.abs(window.scrollY - lastScroll) < 10) { + clearInterval(state.concludeScrollingStateInterval); + state.scrollingAfterClick = false; + state.concludeScrollingStateInterval = null; + } else { + lastScroll = window.scrollY; + } + }, 200); +} + +function addHashLinkListeners() { // Instead of defining a scroll offset (to account for the sticky heading) // in JavaScript, we interface with the CSS property 'scroll-margin-top'. // This lets the scroll offset be consolidated where it makes sense, and // sets an appropriate offset when (re)loading a page with hash for free! - let wasHighlighted; + const info = hashLinkInfo; + const {state, event} = info; - for (const a of document.links) { - const href = a.getAttribute('href'); - if (!href || !href.startsWith('#')) { - continue; - } + for (const {hashLink, href, target} of stitchArrays({ + hashLink: info.links, + href: info.hrefs, + target: info.targets, + })) { + hashLink.addEventListener('click', evt => { + if (evt.metaKey || evt.shiftKey || evt.ctrlKey || evt.altKey) { + return; + } - a.addEventListener('click', handleHashLinkClicked); - } + // Hide skipper box right away, so the layout is updated on time for the + // math operations coming up next. + const skipper = document.getElementById('skippers'); + skipper.style.display = 'none'; + setTimeout(() => skipper.style.display = ''); - function handleHashLinkClicked(evt) { - if (evt.metaKey || evt.shiftKey || evt.ctrlKey || evt.altKey) { - return; - } + const box = target.getBoundingClientRect(); + const style = window.getComputedStyle(target); - const href = evt.target.getAttribute('href'); - const id = href.slice(1); - const linked = document.getElementById(id); + const scrollY = + window.scrollY + + box.top + - style['scroll-margin-top'].replace('px', ''); - if (!linked) { - return; - } - - // Hide skipper box right away, so the layout is updated on time for the - // math operations coming up next. - const skipper = document.getElementById('skippers'); - skipper.style.display = 'none'; - setTimeout(() => skipper.style.display = ''); + evt.preventDefault(); + history.pushState({}, '', href); + window.scrollTo({top: scrollY, behavior: 'smooth'}); + target.focus({preventScroll: true}); - const box = linked.getBoundingClientRect(); - const style = window.getComputedStyle(linked); + const maxScroll = + document.body.scrollHeight + - window.innerHeight; - const scrollY = - window.scrollY - + box.top - - style['scroll-margin-top'].replace('px', ''); + if (scrollY > maxScroll && target.classList.contains('content-heading')) { + if (state.highlightedTarget) { + state.highlightedTarget.classList.remove('highlight-hash-link'); + } - evt.preventDefault(); - history.pushState({}, '', href); - window.scrollTo({top: scrollY, behavior: 'smooth'}); - linked.focus({preventScroll: true}); + target.classList.add('highlight-hash-link'); + state.highlightedTarget = target; + } - const maxScroll = - document.body.scrollHeight - - window.innerHeight; + processScrollingAfterHashLinkClicked(); - if (scrollY > maxScroll && linked.classList.contains('content-heading')) { - if (wasHighlighted) { - wasHighlighted.classList.remove('highlight-hash-link'); + for (const handler of event.whenHashLinkClicked) { + handler({ + link: hashLink, + }); } + }); + } - wasHighlighted = linked; - linked.classList.add('highlight-hash-link'); - linked.addEventListener('animationend', function handle(evt) { - if (evt.animationName === 'highlight-hash-link') { - linked.removeEventListener('animationend', handle); - linked.classList.remove('highlight-hash-link'); - wasHighlighted = null; - } - }); - } + for (const target of info.targets) { + target.addEventListener('animationend', evt => { + if (evt.animationName !== 'highlight-hash-link') return; + target.classList.remove('highlight-hash-link'); + if (target !== state.highlightedTarget) return; + state.highlightedTarget = null; + }); } } -addHashLinkHandlers(); +clientSteps.getPageReferences.push(getHashLinkReferences); +clientSteps.addPageListeners.push(addHashLinkListeners); // Sticky content heading --------------------------------- -const stickyHeadingInfo = Array.from(document.querySelectorAll('.content-sticky-heading-container')) - .map(stickyContainer => { - const {parentElement: contentContainer} = stickyContainer; - const stickySubheadingRow = stickyContainer.querySelector('.content-sticky-subheading-row'); - const stickySubheading = stickySubheadingRow.querySelector('h2'); - let stickyCoverContainer = stickyContainer.querySelector('.content-sticky-heading-cover-container'); - let stickyCover = stickyCoverContainer?.querySelector('.content-sticky-heading-cover'); - const contentHeadings = Array.from(contentContainer.querySelectorAll('.content-heading')); - const contentCover = contentContainer.querySelector('#cover-art-container'); - - if (stickyCover?.querySelector('.image-text-area')) { - stickyCoverContainer.remove(); - stickyCoverContainer = null; - stickyCover = null; - } +const stickyHeadingInfo = clientInfo.stickyHeadingInfo = { + stickyContainers: null, - return { - contentContainer, - contentCover, - contentHeadings, - stickyContainer, - stickyCover, - stickyCoverContainer, - stickySubheading, - stickySubheadingRow, - state: { - displayedHeading: null, - }, - }; - }); + stickySubheadingRows: null, + stickySubheadings: null, -const topOfViewInside = (el, scroll = window.scrollY) => ( - scroll > el.offsetTop && - scroll < el.offsetTop + el.offsetHeight -); - -function prepareStickyHeadings() { - for (const { - contentCover, - stickyCover, - } of stickyHeadingInfo) { - const coverRevealImage = contentCover?.querySelector('.reveal'); - if (coverRevealImage) { - stickyCover.classList.add('content-sticky-heading-cover-needs-reveal'); - coverRevealImage.addEventListener('hsmusic-reveal', () => { - stickyCover.classList.remove('content-sticky-heading-cover-needs-reveal'); - }); + stickyCoverContainers: null, + stickyCoverTextAreas: null, + stickyCovers: null, + + contentContainers: null, + contentHeadings: null, + contentCovers: null, + contentCoversReveal: null, + + state: { + displayedHeading: null, + }, + + event: { + whenDisplayedHeadingChanges: [], + }, +}; + +function getStickyHeadingReferences() { + const info = stickyHeadingInfo; + + info.stickyContainers = + Array.from(document.getElementsByClassName('content-sticky-heading-container')); + + info.stickyCoverContainers = + info.stickyContainers + .map(el => el.querySelector('.content-sticky-heading-cover-container')); + + info.stickyCovers = + info.stickyCoverContainers + .map(el => el?.querySelector('.content-sticky-heading-cover')); + + info.stickyCoverTextAreas = + info.stickyCovers + .map(el => el?.querySelector('.image-text-area')); + + info.stickySubheadingRows = + info.stickyContainers + .map(el => el.querySelector('.content-sticky-subheading-row')); + + info.stickySubheadings = + info.stickySubheadingRows + .map(el => el.querySelector('h2')); + + info.contentContainers = + info.stickyContainers + .map(el => el.parentElement); + + info.contentCovers = + info.contentContainers + .map(el => el.querySelector('#cover-art-container')); + + info.contentCoversReveal = + info.contentCovers + .map(el => el ? !!el.querySelector('.reveal') : null); + + info.contentHeadings = + info.contentContainers + .map(el => Array.from(el.querySelectorAll('.content-heading'))); +} + +function removeTextPlaceholderStickyHeadingCovers() { + const info = stickyHeadingInfo; + + const hasTextArea = + info.stickyCoverTextAreas.map(el => !!el); + + const coverContainersWithTextArea = + info.stickyCoverContainers + .filter((_el, index) => hasTextArea[index]); + + for (const el of coverContainersWithTextArea) { + el.remove(); + } + + info.stickyCoverContainers = + info.stickyCoverContainers + .map((el, index) => hasTextArea[index] ? null : el); + + info.stickyCovers = + info.stickyCovers + .map((el, index) => hasTextArea[index] ? null : el); + + info.stickyCoverTextAreas = + info.stickyCoverTextAreas + .slice() + .fill(null); +} + +function addRevealClassToStickyHeadingCovers() { + const info = stickyHeadingInfo; + + const stickyCoversWhichReveal = + info.stickyCovers + .filter((_el, index) => info.contentCoversReveal[index]); + + for (const el of stickyCoversWhichReveal) { + el.classList.add('content-sticky-heading-cover-needs-reveal'); + } +} + +function addRevealListenersForStickyHeadingCovers() { + const info = stickyHeadingInfo; + + const stickyCovers = info.stickyCovers.slice(); + const contentCovers = info.contentCovers.slice(); + + filterMultipleArrays( + stickyCovers, + contentCovers, + (_stickyCover, _contentCover, index) => info.contentCoversReveal[index]); + + for (const {stickyCover, contentCover} of stitchArrays({ + stickyCover: stickyCovers, + contentCover: contentCovers, + })) { + // TODO: Janky - should use internal event instead of DOM event + contentCover.querySelector('.reveal').addEventListener('hsmusic-reveal', () => { + stickyCover.classList.remove('content-sticky-heading-cover-needs-reveal'); + }); + } +} + +function topOfViewInside(el, scroll = window.scrollY) { + return ( + scroll > el.offsetTop && + scroll < el.offsetTop + el.offsetHeight); +} + +function updateStickyCoverVisibility(index) { + const info = stickyHeadingInfo; + + const stickyCoverContainer = info.stickyCoverContainers[index]; + const contentCover = info.contentCovers[index]; + + if (contentCover && stickyCoverContainer) { + if (contentCover.getBoundingClientRect().bottom < 0) { + stickyCoverContainer.classList.add('visible'); + } else { + stickyCoverContainer.classList.remove('visible'); } } } -function updateStickyHeading() { - for (const { - contentContainer, - contentCover, - contentHeadings, - stickyContainer, - stickyCoverContainer, - stickySubheading, - stickySubheadingRow, - state, - } of stickyHeadingInfo) { - let closestHeading = null; +function getContentHeadingClosestToStickySubheading(index) { + const info = stickyHeadingInfo; - if (contentCover && stickyCoverContainer) { - if (contentCover.getBoundingClientRect().bottom < 0) { - stickyCoverContainer.classList.add('visible'); - } else { - stickyCoverContainer.classList.remove('visible'); - } + const contentContainer = info.contentContainers[index]; + + if (!topOfViewInside(contentContainer)) { + return null; + } + + const stickySubheading = info.stickySubheadings[index]; + + if (stickySubheading.childNodes.length === 0) { + // Supply a non-breaking space to ensure correct basic line height. + stickySubheading.appendChild(document.createTextNode('\xA0')); + } + + const stickyContainer = info.stickyContainers[index]; + const stickyRect = stickyContainer.getBoundingClientRect(); + + // TODO: Should this compute with the subheading row instead of h2? + const subheadingRect = stickySubheading.getBoundingClientRect(); + + const stickyBottom = stickyRect.bottom + subheadingRect.height; + + // Iterate from bottom to top of the content area. + const contentHeadings = info.contentHeadings[index]; + for (const heading of contentHeadings.slice().reverse()) { + const headingRect = heading.getBoundingClientRect(); + if (headingRect.y + headingRect.height / 1.5 < stickyBottom + 20) { + return heading; } + } - if (topOfViewInside(contentContainer)) { - if (stickySubheading.childNodes.length === 0) { - //   to ensure correct basic line height - stickySubheading.appendChild(document.createTextNode('\xA0')); - } + return null; +} - const stickyRect = stickyContainer.getBoundingClientRect(); - const subheadingRect = stickySubheading.getBoundingClientRect(); - const stickyBottom = stickyRect.bottom + subheadingRect.height; - - // This array is reversed so that we're starting from the bottom when - // iterating over it. - for (let i = contentHeadings.length - 1; i >= 0; i--) { - const heading = contentHeadings[i]; - const headingRect = heading.getBoundingClientRect(); - if (headingRect.y + headingRect.height / 1.5 < stickyBottom + 20) { - closestHeading = heading; - break; +function updateStickySubheadingContent(index) { + const info = stickyHeadingInfo; + const {event, state} = info; + + const closestHeading = getContentHeadingClosestToStickySubheading(index); + + if (state.displayedHeading === closestHeading) return; + + const stickySubheadingRow = info.stickySubheadingRows[index]; + + if (closestHeading) { + const stickySubheading = info.stickySubheadings[index]; + + // Array.from needed to iterate over a live array with for..of + for (const child of Array.from(stickySubheading.childNodes)) { + child.remove(); + } + + for (const child of closestHeading.childNodes) { + if (child.tagName === 'A') { + for (const grandchild of child.childNodes) { + stickySubheading.appendChild(grandchild.cloneNode(true)); } + } else { + stickySubheading.appendChild(child.cloneNode(true)); } } - if (state.displayedHeading !== closestHeading) { - if (closestHeading) { - // Array.from needed to iterate over a live array with for..of - for (const child of Array.from(stickySubheading.childNodes)) { - child.remove(); - } + stickySubheadingRow.classList.add('visible'); + } else { + stickySubheadingRow.classList.remove('visible'); + } - for (const child of closestHeading.childNodes) { - if (child.tagName === 'A') { - for (const grandchild of child.childNodes) { - stickySubheading.appendChild(grandchild.cloneNode(true)); - } - } else { - stickySubheading.appendChild(child.cloneNode(true)); - } - } + const oldDisplayedHeading = state.displayedHeading; - stickySubheadingRow.classList.add('visible'); - } else { - stickySubheadingRow.classList.remove('visible'); - } + state.displayedHeading = closestHeading; - state.displayedHeading = closestHeading; - } + for (const handler of event.whenDisplayedHeadingChanges) { + handler(index, { + oldHeading: oldDisplayedHeading, + newHeading: closestHeading, + }); } } -document.addEventListener('scroll', updateStickyHeading); -prepareStickyHeadings(); -updateStickyHeading(); +function updateStickyHeadings(index) { + updateStickyCoverVisibility(index); + updateStickySubheadingContent(index); +} + +function initializeStateForStickyHeadings() { + for (let i = 0; i < stickyHeadingInfo.stickyContainers.length; i++) { + updateStickyHeadings(i); + } +} + +function addScrollListenerForStickyHeadings() { + document.addEventListener('scroll', () => { + for (let i = 0; i < stickyHeadingInfo.stickyContainers.length; i++) { + updateStickyHeadings(i); + } + }); +} + +clientSteps.getPageReferences.push(getStickyHeadingReferences); +clientSteps.mutatePageContent.push(removeTextPlaceholderStickyHeadingCovers); +clientSteps.mutatePageContent.push(addRevealClassToStickyHeadingCovers); +clientSteps.initializeState.push(initializeStateForStickyHeadings); +clientSteps.addPageListeners.push(addRevealListenersForStickyHeadingCovers); +clientSteps.addPageListeners.push(addScrollListenerForStickyHeadings); // Image overlay ------------------------------------------ @@ -970,3 +1208,16 @@ for (const info of groupContributionsTableInfo) { sortGroupContributionsTableBy(info, 'count'); }); } + +// Run setup steps ---------------------------------------- + +for (const [key, steps] of Object.entries(clientSteps)) { + for (const step of steps) { + try { + step(); + } catch (error) { + console.warn(`During ${key}, failed to run ${step.name}`); + console.debug(error); + } + } +} -- cgit 1.3.0-6-gf8a5 From 33f622ca94cdac2b7b6b1d3bbd57a96248e57035 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 24 Sep 2023 15:41:53 -0300 Subject: client: implement album commentary sidebar dynamics --- src/static/client2.js | 211 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 211 insertions(+) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 96b422a8..4f4a7153 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -1209,6 +1209,217 @@ for (const info of groupContributionsTableInfo) { }); } +// Sticky commentary sidebar ------------------------------ + +const albumCommentarySidebarInfo = clientInfo.albumCommentarySidebarInfo = { + sidebar: null, + + sidebarTrackLinks: null, + sidebarTrackDirectories: null, + + sidebarTrackSections: null, + sidebarTrackSectionStartIndices: null, + + state: { + currentTrackSection: null, + currentTrackLink: null, + justChangedTrackSection: false, + }, +}; + +function getAlbumCommentarySidebarReferences() { + const info = albumCommentarySidebarInfo; + + info.sidebar = + document.getElementById('sidebar-left'); + + info.sidebarHeading = + info.sidebar.querySelector('h1'); + + info.sidebarTrackLinks = + Array.from(info.sidebar.querySelectorAll('li a')); + + info.sidebarTrackDirectories = + info.sidebarTrackLinks + .map(el => el.getAttribute('href').slice(1)); + + info.sidebarTrackSections = + Array.from(info.sidebar.getElementsByTagName('details')); + + info.sidebarTrackSectionStartIndices = + info.sidebarTrackSections + .map(details => details.querySelector('ol, ul')) + .reduce( + (accumulator, _list, index, array) => + (empty(accumulator) + ? [0] + : [ + ...accumulator, + (accumulator[accumulator.length - 1] + + array[index - 1].querySelectorAll('li a').length), + ]), + []); +} + +function scrollAlbumCommentarySidebar() { + const info = albumCommentarySidebarInfo; + const {state} = info; + const {currentTrackLink, currentTrackSection} = state; + + if (!currentTrackLink) { + return; + } + + const {sidebar, sidebarHeading} = info; + + const scrollTop = sidebar.scrollTop; + + const headingRect = sidebarHeading.getBoundingClientRect(); + const sidebarRect = sidebar.getBoundingClientRect(); + + const stickyPadding = headingRect.height; + const sidebarViewportHeight = sidebarRect.height - stickyPadding; + + const linkRect = currentTrackLink.getBoundingClientRect(); + const sectionRect = currentTrackSection.getBoundingClientRect(); + + const sectionTopEdge = + sectionRect.top - (sidebarRect.top - scrollTop); + + const sectionHeight = + sectionRect.height; + + const sectionScrollTop = + sectionTopEdge - stickyPadding - 10; + + const linkTopEdge = + linkRect.top - (sidebarRect.top - scrollTop); + + const linkBottomEdge = + linkRect.bottom - (sidebarRect.top - scrollTop); + + const linkScrollTop = + linkTopEdge - stickyPadding - 5; + + const linkDistanceFromSection = + linkScrollTop - sectionTopEdge; + + const linkVisibleFromTopOfSection = + linkBottomEdge - sectionTopEdge > sidebarViewportHeight; + + const linkScrollBottom = + linkScrollTop - sidebarViewportHeight + linkRect.height + 20; + + const maxScrollInViewport = + scrollTop + stickyPadding + sidebarViewportHeight; + + const minScrollInViewport = + scrollTop + stickyPadding; + + if (linkBottomEdge > maxScrollInViewport) { + if (linkVisibleFromTopOfSection) { + sidebar.scrollTo({top: linkScrollBottom, behavior: 'smooth'}); + } else { + sidebar.scrollTo({top: sectionScrollTop, behavior: 'smooth'}); + } + } else if (linkTopEdge < minScrollInViewport) { + if (linkVisibleFromTopOfSection) { + sidebar.scrollTo({top: linkScrollTop, behavior: 'smooth'}); + } else { + sidebar.scrollTo({top: sectionScrollTop, behavior: 'smooth'}); + } + } else if (state.justChangedTrackSection) { + if (sectionHeight < sidebarViewportHeight) { + sidebar.scrollTo({top: sectionScrollTop, behavior: 'smooth'}); + } + } +} + +function markDirectoryAsCurrentForAlbumCommentary(trackDirectory) { + const info = albumCommentarySidebarInfo; + const {state} = info; + + const trackIndex = + (trackDirectory + ? info.sidebarTrackDirectories + .indexOf(trackDirectory) + : -1); + + const sectionIndex = + (trackIndex >= 0 + ? info.sidebarTrackSectionStartIndices + .findIndex((start, index, array) => + (index === array.length - 1 + ? true + : trackIndex < array[index + 1])) + : -1); + + const sidebarTrackLink = + (trackIndex >= 0 + ? info.sidebarTrackLinks[trackIndex] + : null); + + const sidebarTrackSection = + (sectionIndex >= 0 + ? info.sidebarTrackSections[sectionIndex] + : null); + + state.currentTrackLink?.classList?.remove('current'); + state.currentTrackLink = sidebarTrackLink; + state.currentTrackLink?.classList?.add('current'); + + if (sidebarTrackSection !== state.currentTrackSection) { + if (sidebarTrackSection && !sidebarTrackSection.open) { + if (state.currentTrackSection) { + state.currentTrackSection.open = false; + } + + sidebarTrackSection.open = true; + } + + state.currentTrackSection?.classList?.remove('current'); + state.currentTrackSection = sidebarTrackSection; + state.currentTrackSection?.classList?.add('current'); + state.justChangedTrackSection = true; + } else { + state.justChangedTrackSection = false; + } +} + +function addAlbumCommentaryInternalListeners() { + const info = albumCommentarySidebarInfo; + + const mainContentIndex = + (stickyHeadingInfo.contentContainers ?? []) + .findIndex(({id}) => id === 'content'); + + if (mainContentIndex === -1) return; + + stickyHeadingInfo.event.whenDisplayedHeadingChanges.push((index, {newHeading}) => { + if (index !== mainContentIndex) return; + if (hashLinkInfo.state.scrollingAfterClick) return; + + const trackDirectory = + (newHeading + ? newHeading.id + : null); + + markDirectoryAsCurrentForAlbumCommentary(trackDirectory); + scrollAlbumCommentarySidebar(); + }); + + hashLinkInfo.event.whenHashLinkClicked.push(({link}) => { + const hash = link.getAttribute('href').slice(1); + if (!info.sidebarTrackDirectories.includes(hash)) return; + markDirectoryAsCurrentForAlbumCommentary(hash); + }); +} + +if (document.documentElement.dataset.urlKey === 'localized.albumCommentary') { + clientSteps.getPageReferences.push(getAlbumCommentarySidebarReferences); + clientSteps.addInternalListeners.push(addAlbumCommentaryInternalListeners); +} + // Run setup steps ---------------------------------------- for (const [key, steps] of Object.entries(clientSteps)) { -- cgit 1.3.0-6-gf8a5 From d21a899ec62d0298f298a9fbdd8b74e5bc44c681 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 6 Oct 2023 09:01:35 -0300 Subject: css: intervene with default scrollbar styling on sticky sidebar --- src/static/site4.css | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) (limited to 'src/static') diff --git a/src/static/site4.css b/src/static/site4.css index 0e6166b4..ab17bf0c 100644 --- a/src/static/site4.css +++ b/src/static/site4.css @@ -1451,12 +1451,27 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r .sidebar-column.sidebar.sticky-column { max-height: calc(100vh - 20px); - overflow-y: scroll; align-self: start; padding-bottom: 0; box-sizing: border-box; flex-basis: 275px; padding-top: 0; + overflow-y: scroll; + scrollbar-width: thin; + scrollbar-color: var(--dark-color); +} + +.sidebar-column.sidebar.sticky-column::-webkit-scrollbar { + background: var(--dark-color); + width: 12px; +} + +.sidebar-column.sidebar.sticky-column::-webkit-scrollbar-thumb { + transition: background 0.2s; + background: rgba(255, 255, 255, 0.2); + border: 3px solid transparent; + border-radius: 10px; + background-clip: content-box; } .sidebar-column.sidebar.sticky-column > h1 { -- cgit 1.3.0-6-gf8a5 From d7dcbbfe67ab7e8728da3f0503a6b1a20e9e2664 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Fri, 6 Oct 2023 18:37:08 -0300 Subject: content, css: supporting changes for better secondary nav dynamics --- src/static/site4.css | 1745 -------------------------------------------------- src/static/site5.css | 1745 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1745 insertions(+), 1745 deletions(-) delete mode 100644 src/static/site4.css create mode 100644 src/static/site5.css (limited to 'src/static') diff --git a/src/static/site4.css b/src/static/site4.css deleted file mode 100644 index ab17bf0c..00000000 --- a/src/static/site4.css +++ /dev/null @@ -1,1745 +0,0 @@ -/* A frontend file! Wow. - * This file is just loaded statically 8y s in the HTML files, so there's - * no need to re-run upd8.js when tweaking values here. Handy! - */ - -:root { - --primary-color: #0088ff; -} - -/* Layout - Common - * - */ - -body { - margin: 10px; - overflow-y: scroll; -} - -body::before { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: -1; - - /* NB: these are 100 LVW, "largest view width", etc. - * Stabilizes background on viewports with modal dimensions, - * e.g. expanding/shrinking tab bar or collapsible find bar. - * 100% dimensions are kept above for browser compatibility. - */ - width: 100lvw; - height: 100lvh; -} - -#page-container { - max-width: 1100px; - margin: 10px auto 50px; - padding: 15px 0; -} - -#page-container > * { - margin-left: 15px; - margin-right: 15px; -} - -#skippers:focus-within { - position: static; - width: unset; - height: unset; -} - -#banner { - margin: 10px 0; - width: 100%; - position: relative; -} - -#banner::after { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; -} - -#banner img { - display: block; - width: 100%; - height: auto; -} - -#skippers { - position: absolute; - left: -10000px; - top: auto; - width: 1px; - height: 1px; -} - -.layout-columns { - display: flex; - align-items: stretch; -} - -#header, -#secondary-nav, -#skippers, -#footer { - padding: 5px; -} - -#header, -#secondary-nav, -#skippers { - margin-bottom: 10px; -} - -#footer { - margin-top: 10px; -} - -#header { - display: grid; -} - -#header.nav-has-main-links.nav-has-content { - grid-template-columns: 2.5fr 3fr; - grid-template-rows: min-content 1fr; - grid-template-areas: - "main-links content" - "bottom-row content"; -} - -#header.nav-has-main-links:not(.nav-has-content) { - grid-template-columns: 1fr; - grid-template-areas: - "main-links" - "bottom-row"; -} - -.nav-main-links { - grid-area: main-links; - margin-right: 20px; -} - -.nav-content { - grid-area: content; -} - -.nav-bottom-row { - grid-area: bottom-row; - align-self: start; -} - -.sidebar-column { - flex: 1 1 20%; - min-width: 150px; - max-width: 250px; - flex-basis: 250px; - align-self: flex-start; -} - -.sidebar-column.wide { - max-width: 350px; - flex-basis: 300px; - flex-shrink: 0; - flex-grow: 1; -} - -.sidebar-multiple { - display: flex; - flex-direction: column; -} - -.sidebar-multiple .sidebar:not(:first-child) { - margin-top: 15px; -} - -.sidebar { - --content-padding: 5px; - padding: var(--content-padding); -} - -#sidebar-left { - margin-right: 10px; -} - -#sidebar-right { - margin-left: 10px; -} - -#content { - position: relative; - --content-padding: 20px; - box-sizing: border-box; - padding: var(--content-padding); - flex-grow: 1; - flex-shrink: 3; -} - -.footer-content { - margin: 5px 12%; -} - -.footer-content > :first-child { - margin-top: 0; -} - -.footer-content > :last-child { - margin-bottom: 0; -} - -.footer-localization-links { - margin: 5px 12%; -} - -/* Design & Appearance - Layout elements */ - -body { - background: black; -} - -body::before { - background-image: url("../media/bg.jpg"); - background-position: center; - background-size: cover; - opacity: 0.5; -} - -#page-container { - background-color: var(--bg-color, rgba(35, 35, 35, 0.8)); - color: #ffffff; - box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); -} - -#skippers > * { - display: inline-block; -} - -#skippers > .skipper-list:not(:last-child)::after { - display: inline-block; - content: "\00a0"; - margin-left: 2px; - margin-right: -2px; - border-left: 1px dotted; -} - -#skippers .skipper-list > .skipper:not(:last-child)::after { - content: " \00b7 "; - font-weight: 800; -} - -#banner { - background: black; - background-color: var(--dim-color); - border-bottom: 1px solid var(--primary-color); -} - -#banner::after { - box-shadow: inset 0 -2px 3px rgba(0, 0, 0, 0.35); - pointer-events: none; -} - -#banner.dim img { - opacity: 0.8; -} - -#header, -#secondary-nav, -#skippers, -#footer, -.sidebar { - font-size: 0.85em; -} - -.sidebar, -#content, -#header, -#secondary-nav, -#skippers, -#footer { - background-color: rgba(0, 0, 0, 0.6); - border: 1px dotted var(--primary-color); - border-radius: 3px; - transition: background-color 0.2s; -} - -/* -.sidebar:focus-within, -#content:focus-within, -#header:focus-within, -#secondary-nav:focus-within, -#skippers:focus-within, -#footer:focus-within { - background-color: rgba(0, 0, 0, 0.85); - border-style: solid; -} -*/ - -.sidebar > h1, -.sidebar > h2, -.sidebar > h3, -.sidebar > p { - text-align: center; -} - -.sidebar h1 { - font-size: 1.25em; -} - -.sidebar h2 { - font-size: 1.1em; - margin: 0; -} - -.sidebar h3 { - font-size: 1.1em; - font-style: oblique; - font-variant: small-caps; - margin-top: 0.3em; - margin-bottom: 0em; -} - -.sidebar > p { - margin: 0.5em 0; - padding: 0 5px; -} - -.sidebar hr { - color: #555; - margin: 10px 5px; -} - -.sidebar > ol, -.sidebar > ul { - padding-left: 30px; - padding-right: 15px; -} - -.sidebar > dl { - padding-right: 15px; - padding-left: 0; -} - -.sidebar > dl dt { - padding-left: 10px; - margin-top: 0.5em; -} - -.sidebar > dl dt.current { - font-weight: 800; -} - -.sidebar > dl dd { - margin-left: 0; -} - -.sidebar > dl dd ul { - padding-left: 30px; - margin-left: 0; -} - -.sidebar > dl .side { - padding-left: 10px; -} - -.sidebar li.current { - font-weight: 800; -} - -.sidebar li { - overflow-wrap: break-word; -} - -.sidebar > details.current summary { - font-weight: 800; -} - -.sidebar > details summary { - margin-top: 0.5em; - padding-left: 5px; -} - -summary > span:hover { - cursor: pointer; - text-decoration: underline; - text-decoration-color: var(--primary-color); -} - -summary .group-name { - color: var(--primary-color); -} - -.sidebar > details ul, -.sidebar > details ol { - margin-top: 0; - margin-bottom: 0; -} - -.sidebar > details:last-child { - margin-bottom: 10px; -} - -.sidebar > details[open] { - margin-bottom: 1em; -} - -.sidebar article { - text-align: left; - margin: 5px 5px 15px 5px; -} - -.sidebar article:last-child { - margin-bottom: 5px; -} - -.sidebar article h2, -.news-index h2 { - border-bottom: 1px dotted; -} - -.sidebar article h2 time, -.news-index time { - float: right; - font-weight: normal; -} - -#content { - overflow-wrap: anywhere; -} - -footer { - text-align: center; - font-style: oblique; -} - -.footer-localization-links > span:not(:last-child)::after { - content: " \00b7 "; - font-weight: 800; -} - -/* Design & Appearance - Content elements */ - -a { - color: var(--primary-color); - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -a.current { - font-weight: 800; -} - -.nav-main-links > span > span { - white-space: nowrap; -} - -.nav-main-links > span.current > span.nav-link-content > a { - font-weight: 800; -} - -.nav-links-index > span:not(:first-child):not(.no-divider)::before, -.nav-links-groups > span:not(:first-child):not(.no-divider)::before { - content: "\0020\00b7\0020"; - font-weight: 800; -} - -.nav-links-hierarchical > span:not(:first-child):not(.no-divider)::before { - content: "\0020/\0020"; -} - -#header .chronology .heading, -#header .chronology .buttons { - white-space: nowrap; -} - -#secondary-nav { - text-align: center; -} - -.nowrap { - white-space: nowrap; -} - -.icons { - font-style: normal; - white-space: nowrap; -} - -.icon { - display: inline-block; - width: 24px; - height: 1em; - position: relative; -} - -.icon > svg { - width: 24px; - height: 24px; - top: -0.25em; - position: absolute; - fill: var(--primary-color); -} - -.rerelease, -.other-group-accent { - opacity: 0.7; - font-style: oblique; -} - -.other-group-accent { - white-space: nowrap; -} - -.content-columns { - columns: 2; -} - -.content-columns .column { - break-inside: avoid; -} - -.content-columns .column h2 { - margin-top: 0; - font-size: 1em; -} - -p .current { - font-weight: 800; -} - -#cover-art-container { - font-size: 0.8em; -} - -#cover-art .square { - box-shadow: 0 0 3px 6px rgba(0, 0, 0, 0.35); -} - -#cover-art img { - display: block; - width: 100%; - height: 100%; -} - -#cover-art-container p { - margin-top: 5px; -} - -.commentary-art { - float: right; - width: 30%; - max-width: 250px; - margin: 15px 0 10px 20px; -} - -.js-hide, -.js-show-once-data, -.js-hide-once-data { - display: none; -} - -.content-image { - margin-top: 1em; - margin-bottom: 1em; -} - -a.box:focus { - outline: 3px double var(--primary-color); -} - -a.box:focus:not(:focus-visible) { - outline: none; -} - -a.box img { - display: block; - max-width: 100%; - height: auto; -} - -.square .image-container { - width: 100%; - height: 100%; -} - -h1 { - font-size: 1.5em; -} - -#content li { - margin-bottom: 4px; -} - -#content li i { - white-space: nowrap; -} - -#content.top-index h1, -#content.flash-index h1 { - text-align: center; - font-size: 2em; -} - -html[data-url-key="localized.home"] #content h1 { - text-align: center; - font-size: 2.5em; -} - -#content.flash-index h2 { - text-align: center; - font-size: 2.5em; - font-variant: small-caps; - font-style: oblique; - margin-bottom: 0; - text-align: center; - width: 100%; -} - -#content.top-index h2 { - text-align: center; - font-size: 2em; - font-weight: normal; - margin-bottom: 0.25em; -} - -.quick-info { - text-align: center; -} - -ul.quick-info { - list-style: none; - padding-left: 0; -} - -ul.quick-info li { - display: inline-block; -} - -ul.quick-info li:not(:last-child)::after { - content: " \00b7 "; - font-weight: 800; -} - -.carousel-container + .quick-info { - margin-top: 25px; -} - -#intro-menu { - margin: 24px 0; - padding: 10px; - background-color: #222222; - text-align: center; - border: 1px dotted var(--primary-color); - border-radius: 2px; -} - -#intro-menu p { - margin: 12px 0; -} - -#intro-menu a { - margin: 0 6px; -} - -li .by { - display: inline-block; - font-style: oblique; -} - -li .by a { - display: inline-block; -} - -p code { - font-size: 1em; - font-family: "courier new"; - font-weight: 800; -} - -#content blockquote { - margin-left: 40px; - max-width: 600px; - margin-right: 0; -} - -#content blockquote blockquote { - margin-left: 10px; - padding-left: 10px; - margin-right: 20px; - border-left: dotted 1px; - padding-top: 6px; - padding-bottom: 6px; -} - -#content blockquote blockquote > :first-child { - margin-top: 0; -} - -#content blockquote blockquote > :last-child { - margin-bottom: 0; -} - -main.long-content .main-content-container, -main.long-content > h1 { - padding-left: 12%; - padding-right: 12%; -} - -dl dt { - padding-left: 40px; - max-width: 600px; -} - -dl dt { - margin-bottom: 2px; -} - -dl dd { - margin-bottom: 1em; -} - -dl ul, -dl ol { - margin-top: 0; - margin-bottom: 0; -} - -ul > li.has-details { - list-style-type: none; - margin-left: -17px; -} - -.album-group-list dt { - font-style: oblique; - padding-left: 0; -} - -.album-group-list dd { - margin-left: 0; -} - -.group-chronology-link { - font-style: oblique; -} - -#content hr { - border: 1px inset #808080; - width: 100%; -} - -#content hr.split::before { - content: "(split)"; - color: #808080; -} - -#content hr.split { - position: relative; - overflow: hidden; - border: none; -} - -#content hr.split::after { - display: inline-block; - content: ""; - border: 1px inset #808080; - width: 100%; - position: absolute; - top: 50%; - margin-top: -2px; - margin-left: 10px; -} - -li > ul { - margin-top: 5px; -} - -.group-contributions-table { - display: inline-block; -} - -.group-contributions-table .group-contributions-row { - display: flex; - justify-content: space-between; -} - -.group-contributions-table .group-contributions-metrics { - margin-left: 1.5ch; - white-space: nowrap; -} - -.group-contributions-sorted-by-count:not(.visible), -.group-contributions-sorted-by-duration:not(.visible) { - display: none; -} - -/* Images */ - -.image-container { - border: 2px solid var(--primary-color); - box-sizing: border-box; - position: relative; - padding: 5px; - text-align: left; - background-color: var(--dim-color); - color: white; - display: inline-block; - height: 100%; -} - -.image-text-area { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - display: flex; - align-items: center; - justify-content: center; - text-align: center; - padding: 5px 15px; - background: rgba(0, 0, 0, 0.65); - box-shadow: 0 0 5px rgba(0, 0, 0, 0.5) inset; - line-height: 1.35em; - color: var(--primary-color); - font-style: oblique; - text-shadow: 0 2px 5px rgba(0, 0, 0, 0.75); -} - -.image-inner-area { - width: 100%; - height: 100%; -} - -img { - object-fit: cover; -} - -.reveal { - position: relative; - width: 100%; - height: 100%; -} - -.reveal img { - filter: blur(20px); - opacity: 0.4; -} - -.reveal-text-container { - position: absolute; - top: 15px; - left: 10px; - right: 10px; - bottom: 10px; - display: flex; - flex-direction: column; - justify-content: flex-start; -} - -.reveal-text { - color: white; - text-align: center; - font-weight: bold; -} - -.reveal-interaction { - opacity: 0.8; -} - -.reveal.revealed img { - filter: none; - opacity: 1; -} - -.reveal.revealed .reveal-text { - display: none; -} - -.sidebar .image-container { - max-width: 350px; -} - -/* Grid listings */ - -.grid-listing { - display: flex; - flex-wrap: wrap; - justify-content: center; - align-items: flex-start; - padding: 5px 15px; -} - -.grid-item { - font-size: 0.9em; -} - -.grid-item { - display: inline-block; - text-align: center; - background-color: #111111; - border: 1px dotted var(--primary-color); - border-radius: 2px; - padding: 5px; - margin: 10px; -} - -.grid-item img { - width: 100%; - height: 100% !important; - margin-top: auto; - margin-bottom: auto; -} - -.grid-item:hover { - text-decoration: none; -} - -.grid-actions .grid-item:hover { - text-decoration: underline; -} - -.grid-item > span { - display: block; - overflow-wrap: break-word; - hyphens: auto; -} - -.grid-item > span:not(:first-child) { - margin-top: 2px; -} - -.grid-item > span:first-of-type { - margin-top: 6px; -} - -.grid-item > span:not(:first-of-type) { - font-size: 0.9em; - opacity: 0.8; -} - -.grid-item:hover > span:first-of-type { - text-decoration: underline; -} - -.grid-listing > .grid-item { - flex: 1 25%; - max-width: 200px; -} - -.grid-actions { - display: flex; - flex-direction: row; - margin: 15px; - align-self: center; - flex-wrap: wrap; - justify-content: center; -} - -.grid-actions > .grid-item { - flex-basis: unset !important; - margin: 5px; - width: 120px; - --primary-color: inherit !important; - --dim-color: inherit !important; -} - -/* Carousel */ - -.carousel-container { - --carousel-tile-min-width: 120px; - --carousel-row-count: 3; - --carousel-column-count: 6; - - position: relative; - overflow: hidden; - margin: 20px 0 5px 0; - padding: 8px 0; -} - -.carousel-container::before { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - z-index: -20; - background-color: var(--dim-color); - filter: brightness(0.6); -} - -.carousel-container::after { - content: ""; - pointer-events: none; - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - border: 1px solid var(--primary-color); - border-radius: 4px; - z-index: 40; - box-shadow: - inset 20px 2px 40px var(--shadow-color), - inset -20px -2px 40px var(--shadow-color); -} - -.carousel-container:hover .carousel-grid { - animation-play-state: running; -} - -html[data-url-key="localized.home"] .carousel-container { - --carousel-tile-size: 140px; -} - -.carousel-container[data-carousel-rows="1"] { --carousel-row-count: 1; } -.carousel-container[data-carousel-rows="2"] { --carousel-row-count: 2; } -.carousel-container[data-carousel-rows="3"] { --carousel-row-count: 3; } -.carousel-container[data-carousel-columns="4"] { --carousel-column-count: 4; } -.carousel-container[data-carousel-columns="5"] { --carousel-column-count: 5; } -.carousel-container[data-carousel-columns="6"] { --carousel-column-count: 6; } - -.carousel-grid:nth-child(2), -.carousel-grid:nth-child(3) { - position: absolute; - top: 8px; - left: 0; - right: 0; -} - -.carousel-grid:nth-child(2) { - animation-name: carousel-marquee2; -} - -.carousel-grid:nth-child(3) { - animation-name: carousel-marquee3; -} - -@keyframes carousel-marquee1 { - 0% { - transform: translateX(-100%) translateX(70px); - } - - 100% { - transform: translateX(-200%) translateX(70px); - } -} - -@keyframes carousel-marquee2 { - 0% { - transform: translateX(0%) translateX(70px); - } - - 100% { - transform: translateX(-100%) translateX(70px); - } -} - -@keyframes carousel-marquee3 { - 0% { - transform: translateX(100%) translateX(70px); - } - - 100% { - transform: translateX(0%) translateX(70px); - } -} - -.carousel-grid { - /* Thanks to: https://css-tricks.com/an-auto-filling-css-grid-with-max-columns/ */ - --carousel-gap-count: calc(var(--carousel-column-count) - 1); - --carousel-total-gap-width: calc(var(--carousel-gap-count) * 10px); - --carousel-calculated-tile-max-width: calc((100% - var(--carousel-total-gap-width)) / var(--carousel-column-count)); - - display: grid; - grid-template-columns: repeat(auto-fill, minmax(max(var(--carousel-tile-min-width), var(--carousel-calculated-tile-max-width)), 1fr)); - grid-template-rows: repeat(var(--carousel-row-count), auto); - grid-auto-flow: dense; - grid-auto-rows: 0; - overflow: hidden; - margin: auto; - - transform: translateX(0); - animation: carousel-marquee1 40s linear infinite; - animation-play-state: paused; - z-index: 5; -} - -.carousel-item { - display: inline-block; - margin: 0; - flex: 1 1 150px; - padding: 3px; - border-radius: 10px; - filter: brightness(0.8); -} - -.carousel-item .image-container { - border: none; - padding: 0; -} - -.carousel-item img { - width: 100%; - height: 100%; - margin-top: auto; - margin-bottom: auto; - border-radius: 6px; -} - -.carousel-item:hover { - filter: brightness(1); - background: var(--dim-color); -} - -/* Squares */ - -.square { - position: relative; - width: 100%; -} - -.square::after { - content: ""; - display: block; - padding-bottom: 100%; -} - -.square-content { - position: absolute; - width: 100%; - height: 100%; -} - -/* Info card */ - -#info-card-container { - position: absolute; - - left: 0; - right: 10px; - - pointer-events: none; /* Padding area shouldn't 8e interactive. */ - display: none; -} - -#info-card-container.show, -#info-card-container.hide { - display: flex; -} - -#info-card-container > * { - flex-basis: 400px; -} - -#info-card-container.show { - animation: 0.2s linear forwards info-card-show; - transition: top 0.1s, left 0.1s; -} - -#info-card-container.hide { - animation: 0.2s linear forwards info-card-hide; -} - -@keyframes info-card-show { - 0% { - opacity: 0; - margin-top: -5px; - } - - 100% { - opacity: 1; - margin-top: 0; - } -} - -@keyframes info-card-hide { - 0% { - opacity: 1; - margin-top: 0; - } - - 100% { - opacity: 0; - margin-top: 5px; - display: none !important; - } -} - -.info-card-decor { - padding-left: 3ch; - border-top: 1px solid white; -} - -.info-card { - background-color: black; - color: white; - - border: 1px dotted var(--primary-color); - border-radius: 3px; - box-shadow: 0 5px 5px black; - - padding: 5px; - font-size: 0.9em; - - pointer-events: none; -} - -.info-card::after { - content: ""; - display: block; - clear: both; -} - -#info-card-container.show .info-card { - animation: 0.01s linear 0.2s forwards info-card-become-interactive; -} - -@keyframes info-card-become-interactive { - to { - pointer-events: auto; - } -} - -.info-card-art-container { - float: right; - - width: 40%; - margin: 5px; - font-size: 0.8em; - - /* Dynamically shown. */ - display: none; -} - -.info-card-art-container .image-container { - padding: 2px; -} - -.info-card-art { - display: block; - width: 100%; - height: 100%; -} - -.info-card-name { - font-size: 1em; - border-bottom: 1px dotted; - margin: 0; -} - -.info-card p { - margin-top: 0.25em; - margin-bottom: 0.25em; -} - -.info-card p:last-child { - margin-bottom: 0; -} - -/* Custom hash links */ - -.content-heading { - border-bottom: 3px double transparent; - margin-bottom: -3px; -} - -.content-heading.highlight-hash-link { - animation: highlight-hash-link 4s; - animation-delay: 125ms; -} - -h3.content-heading { - clear: both; -} - -/* This animation's name is referenced in JavaScript */ -@keyframes highlight-hash-link { - 0% { - border-bottom-color: transparent; - } - - 10% { - border-bottom-color: white; - } - - 25% { - border-bottom-color: white; - } - - 100% { - border-bottom-color: transparent; - } -} - -/* Sticky heading */ - -#content [id] { - /* Adjust scroll margin. */ - scroll-margin-top: calc( - 74px /* Sticky heading */ - + 33px /* Sticky subheading */ - - 1em /* One line of text (align bottom) */ - - 12px /* Padding for hanging letters & focus ring */ - ); -} - -.content-sticky-heading-container { - position: sticky; - top: 0; - - margin: calc(-1 * var(--content-padding)); - margin-bottom: calc(0.5 * var(--content-padding)); - - transform: translateY(-5px); -} - -main.long-content .content-sticky-heading-container { - padding-left: 0; - padding-right: 0; -} - -main.long-content .content-sticky-heading-container .content-sticky-heading-row, -main.long-content .content-sticky-heading-container .content-sticky-subheading-row { - padding-left: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); - padding-right: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); -} - -.content-sticky-heading-row { - box-sizing: border-box; - padding: - calc(1.25 * var(--content-padding) + 5px) - 20px - calc(0.75 * var(--content-padding)) - 20px; - - width: 100%; - margin: 0; - - background: var(--bg-black-color); - border-bottom: 1px dotted rgba(220, 220, 220, 0.4); - - -webkit-backdrop-filter: blur(6px); - backdrop-filter: blur(6px); -} - -.content-sticky-heading-container.has-cover .content-sticky-heading-row, -.content-sticky-heading-container.has-cover .content-sticky-subheading-row { - display: grid; - grid-template-areas: - "title cover"; - grid-template-columns: 1fr min(40%, 400px); -} - -.content-sticky-heading-row h1 { - margin: 0; - padding-right: 10px; -} - -.content-sticky-heading-cover-container { - position: relative; - height: 0; - margin: -15px 0px -5px -5px; -} - -.content-sticky-heading-cover-needs-reveal { - display: none; -} - -.content-sticky-heading-cover { - position: absolute; - top: 0; - width: 80px; - right: 10px; - box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.25); - transition: transform 0.35s, opacity 0.25s; -} - -.content-sticky-heading-cover-container:not(.visible) .content-sticky-heading-cover { - opacity: 0; - transform: translateY(15px); -} - -.content-sticky-heading-cover .image-container { - border-width: 1px; - padding: 2px; -} - -.content-sticky-heading-cover img { - display: block; - width: 100%; - height: 100%; -} - -.content-sticky-subheading-row { - position: absolute; - width: 100%; - box-sizing: border-box; - padding: 10px 40px 5px 20px; - margin-top: 0; - z-index: -1; - - background: var(--bg-black-color); - border-bottom: 1px dotted rgba(220, 220, 220, 0.4); - - -webkit-backdrop-filter: blur(3px); - backdrop-filter: blur(3px); - - transition: margin-top 0.35s, opacity 0.25s; -} - -.content-sticky-subheading-row h2 { - margin: 0; - - font-size: 0.9em !important; - font-weight: normal; - font-style: oblique; - color: #eee; -} - -.content-sticky-subheading-row:not(.visible) { - margin-top: -20px; - opacity: 0; -} - -.content-sticky-heading-container h2.visible { - margin-top: 0; - opacity: 1; -} - -.content-sticky-heading-row { - box-shadow: - inset 0 10px 10px -5px var(--shadow-color), - 0 4px 4px rgba(0, 0, 0, 0.8); -} - -.content-sticky-heading-container h2.visible { - box-shadow: - inset 0 10px 10px -5px var(--shadow-color), - 0 4px 4px rgba(0, 0, 0, 0.8); -} - -#content, .sidebar { - contain: paint; -} - -/* Sticky sidebar */ - -.sidebar-column.sidebar.sticky-column, -.sidebar-column.sidebar.sticky-last, -.sidebar-multiple.sticky-last > .sidebar:last-child, -.sidebar-multiple.sticky-column { - position: sticky; - top: 10px; -} - -.sidebar-multiple.sticky-last { - align-self: stretch; -} - -.sidebar-multiple.sticky-column { - align-self: flex-start; -} - -.sidebar-column.sidebar.sticky-column { - max-height: calc(100vh - 20px); - align-self: start; - padding-bottom: 0; - box-sizing: border-box; - flex-basis: 275px; - padding-top: 0; - overflow-y: scroll; - scrollbar-width: thin; - scrollbar-color: var(--dark-color); -} - -.sidebar-column.sidebar.sticky-column::-webkit-scrollbar { - background: var(--dark-color); - width: 12px; -} - -.sidebar-column.sidebar.sticky-column::-webkit-scrollbar-thumb { - transition: background 0.2s; - background: rgba(255, 255, 255, 0.2); - border: 3px solid transparent; - border-radius: 10px; - background-clip: content-box; -} - -.sidebar-column.sidebar.sticky-column > h1 { - position: sticky; - top: 0; - margin: 0 calc(-1 * var(--content-padding)); - margin-bottom: 10px; - - border-bottom: 1px dotted rgba(220, 220, 220, 0.4); - padding: 10px 5px; - - background: var(--bg-black-color); - -webkit-backdrop-filter: blur(3px); - backdrop-filter: blur(3px); -} - -/* Image overlay */ - -#image-overlay-container { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 20px 40px; - box-sizing: border-box; - - opacity: 0; - pointer-events: none; - transition: opacity 0.4s; - - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; -} - -#image-overlay-container.visible { - opacity: 1; - pointer-events: auto; -} - -#image-overlay-content-container { - border-radius: 0 0 8px 8px; - border: 2px solid var(--primary-color); - background: var(--dim-ghost-color); - padding: 3px; - overflow: hidden; - - -webkit-backdrop-filter: blur(3px); - backdrop-filter: blur(3px); -} - -#image-overlay-image-container { - display: block; - position: relative; - overflow: hidden; - width: 80vmin; - height: 80vmin; -} - -#image-overlay-image, -#image-overlay-image-thumb { - display: inline-block; - object-fit: contain; - width: 100%; - height: 100%; - background: rgba(0, 0, 0, 0.65); -} - -#image-overlay-image { - position: absolute; - top: 0; - left: 0; -} - -#image-overlay-image-thumb { - filter: blur(16px); - transform: scale(1.5); -} - -#image-overlay-container.loaded #image-overlay-image-thumb { - opacity: 0; - pointer-events: none; - transition: opacity 0.25s; -} - -#image-overlay-image-container::after { - content: ""; - display: block; - position: absolute; - bottom: 0; - left: 0; - height: 4px; - width: var(--download-progress); - background: var(--primary-color); - box-shadow: 0 -3px 12px 4px var(--primary-color); - transition: 0.25s; -} - -#image-overlay-container.loaded #image-overlay-image-container::after { - width: 100%; - background: white; - opacity: 0; -} - -#image-overlay-container.errored #image-overlay-image-container::after { - width: 100%; - background: red; -} - -#image-overlay-container:not(.visible) #image-overlay-image-container::after { - width: 0 !important; -} - -#image-overlay-action-container { - padding: 4px 4px 6px 4px; - border-radius: 0 0 5px 5px; - background: var(--bg-black-color); - color: white; - font-style: oblique; - text-align: center; -} - -#image-overlay-container #image-overlay-action-content-without-size:not(.visible), -#image-overlay-container #image-overlay-action-content-with-size:not(.visible), -#image-overlay-container #image-overlay-file-size-warning:not(.visible), -#image-overlay-container #image-overlay-file-size-kilobytes:not(.visible), -#image-overlay-container #image-overlay-file-size-megabytes:not(.visible) { - display: none; -} - -#image-overlay-file-size-warning { - opacity: 0.8; - font-size: 0.9em; -} - -/* important easter egg mode */ - -html[data-language-code="preview-en"][data-url-key="localized.home"] #content - h1::after { - font-family: cursive; - display: block; - content: "(Preview Build)"; - font-size: 0.8em; -} - -/* Layout - Wide (most computers) */ - -@media (min-width: 900px) { - #secondary-nav:not(.no-hide) { - display: none; - } -} - -/* Layout - Medium (tablets, some landscape mobiles) - * - * Note: Rules defined here are exclusive to "medium" width, i.e. they don't - * additionally apply to "thin". Use the later section which applies to both - * if so desired. - */ - -@media (min-width: 600px) and (max-width: 899.98px) { -} - -/* Layout - Wide or Medium */ - -@media (min-width: 600px) { - .content-sticky-heading-container { - /* Safari doesn't always play nicely with position: sticky, - * this seems to fix images sometimes displaying above the - * position: absolute subheading (h2) child - * - * See also: https://stackoverflow.com/questions/50224855/ - */ - transform: translate3d(0, 0, 0); - z-index: 1; - } - - /* Cover art floats to the right. It's positioned in HTML beneath the - * heading, so pull it up a little to "float" on top. - */ - #cover-art-container { - float: right; - width: 40%; - max-width: 400px; - margin: -60px 0 10px 10px; - - position: relative; - z-index: 2; - } - - html[data-url-key="localized.home"] .layout-columns.has-one-sidebar .grid-listing > .grid-item:not(:nth-child(n+10)) { - flex-basis: 23%; - margin: 15px; - } - - html[data-url-key="localized.home"] .layout-columns.has-one-sidebar .grid-listing > .grid-item:nth-child(n+10) { - flex-basis: 18%; - margin: 10px; - } -} - -/* Layout - Medium or Thin */ - -@media (max-width: 899.98px) { - .sidebar-column:not(.no-hide) { - display: none; - } - - #secondary-nav { - display: block; - } - - .layout-columns.vertical-when-thin { - flex-direction: column; - } - - .layout-columns.vertical-when-thin > *:not(:last-child) { - margin-bottom: 10px; - } - - .sidebar-column.no-hide { - max-width: unset !important; - flex-basis: unset !important; - margin-right: 0 !important; - margin-left: 0 !important; - width: 100%; - } - - .sidebar .news-entry:not(.first-news-entry) { - display: none; - } - - .grid-listing > .grid-item { - flex-basis: 40%; - } -} - -/* Layout - Thin (phones) */ - -@media (max-width: 600px) { - .content-columns { - columns: 1; - } - - #cover-art-container { - margin: 25px 0 5px 0; - width: 100%; - max-width: unset; - } - - /* Show sticky heading above cover art */ - - .content-sticky-heading-container { - z-index: 2; - } - - /* Disable grid features, just line header children up vertically */ - - #header { - display: block; - } - - #header > div:not(:first-child) { - margin-top: 0.5em; - } -} diff --git a/src/static/site5.css b/src/static/site5.css new file mode 100644 index 00000000..7b3e3e03 --- /dev/null +++ b/src/static/site5.css @@ -0,0 +1,1745 @@ +/* A frontend file! Wow. + * This file is just loaded statically 8y s in the HTML files, so there's + * no need to re-run upd8.js when tweaking values here. Handy! + */ + +:root { + --primary-color: #0088ff; +} + +/* Layout - Common + * + */ + +body { + margin: 10px; + overflow-y: scroll; +} + +body::before { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + + /* NB: these are 100 LVW, "largest view width", etc. + * Stabilizes background on viewports with modal dimensions, + * e.g. expanding/shrinking tab bar or collapsible find bar. + * 100% dimensions are kept above for browser compatibility. + */ + width: 100lvw; + height: 100lvh; +} + +#page-container { + max-width: 1100px; + margin: 10px auto 50px; + padding: 15px 0; +} + +#page-container > * { + margin-left: 15px; + margin-right: 15px; +} + +#skippers:focus-within { + position: static; + width: unset; + height: unset; +} + +#banner { + margin: 10px 0; + width: 100%; + position: relative; +} + +#banner::after { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +#banner img { + display: block; + width: 100%; + height: auto; +} + +#skippers { + position: absolute; + left: -10000px; + top: auto; + width: 1px; + height: 1px; +} + +.layout-columns { + display: flex; + align-items: stretch; +} + +#header, +#secondary-nav, +#skippers, +#footer { + padding: 5px; +} + +#header, +#secondary-nav, +#skippers { + margin-bottom: 10px; +} + +#footer { + margin-top: 10px; +} + +#header { + display: grid; +} + +#header.nav-has-main-links.nav-has-content { + grid-template-columns: 2.5fr 3fr; + grid-template-rows: min-content 1fr; + grid-template-areas: + "main-links content" + "bottom-row content"; +} + +#header.nav-has-main-links:not(.nav-has-content) { + grid-template-columns: 1fr; + grid-template-areas: + "main-links" + "bottom-row"; +} + +.nav-main-links { + grid-area: main-links; + margin-right: 20px; +} + +.nav-content { + grid-area: content; +} + +.nav-bottom-row { + grid-area: bottom-row; + align-self: start; +} + +.sidebar-column { + flex: 1 1 20%; + min-width: 150px; + max-width: 250px; + flex-basis: 250px; + align-self: flex-start; +} + +.sidebar-column.wide { + max-width: 350px; + flex-basis: 300px; + flex-shrink: 0; + flex-grow: 1; +} + +.sidebar-multiple { + display: flex; + flex-direction: column; +} + +.sidebar-multiple .sidebar:not(:first-child) { + margin-top: 15px; +} + +.sidebar { + --content-padding: 5px; + padding: var(--content-padding); +} + +#sidebar-left { + margin-right: 10px; +} + +#sidebar-right { + margin-left: 10px; +} + +#content { + position: relative; + --content-padding: 20px; + box-sizing: border-box; + padding: var(--content-padding); + flex-grow: 1; + flex-shrink: 3; +} + +.footer-content { + margin: 5px 12%; +} + +.footer-content > :first-child { + margin-top: 0; +} + +.footer-content > :last-child { + margin-bottom: 0; +} + +.footer-localization-links { + margin: 5px 12%; +} + +/* Design & Appearance - Layout elements */ + +body { + background: black; +} + +body::before { + background-image: url("../media/bg.jpg"); + background-position: center; + background-size: cover; + opacity: 0.5; +} + +#page-container { + background-color: var(--bg-color, rgba(35, 35, 35, 0.8)); + color: #ffffff; + box-shadow: 0 0 40px rgba(0, 0, 0, 0.5); +} + +#skippers > * { + display: inline-block; +} + +#skippers > .skipper-list:not(:last-child)::after { + display: inline-block; + content: "\00a0"; + margin-left: 2px; + margin-right: -2px; + border-left: 1px dotted; +} + +#skippers .skipper-list > .skipper:not(:last-child)::after { + content: " \00b7 "; + font-weight: 800; +} + +#banner { + background: black; + background-color: var(--dim-color); + border-bottom: 1px solid var(--primary-color); +} + +#banner::after { + box-shadow: inset 0 -2px 3px rgba(0, 0, 0, 0.35); + pointer-events: none; +} + +#banner.dim img { + opacity: 0.8; +} + +#header, +#secondary-nav, +#skippers, +#footer, +.sidebar { + font-size: 0.85em; +} + +.sidebar, +#content, +#header, +#secondary-nav, +#skippers, +#footer { + background-color: rgba(0, 0, 0, 0.6); + border: 1px dotted var(--primary-color); + border-radius: 3px; + transition: background-color 0.2s; +} + +/* +.sidebar:focus-within, +#content:focus-within, +#header:focus-within, +#secondary-nav:focus-within, +#skippers:focus-within, +#footer:focus-within { + background-color: rgba(0, 0, 0, 0.85); + border-style: solid; +} +*/ + +.sidebar > h1, +.sidebar > h2, +.sidebar > h3, +.sidebar > p { + text-align: center; +} + +.sidebar h1 { + font-size: 1.25em; +} + +.sidebar h2 { + font-size: 1.1em; + margin: 0; +} + +.sidebar h3 { + font-size: 1.1em; + font-style: oblique; + font-variant: small-caps; + margin-top: 0.3em; + margin-bottom: 0em; +} + +.sidebar > p { + margin: 0.5em 0; + padding: 0 5px; +} + +.sidebar hr { + color: #555; + margin: 10px 5px; +} + +.sidebar > ol, +.sidebar > ul { + padding-left: 30px; + padding-right: 15px; +} + +.sidebar > dl { + padding-right: 15px; + padding-left: 0; +} + +.sidebar > dl dt { + padding-left: 10px; + margin-top: 0.5em; +} + +.sidebar > dl dt.current { + font-weight: 800; +} + +.sidebar > dl dd { + margin-left: 0; +} + +.sidebar > dl dd ul { + padding-left: 30px; + margin-left: 0; +} + +.sidebar > dl .side { + padding-left: 10px; +} + +.sidebar li.current { + font-weight: 800; +} + +.sidebar li { + overflow-wrap: break-word; +} + +.sidebar > details.current summary { + font-weight: 800; +} + +.sidebar > details summary { + margin-top: 0.5em; + padding-left: 5px; +} + +summary > span:hover { + cursor: pointer; + text-decoration: underline; + text-decoration-color: var(--primary-color); +} + +summary .group-name { + color: var(--primary-color); +} + +.sidebar > details ul, +.sidebar > details ol { + margin-top: 0; + margin-bottom: 0; +} + +.sidebar > details:last-child { + margin-bottom: 10px; +} + +.sidebar > details[open] { + margin-bottom: 1em; +} + +.sidebar article { + text-align: left; + margin: 5px 5px 15px 5px; +} + +.sidebar article:last-child { + margin-bottom: 5px; +} + +.sidebar article h2, +.news-index h2 { + border-bottom: 1px dotted; +} + +.sidebar article h2 time, +.news-index time { + float: right; + font-weight: normal; +} + +#content { + overflow-wrap: anywhere; +} + +footer { + text-align: center; + font-style: oblique; +} + +.footer-localization-links > span:not(:last-child)::after { + content: " \00b7 "; + font-weight: 800; +} + +/* Design & Appearance - Content elements */ + +a { + color: var(--primary-color); + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +a.current { + font-weight: 800; +} + +.nav-main-links > span > span { + white-space: nowrap; +} + +.nav-main-links > span.current > span.nav-link-content > a { + font-weight: 800; +} + +.nav-links-index > span:not(:first-child):not(.no-divider)::before, +.nav-links-groups > span:not(:first-child):not(.no-divider)::before { + content: "\0020\00b7\0020"; + font-weight: 800; +} + +.nav-links-hierarchical > span:not(:first-child):not(.no-divider)::before { + content: "\0020/\0020"; +} + +#header .chronology .heading, +#header .chronology .buttons { + white-space: nowrap; +} + +#secondary-nav { + text-align: center; +} + +.nowrap { + white-space: nowrap; +} + +.icons { + font-style: normal; + white-space: nowrap; +} + +.icon { + display: inline-block; + width: 24px; + height: 1em; + position: relative; +} + +.icon > svg { + width: 24px; + height: 24px; + top: -0.25em; + position: absolute; + fill: var(--primary-color); +} + +.rerelease, +.other-group-accent { + opacity: 0.7; + font-style: oblique; +} + +.other-group-accent { + white-space: nowrap; +} + +.content-columns { + columns: 2; +} + +.content-columns .column { + break-inside: avoid; +} + +.content-columns .column h2 { + margin-top: 0; + font-size: 1em; +} + +p .current { + font-weight: 800; +} + +#cover-art-container { + font-size: 0.8em; +} + +#cover-art .square { + box-shadow: 0 0 3px 6px rgba(0, 0, 0, 0.35); +} + +#cover-art img { + display: block; + width: 100%; + height: 100%; +} + +#cover-art-container p { + margin-top: 5px; +} + +.commentary-art { + float: right; + width: 30%; + max-width: 250px; + margin: 15px 0 10px 20px; +} + +.js-hide, +.js-show-once-data, +.js-hide-once-data { + display: none; +} + +.content-image { + margin-top: 1em; + margin-bottom: 1em; +} + +a.box:focus { + outline: 3px double var(--primary-color); +} + +a.box:focus:not(:focus-visible) { + outline: none; +} + +a.box img { + display: block; + max-width: 100%; + height: auto; +} + +.square .image-container { + width: 100%; + height: 100%; +} + +h1 { + font-size: 1.5em; +} + +#content li { + margin-bottom: 4px; +} + +#content li i { + white-space: nowrap; +} + +#content.top-index h1, +#content.flash-index h1 { + text-align: center; + font-size: 2em; +} + +html[data-url-key="localized.home"] #content h1 { + text-align: center; + font-size: 2.5em; +} + +#content.flash-index h2 { + text-align: center; + font-size: 2.5em; + font-variant: small-caps; + font-style: oblique; + margin-bottom: 0; + text-align: center; + width: 100%; +} + +#content.top-index h2 { + text-align: center; + font-size: 2em; + font-weight: normal; + margin-bottom: 0.25em; +} + +.quick-info { + text-align: center; +} + +ul.quick-info { + list-style: none; + padding-left: 0; +} + +ul.quick-info li { + display: inline-block; +} + +ul.quick-info li:not(:last-child)::after { + content: " \00b7 "; + font-weight: 800; +} + +.carousel-container + .quick-info { + margin-top: 25px; +} + +#intro-menu { + margin: 24px 0; + padding: 10px; + background-color: #222222; + text-align: center; + border: 1px dotted var(--primary-color); + border-radius: 2px; +} + +#intro-menu p { + margin: 12px 0; +} + +#intro-menu a { + margin: 0 6px; +} + +li .by { + display: inline-block; + font-style: oblique; +} + +li .by a { + display: inline-block; +} + +p code { + font-size: 1em; + font-family: "courier new"; + font-weight: 800; +} + +#content blockquote { + margin-left: 40px; + max-width: 600px; + margin-right: 0; +} + +#content blockquote blockquote { + margin-left: 10px; + padding-left: 10px; + margin-right: 20px; + border-left: dotted 1px; + padding-top: 6px; + padding-bottom: 6px; +} + +#content blockquote blockquote > :first-child { + margin-top: 0; +} + +#content blockquote blockquote > :last-child { + margin-bottom: 0; +} + +main.long-content .main-content-container, +main.long-content > h1 { + padding-left: 12%; + padding-right: 12%; +} + +dl dt { + padding-left: 40px; + max-width: 600px; +} + +dl dt { + margin-bottom: 2px; +} + +dl dd { + margin-bottom: 1em; +} + +dl ul, +dl ol { + margin-top: 0; + margin-bottom: 0; +} + +ul > li.has-details { + list-style-type: none; + margin-left: -17px; +} + +.album-group-list dt { + font-style: oblique; + padding-left: 0; +} + +.album-group-list dd { + margin-left: 0; +} + +.group-chronology-link { + font-style: oblique; +} + +#content hr { + border: 1px inset #808080; + width: 100%; +} + +#content hr.split::before { + content: "(split)"; + color: #808080; +} + +#content hr.split { + position: relative; + overflow: hidden; + border: none; +} + +#content hr.split::after { + display: inline-block; + content: ""; + border: 1px inset #808080; + width: 100%; + position: absolute; + top: 50%; + margin-top: -2px; + margin-left: 10px; +} + +li > ul { + margin-top: 5px; +} + +.group-contributions-table { + display: inline-block; +} + +.group-contributions-table .group-contributions-row { + display: flex; + justify-content: space-between; +} + +.group-contributions-table .group-contributions-metrics { + margin-left: 1.5ch; + white-space: nowrap; +} + +.group-contributions-sorted-by-count:not(.visible), +.group-contributions-sorted-by-duration:not(.visible) { + display: none; +} + +/* Images */ + +.image-container { + border: 2px solid var(--primary-color); + box-sizing: border-box; + position: relative; + padding: 5px; + text-align: left; + background-color: var(--dim-color); + color: white; + display: inline-block; + height: 100%; +} + +.image-text-area { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 5px 15px; + background: rgba(0, 0, 0, 0.65); + box-shadow: 0 0 5px rgba(0, 0, 0, 0.5) inset; + line-height: 1.35em; + color: var(--primary-color); + font-style: oblique; + text-shadow: 0 2px 5px rgba(0, 0, 0, 0.75); +} + +.image-inner-area { + width: 100%; + height: 100%; +} + +img { + object-fit: cover; +} + +.reveal { + position: relative; + width: 100%; + height: 100%; +} + +.reveal img { + filter: blur(20px); + opacity: 0.4; +} + +.reveal-text-container { + position: absolute; + top: 15px; + left: 10px; + right: 10px; + bottom: 10px; + display: flex; + flex-direction: column; + justify-content: flex-start; +} + +.reveal-text { + color: white; + text-align: center; + font-weight: bold; +} + +.reveal-interaction { + opacity: 0.8; +} + +.reveal.revealed img { + filter: none; + opacity: 1; +} + +.reveal.revealed .reveal-text { + display: none; +} + +.sidebar .image-container { + max-width: 350px; +} + +/* Grid listings */ + +.grid-listing { + display: flex; + flex-wrap: wrap; + justify-content: center; + align-items: flex-start; + padding: 5px 15px; +} + +.grid-item { + font-size: 0.9em; +} + +.grid-item { + display: inline-block; + text-align: center; + background-color: #111111; + border: 1px dotted var(--primary-color); + border-radius: 2px; + padding: 5px; + margin: 10px; +} + +.grid-item img { + width: 100%; + height: 100% !important; + margin-top: auto; + margin-bottom: auto; +} + +.grid-item:hover { + text-decoration: none; +} + +.grid-actions .grid-item:hover { + text-decoration: underline; +} + +.grid-item > span { + display: block; + overflow-wrap: break-word; + hyphens: auto; +} + +.grid-item > span:not(:first-child) { + margin-top: 2px; +} + +.grid-item > span:first-of-type { + margin-top: 6px; +} + +.grid-item > span:not(:first-of-type) { + font-size: 0.9em; + opacity: 0.8; +} + +.grid-item:hover > span:first-of-type { + text-decoration: underline; +} + +.grid-listing > .grid-item { + flex: 1 25%; + max-width: 200px; +} + +.grid-actions { + display: flex; + flex-direction: row; + margin: 15px; + align-self: center; + flex-wrap: wrap; + justify-content: center; +} + +.grid-actions > .grid-item { + flex-basis: unset !important; + margin: 5px; + width: 120px; + --primary-color: inherit !important; + --dim-color: inherit !important; +} + +/* Carousel */ + +.carousel-container { + --carousel-tile-min-width: 120px; + --carousel-row-count: 3; + --carousel-column-count: 6; + + position: relative; + overflow: hidden; + margin: 20px 0 5px 0; + padding: 8px 0; +} + +.carousel-container::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: -20; + background-color: var(--dim-color); + filter: brightness(0.6); +} + +.carousel-container::after { + content: ""; + pointer-events: none; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border: 1px solid var(--primary-color); + border-radius: 4px; + z-index: 40; + box-shadow: + inset 20px 2px 40px var(--shadow-color), + inset -20px -2px 40px var(--shadow-color); +} + +.carousel-container:hover .carousel-grid { + animation-play-state: running; +} + +html[data-url-key="localized.home"] .carousel-container { + --carousel-tile-size: 140px; +} + +.carousel-container[data-carousel-rows="1"] { --carousel-row-count: 1; } +.carousel-container[data-carousel-rows="2"] { --carousel-row-count: 2; } +.carousel-container[data-carousel-rows="3"] { --carousel-row-count: 3; } +.carousel-container[data-carousel-columns="4"] { --carousel-column-count: 4; } +.carousel-container[data-carousel-columns="5"] { --carousel-column-count: 5; } +.carousel-container[data-carousel-columns="6"] { --carousel-column-count: 6; } + +.carousel-grid:nth-child(2), +.carousel-grid:nth-child(3) { + position: absolute; + top: 8px; + left: 0; + right: 0; +} + +.carousel-grid:nth-child(2) { + animation-name: carousel-marquee2; +} + +.carousel-grid:nth-child(3) { + animation-name: carousel-marquee3; +} + +@keyframes carousel-marquee1 { + 0% { + transform: translateX(-100%) translateX(70px); + } + + 100% { + transform: translateX(-200%) translateX(70px); + } +} + +@keyframes carousel-marquee2 { + 0% { + transform: translateX(0%) translateX(70px); + } + + 100% { + transform: translateX(-100%) translateX(70px); + } +} + +@keyframes carousel-marquee3 { + 0% { + transform: translateX(100%) translateX(70px); + } + + 100% { + transform: translateX(0%) translateX(70px); + } +} + +.carousel-grid { + /* Thanks to: https://css-tricks.com/an-auto-filling-css-grid-with-max-columns/ */ + --carousel-gap-count: calc(var(--carousel-column-count) - 1); + --carousel-total-gap-width: calc(var(--carousel-gap-count) * 10px); + --carousel-calculated-tile-max-width: calc((100% - var(--carousel-total-gap-width)) / var(--carousel-column-count)); + + display: grid; + grid-template-columns: repeat(auto-fill, minmax(max(var(--carousel-tile-min-width), var(--carousel-calculated-tile-max-width)), 1fr)); + grid-template-rows: repeat(var(--carousel-row-count), auto); + grid-auto-flow: dense; + grid-auto-rows: 0; + overflow: hidden; + margin: auto; + + transform: translateX(0); + animation: carousel-marquee1 40s linear infinite; + animation-play-state: paused; + z-index: 5; +} + +.carousel-item { + display: inline-block; + margin: 0; + flex: 1 1 150px; + padding: 3px; + border-radius: 10px; + filter: brightness(0.8); +} + +.carousel-item .image-container { + border: none; + padding: 0; +} + +.carousel-item img { + width: 100%; + height: 100%; + margin-top: auto; + margin-bottom: auto; + border-radius: 6px; +} + +.carousel-item:hover { + filter: brightness(1); + background: var(--dim-color); +} + +/* Squares */ + +.square { + position: relative; + width: 100%; +} + +.square::after { + content: ""; + display: block; + padding-bottom: 100%; +} + +.square-content { + position: absolute; + width: 100%; + height: 100%; +} + +/* Info card */ + +#info-card-container { + position: absolute; + + left: 0; + right: 10px; + + pointer-events: none; /* Padding area shouldn't 8e interactive. */ + display: none; +} + +#info-card-container.show, +#info-card-container.hide { + display: flex; +} + +#info-card-container > * { + flex-basis: 400px; +} + +#info-card-container.show { + animation: 0.2s linear forwards info-card-show; + transition: top 0.1s, left 0.1s; +} + +#info-card-container.hide { + animation: 0.2s linear forwards info-card-hide; +} + +@keyframes info-card-show { + 0% { + opacity: 0; + margin-top: -5px; + } + + 100% { + opacity: 1; + margin-top: 0; + } +} + +@keyframes info-card-hide { + 0% { + opacity: 1; + margin-top: 0; + } + + 100% { + opacity: 0; + margin-top: 5px; + display: none !important; + } +} + +.info-card-decor { + padding-left: 3ch; + border-top: 1px solid white; +} + +.info-card { + background-color: black; + color: white; + + border: 1px dotted var(--primary-color); + border-radius: 3px; + box-shadow: 0 5px 5px black; + + padding: 5px; + font-size: 0.9em; + + pointer-events: none; +} + +.info-card::after { + content: ""; + display: block; + clear: both; +} + +#info-card-container.show .info-card { + animation: 0.01s linear 0.2s forwards info-card-become-interactive; +} + +@keyframes info-card-become-interactive { + to { + pointer-events: auto; + } +} + +.info-card-art-container { + float: right; + + width: 40%; + margin: 5px; + font-size: 0.8em; + + /* Dynamically shown. */ + display: none; +} + +.info-card-art-container .image-container { + padding: 2px; +} + +.info-card-art { + display: block; + width: 100%; + height: 100%; +} + +.info-card-name { + font-size: 1em; + border-bottom: 1px dotted; + margin: 0; +} + +.info-card p { + margin-top: 0.25em; + margin-bottom: 0.25em; +} + +.info-card p:last-child { + margin-bottom: 0; +} + +/* Custom hash links */ + +.content-heading { + border-bottom: 3px double transparent; + margin-bottom: -3px; +} + +.content-heading.highlight-hash-link { + animation: highlight-hash-link 4s; + animation-delay: 125ms; +} + +h3.content-heading { + clear: both; +} + +/* This animation's name is referenced in JavaScript */ +@keyframes highlight-hash-link { + 0% { + border-bottom-color: transparent; + } + + 10% { + border-bottom-color: white; + } + + 25% { + border-bottom-color: white; + } + + 100% { + border-bottom-color: transparent; + } +} + +/* Sticky heading */ + +#content [id] { + /* Adjust scroll margin. */ + scroll-margin-top: calc( + 74px /* Sticky heading */ + + 33px /* Sticky subheading */ + - 1em /* One line of text (align bottom) */ + - 12px /* Padding for hanging letters & focus ring */ + ); +} + +.content-sticky-heading-container { + position: sticky; + top: 0; + + margin: calc(-1 * var(--content-padding)); + margin-bottom: calc(0.5 * var(--content-padding)); + + transform: translateY(-5px); +} + +main.long-content .content-sticky-heading-container { + padding-left: 0; + padding-right: 0; +} + +main.long-content .content-sticky-heading-container .content-sticky-heading-row, +main.long-content .content-sticky-heading-container .content-sticky-subheading-row { + padding-left: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); + padding-right: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); +} + +.content-sticky-heading-row { + box-sizing: border-box; + padding: + calc(1.25 * var(--content-padding) + 5px) + 20px + calc(0.75 * var(--content-padding)) + 20px; + + width: 100%; + margin: 0; + + background: var(--bg-black-color); + border-bottom: 1px dotted rgba(220, 220, 220, 0.4); + + -webkit-backdrop-filter: blur(6px); + backdrop-filter: blur(6px); +} + +.content-sticky-heading-container.has-cover .content-sticky-heading-row, +.content-sticky-heading-container.has-cover .content-sticky-subheading-row { + display: grid; + grid-template-areas: + "title cover"; + grid-template-columns: 1fr min(40%, 400px); +} + +.content-sticky-heading-row h1 { + margin: 0; + padding-right: 10px; +} + +.content-sticky-heading-cover-container { + position: relative; + height: 0; + margin: -15px 0px -5px -5px; +} + +.content-sticky-heading-cover-needs-reveal { + display: none; +} + +.content-sticky-heading-cover { + position: absolute; + top: 0; + width: 80px; + right: 10px; + box-shadow: 0 0 2px 2px rgba(0, 0, 0, 0.25); + transition: transform 0.35s, opacity 0.25s; +} + +.content-sticky-heading-cover-container:not(.visible) .content-sticky-heading-cover { + opacity: 0; + transform: translateY(15px); +} + +.content-sticky-heading-cover .image-container { + border-width: 1px; + padding: 2px; +} + +.content-sticky-heading-cover img { + display: block; + width: 100%; + height: 100%; +} + +.content-sticky-subheading-row { + position: absolute; + width: 100%; + box-sizing: border-box; + padding: 10px 40px 5px 20px; + margin-top: 0; + z-index: -1; + + background: var(--bg-black-color); + border-bottom: 1px dotted rgba(220, 220, 220, 0.4); + + -webkit-backdrop-filter: blur(3px); + backdrop-filter: blur(3px); + + transition: margin-top 0.35s, opacity 0.25s; +} + +.content-sticky-subheading-row h2 { + margin: 0; + + font-size: 0.9em !important; + font-weight: normal; + font-style: oblique; + color: #eee; +} + +.content-sticky-subheading-row:not(.visible) { + margin-top: -20px; + opacity: 0; +} + +.content-sticky-heading-container h2.visible { + margin-top: 0; + opacity: 1; +} + +.content-sticky-heading-row { + box-shadow: + inset 0 10px 10px -5px var(--shadow-color), + 0 4px 4px rgba(0, 0, 0, 0.8); +} + +.content-sticky-heading-container h2.visible { + box-shadow: + inset 0 10px 10px -5px var(--shadow-color), + 0 4px 4px rgba(0, 0, 0, 0.8); +} + +#content, .sidebar { + contain: paint; +} + +/* Sticky sidebar */ + +.sidebar-column.sidebar.sticky-column, +.sidebar-column.sidebar.sticky-last, +.sidebar-multiple.sticky-last > .sidebar:last-child, +.sidebar-multiple.sticky-column { + position: sticky; + top: 10px; +} + +.sidebar-multiple.sticky-last { + align-self: stretch; +} + +.sidebar-multiple.sticky-column { + align-self: flex-start; +} + +.sidebar-column.sidebar.sticky-column { + max-height: calc(100vh - 20px); + align-self: start; + padding-bottom: 0; + box-sizing: border-box; + flex-basis: 275px; + padding-top: 0; + overflow-y: scroll; + scrollbar-width: thin; + scrollbar-color: var(--dark-color); +} + +.sidebar-column.sidebar.sticky-column::-webkit-scrollbar { + background: var(--dark-color); + width: 12px; +} + +.sidebar-column.sidebar.sticky-column::-webkit-scrollbar-thumb { + transition: background 0.2s; + background: rgba(255, 255, 255, 0.2); + border: 3px solid transparent; + border-radius: 10px; + background-clip: content-box; +} + +.sidebar-column.sidebar.sticky-column > h1 { + position: sticky; + top: 0; + margin: 0 calc(-1 * var(--content-padding)); + margin-bottom: 10px; + + border-bottom: 1px dotted rgba(220, 220, 220, 0.4); + padding: 10px 5px; + + background: var(--bg-black-color); + -webkit-backdrop-filter: blur(3px); + backdrop-filter: blur(3px); +} + +/* Image overlay */ + +#image-overlay-container { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 20px 40px; + box-sizing: border-box; + + opacity: 0; + pointer-events: none; + transition: opacity 0.4s; + + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +#image-overlay-container.visible { + opacity: 1; + pointer-events: auto; +} + +#image-overlay-content-container { + border-radius: 0 0 8px 8px; + border: 2px solid var(--primary-color); + background: var(--dim-ghost-color); + padding: 3px; + overflow: hidden; + + -webkit-backdrop-filter: blur(3px); + backdrop-filter: blur(3px); +} + +#image-overlay-image-container { + display: block; + position: relative; + overflow: hidden; + width: 80vmin; + height: 80vmin; +} + +#image-overlay-image, +#image-overlay-image-thumb { + display: inline-block; + object-fit: contain; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.65); +} + +#image-overlay-image { + position: absolute; + top: 0; + left: 0; +} + +#image-overlay-image-thumb { + filter: blur(16px); + transform: scale(1.5); +} + +#image-overlay-container.loaded #image-overlay-image-thumb { + opacity: 0; + pointer-events: none; + transition: opacity 0.25s; +} + +#image-overlay-image-container::after { + content: ""; + display: block; + position: absolute; + bottom: 0; + left: 0; + height: 4px; + width: var(--download-progress); + background: var(--primary-color); + box-shadow: 0 -3px 12px 4px var(--primary-color); + transition: 0.25s; +} + +#image-overlay-container.loaded #image-overlay-image-container::after { + width: 100%; + background: white; + opacity: 0; +} + +#image-overlay-container.errored #image-overlay-image-container::after { + width: 100%; + background: red; +} + +#image-overlay-container:not(.visible) #image-overlay-image-container::after { + width: 0 !important; +} + +#image-overlay-action-container { + padding: 4px 4px 6px 4px; + border-radius: 0 0 5px 5px; + background: var(--bg-black-color); + color: white; + font-style: oblique; + text-align: center; +} + +#image-overlay-container #image-overlay-action-content-without-size:not(.visible), +#image-overlay-container #image-overlay-action-content-with-size:not(.visible), +#image-overlay-container #image-overlay-file-size-warning:not(.visible), +#image-overlay-container #image-overlay-file-size-kilobytes:not(.visible), +#image-overlay-container #image-overlay-file-size-megabytes:not(.visible) { + display: none; +} + +#image-overlay-file-size-warning { + opacity: 0.8; + font-size: 0.9em; +} + +/* important easter egg mode */ + +html[data-language-code="preview-en"][data-url-key="localized.home"] #content + h1::after { + font-family: cursive; + display: block; + content: "(Preview Build)"; + font-size: 0.8em; +} + +/* Layout - Wide (most computers) */ + +@media (min-width: 900px) { + #page-container:not(.has-zero-sidebars) #secondary-nav { + display: none; + } +} + +/* Layout - Medium (tablets, some landscape mobiles) + * + * Note: Rules defined here are exclusive to "medium" width, i.e. they don't + * additionally apply to "thin". Use the later section which applies to both + * if so desired. + */ + +@media (min-width: 600px) and (max-width: 899.98px) { +} + +/* Layout - Wide or Medium */ + +@media (min-width: 600px) { + .content-sticky-heading-container { + /* Safari doesn't always play nicely with position: sticky, + * this seems to fix images sometimes displaying above the + * position: absolute subheading (h2) child + * + * See also: https://stackoverflow.com/questions/50224855/ + */ + transform: translate3d(0, 0, 0); + z-index: 1; + } + + /* Cover art floats to the right. It's positioned in HTML beneath the + * heading, so pull it up a little to "float" on top. + */ + #cover-art-container { + float: right; + width: 40%; + max-width: 400px; + margin: -60px 0 10px 10px; + + position: relative; + z-index: 2; + } + + html[data-url-key="localized.home"] #page-container.has-one-sidebar .grid-listing > .grid-item:not(:nth-child(n+10)) { + flex-basis: 23%; + margin: 15px; + } + + html[data-url-key="localized.home"] #page-container.has-one-sidebar .grid-listing > .grid-item:nth-child(n+10) { + flex-basis: 18%; + margin: 10px; + } +} + +/* Layout - Medium or Thin */ + +@media (max-width: 899.98px) { + .sidebar-column:not(.no-hide) { + display: none; + } + + #secondary-nav { + display: block; + } + + .layout-columns.vertical-when-thin { + flex-direction: column; + } + + .layout-columns.vertical-when-thin > *:not(:last-child) { + margin-bottom: 10px; + } + + .sidebar-column.no-hide { + max-width: unset !important; + flex-basis: unset !important; + margin-right: 0 !important; + margin-left: 0 !important; + width: 100%; + } + + .sidebar .news-entry:not(.first-news-entry) { + display: none; + } + + .grid-listing > .grid-item { + flex-basis: 40%; + } +} + +/* Layout - Thin (phones) */ + +@media (max-width: 600px) { + .content-columns { + columns: 1; + } + + #cover-art-container { + margin: 25px 0 5px 0; + width: 100%; + max-width: unset; + } + + /* Show sticky heading above cover art */ + + .content-sticky-heading-container { + z-index: 2; + } + + /* Disable grid features, just line header children up vertically */ + + #header { + display: block; + } + + #header > div:not(:first-child) { + margin-top: 0.5em; + } +} -- cgit 1.3.0-6-gf8a5 From 3a871cf43a11b87392d26320c736b516925da684 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 11 Oct 2023 14:32:28 -0300 Subject: implement flash act pages, rework flash sidebar layout --- src/static/site5.css | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/static') diff --git a/src/static/site5.css b/src/static/site5.css index 7b3e3e03..b7f1b669 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -285,6 +285,8 @@ body::before { .sidebar > h3, .sidebar > p { text-align: center; + padding-left: 4px; + padding-right: 4px; } .sidebar h1 { -- cgit 1.3.0-6-gf8a5 From a42078aed0805209ecb4724ea55a35e3909541dc Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 23 Oct 2023 11:56:13 -0300 Subject: css: inset long-content pages a little lighter in thin layout --- src/static/site5.css | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'src/static') diff --git a/src/static/site5.css b/src/static/site5.css index b7f1b669..6076228b 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -689,10 +689,14 @@ p code { margin-bottom: 0; } +main.long-content { + --long-content-padding-ratio: 0.12; +} + main.long-content .main-content-container, main.long-content > h1 { - padding-left: 12%; - padding-right: 12%; + padding-left: calc(var(--long-content-padding-ratio) * 100%); + padding-right: calc(var(--long-content-padding-ratio) * 100%); } dl dt { @@ -1311,8 +1315,8 @@ main.long-content .content-sticky-heading-container { main.long-content .content-sticky-heading-container .content-sticky-heading-row, main.long-content .content-sticky-heading-container .content-sticky-subheading-row { - padding-left: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); - padding-right: calc(0.12 * (100% - 2 * var(--content-padding)) + var(--content-padding)); + padding-left: calc(var(--long-content-padding-ratio) * (100% - 2 * var(--content-padding)) + var(--content-padding)); + padding-right: calc(var(--long-content-padding-ratio) * (100% - 2 * var(--content-padding)) + var(--content-padding)); } .content-sticky-heading-row { @@ -1744,4 +1748,8 @@ html[data-language-code="preview-en"][data-url-key="localized.home"] #content #header > div:not(:first-child) { margin-top: 0.5em; } + + main.long-content { + --long-content-padding-ratio: 0.04; + } } -- cgit 1.3.0-6-gf8a5 From 63075c650bf990407e9eefe3e9f135b2425a2ded Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 23 Oct 2023 13:48:47 -0300 Subject: content, css: linkTemplate: new linkless slot --- src/static/site5.css | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/static') diff --git a/src/static/site5.css b/src/static/site5.css index 6076228b..d50d4623 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -439,6 +439,14 @@ a.current { font-weight: 800; } +a:not([href]) { + cursor: default; +} + +a:not([href]):hover { + text-decoration: none; +} + .nav-main-links > span > span { white-space: nowrap; } -- cgit 1.3.0-6-gf8a5 From 22b1823dd82bf4fd2063d121c743d02e452fe7f3 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 23 Oct 2023 13:49:20 -0300 Subject: content: generateAlbimSidebarTrackSection: handle commentary-less tracks --- src/static/site5.css | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/static') diff --git a/src/static/site5.css b/src/static/site5.css index d50d4623..0eb7dcda 100644 --- a/src/static/site5.css +++ b/src/static/site5.css @@ -794,6 +794,10 @@ li > ul { display: none; } +html[data-url-key="localized.albumCommentary"] li.no-commentary { + opacity: 0.7; +} + /* Images */ .image-container { -- cgit 1.3.0-6-gf8a5 From d62e174c5b7d71c38270ef93da7ad4b640c4d72b Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Mon, 23 Oct 2023 23:43:44 -0300 Subject: client: don't have album sidebar break on tracks w/o href --- src/static/client2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/static') diff --git a/src/static/client2.js b/src/static/client2.js index 4f4a7153..758d91a6 100644 --- a/src/static/client2.js +++ b/src/static/client2.js @@ -1241,7 +1241,7 @@ function getAlbumCommentarySidebarReferences() { info.sidebarTrackDirectories = info.sidebarTrackLinks - .map(el => el.getAttribute('href').slice(1)); + .map(el => el.getAttribute('href')?.slice(1) ?? null); info.sidebarTrackSections = Array.from(info.sidebar.getElementsByTagName('details')); -- cgit 1.3.0-6-gf8a5