code:
#main {
    height: 100vh;
    display: flex;
    flex-flow: column;
}

#main > .row {
    margin: .5rem;
}

.canvas {
    background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAG0lEQVQYV2M0Njb+v3v3bgbGd+/e/Xd1dWUAAEzpCGTyODpxAAAAAElFTkSuQmCC') repeat;
    display: inline-block;
    overflow: auto;
    font-size: 0;
}

#ViewList {
    overflow: scroll;
}

#ViewList > * {
    float: left;
    margin: 0.25rem;
}

#ViewList > .card > .card-block {
    padding: 0.5rem;
}

.LayerBadges:not(.collapse) {
    display: flex;
    flex-wrap: wrap;
}

.LayerBadges > * {
    margin: 0 .5rem;
}

.LayerListItem {
    display: flex;
    align-items: center;
}

.LayerListItem:hover {
    background: #ddeeff;
    cursor: pointer;
}

.LayerListItem *:hover {
    cursor: pointer;
}

.LayerListItem.selected {
    background: #bbddff;
}

#SelLayerCanvas {
    width: 100%;
    overflow: hidden;
}

#Palettes, #Layers {
    overflow-y: auto;
    display: flex;
    flex-flow: column nowrap;
}

.PaletteItem {
    border: 1px solid rgba(0, 0, 0, 0.5);
    border-bottom-style: none;
    padding: 0.25rem 0.25rem 0.01rem 0.25rem;
}

.PaletteItem:first-of-type {
    border-radius: 2px 2px 0 0;
}

.PaletteItem:last-of-type {
    border-radius: 0 0 2px 2px;
    border-bottom-style: solid;
}

.PaletteItem:not(.selected) .-details {
    display: none;
}

.PaletteItem:not(.selected):hover {
    background: #ddeeff;
    cursor: pointer;
}

.PaletteItem:not(.selected) :hover {
    cursor: pointer;
}

.PaletteItem:not(.selected) {
}

.PaletteItem.selected h6 {
}

.PaletteItem input {
    flex-shrink: 1;
    flex-grow: 1;
    width: 100px;
}

.-paliprop {
    display: flex;
    flex-wrap: nowrap;
    width: 200px;
}

.-paliprop > * {
}

.-palibox {
    flex-basis: 8.333%;
}

.-palilab {
    flex-basis: 33.333%;
}

.-palishort {
    flex-basis: 50%;
}

.-palilong {
    flex-basis: 66.667%;
}

.ctrl {
    width: 26px;
    height: 26px;
    padding:0;
    display: inline-flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    border: 1px solid rgba(127, 127, 127, 0.1);
    border-radius: 4px;
    cursor: pointer;
}

button.badge {
    border: none;
}

.ctrl:hover {
    border: 1px solid rgba(127, 127, 127, 0.5);
}

a.ctrl, a.ctrl:hover, a.ctrl:focus, a.ctrl:active {
    text-decoration: none;
}

.fb-1-12 { flex-basis: 8.333%; }

.fb-2-12 { flex-basis: 16.667%; }

.fb-3-12 { flex-basis: 25.000%; }

.fb-4-12 { flex-basis: 33.333%; }

.fb-5-12 { flex-basis: 41.667%; }

.fb-6-12 { flex-basis: 50.000%; }

.fb-7-12 { flex-basis: 58.333%; }

.fb-8-12 { flex-basis: 66.667%; }

.fb-9-12 { flex-basis: 75.000%; }

.fb-10-12 { flex-basis: 83.333%; }

.fb-11-12 { flex-basis: 91.667%; }

.fb-12-12 { flex-basis: 100%; }

license: unknown


code:
<!DOCTYPE html>

	<meta charset="UTF-8">
	<title>Sprite model editor</title>
	<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"
	      integrity="sha384-rwoIResjU2yc3z8GV/NPeZWAv56rSmLldC3R/AZzGRnGxQQKnKkoFVhFQhNUwEyJ" crossorigin="anonymous">
	<link rel="stylesheet" href="css/tether.min.css">
	<link rel="stylesheet" href="css/font-awesome.min.css">

	<link rel="stylesheet" href="spred.css">

CoC Sprite Editor

			<button type="button" class="btn btn-secondary" onclick="spred.addCompositeView(spred.defaultPartList())">
				 Add view
			</button>
			<button class="btn btn-secondary" onclick="spred.saveSpritemaps()">
				 Save spritemaps
			</button>
			<button class="btn btn-secondary" onclick="spred.savePalettes()">
				 Save palettes
			</button>

Filesystem inaccessible

					(Tip: you can upload multiple files at once)

Controls

Left button

					<input type="radio" name="lmb-action" id="lmba-drag" value="drag" checked>
					Drag

					<input type="radio" id="lmba-nothing" name="lmb-action" value="nothing">
					Nothing

					<input type="radio" id="lmba-erase" name="lmb-action" value="erase" disabled>
					Erase

					<input type="radio" id="lmba-keycolor" name="lmb-action" value="keycolor" disabled>
					Key color...

				<select id="lmb-color" class="form-control" disabled>
					<option value="#FF88FF">hair lighten(25)</option>
					<option value="#CC00CC">hair</option>
				</select>

<button class="ctrl" type="button" onclick="$('#Palettes').toggle()"></button> Palette

<button class="ctrl" type="button" onclick="$('#Layers').toggle()"></button> Layers

Selected:

						<button class="ctrl" type="button" onclick="spred.selPartMove(-1,0)">

						</button>
						<button class="ctrl" type="button" onclick="spred.selPartMove(1,0)">

						</button>
						<button class="ctrl" type="button" onclick="spred.selPartMove(0,-1)">

						</button>
						<button class="ctrl" type="button" onclick="spred.selPartMove(0,1)">

						</button>

Canvas:

						right-click & copy/save


code:
/*
 * Generated by CoC/DebugMenu/TS-Export
 */
var AntennaeTypes;
(function (AntennaeTypes) {
    AntennaeTypes[AntennaeTypes["NONE"] = 0] = "NONE";
    AntennaeTypes[AntennaeTypes["MANTIS"] = 1] = "MANTIS";
    AntennaeTypes[AntennaeTypes["BEE"] = 2] = "BEE";
    AntennaeTypes[AntennaeTypes["COCKATRICE"] = 3] = "COCKATRICE";
})(AntennaeTypes || (AntennaeTypes = {}));
var ArmTypes;
(function (ArmTypes) {
    ArmTypes[ArmTypes["HUMAN"] = 0] = "HUMAN";
    ArmTypes[ArmTypes["HARPY"] = 1] = "HARPY";
    ArmTypes[ArmTypes["SPIDER"] = 2] = "SPIDER";
    ArmTypes[ArmTypes["MANTIS"] = 3] = "MANTIS";
    ArmTypes[ArmTypes["BEE"] = 4] = "BEE";
    ArmTypes[ArmTypes["SALAMANDER"] = 5] = "SALAMANDER";
    ArmTypes[ArmTypes["PHOENIX"] = 6] = "PHOENIX";
    ArmTypes[ArmTypes["PLANT"] = 7] = "PLANT";
    ArmTypes[ArmTypes["SHARK"] = 8] = "SHARK";
    ArmTypes[ArmTypes["GARGOYLE"] = 9] = "GARGOYLE";
    ArmTypes[ArmTypes["WOLF"] = 10] = "WOLF";
    ArmTypes[ArmTypes["LION"] = 11] = "LION";
    ArmTypes[ArmTypes["KITSUNE"] = 12] = "KITSUNE";
    ArmTypes[ArmTypes["FOX"] = 13] = "FOX";
    ArmTypes[ArmTypes["LIZARD"] = 14] = "LIZARD";
    ArmTypes[ArmTypes["DRAGON"] = 15] = "DRAGON";
    ArmTypes[ArmTypes["YETI"] = 16] = "YETI";
    ArmTypes[ArmTypes["ORCA"] = 17] = "ORCA";
    ArmTypes[ArmTypes["PLANT2"] = 18] = "PLANT2";
    ArmTypes[ArmTypes["DEVIL"] = 19] = "DEVIL";
    ArmTypes[ArmTypes["ONI"] = 20] = "ONI";
    ArmTypes[ArmTypes["ELF"] = 21] = "ELF";
    ArmTypes[ArmTypes["RAIJU"] = 22] = "RAIJU";
    ArmTypes[ArmTypes["RED_PANDA"] = 23] = "RED_PANDA";
    ArmTypes[ArmTypes["GARGOYLE_2"] = 24] = "GARGOYLE_2";
    ArmTypes[ArmTypes["CAT"] = 25] = "CAT";
    ArmTypes[ArmTypes["AVIAN"] = 26] = "AVIAN";
    ArmTypes[ArmTypes["GRYPHON"] = 27] = "GRYPHON";
    ArmTypes[ArmTypes["SPHINX"] = 28] = "SPHINX";
})(ArmTypes || (ArmTypes = {}));
var BeardTypes;
(function (BeardTypes) {
    BeardTypes[BeardTypes["NORMAL"] = 0] = "NORMAL";
    BeardTypes[BeardTypes["GOATEE"] = 1] = "GOATEE";
    BeardTypes[BeardTypes["CLEANCUT"] = 2] = "CLEANCUT";
    BeardTypes[BeardTypes["MOUNTAINMAN"] = 3] = "MOUNTAINMAN";
})(BeardTypes || (BeardTypes = {}));
var ClawTypes;
(function (ClawTypes) {
    ClawTypes[ClawTypes["NORMAL"] = 0] = "NORMAL";
    ClawTypes[ClawTypes["LIZARD"] = 1] = "LIZARD";
    ClawTypes[ClawTypes["DRAGON"] = 2] = "DRAGON";
    ClawTypes[ClawTypes["SALAMANDER"] = 3] = "SALAMANDER";
    ClawTypes[ClawTypes["CAT"] = 4] = "CAT";
    ClawTypes[ClawTypes["DOG"] = 5] = "DOG";
    ClawTypes[ClawTypes["RAPTOR"] = 6] = "RAPTOR";
    ClawTypes[ClawTypes["MANTIS"] = 7] = "MANTIS";
    ClawTypes[ClawTypes["IMP"] = 8] = "IMP";
    ClawTypes[ClawTypes["COCKATRICE"] = 9] = "COCKATRICE";
    ClawTypes[ClawTypes["RED_PANDA"] = 10] = "RED_PANDA";
})(ClawTypes || (ClawTypes = {}));
var EarTypes;
(function (EarTypes) {
    EarTypes[EarTypes["HUMAN"] = 0] = "HUMAN";
    EarTypes[EarTypes["HORSE"] = 1] = "HORSE";
    EarTypes[EarTypes["DOG"] = 2] = "DOG";
    EarTypes[EarTypes["COW"] = 3] = "COW";
    EarTypes[EarTypes["ELFIN"] = 4] = "ELFIN";
    EarTypes[EarTypes["CAT"] = 5] = "CAT";
    EarTypes[EarTypes["LIZARD"] = 6] = "LIZARD";
    EarTypes[EarTypes["BUNNY"] = 7] = "BUNNY";
    EarTypes[EarTypes["KANGAROO"] = 8] = "KANGAROO";
    EarTypes[EarTypes["FOX"] = 9] = "FOX";
    EarTypes[EarTypes["DRAGON"] = 10] = "DRAGON";
    EarTypes[EarTypes["RACCOON"] = 11] = "RACCOON";
    EarTypes[EarTypes["MOUSE"] = 12] = "MOUSE";
    EarTypes[EarTypes["FERRET"] = 13] = "FERRET";
    EarTypes[EarTypes["PIG"] = 14] = "PIG";
    EarTypes[EarTypes["RHINO"] = 15] = "RHINO";
    EarTypes[EarTypes["ECHIDNA"] = 16] = "ECHIDNA";
    EarTypes[EarTypes["DEER"] = 17] = "DEER";
    EarTypes[EarTypes["WOLF"] = 18] = "WOLF";
    EarTypes[EarTypes["LION"] = 19] = "LION";
    EarTypes[EarTypes["YETI"] = 20] = "YETI";
    EarTypes[EarTypes["ORCA"] = 21] = "ORCA";
    EarTypes[EarTypes["SNAKE"] = 22] = "SNAKE";
    EarTypes[EarTypes["GOAT"] = 23] = "GOAT";
    EarTypes[EarTypes["ONI"] = 24] = "ONI";
    EarTypes[EarTypes["ELVEN"] = 25] = "ELVEN";
    EarTypes[EarTypes["WEASEL"] = 26] = "WEASEL";
    EarTypes[EarTypes["BAT"] = 27] = "BAT";
    EarTypes[EarTypes["VAMPIRE"] = 28] = "VAMPIRE";
    EarTypes[EarTypes["RED_PANDA"] = 29] = "RED_PANDA";
    EarTypes[EarTypes["AVIAN"] = 30] = "AVIAN";
    EarTypes[EarTypes["GRYPHON"] = 31] = "GRYPHON";
})(EarTypes || (EarTypes = {}));
var EyeTypes;
(function (EyeTypes) {
    EyeTypes[EyeTypes["HUMAN"] = 0] = "HUMAN";
    EyeTypes[EyeTypes["FOUR_SPIDER_EYES"] = 1] = "FOUR_SPIDER_EYES";
    EyeTypes[EyeTypes["BLACK_EYES_SAND_TRAP"] = 2] = "BLACK_EYES_SAND_TRAP";
    EyeTypes[EyeTypes["CAT_SLITS"] = 3] = "CAT_SLITS";
    EyeTypes[EyeTypes["GORGON"] = 4] = "GORGON";
    EyeTypes[EyeTypes["FENRIR"] = 5] = "FENRIR";
    EyeTypes[EyeTypes["MANTICORE"] = 6] = "MANTICORE";
    EyeTypes[EyeTypes["FOX"] = 7] = "FOX";
    EyeTypes[EyeTypes["REPTILIAN"] = 8] = "REPTILIAN";
    EyeTypes[EyeTypes["SNAKE"] = 9] = "SNAKE";
    EyeTypes[EyeTypes["DRAGON"] = 10] = "DRAGON";
    EyeTypes[EyeTypes["DEVIL"] = 11] = "DEVIL";
    EyeTypes[EyeTypes["ONI"] = 12] = "ONI";
    EyeTypes[EyeTypes["ELF"] = 13] = "ELF";
    EyeTypes[EyeTypes["RAIJU"] = 14] = "RAIJU";
    EyeTypes[EyeTypes["VAMPIRE"] = 15] = "VAMPIRE";
    EyeTypes[EyeTypes["GEMSTONES"] = 16] = "GEMSTONES";
    EyeTypes[EyeTypes["FERAL"] = 17] = "FERAL";
    EyeTypes[EyeTypes["GRYPHON"] = 18] = "GRYPHON";
})(EyeTypes || (EyeTypes = {}));
var FaceTypes;
(function (FaceTypes) {
    FaceTypes[FaceTypes["HUMAN"] = 0] = "HUMAN";
    FaceTypes[FaceTypes["HORSE"] = 1] = "HORSE";
    FaceTypes[FaceTypes["DOG"] = 2] = "DOG";
    FaceTypes[FaceTypes["COW_MINOTAUR"] = 3] = "COW_MINOTAUR";
    FaceTypes[FaceTypes["SHARK_TEETH"] = 4] = "SHARK_TEETH";
    FaceTypes[FaceTypes["SNAKE_FANGS"] = 5] = "SNAKE_FANGS";
    FaceTypes[FaceTypes["CAT"] = 6] = "CAT";
    FaceTypes[FaceTypes["LIZARD"] = 7] = "LIZARD";
    FaceTypes[FaceTypes["BUNNY"] = 8] = "BUNNY";
    FaceTypes[FaceTypes["KANGAROO"] = 9] = "KANGAROO";
    FaceTypes[FaceTypes["SPIDER_FANGS"] = 10] = "SPIDER_FANGS";
    FaceTypes[FaceTypes["FOX"] = 11] = "FOX";
    FaceTypes[FaceTypes["DRAGON"] = 12] = "DRAGON";
    FaceTypes[FaceTypes["RACCOON_MASK"] = 13] = "RACCOON_MASK";
    FaceTypes[FaceTypes["RACCOON"] = 14] = "RACCOON";
    FaceTypes[FaceTypes["BUCKTEETH"] = 15] = "BUCKTEETH";
    FaceTypes[FaceTypes["MOUSE"] = 16] = "MOUSE";
    FaceTypes[FaceTypes["FERRET_MASK"] = 17] = "FERRET_MASK";
    FaceTypes[FaceTypes["FERRET"] = 18] = "FERRET";
    FaceTypes[FaceTypes["PIG"] = 19] = "PIG";
    FaceTypes[FaceTypes["BOAR"] = 20] = "BOAR";
    FaceTypes[FaceTypes["RHINO"] = 21] = "RHINO";
    FaceTypes[FaceTypes["ECHIDNA"] = 22] = "ECHIDNA";
    FaceTypes[FaceTypes["DEER"] = 23] = "DEER";
    FaceTypes[FaceTypes["WOLF"] = 24] = "WOLF";
    FaceTypes[FaceTypes["MANTICORE"] = 25] = "MANTICORE";
    FaceTypes[FaceTypes["SALAMANDER_FANGS"] = 26] = "SALAMANDER_FANGS";
    FaceTypes[FaceTypes["YETI_FANGS"] = 27] = "YETI_FANGS";
    FaceTypes[FaceTypes["ORCA"] = 28] = "ORCA";
    FaceTypes[FaceTypes["PLANT_DRAGON"] = 29] = "PLANT_DRAGON";
    FaceTypes[FaceTypes["DRAGON_FANGS"] = 30] = "DRAGON_FANGS";
    FaceTypes[FaceTypes["DEVIL_FANGS"] = 31] = "DEVIL_FANGS";
    FaceTypes[FaceTypes["ONI_TEETH"] = 32] = "ONI_TEETH";
    FaceTypes[FaceTypes["RAIJU_FANGS"] = 33] = "RAIJU_FANGS";
    FaceTypes[FaceTypes["VAMPIRE"] = 34] = "VAMPIRE";
    FaceTypes[FaceTypes["BUCKTOOTH"] = 35] = "BUCKTOOTH";
    FaceTypes[FaceTypes["JABBERWOCKY"] = 36] = "JABBERWOCKY";
    FaceTypes[FaceTypes["RED_PANDA"] = 37] = "RED_PANDA";
    FaceTypes[FaceTypes["CAT_CANINES"] = 38] = "CAT_CANINES";
    FaceTypes[FaceTypes["CHESHIRE"] = 39] = "CHESHIRE";
    FaceTypes[FaceTypes["CHESHIRE_SMILE"] = 40] = "CHESHIRE_SMILE";
    FaceTypes[FaceTypes["AVIAN"] = 41] = "AVIAN";
    FaceTypes[FaceTypes["WOLF_FANGS"] = 42] = "WOLF_FANGS";
})(FaceTypes || (FaceTypes = {}));
var GenderValues;
(function (GenderValues) {
    GenderValues[GenderValues["GENDER_NONE"] = 0] = "GENDER_NONE";
    GenderValues[GenderValues["GENDER_MALE"] = 1] = "GENDER_MALE";
    GenderValues[GenderValues["GENDER_FEMALE"] = 2] = "GENDER_FEMALE";
    GenderValues[GenderValues["GENDER_HERM"] = 3] = "GENDER_HERM";
})(GenderValues || (GenderValues = {}));
var GillTypes;
(function (GillTypes) {
    GillTypes[GillTypes["NONE"] = 0] = "NONE";
    GillTypes[GillTypes["ANEMONE"] = 1] = "ANEMONE";
    GillTypes[GillTypes["FISH"] = 2] = "FISH";
    GillTypes[GillTypes["GILLS_IN_TENTACLE_LEGS"] = 3] = "GILLS_IN_TENTACLE_LEGS";
})(GillTypes || (GillTypes = {}));
var HairTypes;
(function (HairTypes) {
    HairTypes[HairTypes["NORMAL"] = 0] = "NORMAL";
    HairTypes[HairTypes["FEATHER"] = 1] = "FEATHER";
    HairTypes[HairTypes["GHOST"] = 2] = "GHOST";
    HairTypes[HairTypes["GOO"] = 3] = "GOO";
    HairTypes[HairTypes["ANEMONE"] = 4] = "ANEMONE";
    HairTypes[HairTypes["QUILL"] = 5] = "QUILL";
    HairTypes[HairTypes["GORGON"] = 6] = "GORGON";
    HairTypes[HairTypes["LEAF"] = 7] = "LEAF";
    HairTypes[HairTypes["FLUFFY"] = 8] = "FLUFFY";
    HairTypes[HairTypes["GRASS"] = 9] = "GRASS";
    HairTypes[HairTypes["SILKEN"] = 10] = "SILKEN";
    HairTypes[HairTypes["STORM"] = 11] = "STORM";
})(HairTypes || (HairTypes = {}));
var HornTypes;
(function (HornTypes) {
    HornTypes[HornTypes["NONE"] = 0] = "NONE";
    HornTypes[HornTypes["DEMON"] = 1] = "DEMON";
    HornTypes[HornTypes["COW_MINOTAUR"] = 2] = "COW_MINOTAUR";
    HornTypes[HornTypes["DRACONIC_X2"] = 3] = "DRACONIC_X2";
    HornTypes[HornTypes["DRACONIC_X4_12_INCH_LONG"] = 4] = "DRACONIC_X4_12_INCH_LONG";
    HornTypes[HornTypes["ANTLERS"] = 5] = "ANTLERS";
    HornTypes[HornTypes["GOAT"] = 6] = "GOAT";
    HornTypes[HornTypes["UNICORN"] = 7] = "UNICORN";
    HornTypes[HornTypes["RHINO"] = 8] = "RHINO";
    HornTypes[HornTypes["OAK"] = 9] = "OAK";
    HornTypes[HornTypes["GARGOYLE"] = 10] = "GARGOYLE";
    HornTypes[HornTypes["ORCHID"] = 11] = "ORCHID";
    HornTypes[HornTypes["ONI_X2"] = 12] = "ONI_X2";
    HornTypes[HornTypes["ONI"] = 13] = "ONI";
    HornTypes[HornTypes["BICORN"] = 14] = "BICORN";
})(HornTypes || (HornTypes = {}));
var LowerBodyTypes;
(function (LowerBodyTypes) {
    LowerBodyTypes[LowerBodyTypes["HUMAN"] = 0] = "HUMAN";
    LowerBodyTypes[LowerBodyTypes["HOOFED"] = 1] = "HOOFED";
    LowerBodyTypes[LowerBodyTypes["DOG"] = 2] = "DOG";
    LowerBodyTypes[LowerBodyTypes["NAGA"] = 3] = "NAGA";
    LowerBodyTypes[LowerBodyTypes["CENTAUR"] = 4] = "CENTAUR";
    LowerBodyTypes[LowerBodyTypes["DEMONIC_HIGH_HEELS"] = 5] = "DEMONIC_HIGH_HEELS";
    LowerBodyTypes[LowerBodyTypes["DEMONIC_CLAWS"] = 6] = "DEMONIC_CLAWS";
    LowerBodyTypes[LowerBodyTypes["BEE"] = 7] = "BEE";
    LowerBodyTypes[LowerBodyTypes["GOO"] = 8] = "GOO";
    LowerBodyTypes[LowerBodyTypes["CAT"] = 9] = "CAT";
    LowerBodyTypes[LowerBodyTypes["LIZARD"] = 10] = "LIZARD";
    LowerBodyTypes[LowerBodyTypes["PONY"] = 11] = "PONY";
    LowerBodyTypes[LowerBodyTypes["BUNNY"] = 12] = "BUNNY";
    LowerBodyTypes[LowerBodyTypes["HARPY"] = 13] = "HARPY";
    LowerBodyTypes[LowerBodyTypes["KANGAROO"] = 14] = "KANGAROO";
    LowerBodyTypes[LowerBodyTypes["CHITINOUS_SPIDER_LEGS"] = 15] = "CHITINOUS_SPIDER_LEGS";
    LowerBodyTypes[LowerBodyTypes["DRIDER"] = 16] = "DRIDER";
    LowerBodyTypes[LowerBodyTypes["FOX"] = 17] = "FOX";
    LowerBodyTypes[LowerBodyTypes["DRAGON"] = 18] = "DRAGON";
    LowerBodyTypes[LowerBodyTypes["RACCOON"] = 19] = "RACCOON";
    LowerBodyTypes[LowerBodyTypes["FERRET"] = 20] = "FERRET";
    LowerBodyTypes[LowerBodyTypes["CLOVEN_HOOFED"] = 21] = "CLOVEN_HOOFED";
    LowerBodyTypes[LowerBodyTypes["ECHIDNA"] = 23] = "ECHIDNA";
    LowerBodyTypes[LowerBodyTypes["DEERTAUR"] = 24] = "DEERTAUR";
    LowerBodyTypes[LowerBodyTypes["SALAMANDER"] = 25] = "SALAMANDER";
    LowerBodyTypes[LowerBodyTypes["SCYLLA"] = 26] = "SCYLLA";
    LowerBodyTypes[LowerBodyTypes["MANTIS"] = 27] = "MANTIS";
    LowerBodyTypes[LowerBodyTypes["SHARK"] = 29] = "SHARK";
    LowerBodyTypes[LowerBodyTypes["GARGOYLE"] = 30] = "GARGOYLE";
    LowerBodyTypes[LowerBodyTypes["PLANT_HIGH_HEELS"] = 31] = "PLANT_HIGH_HEELS";
    LowerBodyTypes[LowerBodyTypes["PLANT_ROOT_CLAWS"] = 32] = "PLANT_ROOT_CLAWS";
    LowerBodyTypes[LowerBodyTypes["WOLF"] = 33] = "WOLF";
    LowerBodyTypes[LowerBodyTypes["PLANT_FLOWER"] = 34] = "PLANT_FLOWER";
    LowerBodyTypes[LowerBodyTypes["LION"] = 35] = "LION";
    LowerBodyTypes[LowerBodyTypes["YETI"] = 36] = "YETI";
    LowerBodyTypes[LowerBodyTypes["ORCA"] = 37] = "ORCA";
    LowerBodyTypes[LowerBodyTypes["YGG_ROOT_CLAWS"] = 38] = "YGG_ROOT_CLAWS";
    LowerBodyTypes[LowerBodyTypes["ONI"] = 39] = "ONI";
    LowerBodyTypes[LowerBodyTypes["ELF"] = 40] = "ELF";
    LowerBodyTypes[LowerBodyTypes["RAIJU"] = 41] = "RAIJU";
    LowerBodyTypes[LowerBodyTypes["RED_PANDA"] = 42] = "RED_PANDA";
    LowerBodyTypes[LowerBodyTypes["GARGOYLE_2"] = 43] = "GARGOYLE_2";
    LowerBodyTypes[LowerBodyTypes["AVIAN"] = 44] = "AVIAN";
    LowerBodyTypes[LowerBodyTypes["GRYPHON"] = 45] = "GRYPHON";
})(LowerBodyTypes || (LowerBodyTypes = {}));
var RearBodyTypes;
(function (RearBodyTypes) {
    RearBodyTypes[RearBodyTypes["NONE"] = 0] = "NONE";
    RearBodyTypes[RearBodyTypes["DRACONIC_MANE"] = 1] = "DRACONIC_MANE";
    RearBodyTypes[RearBodyTypes["DRACONIC_SPIKES"] = 2] = "DRACONIC_SPIKES";
    RearBodyTypes[RearBodyTypes["FENRIR_ICE_SPIKES"] = 3] = "FENRIR_ICE_SPIKES";
    RearBodyTypes[RearBodyTypes["LION_MANE"] = 4] = "LION_MANE";
    RearBodyTypes[RearBodyTypes["BEHEMOTH"] = 5] = "BEHEMOTH";
    RearBodyTypes[RearBodyTypes["SHARK_FIN"] = 6] = "SHARK_FIN";
    RearBodyTypes[RearBodyTypes["ORCA_BLOWHOLE"] = 7] = "ORCA_BLOWHOLE";
    RearBodyTypes[RearBodyTypes["RAIJU_MANE"] = 8] = "RAIJU_MANE";
    RearBodyTypes[RearBodyTypes["BAT_COLLAR"] = 9] = "BAT_COLLAR";
    RearBodyTypes[RearBodyTypes["WOLF_COLLAR"] = 10] = "WOLF_COLLAR";
})(RearBodyTypes || (RearBodyTypes = {}));
var SkinPatterns;
(function (SkinPatterns) {
    SkinPatterns[SkinPatterns["PATTERN_NONE"] = 0] = "PATTERN_NONE";
    SkinPatterns[SkinPatterns["PATTERN_MAGICAL_TATTOO"] = 1] = "PATTERN_MAGICAL_TATTOO";
    SkinPatterns[SkinPatterns["PATTERN_ORCA_UNDERBODY"] = 2] = "PATTERN_ORCA_UNDERBODY";
    SkinPatterns[SkinPatterns["PATTERN_BEE_STRIPES"] = 3] = "PATTERN_BEE_STRIPES";
    SkinPatterns[SkinPatterns["PATTERN_TIGER_STRIPES"] = 4] = "PATTERN_TIGER_STRIPES";
    SkinPatterns[SkinPatterns["PATTERN_BATTLE_TATTOO"] = 5] = "PATTERN_BATTLE_TATTOO";
    SkinPatterns[SkinPatterns["PATTERN_SPOTTED"] = 6] = "PATTERN_SPOTTED";
    SkinPatterns[SkinPatterns["PATTERN_LIGHTNING_SHAPED_TATTOO"] = 7] = "PATTERN_LIGHTNING_SHAPED_TATTOO";
    SkinPatterns[SkinPatterns["PATTERN_RED_PANDA_UNDERBODY"] = 8] = "PATTERN_RED_PANDA_UNDERBODY";
})(SkinPatterns || (SkinPatterns = {}));
var SkinTypes;
(function (SkinTypes) {
    SkinTypes[SkinTypes["PLAIN"] = 0] = "PLAIN";
    SkinTypes[SkinTypes["FUR"] = 1] = "FUR";
    SkinTypes[SkinTypes["SCALES"] = 2] = "SCALES";
    SkinTypes[SkinTypes["GOO"] = 3] = "GOO";
    SkinTypes[SkinTypes["UNDEFINED"] = 4] = "UNDEFINED";
    SkinTypes[SkinTypes["CHITIN"] = 5] = "CHITIN";
    SkinTypes[SkinTypes["BARK"] = 6] = "BARK";
    SkinTypes[SkinTypes["STONE"] = 7] = "STONE";
    SkinTypes[SkinTypes["TATTOED"] = 8] = "TATTOED";
    SkinTypes[SkinTypes["AQUA_SCALES"] = 9] = "AQUA_SCALES";
    SkinTypes[SkinTypes["PARTIAL_FUR"] = 10] = "PARTIAL_FUR";
    SkinTypes[SkinTypes["PARTIAL_SCALES"] = 11] = "PARTIAL_SCALES";
    SkinTypes[SkinTypes["PARTIAL_CHITIN"] = 12] = "PARTIAL_CHITIN";
    SkinTypes[SkinTypes["PARTIAL_BARK"] = 13] = "PARTIAL_BARK";
    SkinTypes[SkinTypes["DRAGON_SCALES"] = 14] = "DRAGON_SCALES";
    SkinTypes[SkinTypes["MOSS"] = 15] = "MOSS";
    SkinTypes[SkinTypes["PARTIAL_DRAGON_SCALES"] = 16] = "PARTIAL_DRAGON_SCALES";
    SkinTypes[SkinTypes["PARTIAL_STONE"] = 17] = "PARTIAL_STONE";
    SkinTypes[SkinTypes["PARTIAL_AQUA_SCALES"] = 18] = "PARTIAL_AQUA_SCALES";
    SkinTypes[SkinTypes["AQUA_RUBBER_LIKE"] = 19] = "AQUA_RUBBER_LIKE";
    SkinTypes[SkinTypes["TATTOED_ONI"] = 20] = "TATTOED_ONI";
    SkinTypes[SkinTypes["FEATHER"] = 21] = "FEATHER";
})(SkinTypes || (SkinTypes = {}));
var TailTypes;
(function (TailTypes) {
    TailTypes[TailTypes["NONE"] = 0] = "NONE";
    TailTypes[TailTypes["HORSE"] = 1] = "HORSE";
    TailTypes[TailTypes["DOG"] = 2] = "DOG";
    TailTypes[TailTypes["DEMONIC"] = 3] = "DEMONIC";
    TailTypes[TailTypes["COW"] = 4] = "COW";
    TailTypes[TailTypes["SPIDER_ADBOMEN"] = 5] = "SPIDER_ADBOMEN";
    TailTypes[TailTypes["BEE_ABDOMEN"] = 6] = "BEE_ABDOMEN";
    TailTypes[TailTypes["SHARK"] = 7] = "SHARK";
    TailTypes[TailTypes["CAT"] = 8] = "CAT";
    TailTypes[TailTypes["LIZARD"] = 9] = "LIZARD";
    TailTypes[TailTypes["RABBIT"] = 10] = "RABBIT";
    TailTypes[TailTypes["HARPY"] = 11] = "HARPY";
    TailTypes[TailTypes["KANGAROO"] = 12] = "KANGAROO";
    TailTypes[TailTypes["FOX"] = 13] = "FOX";
    TailTypes[TailTypes["DRACONIC"] = 14] = "DRACONIC";
    TailTypes[TailTypes["RACCOON"] = 15] = "RACCOON";
    TailTypes[TailTypes["MOUSE"] = 16] = "MOUSE";
    TailTypes[TailTypes["FERRET"] = 17] = "FERRET";
    TailTypes[TailTypes["BEHEMOTH"] = 18] = "BEHEMOTH";
    TailTypes[TailTypes["PIG"] = 19] = "PIG";
    TailTypes[TailTypes["SCORPION"] = 20] = "SCORPION";
    TailTypes[TailTypes["GOAT"] = 21] = "GOAT";
    TailTypes[TailTypes["RHINO"] = 22] = "RHINO";
    TailTypes[TailTypes["ECHIDNA"] = 23] = "ECHIDNA";
    TailTypes[TailTypes["DEER"] = 24] = "DEER";
    TailTypes[TailTypes["SALAMANDER"] = 25] = "SALAMANDER";
    TailTypes[TailTypes["KITSHOO"] = 26] = "KITSHOO";
    TailTypes[TailTypes["MANTIS_ABDOMEN"] = 27] = "MANTIS_ABDOMEN";
    TailTypes[TailTypes["MANTICORE_PUSSYTAIL"] = 28] = "MANTICORE_PUSSYTAIL";
    TailTypes[TailTypes["WOLF"] = 29] = "WOLF";
    TailTypes[TailTypes["GARGOYLE"] = 30] = "GARGOYLE";
    TailTypes[TailTypes["ORCA"] = 31] = "ORCA";
    TailTypes[TailTypes["YGGDRASIL"] = 32] = "YGGDRASIL";
    TailTypes[TailTypes["RAIJU"] = 33] = "RAIJU";
    TailTypes[TailTypes["RED_PANDA"] = 34] = "RED_PANDA";
    TailTypes[TailTypes["GARGOYLE_2"] = 35] = "GARGOYLE_2";
    TailTypes[TailTypes["AVIAN"] = 36] = "AVIAN";
    TailTypes[TailTypes["GRIFFIN"] = 37] = "GRIFFIN";
    TailTypes[TailTypes["LION"] = 38] = "LION";
})(TailTypes || (TailTypes = {}));
var TongueTypes;
(function (TongueTypes) {
    TongueTypes[TongueTypes["HUMAN"] = 0] = "HUMAN";
    TongueTypes[TongueTypes["SNAKE"] = 1] = "SNAKE";
    TongueTypes[TongueTypes["DEMONIC"] = 2] = "DEMONIC";
    TongueTypes[TongueTypes["DRACONIC"] = 3] = "DRACONIC";
    TongueTypes[TongueTypes["ECHIDNA"] = 4] = "ECHIDNA";
    TongueTypes[TongueTypes["CAT"] = 5] = "CAT";
    TongueTypes[TongueTypes["ELF"] = 6] = "ELF";
    TongueTypes[TongueTypes["DOG"] = 7] = "DOG";
})(TongueTypes || (TongueTypes = {}));
var WingTypes;
(function (WingTypes) {
    WingTypes[WingTypes["NONE"] = 0] = "NONE";
    WingTypes[WingTypes["BEE_LIKE_SMALL"] = 1] = "BEE_LIKE_SMALL";
    WingTypes[WingTypes["BEE_LIKE_LARGE"] = 2] = "BEE_LIKE_LARGE";
    WingTypes[WingTypes["HARPY"] = 4] = "HARPY";
    WingTypes[WingTypes["IMP"] = 5] = "IMP";
    WingTypes[WingTypes["BAT_LIKE_TINY"] = 6] = "BAT_LIKE_TINY";
    WingTypes[WingTypes["BAT_LIKE_LARGE"] = 7] = "BAT_LIKE_LARGE";
    WingTypes[WingTypes["SHARK_FIN"] = 8] = "SHARK_FIN";
    WingTypes[WingTypes["FEATHERED_LARGE"] = 9] = "FEATHERED_LARGE";
    WingTypes[WingTypes["DRACONIC_SMALL"] = 10] = "DRACONIC_SMALL";
    WingTypes[WingTypes["DRACONIC_LARGE"] = 11] = "DRACONIC_LARGE";
    WingTypes[WingTypes["GIANT_DRAGONFLY"] = 12] = "GIANT_DRAGONFLY";
    WingTypes[WingTypes["BAT_LIKE_LARGE_2"] = 13] = "BAT_LIKE_LARGE_2";
    WingTypes[WingTypes["DRACONIC_HUGE"] = 14] = "DRACONIC_HUGE";
    WingTypes[WingTypes["FEATHERED_PHOENIX"] = 15] = "FEATHERED_PHOENIX";
    WingTypes[WingTypes["FEATHERED_ALICORN"] = 16] = "FEATHERED_ALICORN";
    WingTypes[WingTypes["MANTIS_LIKE_SMALL"] = 17] = "MANTIS_LIKE_SMALL";
    WingTypes[WingTypes["MANTIS_LIKE_LARGE"] = 18] = "MANTIS_LIKE_LARGE";
    WingTypes[WingTypes["MANTIS_LIKE_LARGE_2"] = 19] = "MANTIS_LIKE_LARGE_2";
    WingTypes[WingTypes["GARGOYLE_LIKE_LARGE"] = 20] = "GARGOYLE_LIKE_LARGE";
    WingTypes[WingTypes["PLANT"] = 21] = "PLANT";
    WingTypes[WingTypes["MANTICORE_LIKE_SMALL"] = 22] = "MANTICORE_LIKE_SMALL";
    WingTypes[WingTypes["MANTICORE_LIKE_LARGE"] = 23] = "MANTICORE_LIKE_LARGE";
    WingTypes[WingTypes["BAT_ARM"] = 24] = "BAT_ARM";
    WingTypes[WingTypes["VAMPIRE"] = 25] = "VAMPIRE";
    WingTypes[WingTypes["FEY_DRAGON_WINGS"] = 26] = "FEY_DRAGON_WINGS";
    WingTypes[WingTypes["FEATHERED_AVIAN"] = 27] = "FEATHERED_AVIAN";
    WingTypes[WingTypes["NIGHTMARE"] = 28] = "NIGHTMARE";
    WingTypes[WingTypes["FEATHERED_SPHINX"] = 29] = "FEATHERED_SPHINX";
})(WingTypes || (WingTypes = {}));
function RGBA(i) {
    let rgb = i.toRgb();
    return (((rgb.a * 0xff) & 0xff) << 24
        | (rgb.b & 0xff) << 16
        | (rgb.g & 0xff) << 8
        | (rgb.r & 0xff)) >>> 0;
}
function bound(min, x, max = Infinity) {
    return min < x ? x < max ? x : max : min;
}
function randint(n) {
    return Math.floor(Math.random() * n);
}
function randel(arr) {
    return arr[randint(arr.length)];
}
function $new(selector = 'div', ...content) {
    let ss = selector.split(/\./);
    let tagName = ss[0] || 'div';
    let d = document.createElement(tagName);
    d.className = ss.slice(1).join(' ');
    if (tagName == 'button')
        d.type = 'button';
    if (tagName == 'a')
        d.href = 'javascript:void(0)';
    let j = $(d);
    for (let c of content)
        j.append(c);
    return j;
}
function obj2kvpairs(o) {
    let rslt = [];
    for (let i = 0, a = Object.keys(o), n = a.length; i < n; i++) {
        rslt[i] = [a[i], o[a[i]]];
    }
    return rslt;
}
function colormap(src, map) {
    let dst = new ImageData(src.width, src.height);
    let sarr = new Uint32Array(src.data.buffer);
    let darr = new Uint32Array(dst.data.buffer);
    for (let i = 0, n = darr.length; i < n; i++) {
        darr[i] = sarr[i];
        let sa = (sarr[i] & 0xff000000) >>> 0;
        let srgb = (sarr[i] | 0xff000000) >>> 0;
        if (sa === 0 || srgb === 0)
            continue;
        for (let j = 0, m = map.length; j < m; j++) {
            if (srgb === map[j][0]) {
                darr[i] = ((map[j][1] & 0x00ffffff) | sa) >>> 0;
                break;
            }
        }
    }
    return dst;
}
/*
 * Takes a rect starting from (srcdx, srcdy) of size (srcw x srch) from src
 * Scales it `scale` times.
 * Puts in onto dst starting from (dstdx, dstdy), with a dst bounds limited at (dstw x dsth)
 */
function drawImage(src, srcdx, srcdy, srcw, srch, dst, dstdx, dstdy, dstw, dsth, scale) {
    let sx = srcdx, sy = srcdy, sw = srcw, sh = srch;
    let dx = dstdx, dy = dstdy;
    if (dx < 0) {
        sx -= dx;
        dx = 0;
    }
    if (dy < 0) {
        sy -= dy;
        dy = 0;
    }
    if (dx + sw > dstw)
        sw = dstw - dx;
    if (dy + sh > dsth)
        sh = dsth - dy;
    dst.drawImage(src, sx, sy, sw, sh, dx * scale, dy * scale, sw * scale, sh * scale);
}
var utils;
(function (utils) {
    utils.basedir = window['spred_basedir'] || '../../';
    utils.canAjax = location.protocol != 'file:';
    let FileAsker;
    (function (FileAsker) {
        let fileReaders = {};
        function filename(f) {
            let j = f.lastIndexOf('/');
            if (j >= 0)
                return f.substring(j + 1);
            return f;
        }
        FileAsker.filename = filename;
        function wantFile(f) {
            return filename(f) in fileReaders;
        }
        FileAsker.wantFile = wantFile;
        function checkFiles(e) {
            let filesArray = e.target.files;
            for (let i = 0; i < filesArray.length; i++) {
                let file = filesArray[i];
                let name = filename(file.name);
                let handler = fileReaders[name];
                if (handler) {
                    delete fileReaders[name];
                    handler(file);
                }
            }
        }
        function askFile(url, handler) {
            let fileinput = $new('input').attr('type', 'file').attr('multiple', 'true').change(checkFiles);
            let dropzone = $new('p', 'Please select manually the ', $new('code', url), ' file:', fileinput);
            $('#LoadingList').append(dropzone);
            $('#Loading').show();
            fileReaders[filename(url)] = (file) => {
                dropzone.remove();
                $('#Loading').toggle($('#LoadingList>*').length > 0);
                handler(file);
            };
        }
        FileAsker.askFile = askFile;
    })(FileAsker || (FileAsker = {}));
    function url2img(src) {
        return new Promise((resolve, reject) => {
            let img = document.createElement('img');
            img.onload = (e) => {
                resolve(img);
            };
            img.src = src;
        });
    }
    utils.url2img = url2img;
    function loadFile(url, format) {
        return new Promise((resolve, reject) => {
            if (!utils.canAjax) {
                FileAsker.askFile(url, file => {
                    if (format == 'img') {
                        url2img(URL.createObjectURL(file)).then(resolve);
                    }
                    else {
                        let fr = new FileReader();
                        fr.onload = () => {
                            if (format == 'xml') {
                                resolve($.parseXML(fr.result));
                            }
                            else {
                                resolve(fr.result);
                            }
                            return;
                        };
                        fr.readAsText(file);
                    }
                });
            }
            else if (format != 'img') {
                $.ajax(url, { dataType: format }).then(resolve).fail(reject);
            }
            else
                url2img(url).then(resolve);
        });
    }
    utils.loadFile = loadFile;
    function xmlget(x, query) {
        let m;
        let orig = query;
        while (query && x) {
            if ((m = query.match(/^ *>? *([\w_-]+) */))) {
                x = Array.from(x.children).find(e => e.tagName.toUpperCase() == m[1].toUpperCase());
                query = query.substring(m[0].length);
            }
            else if ((m = query.match(/^ *@ *([\w_-]+) *$/))) {
                query = query.substring(m[0].length);
                let a = x.getAttribute(m[1]);
                if (a === null)
                    return undefined;
                return a;
            }
            else {
                throw 'Bad selector "' + orig + '", halted on "' + query + '"';
            }
        }
        if (!x)
            return undefined;
        return x.innerHTML;
    }
    utils.xmlget = xmlget;
    function unindent(s) {
        if (s === undefined)
            return undefined;
        let m = s.match(/^(\n\s+)/);
        if (m)
            s = s.replace(new RegExp(m[1], 'g'), '\n').substr(1);
        s = s.replace(/(\n\s+)$/, '');
        return s;
    }
    utils.unindent = unindent;
    function xmlgeti(x, query) {
        let s = xmlget(x, query);
        if (s === undefined)
            return undefined;
        return (+s) | 0;
    }
    utils.xmlgeti = xmlgeti;
    function xmlgetf(x, query) {
        let s = xmlget(x, query);
        if (s === undefined)
            return undefined;
        return +s;
    }
    utils.xmlgetf = xmlgetf;
    function parseLength(s) {
        if (typeof s === 'undefined')
            return undefined;
        if (typeof s === 'number')
            return s;
        if (!isNaN(+s))
            return +s;
        let m;
        if ((m = s.match(/^(?:(\d+)')(?:(\d+)")?$/))) {
            return (+m[1]) * 12 + +m[2];
        }
        else if ((m = s.match(/^(?:(\d+)m)?\s*(?:(\d+)cm)$/))) {
            let cm = (+m[1]) * 100 + +m[2];
            return +(cm / 2.54);
        }
        else {
            throw "Not a valid length: " + JSON.stringify(s);
        }
    }
    utils.parseLength = parseLength;
    function lengthString(n) {
        if (n === undefined)
            return undefined;
        if (n > -0.05 && n < +0.05)
            return '0';
        let s = '';
        if (n < 0) {
            s = '-';
            n = -n;
        }
        let ft = (n / 12) | 0;
        if (ft > 0)
            s += ft + "'";
        let ic = (((n - ft * 12) * 10) | 0) / 10;
        if (ic != (ic | 0))
            s += ic.toFixed(1) + '"';
        else if (ic != 0)
            s += ic.toFixed(0) + '"';
        return s;
    }
    utils.lengthString = lengthString;
    function dictLookup(dict, s) {
        if (typeof s === 'undefined')
            return undefined;
        if (s in dict)
            return dict[s];
        if (!isNaN(+s))
            return +s;
        throw "Not a valid number or enum constant: " + JSON.stringify(s);
    }
    utils.dictLookup = dictLookup;
    function dictLookupName(dict, s) {
        if (s === undefined)
            return undefined;
        if (s in dict)
            return dict[s];
        return "" + s;
    }
    utils.dictLookupName = dictLookupName;
    function enumNames(E) {
        return Object.keys(E).filter(k => typeof E[k] === "number");
    }
    utils.enumNames = enumNames;
    function enumValues(E) {
        return enumNames(E).map(k => E[k]);
    }
    utils.enumValues = enumValues;
    function enumAsOptions(E) {
        return enumNames(E).map(k => $('<option>').val(E[k]).html(k)[0]);
    }
    utils.enumAsOptions = enumAsOptions;
})(utils || (utils = {}));
/*
 * Created by aimozg on 27.07.2017.
 * Confidential until published on GitHub
 */
///<reference path="typings/jquery.d.ts"/>
///<reference path="utils.ts"/>
var spred;
(function (spred) {
    var loadFile = utils.loadFile;
    var basedir = utils.basedir;
    var url2img = utils.url2img;
    spred.g_composites = [];
    spred.g_selsprite = '';
    spred.g_selpart = null;
    function tfcolor(tc, name, value) {
        let hsl = tc.clone().toHsl();
        switch (name) {
            case 'lighten':
                let l2a = hsl.l + (1.0 - hsl.l) * value / 100;
                let l2b = bound(0, hsl.l + value / 100, 1);
                return tinycolor({ h: hsl.h, s: hsl.s, l: Math.max(l2a, l2b) });
            case 'darken':
                return tinycolor({ h: hsl.h, s: hsl.s, l: hsl.l * (100 - value) / 100 });
        }
        return tc;
    }
    spred.tfcolor = tfcolor;
    function defaultPartList() {
        let o = [];
        let q = spred.g_model.logic.slice();
        while (q.length > 0) {
            let l = q.pop();
            if (l instanceof LogicIf) {
                if (randint(2) != 0)
                    q.push(...l.then);
            }
            else if (l instanceof LogicShow) {
                o.push(l.partExpr);
            }
            else if (l instanceof LogicHide) {
                let m;
                let part = l.partExpr;
                if ((m = part.match(/^([^*]+)\*$/))) {
                    o = o.filter(e => !e.startsWith(m[1]));
                }
                else {
                    o = o.filter(e => e != part);
                }
            }
            else if (l instanceof LogicSwitch) {
                let i = randint(l.cases.length + 1) - 1;
                if (i < 0)
                    q.push(...l.default);
                else
                    q.push(...l.cases[i].body);
            }
        }
        return o;
    }
    spred.defaultPartList = defaultPartList;
    /*
    function mkimg(colors: string[][]): HTMLCanvasElement {
        const hex     = {a: 10, b: 11, c: 12, d: 13, e: 14, f: 15, A: 10, B: 11, C: 12, D: 13, E: 14, F: 15};
        let canvas    = document.createElement('canvas');
        let w         = colors[0].length;
        let h         = colors.length;
        canvas.width  = w;
        canvas.height = h;
        let c2d       = canvas.getContext('2d');
        let id        = c2d.getImageData(0, 0, w, h);
        let px        = id.data;
        let i         = 0;
        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                let rgb    = colors[y][x];
                let r: any = rgb.charAt(0), g: any = rgb.charAt(1), b: any = rgb.charAt(2);
                r          = hex[r] || +r;
                g          = hex[g] || +g;
                b          = hex[b] || +b;
                px[i++]    = (r << 4) | r;
                px[i++]    = (g << 4) | g;
                px[i++]    = (b << 4) | b;
                px[i++]    = 0xff;
            }
        }
        c2d.putImageData(id, 0, 0);
        return canvas;
    }
    */
    function newCanvas(width, height, code = () => { }) {
        let canvas = document.createElement('canvas');
        canvas.width = width;
        canvas.height = height;
        code(canvas.getContext('2d'));
        return canvas;
    }
    spred.newCanvas = newCanvas;
    function paletteOptions(palette) {
        return Object.keys(palette).map(name => $new('option', name).attr('value', /*palette[name]*/ name));
    }
    spred.paletteOptions = paletteOptions;
    class StructLayer {
        constructor(name) {
            this.name = name;
            this.parts = [];
        }
        findPart(partName) {
            let fullname = this.name + '/' + partName;
            return this.parts.find(part => part.name == fullname);
        }
    }
    spred.StructLayer = StructLayer;
    class Part {
        constructor(name, spriteref, dx, dy) {
            this.name = name;
            this.spriteref = spriteref;
            this.dx = dx;
            this.dy = dy;
        }
        get sprite() {
            return this.spriteref();
        }
        updateUI() {
            let c2d = this.ui.find('canvas')[0].getContext('2d');
            let cw = this.sprite.width, ch = this.sprite.height;
            c2d.drawImage(this.sprite.canvas, 0, 0, cw > ch ? 32 : 32 * ch / cw, cw > ch ? 32 * ch / cw : 32);
        }
    }
    spred.Part = Part;
    class Composite {
        constructor(model, visibleNames = [], zoom = 1) {
            this.model = model;
            this._parts = {};
            this._cache = {};
            this._version = 0;
            this.colormap = {};
            this.canvas = newCanvas(model.width * zoom, model.height * zoom);
            this.canvas.setAttribute('focusable', 'true');
            this.partNames = visibleNames.slice(0);
            this.redraw();
        }
        get partNames() {
            return this.model.allParts().filter(p => p.name in this._parts).map(p => p.name);
        }
        set partNames(value) {
            this._parts = value.reduce((r, e) => {
                r[e] = true;
                return r;
            }, {});
        }
        clearCache() {
            this._cache = {};
        }
        redraw(x = 0, y = 0, w = this.model.width, h = this.model.height) {
            let version = ++this._version;
            let ctx2d = this.canvas.getContext('2d');
            ctx2d.imageSmoothingEnabled = false;
            let z = this.zoom;
            let [xz, yz, wz, hz] = [x * z, y * z, w * z, h * z];
            ctx2d.clearRect(xz, yz, wz, hz);
            let p0 = new Promise((resolve, reject) => {
                resolve(ctx2d);
            });
            let cmap = [];
            let commonPalette = spred.g_model.palettes['common'];
            for (let ck of this.model.colorkeys) {
                if (!(ck.base in this.colormap))
                    continue;
                let cpPal = spred.g_model.palettes[ck.base] || {};
                let cname = this.colormap[ck.base];
                let base = tinycolor(cpPal[cname] || commonPalette[cname]);
                if (ck.transform)
                    for (let tf of ck.transform.split(',')) {
                        let m = tf.match(/^([a-z]+)\((\d+)\)$/);
                        if (m)
                            base = tfcolor(base, m[1], +m[2]);
                    }
                cmap.push([RGBA(tinycolor(ck.src)), RGBA(base)]);
            }
            for (let a = this.model.allParts(), i = a.length - 1; i >= 0; i--) {
                let part = a[i];
                if (this._parts[part.name]) {
                    p0 = p0.then(ctx2d => {
                        if (version != this._version)
                            return ctx2d;
                        let sprite = this.model.sprite(part.name);
                        if (part.name in this._cache) {
                            drawImage(this._cache[part.name], x, y, w, h, ctx2d, part.dx + sprite.dx - this.model.originX, part.dy + sprite.dy - this.model.originY, this.model.width, this.model.height, z);
                            return ctx2d;
                        }
                        else {
                            let idata = sprite.ctx2d.getImageData(x, y, w, h);
                            idata = colormap(idata, cmap);
                            return createImageBitmap(idata).then(bmp => {
                                if (x == 0 && y == 0 && w == this.model.width && h == this.model.height) {
                                    this._cache[part.name] = bmp;
                                }
                                if (version != this._version)
                                    return ctx2d;
                                drawImage(bmp, x, y, w, h, ctx2d, part.dx + sprite.dx - this.model.originX, part.dy + sprite.dy - this.model.originY, this.model.width, this.model.height, z);
                                return ctx2d;
                            });
                        }
                    });
                }
            }
            /*p0.then(ctx2d => {
                let data      = colormap(ctx2d.getImageData(xz,yz,wz,hz), cmap);
                ctx2d.putImageData(data,0,0);
                return ctx2d;
            })*/
        }
        hideAll(name) {
            this._parts = {};
        }
        isVisible(partName) {
            return this._parts[partName];
        }
        setVisible(partName, visibility) {
            this._parts[partName] = visibility;
        }
        get zoom() {
            return this.canvas.width / this.model.width;
        }
        set zoom(value) {
            value = Math.max(1, value | 0);
            this.canvas.width = this.model.width * value;
            this.canvas.height = this.model.height * value;
            this.redraw();
        }
    }
    spred.Composite = Composite;
    class Sprite {
        constructor(name, width, height, src, srcX, srcY, dx, dy) {
            this.name = name;
            this.width = width;
            this.height = height;
            this.srcX = srcX;
            this.srcY = srcY;
            this.dx = dx;
            this.dy = dy;
            this.canvas = document.createElement('canvas');
            this.canvas.width = width;
            this.canvas.height = height;
            this.ctx2d = this.canvas.getContext('2d');
            this.ctx2d.imageSmoothingEnabled = false;
            this.ctx2d.drawImage(src, srcX, srcY, width, height, 0, 0, width, height);
        }
        updateUI() {
            let c2d = this.ui.find('canvas')[0].getContext('2d');
            let cw = this.width, ch = this.height;
            c2d.drawImage(this.canvas, 0, 0, cw > ch ? 32 : 32 * ch / cw, cw > ch ? 32 * ch / cw : 32);
        }
    }
    spred.Sprite = Sprite;
    class Spritesheet {
        constructor(modeldir, src) {
            this.sprites = {};
            let x = $(src);
            this.cellwidth = +x.attr('cellwidth');
            this.cellheight = +x.attr('cellheight');
            this.sprites = {};
            this.whenLoaded =
                loadFile(modeldir + x.attr('file'), 'img').then(img => {
                    let positions = {};
                    x.children("row").each((i, row) => {
                        let names = row.textContent.split(',');
                        for (let j = 0; j < names.length; j++) {
                            if (names[j])
                                positions[names[j]] = [this.cellwidth * j, this.cellheight * i];
                        }
                    });
                    Object.keys(positions)
                        .forEach(key => {
                        this.sprites[key] = new Sprite(key, this.cellheight, this.cellwidth, img, positions[key][0], positions[key][1], 0, 0);
                    });
                    this.img = img;
                    console.log('Loaded spritesheet ' + img.src);
                    return this;
                });
        }
        isLoaded() {
            return this.img != null;
        }
    }
    spred.Spritesheet = Spritesheet;
    class Spritemap {
        constructor(modeldir, src) {
            this.sprites = {};
            let x = $(src);
            this.sprites = {};
            this.file = x.attr('file');
            this.whenLoaded =
                loadFile(modeldir + this.file, 'img').then(img => {
                    x.children("cell").each((i, cell) => {
                        let name = cell.getAttribute('name');
                        let rect = (cell.getAttribute('rect') || '').match(/^(\d+),(\d+),(\d+),(\d+)$/);
                        let x, y, w, h;
                        if (rect) {
                            [x, y, w, h] = [+rect[1], +rect[2], +rect[3], +rect[4]];
                        }
                        else {
                            x = +cell.getAttribute('x');
                            y = +cell.getAttribute('y');
                            w = +cell.getAttribute('w');
                            h = +cell.getAttribute('h');
                        }
                        this.sprites[name] = new Sprite(name, w, h, img, x, y, +(cell.getAttribute('dx') || '0'), +(cell.getAttribute('dy') || '0'));
                    });
                    console.log('Loaded spritemap ' + img.src);
                    return this;
                });
        }
        serialize() {
            return `    <spritemap file="${this.file}">\n` +
                Object.keys(this.sprites).map(sn => {
                    let s = this.sprites[sn];
                    return `        <cell rect="${s.srcX},${s.srcY},${s.width},${s.height}" name="${s.name}" dx="${s.dx}" dy="${s.dy}"/>`;
                }).join('\n') + `\n    </spritemap>`;
        }
        isLoaded() {
            return this.img != null;
        }
    }
    spred.Spritemap = Spritemap;
    class LogicStmt {
        static parseBlock(x) {
            return x.children().toArray().map(x => LogicStmt.parse(x));
        }
        static parse(x) {
            switch (x.tagName.toLowerCase()) {
                case 'switch':
                    let lswitch = new LogicSwitch(x.getAttribute("value"));
                    $(x).children('case').each((i, x2) => {
                        let lcase = new LogicCase(x2.getAttribute('value'), x2.getAttribute('test'));
                        lcase.body = LogicStmt.parseBlock($(x2));
                        lswitch.cases.push(lcase);
                    });
                    lswitch.default = LogicStmt.parseBlock($(x).children('default'));
                    return lswitch;
                case 'show':
                    return new LogicShow(x.getAttribute('part'));
                case 'hide':
                    return new LogicHide(x.getAttribute('part'));
                case 'if':
                    let lif = new LogicIf(x.getAttribute("test"));
                    lif.then = LogicStmt.parseBlock($(x));
                    return lif;
                default:
                    console.warn('Unsupported logic element ' + x.tagName);
                    return null;
            }
        }
    }
    spred.LogicStmt = LogicStmt;
    class LogicCase {
        constructor(valueExpr, testExpr) {
            this.valueExpr = valueExpr;
            this.testExpr = testExpr;
            this.body = [];
        }
    }
    spred.LogicCase = LogicCase;
    class LogicSwitch extends LogicStmt {
        constructor(valueExpr) {
            super();
            this.valueExpr = valueExpr;
            this.cases = [];
            this.default = [];
        }
    }
    spred.LogicSwitch = LogicSwitch;
    class LogicShow extends LogicStmt {
        constructor(partExpr) {
            super();
            this.partExpr = partExpr;
        }
    }
    spred.LogicShow = LogicShow;
    class LogicHide extends LogicStmt {
        constructor(partExpr) {
            super();
            this.partExpr = partExpr;
        }
    }
    spred.LogicHide = LogicHide;
    class LogicIf extends LogicStmt {
        constructor(testExpr) {
            super();
            this.testExpr = testExpr;
            this.then = [];
        }
    }
    spred.LogicIf = LogicIf;
    class Model {
        constructor(src) {
            this.sprites = {};
            // private parts: Part[]         = [];
            this.layers = [];
            this.colorProps = [];
            this.colorkeys = [];
            this.logic = [];
            let xmodel = $(src).children('model');
            this.name = xmodel.attr('name');
            this.dir = basedir + xmodel.attr('dir');
            this.originX = parseInt(xmodel.attr('originX') || '0');
            this.originY = parseInt(xmodel.attr('originY') || '0');
            this.width = parseInt(xmodel.attr('width'));
            this.height = parseInt(xmodel.attr('height'));
            this.spritesheets = [];
            this.spritemaps = [];
            this.palettes = {};
            xmodel.find('colorkeys>key').each((i, e) => {
                this.colorkeys.push({
                    src: e.getAttribute('src'),
                    base: e.getAttribute('base'),
                    transform: e.getAttribute('transform') || ''
                });
            });
            //noinspection CssInvalidHtmlTagReference
            xmodel.find('palettes>palette').each((i, e) => {
                let palette = {};
                $(e).find('color').each((i, e) => {
                    palette[e.getAttribute('name')] = e.textContent;
                });
                this.palettes[e.getAttribute('name')] = palette;
            });
            //noinspection CssInvalidHtmlTagReference
            xmodel.find('properties>property').each((i, e) => {
                let cpname = e.getAttribute('name');
                this.colorProps.push({
                    name: cpname,
                    src: e.getAttribute('src'),
                    palette: (e.getAttribute('palette') || 'common').split(','),
                    def: e.getAttribute('default')
                });
            });
            xmodel.find('spritesheet').each((i, x) => {
                let spritesheet = new Spritesheet(this.dir, x);
                this.spritesheets.push(spritesheet);
            });
            xmodel.find('spritemap').each((i, x) => {
                let spritemap = new Spritemap(this.dir, x);
                this.spritemaps.push(spritemap);
            });
            this.logic.push(...LogicStmt.parseBlock(xmodel.find('logic')));
            this.whenLoaded =
                Promise.all(this.spritesheets.map(p => p.whenLoaded)
                    .concat(this.spritemaps.map(p => p.whenLoaded))).then(() => {
                    console.log('Loaded model');
                    xmodel.find('layers>layer').each((i, x) => {
                        this.layers.push(new StructLayer(x.getAttribute('name')));
                    });
                    for (let ss of this.spritesheets) {
                        for (let sname in ss.sprites) {
                            this.addSprite(ss.sprites[sname]);
                        }
                    }
                    for (let sm of this.spritemaps) {
                        for (let sname in sm.sprites) {
                            this.addSprite(sm.sprites[sname]);
                        }
                    }
                    /*
                    xmodel.find('show').each((i, x) => {
                        let ln     = x.getAttribute('part');
                        let ldx    = +(x.getAttribute('dx') || '0');
                        let ldy    = +(x.getAttribute('dy') || '0');
                        let sprite = this.sprites[ln];
                        let lid: string;
                        if (x.id) {
                            lid = '#' + x.id;
                        } else if (ldx || ldy) {
                            lid = ln + '@' + ldx + ',' + ldy;
                        } else {
                            lid = ln;
                        }
                        if (!sprite) {
                            console.warn("Part " + lid + " refered non-existing sprite " + ln);
                        } else {
                            if (this.parts.every(l => l.name != lid)) {
                                this.parts.push(new Part(lid, sprite, ldx, ldy));
                            }
                        }
                    });
                    */
                    return this;
                });
        }
        allParts() {
            return [].concat(...this.layers.map(l => l.parts));
        }
        putPixel(x, y, color) {
            let l = getSelSprite();
            if (!l)
                return;
            let ctx = l.ctx2d;
            if (!color) {
                ctx.clearRect(x, y, 1, 1);
            }
            else {
                ctx.fillStyle = color;
                ctx.fillRect(x, y, 1, 1);
            }
        }
        isLoaded() {
            return this.spritesheets.every(s => s.isLoaded())
                && this.spritemaps.every(s => s.isLoaded());
        }
        sprite(name) {
            return this.sprites[name];
        }
        addSprite(s) {
            this.sprites[s.name] = s;
            let ln = s.name.split('/')[0];
            let layer = this.layers.find(l => l.name == ln);
            if (!layer) {
                console.warn("Layer not found for sprite " + s.name);
                return;
            }
            layer.parts.push(new Part(s.name, () => this.sprite(s.name), 0, 0));
        }
    }
    spred.Model = Model;
    function updateCompositeParts(composite) {
        composite.ui.find('.LayerBadges').html('').append(composite.model.layers.map(layer => $new('div', $new('label', $new('strong', layer.name + ': '), layer.parts.map(p => $new('button.badge' +
            (composite.isVisible(p.name) ? '.badge-primary' : '.badge-default'), p.name.substring(layer.name.length + 1)).click((e) => {
            $(e.target).toggleClass('badge-primary').toggleClass('badge-default');
            composite.setVisible(p.name, !composite.isVisible(p.name));
            composite.redraw();
            selPart(p);
        }))))));
    }
    spred.updateCompositeParts = updateCompositeParts;
    function addCompositeView(parts, zoom = 1) {
        let composite = new Composite(spred.g_model, parts, zoom);
        /*for (let ln of parts) {
            if (g_model.parts.every(l => l.name != ln)) {
                console.warn("Non-existing part " + ln);
            }
        }*/
        let commonPalette = spred.g_model.palettes['common'];
        for (let cp of spred.g_model.colorProps) {
            let cpname = cp.name;
            let cpPal = spred.g_model.palettes[cpname] || {};
            let cname = randel(Object.keys(commonPalette).concat(Object.keys(cpPal)));
            composite.colormap[cpname] = cname;
        }
        composite.redraw();
        $('#ViewList').append(composite.ui = $new('.card.card-secondary.d-inline-flex', $new('.card-block', $new('h5.card-title', $new('button.ctrl.text-danger.pull-right', $new('span.fa.fa-close')).click(() => {
            removeCompositeView(composite);
        }), $new('button.ctrl', $new('span.fa.fa-search-plus')).click(() => {
            composite.zoom++;
        }), $new('button.ctrl', $new('span.fa.fa-search-minus')).click(() => {
            composite.zoom--;
        }), $new('button.ctrl', $new('span.fa.fa-reorder')).click(e => {
            composite.ui.find('.LayerBadges').toggleClass('collapse');
        }), $new('button.ctrl', $new('span.fa.fa-paint-brush')).click(e => {
            composite.ui.find('.Colors').toggleClass('collapse');
        })), $new('div', $new('.canvas', composite.canvas)), $new('.LayerBadges.collapse'), $new('div', $new('.Colors.collapse', ...spred.g_model.colorProps.map(cp => $new('.row.control-group', $new('label.control-label.col-4', cp.name), $new('select.form-control.col-8', $new('option', '--none--')
            .attr('selected', 'true')
            .attr('value', ''), $new('optgroup', ...paletteOptions(spred.g_model.palettes[cp.name] || {}))
            .attr('label', cp.name + ' special'), $new('optgroup', ...paletteOptions(commonPalette))
            .attr('label', 'Common')).change(e => {
            let s = e.target;
            if (s.value) {
                composite.colormap[cp.name] = s.value;
            }
            else {
                delete composite.colormap[cp.name];
            }
            composite.clearCache();
            composite.redraw();
        }).val(composite.colormap[cp.name])))))
        /*$new('textarea.col.form-control'
        ).val(parts.join(', ')
        ).on('input change', e => {
            composite.parts = (e.target as HTMLTextAreaElement).value.split(/, *!/);
            composite.redraw();
        })*/
        )));
        let drawing = false;
        let dragging = false;
        let dirty = false;
        let x0 = spred.g_model.width, y0 = spred.g_model.height, x1 = -1, y1 = -1;
        let color = null;
        function putPixel(cx, cy) {
            let x = (cx / composite.zoom) | 0;
            let y = (cy / composite.zoom) | 0;
            dirty = true;
            spred.g_model.putPixel(x, y, color);
            composite.clearCache();
            composite.redraw(x, y, 1, 1);
            if (x < x0)
                x0 = x;
            if (x > x1)
                x1 = x;
            if (y < y0)
                y0 = y;
            if (y > y1)
                y1 = y;
        }
        $(composite.canvas).mousedown(e => {
            let action = $('[name=lmb-action]:checked').val();
            let keycolors = $('#lmb-color');
            switch (action) {
                case 'nothing':
                    return;
                case 'erase':
                    color = null;
                    break;
                case 'keycolor':
                    color = keycolors.val();
                    break;
                case 'drag':
                    dragging = true;
                    x0 = e.offsetX;
                    y0 = e.offsetY;
                    return;
            }
            drawing = true;
            putPixel(e.offsetX, e.offsetY);
        }).mousemove(e => {
            if (drawing) {
                putPixel(e.offsetX, e.offsetY);
            }
            else if (dragging) {
                let dx = ((e.offsetX - x0) / composite.zoom) | 0;
                let dy = ((e.offsetY - y0) / composite.zoom) | 0;
                if (dx || dy) {
                    selPartMove(dx, dy);
                    x0 = e.offsetX;
                    y0 = e.offsetY;
                }
            }
        }).on('mouseup mouseout', e => {
            drawing = false;
            dragging = false;
            if (dirty) {
                for (let obj of spred.g_composites) {
                    obj.clearCache();
                }
                redrawAll(x0, y0, x1 - x0 + 1, y1 - y0 + 1);
                dirty = false;
            }
        });
        spred.g_composites.push(composite);
        updateCompositeParts(composite);
        return composite;
    }
    spred.addCompositeView = addCompositeView;
    function removeCompositeView(composite) {
        let i = spred.g_composites.indexOf(composite);
        if (i < 0)
            return;
        spred.g_composites.splice(i, 1);
        composite.ui.remove();
    }
    spred.removeCompositeView = removeCompositeView;
    function redrawAll(x = 0, y = 0, w = spred.g_model.width, h = spred.g_model.height) {
        for (let obj of spred.g_composites) {
            obj.redraw(x, y, w, h);
        }
    }
    spred.redrawAll = redrawAll;
    function g_update_color(pname, cname) {
        for (let obj of spred.g_composites) {
            for (let kn in obj.colormap) {
                if (pname != 'common' && pname != kn)
                    continue;
                let kv = obj.colormap[kn];
                if (kv == cname) {
                    obj.clearCache();
                    obj.redraw();
                    break;
                }
            }
        }
    }
    spred.g_update_color = g_update_color;
    /*
    export function swapLayers(a: number, b: number) {
        let l0           = g_model.parts[a];
        g_model.parts[a] = g_model.parts[b];
        g_model.parts[b] = l0;
        showSpriteList(g_model);
        redrawAll();
    }
    */
    /*
    function showSpriteList(model: Model) {

        let list = $('#LayerList');
        for (let sn in model.sprites) {
            let sprite = model.sprites[sn];
            if (sprite) sprite.ui.detach().appendTo(list);
        }

    }
    */
    function showLayerList(model) {
        let lbs = model.layers.map(layer => $new('div', $new('h5', layer.name), layer.parts.map(p => p.ui.detach())));
        $('#LayerList').html('').append(lbs);
    }
    function getSelSprite() {
        return spred.g_model.sprite(spred.g_selsprite);
    }
    spred.getSelSprite = getSelSprite;
    function selSprite(name) {
        spred.g_selsprite = name;
        /*$('#SelLayerName').html(name);
        $('.LayerListItem').removeClass('selected');
        let l = getSelSprite();
        if (l) {
            l.ui.addClass('selected');
            $('#SelLayerCanvas').html('').append(l.canvas);
        }*/
    }
    spred.selSprite = selSprite;
    function selPart(part) {
        spred.g_selpart = part;
        $('#SelLayerName').html(part.name);
        $('#SelLayerPos').html('(dx = ' + (part.dx + part.sprite.dx) +
            ', dy = ' + (part.dy + part.sprite.dy) + ')');
        $('.LayerListItem').removeClass('selected');
        part.ui.addClass('selected');
        $('#SelLayerCanvas').html('').append(part.sprite.canvas);
    }
    spred.selPart = selPart;
    /*
    export function selLayerUp() {
        let i = g_model.layers.findIndex(layer=>layer.name==g_selpart);
        if (i > 0) swapLayers(i, i - 1);
    }

    export function selLayerDown() {
        let i = g_model.partNames.indexOf(g_selpart);
        if (i >= 0 && i < g_model.partNames.length - 1) swapLayers(i, i + 1);
    }
    */
    function selPartMove(dx, dy) {
        let part = spred.g_selpart;
        if (part) {
            part.sprite.dx += dx;
            part.sprite.dy += dy;
            redrawAll();
            $('#SelLayerPos').html('(dx = ' + (part.dx + part.sprite.dx) +
                ', dy = ' + (part.dy + part.sprite.dy) + ')');
        }
    }
    spred.selPartMove = selPartMove;
    function grabData(blob) {
        let mask = $('#ClipboardMask').val();
        let i32mask = mask ? RGBA(tinycolor(mask)) : 0;
        url2img(URL.createObjectURL(blob)).then(img => {
            switch ($("input[name=clipboard-action]:checked").val()) {
                case 'replace':
                    let sprite = getSelSprite();
                    if (!sprite)
                        return;
                    sprite.ctx2d.clearRect(0, 0, sprite.width, sprite.height);
                    sprite.ctx2d.drawImage(img, 0, 0);
                    if (i32mask != 0) {
                        let data = sprite.ctx2d.getImageData(0, 0, sprite.width, sprite.height);
                        data = colormap(data, [[i32mask, 0]]);
                        sprite.ctx2d.clearRect(0, 0, sprite.width, sprite.height);
                        sprite.ctx2d.putImageData(data, 0, 0);
                    }
                    sprite.updateUI();
                    redrawAll();
                    break;
            }
        });
    }
    function loadModel(data) {
        spred.g_model = new Model(data);
        spred.g_model.whenLoaded.then((model) => {
            for (let ln in model.sprites) {
                let sprite = model.sprites[ln];
                sprite.ui = $new('div.LayerListItem', $new('label', ln), newCanvas(32, 32)).click(e => selSprite(ln));
                sprite.updateUI();
            }
            $('#SelLayerCanvas').css('min-width', model.width + 'px');
            selSprite(Object.keys(model.sprites)[0]);
            $('#lmb-color').html('').append(model.colorkeys.map(ck => $new('option', ck.base + (ck.transform ? ' ' + ck.transform : '')).attr('value', ck.src)));
            for (let part of model.allParts()) {
                part.ui = $new('div.LayerListItem', $new('label', part.name), newCanvas(32, 32)).click(e => selPart(part));
                part.updateUI();
            }
            showLayerList(model);
            let pals = $('#Palettes');
            for (let pn in model.palettes) {
                let pal = model.palettes[pn];
                pals.append($new('div', $new('h5', pn), Object.keys(pal).map(cn => {
                    let sref = $new('span', '\xA0');
                    let shl = $new('label.-palilab', 'H=XXX');
                    /*let sha = $new('span','\xA0');
                    let shb = $new('span','\xA0');*/
                    let ssl = $new('label.-palilab', 'S=XXX%');
                    let ssa = $new('span', '\xA0');
                    let ssb = $new('span', '\xA0');
                    let sll = $new('label.-palilab', 'S=XXX%');
                    let sla = $new('span', '\xA0');
                    let slb = $new('span', '\xA0');
                    function setcolor(color) {
                        let hsl = color.toHsl();
                        sref.css('background-color', color.toHslString()).css('color', color.isDark() ? '#ffffff' : '#000000');
                        sref.text(color.toHexString());
                        shl.text('H=' + (hsl.h | 0));
                        /*sha.css('background-color',tinycolor({
                            h:(hsl.h+240)%360,s:hsl.s,l:hsl.l}).toHslString());
                        shb.css('background-color',tinycolor({
                            h:(hsl.h+120)%360,s:hsl.s,l:hsl.l}).toHslString());*/
                        ssl.text('S=' + ((hsl.s * 100) | 0) + '%');
                        ssa.css('background-color', tinycolor({ h: hsl.h, s: 0.1, l: hsl.l }).toHslString());
                        ssb.css('background-color', tinycolor({ h: hsl.h, s: 0.9, l: hsl.l }).toHslString());
                        sll.text('L=' + ((hsl.l * 100) | 0) + '%');
                        sla.css('background-color', tinycolor({ h: hsl.h, s: hsl.s, l: 0.1 }).toHslString());
                        slb.css('background-color', tinycolor({ h: hsl.h, s: hsl.s, l: 0.9 }).toHslString());
                        pal[cn] = color.toHexString();
                        g_update_color(pn, cn);
                    }
                    let hsl = tinycolor(pal[cn]).toHsl();
                    let hinput = $('<input type="range" min="0" max="360">').val(hsl.h | 0);
                    let sinput = $('<input type="range" min="0" max="100">').val((hsl.s * 100) | 0);
                    let linput = $('<input type="range" min="0" max="100">').val((hsl.l * 100) | 0);
                    function updcolor() {
                        setcolor(tinycolor({
                            h: +hinput.val(),
                            s: (+sinput.val()) / 100,
                            l: (+linput.val()) / 100
                        }));
                    }
                    setcolor(tinycolor(pal[cn]));
                    return $new('div.PaletteItem', $new('h6', cn + ' ', sref), $new('div.-details', $new('.-paliprop', shl,
                    /*sha.addClass('.-palibox'),*/
                    hinput.addClass('.-palilong').on('input change', updcolor)), $new('.-paliprop', ssl, ssa.addClass('-palibox'), $new('div', sinput.addClass('-palishort').on('input change', updcolor)), ssb.addClass('-palibox')), $new('.-paliprop', sll, sla.addClass('-palibox'), $new('div', linput.addClass('-palishort').on('input change', updcolor)), slb.addClass('-palibox')))).click((e) => {
                        let p = $(e.target).parents().addBack().filter('.PaletteItem');
                        if (!p.hasClass('selected')) {
                            $('.PaletteItem.selected').removeClass('selected');
                            p.addClass('selected');
                        }
                    });
                })));
            }
            selPart(model.layers[0].parts[0]);
            addCompositeView([
                'ears_bg/human',
                'eyes/human',
                'hair/0', 'hair_bg/0',
                'head/human', 'face/human',
                'breasts/D',
                'arms/human', 'arms_bg/human',
                'legs/human',
                'torso/human'
            ]);
            addCompositeView([
                'ears_bg/elfin', 'ears/elfin',
                'eyes/human',
                'hair/0', 'hair_bg/0',
                'head/human', 'face/human',
                'breasts/D',
                'arms/human', 'arms_bg/human',
                'legs/human',
                'torso/human',
                'wings/scales', 'wings_bg/scales',
                'horns_bg/demon', 'tail/demon'
            ]);
            addCompositeView([
                'ears_bg/human',
                'eyes/human',
                'hair/slime', 'hair_bg/slime2',
                'head/goo', 'face/goo',
                'neck/goocore',
                'breasts/Dgoo',
                'arms/goo', 'arms_bg/goo',
                'legs/gooblob',
                'torso/goo'
            ]);
            addCompositeView([
                'ears_bg/human',
                'eyes/devil',
                'horns/devil', 'horns_bg/devil',
                'hair/0', 'hair_bg/0',
                'head/human', 'face/shark',
                'breasts/D',
                'arms/human', 'arms_bg/human',
                'hands/devil', 'hands_bg/devil',
                'legs/devil',
                'torso/human',
                'tail/goat'
            ]);
            addCompositeView([
                'ears_bg/elfin', 'ears/elfin',
                'eyes/human',
                'hair/feather', 'hair_bg/feather',
                'head/human', 'face/human_fang',
                'breasts/D',
                'arms/human', 'arms_bg/human',
                'hands/harpy', 'hands_bg/harpy',
                'legs/harpy_human',
                'torso/human',
                'wings/feather_large', 'wings_bg/feather_large',
                'tail/harpy'
            ]);
            addCompositeView([
                'ears/orca',
                'eyes/orca',
                'hair/0', 'hair_bg/0',
                'head/orca', 'face/orca',
                'breasts/Dskin2',
                'arms/orca', 'arms_bg/orca',
                'hands/fins-orca', 'hands_bg/fins-orca',
                'legs/orca',
                'torso/orca',
                'tail/orca'
            ]);
            addCompositeView([
                'ears/cat',
                'eyes/cat',
                'hair/0', 'hair_bg/0',
                'head/fur', 'face/fur',
                'breasts/Dfur',
                'arms/fur', 'arms_bg/fur',
                'legs/fur',
                'torso/fur',
                'tail/cat1', 'tail/cat2'
            ]);
            addCompositeView([
                'ears/human',
                'eyes/manticore',
                'hair/0', 'hair_bg/0',
                'head/human', 'face/human', 'neck/manticore',
                'breasts/D',
                'arms/manticore_sit',
                'legs/manticore_sit',
                'torso/human',
                'tail/manticore',
                'wings/mantibig', 'wings_bg/mantibig'
            ]);
            addCompositeView([
                'hair/gorgon', 'hair_bg/gorgon',
                'eyes/cat',
                'head/pscales', 'face/human_fang',
                'breasts/Dpscales',
                'arms_bg/pscales', 'arms/pscales',
                'ears_bg/naga', 'ears/naga',
                'legs/naga',
                'torso/pscales'
            ]);
            addCompositeView([
                'horns/2large',
                'eyes/cat',
                'head/scales', 'face/reptile',
                'breasts/Dscales',
                'arms/scales', 'arms_bg/scales',
                'legs/scales',
                'torso/scales',
                'tail/reptile', 'tail_fg/reptile_fire',
                'wings/scales',
                'wings_bg/scales',
            ]);
            addCompositeView([
                'antennae/bee',
                'eyes/sandtrap',
                'head/bee', 'face/insect',
                'breasts/Dbee',
                'arms/bee', 'arms_bg/bee',
                'legs/chitin',
                'torso/bee',
                'tail/bee_abdomen',
                'wings/bee'
            ]);
            addCompositeView([
                'horns/2large',
                'eyes/spider',
                'head/chitin', 'face/spider',
                'breasts/Dchitin',
                'arms/chitin2', 'arms_bg/chitin2',
                'legs_bg/drider',
                'torso/chitin'
            ]);
            addCompositeView([
                'ears_bg/mouse', 'ears/mouse',
                'eyes/cat',
                'head/fur', 'face/mouse',
                'breasts/Dfur_nn',
                'arms/fur', 'arms_bg/fur',
                'hands/fire', 'hands_bg/fire',
                'legs/fur',
                'feet/fire',
                'torso/fur', 'tail/mouse_fire'
            ]);
            addCompositeView([
                'ears_bg/weasel', 'ears/weasel',
                'eyes/cat',
                'head/human', 'face/human_fang', 'neck/weasel',
                'breasts/D',
                'arms/human', 'arms_bg/human',
                'legs/human',
                'torso/human',
                'hair/raiju', 'hair_bg/raiju3',
                'tail/weasel'
            ]);
            $('#ClipboardGrabber').on('paste', e => {
                e.stopPropagation();
                e.preventDefault();
                let cd = e.originalEvent.clipboardData;
                for (let i = 0, n = cd.items.length; i < n; i++) {
                    let item = cd.items[i];
                    if (item.type.indexOf('image/') == 0) {
                        grabData(item.getAsFile());
                        return;
                    }
                    else {
                        console.log('skip ' + item.kind + ' ' + item.type);
                    }
                }
                alert("Please paste 1 image data or file");
            });
        });
    }
    spred.loadModel = loadModel;
    function saveSomething(content) {
        $('#Loading').after($new('.row', $new('.col-12.card.card-success', $new('.card-block', $new('button.ctrl.text-danger.pull-left', $new('span.fa.fa-close')).click((e) => {
            $(e.target).parents('.row').remove();
        }), $new('button.ctrl.text-info.pull-left', $new('span.fa.fa-copy')).click((e) => {
            let ta = $(e.target).parents('.row').find('textarea');
            ta.focus().select();
            document.execCommand('copy');
            ta.val('Contents copied to clipboard!');
        }), $new('textarea.form-control').val(content)))).css('flex-shrink', '0'));
    }
    function savePalettes() {
        let s = ['    <palettes>'];
        for (let pname in spred.g_model.palettes) {
            s.push(`        <palette name="${pname}">`);
            for (let cname in spred.g_model.palettes[pname]) {
                s.push(`            <color name="${cname}">${spred.g_model.palettes[pname][cname]}</color>`);
            }
            s.push(`        </palette>`);
        }
        saveSomething(s.join('\r\n'));
    }
    spred.savePalettes = savePalettes;
    function saveSpritemaps() {
        saveSomething(spred.g_model.spritemaps.map(s => s.serialize()).join('\n'));
        /*<button type="button" class="close" data-dismiss="alert" aria-label="Close">
  <span aria-hidden="true">×</span>
</button>*/
    }
    spred.saveSpritemaps = saveSpritemaps;
    function initSpred() {
        loadFile(basedir + 'res/model.xml', 'xml').then(loadModel);
    }
    spred.initSpred = initSpred;
    $(() => {
        initSpred();
    });
})(spred || (spred = {}));