feat(miieditor): switch to mii-js, add missing props

This commit is contained in:
Ash Monty 2022-08-13 16:52:06 +02:00
parent c8b424e18e
commit deb091932a
No known key found for this signature in database
GPG Key ID: 740B7C88251D49B6
10 changed files with 441 additions and 522 deletions

164
package-lock.json generated
View File

@ -25,11 +25,11 @@
"gray-matter": "^4.0.3",
"kaitai-struct": "^0.9.0",
"marked": "^4.0.10",
"mii-js": "github:PretendoNetwork/mii-js#v1.0.2",
"mongoose": "^6.4.0",
"morgan": "^1.10.0",
"nodemailer": "^6.7.5",
"stripe": "^9.9.0",
"trello": "^0.11.0",
"uuid": "^8.3.2"
},
"devDependencies": {
@ -576,6 +576,11 @@
"node": ">= 0.8"
}
},
"node_modules/bit-buffer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz",
"integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA=="
},
"node_modules/bn.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@ -1489,11 +1494,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/es6-promise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y="
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -2915,6 +2915,14 @@
"node": ">= 0.6"
}
},
"node_modules/mii-js": {
"version": "1.0.2",
"resolved": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#d3386c348b8428fc8514dbb97a7c24ffefa3017e",
"license": "ISC",
"dependencies": {
"bit-buffer": "^0.2.5"
}
},
"node_modules/miller-rabin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
@ -3661,33 +3669,6 @@
"lowercase-keys": "^2.0.0"
}
},
"node_modules/restler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/restler/-/restler-3.3.0.tgz",
"integrity": "sha512-TrnPEY3DJgFJgPd5vJmHpOj0WlAzLcX2eIcAqc/EB/YlgPFz0aMD55GkQN16d3nX0ydlq93lthZWGKyBpVnFEg==",
"dependencies": {
"iconv-lite": "0.2.11",
"qs": "1.2.0",
"xml2js": "0.4.0",
"yaml": "0.2.3"
},
"engines": {
"node": ">= 0.10.x"
}
},
"node_modules/restler/node_modules/iconv-lite": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz",
"integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg=",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/restler/node_modules/qs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz",
"integrity": "sha512-XUf0O7rlGjbH+n7uqyT+xn362fmoPe4ehtHL6VK1nbSgQ7CqG0ZZLr1nU2EyXlRq++YphPdQ/5scjIWNMSPnhg=="
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -3734,11 +3715,6 @@
"node": ">=6"
}
},
"node_modules/sax": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
},
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@ -4274,19 +4250,6 @@
"node": ">=12"
}
},
"node_modules/trello": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz",
"integrity": "sha512-WMjfSTA+ybGnd36ZoMX7Ia7wfpU9KP2lsafv9mjLwFcE1ECp8b9/rX3uW4ivNO7jVLnHRyQ1pHsirbMvK0Ix2Q==",
"dependencies": {
"es6-promise": "~3.0.2",
"object-assign": "~4.1.0",
"restler": "~3.3.0"
},
"engines": {
"node": ">= 0.10.x"
}
},
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -4578,23 +4541,6 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/xml2js": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.0.tgz",
"integrity": "sha1-Ek/EEUtBKcgQgA7LKshs8lRiy5o=",
"dependencies": {
"sax": "0.5.x",
"xmlbuilder": ">=0.4.2"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"engines": {
"node": ">=8.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -4608,14 +4554,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yaml": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-0.2.3.tgz",
"integrity": "sha1-tUUOkudu82td0k42YAkeuu7z5cc=",
"engines": {
"node": "*"
}
}
},
"dependencies": {
@ -5045,6 +4983,11 @@
"safe-buffer": "5.1.2"
}
},
"bit-buffer": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/bit-buffer/-/bit-buffer-0.2.5.tgz",
"integrity": "sha512-x1yGnmXvFg6e3DiyRztElbcn1bsCTFSoM/ncAzY62uE0JdTl5xlKJd0ooqLYoPbhdsnpehSIQrdIvclcZJYwiA=="
},
"bn.js": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz",
@ -5812,11 +5755,6 @@
"is-symbol": "^1.0.2"
}
},
"es6-promise": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.0.2.tgz",
"integrity": "sha1-AQ1YWEI6XxGJeWZfRkhqlcbuK7Y="
},
"escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@ -6869,6 +6807,13 @@
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mii-js": {
"version": "git+ssh://git@github.com/PretendoNetwork/mii-js.git#d3386c348b8428fc8514dbb97a7c24ffefa3017e",
"from": "mii-js@github:PretendoNetwork/mii-js#v1.0.2",
"requires": {
"bit-buffer": "^0.2.5"
}
},
"miller-rabin": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz",
@ -7450,29 +7395,6 @@
"lowercase-keys": "^2.0.0"
}
},
"restler": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/restler/-/restler-3.3.0.tgz",
"integrity": "sha512-TrnPEY3DJgFJgPd5vJmHpOj0WlAzLcX2eIcAqc/EB/YlgPFz0aMD55GkQN16d3nX0ydlq93lthZWGKyBpVnFEg==",
"requires": {
"iconv-lite": "0.2.11",
"qs": "1.2.0",
"xml2js": "0.4.0",
"yaml": "0.2.3"
},
"dependencies": {
"iconv-lite": {
"version": "0.2.11",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.2.11.tgz",
"integrity": "sha1-HOYKOleGSiktEyH/RgnKS7llrcg="
},
"qs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-1.2.0.tgz",
"integrity": "sha512-XUf0O7rlGjbH+n7uqyT+xn362fmoPe4ehtHL6VK1nbSgQ7CqG0ZZLr1nU2EyXlRq++YphPdQ/5scjIWNMSPnhg=="
}
}
},
"rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
@ -7510,11 +7432,6 @@
"sparse-bitfield": "^3.0.3"
}
},
"sax": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/sax/-/sax-0.5.8.tgz",
"integrity": "sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE="
},
"section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@ -7932,16 +7849,6 @@
"punycode": "^2.1.1"
}
},
"trello": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/trello/-/trello-0.11.0.tgz",
"integrity": "sha512-WMjfSTA+ybGnd36ZoMX7Ia7wfpU9KP2lsafv9mjLwFcE1ECp8b9/rX3uW4ivNO7jVLnHRyQ1pHsirbMvK0Ix2Q==",
"requires": {
"es6-promise": "~3.0.2",
"object-assign": "~4.1.0",
"restler": "~3.3.0"
}
},
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
@ -8166,20 +8073,6 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"xml2js": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.0.tgz",
"integrity": "sha1-Ek/EEUtBKcgQgA7LKshs8lRiy5o=",
"requires": {
"sax": "0.5.x",
"xmlbuilder": ">=0.4.2"
}
},
"xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@ -8190,11 +8083,6 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yaml": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-0.2.3.tgz",
"integrity": "sha1-tUUOkudu82td0k42YAkeuu7z5cc="
}
}
}

View File

@ -33,6 +33,7 @@
"gray-matter": "^4.0.3",
"kaitai-struct": "^0.9.0",
"marked": "^4.0.10",
"mii-js": "github:PretendoNetwork/mii-js#v1.0.2",
"mongoose": "^6.4.0",
"morgan": "^1.10.0",
"nodemailer": "^6.7.5",

View File

@ -190,6 +190,49 @@ div.subtabs .subtabbtn.active:hover::before {
transform: scale(3);
}
.has-textinput {
grid-template-columns: 1fr 1fr;
grid-template-rows: auto;
grid-auto-rows: auto;
gap: 24px;
}
.has-textinput label {
display: block;
margin-bottom: 6px;
text-transform: uppercase;
font-size: 12px;
}
div.birthday {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: end;
gap: 12px;
}
div.birthday span {
margin-bottom: 12px;
}
.has-textinput .icons {
display: flex;
flex-flow: row nowrap;
justify-content: space-between;
}
.has-textinput .icons input[type="checkbox"] {
box-sizing: border-box;
margin: 0;
height: 49px;
width: 49px;
}
input[type="checkbox"]#allowCopying:checked {
background: no-repeat center/80% url(../images/copy.svg), var(--accent-shade-0);
}
input[type="checkbox"]#disableSharing:checked {
background: no-repeat center/80% url(../images/share.svg), var(--accent-shade-0);
}
input[type="checkbox"]#favorite:checked {
background: no-repeat center/80% url(../images/star.svg), var(--accent-shade-0);
}
form.params {
grid-template-columns: repeat(2, auto);
height: 618px;
@ -593,7 +636,7 @@ button * {
grid-row: 4;
}
fieldset:not(.has-sliders) {
fieldset:not(.has-sliders, .has-textinput) {
position: relative;
margin-bottom: -60px;
}
@ -648,6 +691,10 @@ button * {
width: 36px;
height: 36px;
}
.has-textinput {
grid-template-columns: 1fr;
}
}
@media screen and (max-width: 360px) {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-copy"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>

After

Width:  |  Height:  |  Size: 344 B

View File

@ -27,11 +27,11 @@
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:lockguides="false"
inkscape:zoom="5.979798"
inkscape:cx="65.888513"
inkscape:cy="316.23142"
inkscape:zoom="5.6568543"
inkscape:cx="104.56341"
inkscape:cy="351.43207"
inkscape:window-width="1920"
inkscape:window-height="1023"
inkscape:window-height="1025"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
@ -75,7 +75,7 @@
d="m 38.391224,328.78155 c -0.402278,0.001 -0.847854,0.0668 -1.343327,0.20523 a 0.43421497,0.4342385 0 0 0 -0.041,0.0153 c -0.576068,0.22485 -0.849154,0.72713 -0.944648,1.11432 -0.095,0.3872 -0.053,0.72084 -0.053,0.72084 a 0.43421497,0.4342385 0 0 0 0.642766,0.32564 c 1.156837,-0.6469 1.853099,-0.71378 2.39647,-0.61567 0.54327,0.0981 0.981247,0.4064 1.575614,0.64112 a 0.43421497,0.4342385 0 0 0 0.537671,-0.61568 c -0.252786,-0.45231 -0.562569,-0.89979 -1.012545,-1.23983 -0.450075,-0.34005 -1.039843,-0.5546 -1.758805,-0.55123 z m 7.218009,0 c -0.718861,-0.004 -1.308729,0.21119 -1.758704,0.55123 -0.450076,0.34005 -0.759759,0.78753 -1.012546,1.23982 a 0.43421497,0.4342385 0 0 0 0.537671,0.61568 c 0.594368,-0.2347 1.032244,-0.54301 1.575615,-0.64111 0.54327,-0.0981 1.239633,-0.0313 2.39647,0.61567 a 0.43421497,0.4342385 0 0 0 0.642665,-0.32565 c 0,0 0.043,-0.33363 -0.053,-0.72083 -0.09499,-0.38719 -0.36858,-0.88946 -0.944649,-1.11432 a 0.43421497,0.4342385 0 0 0 -0.041,-0.0153 c -0.495374,-0.13835 -0.941049,-0.20326 -1.343228,-0.20522 z" />
<path
id="path35997"
style="color:#000000;display:inline;fill:#cab1fb;stroke-width:0.750013;stroke-linecap:round;stroke-linejoin:round;-inkscape-stroke:none"
style="color:#000000;display:inline;fill:#cab1fb;stroke-width:0.750013;stroke-linecap:round;stroke-linejoin:round"
d="m 162.0003,323.99985 c -0.032,-2e-5 -0.064,0.005 -0.097,0.0132 -0.063,0.0167 -0.1206,0.0495 -0.167,0.0952 l -1.774,1.752 c -0.2381,0.23564 -0.071,0.64149 0.2637,0.64161 h 0.5655 v 2.28958 h -2.2896 v -0.56543 c -2e-4,-0.335 -0.406,-0.50179 -0.6416,-0.26368 l -1.752,1.77395 c -0.1444,0.14611 -0.1444,0.38124 0,0.52735 l 1.752,1.77396 c 0.2356,0.23811 0.6415,0.0713 0.6416,-0.26368 v -0.56543 h 2.2896 v 2.28958 h -0.5655 c -0.335,1.2e-4 -0.5018,0.40596 -0.2637,0.64161 l 1.774,1.75199 c 0.1461,0.14445 0.3813,0.14445 0.5274,0 l 1.774,-1.75199 c 0.2381,-0.23565 0.071,-0.64149 -0.2637,-0.64161 h -0.5655 v -2.28958 h 2.2896 v 0.56543 c 1e-4,0.335 0.406,0.50179 0.6416,0.26368 l 1.752,-1.77396 c 0.1444,-0.14611 0.1444,-0.38124 0,-0.52735 l -1.752,-1.77395 c -0.2356,-0.23811 -0.6415,-0.0713 -0.6416,0.26368 v 0.56543 h -2.2896 v -2.28958 h 0.5655 c 0.335,-1.2e-4 0.5018,-0.40597 0.2637,-0.64161 l -1.774,-1.752 c -0.071,-0.07 -0.1665,-0.10835 -0.2637,-0.1084 z" />
<g
id="g240118-7-6-5"
@ -10159,4 +10159,28 @@
d="m 132,314.66667 v 8 q 0,0.55 -0.39133,0.942 -0.392,0.39133 -0.942,0.39133 h -9.33334 q -0.55,0 -0.942,-0.39133 Q 120,323.21667 120,322.66667 v -9.33334 q 0,-0.55 0.39133,-0.942 0.392,-0.39133 0.942,-0.39133 h 8 z M 126,322 q 0.83333,0 1.41667,-0.58333 Q 128,320.83333 128,320 q 0,-0.83333 -0.58333,-1.41667 Q 126.83333,318 126,318 q -0.83333,0 -1.41667,0.58333 Q 124,319.16667 124,320 q 0,0.83333 0.58333,1.41667 Q 125.16667,322 126,322 Z m -4,-5.33333 h 6 V 314 h -6 z"
id="path1886"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.666667" />
<g
id="g22237"
transform="matrix(1.3304782,0,0,1.3304782,-66.039968,-106.69792)">
<path
id="path35997-1"
style="color:#000000;display:inline;fill:#cab1fb;stroke-width:0.750013;stroke-linecap:round;stroke-linejoin:round"
d="m 196.63289,328.58644 c -0.23697,-0.23679 -0.64189,-0.0678 -0.64015,0.26722 l 0.0156,2.49326 c 0.001,0.20545 0.16748,0.37176 0.37293,0.37292 l 2.49325,0.0156 c 0.33499,0.002 0.50381,-0.4034 0.26722,-0.64015 -0.0752,-0.0692 -1.22995,-1.21297 -2.50885,-2.50885 z"
sodipodi:nodetypes="ccccccc"
class="UnoptimicedTransforms"
transform="matrix(0.97300689,-0.22965134,-0.22965134,0.97300689,70.688242,54.684049)" />
<g
id="g22203"
transform="rotate(45,189.247,328.41497)">
<path
id="path218531-2-3-2"
style="display:inline;fill:#cab1fb;fill-opacity:1;stroke:none;stroke-width:0.689529px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 190.83423,328.86858 0.0108,-4.77785 c -0.56571,-1.27775 -2.25307,-1.83648 -3.18455,0.008 l -0.0118,4.77666 z"
sodipodi:nodetypes="ccccc" />
</g>
<path
d="m 189.04908,330.93537 c 0.10967,-0.0169 0.22718,-0.0749 0.32449,-0.1721 l 0.14184,-0.14252 c 0.19492,-0.19417 0.23679,-0.46541 0.0937,-0.6074 l -1.96579,-1.95306 c -0.14291,-0.14202 -0.41545,-0.1 -0.61039,0.0942 l -0.14162,0.14123 c -0.1948,0.19413 -0.23668,0.46539 -0.0937,0.60741 l 1.96563,1.95432 c 0.0719,0.0709 0.17567,0.0949 0.28589,0.0779 z"
style="display:inline;fill:#cab1fb;fill-opacity:1;stroke:none;stroke-width:0.590169px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
id="path22213" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 570 KiB

After

Width:  |  Height:  |  Size: 571 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-share-2"><circle cx="18" cy="5" r="3"></circle><circle cx="6" cy="12" r="3"></circle><circle cx="18" cy="19" r="3"></circle><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"></line><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"></line></svg>

After

Width:  |  Height:  |  Size: 438 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"></polygon></svg>

After

Width:  |  Height:  |  Size: 332 B

View File

@ -1,214 +0,0 @@
// Fetched from https://github.com/PretendoNetwork/account/blob/master/src/mii.js on 2021-12-16
const KaitaiStream = require('kaitai-struct/KaitaiStream');
class Mii extends KaitaiStream {
constructor(arrayBuffer, byteOffset) {
super(arrayBuffer, byteOffset);
this.decode();
}
decode() {
// Decode raw data
// A lot of this goes unused
this.unknown1 = this.readU1();
this.characterSet = this.readBitsIntBe(2);
this.regionLock = this.readBitsIntBe(2);
this.profanityFlag = this.readBitsIntBe(1) !== 0;
this.copying = this.readBitsIntBe(1) !== 0;
this.unknown2 = this.readBitsIntBe(2);
this.slotIndex = this.readBitsIntBe(4);
this.pageIndex = this.readBitsIntBe(4);
this.version = this.readBitsIntBe(4);
this.unknown3 = this.readBitsIntBe(4);
this.systemId = Array(8).fill().map(() => this.readU1());
this.avatarId = Array(4).fill().map(() => this.readU1());
this.clientId = Array(6).fill().map(() => this.readU1());
this.padding = this.readU2le();
this.miiMetaData = this.readU2le();
this.miiName = Buffer.from(this.readBytes(20)).toString('utf16le');
this.height = this.readU1();
this.build = this.readU1();
this.faceColor = this.readBitsIntBe(3);
this.faceType = this.readBitsIntBe(4);
this.mingle = this.readBitsIntBe(1) !== 0;
this.faceMakeup = this.readBitsIntBe(4);
this.faceWrinkles = this.readBitsIntBe(4);
this.alignToByte();
this.hairType = this.readU1();
this.unknown5 = this.readBitsIntBe(4);
this.hairFlip = this.readBitsIntBe(1) !== 0;
this.hairColor = this.readBitsIntBe(3);
this.alignToByte();
this.eyeData = this.readU4le();
this.eyebrowData = this.readU4le();
this.noseData = this.readU2le();
this.mouthData = this.readU2le();
this.mouthData2 = this.readU2le();
this.facialHairData = this.readU2le();
this.glassesData = this.readU2le();
this.moleData = this.readU2le();
this.creatorName = Buffer.from(this.readBytes(20)).toString('utf16le');
this.padding2 = this.readU2le();
this.checksum = this.readU2le();
// Carve out more specific data from the above values
// TODO: read these bits directly instead of getting them later
this.gender = (this.miiMetaData & 1);
this.birthMonth = ((this.miiMetaData >> 1) & 15);
this.birthDay = ((this.miiMetaData >> 5) & 31);
this.favoriteColor = ((this.miiMetaData >> 10) & 15);
this.favorite = ((this.miiMetaData >> 14) & 1);
this.eyeType = (this.eyeData & 63);
this.eyeColor = ((this.eyeData >> 6) & 7);
this.eyeSize = ((this.eyeData >> 9) & 7);
this.eyeStretch = ((this.eyeData >> 13) & 7);
this.eyeRotation = ((this.eyeData >> 16) & 31);
this.eyeHorizontal = ((this.eyeData >> 21) & 15);
this.eyeVertical = ((this.eyeData >> 25) & 31);
this.eyebrowType = (this.eyebrowData & 31);
this.eyebrowColor = ((this.eyebrowData >> 5) & 7);
this.eyebrowSize = ((this.eyebrowData >> 8) & 15);
this.eyebrowStretch = ((this.eyebrowData >> 12) & 7);
this.eyebrowRotation = ((this.eyebrowData >> 16) & 15);
this.eyebrowHorizontal = ((this.eyebrowData >> 21) & 15);
this.eyebrowVertical = ((this.eyebrowData >> 25) & 31);
this.noseType = (this.noseData & 31);
this.noseSize = ((this.noseData >> 5) & 15);
this.noseVertical = ((this.noseData >> 9) & 31);
this.mouthType = (this.mouthData & 63);
this.mouthColor = ((this.mouthData >> 6) & 7);
this.mouthSize = ((this.mouthData >> 9) & 15);
this.mouthStretch = ((this.mouthData >> 13) & 7);
this.mouthVertical = (this.mouthData2 & 31);
this.facialHairMustache = ((this.mouthData2 >> 5) & 7);
this.facialHairType = (this.facialHairData & 7);
this.facialHairColor = ((this.facialHairData >> 3) & 7);
this.facialHairSize = ((this.facialHairData >> 6) & 15);
this.facialHairVertical = ((this.facialHairData >> 10) & 31);
this.glassesType = (this.glassesData & 15);
this.glassesColor = (this.glassesData >> 4) & 7;
this.glassesSize = (this.glassesData >> 7) & 15;
this.glassesVertical = (this.glassesData >> 11) & 15;
this.moleEnable = (this.moleData >> 15);
this.moleSize = ((this.moleData >> 1) & 15);
this.moleHorizontal = ((this.moleData >> 5) & 31);
this.moleVertical = ((this.moleData >> 10) & 31);
}
toStudioMii() {
/*
Can also disable randomization with:
let miiStudioData = Buffer.alloc(0x2F);
let next = 256;
and removing "randomizer" and the "miiStudioData.writeUInt8(randomizer);" call
*/
const miiStudioData = Buffer.alloc(0x2F);
const randomizer = Math.floor(256 * Math.random());
let next = randomizer;
let pos = 1;
function encodeMiiPart(partValue) {
const encoded = (7 + (partValue ^ next)) % 256;
next = encoded;
miiStudioData.writeUInt8(encoded, pos);
pos++;
}
miiStudioData.writeUInt8(randomizer);
if (this.facialHairColor === 0) {
encodeMiiPart(8);
} else {
encodeMiiPart(this.facialHairColor);
}
encodeMiiPart(this.facialHairType);
encodeMiiPart(this.build);
encodeMiiPart(this.eyeStretch);
encodeMiiPart(this.eyeColor + 8);
encodeMiiPart(this.eyeRotation);
encodeMiiPart(this.eyeSize);
encodeMiiPart(this.eyeType);
encodeMiiPart(this.eyeHorizontal);
encodeMiiPart(this.eyeVertical);
encodeMiiPart(this.eyebrowStretch);
if (this.eyebrowColor === 0) {
encodeMiiPart(8);
} else {
encodeMiiPart(this.eyebrowColor);
}
encodeMiiPart(this.eyebrowRotation);
encodeMiiPart(this.eyebrowSize);
encodeMiiPart(this.eyebrowType);
encodeMiiPart(this.eyebrowHorizontal);
encodeMiiPart(this.eyebrowVertical);
encodeMiiPart(this.faceColor);
encodeMiiPart(this.faceMakeup);
encodeMiiPart(this.faceType);
encodeMiiPart(this.faceWrinkles);
encodeMiiPart(this.favoriteColor);
encodeMiiPart(this.gender);
if (this.glassesColor == 0) {
encodeMiiPart(8);
} else if (this.glassesColor < 6) {
encodeMiiPart(this.glassesColor + 13);
} else {
encodeMiiPart(0);
}
encodeMiiPart(this.glassesSize);
encodeMiiPart(this.glassesType);
encodeMiiPart(this.glassesVertical);
if (this.hairColor == 0) {
encodeMiiPart(8);
} else {
encodeMiiPart(this.hairColor);
}
encodeMiiPart(this.hairFlip ? 1 : 0);
encodeMiiPart(this.hairType);
encodeMiiPart(this.height);
encodeMiiPart(this.moleSize);
encodeMiiPart(this.moleEnable);
encodeMiiPart(this.moleHorizontal);
encodeMiiPart(this.moleVertical);
encodeMiiPart(this.mouthStretch);
if (this.mouthColor < 4) {
encodeMiiPart(this.mouthColor + 19);
} else {
encodeMiiPart(0);
}
encodeMiiPart(this.mouthSize);
encodeMiiPart(this.mouthType);
encodeMiiPart(this.mouthVertical);
encodeMiiPart(this.facialHairSize);
encodeMiiPart(this.facialHairMustache);
encodeMiiPart(this.facialHairVertical);
encodeMiiPart(this.noseSize);
encodeMiiPart(this.noseType);
encodeMiiPart(this.noseVertical);
return miiStudioData;
}
}
module.exports = Mii;

View File

@ -1,4 +1,4 @@
// This file gets automatically bundled with browserify when running the start script. This also means that after any update you're gonna need to restart the server.
// This file gets automatically bundled with browserify when running the start script. This also means that after any update you're gonna need to restart the server to see any changes.
// Prevent the user from reloading or leaving the page
window.addEventListener('beforeunload', function (e) {
@ -6,7 +6,7 @@ window.addEventListener('beforeunload', function (e) {
e.returnValue = '';
});
const Mii = require('./MiiClass.js');
const Mii = require('mii-js');
// MII RENDERER
@ -15,32 +15,52 @@ const encodedUserMiiData = document.querySelector(
'script#encodedUserMiiData'
).textContent;
document.querySelector('script#encodedUserMiiData').remove();
// We initialize the Mii object with the encoded data and render the Mii
// We initialize the Mii object
const mii = new Mii(Buffer.from(encodedUserMiiData, 'base64'));
// We set the img sources for the unedited miis in the save animation
const miiStudioNeutralUrl = mii.studioUrl({
width: 512,
bgColor: '13173300',
});
const miiStudioSorrowUrl = mii.studioUrl({
width: 512,
bgColor: '13173300',
expression: 'sorrow',
});
document.querySelector('.mii-comparison img.old-mii').src = miiStudioNeutralUrl;
document.querySelector('.mii-comparison.confirmed img.old-mii').src =
miiStudioSorrowUrl;
// Initial mii render
renderMii();
const oldMiiStudioData = mii.toStudioMii().toString('hex');
// initial setup for saving animation
document.querySelector('.mii-comparison img.old-mii').src = `https://studio.mii.nintendo.com/miis/image.png?data=${oldMiiStudioData}&type=face&expression=normal&width=512&bgColor=13173300`;
document.querySelector('.mii-comparison.confirmed img.old-mii').src = `https://studio.mii.nintendo.com/miis/image.png?data=${oldMiiStudioData}&type=face&expression=sorrow&width=512&bgColor=13173300`;
// This function renders the Mii on the page
function renderMii(type) {
type = type || 'all_body'; // Can be 'all_body' or 'face'
const miiStudioUrl = mii.studioUrl({
width: 512,
bgColor: '13173300',
type: type || 'all_body',
});
const miiStudioData = mii.toStudioMii().toString('hex');
document.querySelector(
'img#mii-img'
).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=${type}&expression=normal&width=512&bgColor=13173300`;
const faceMiiStudioUrl = mii.studioUrl({
width: 512,
bgColor: '13173300',
});
const faceMiiStudioSmileUrl = mii.studioUrl({
width: 512,
bgColor: '13173300',
expression: 'smile'
});
// sets the mii
document.querySelector('img#mii-img').src = miiStudioUrl;
// sets the new mii in the save tab to the new mii
document.querySelector(
'.mii-comparison img.new-mii'
).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=face&expression=normal&width=512&bgColor=13173300`;
document.querySelector(
'.mii-comparison.confirmed img.new-mii'
).src = `https://studio.mii.nintendo.com/miis/image.png?data=${miiStudioData}&type=face&expression=smile&width=512&bgColor=13173300`;
document.querySelector('.mii-comparison img.new-mii').src = faceMiiStudioUrl;
document.querySelector('.mii-comparison.confirmed img.new-mii').src = faceMiiStudioSmileUrl;
// this sets the mii height so that the face width stays the same
document.querySelector('img#mii-img').style.height = `${
@ -58,27 +78,91 @@ function renderMii(type) {
// This function updates a prop of the Mii and rerenders it
function updateMii(e, type) {
const prop = e.target.name;
const value = e.target.value;
mii[prop] = parseInt(value);
let value = e.target.value || e.target.defaultValue;
// if the value comes from a checkbox, we use the checked property
if (value === 'on' || value === 'off') {
value = e.target.checked;
}
// if the prop is disableSharing, we set the value to the opposite of the current value
if (prop === 'disableSharing') {
value = !value;
}
// Handle booleans, on/offs and strings
if (value === 'true' || value === 'false') {
mii[prop] = value === 'true';
} else if (value === 'on' || value === 'off') {
mii[prop] = value === 'on';
} else if (isNaN(parseInt(value))) {
mii[prop] = value;
} else {
mii[prop] = parseInt(value);
}
renderMii(type);
}
// prevent the user from inputting values out of the range
function handleRange(e) {
if (e.target.value === '') return;
const value = parseInt(e.target.value);
// if the value is out of range, set to the nearest in-range value
if (value > e.target.max) {
e.target.value = e.target.max;
} else if (value < e.target.min) {
e.target.value = e.target.min;
}
}
function handleCalendar(e) {
const month = parseInt(e.target.value);
// we get the days in the month (hardcoded to 2024 'cause it's a leap year)
const daysInMonth = new Date(2024, month, 0).getDate();
const birthDayElement = document.querySelector('input[type=\'number\']#birthDay');
// we set the max value for the day selector to the days in the month
birthDayElement.max = daysInMonth;
// if the day is greater than the days in the month, set it to the max
if (parseInt(birthDayElement.value) > daysInMonth) {
birthDayElement.value = daysInMonth;
}
}
function preventEmpty(e) {
if (e.target.value !== '') return;
e.target.value = e.target.defaultValue;
}
document.querySelectorAll('fieldset').forEach((fieldset) => {
fieldset.addEventListener('change', (e) => updateMii(e));
});
document.querySelectorAll('input[type=\'range\']').forEach((fieldset) => {
fieldset.addEventListener('input', (e) => updateMii(e));
document.querySelectorAll('input[type=\'range\']').forEach((input) => {
input.addEventListener('input', (e) => updateMii(e));
});
document.querySelectorAll('input[type=\'text\'], input[type=\'number\']').forEach((input) => {
input.addEventListener('blur', (e) => preventEmpty(e));
});
document.querySelector('input[type=\'number\']#birthMonth').addEventListener('blur', (e) => handleCalendar(e));
// FORM
// Here we preselect the options corresponding to the Mii's current values
[
'faceType',
'faceColor',
'faceMakeup',
'faceWrinkles',
'skinColor',
'makeupType',
'wrinklesType',
'hairType',
'hairColor',
'eyebrowType',
@ -90,45 +174,78 @@ document.querySelectorAll('input[type=\'range\']').forEach((fieldset) => {
'mouthColor',
'glassesType',
'glassesColor',
'facialHairType',
'beardType',
'facialHairColor',
'facialHairMustache',
'moleEnable',
'mustacheType',
'moleEnabled',
'gender',
'favoriteColor'
'favoriteColor',
].forEach((prop) => {
document.querySelector(`#${prop}${mii[prop]}`).checked = true;
const el = document.querySelector(`#${prop}${mii[prop]}`);
if (el) {
el.checked = true;
}
console.log(`[info] preselected value for ${prop}`);
});
[
'eyebrowVertical',
'eyebrowHorizontal',
'eyebrowRotation',
'eyebrowSize',
'eyebrowStretch',
'eyeVertical',
'eyeHorizontal',
'eyeRotation',
'eyeSize',
'eyeStretch',
'noseVertical',
'noseSize',
'mouthVertical',
'mouthSize',
'mouthStretch',
'glassesVertical',
'glassesSize',
'facialHairVertical',
'facialHairSize',
'moleVertical',
'moleHorizontal',
'moleSize',
'height',
'build'
'favorite',
'allowCopying'
].forEach((prop) => {
document.querySelector(`#${prop}`).value = mii[prop];
const el = document.querySelector(`#${prop}`);
if (el) {
el.checked = mii[prop];
}
console.log(`[info] preselected value for ${prop}`);
});
document.querySelector('#disableSharing').checked = !mii.disableSharing;
console.log('[info] preselected value for disableSharing');
[
'eyebrowYPosition',
'eyebrowSpacing',
'eyebrowRotation',
'eyebrowScale',
'eyebrowVerticalStretch',
'eyeYPosition',
'eyeSpacing',
'eyeRotation',
'eyeScale',
'eyeVerticalStretch',
'noseYPosition',
'noseScale',
'mouthYPosition',
'mouthScale',
'mouthHorizontalStretch',
'glassesYPosition',
'glassesScale',
'mustacheYPosition',
'mustacheScale',
'moleYPosition',
'moleXPosition',
'moleScale',
'height',
'build',
'miiName',
'creatorName',
'birthDay',
'birthMonth',
].forEach((prop) => {
document.querySelector(`#${prop}`).value = mii[prop];
document.querySelector(`#${prop}`).defaultValue = mii[prop];
console.log(`[info] preselected value for ${prop}`);
});
[
'birthDay',
'birthMonth'
].forEach((prop) => {
document.querySelector(`#${prop}`).addEventListener('input', (e) => {
handleRange(e);
});
});
// TABS, SUBTABS, AND ALL THE INHERENT JANK
@ -136,19 +253,22 @@ function openTab(e, tabType) {
e.preventDefault();
// Deselect all subpages
document.querySelectorAll('.subtab.has-subpages .subpage.active').forEach((el) => {
el.classList?.remove('active');
});
document
.querySelectorAll('.subtab.has-subpages .subpage.active')
.forEach((el) => {
el.classList?.remove('active');
});
document.querySelectorAll('.subtab.active').forEach((el) => {
el.classList?.remove('active');
});
const buttonReplacement = tabType.charAt(0).toUpperCase() + tabType.slice(1);
const buttonReplacement =
tabType.charAt(0).toUpperCase() + tabType.slice(1);
document.querySelectorAll(`.${tabType}.active`).forEach(el => {
document.querySelectorAll(`.${tabType}.active`).forEach((el) => {
el?.classList?.remove('active');
});
document.querySelectorAll(`.${tabType}btn.active`).forEach(el => {
document.querySelectorAll(`.${tabType}btn.active`).forEach((el) => {
el?.classList?.remove('active');
});
@ -170,9 +290,7 @@ function openTab(e, tabType) {
});
// Selects the first subpage if there is one
document
.querySelector(`#${selectedID} .subpage`)
?.classList?.add('active');
document.querySelector(`#${selectedID} .subpage`)?.classList?.add('active');
}
// Here we bind all of the functions to the corresponding buttons
@ -223,26 +341,41 @@ document.querySelectorAll('button.page-btn').forEach((el) => {
});
// mii saving business (animation jank & actual saving)
document.querySelector('#saveTab #saveButton').addEventListener('click', (e) => {
e.preventDefault();
document
.querySelector('#saveTab #saveButton')
.addEventListener('click', (e) => {
e.preventDefault();
document.querySelector('#saveTab #saveButton').classList.add('inactive', 'fade-out');
document.querySelector('.tabs').style.pointerEvents = 'none';
document.querySelector('.mii-comparison.confirmed').style.opacity = 1;
document.querySelector('#saveTab p.save-prompt').classList.add('fade-out');
document
.querySelector('#saveTab #saveButton')
.classList.add('inactive', 'fade-out');
document.querySelector('.tabs').style.pointerEvents = 'none';
document.querySelector('.mii-comparison.confirmed').style.opacity = 1;
document
.querySelector('#saveTab p.save-prompt')
.classList.add('fade-out');
setTimeout(() => {
document.querySelector('.mii-comparison.unconfirmed').style.opacity = 0;
}, 500);
setTimeout(() => {
document.querySelector(
'.mii-comparison.unconfirmed'
).style.opacity = 0;
}, 500);
setTimeout(() => {
document.querySelector('.mii-comparison.confirmed .old-mii').classList.add('fade-out');
document.querySelector('.mii-comparison.confirmed svg').classList.add('fade-out');
}, 1500);
setTimeout(() => {
document
.querySelector('.mii-comparison.confirmed .old-mii')
.classList.add('fade-out');
document
.querySelector('.mii-comparison.confirmed svg')
.classList.add('fade-out');
}, 1500);
setTimeout(() => {
document.querySelector('.mii-comparison.confirmed .new-mii-wrapper').classList.add('centered-mii-img');
}, 2000);
setTimeout(() => {
document
.querySelector('.mii-comparison.confirmed .new-mii-wrapper')
.classList.add('centered-mii-img');
}, 2000);
});
alert(mii.encode().toString('base64'));
// CHECK IF MII IS VALID SERVERSIDE
});

View File

@ -37,8 +37,8 @@
<button class="tabbtn" id="noseTypeButton" style="--assetcol: 4"></button>
<button class="tabbtn" id="mouthTypeButton" style="--assetcol: 5"></button>
<button class="tabbtn" id="glassesTypeButton" style="--assetcol: 6"></button>
<button class="tabbtn" id="facialHairTypeButton"style="--assetcol: 7"></button>
<button class="tabbtn" id="moleEnableButton" style="--assetcol: 8"></button>
<button class="tabbtn" id="beardTypeButton"style="--assetcol: 7"></button>
<button class="tabbtn" id="moleEnabledButton" style="--assetcol: 8"></button>
<button class="tabbtn" id="miiscButton" style="--assetcol: 9"></button>
<button class="tabbtn" id="saveButton" style="--assetcol: 10"></button>
</div>
@ -46,9 +46,9 @@
<div class="tab active" id="faceTypeTab">
<div class="subtabs">
<button class="subtabbtn active" id="faceTypeSubButton" style="--assetcol: 0"></button>
<button class="subtabbtn" id="faceMakeupSubButton" style="--assetcol: 1"></button>
<button class="subtabbtn" id="faceWrinklesSubButton" style="--assetcol: 2"></button>
<button class="subtabbtn" id="faceColorSubButton" style="--assetcol: 12"></button>
<button class="subtabbtn" id="makeupTypeSubButton" style="--assetcol: 1"></button>
<button class="subtabbtn" id="wrinklesTypeSubButton" style="--assetcol: 2"></button>
<button class="subtabbtn" id="skinColorSubButton" style="--assetcol: 12"></button>
</div>
<fieldset class="subtab active" id="faceType" style="--assetrow: 0">
@ -58,32 +58,32 @@
{{/each}}
</fieldset>
<fieldset id="faceColor" class="subtab color">
<input type="radio" name="faceColor" id="faceColor0" value="0" />
<label for="faceColor0" style="background: #FFCE98 !important;" ></label>
<input type="radio" name="faceColor" id="faceColor1" value="1" />
<label for="faceColor1" style="background: #FFBA64 !important;" ></label>
<input type="radio" name="faceColor" id="faceColor2" value="2" />
<label for="faceColor2" style="background: #FF7C3E !important;" ></label>
<input type="radio" name="faceColor" id="faceColor3" value="3" />
<label for="faceColor3" style="background: #FFB184 !important;" ></label>
<input type="radio" name="faceColor" id="faceColor4" value="4" />
<label for="faceColor4" style="background: #CA5326 !important;" ></label>
<input type="radio" name="faceColor" id="faceColor5" value="5" />
<label for="faceColor5" style="background: #752E17 !important;" ></label>
<fieldset id="skinColor" class="subtab color">
<input type="radio" name="skinColor" id="skinColor0" value="0" />
<label for="skinColor0" style="background: #FFCE98 !important;" ></label>
<input type="radio" name="skinColor" id="skinColor1" value="1" />
<label for="skinColor1" style="background: #FFBA64 !important;" ></label>
<input type="radio" name="skinColor" id="skinColor2" value="2" />
<label for="skinColor2" style="background: #FF7C3E !important;" ></label>
<input type="radio" name="skinColor" id="skinColor3" value="3" />
<label for="skinColor3" style="background: #FFB184 !important;" ></label>
<input type="radio" name="skinColor" id="skinColor4" value="4" />
<label for="skinColor4" style="background: #CA5326 !important;" ></label>
<input type="radio" name="skinColor" id="skinColor5" value="5" />
<label for="skinColor5" style="background: #752E17 !important;" ></label>
</fieldset>
<fieldset id="faceMakeup" class="subtab" style="--assetrow: 1">
<fieldset id="makeupType" class="subtab" style="--assetrow: 1">
{{#each editorJSON.arrayOf12}}
<input type="radio" name="faceMakeup" id="faceMakeup{{this}}" value="{{this}}" />
<label for="faceMakeup{{this}}" style="--assetcol: {{@key}}"></label>
<input type="radio" name="makeupType" id="makeupType{{this}}" value="{{this}}" />
<label for="makeupType{{this}}" style="--assetcol: {{@key}}"></label>
{{/each}}
</fieldset>
<fieldset id="faceWrinkles" class="subtab" style="--assetrow: 2">
<fieldset id="wrinklesType" class="subtab" style="--assetrow: 2">
{{#each editorJSON.arrayOf12}}
<input type="radio" name="faceWrinkles" id="faceWrinkles{{this}}" value="{{this}}" />
<label for="faceWrinkles{{this}}" style="--assetcol: {{@key}}"></label>
<input type="radio" name="wrinklesType" id="wrinklesType{{this}}" value="{{this}}" />
<label for="wrinklesType{{this}}" style="--assetcol: {{@key}}"></label>
{{/each}}
</fieldset>
@ -173,16 +173,16 @@
</fieldset>
<fieldset id="eyebrowPosition" class="subtab has-sliders">
<label for="eyebrowVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="eyebrowVertical" id="eyebrowVertical" min="3" max="18" />
<label for="eyebrowHorizontal" style="--assetcol: 4"></label>
<input type="range" list="tickmarks" name="eyebrowHorizontal" id="eyebrowHorizontal" min="0" max="12" />
<label for="eyebrowYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="eyebrowYPosition" id="eyebrowYPosition" min="3" max="18" />
<label for="eyebrowSpacing" style="--assetcol: 4"></label>
<input type="range" list="tickmarks" name="eyebrowSpacing" id="eyebrowSpacing" min="0" max="12" />
<label for="eyebrowRotation" style="--assetcol: 5"></label>
<input type="range" list="tickmarks" class="invert" name="eyebrowRotation" id="eyebrowRotation" min="0" max="11" />
<label for="eyebrowSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="eyebrowSize" id="eyebrowSize" min="0" max="8" />
<label for="eyebrowStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="eyebrowStretch" id="eyebrowStretch" min="0" max="6" />
<label for="eyebrowScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="eyebrowScale" id="eyebrowScale" min="0" max="8" />
<label for="eyebrowVerticalStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="eyebrowVerticalStretch" id="eyebrowVerticalStretch" min="0" max="6" />
</fieldset>
<fieldset id="eyebrowColor" class="subtab color">
@ -251,16 +251,16 @@
</fieldset>
<fieldset id="eyePosition" class="subtab has-sliders">
<label for="eyeVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="eyeVertical" id="eyeVertical" min="0" max="18" />
<label for="eyeHorizontal" style="--assetcol: 4"></label>
<input type="range" list="tickmarks" name="eyeHorizontal" id="eyeHorizontal" min="0" max="12" />
<label for="eyeYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="eyeYPosition" id="eyeYPosition" min="0" max="18" />
<label for="eyeSpacing" style="--assetcol: 4"></label>
<input type="range" list="tickmarks" name="eyeSpacing" id="eyeSpacing" min="0" max="12" />
<label for="eyeRotation" style="--assetcol: 5"></label>
<input type="range" class="invert" list="tickmarks" name="eyeRotation" id="eyeRotation" min="0" max="7" />
<label for="eyeSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="eyeSize" id="eyeSize" min="0" max="7" />
<label for="eyeStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="eyeStretch" id="eyeStretch" min="0" max="6" />
<label for="eyeScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="eyeScale" id="eyeScale" min="0" max="7" />
<label for="eyeVerticalStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="eyeVerticalStretch" id="eyeVerticalStretch" min="0" max="6" />
</fieldset>
</div>
@ -294,10 +294,10 @@
</fieldset>
<fieldset id="nosePosition" class="subtab has-sliders">
<label for="noseVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="noseVertical" id="noseVertical" min="0" max="18" />
<label for="noseSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="noseSize" id="noseSize" min="0" max="8" />
<label for="noseYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="noseYPosition" id="noseYPosition" min="0" max="18" />
<label for="noseScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="noseScale" id="noseScale" min="0" max="8" />
</fieldset>
</div>
@ -346,12 +346,12 @@
</fieldset>
<fieldset id="mouthPosition" class="subtab has-sliders">
<label for="mouthVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="mouthVertical" id="mouthVertical" min="0" max="18" />
<label for="mouthSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="mouthSize" id="mouthSize" min="0" max="8" />
<label for="mouthStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="mouthStretch" id="mouthStretch" min="0" max="6" />
<label for="mouthYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="mouthYPosition" id="mouthYPosition" min="0" max="18" />
<label for="mouthScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="mouthScale" id="mouthScale" min="0" max="8" />
<label for="mouthHorizontalStretch" style="--assetcol: 7"></label>
<input type="range" list="tickmarks" name="mouthHorizontalStretch" id="mouthHorizontalStretch" min="0" max="6" />
</fieldset>
</div>
@ -384,25 +384,25 @@
</fieldset>
<fieldset id="glassesPosition" class="subtab has-sliders">
<label for="glassesVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="glassesVertical" id="glassesVertical" min="0" max="20" />
<label for="glassesSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="glassesSize" id="glassesSize" min="0" max="7" />
<label for="glassesYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="glassesYPosition" id="glassesYPosition" min="0" max="20" />
<label for="glassesScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="glassesScale" id="glassesScale" min="0" max="7" />
</fieldset>
</div>
<div class="tab" id="facialHairTypeTab">
<div class="tab" id="beardTypeTab">
<div class="subtabs">
<button class="subtabbtn active" id="facialHairTypeSubButton" style="--assetcol: 8"></button>
<button class="subtabbtn" id="facialHairMustacheSubButton" style="--assetcol: 9"></button>
<button class="subtabbtn active" id="beardTypeSubButton" style="--assetcol: 8"></button>
<button class="subtabbtn" id="mustacheTypeSubButton" style="--assetcol: 9"></button>
<button class="subtabbtn" id="facialHairPositionSubButton" style="--assetcol: 13"></button>
<button class="subtabbtn" id="facialHairColorSubButton" style="--assetcol: 12"></button>
</div>
<fieldset id="facialHairType" class="subtab active" style="--assetrow: 9">
<fieldset id="beardType" class="subtab active" style="--assetrow: 9">
{{#each editorJSON.arrayOf6}}
<input type="radio" name="facialHairType" id="facialHairType{{this}}" value="{{this}}" />
<label for="facialHairType{{this}}" style="--assetcol: {{@key}}"></label>
<input type="radio" name="beardType" id="beardType{{this}}" value="{{this}}" />
<label for="beardType{{this}}" style="--assetcol: {{@key}}"></label>
{{/each}}
</fieldset>
@ -425,40 +425,40 @@
<label for="facialHairColor7" style="background: #FEB454 !important;" ></label>
</fieldset>
<fieldset id="facialHairMustache" class="subtab active" style="--assetrow: 10">
<fieldset id="mustacheType" class="subtab active" style="--assetrow: 10">
{{#each editorJSON.arrayOf6}}
<input type="radio" name="facialHairMustache" id="facialHairMustache{{this}}" value="{{this}}" />
<label for="facialHairMustache{{this}}" style="--assetcol: {{@key}}"></label>
<input type="radio" name="mustacheType" id="mustacheType{{this}}" value="{{this}}" />
<label for="mustacheType{{this}}" style="--assetcol: {{@key}}"></label>
{{/each}}
</fieldset>
<fieldset id="facialHairPosition" class="subtab has-sliders">
<label for="facialHairVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="facialHairVertical" id="facialHairVertical" min="0" max="16" />
<label for="facialHairSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="facialHairSize" id="facialHairSize" min="0" max="8" />
<label for="mustacheYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="mustacheYPosition" id="mustacheYPosition" min="0" max="16" />
<label for="mustacheScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="mustacheScale" id="mustacheScale" min="0" max="8" />
</fieldset>
</div>
<div class="tab" id="moleEnableTab">
<div class="tab" id="moleEnabledTab">
<div class="subtabs">
<button class="subtabbtn active" id="moleEnableSubButton" style="--assetcol: 10"></button>
<button class="subtabbtn active" id="moleEnabledSubButton" style="--assetcol: 10"></button>
<button class="subtabbtn" id="molePositionSubButton" style="--assetcol: 13"></button>
</div>
<fieldset id="moleEnable" class="subtab active" style="--assetrow: 11">
<input type="radio" name="moleEnable" id="moleEnable0" value="0" />
<label for="moleEnable0" style="--assetcol: 0"></label>
<input type="radio" name="moleEnable" id="moleEnable1" value="1" />
<label for="moleEnable1" style="--assetcol: 1"></label>
<fieldset id="moleEnabled" class="subtab active" style="--assetrow: 11">
<input type="radio" name="moleEnabled" id="moleEnabledfalse" value="false" />
<label for="moleEnabledfalse" style="--assetcol: 0"></label>
<input type="radio" name="moleEnabled" id="moleEnabledtrue" value="true" />
<label for="moleEnabledtrue" style="--assetcol: 1"></label>
</fieldset>
<fieldset id="molePosition" class="subtab has-sliders">
<label for="moleVertical" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="moleVertical" id="moleVertical" min="0" max="30" />
<label for="moleHorizontal" style="--assetcol: 3"></label>
<input type="range" list="tickmarks" name="moleHorizontal" id="moleHorizontal" min="0" max="16" />
<label for="moleSize" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="moleSize" id="moleSize" min="0" max="8" />
<label for="moleYPosition" style="--assetcol: 2"></label>
<input type="range" list="tickmarks" class="invert" name="moleYPosition" id="moleYPosition" min="0" max="30" />
<label for="moleXPosition" style="--assetcol: 3"></label>
<input type="range" list="tickmarks" name="moleXPosition" id="moleXPosition" min="0" max="16" />
<label for="moleScale" style="--assetcol: 6"></label>
<input type="range" list="tickmarks" name="moleScale" id="moleScale" min="0" max="8" />
</fieldset>
</div>
@ -467,6 +467,7 @@
<button class="subtabbtn active" id="sizeSubButton" style="--assetcol: 14"></button>
<button class="subtabbtn" id="genderSubButton" style="--assetcol: 11"></button>
<button class="subtabbtn" id="favoriteColorSubButton" style="--assetcol: 12"></button>
<button class="subtabbtn" id="infoSubButton" style="--assetcol: 15"></button>
</div>
<fieldset id="size" class="subtab active has-sliders">
@ -483,6 +484,42 @@
<label for="gender1" style="--assetcol: 1"></label>
</fieldset>
<fieldset id="info" class="subtab active has-textinput">
<div>
<label for="miiName">Nickname</label>
<input type="text" name="miiName" id="miiName" minLength="1" maxLength="10" />
</div>
<div>
<label for="creatorName">Creator</label>
<input type="text" name="creatorName" id="creatorName" minLength="1" maxLength="10" />
</div>
<div class="birthday">
<div>
<label for="birthDay">Birth day</label>
<input type="number" name="birthDay" id="birthDay" min="1" max="31" />
</div>
<span>/</span>
<div>
<label for="birthMonth">Month</label>
<input type="number" name="birthMonth" id="birthMonth" min="1" max="12" />
</div>
</div>
<div class="icons">
<div>
<label for="favorite">Favorite</label>
<input type="checkbox" name="favorite" id="favorite" />
</div>
<div>
<label for="disableSharing">Sharing</label>
<input type="checkbox" name="disableSharing" id="disableSharing" />
</div>
<div>
<label for="allowCopying">Copying</label>
<input type="checkbox" name="allowCopying" id="allowCopying" />
</div>
</div>
</fieldset>
<fieldset id="favoriteColor" class="subtab">
<input type="radio" name="favoriteColor" id="favoriteColor0" value="0" />
<label for="favoriteColor0" style="background: #FF2216 !important;" ></label>