this really works!
16
data/images/imageCSV.csv
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
displayName,imagePath,maskOptions
|
||||||
|
White,m15WFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Blue,m15UFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Black,m15BFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Red,m15RFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Green,m15GFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Multicolored,m15MFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Artifact,m15AFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Colorless,m15CFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
White Land,m15WLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Blue Land,m15ULFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Black Land,m15BLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Red Land,m15RLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Green Land,m15GLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Multicolored Land,m15MLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
||||||
|
Colorless Land,m15CLFull.png,Full-0-0-750-1050;Title-0-0-750-1050;Type-0-0-750-1050;Rules Text-0-0-750-1050;Pinline-0-0-750-1050;Frame-0-0-750-1050
|
|
BIN
data/images/masks/Border.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
data/images/masks/Frame.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
data/images/masks/Full.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
data/images/masks/Pinline.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
data/images/masks/RightHalf.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
data/images/masks/RulesText.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
data/images/masks/Title.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
data/images/masks/Type.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
@@ -1,36 +1,200 @@
|
|||||||
//============================================//
|
//============================================//
|
||||||
// Card Conjurer, by Kyle Burton //
|
// Card Conjurer, by Kyle Burton //
|
||||||
//============================================//
|
//============================================//
|
||||||
|
/* Test things! */
|
||||||
|
function testFunction() {
|
||||||
|
// console.log("test function begin");
|
||||||
|
// cardMaster.innerHTML += frameList[0].cardMasterElement("Full");
|
||||||
|
// console.log("test function end");
|
||||||
|
// cardMasterUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Initiate! */
|
/* Initiate! */
|
||||||
window.onload = initiate
|
window.onload = initiate;
|
||||||
function initiate() {
|
function initiate() {
|
||||||
window.cardWidth = 750;
|
window.cardWidth = 750;
|
||||||
window.cardHeight = 1050;
|
window.cardHeight = 1050;
|
||||||
|
window.frameList = new Array();
|
||||||
|
window.maskNameList = ["Right Half", "Full", "Title", "Type", "Rules Text", "Pinline", "Frame"];
|
||||||
|
window.maskList = [];
|
||||||
|
window.selectedFrame = -1;
|
||||||
|
window.selectedMask = "";
|
||||||
|
for (var i = 0; i < maskNameList.length; i++) {
|
||||||
|
var imageSource = "data/images/masks/" + maskNameList[i].replace(" ", "") + ".png";
|
||||||
|
maskList[i] = new Image();
|
||||||
|
maskList[i].src = imageSource;
|
||||||
|
}
|
||||||
|
window.cardMaster = document.getElementById("cardMaster");
|
||||||
window.displayCanvas = document.getElementById("displayCanvas");
|
window.displayCanvas = document.getElementById("displayCanvas");
|
||||||
document.getElementById("displayCanvas").width = cardWidth;
|
document.getElementById("displayCanvas").width = cardWidth;
|
||||||
document.getElementById("displayCanvas").height = cardHeight;
|
document.getElementById("displayCanvas").height = cardHeight;
|
||||||
window.displayContext = displayCanvas.getContext("2d");
|
window.displayContext = displayCanvas.getContext("2d");
|
||||||
// loadScript("data/scripts/sortable.js");
|
newCanvas("frameMask");
|
||||||
import Sortable from './data/scripts/sortable.js';
|
newCanvas("frameFinal");
|
||||||
// var el = document.getElementById('items');
|
newCanvas("cardFinal");
|
||||||
// var sortable = Sortable.create(el);
|
//Loads up anything that uses Sortable.js
|
||||||
console.log("init done")
|
var sortable = Sortable.create(cardMaster, {animation: 150, ghostClass: "cardMasterElementMoving"});
|
||||||
|
//initiation is complete, ready to load image data
|
||||||
|
console.log("init done");
|
||||||
|
loadImageCSV();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Loads all the image info from the CSV! */
|
||||||
|
function loadImageCSV() {
|
||||||
|
var xhttp = new XMLHttpRequest();
|
||||||
|
xhttp.onreadystatechange = function() {
|
||||||
|
if (this.readyState == 4) {
|
||||||
|
var splitImageCSV = xhttp.responseText.split("\n");
|
||||||
|
for (var i = 1; i < splitImageCSV.length; i++) {
|
||||||
|
var splitIndividualImageCSV = splitImageCSV[i].split(",");
|
||||||
|
frameList[frameList.length] = new frameImage(splitIndividualImageCSV[0], "data/images/" + splitIndividualImageCSV[1], splitIndividualImageCSV[2]);
|
||||||
|
}
|
||||||
|
console.log("frame list loaded");
|
||||||
|
for (var i = 0; i < frameList.length; i++) {
|
||||||
|
// frameList[i].framePickerElement(document.getElementById("framePicker"));
|
||||||
|
document.getElementById("framePicker").innerHTML += frameList[i].framePickerElement();
|
||||||
|
}
|
||||||
|
//I don't like these here, because even though they run, it doesn't populate the mask options
|
||||||
|
// document.getElementsByClassName("frameOption")[0].classList.add("frameOptionSelected");
|
||||||
|
// selectedMask = document.getElementsByClassName("frameOption")[0].id.replace("frameIndex", "");
|
||||||
|
setTimeout(testFunction, 500); //deleteme
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhttp.open("GET", "data/images/imageCSV.csv", true);
|
||||||
|
xhttp.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Image Class */
|
||||||
|
class frameImage {
|
||||||
|
constructor(display, path, masks) {
|
||||||
|
this.displayName = display;
|
||||||
|
this.image = new Image();
|
||||||
|
this.image.src = path;
|
||||||
|
this.maskOptionList = new Array();
|
||||||
|
this.xList = new Array();
|
||||||
|
this.yList = new Array();
|
||||||
|
this.widthList = new Array();
|
||||||
|
this.heightList = new Array();
|
||||||
|
var splitMasks = masks.split(";");
|
||||||
|
for (var i = 0; i < splitMasks.length; i++) {
|
||||||
|
var splitIndividualMasks = splitMasks[i].split("-");
|
||||||
|
this.maskOptionList[i] = splitIndividualMasks[0];
|
||||||
|
this.xList[i] = scale(parseInt(splitIndividualMasks[1]));
|
||||||
|
this.yList[i] = scale(parseInt(splitIndividualMasks[2]));
|
||||||
|
this.widthList[i] = scale(parseInt(splitIndividualMasks[3]));
|
||||||
|
this.heightList[i] = scale(parseInt(splitIndividualMasks[4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cardMasterElement(targetMask, right) {
|
||||||
|
var extraMask = ""
|
||||||
|
if (right) {
|
||||||
|
extraMask = " - Right"
|
||||||
|
}
|
||||||
|
return "<div id='frameIndex" + frameList.indexOf(this) + "' class='cardMasterElement'>" + this.displayName + " (" + targetMask + extraMask + ")<span class='closeCardMasterElement' onclick='deleteCardMasterElement(event)'>x</span></div>";
|
||||||
|
}
|
||||||
|
framePickerElement(targetElement) {
|
||||||
|
return "<div id='frameIndex" + frameList.indexOf(this) + "' class='frameOption' onclick='frameOptionClicked(event)'><img src=" + this.image.src + "></div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* User input for card master */
|
||||||
|
function frameOptionClicked(event) {
|
||||||
|
//Takes the clicked element, determines the right frame image index, sets the selected frame, and displays available masks
|
||||||
|
//most importantly, stores the selected frame under 'selectedFrame'
|
||||||
|
var clickedElement = event.target;
|
||||||
|
if (clickedElement.nodeName == "IMG") {
|
||||||
|
clickedElement = event.target.parentElement;
|
||||||
|
}
|
||||||
|
var frameOptionList = document.getElementsByClassName("frameOption");
|
||||||
|
for (var i = 0; i < frameOptionList.length; i++) {
|
||||||
|
frameOptionList[i].classList.remove("frameOptionSelected");
|
||||||
|
}
|
||||||
|
clickedElement.classList.add("frameOptionSelected");
|
||||||
|
clickedElementIndex = clickedElement.id.replace("frameIndex", "");
|
||||||
|
if (clickedElementIndex != selectedFrame) {
|
||||||
|
selectedFrame = parseInt(clickedElementIndex);
|
||||||
|
document.getElementById("maskPicker").innerHTML = "";
|
||||||
|
for (var i = 0; i < frameList[selectedFrame].maskOptionList.length; i++) {
|
||||||
|
document.getElementById("maskPicker").innerHTML += "<div class='maskOption' onclick='maskOptionClicked(event)' id='maskName" + frameList[selectedFrame].maskOptionList[i] + "'><img src='" + maskList[maskNameList.indexOf(frameList[selectedFrame].maskOptionList[i])].src + "'>" + frameList[selectedFrame].maskOptionList[i] + "</div>";
|
||||||
|
}
|
||||||
|
document.getElementsByClassName("maskOption")[0].classList.add("maskOptionSelected");
|
||||||
|
selectedMask = document.getElementsByClassName("maskOption")[0].id.replace("maskName", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function maskOptionClicked(event) {
|
||||||
|
//Determines which mask was selected, and stores that value under 'selectedMask'
|
||||||
|
var clickedElement = event.target;
|
||||||
|
if (clickedElement.nodeName == "IMG") {
|
||||||
|
clickedElement = event.target.parentElement;
|
||||||
|
}
|
||||||
|
var maskOptionList = document.getElementsByClassName("maskOption");
|
||||||
|
for (var i = 0; i < maskOptionList.length; i++) {
|
||||||
|
maskOptionList[i].classList.remove("maskOptionSelected");
|
||||||
|
}
|
||||||
|
clickedElement.classList.add("maskOptionSelected");
|
||||||
|
selectedMask = clickedElement.id.replace("maskName", "");
|
||||||
|
}
|
||||||
|
function addFrameToCardMaster(right = false) {
|
||||||
|
//Takes the stored selectedFrame and selectedMask to add the frame w/ mask to the card master!
|
||||||
|
if (selectedFrame > -1 && selectedMask != "") {
|
||||||
|
cardMaster.innerHTML = frameList[selectedFrame].cardMasterElement(selectedMask, right) + cardMaster.innerHTML;
|
||||||
|
cardMasterUpdated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function deleteCardMasterElement(event) {
|
||||||
|
event.target.parentElement.parentElement.removeChild(event.target.parentElement);
|
||||||
|
cardMasterUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Card Master Cool Stuff! */
|
||||||
|
function cardMasterUpdated() {
|
||||||
|
console.log("The card master is updating!");
|
||||||
|
frameFinalContext.clearRect(0, 0, cardWidth, cardHeight);
|
||||||
|
for (var i = cardMaster.children.length - 1; i >= 0; i--) {
|
||||||
|
var targetChild = cardMaster.children[i];
|
||||||
|
var frameToDraw = frameList[parseInt(targetChild.id.replace("frameIndex", ""))];
|
||||||
|
var maskName = targetChild.innerHTML.slice(targetChild.innerHTML.indexOf("(") + 1, targetChild.innerHTML.indexOf(")"));
|
||||||
|
var rightHalf = false;
|
||||||
|
if (maskName.includes(" - Right")) {
|
||||||
|
maskName = maskName.replace(" - Right", "");
|
||||||
|
rightHalf = true;
|
||||||
|
}
|
||||||
|
var maskIndex = frameToDraw.maskOptionList.indexOf(maskName);
|
||||||
|
var maskImageIndex = maskNameList.indexOf(maskName)
|
||||||
|
//Clears the temporary mask canvas, draws the mask, draws the image over it, then copies it to the final frame canvas
|
||||||
|
frameMaskContext.globalCompositeOperation = "source-over";
|
||||||
|
frameMaskContext.clearRect(0, 0, cardWidth, cardHeight);
|
||||||
|
frameMaskContext.drawImage(maskList[maskImageIndex], 0, 0, cardWidth, cardHeight);
|
||||||
|
if (rightHalf) {
|
||||||
|
frameMaskContext.globalCompositeOperation = "source-in"
|
||||||
|
frameMaskContext.drawImage(maskList[0], 0, 0, cardWidth, cardHeight)
|
||||||
|
}
|
||||||
|
frameMaskContext.globalCompositeOperation = "source-in";
|
||||||
|
frameMaskContext.drawImage(frameToDraw.image, frameToDraw.xList[maskIndex], frameToDraw.yList[maskIndex], frameToDraw.widthList[maskIndex], frameToDraw.heightList[maskIndex]);
|
||||||
|
frameFinalContext.drawImage(frameMaskCanvas, 0, 0, cardWidth, cardHeight);
|
||||||
|
}
|
||||||
|
cardImageUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Overall card stuff */
|
||||||
|
function cardImageUpdated() {
|
||||||
|
cardFinalContext.clearRect(0, 0, cardWidth, cardHeight);
|
||||||
|
displayContext.clearRect(0, 0, cardWidth, cardHeight);
|
||||||
|
cardFinalContext.drawImage(frameFinalCanvas, 0, 0, cardWidth, cardHeight);
|
||||||
|
displayContext.drawImage(cardFinalCanvas, 0, 0, cardWidth, cardHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Misc convenient functions */
|
||||||
|
function scale(input) {
|
||||||
|
return input * cardWidth / 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Functions that make stuff */
|
/* Functions that make stuff */
|
||||||
@@ -42,8 +206,6 @@ function newCanvas(newCanvasName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Functions that manage the website */
|
/* Functions that manage the website */
|
||||||
function toggleTabs(event, targetTab, tabSubject) {
|
function toggleTabs(event, targetTab, tabSubject) {
|
||||||
var tabList = document.getElementsByClassName(tabSubject);
|
var tabList = document.getElementsByClassName(tabSubject);
|
||||||
@@ -51,8 +213,8 @@ function toggleTabs(event, targetTab, tabSubject) {
|
|||||||
tabList[i].classList.remove("tabVisible");
|
tabList[i].classList.remove("tabVisible");
|
||||||
tabList[i].classList.remove("tabOptionSelected");
|
tabList[i].classList.remove("tabOptionSelected");
|
||||||
}
|
}
|
||||||
document.getElementById(targetTab).classList.add("tabVisible")
|
document.getElementById(targetTab).classList.add("tabVisible");
|
||||||
event.target.classList.add("tabOptionSelected")
|
event.target.classList.add("tabOptionSelected");
|
||||||
}
|
}
|
||||||
function loadScript(scriptPath){
|
function loadScript(scriptPath){
|
||||||
var script = document.createElement("script");
|
var script = document.createElement("script");
|
||||||
|
@@ -3688,5 +3688,5 @@ function removeMultiDragElements() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Sortable;
|
// export default Sortable;
|
||||||
export { AutoScrollPlugin as AutoScroll, MultiDragPlugin as MultiDrag, OnSpill, Sortable, SwapPlugin as Swap };
|
// export { AutoScrollPlugin as AutoScroll, MultiDragPlugin as MultiDrag, OnSpill, Sortable, SwapPlugin as Swap };
|
@@ -91,10 +91,14 @@ canvas {
|
|||||||
grid-template-columns: auto;
|
grid-template-columns: auto;
|
||||||
font: 1.6em mplantin;
|
font: 1.6em mplantin;
|
||||||
}
|
}
|
||||||
.imageGrid {
|
.splitGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 9em;
|
grid-template-columns: 50% 50%;
|
||||||
min-height: 12.5em;
|
}
|
||||||
|
.frameGrid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(4em, 1fr));
|
||||||
|
grid-auto-rows: min-content;
|
||||||
}
|
}
|
||||||
.footerGrid {
|
.footerGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -257,16 +261,51 @@ footer a:hover {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cardMasterElement {
|
||||||
|
font: 1em belerenbsc;
|
||||||
|
background-color: var(--clear-light);
|
||||||
|
border: 1px solid var(--light-color);
|
||||||
|
border-radius: 0.25em;
|
||||||
|
padding: 0.25em;
|
||||||
|
margin-top: 0.25em;
|
||||||
|
}
|
||||||
|
.cardMasterElementMoving {
|
||||||
|
background-color: var(--clear-dark);
|
||||||
|
}
|
||||||
|
.closeCardMasterElement {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
/*top: 50%;*/
|
||||||
|
left: 96%;
|
||||||
|
/*padding: 12px 16px;*/
|
||||||
|
/*transform: translate(0%, -50%);*/
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.frameOption {
|
||||||
|
height: 4em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.frameOption img {
|
||||||
|
max-width: 4em;
|
||||||
|
max-height: 4em;
|
||||||
|
}
|
||||||
|
.maskOption img {
|
||||||
|
max-width: 2em;
|
||||||
|
max-height: 2em;
|
||||||
|
}
|
||||||
|
.frameOption.frameOptionSelected, .maskOption.maskOptionSelected {
|
||||||
|
background-color: var(--clear-dark);
|
||||||
|
}
|
||||||
|
.maskOption, .frameOption {
|
||||||
|
background-color: var(--clear-light);
|
||||||
|
border: 1px solid var(--light-color);
|
||||||
|
border-radius: 0.25em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.maskOption {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
padding: 0.125em;
|
||||||
|
}
|
||||||
|
18
test.html
@@ -8,11 +8,6 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<ul id="items">
|
|
||||||
<li>item 1</li>
|
|
||||||
<li>item 2</li>
|
|
||||||
<li>item 3</li>
|
|
||||||
</ul>
|
|
||||||
<div class="mainDiv">
|
<div class="mainDiv">
|
||||||
<div class="pageTitle">
|
<div class="pageTitle">
|
||||||
Card Conjurer
|
Card Conjurer
|
||||||
@@ -29,7 +24,12 @@
|
|||||||
<div class="tabOption mainEditor" onclick="toggleTabs(event, 'art', 'mainEditor')">Art</div>
|
<div class="tabOption mainEditor" onclick="toggleTabs(event, 'art', 'mainEditor')">Art</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabContent mainEditor tabVisible" id="frame">
|
<div class="tabContent mainEditor tabVisible" id="frame">
|
||||||
oh boy the frames!
|
<div class="splitGrid">
|
||||||
|
<div id="framePicker" class="frameGrid"></div>
|
||||||
|
<div id="maskPicker"></div>
|
||||||
|
</div>
|
||||||
|
<button onclick="addFrameToCardMaster()">Add</button><button onclick="addFrameToCardMaster(true)">Add To Right Half</button>
|
||||||
|
<div id="cardMaster" onchange="cardMasterUpdated()"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabContent mainEditor" id="text">
|
<div class="tabContent mainEditor" id="text">
|
||||||
oh cool some text!
|
oh cool some text!
|
||||||
@@ -91,7 +91,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script src="data/scripts/colors.js"></script>
|
<script async src="data/scripts/sortable.js"></script>
|
||||||
<!-- <script src="data/scripts/sortable.js" type="module"></script> -->
|
<script defer src="data/scripts/main.js"></script>
|
||||||
<script type="module" src="data/scripts/main.js"></script>
|
<script defer src="data/scripts/colors.js"></script>
|
||||||
<html>
|
<html>
|
||||||
|