JSFiddle - React, Tailwind, and code Playground

by Gwyn Milcote

HTML

<div id='container'>
    
    <div id='popup-error-container' style='display:none'>
        <div id='popup-error-title'>
            Oops, there's a problem...
        </div>
        <div id='popup-error-text'>
            [error text here]
        </div>
        <div id='popup-error-close'>
            Close
        </div>
    </div>
    
    <div id='intro'>
        <div id='intro-title'>Campaign Editor</div>
        <div id='intro-text'>
        <p>Tired of playing the official game over and over? Using this tool, you can create your own NQ-style fan game. Design maps, enemies, NPCs to talk and trade with... no coding needed!</p>
        <p>A simple demo-game is loaded by default. Look through the tabs to see how it's put together, maybe fiddle with some stuff, and try playing it in the 'Play' tab.</p>
        <p>In the 'Basic' tab, click 'Export' button to get a big chunk of code - this is the database object that makes the campaign unique. Save the code in a .txt file or something. Later, you can paste it into the 'Import' box to re-load it into this editor.</p> 
        <p></p>
        <p>You can use your own images; upload them somewhere else and paste the URLs in here. For your game to be playable by the world, you'll need a web-page where you can write HTML/CSS and include JS files. Include your <b>database.js</b> file (exported from here) first, and then the <b>nq.js</b> file (the game engine script), as well as the HTML and CSS from <a href=''>here</a>. View your web-page and your campaign should be ready to go!</p>
        <p>NQ2 has much more complex mechanics, and might get its own editor someday...</p>
        </div>
    </div>
    
    <div id='tab-buttons'>
        <div class='tab-button' data-tab='basic'>Basics</div>
        <div class='tab-button' data-tab='terrain'>Terrain</div>
        <div class='tab-button' data-tab='map'>Maps</div>
        <div class='tab-button' data-tab='portal'>Portals</div>
        <div class='tab-button'...

CSS

div, img, canvas, p, button, ul, li, input, select {
    box-sizing: border-box;
}

body {
    background-image: url("http://dreamicus.com/data/valley/valley-01.jpg");
    background-size: cover;
    background-position: center;
    background-attachment: fixed;
    color: rgba(0, 0, 0, 0.75);
}

input[type="number"] {
    width: 60px;
}

#container {
    width: 900px;
    margin: 20px auto;
    
}

#popup-error-container {
    z-index: 3;
    position: fixed;
    width: 400px;
    height: 160px;
    top: calc(50% - 80px);
    left: calc(50% - 200px);
    background-color: #fff;
    border: 1px solid grey;
    padding: 10px;
    box-shadow: 0 0 20px 10px rgba(0, 0, 0, 0.2);
}
#popup-error-title {
    text-align: center;
    font-weight: bold;
    font-size: 20px;
}
#popup-error-text {
    border-top: 1px solid silver;
    border-bottom: 1px solid silver;
    padding: 10px 0;
    margin: 5px 0;
    height: 90px;
    overflow: auto;
}
#popup-error-close {
    cursor: pointer;
    text-align: center;
    font-weight: bold;
}


#intro {
    width: 600px;
    background-color: #fff;
    padding: 10px;
    border: 1px solid grey;
    border-radius: 2px;
    margin: 30px auto;
}
#intro-title {
    text-align: center;
    font-size: 30px;
    font-variant: small-caps;
    font-weight: bold;
}
#intro-text {
    max-height: 200px;
    overflow: auto;
    margin: 10px 0 5px 0;
    padding: 0 10px;
    border: 1px solid silver;
    background-color: #eee;
}


#tab-buttons {
    width: 100%;
    display: flex;
    flex-flow: row;
    justify-content: space-evenly;
    position: relative;
    top: 1px;
}
#tab-buttons .tab-button {
    padding: 5px 10px;
    background-color: silver;
    border: 1px solid grey;
    border-radius: 2px 2px 0 0;
    transition: 0.2s;
}
#tab-buttons .tab-button:hover {
    cursor: pointer;
    background-color: #fff;
    transition: 0.2s;
}
#tab-buttons .active-tab {
    background-color: #fff;
    border-bottom: 1px solid #fff;
}


#editor {
   ...

JavaScript

let db = {
    
    tilePath: "http://images.neopets.com/nq/tp/",
    enemyPath: "http://images.neopets.com/nq/m/",

    tileUrls: [
          "grassland",
          "water_iso",
          "forest",
          "mountain",
          "hills",
          "jungle",
          "desert",
          "dirt",
          "city",
          "castle",
          "ruins",
          "cave",
          "stone",
          "swamp",
          "snow",
          "cave_ent",
          "cave_exit",
          "cave_up",
          "cave_down",
          "dungeon",
          "dungeon_barrel",
          "dungeon_crate",
          "dungeon_chair",
          "dungeon_table",
          "dungeon_pillar",
          "dungeon_crate",
          "dungeon_bed",
          "dungeon_portal",
          "dungeon_door",
          "dungeon_up",
          "dungeon_down"
    ],
    
    settings: {},
    
    items: [
        {
            id: 1, name: "glowing stone",
            desc: "about...",
            maxQty: 10
        },
        {
            id: 2, name: "aaa",
            desc: "aaaaaaa",
            maxQty: 5
        }
    ],
    
    terrains: [
        {
            id: 1, name: "Grass",
            isFluid: false,
            url: "grassland"
        },{
            id: 2, name: "Water",
            isFluid: true,
            url: "water_iso"
        }
    ],
    
    enemies: [
        {
            id: 1, name: "a snow imp",
            minHp: 8, maxHp: 10, level: 1,
            url: "asnowimp"
        },{
            id: 2, name: "a fire imp",
            minHp: 10, maxHp: 11, level: 1,
            url: "afireimp"
        },{
            id: 3, name: "a plains lupe",
            minHp: 14, maxHp: 16, level: 2,
            url: "aplainswolf"
        },{
            id: 4, name: "a plains aisha",
            minHp: 14, maxHp: 15, level: 2,
            url: "aplainscougar"
        },{
            id: 5, name: "a grey lupe",
            minHp: 19, maxHp: 21, level: 3,
            url: "agraywolf"
  ...