From ae5f68ba51bbbe308cc56e70e70209652c869843 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Sun, 26 Feb 2023 17:44:53 -0400 Subject: basic image overlays --- src/gen-thumbs.js | 1 + src/misc-templates.js | 4 ++- src/static/client.js | 74 +++++++++++++++++++++++++++++++++++++++++ src/static/site3.css | 82 ++++++++++++++++++++++++++++++++++++++++++++++ src/strings-default.json | 2 ++ src/util/colors.js | 2 ++ src/write/page-template.js | 15 +++++++++ 7 files changed, 179 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/gen-thumbs.js b/src/gen-thumbs.js index dc1f6fb4..89223778 100644 --- a/src/gen-thumbs.js +++ b/src/gen-thumbs.js @@ -191,6 +191,7 @@ function generateImageThumbnails(filePath, {spawnConvert}) { ]); return Promise.all([ + promisifyProcess(convert('.huge', {size: 1800, quality: 96}), false), promisifyProcess(convert('.medium', {size: 400, quality: 95}), false), promisifyProcess(convert('.small', {size: 250, quality: 85}), false), ]); diff --git a/src/misc-templates.js b/src/misc-templates.js index 8a61bf7f..21dca90e 100644 --- a/src/misc-templates.js +++ b/src/misc-templates.js @@ -309,6 +309,7 @@ function unbound_getThemeString(color, { primary, dark, dim, + dimGhost, bg, bgBlack, shadow, @@ -318,6 +319,7 @@ function unbound_getThemeString(color, { `--primary-color: ${primary}`, `--dark-color: ${dark}`, `--dim-color: ${dim}`, + `--dim-ghost-color: ${dimGhost}`, `--bg-color: ${bg}`, `--bg-black-color: ${bgBlack}`, `--shadow-color: ${shadow}`, @@ -737,7 +739,7 @@ function unbound_img({ wrapped = html.tag('a', { id, - class: ['box', hide && 'js-hide'], + class: ['box', hide && 'js-hide', 'image-link'], href: typeof link === 'string' ? link : originalSrc, }, wrapped); diff --git a/src/static/client.js b/src/static/client.js index 15f21fdb..9ae5510a 100644 --- a/src/static/client.js +++ b/src/static/client.js @@ -547,3 +547,77 @@ function updateStickyHeading() { document.addEventListener('scroll', updateStickyHeading); updateStickyHeading(); + +// Image overlay ------------------------------------------ + +function addImageOverlayClickHandlers() { + for (const img of document.querySelectorAll('.image-link')) { + img.addEventListener('click', handleImageLinkClicked); + } + + const container = document.getElementById('image-overlay-container'); + const actionContainer = document.getElementById('image-overlay-action-container'); + + container.addEventListener('click', handleContainerClicked); + document.body.addEventListener('keydown', handleKeyDown); + + function handleContainerClicked(evt) { + // Only hide the image overlay if actually clicking the background. + if (evt.target !== container) { + return; + } + + // If you clicked anything close to or beneath the action bar, don't hide + // the image overlay. + const rect = actionContainer.getBoundingClientRect(); + if (evt.clientY >= rect.top - 40) { + return; + } + + container.classList.remove('visible'); + } + + function handleKeyDown(evt) { + if (evt.key === 'Escape' || evt.key === 'Esc' || evt.keyCode === 27) { + container.classList.remove('visible'); + } + } +} + +function handleImageLinkClicked(evt) { + if (evt.metaKey || evt.shiftKey || evt.altKey) { + return; + } + evt.preventDefault(); + + const container = document.getElementById('image-overlay-container'); + container.classList.add('visible'); + container.classList.remove('loaded'); + container.classList.remove('errored'); + + const viewOriginal = document.getElementById('image-overlay-view-original'); + const mainImage = document.getElementById('image-overlay-image'); + const thumbImage = document.getElementById('image-overlay-image-thumb'); + + const source = evt.target.closest('a').href; + mainImage.src = source.replace(/\.(jpg|png)$/, '.huge.jpg'); + thumbImage.src = source.replace(/\.(jpg|png)$/, '.small.jpg'); + viewOriginal.href = source; + + mainImage.addEventListener('load', handleMainImageLoaded); + mainImage.addEventListener('error', handleMainImageErrored); + + function handleMainImageLoaded() { + mainImage.removeEventListener('load', handleMainImageLoaded); + mainImage.removeEventListener('error', handleMainImageErrored); + container.classList.add('loaded'); + } + + function handleMainImageErrored() { + mainImage.removeEventListener('load', handleMainImageLoaded); + mainImage.removeEventListener('error', handleMainImageErrored); + container.classList.add('errored'); + } +} + +addImageOverlayClickHandlers(); diff --git a/src/static/site3.css b/src/static/site3.css index cc853b65..7abb5351 100644 --- a/src/static/site3.css +++ b/src/static/site3.css @@ -1274,6 +1274,88 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r contain: paint; } +/* 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); +} + +#image-overlay-container.loaded #image-overlay-image-thumb { + opacity: 0; + pointer-events: none; + transition: opacity 0.25s; +} + +#image-overlay-action-container { + padding: 8px 4px 6px 4px; + border-radius: 0 0 5px 5px; + background: var(--bg-black-color); + color: white; + font-style: oblique; + text-align: center; +} + /* important easter egg mode */ html[data-language-code="preview-en"][data-url-key="localized.home"] #content diff --git a/src/strings-default.json b/src/strings-default.json index 0faa4f7c..37e5fbad 100644 --- a/src/strings-default.json +++ b/src/strings-default.json @@ -96,6 +96,8 @@ "releaseInfo.viewCommentary.link": "commentary page", "releaseInfo.viewGallery": "View {LINK}!", "releaseInfo.viewGallery.link": "gallery page", + "releaseInfo.viewOriginalFile": "View {LINK}.", + "releaseInfo.viewOriginalFile.link":" original file", "releaseInfo.listenOn": "Listen on {LINKS}.", "releaseInfo.listenOn.noLinks": "This track has no URLs at which it can be listened.", "releaseInfo.visitOn": "Visit on {LINKS}.", diff --git a/src/util/colors.js b/src/util/colors.js index c9ef69bf..8aa7bda9 100644 --- a/src/util/colors.js +++ b/src/util/colors.js @@ -12,6 +12,7 @@ export function getColors(themeColor, { const dark = primary.luminance(0.02); const dim = primary.desaturate(2).darken(1.5); + const dimGhost = dim.alpha(0.8); const light = chroma.average(['#ffffff', primary], 'rgb', [4, 1]); const bg = primary.luminance(0.008).desaturate(3.5).alpha(0.8); @@ -26,6 +27,7 @@ export function getColors(themeColor, { dark: dark.hex(), dim: dim.hex(), + dimGhost: dimGhost.hex(), light: light.hex(), bg: bg.hex(), diff --git a/src/write/page-template.js b/src/write/page-template.js index de369018..f47d3f0d 100644 --- a/src/write/page-template.js +++ b/src/write/page-template.js @@ -439,6 +439,20 @@ export function generateDocumentHTML(pageInfo, { })), ]))); + const imageOverlayHTML = html.tag('div', {id: 'image-overlay-container'}, + html.tag('div', {id: 'image-overlay-content-container'}, [ + html.tag('a', {id: 'image-overlay-image-container'}, [ + html.tag('img', {id: 'image-overlay-image'}), + html.tag('img', {id: 'image-overlay-image-thumb'}), + ]), + html.tag('div', {id: 'image-overlay-action-container'}, [ + language.$('releaseInfo.viewOriginalFile', { + link: html.tag('a', {id: 'image-overlay-view-original'}, + language.$('releaseInfo.viewOriginalFile.link')), + }), + ]), + ])); + const socialEmbedHTML = [ socialEmbed.title && html.tag('meta', {property: 'og:title', content: socialEmbed.title}), @@ -580,6 +594,7 @@ export function generateDocumentHTML(pageInfo, { ]), infoCardHTML, + imageOverlayHTML, html.tag('script', { type: 'module', -- cgit 1.3.0-6-gf8a5