« get me outta code hell

hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/gen-thumbs.js1
-rw-r--r--src/misc-templates.js4
-rw-r--r--src/static/client.js74
-rw-r--r--src/static/site3.css82
-rw-r--r--src/strings-default.json2
-rw-r--r--src/util/colors.js2
-rw-r--r--src/write/page-template.js15
7 files changed, 179 insertions, 1 deletions
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 db97e536..171b4825 100644
--- a/src/misc-templates.js
+++ b/src/misc-templates.js
@@ -294,6 +294,7 @@ function unbound_getThemeString(color, {
     primary,
     dark,
     dim,
+    dimGhost,
     bg,
     bgBlack,
     shadow,
@@ -303,6 +304,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}`,
@@ -722,7 +724,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 4eb1d2ba..47936d82 100644
--- a/src/static/client.js
+++ b/src/static/client.js
@@ -622,3 +622,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 c522bc9d..449e6fad 100644
--- a/src/static/site3.css
+++ b/src/static/site3.css
@@ -1326,6 +1326,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 0c775e34..bfe358e4 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 96036df2..bd52c456 100644
--- a/src/write/page-template.js
+++ b/src/write/page-template.js
@@ -520,6 +520,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}),
@@ -638,6 +652,7 @@ export function generateDocumentHTML(pageInfo, {
           ]),
 
           infoCardHTML,
+          imageOverlayHTML,
 
           html.tag('script', {
             type: 'module',