Greasy Fork

Greasy Fork is available in English.

DeviantArt Search Galleries and Favorites

Creates a search function that works on artist galleries and favorites collections. Search by deviation title and artist name. Optional alphabetical sorting.

目前为 2022-12-15 提交的版本,查看 最新版本

// ==UserScript==
// @name         DeviantArt Search Galleries and Favorites
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Creates a search function that works on artist galleries and favorites collections. Search by deviation title and artist name. Optional alphabetical sorting.
// @author       corepower
// @match        https://www.deviantart.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=deviantart.com
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

///////////////////////////////////////
////// User Preference Variables //////

    // Maximum search return: Maximum number of results that will tile after a search. This can be set as large as you like, but the page may slow down.
    // Possibly even consider lowering it if things are laggy while searching.
    const maximumsearchreturn = 1000;

    // Sort results alphabetically: if set to true, search results will be alphabetized. Used in conject with sortresultsbyartistortitle variable below.
    // If set to false, the default DeviantArt order is used which is based on date added.
    const sortresultsalphabetically = false;
    //Sort results by artist or title: if set to "artist" sorting will be based on artist. If set to "title", sorting will be based on title. 
    const sortresultsbyartistortitle = "title";

    // Pagination rate limit: the amount of time, in seconds to delay downloading gallery/collection page results during search indexing.
    // Can be set to 0 but if lots of large galleries are indexed, DeviantArt may automatically temp ban your account/IP.
    // Note: Multiple searches on a single page will not rerun the index, therefore unlimited searches may be run on a single page without consequence as long as the page is not navigated or refreshed. 
    const paginationratelimit = 1.0;

    // Rate limit warning: If true, puts a warning message in the search output text while indexing about rate limits if the rate limit is under two seconds on a large search.
    // Set to false to disable warning.
    const ratelimitwarning = true;

    // Pivotal row height: height, in pixels, that the dynamic deviation tiles will be based around. Tiles will be this tall or larger.
    const pivotalrowheight = 280;

    // Tile margin: margin between deviation tiles, in pixels. DeviantArt uses 4 pixels, don't know why you would change this but you can.
    const tilemargin = 4;

    // Preferred size: this is a named identifier for a size of DeviantArt generated thumbnails. Larger numbers are generally higher quality.
    // Set this variable based on your internet speed or personal preference.
    // Choose a value from amongst the following list: ["92S", "150", "200H", "300W", "375W", "414W", "250T", "350T","400T", "preview", "social_preview"]
    const preferredsize = "350T";

    // Debug: debug flag turns on various console outputs for debugging. Outputs may or may not make sense, they were added as needed.
    const debug = false;

//// End User Preference Variables ////
///////////////////////////////////////


/////////////////////////////////////////////////////////
////// Desired Features List - NOT YET IMPLEMENTED //////

// These features may come in future versions.

// * Navigate within DeviantArt and reset search capability based on new page (partially implemented)
// * Dynamic tile visibility so that any number of search results can be efficiently displayed without slowing down the page.
// * React framework tie-ins for things like element creation, event listeners, and deviation favoriting. This may not be feasible. I don't know React.

////// End Desired Features List ////////////////////////
/////////////////////////////////////////////////////////



//Special event listener for React-style internal navigation
//Credit to Raja Osama: https://rajaosama.me/blogs/detect-react-route-change-in-vanilla-js
//Re-run entire script after this type of navigation
let monitored_url = location.href;
document.body.addEventListener('click', ()=>{
    requestAnimationFrame(()=>{
        if(monitored_url!==location.href){
                let old_url = monitored_url;
                monitored_url = location.href

                //DEBUG
                if(debug && false) console.log('url changed to: ', monitored_url);

                //Check for specific case where navigation is between Galleries and Favorites on a single artist

                let old_url_parts = old_url.split("/");
                let monitored_url_parts = monitored_url.split("/");

                //Disregard these base path type of /artist
                if(old_url_parts.length < 5 || monitored_url_parts.length < 5)
                    return;

                //Check for if navigations from gallery -> favourite or favourite -> gallery
                let is_old_url_favourite = old_url_parts[4] == "favourites";
                let is_new_url_favourite = monitored_url_parts[4] == "favourites";
                if( is_old_url_favourite ? !is_new_url_favourite : is_new_url_favourite ) {

                    return;
                    //TODO modify main script execution for this type of internal navigation then enable observer by uncommenting code below
                    //set observer on this element to load
                    // waitForElm('._3h7d3').then((elm) => {
                    //     if(debug && true) console.log('Element ._3h7d3 is ready');
                    //     //executeScript();
                    // });
                }
                else { //not a problem navigation, run script regularly
                    executeScript();
                }
        }
    });
}, true);

//DEBUG
if(debug && false) console.log("Entrypoint ready state: ", document.readyState);
if(debug && false) document.addEventListener('readystatechange', () => console.log("Ready state change: ", document.readyState));


//Page needs to be fully loaded for script to work
if(document.readyState == "loading" || document.readyState == "interactive")
    window.addEventListener('load',executeScript);
else
    executeScript();


//Function wrapper for the entire script to enable document readiness check above, as well as for re-running after React-style navigation events
function executeScript() {
    
    //DEBUG
    if(debug && false) console.log("Entered script execution.");
    if(debug && false) console.log("Internal entrypoint ready state: ", document.readyState);

//Only execute script on gallery pages, otherwise return immediately
    let pathparts = window.location.pathname.split("/");
    if(pathparts.length < 3) {
        return;
    }
    if(pathparts[2] != "gallery" && pathparts[2] != "favourites") {
        return;
    }


//Creating and adding this search element structure to the DOM
//<div class="_1hkGk DRK5r _1bp4v">
//    <span class="DLN_H">
//        <span class="z8jNZ _1yoxj _2F1i2">
//            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
//                <path d="M19 11a8 8 0 10-3.095 6.32l3.63 3.63.095.083a1 1 0 001.32-1.498l-3.63-3.63A7.965 7.965 0 0019 11zM5 11a6 6 0 1112 0 6 6 0 01-12 0z"></path>
//            </svg>
//        </span>
//    </span>
//    <input type="text" class="aFKMF _3kAA3 _2KZ9p" aria-invalid="false" id="search-gallery" autocomplete="off" placeholder="Search Gallery" value="">
//</div>

    let searchparentparent = document.getElementsByClassName("_3h7d3")[0];

    let searchparent = document.createElement("div");
    searchparent.className = "_1hkGk DRK5r _1bp4v";
    searchparent.style.width = "350px";

    let searchiconparentparent = document.createElement("span");
    searchiconparentparent.className = "DLN_H";

    let searchiconparent = document.createElement("span");
    searchiconparent.className = "z8jNZ _1yoxj _2F1i2";

    var searchicon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
    searchicon.setAttribute("viewBox", "0 0 24 24");

    var searchiconpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
    searchiconpath.setAttribute("d", "M19 11a8 8 0 10-3.095 6.32l3.63 3.63.095.083a1 1 0 001.32-1.498l-3.63-3.63A7.965 7.965 0 0019 11zM5 11a6 6 0 1112 0 6 6 0 01-12 0z");

    let searchelement = document.createElement('input');
    searchelement.type = 'text';
    searchelement.className = 'aFKMF _3kAA3 _2KZ9p';
    searchelement.setAttribute('aria-invalid', 'false');
    searchelement.id = 'search-gallery';
    searchelement.autocomplete = 'off';
    searchelement.placeholder = 'Search Gallery';
    searchelement.value = '';

    searchicon.appendChild(searchiconpath);
    searchiconparent.appendChild(searchicon);
    searchiconparentparent.appendChild(searchiconparent);

    searchparent.appendChild(searchiconparentparent);
    searchparent.appendChild(searchelement);

    //Create search results text output and put it next to the search box
    let searchoutputtext = document.createElement("span");
    searchoutputtext.style.margin = "8px";
    searchoutputtext.style.maxWidth = "1000px";

    searchparentparent.appendChild(searchoutputtext);
    searchparentparent.appendChild(searchparent);
    

//Add event listeners
    searchelement.addEventListener("focus", buildIndex, {once : true});
    searchelement.addEventListener("keyup", search);


//define persistent globals for search indexing
    const csrftoken = window.__CSRF_TOKEN__;
    const artist_friendly_id = pathparts[1];//from the early URL path deconstruction

    let deviations;//this will be objects to index search with
    let matchingdeviations;//this will be objects that match the search

    let initialstatescripttext = document.getElementsByTagName("script")[11].innerHTML;
    let initialstatematch = initialstatescripttext.match(/\.__INITIAL_STATE__\s*=\s*JSON\.parse\(("[^\n]+")/);
    if(initialstatematch.length < 2) {
        console.error("DeviantArt Gallery Search Userscript Error: Could not parse script tag for __INITIAL_STATE__")
        searchelement.placeholder = 'Script Error';
        return;
    }
    let initialstateJSON = JSON.parse(eval(initialstatematch[1]));
    //console.log(initialstateJSON);

    let isgallery = pathparts[2] == "gallery";

    let collectionorgalleryfoldername = isgallery ? "galleryFolder" : "collectionFolder";
    let foldersJSON = initialstateJSON["@@entities"][collectionorgalleryfoldername];
    //console.log(foldersJSON);

    let isallfolder = false;
    if(pathparts.length >= 4 && pathparts[3] == "all") {
        isallfolder = true;
    }
    
    let folderid = "-1";// folder id of "-1" corresponds to "All" gallery
    let gallerysize = 0;
    if(!isallfolder) {
        //Get folder id from current url as most up-to-date source, else default assigned folder id from initial state
        //TODO this is imperfect. Fails if internal React navigation between galleries and favorites is initiated.
        if(pathparts.length >= 4) {
            folderid = pathparts[3];
        }
        else {//Featured or imperfect navigation, for now just handle featured
            
            for(let folder in foldersJSON) {
                if(foldersJSON[folder].name == "Featured"){
                    folderid = folder;
                    break;
                }
            }
            //folderid = initialstateJSON["gallectionSection"]["currentlyViewedFolderId"];
        }
    }
    gallerysize = foldersJSON[folderid]["totalItemCount"];


    //construct reuseable gallery pagination api string pieces
    let paginationstring_base = "https://www.deviantart.com/_napi/da-user-profile/api/" 
        + (isgallery ? "gallery" : "collection") 
        + "/contents?username=" + artist_friendly_id 
        + "&csrf_token=" + csrftoken 
        + (isallfolder ? "&all_folder=true" : "&folderid=" + folderid) 
        + "&limit=60&offset=";


    //Get remaining UI elements needed for reference
    let itemscontainer = document.getElementsByClassName("RMUi2")[0];
    let searchitemscontainer = itemscontainer.cloneNode();
    

    itemscontainer.parentNode.appendChild(searchitemscontainer);




//This function indexes the gallery to be searched on textbox focus
    async function buildIndex() {
        searchelement.placeholder = 'Building Search Index...';
        searchelement.blur();
        searchelement.setAttribute('readonly', 'readonly');

        //DEBUG indexing performance
        let starttime; 
        if(debug && true) {
            starttime = performance.now();
        }

        let currentoffset = 0;
        let paginated_urls = [];
        while(currentoffset < gallerysize) {
            let currentpaginationurl = paginationstring_base + currentoffset;
            paginated_urls.push(currentpaginationurl);

            currentoffset += 60;//60 is maximum pagination
        }
        //DEBUG
        if(debug && false) console.log(paginated_urls);

        await concatenateJson(paginated_urls)
        .then(concatenatedJson => {
            //prepare indexable items by making them lowercase
            let lowercasejson = concatenatedJson;
            for(let i=0; i< lowercasejson.length; i++) {
                let deviation = lowercasejson[i].deviation;
                deviation.gs_username = deviation.author.username.toLowerCase();
                deviation.gs_title = deviation.title.toLowerCase();

                //Collect static thumbnail meta info. easier to do once
                let thumbpathstring = "";
                for(let i=0; i<deviation.media.types.length; i++) {
                    if(deviation.media.types[i].t == preferredsize) {
                        deviation.gs_thumb_path_string = deviation.media.types[i].c;
                        deviation.gs_thumb_width = deviation.media.types[i].w;
                        deviation.gs_thumb_height = deviation.media.types[i].h;
                        break;
                    }
                }

                //Set text-based deviations to have square tiles
                if(deviation.type == "journal" || deviation.type == "literature" || deviation.type == "status") {
                    deviation.gs_thumb_width = 300;
                    deviation.gs_thumb_height = 300;
                }
            }
            
            //
            if(sortresultsalphabetically == true)
            {
                if(sortresultsbyartistortitle == "artist")
                    deviations = sortDeviationByKey(lowercasejson, "gs_username");
                else
                    deviations = sortDeviationByKey(lowercasejson, "gs_title");
            }
            else {
                deviations = lowercasejson;
            }
            
        });

        searchelement.removeAttribute('readonly');
        searchelement.focus();
        searchelement.placeholder = 'Search Gallery';

        //DEBUG indexing performance
        let endtime; 
        if(debug && true) {
            endtime = performance.now();
            console.log("Indexing took " + (endtime - starttime) + " milliseconds for " + paginated_urls.length + " page" + (paginated_urls.length == 1 ? "." : "s."))
        }
    }

//Basic JSON fetch from URL function
    async function fetchJson(url) {
        // Use the fetch API to download the URL
        const response = await fetch(url);

        // Parse the JSON result from the response
        const json = await response.json();

        return json;
    }

//Fetch all jsons then concatenate
   async function concatenateJson(urls) {
        let results = [];

        //Convert user preference on rate limit to useable millisecond value
        let waitmilliseconds = Math.ceil(paginationratelimit * 1000);

        let pagedownloadestimate = paginationratelimit + 0.306;// 306 milliseconds based on average download test
        let friendlytimeremaing = pagedownloadestimate * urls.length;

        if(urls.length > 30 && paginationratelimit < 2.0 && ratelimitwarning) {
            searchoutputtext.style.color = "orange";
            searchoutputtext.innerHTML = "This is a large gallery to search. If you run searches like this frequently, " 
                + "consider raising the rate limit to 2 seconds or longer to avoid DeviantArt temp bans. "
                + "Rate limits can be changed and this warning can be turned off in the user preference variables inside the userscript.";
        }

        // Loop through the URLs
        for (let i=0; i<urls.length; i++) {
     
            //Show rough indexing time remaining to the user. The bulk of the indexing time comes from this rate limited function
            if(urls.length != 1) {
                searchelement.placeholder = 'Building Search Index... (' + Math.ceil(friendlytimeremaing) + " seconds remaining)";

                //DEBUG
                if(debug && false) console.log("Time remaining: " + friendlytimeremaing);

                friendlytimeremaing -= pagedownloadestimate;
            }

            //Set wait time between JSON fetches, only if there is a "between" to begin with. Also only do this if rate limit is not 0.
            if(i > 0 && waitmilliseconds != 0) {
                //DEBUG
                if(debug && false) console.log("Waiting " + waitmilliseconds + " milliseconds.");

                await new Promise(resolve => setTimeout(resolve, waitmilliseconds));
            }

            //DEBUG
            if(debug && false) console.log("Download round " + i);

            // Download the current URL and concatenate the JSON result
            let response = await fetchJson(urls[i]);
            let json = response.results;
            results = results.concat(json);
        }

        //Reset output text after possible rate limit warning.
        searchoutputtext.style.color = "rgb(242, 242, 242)";
        searchoutputtext.innerHTML = "";

        return results;
    }

//Searches through deviations array on keyup event
    function search() {
        let currentsearchtext = searchelement.value.toLowerCase();
        //DEBUG
        if(debug && true) console.log("Searching: " + currentsearchtext);

        //Restore gallery visibility and abort if search goes inactive
        if(currentsearchtext == "") {
            itemscontainer.style.display = "";
            searchitemscontainer.style.display = "none";
            searchoutputtext.innerHTML = "";
            return;
        }   
        
        //hide original gallery, show custom search gallery
        itemscontainer.style.display = "none";
        searchitemscontainer.style.display = "";

        //search
        let resultsarray = [];
        for(let i=0; i< deviations.length; i++) {
            let deviation = deviations[i].deviation;

            if(deviation.gs_username.includes(currentsearchtext)) {
                resultsarray.push(deviation);
                continue;
            }
            if(deviation.gs_title.includes(currentsearchtext)) {
                resultsarray.push(deviation);
                continue;
            }
        }

        matchingdeviations = resultsarray;

        //Now display based on results
        if(resultsarray.length == 0) {
            searchoutputtext.innerHTML = "No results.";
            return;
        }

        //DEBUG
        if(debug && true) console.log("Results:", matchingdeviations);

        if(resultsarray.length > maximumsearchreturn) {
            searchoutputtext.innerHTML = resultsarray.length + " results. Too many for display. (Maximum is set to " + maximumsearchreturn + " results)";
            return;
        }

        searchoutputtext.innerHTML = resultsarray.length + " result" + (resultsarray.length == 1 ? "." : "s.");

        assignTileDimensions();
        tileElements();
    }

//This function matches row widths, row height preference, and image aspect ratios to tile items evenly in every row.
    function assignTileDimensions() {
        let viewportwidth = parseFloat(window.getComputedStyle(itemscontainer).width);

        //DEBUG
        if(debug && false) console.log("Viewport width: ", viewportwidth);

        //begin tiling rows
        let useditemscount = 0;
        while(useditemscount < matchingdeviations.length)
        {
            let currentindex = useditemscount;

            //discover row item count based on pivotal row height	
            let rowitemcount = 0;
            let currenttotalaspectedwidth = 0;
            let isfinalrow = false;
            while(currentindex < matchingdeviations.length)
            {	
                let croppedwidth  = matchingdeviations[currentindex].gs_thumb_width;
                let croppedheight = matchingdeviations[currentindex].gs_thumb_height;

                let aspectedwidth = getAspectedWidth(croppedwidth, croppedheight, pivotalrowheight);

                currenttotalaspectedwidth += aspectedwidth + (tilemargin*2);

                if(currenttotalaspectedwidth >= viewportwidth)
                {
                    break;
                }
                else
                {
                    rowitemcount++;
                    currentindex++;
                }

                //last row case detector
                if(currentindex == matchingdeviations.length)
                {
                    isfinalrow = true;
                }
            }


            //now we have row item count, size to fill usable viewport width and then aspect for final row height
            //console.log("Row item count: ", rowitemcount);

            var useableviewportwidth = viewportwidth - (rowitemcount*tilemargin*2);
            //console.log("Useable viewport width: ", useableviewportwidth);

            currentindex = useditemscount;//reset to beginning of row index

            if(!isfinalrow)//perform sizing calculations on every row but the last
            {
                //get width ratio denominator
                var sumwidthratios = 0;
                for(let i=0; i<rowitemcount; i++)
                {
                    let croppedwidth  = matchingdeviations[currentindex + i].gs_thumb_width;
                    let croppedheight = matchingdeviations[currentindex + i].gs_thumb_height;

                    sumwidthratios += croppedwidth/croppedheight;
                }

                //set final widths using width ratio percentage of total summed width ratios, set final height one time off of final width	
                let finalheight = 0;
                let remainder = 0;//pass on unused pixel space to the next element
                for(let i=0; i<rowitemcount; i++)
                {
                    let currenttiledeviation = matchingdeviations[currentindex + i];

                    let croppedwidth  = matchingdeviations[currentindex + i].gs_thumb_width;
                    let croppedheight = matchingdeviations[currentindex + i].gs_thumb_height;

                    let currentwidthratio = croppedwidth/croppedheight;

                    let exactfinalwidth = (currentwidthratio * useableviewportwidth / sumwidthratios) + remainder;
                    let finalwidth = Math.floor(exactfinalwidth);
                    remainder = exactfinalwidth - finalwidth;

                    if(i == 0)
                    {
                        finalheight = getAspectedHeight(croppedwidth, croppedheight, finalwidth);
                    }

                    //The current tile widths will change every time the viewport size changes or the search results change
                    currenttiledeviation.gs_tile_width = finalwidth + "px";
                    currenttiledeviation.gs_tile_height = finalheight + "px";
                }

                useditemscount += rowitemcount;
            }
            else//final row, size based off of simple pivotal row height
            {
                let remainder = 0;//pass on unused pixel space to the next element
                let finalheight = 0;
                for(let i=0; i<rowitemcount; i++)
                {
                    let currenttiledeviation = matchingdeviations[currentindex + i];

                    let croppedwidth  = matchingdeviations[currentindex + i].gs_thumb_width;
                    let croppedheight = matchingdeviations[currentindex + i].gs_thumb_height;


                    let exactfinalwidth = getAspectedWidth(croppedwidth, croppedheight, pivotalrowheight);
                    let finalwidth = Math.floor(exactfinalwidth);
                    remainder = exactfinalwidth - finalwidth;

                    if(i == 0)
                    {
                        finalheight = getAspectedHeight(croppedwidth, croppedheight, finalwidth);
                    }

                    //The current tile widths will change every time the viewport size changes or the search results change
                    currenttiledeviation.gs_tile_width= finalwidth + "px";
                    currenttiledeviation.gs_tile_height= finalheight + "px";
                }

                useditemscount += rowitemcount;
            }

            //DEBUG
            //break;
        }

    }

    function tileElements() {
        //Reset search items container
        searchitemscontainer.innerHTML = "";

        for(let i=0; i<matchingdeviations.length; i++) {
            let currenttileelement = createDeviationElement(matchingdeviations[i], 300, 300)
            searchitemscontainer.appendChild(currenttileelement);
        }
    }

    function createDeviationElement(deviation) {
        //TODO rewrite create functions to use gs_width and gs_height as assigned in assignTileDimensions()

        if(deviation.type == "literature") {
            return createLiteratureDeviationElement(deviation);
        }
        else if(deviation.type == "journal" || deviation.type == "status") {
            return createJournalDeviationElement(deviation);
        }
        else {// image type and other types 

            //DEBUG
            if(debug && true) {
                if(deviation.type != "image" && deviation.type != "pdf" && deviation.type != "film")
                    console.log(deviation.type);
            }
            
            return createImageDeviationElement(deviation);
        }

        
    }

    function createImageDeviationElement(deviation) {
        let outermostdiv = document.createElement("div");
        outermostdiv.style.width = deviation.gs_tile_width;
        outermostdiv.style.height = deviation.gs_tile_height;
        outermostdiv.style.display = "inline-block";
        outermostdiv.style.float = "left";
        outermostdiv.style.position = "relative";
        outermostdiv.style.margin = tilemargin + "px";

        //construct thumbnail url
        let thumbpathstring = deviation.gs_thumb_path_string;
        thumbpathstring = thumbpathstring.replace("<prettyName>", deviation.media.prettyName);

        let tokenstring = deviation.media.token == null ? "" : "?token=" + deviation.media.token[0];

        let thumburl = deviation.media.baseUri + thumbpathstring + tokenstring;

        //Create Image DONE
        let imgelement = document.createElement("img");
        imgelement.alt = deviation.title;
        imgelement.src = thumburl;
        imgelement.style.width = "100%";
        imgelement.style.height = "100%";
        imgelement.style.objectFit = "cover";
        imgelement.style.objectPosition = "50% 100%";

        //Image container Done
        let imgcontainer = document.createElement("div")
        imgcontainer.className = "_24Wda";
        imgcontainer.style.width = "100%";
        imgcontainer.style.height = "100%";

        //Create link to deviation page Done
        let artlinkelement = document.createElement("a")
        artlinkelement.href = deviation.url;

        //Draggable div container, also contains mouseover event assigned later
        let draggablecontainer = document.createElement("div")
        draggablecontainer.style.width = "100%";
        draggablecontainer.style.height = "100%";
        draggablecontainer.className = "_1xcj5 _1QdgI";
        draggablecontainer.draggable = "true";

        imgcontainer.appendChild(imgelement);
        artlinkelement.appendChild(imgcontainer);
        draggablecontainer.appendChild(artlinkelement);
        outermostdiv.appendChild(draggablecontainer);

        //////////////////////////////////
        //Mouseover elements section
        //////////////////////////////////

        //Outer mouseover element
        let outermousediv = document.createElement("div");
        outermousediv.style.visibility = "hidden";
        outermousediv.style.width = "100%";
        outermousediv.style.height = "100%";
        outermousediv.className = "_2jPGh _3Cax3";
        //event attaches to its parent
        draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; });
        draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; });

        //Divs for slight black fade on hover
        let blackfadeouterdiv = document.createElement("div");
        blackfadeouterdiv.className = "_1mmGw";
        let blackfadeinnerdiv = document.createElement("div");
        blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8";

        blackfadeouterdiv.appendChild(blackfadeinnerdiv);
        outermousediv.appendChild(blackfadeouterdiv);

        //Next inward div
        let innermousediv = document.createElement("div");
        innermousediv.style.width = "100%";
        innermousediv.style.height = "100%";
        innermousediv.className = "_2ehf4 YpNhf";

        //Div for all all meta elements container (title, artist, icon, comments)
        let metaelementscontainer = document.createElement("div");
        metaelementscontainer.className = "_5Xty_";

        //Div to contain title and artist elements (title, artist, icon)
        let titleartistcontainer = document.createElement("div");
        titleartistcontainer.className = "_1mmGw _31MCr";

        //Link with Title
        let arttitlelinkelement = document.createElement("a")
        arttitlelinkelement.href = deviation.url;
        arttitlelinkelement.className = "KoW6A";

        //Title
        let arttitleelement = document.createElement("h2")
        arttitleelement.className = "_1lmpZ";
        arttitleelement.innerHTML = deviation.title;


        arttitlelinkelement.appendChild(arttitleelement);
        titleartistcontainer.appendChild(arttitlelinkelement);
        metaelementscontainer.appendChild(titleartistcontainer);
        innermousediv.appendChild(metaelementscontainer);
        outermousediv.appendChild(innermousediv);
        draggablecontainer.appendChild(outermousediv);


        //Subsection for artist icon and name

        //Divs to contain title and artist elements (title, artist, icon)
        let iconartistcontainer = document.createElement("div");
        iconartistcontainer.className = "_13y-9";
        let iconartistcontainer2 = document.createElement("div");
        iconartistcontainer2.className = "_2o1Q1";

        //Div to contain artist icon
        let iconcontainer = document.createElement("div");
        iconcontainer.className = "_3CR67 _1I9Ar";

        //Link for artist that surrounds icon
        let artisticonlinkelement = document.createElement("a")
        artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artisticonlinkelement.className = "user-link _2f0dA _23x0l";

        //Artist icon element
        let artisticonelement = document.createElement("img");
        artisticonelement.alt = deviation.author.username + "'s avatar";
        artisticonelement.src = deviation.author.usericon;
        artisticonelement.style.width = "24px";
        artisticonelement.style.height = "24px";
        artisticonelement.loading = "lazy";
        artisticonelement.className = "_1IDJa";

        artisticonlinkelement.appendChild(artisticonelement);
        iconcontainer.appendChild(artisticonlinkelement);

        //Div to contain artist name
        let artistnamecontainer = document.createElement("div");
        artistnamecontainer.className = "_3CR67 k4CiA";

        //Link for artist that surrounds icon
        let artistnamelinkelement = document.createElement("a")
        artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artistnamelinkelement.className = "user-link _2f0dA";

        //Artist name text span
        let artistnametextelement = document.createElement("span")
        artistnametextelement.className = "_2UI2c";
        artistnametextelement.innerHTML = deviation.author.username;

        //Artist cursor span
        let artistcursorelement = document.createElement("span")
        artistcursorelement.className = "_3LUMH _1NhtS G0rcN";
        artistcursorelement.style.cursor = "pointer";
        artistcursorelement.role = "img";

        artistnamelinkelement.appendChild(artistnametextelement);
        artistnamelinkelement.appendChild(artistcursorelement);
        artistnamecontainer.appendChild(artistnamelinkelement);

        iconartistcontainer2.appendChild(iconcontainer);
        iconartistcontainer2.appendChild(artistnamecontainer);
        iconartistcontainer.appendChild(iconartistcontainer2);
        titleartistcontainer.appendChild(iconartistcontainer);

        //Subsection for comments icon and link elements

        //Div to contain comment icon and link elements
        let commenticonlinkcontainer = document.createElement("div");
        commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd";

        //Link for comment section
        let commentlinkelement = document.createElement("a")
        commentlinkelement.href = deviation.url + "#comments";
        commentlinkelement.className = "_1-Wh7 x48yz";

        //Comment icon span
        let commenticonspan = document.createElement("span")
        commenticonspan.className = "z8jNZ _1yoxj _38kc5";

        //Comment icon (SVG)
        let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        commenticonSVG.setAttribute("viewBox", "0 0 24 24");
        commenticonSVG.setAttribute("version", "1.1");
        commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink");

        //Comment icon (SVG PATH)
        let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
        commenticonpath.setAttribute("fill-rule", "evenodd");
        commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" 
            + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" 
            + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z");

        //Comment count span
        let commentcountspan = document.createElement("span")
        commentcountspan.innerHTML = deviation.stats.comments;

        commenticonSVG.appendChild(commenticonpath);
        commenticonspan.appendChild(commenticonSVG);
        commentlinkelement.appendChild(commenticonspan);
        commentlinkelement.appendChild(commentcountspan);
        commenticonlinkcontainer.appendChild(commentlinkelement);
        metaelementscontainer.appendChild(commenticonlinkcontainer);

        return outermostdiv;
    }

    function createLiteratureDeviationElement(deviation) {
        let outermostdiv = document.createElement("div");
        outermostdiv.style.width = deviation.gs_tile_width;
        outermostdiv.style.height = deviation.gs_tile_height;
        outermostdiv.style.display = "inline-block";
        outermostdiv.style.float = "left";
        outermostdiv.style.position = "relative";
        outermostdiv.style.margin = tilemargin + "px";

        //Draggable div container, also contains mouseover event assigned later
        let draggablecontainer = document.createElement("div")
        draggablecontainer.style.width = "100%";
        draggablecontainer.style.height = "100%";
        draggablecontainer.className = "_1xcj5 _1QdgI";
        draggablecontainer.draggable = "true";

        //Section element that contains literature display elements
        let sectionelement = document.createElement("section");
        sectionelement.className = "_33VtO _3rqZq";
        sectionelement.style.width = "100%";
        sectionelement.style.height = "100%";
        
        //Fancy literature deviation background element container
        let literaturebgcontainer = document.createElement("div")
        literaturebgcontainer.className = "xPxyA LXVwg";

        //Fancy literature deviation background (SVG)
        let litbgSVG = document.createElement("svg");
        litbgSVG.setAttribute("viewBox", "0 0 15 12");
        litbgSVG.setAttribute("height", "100%");
        litbgSVG.setAttribute("preserveAspectRatio", "xMidYMin slice");
        litbgSVG.setAttribute("fill-rule", "evenodd");

        //Fancy literature deviation background (lineargradient)
        let litbglineargradient = document.createElement("linearGradient");
        litbglineargradient.setAttribute("x1", "87.8481761%");
        litbglineargradient.setAttribute("y1", "16.3690766%");
        litbglineargradient.setAttribute("x2", "45.4107524%");
        litbglineargradient.setAttribute("y2", "71.4898596%");

        //Fancy literature deviation background (stop color)s
        let litbgstop1 = document.createElement("stop");
        litbgstop1.setAttribute("stop-color", "#00FF62");
        litbgstop1.setAttribute("offset", "0%");
        let litbgstop2 = document.createElement("stop");
        litbgstop2.setAttribute("stop-color", "#3197EF");
        litbgstop2.setAttribute("offset", "100%");
        litbgstop2.setAttribute("stop-opacity", "0");

        litbglineargradient.appendChild(litbgstop1);
        litbglineargradient.appendChild(litbgstop2);
        litbgSVG.appendChild(litbglineargradient);
        literaturebgcontainer.appendChild(litbgSVG);
        sectionelement.appendChild(literaturebgcontainer);
        draggablecontainer.appendChild(sectionelement);
        outermostdiv.appendChild(draggablecontainer);

        //Literature Preview Subsection

        //Fancy literature deviation background element container
        let literaturecategorydiv = document.createElement("div")
        literaturecategorydiv.className = "_3hLq8";
        literaturecategorydiv.innerHTML = "Literature";
        //Literature Preview Title
        let literaturepreviewtitle = document.createElement("h2")
        literaturepreviewtitle.className = "_2mwJN";
        literaturepreviewtitle.innerHTML = deviation.title;
        //Literature Preview Text
        let literaturepreviewtext = document.createElement("h2")
        literaturepreviewtext.className = "heXvc";
        literaturepreviewtext.innerHTML = deviation.textContent.excerpt;

        sectionelement.appendChild(literaturecategorydiv);
        sectionelement.appendChild(literaturepreviewtitle);
        sectionelement.appendChild(literaturepreviewtext);

        //////////////////////////////////
        //Mouseover elements section
        //////////////////////////////////

        //Outer mouseover element
        let outermousediv = document.createElement("div");
        outermousediv.style.visibility = "hidden";
        outermousediv.style.width = "100%";
        outermousediv.style.height = "100%";
        outermousediv.className = "_2jPGh _3Cax3";
        //event attaches to its parent
        draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; });
        draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; });

        //Divs for slight black fade on hover
        let blackfadeouterdiv = document.createElement("div");
        blackfadeouterdiv.className = "_1mmGw";
        let blackfadeinnerdiv = document.createElement("div");
        blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8";

        blackfadeouterdiv.appendChild(blackfadeinnerdiv);
        outermousediv.appendChild(blackfadeouterdiv);

        //Next inward div
        let innermousediv = document.createElement("div");
        innermousediv.style.width = "100%";
        innermousediv.style.height = "100%";
        innermousediv.className = "_2ehf4 YpNhf";

        //Div for all all meta elements container (title, artist, icon, comments)
        let metaelementscontainer = document.createElement("div");
        metaelementscontainer.className = "_5Xty_";

        //Div to contain title and artist elements (title, artist, icon)
        let titleartistcontainer = document.createElement("div");
        titleartistcontainer.className = "_1mmGw _31MCr";

        // //Link with Title
        // let arttitlelinkelement = document.createElement("a")
        // arttitlelinkelement.href = deviation.url;
        // arttitlelinkelement.className = "KoW6A";

        // //Title
        // let arttitleelement = document.createElement("h2")
        // arttitleelement.className = "_1lmpZ";
        // arttitleelement.innerHTML = deviation.title;


        // arttitlelinkelement.appendChild(arttitleelement);
        //titleartistcontainer.appendChild(arttitlelinkelement);
        metaelementscontainer.appendChild(titleartistcontainer);
        innermousediv.appendChild(metaelementscontainer);
        outermousediv.appendChild(innermousediv);
        draggablecontainer.appendChild(outermousediv);


        //Subsection for artist icon and name

        //Divs to contain title and artist elements (title, artist, icon)
        let iconartistcontainer = document.createElement("div");
        iconartistcontainer.className = "_13y-9";
        let iconartistcontainer2 = document.createElement("div");
        iconartistcontainer2.className = "_2o1Q1";

        //Div to contain artist icon
        let iconcontainer = document.createElement("div");
        iconcontainer.className = "_3CR67 _1I9Ar";

        //Link for artist that surrounds icon
        let artisticonlinkelement = document.createElement("a")
        artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artisticonlinkelement.className = "user-link _2f0dA _23x0l";

        //Artist icon element
        let artisticonelement = document.createElement("img");
        artisticonelement.alt = deviation.author.username + "'s avatar";
        artisticonelement.src = deviation.author.usericon;
        artisticonelement.style.width = "24px";
        artisticonelement.style.height = "24px";
        artisticonelement.loading = "lazy";
        artisticonelement.className = "_1IDJa";

        artisticonlinkelement.appendChild(artisticonelement);
        iconcontainer.appendChild(artisticonlinkelement);

        //Div to contain artist name
        let artistnamecontainer = document.createElement("div");
        artistnamecontainer.className = "_3CR67 k4CiA";

        //Link for artist that surrounds icon
        let artistnamelinkelement = document.createElement("a")
        artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artistnamelinkelement.className = "user-link _2f0dA";

        //Artist name text span
        let artistnametextelement = document.createElement("span")
        artistnametextelement.className = "_2UI2c";
        artistnametextelement.innerHTML = deviation.author.username;

        //Artist cursor span
        let artistcursorelement = document.createElement("span")
        artistcursorelement.className = "_3LUMH _1NhtS G0rcN";
        artistcursorelement.style.cursor = "pointer";
        artistcursorelement.role = "img";

        artistnamelinkelement.appendChild(artistnametextelement);
        artistnamelinkelement.appendChild(artistcursorelement);
        artistnamecontainer.appendChild(artistnamelinkelement);

        iconartistcontainer2.appendChild(iconcontainer);
        iconartistcontainer2.appendChild(artistnamecontainer);
        iconartistcontainer.appendChild(iconartistcontainer2);
        titleartistcontainer.appendChild(iconartistcontainer);

        //Subsection for comments icon and link elements

        //Div to contain comment icon and link elements
        let commenticonlinkcontainer = document.createElement("div");
        commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd";

        //Link for comment section
        let commentlinkelement = document.createElement("a")
        commentlinkelement.href = deviation.url + "#comments";
        commentlinkelement.className = "_1-Wh7 x48yz";

        //Comment icon span
        let commenticonspan = document.createElement("span")
        commenticonspan.className = "z8jNZ _1yoxj _38kc5";

        //Comment icon (SVG)
        let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        commenticonSVG.setAttribute("viewBox", "0 0 24 24");
        commenticonSVG.setAttribute("version", "1.1");
        commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink");

        //Comment icon (SVG PATH)
        let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
        commenticonpath.setAttribute("fill-rule", "evenodd");
        commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" 
            + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" 
            + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z");

        //Comment count span
        let commentcountspan = document.createElement("span")
        commentcountspan.innerHTML = deviation.stats.comments;

        commenticonSVG.appendChild(commenticonpath);
        commenticonspan.appendChild(commenticonSVG);
        commentlinkelement.appendChild(commenticonspan);
        commentlinkelement.appendChild(commentcountspan);
        commenticonlinkcontainer.appendChild(commentlinkelement);
        metaelementscontainer.appendChild(commenticonlinkcontainer);

        return outermostdiv;
    }

    function createJournalDeviationElement(deviation) {
        let outermostdiv = document.createElement("div");
        outermostdiv.style.width = deviation.gs_tile_width;
        outermostdiv.style.height = deviation.gs_tile_height;
        outermostdiv.style.display = "inline-block";
        outermostdiv.style.float = "left";
        outermostdiv.style.position = "relative";
        outermostdiv.style.margin = tilemargin + "px";

        //Draggable div container, also contains mouseover event assigned later
        let draggablecontainer = document.createElement("div")
        draggablecontainer.style.width = "100%";
        draggablecontainer.style.height = "100%";
        draggablecontainer.className = "_1xcj5 _1QdgI";
        draggablecontainer.draggable = "true";

        //Section element that contains journal display elements
        let sectionelement = document.createElement("section");
        sectionelement.className = "_1C7DQ _1L6MH";
        sectionelement.style.width = "100%";
        sectionelement.style.height = "100%";
        
        //Journal title container
        let journaltitlecontainer = document.createElement("div")
        journaltitlecontainer.className = "_1i4Yb";

        //Journal Title
        let journaltitle = document.createElement("h2")
        journaltitle.className = "mhmhR";
        journaltitle.innerHTML = deviation.title;

        journaltitlecontainer.appendChild(journaltitle);
        sectionelement.appendChild(journaltitlecontainer);
        draggablecontainer.appendChild(sectionelement);
        outermostdiv.appendChild(draggablecontainer);

        //Journal Excerpt subsection

        //Journal preview container
        let journalpreviewcontainer = document.createElement("div")
        journalpreviewcontainer.className = "_2Hfrr";

        //Journal datetime container
        let journaldtcontainer = document.createElement("div")
        journaldtcontainer.className = "uBAbQ";

        //Journal datetime, also get the correct date format. Example: Dec 14, 2015
        let journaldt = document.createElement("time")
        journaldt.dateTime = deviation.publishedTime;
        let date = new Date(deviation.publishedTime);
        let formattedDate = date.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric'});
        journaldt.innerHTML = formattedDate;

        //Journal excerpt div
        let journalexcerptdiv = document.createElement("div")
        journalexcerptdiv.className = "legacy-journal _2HUtS";
        journalexcerptdiv.innerHTML = deviation.textContent.excerpt;

        journaldtcontainer.appendChild(journaldt);
        journalpreviewcontainer.appendChild(journaldtcontainer);
        journalpreviewcontainer.appendChild(journalexcerptdiv);
        sectionelement.appendChild(journalpreviewcontainer);


        //////////////////////////////////
        //Mouseover elements section
        //////////////////////////////////

        //Outer mouseover element
        let outermousediv = document.createElement("div");
        outermousediv.style.visibility = "hidden";
        outermousediv.style.width = "100%";
        outermousediv.style.height = "100%";
        outermousediv.className = "_2jPGh _3Cax3";
        //event attaches to its parent
        draggablecontainer.addEventListener("mouseover", (event) => { outermousediv.style.visibility = ""; });
        draggablecontainer.addEventListener("mouseout", (event) => { outermousediv.style.visibility = "hidden"; });

        //Divs for slight black fade on hover
        let blackfadeouterdiv = document.createElement("div");
        blackfadeouterdiv.className = "_1mmGw";
        let blackfadeinnerdiv = document.createElement("div");
        blackfadeinnerdiv.className = "cjZ9o _2QZ8F _3b-i8";

        blackfadeouterdiv.appendChild(blackfadeinnerdiv);
        outermousediv.appendChild(blackfadeouterdiv);

        //Next inward div
        let innermousediv = document.createElement("div");
        innermousediv.style.width = "100%";
        innermousediv.style.height = "100%";
        innermousediv.className = "_2ehf4 YpNhf";

        //Div for all all meta elements container (title, artist, icon, comments)
        let metaelementscontainer = document.createElement("div");
        metaelementscontainer.className = "_5Xty_";

        //Div to contain title and artist elements (title, artist, icon)
        let titleartistcontainer = document.createElement("div");
        titleartistcontainer.className = "_1mmGw _31MCr";

        // arttitlelinkelement.appendChild(arttitleelement);
        //titleartistcontainer.appendChild(arttitlelinkelement);
        metaelementscontainer.appendChild(titleartistcontainer);
        innermousediv.appendChild(metaelementscontainer);
        outermousediv.appendChild(innermousediv);
        draggablecontainer.appendChild(outermousediv);


        //Subsection for artist icon and name

        //Divs to contain title and artist elements (title, artist, icon)
        let iconartistcontainer = document.createElement("div");
        iconartistcontainer.className = "_13y-9";
        let iconartistcontainer2 = document.createElement("div");
        iconartistcontainer2.className = "_2o1Q1";

        //Div to contain artist icon
        let iconcontainer = document.createElement("div");
        iconcontainer.className = "_3CR67 _1I9Ar";

        //Link for artist that surrounds icon
        let artisticonlinkelement = document.createElement("a")
        artisticonlinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artisticonlinkelement.className = "user-link _2f0dA _23x0l";

        //Artist icon element
        let artisticonelement = document.createElement("img");
        artisticonelement.alt = deviation.author.username + "'s avatar";
        artisticonelement.src = deviation.author.usericon;
        artisticonelement.style.width = "24px";
        artisticonelement.style.height = "24px";
        artisticonelement.loading = "lazy";
        artisticonelement.className = "_1IDJa";

        artisticonlinkelement.appendChild(artisticonelement);
        iconcontainer.appendChild(artisticonlinkelement);

        //Div to contain artist name
        let artistnamecontainer = document.createElement("div");
        artistnamecontainer.className = "_3CR67 k4CiA";

        //Link for artist that surrounds icon
        let artistnamelinkelement = document.createElement("a")
        artistnamelinkelement.href = "https://www.deviantart.com/" + deviation.gs_username;
        artistnamelinkelement.className = "user-link _2f0dA";

        //Artist name text span
        let artistnametextelement = document.createElement("span")
        artistnametextelement.className = "_2UI2c";
        artistnametextelement.innerHTML = deviation.author.username;

        //Artist cursor span
        let artistcursorelement = document.createElement("span")
        artistcursorelement.className = "_3LUMH _1NhtS G0rcN";
        artistcursorelement.style.cursor = "pointer";
        artistcursorelement.role = "img";

        artistnamelinkelement.appendChild(artistnametextelement);
        artistnamelinkelement.appendChild(artistcursorelement);
        artistnamecontainer.appendChild(artistnamelinkelement);

        iconartistcontainer2.appendChild(iconcontainer);
        iconartistcontainer2.appendChild(artistnamecontainer);
        iconartistcontainer.appendChild(iconartistcontainer2);
        titleartistcontainer.appendChild(iconartistcontainer);

        //Subsection for comments icon and link elements

        //Div to contain comment icon and link elements
        let commenticonlinkcontainer = document.createElement("div");
        commenticonlinkcontainer.className = "_1mmGw _2WpJA _6oiPd";

        //Link for comment section
        let commentlinkelement = document.createElement("a")
        commentlinkelement.href = deviation.url + "#comments";
        commentlinkelement.className = "_1-Wh7 x48yz";

        //Comment icon span
        let commenticonspan = document.createElement("span")
        commenticonspan.className = "z8jNZ _1yoxj _38kc5";

        //Comment icon (SVG)
        let commenticonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg");
        commenticonSVG.setAttribute("viewBox", "0 0 24 24");
        commenticonSVG.setAttribute("version", "1.1");
        commenticonSVG.setAttribute("xlmns:xlink", "http://www.w3.org/1999/xlink");

        //Comment icon (SVG PATH)
        let commenticonpath = document.createElementNS("http://www.w3.org/2000/svg", "path");
        commenticonpath.setAttribute("fill-rule", "evenodd");
        commenticonpath.setAttribute("d", "M20 3a1 1 0 01.993.883L21 4v9.586a1 1 0 01-.206.608l-.087.099-2.414 2.414a1 1 0 01-.576.284l-.131.009H13l-2.7 3.6a1 1 0" 
            + " 01-.683.393L9.5 21H8a1 1 0 01-.993-.883L7 20v-3H4a1 1 0 01-.993-.883L3 16V6.414a1 1 0 01.206-.608l.087-.099 2.414-2.414a1 1 0 01.576-.284L6.414" 
            + " 3H20zm-1 2H6.828L5 6.828V15h4v4l3-4h5.17L19 13.17V5z");

        //Comment count span
        let commentcountspan = document.createElement("span")
        commentcountspan.innerHTML = deviation.stats.comments;

        commenticonSVG.appendChild(commenticonpath);
        commenticonspan.appendChild(commenticonSVG);
        commentlinkelement.appendChild(commenticonspan);
        commentlinkelement.appendChild(commentcountspan);
        commenticonlinkcontainer.appendChild(commentlinkelement);
        metaelementscontainer.appendChild(commenticonlinkcontainer);

        return outermostdiv;
    }

/////////////////////////////////////////////////////////////////////////////////////
/////  Other Helper Functions                                                   /////
/////////////////////////////////////////////////////////////////////////////////////

    function getAspectedWidth(origwidth, origheight, sizedheight)
    {
        return (origwidth * sizedheight) / origheight;
    }

    function getAspectedHeight(origwidth, origheight, sizedwidth)
    {
        return (origheight * sizedwidth) / origwidth;
    }

    
    //Function to sort deviationJSON based on attribute provided
    function sortDeviationByKey(array, key) {
        return array.sort(function(a, b) {
            var x = a.deviation[key]; var y = b.deviation[key];
            return x.localeCompare(y);
        });
     }
    

}

//DOM Element Mutation Observer
//Credit to Yong Wang on StackOverflow: https://stackoverflow.com/questions/5525071/how-to-wait-until-an-element-exists
function waitForElm(selector) {
    return new Promise(resolve => {
        if (document.querySelector(selector)) {
            return resolve(document.querySelector(selector));
        }

        const observer = new MutationObserver(mutations => {
            if (document.querySelector(selector)) {
                resolve(document.querySelector(selector));
                observer.disconnect();
            }
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    });
}


})();