JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

Clothing editor. Design multiple layers, move order up or down.<br>

<fieldset id='attire_create_container'>
    <legend>Create New Layer</legend>
    <span id='attire_create_info'>msg about no layers, or warn of max</span>
    <select id='attire_create_type'>...</select> 
    <button id='attire_create_btn'>Create</button>
</fieldset>

<fieldset id='attire_layers_container'>
    <legend>Layer List</legend>
    <div id='attire_layers'>Layers here...</div>
</fieldset>

<fieldset id='attire_edit_container'>
    <legend>Edit Layer</legend>
    <span id='attire_info'>Description here...</span>
    <div id='attire_inputs'>Dropdowns here...</div> 
</fieldset>

<div class='coat_container coat_full'>
    <div id='dragon_coat'>Coat layers here...</div>
    <div id='dragon_attire'>Attire layers here, in own divs...</div>
</div>

CSS

.coat_container {
    margin: 20px auto;
    position: relative;
    border: 1px solid green;
}
.coat_container img {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
.coat_full {
    width: 400px;
    height: 300px;
}

/*
* Up to 4 wearable pieces, in own divs.
* z-index is swapped, for 'layer orders'.
*//*
.z_1 { z-index: 1; }
.z_2 { z-index: 2; }
.z_3 { z-index: 3; }
.z_4 { z-index: 4; }
.z_5 { z-index: 5; }
.z_6 { z-index: 6; }
.z_7 { z-index: 7; }
.z_8 { z-index: 8; }
*/
.attire_layer {
    padding: 5px;
    border: 1px solid silver;
    display: flex;
    flex-flow: row wrap;
}

.attire_layer span {
    flex: 111;
    cursor: pointer;
}


.attire_selected {
    border: 1px solid orange;
    background: gold;
}

fieldset {
    margin: 15px auto;
}

fieldset legend {
    padding: 5px 10px;
    border: 1px solid grey;
}

.pink {filter: invert(39%) sepia(83%) saturate(666%) hue-rotate(284deg) brightness(94%) contrast(91%);}
.slate {filter: invert(60%) sepia(34%) saturate(240%) hue-rotate(184deg) brightness(92%) contrast(94%);}
.red {filter: invert(29%) sepia(89%) saturate(3528%) hue-rotate(345deg) brightness(107%) contrast(80%);}
.brown {filter: invert(54%) sepia(22%) saturate(1039%) hue-rotate(333deg) brightness(93%) contrast(90%);}
.orange {filter: invert(53%) sepia(34%) saturate(3679%) hue-rotate(1deg) brightness(96%) contrast(104%);}
.peach {filter: invert(68%) sepia(49%) saturate(449%) hue-rotate(326deg) brightness(103%) contrast(98%);}
.olive {filter: invert(71%) sepia(80%) saturate(425%) hue-rotate(21deg) brightness(90%) contrast(101%);}
.gold {filter: invert(80%) sepia(77%) saturate(5230%) hue-rotate(1deg) brightness(105%) contrast(102%);}
.green {filter: invert(58%) sepia(83%) saturate(544%) hue-rotate(65deg) brightness(94%) contrast(94%);}
.blue {filter: invert(84%) sepia(29%) saturate(5255%) hue-rotate(136deg) brightness(96%) contrast(95%);}
.teal {filter: invert(65%) sepia(79%) saturate(281%) hue-rotate(118deg)...

JavaScript

let path = "http://griffusion.elementfx.com/demo/attire/";

/*
* Each dragon has own number of slots for wearables.
*/
let dragonSlots = 4;

let terms = {
    colours: ["blue","green","red","gold","orange","peach","brown","silver","black","white","olive","pink","indigo","slate","teal"],
    
    baseTypes: ["cloak","boots","scarf"],
    
    patternTypes: ["stripes","spots","stars"],
    
    decorTypes: ["trim","ribbon","tassels"],
    
    // Contain wearable objects here.
    layers: [],
    
    lastID: 1,
    
    currentPiece: {},
    
    // Handy template to clone.
    blank: {bC: 1, bT: 1, pC: 1, pT: 0, dC: 1, dT: 0},
    
    // Deep-clone that blank wearable object.
    getBlank: function(){
        return Object.assign({}, terms.blank);
    },
    
    getColourName: function(n){
        if(n < 1 || n > terms.colours.length){
            throw "terms.getColourName(): invalid number, " + n;
        }
        return terms.colours[n - 1];
    },
    getBaseType: function(n){
        if(n < 1 || n > terms.baseTypes.length){
            throw "terms.getBaseType(): invalid number, " + n;
        }
        return terms.baseTypes[n - 1];
    },
    getPatternType: function(n){
        if(n < 1 || n > terms.patternTypes.length){
            throw "terms.getPatternType(): invalid number, " + n;
        }
        return terms.patternTypes[n - 1];
    },
    getDecorType: function(n){
        if(n < 1 || n > terms.decorTypes.length){
            throw "terms.getDecorType(): invalid number, " + n;
        }
        return terms.decorTypes[n - 1];
    },
    
    /*
    * Generate dropdown options for base type.
    * Used when creating a new layer. Call on page load.
    */
    renderCreateOptions: function(){
        let html = "";
        for(let i = 0; i < terms.baseTypes.length; i++){
            let type = terms.baseTypes[i];
            html += "<option value='" + (i+1) + "'>" + type + "</option>";
        }
        $("#attire_create_type").html(html);
   ...