var nullFunc = () => { }; var poppingState = false; var closingGallery = false; var closingFilterBuilder = false; /* jshint ignore:start */ var do_eval = (exp) => eval(exp); /* jshint ignore:end */ function onPopState(event) { resetAppState(); let hash = event.target.location.hash; poppingState = true; if (hash.startsWith('#js:')) do_eval(decodeURIComponent(hash.substr(4))); poppingState = false; closingGallery = false; closingFilterBuilder = false; } function onInit() { colorAccuracyJSWall.classList.add('nodisplay'); colorAccuracyJSProtectedContent.classList.remove('nodisplay'); fillStaticPartOfColorPalette(); let hash = window.location.hash; poppingState = true; if (hash === '') window.history.replaceState(null, document.title, '#'); else if (hash.startsWith('#js:')) do_eval(decodeURIComponent(hash.substr(4))); poppingState = false; updateColorPaletteAverage(); } window.onpopstate = onPopState; var imgQualities = { 'thumb': imgs_thumb, 'small': imgs_small, 'dcent': imgs_dcent, 'large': imgs_large, 'full': imgs_full }; var qualityName = { 'thumb': 'Thumbnail', 'small': 'Small', 'dcent': 'Decent', 'large': 'Large', 'full': 'Full' }; function formatFileSize(bytecount) { units = ['B', 'KB', 'MB', 'GB', 'TB']; magnitude = 0; while (bytecount >= 2048) { bytecount /= 1024; magnitude++; } bytecount = Math.round(bytecount * 10) / 10; return `${bytecount} ${units[magnitude]}`; } function openGallery(image, quality) { if (closingGallery) { setTimeout(closeGallery, 50); return; } if (typeof (quality) == typeof (undefined)) quality = 'dcent'; if (!poppingState) window.history.pushState(null, document.title, `#js:openGallery(${encodeURIComponent(JSON.stringify(image))}, ${encodeURIComponent(JSON.stringify(quality))})` ); galleryBrowser.classList.remove('nodisplay'); item = images[image]; img = imgQualities[quality][image]; galleryImage.style.backgroundImage = `url('./${img}')`; // Qualities highlight if (quality == 'thumb') qualityThumb.style.background = "#619A3588"; else qualityThumb.style.background = null; if (quality == 'small') qualitySmall.style.background = "#619A3588"; else qualitySmall.style.background = null; if (quality == 'dcent') qualityDcent.style.background = "#619A3588"; else qualityDcent.style.background = null; if (quality == 'large') qualityLarge.style.background = "#619A3588"; else qualityLarge.style.background = null; if (quality == 'full') qualityFull.style.background = "#619A3588"; else qualityFull.style.background = null; // Set callback seeThumb.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'thumb')`); seeSmall.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'small')`); seeDcent.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'dcent')`); seeLarge.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'large')`); seeFull.setAttribute('onclick', `openGallery(${JSON.stringify(image)}, 'full')`); // Set download targets downloadThumb.href = './' + imgQualities.thumb[image]; downloadSmall.href = './' + imgQualities.small[image]; downloadDcent.href = './' + imgQualities.dcent[image]; downloadLarge.href = './' + imgQualities.large[image]; downloadFull.href = './' + imgQualities.full[image]; // Set download names downloadThumb.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.thumb}).${formats[image].thumb}`); downloadSmall.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.small}).${formats[image].small}`); downloadDcent.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.dcent}).${formats[image].dcent}`); downloadLarge.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.large}).${formats[image].large}`); downloadFull.setAttribute('download', `${name} - ${image} - ${artists[item.artist].name} - ${item.date.join('-')} (${qualityName.full}).${formats[image].full}`); // Set image sizes sizeThumb.innerText = `(${formatFileSize(sizes[image].thumb)}) - ${formats[image].thumb}`; sizeSmall.innerText = `(${formatFileSize(sizes[image].small)}) - ${formats[image].small}`; sizeDcent.innerText = `(${formatFileSize(sizes[image].dcent)}) - ${formats[image].dcent}`; sizeLarge.innerText = `(${formatFileSize(sizes[image].large)}) - ${formats[image].large}`; sizeFull.innerText = `(${formatFileSize(sizes[image].full)}) - ${formats[image].full}`; } var checkVisible = (elem) => { let posY = (elm) => { let test = elm; let top = 0; while (!!test && test.tagName.toLowerCase() !== "body") { top += test.offsetTop; test = test.offsetParent; } return top; }; let viewPortHeight = () => { let de = document.documentElement; if (!!window.innerWidth) { return window.innerHeight; } else if (de && !isNaN(de.clientHeight)) { return de.clientHeight; } return 0; }; let scrollY = () => { if (window.pageYOffset) { return window.pageYOffset; } return Math.max(document.documentElement.scrollTop, document.body.scrollTop); }; let checkvisible = (elm) => { let vpH = viewPortHeight(); // Viewport Height let st = scrollY(); // Scroll Top let y = posY(elm); let dy = elm.offsetHeight; return ((vpH + st) > y) && (st < (y + dy)); }; return checkvisible(elem); }; function filterArtist(artist) { resetAppState(); if (!poppingState) { window.history.pushState(null, document.title, `#js:filterArtist(${encodeURIComponent(JSON.stringify(artist))})` ); } document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected'); nestedElements.classList.add('nodisplay'); listElements.classList.remove('nodisplay'); filterNav.classList.remove('nodisplay'); let item = artists[artist]; filterIcon.className = "fa fa-user"; filterText.innerText = item.name; for (let key in images) { image = images[key]; let hideable_row = document.getElementById('row_' + key); if (image.artist == artist) { document.getElementById('list-' + key).classList.remove('nodisplay'); if (hideable_row !== null) hideable_row.classList.remove('nodisplay'); } else { document.getElementById('list-' + key).classList.add('nodisplay'); if (hideable_row !== null) hideable_row.classList.add('nodisplay'); } } updateColorPaletteAverage(); } function filterTag(tag) { resetAppState(); if (!poppingState) window.history.pushState(null, document.title, `#js:filterTag(${encodeURIComponent(JSON.stringify(tag))})` ); document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected'); nestedElements.classList.add('nodisplay'); listElements.classList.remove('nodisplay'); filterNav.classList.remove('nodisplay'); let item = tags[tag]; filterIcon.className = "fa fa-" + item.icon; filterText.innerText = item.name; for (let key in images) { image = images[key]; let hideable_row = document.getElementById('row_' + key); if ((image.technology == tag) || (image.tags.indexOf(tag) >= 0)) { document.getElementById('list-' + key).classList.remove('nodisplay'); if (hideable_row !== null) hideable_row.classList.remove('nodisplay'); } else { document.getElementById('list-' + key).classList.add('nodisplay'); if (hideable_row !== null) hideable_row.classList.add('nodisplay'); } } updateColorPaletteAverage(); } function showAsList() { resetAppState(); if (!poppingState) window.history.pushState(null, document.title, `#js:showAsList()` ); nestedElements.classList.add('nodisplay'); listElements.classList.remove('nodisplay'); filterNav.classList.add('nodisplay'); for (let key in images) { image = images[key]; let hideable_row = document.getElementById('row_' + key); document.getElementById('list-' + key).classList.remove('nodisplay'); if (hideable_row !== null) hideable_row.classList.remove('nodisplay'); } } function buildFilters() { if (closingFilterBuilder) { setTimeout(closeFilterBuilder, 50); return; } resetAppState(); if (!poppingState) window.history.pushState(null, document.title, `#js:buildFilters()` ); document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.add('selected'); document.getElementById('filterBuilder').classList.remove('nodisplay'); } function resetAppState(skipScroll) { document.querySelector('#sideCard>.controlButtons>.showNested').classList.remove('selected'); document.querySelector('#sideCard>.controlButtons>.showListed').classList.remove('selected'); document.querySelector('#sideCard>.controlButtons>.showFiltered').classList.remove('selected'); document.getElementById('filterBuilder').classList.add('nodisplay'); nestedElements.classList.remove('nodisplay'); listElements.classList.add('nodisplay'); filterNav.classList.add('nodisplay'); galleryBrowser.classList.add('nodisplay'); for (let item of Array.from(document.querySelectorAll('#colorAccuracy tr.hideablePaletteItem'))) item.classList.remove('nodisplay'); for (let item of Array.from(document.querySelectorAll('#sideCard>.fastNavigation>.fastNavigationCard'))) { item.classList.remove('nodisplay'); item.classList.remove('displaying'); item.onclick = null; } updateColorPaletteAverage(); if (!skipScroll) window.scrollTo(0, 0); } var clearFilters = () => { resetAppState(); window.history.back(); }; var closeGallery = () => { resetAppState(true); closingGallery = true; window.history.back(); }; var closeFilterBuilder = () => { resetAppState(true); closingFilterBuilder = true; window.history.back(); }; var rgxHexColor = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/; var parseHexColor = x => rgxHexColor.exec(x).slice(1).map(i => parseInt(i, 16)); var toHexColor = l => '#' + (l.map(i => ('0' + Math.round(i).toString(16)).slice(-2)).join('')).toUpperCase(); var zip = (...rows) => [...rows[0]].map((_, c) => rows.map(row => row[c])); var euclideanDistance2 = (a1, a2) => zip(a1, a2).map(x => x[0] - x[1]).map(x => x * x).reduce((a, v) => a + v, 0); var euclideanDistance = (a1, a2) => Math.sqrt(euclideanDistance2(a1, a2)); var avg = (...values) => [...values].reduce((acc, val) => acc + val) / values.length; var mixColorChannel = (...values) => Math.sqrt(values.map(v => v * v).reduce((a, v) => a + v) / values.length); function inflateFontAwesome(icon) { let fa = document.createElement('i'); fa.setAttribute('class', `fa fa-${icon}`); fa.setAttribute('aria-hidden', 'true'); return fa; } function round(number, digits) { if (digits === undefined) digits = 0; let factor = Math.pow(10, digits); return Math.round(number * factor) / factor; } function colorSimilarity(color1, color2) { let c1 = parseHexColor(color1).map(x => x / 255); let c2 = parseHexColor(color2).map(x => x / 255); return 1 - (euclideanDistance(c1, c2) / Math.sqrt(3)); } function colorAverage(...colors) { let engine = avg; // Easy to implement; bad colors engine = mixColorChannel; // Simulates light mixing with gamma=2.0 if (colors.length == 0) return null; else return toHexColor( zip( ...colors.map(parseHexColor) ).map(ch => engine(...ch)) ); } function determineLightOrDark(color) { // True: light, False: dark // https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems let y = [0.2126, 0.7152, 0.0722]; let c = parseHexColor(color); let hsp = Math.sqrt( zip( y, c.map(x => x * x) ).map( v => v[0] * v[1] ).reduce( (a, v) => a + v ) ); return hsp > 127; } function updateColorPaletteAverage() { let fields = Array.from(document.querySelectorAll('#row_avg td')); for (let fld of fields) { fld.setAttribute("class", "clr_" + fld.id.slice(8)); fld.setAttribute("title", ""); fld.style.background = null; while (fld.firstChild) fld.firstChild.remove(); } let relevantColors = {}; for (let color of color_list) { if (relevantColors[color] === undefined) relevantColors[color] = []; for (let idx in images) { let hideable_row = document.getElementById('row_' + idx); let image = images[idx]; let colorUsed = ((image.color || {})[color] || {}).colorUsed; if (colorUsed !== null && colorUsed !== undefined && !hideable_row.classList.contains('nodisplay')) { relevantColors[color].push(colorUsed); } } } for (let idx in relevantColors) { let colorsShown = relevantColors[idx]; let cell = document.querySelector('#row_avg td.clr_' + idx); let canonical = canonical_colors[idx].color; let avgColor = colorAverage(...colorsShown); if (avgColor !== null) { cell.style.background = avgColor; let isLight = determineLightOrDark(avgColor); let similarity = round(colorSimilarity(avgColor, canonical) * 100, 2); cell.classList.add(isLight ? 'blackText' : 'whiteText'); cell.appendChild(document.createTextNode(avgColor)); cell.appendChild(document.createElement('br')); cell.appendChild(document.createTextNode(`(${similarity}%)`)); cell.title = `${similarity}% similar to the ideal value`; } } } function fillStaticPartOfColorPalette() { //bodyparts => Display names for body parts //canonical_colors => "Right" colors let basecolors = color_list.slice(0, color_repeated[false]); let deviatedcolors = color_list.slice(color_repeated[false]); for (let color of basecolors) { let canonical = canonical_colors[color].color; for (let imgid in images) { let image = images[imgid]; let colordef = image.color[color]; if (colordef !== undefined) { let cell = `#colorAccuracy table #row_${imgid} .clr_${color}`; cell = document.querySelector(cell); while (cell.firstChild) cell.firstChild.remove(); let similarity = round(colorSimilarity(colordef.colorUsed, canonical) * 100, 2); cell.style.background = colordef.colorUsed; cell.classList.add(colordef.colorIsLight ? 'blackText' : 'whiteText'); cell.appendChild(document.createTextNode(colordef.colorUsed)); cell.appendChild(document.createElement('br')); cell.appendChild(document.createTextNode(`(${similarity}%)`)); cell.title = `"${bodyparts[colordef.targetOnImage]}" color\n${similarity}% similar to the ideal value`; } } } for (let color of deviatedcolors) { let canonical = canonical_colors[color].color; for (let imgid in images) { let image = images[imgid]; let colordef = image.color[color]; if (colordef !== undefined) { let cell = `#colorAccuracy table #row_${imgid} .clr_${color}`; cell = document.querySelector(cell); while (cell.firstChild) cell.firstChild.remove(); if (colordef === null) { cell.appendChild(inflateFontAwesome('2x fa-times-circle')); cell.title = 'This feature is missing on the artwork'; cell.classList.add('featureMissing'); } else { let shouldBeUsing = canonical; if (image.color[colordef.targetAppropriate]) shouldBeUsing = image.color[colordef.targetAppropriate].colorUsed; let problem = (colordef.colorUsed != shouldBeUsing); cell.style.background = colordef.colorUsed; cell.classList.add(colordef.colorIsLight ? 'blackText' : 'whiteText'); cell.appendChild(document.createTextNode(colordef.colorUsed)); cell.appendChild(document.createElement('br')); cell.appendChild(inflateFontAwesome(problem ? 'warning' : 'info-circle')); let title = `"${bodyparts[colordef.targetOnImage]}" color\n`; if (problem) title += `Should have the same color as "${bodyparts[colordef.targetAppropriate]}"`; else title += `Same color as "${bodyparts[colordef.targetAppropriate]}"`; let parent = canonical; let parentColordef = image.color[colordef.targetAppropriate]; if (parentColordef !== undefined && parentColordef !== null) parent = parentColordef.colorUsed; let sp = round(colorSimilarity(colordef.colorUsed, parent) * 100, 2); let sc = round(colorSimilarity(colordef.colorUsed, canonical) * 100, 2); title += '\n'; if (parentColordef !== undefined && parentColordef !== null) title += `${sp}% similar to the one used as "${bodyparts[colordef.targetAppropriate]}"\n`; title += `${sc}% similar to the ideal value`; cell.title = title; } } } } } function onInitRegister() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js').then(function (registration) { console.log('ServiceWorker registration successful with scope: ', registration.scope); }, function (err) { console.log('ServiceWorker registration failed: ', err); }); } } function keepFastNavigationUpdated() { setTimeout(keepFastNavigationUpdated, 30); document.querySelectorAll('#sideCard>fastNavigation>fastNavigationCard'); let nestedElement = document.getElementById('nestedElements'); let listedElement = document.getElementById('listElements'); let nestedButton = document.querySelector('#sideCard>.controlButtons>.showNested'); let listedButton = document.querySelector('#sideCard>.controlButtons>.showListed'); let filterButton = document.querySelector('#sideCard>.controlButtons>.showFiltered'); if (filterButton.classList.contains('selected')) { nestedButton.classList.remove('selected'); listedButton.classList.remove('selected'); } else { if (nestedElement.offsetParent === null) nestedButton.classList.remove('selected'); else nestedButton.classList.add('selected'); if (listedElement.offsetParent === null) listedButton.classList.remove('selected'); else listedButton.classList.add('selected'); } let main = document.getElementById('main'); let sidebarsHidden = document.getElementById('sideCard').offsetParent === null; if (sidebarsHidden) main.classList.add('noSidebars'); else main.classList.remove('noSidebars'); let imagesElement = [ nestedElement, listedElement, ].filter(x => !x.classList.contains('nodisplay'))[0]; let generateSmoothScrollCallback = (element) => (() => { element.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' }); if (!checkVisible(element)) setTimeout(generateSmoothScrollCallback(element), 100); }); for (let cardElement of Array.from(imagesElement.getElementsByClassName('card'))) { let element = cardElement.getElementsByTagName('img')[0]; let imagenum = parseInt(cardElement.getAttribute('id').split('-')[1]); let fastNavElem = document.getElementById(`fastnav-${imagenum}`); let detailsElem = document.getElementById(`details-${imagenum}`); if (checkVisible(element)) fastNavElem.classList.add('displaying'); else fastNavElem.classList.remove('displaying'); if (checkVisible(element)) detailsElem.classList.remove('collapsed'); else detailsElem.classList.add('collapsed'); if (element.offsetParent === null) fastNavElem.classList.add('nodisplay'); else fastNavElem.classList.remove('nodisplay'); fastNavElem.onclick = generateSmoothScrollCallback(element); let titleElem = detailsElem.getElementsByTagName('span')[0]; titleElem.onclick = generateSmoothScrollCallback(element); titleElem.getElementsByTagName('img')[0].onclick = generateSmoothScrollCallback(element); titleElem.getElementsByTagName('span')[0].onclick = generateSmoothScrollCallback(element); } } setTimeout(keepFastNavigationUpdated, 10); onInit(); onInitRegister();