JavaScript and Jupyter references

JavaScript is the most important language you need to learn as a frontend developer. Jupyter Notebooks is a convenient way to learn the language without the overhead of creating a full Website. Jupyter Notebooks had ChatGPT plugins to assist with design and troubleshooting problems. This Notebook has colors on HTML pages that were designed with a dark mode background.

Output using HTML and CSS

Multiple cells are used to setup HTML in this lesson. Many of the JavaScript cells will use the output tag(s) to write into the HTML that has been setup.

  • %%html is used to setup HTML code block
  • "style" tag enables visuals customization
  • "div" tag is setup to receive data
%%html
<html>
    <head>
        <style>
            #output {
                background-color: #353b45;
                padding: 10px;
                border: 3px solid #ccc;
            }
        </style>
    </head>
    <body>
        <div id="output">
            Hello!
        </div>
    </body>
</html>
Hello!

Output Explored

There are several ways to ouput the classic introduction message: "Hello, World!"

  • Before you go further, open Console on your Browser. JavaScript developer leaves Console open all the time!!!
  • The function console.log() outputs to Console, this is often used for inspection or debugging.
  • "Hello, World" is a String literal. This is the referred to as Static text, as it does not change. Developer call this a hard coded string.
  • "Hello, World" literal is a parameter to console.log(), element.txt() and alert().
  • The element.txt function is part of Jupyter Notebook %%js magic. This is convenient for Notebook and testing.
  • The alert command outputs the parameter to a dialog box, so you can see it in this Jupyter notebook. The alert commands are shown, but are commented out as the stop run all execution of the notebook.
  • Note, in a Web Application Debugging: An alert is often used for less savy Developers. Console is used by more savy developers; console often requires setting up a lot of outputs. Source level debugging is the most powerful solution for debugging and does not require alert or console commands.
%%js // required to allow cell to be JavaScript enabled
console.log("JavaScript/Jupyter Output Intro");

// Browser Console output; debugging or tracing
console.log("Hello, World!");
console.log("Hello, World Again!");

// Document Object Model (DOM) output; output to HTML, CSS which is standard for a Web Page
// <mark>select element method</mark>: DOM native JavaScript get, document.getElementByID
document.getElementById("output").textContent = "Hello, World!";
// <mark>jQuery CSS-style method</mark>: Tag for DOM selector, $('#output')
$('#output').append('<br><b>Hello World Again!');  // br is break or new line, b is bold

// Jupyter built in magic element for testing and convenience of development
element.text("Hello, World!"); // element is output option as part of %%js magic
element.append('<br><b>Hello World Again!');

//alert("Hello, World!"); //NOT A GOOD WAY TO HANDLE IT

Multiple Outputs Using One Variable

This second example is a new sequence of code, two or more lines of code forms a sequence. This example defines a variable, thank goodness!!! In the previous example we were typing the string "Hello, World" over and over. Observe with the variable msg="Hello, World!"; we type the string once and now use msg over and over.

  • The variable "var msg =" is used to capture the data
  • The console.log(msg) outputs to console, be sure to Inspect it!
  • The element.text() is part of Jupyter Notebooks and displays as output blow the code on this page. Until we build up some more interesting data for Web Site, we will not use be using the Python HTML, CSS technique.
  • The alert(msg) works the same as previous, but as the other commands uses msg as parameter.
%%js
console.log("Variable Definition");

var msg = "Hello, World!";

// Use msg to output code to Console and Jupyter Notebook
console.log(msg);  //right click browser select Inspect, then select Console to view
element.text(msg);
//alert(msg);

Output Showing Use of a Function

This example passes the defined variable "msg" to the newly defined "function logIt(output)".

  • There are multiple steps in this code..
    • The "definition of the function": "function logIt(output) {}" and everything between curly braces is the definitions of the function. Passing a parameter is required when you call this function.
    • The "call to the function:"logIt(msg)" is the call to the function, this actually runs the function. The variable "msg" is used a parameter when calling the logIt function.
  • Showing reuse of function...
    • There are two calls to the logIt function
    • This is called Prodedural Abstraction, a term that means reusing the same code
%%js
console.log("Function Definition");

/* Function: logIt
 * Parameter: output
 * Description: The parameter is "output" to console and jupyter page
*/
function logIt(output) {
    console.log(output); 
    element.append(output + "<br>");
    //alert(output);
}

// First sequence calling logIt function
var msg = "Hello, World!";
logIt(msg);

// Second sequence calling logIt function
var msg = "Hello, <b>Students</b>!" // replaces content of variable
var classOf = "Welcome CS class of 2023-2024."
logIt(msg + "  " + classOf); // concatenation of strings

Output Showing Loosely Typed Data

JavaScript is a loosely typed language, meaning you don't have to specify what type of information will be stored in a variable in advance.

  • To define a variable you prefix the name with var or const. The variable type is determined by JavaScript at runtime.
  • Python and many interpretive languages are loosely typed like JavaScript. This is considered programmer friendly.
  • Java which is a compiled language is strongly typed, thus you will see terms like String, Integer, Double, and Object in the source code.
  • In JavaScript, the typeof keyword returns the type of the variable. Become familiar with type as it is valuable in conversation and knowing type help you understand how to modify data. Each variable type will have built in methods to manage content within the data type.
%%js
console.log("Examine Data Types");

// Function to add typeof to output
function getType(output) {
    return typeof output + ": " + output;
}

// Function defintion
function logIt(output) {
    console.log(getType(output));  // logs string
    console.info(output);          // logs object
    element.append(getType(output) + "<br>");  // adds to Jupyter output
    //alert(getType(output));
}

// Common Types
element.append("Common Types <br>");
logIt("Mr M"); // String
logIt(1997);    // Number
logIt(true);    // Boolean
element.append("<br>");

// Object Type, this definition is often called a array or list
element.append("Object Type, array <br>");
var scores = [
    90,
    80, 
    100
];  
logIt(scores);
element.append("<br>");

// Complex Object, this definition is often called hash, map, hashmap, or dictionary
element.append("Object Type, hash or dictionary <br>");
var person = { // key:value pairs seperated by comma
    "name": "Mr M", 
    "role": "Teacher"
}; 
logIt(person);
logIt(JSON.stringify(person));  //method used to convert this object into readable format

Build a Person object and JSON

JavaScript and other languages have special properties and syntax to store and represent data. In fact, a class in JavaScript is a special function.

  • Definition of class allows for a collection of data, the "class Person" allows programmer to retain name, github id, and class of a Person.
  • Instance of a class, the "const teacher = new Person("Mr M", "jm1021", 1977)" makes an object "teacher" which is an object representation of "class Person".
  • Setting and Getting properties After creating teacher and student objects, observe that properties can be changed/muted or extracted/accessed.
%%html
<!-- load jQuery and tablesorter scripts -->
<html>
    <head>
        <!-- load jQuery and tablesorter scripts -->
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.31.3/js/jquery.tablesorter.min.js"></script>
        <style>
            /* CSS-style selector maps to table id or other id's in HTML */
            #jsonTable, #flaskTable {
                background-color: #353b45;
                padding: 10px;
                border: 3px solid #ccc;
                box-shadow: 0.8em 0.4em 0.4em grey;
            }
        </style>
    </head>

    <body>
        <!-- Table for writing and extracting jsonText -->
        <table id="jsonTable">
            <thead>
                <tr>
                    <th>Classroom JSON Data</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td id="jsonText">{"classroom":[{"type":"object","name":"sample","ghID":"sample","classOf":2000,"role":"sample"}]}</td>
                </tr>
            </tbody>
        </table>

    </body>
</html>
Classroom JSON Data
{"classroom":[{"type":"object","name":"sample","ghID":"sample","classOf":2000,"role":"sample"}]}
%%js
console.log("Person objects");

/* class: Person
 * Description: A collection of Person data
*/
class Person {
  /* method: constructor
   * parameters: name, ghID - GitHub ID, classOf - Graduation Class 
   * description: returns object when "new Person()" is called with matching parameters
   * assignment: this.name, this.ghID, ... are properties retained in the returned object
   * default: role uses a default property, it is set to "Student"
  */
  constructor(name, ghID, classOf, role="Student") {
    this.name = name;
    this.ghID = ghID;
    this.classOf = classOf;
    this.role = role;
  }

  /* method: setter
   * parameters: role - role in classroom
   * description: this.role is updated from default value to value contained in role parameter
  */
  setRole(role) {
    this.role = role;
  }
  
  /* method: getter
   * description: turns properties of object into JSON object
   * return value: JSON object
  */
  getJSON() {
    const obj = {type: typeof this, name: this.name, ghID: this.ghID, classOf: this.classOf, role: this.role};
    const json = JSON.stringify(obj);
    return json;
  }

  /* method: logIT
   * description: "this" Person object is logged to console
  */
  logIt() {
    //Person Object
    console.info(this);
    //Log to Jupter
    element.append("Person object in JSON <br>");
    element.append(this.getJSON() + "<br>");  
    //alert(this.getJSON());
  }
    
}

// make a new Person Object
const teacher = new Person("Mr M", "jm1021", 1977); // object type is easy to work with in JavaScript
// update role to Teacher
teacher.setRole("Teacher"); // set the role
teacher.logIt();  // log to console

// make a new Person Object
const student = new Person("Jane Doe", "jane", 2007); // object type is easy to work with in JavaScript
student.logIt(); // log to console

Build a Classroom Array/List of Persons and JSON

Many key elements are shown again. New elements include...

  • Building an Array, "var students" is an array of many persons
  • Building a Classroom, this show forEach iteration through an array and .push adding to an array. These are key concepts in all programming languages.
%%js
console.log("Classroom object");

/* class: Person
 * Description: A collection of Person data
*/
class Person {
  /* method: constructor
   * parameters: name, ghID - GitHub ID, classOf - Graduation Class 
   * description: returns object when "new Person()" is called with matching parameters
   * assignment: this.name, this.ghID, ... are properties retained in the returned object
   * default: this.role is a default property retained in object, it is set to "Student"
  */
  constructor(name, ghID, classOf, role="Student") {
    this.name = name;
    this.ghID = ghID;
    this.classOf = classOf;
    this.role = role;
  }

  /* method: setter
   * parameters: role - role in classroom
   * description: this.role is updated from default value to value contained in role parameter
  */
  setRole(role) {
    this.role = role;
  }
  
  /* method: getter
   * description: turns properties of object into JSON object
   * return value: JSON object
  */
  getJSON() {
    const obj = {type: typeof this, name: this.name, ghID: this.ghID, classOf: this.classOf, role: this.role};
    const json = JSON.stringify(obj);
    return json;
  }

  /* method: logIT
   * description: "this" Person object is logged to console
  */
  logIt() {
    //Person Object
    console.info(this);
    //Log to Jupter
    element.append("Person json <br>");
    element.append(this.getJSON() + "<br>");  
    //alert(this.getJSON());
  }
    
}

/* class: Classroom
 * Description: A collection of Person objects
*/
class Classroom {
  /* method: constructor
   * parameters: teacher - a Person object, students - an array of Person objects
   * description: returns object when "new Classroom()" is called containing properties and methods of a Classroom
   * assignment: this.classroom, this.teacher, ... are properties retained in the returned object
  */
  constructor(teacher, students) {
    /* spread: this.classroom contains Teacher object and all Student objects
     * map: this.json contains of map of all persons to JSON
    */
    this.teacher = teacher;
    this.students = students;
    this.classroom = [teacher, ...students]; // ... spread option
    this.json = '{"classroom":[' + this.classroom.map(person => person.getJSON()) + ']}';
  }

  /* method: logIT
   * description: "this" Classroom object is logged to console
  */
  logIt() {
    //Classroom object
    console.log(this);
    
    //Classroom json
    element.append("Classroom object in JSON<br>");
    element.append(this.json + "<br>");  
    //alert(this.json);
  }
}

/* function: constructCompSciClassroom
 * Description: Create data for Classroom and Person objects
 * Returns: A Classroom Object
*/
function constructCompSciClassroom() {
    // define a Teacher object
    const teacher = new Person("Mr M", "jm1021", 1977, "Teacher");  // optional 4th parameter

    // define a student Array of Person objects
    const students = [ 
        new Person("Anthony", "tonyhieu", 2022),
        new Person("Bria", "B-G101", 2023),
        new Person("Allie", "xiaoa0", 2023),
        new Person("Tigran", "Tigran7", 2023),
        new Person("Rebecca", "Rebecca-123", 2023),
        new Person("Vidhi", "VidhiKulkarni", 2024)
    ];

    // make a CompSci classroom from formerly defined teacher and student objects
    return new Classroom(teacher, students);  // returns object
}

// assigns "compsci" to the object returned by "constructCompSciClassroom()" function
const compsci = constructCompSciClassroom();
// output of Objects and JSON in CompSci classroom
compsci.logIt();
// enable sharing of data across jupyter cells
$('#jsonText').text(compsci.json);  // posts/embeds/writes compsci.json to HTML DOM element called jsonText

for loop to generate Table Rows in HTML output

This code extracts JSON text from HTML, that was placed in DOM in an earlier JavaScript cell, then it parses text into a JavaScript object. In addition, there is a for loop that iterates over the extracted object generating formated rows and columns in an HTML table.

  • Table generation is broken into parts...
    • table data is obtained from a classroom array inside of the extracted object.
    • the JavaScript for loop allows the construction of a new row of data for each Person hash object inside of the the Array.
    • in the loop a table row <tr> ... </tr> is created for each Hash object in the Array.
    • in the loop table data, a table column, <td> ... </td> is created for name, ghID, classOf, and role within the Hash object.
%%js
console.log("Classroom Web Page");

// extract JSON text from HTML page
const jsonText = document.getElementById("jsonText").innerHTML;
console.log(jsonText);
element.append("Raw jsonText element embedded in HTML<br>");
element.append( jsonText + "<br>");

// convert JSON text to Object
const classroom = JSON.parse(jsonText).classroom;
console.log(classroom);

// from classroom object creates rows and columns in HTML table
element.append("<br>Formatted data sample from jsonText <br>");
for (var row of classroom) {
    element.append(row.ghID + " " + row.name + '<br>');
    // tr for each row, a new line
    $('#classroom').append('<tr>')
    // td for each column of data
    $('#classroom').append('<td>' + row.name + '</td>')
    $('#classroom').append('<td>' + row.ghID + '</td>')
    $('#classroom').append('<td>' + row.classOf + '</td>')
    $('#classroom').append('<td>' + row.role + '</td>')
    // tr to end row
    $('#classroom').append('</tr>');
}
%%html
<head>
    <!-- load jQuery and DataTables syle and scripts -->
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/1.10.25/css/jquery.dataTables.min.css">
    <script type="text/javascript" language="javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
    <script type="text/javascript" language="javascript" src="https://cdn.datatables.net/1.10.25/js/jquery.dataTables.min.js"></script>
</head>
<table id="flaskTable" class="table" style="width:100%">
    <thead id="flaskHead">
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>DOB</th>
            <th>Age</th>
        </tr>
    </thead>
    <tbody id="flaskBody"></tbody>
</table>

<script>
  $(document).ready(function() {
    fetch('https://flask.nighthawkcodingsociety.com/api/users/', { mode: 'cors' })
    .then(response => {
      if (!response.ok) {
        throw new Error('API response failed');
      }
      return response.json();
    })
    .then(data => {
      for (const row of data) {
        // BUG warning/resolution - DataTable requires row to be single append
        $('#flaskBody').append('<tr><td>' + 
            row.id + '</td><td>' + 
            row.name + '</td><td>' + 
            row.dob + '</td><td>' + 
            row.age + '</td></tr>');
      }
      // BUG warning - Jupyter does not show Datatable controls, works on deployed GitHub pages
      $("#flaskTable").DataTable();
    })
    .catch(error => {
      console.error('Error:', error);
    });
  });
</script>
ID Name DOB Age

Hacks

One key to these hacks is to build confidence with me going into final grade, I would like to see each student adapt this frontend work in their final project. Second key is the finished work can serve as review for the course, notes for the future in relationship to frontend.

  • Adapt this tutorial to your own work
  • Consider what you need to work on to be stronger developer
  • Show something creative or unique, no cloning
  • Be ready to talk to Teacher for 5 to 10 minutes. Individually!!!
  • Show in Jupyter Notebook during discussion, show Theme and ChatGPT
  • Have a runtime final in GithHub Pages (or Fastpage)

"Adapt this tutorial to your own work"

Check out our final project, specifically the Blackjack game and the Uno game, since I led the JavaScript programming of each of these games. I also did pretty much all work on connecting the code to interactable card objects.

"Consider what you need to work on to be a stronger developer"

I think I could do better by planning out how to program a given thing beforehand (functions and parameters/arguments without code), since it often results in quite redundant code where I get lazy and copy-paste. I've also gotten less consistent about making code comments over time.

Build Table code
Taken from our group's Uno game code.

This block of code (partially edited by other group members for image implementation) is a great example. I haven't fixed it since it functions as it should, but it could definitely be optimized. Instead of just making it create a new row in the card table every 12 cards, I made it only split between 2. This could definitely be heavily improved by proper planning before writing.

It could also use some more code comments to clarify the cardID in cardList loop is pulling the indexes of each card, not the cards themselves, since that caused some confusion among my group.

"Show something creative or unique, no cloning"

Since I've always wanted a chance to in this class but never ended up following through because it just didn't fit into any of the projects we've made. Here's a little RPG battle that takes advantage of HTML and JavaScript interacting.

%%html
<style>
    #nametag {
        background-color: #EF0000;
        padding: 10px;
        border: 3px solid;
        color: white;
        font-size: 30px;
        margin: auto;
        text-align: center;
        justify-content: center;
    }
    #battle-container {
        display: flex;
        justify-content: space-between;
    }
    #player-box {
        position: relative;
        align-items: left;
        border: 5px solid;
        color: white;
    }
    
    #player-box {
        position: relative;
        align-items: right;
        border: 5px solid;
        color: white;
    }
    
    #text_footer {
        border: 5px solid #7400A7;
        padding: 8px;
        background-color: #B100FF;
        color: white;
        font-size: 12px;
    }
    
    .attack_button {
        border: 2px solid;
        padding: 3px;
        background-color: white;
        color: black;
        font-size: 15px;
    }
</style>
<html>
    <div id="nametag">
        <p>JAVASCRIPT RPG <button id="start_button" onclick="startGame()" style="font-size:20px;color:white;">PRESS TO START</button></p>
    </div>
    <br>
    <div id="battle-container">
        <div id="player_box">
            FIGHTER
            <div id="fighter_HP"<br>HP: 100</div>
            KNIGHT
            <div id="knight_HP"<br>HP: 125</div>
            MAGE
            <div id="mage_HP"<br>HP: 80</div>
        </div>
        <div id="player_image" style="height:100px;">
            <img id="fighter_img" height="32" width="32" style="transform: scaleX(-1)" src="https://static.wikia.nocookie.net/finalfantasy/images/1/1b/Warrior-ff1-nes.png">
            <img id="knight_img" height="32" width="32" style="transform: scaleX(-1)" src="https://static.wikia.nocookie.net/finalfantasy/images/5/5a/Knight_%28Final_Fantasy%29.png">
            <img id="mage_img" height="32" width="32" style="transform: scaleX(-1)" src="https://static.wikia.nocookie.net/finalfantasy/images/6/64/Whitemage-ff1-nes.png">
        </div>
        <div id="attack_anim_box" style="position:absolue;">
        </div>
        <div id="beast_image">
            <img id="boss_img" height="175" width="150" style="transform:none;" src="https://static.wikia.nocookie.net/finalfantasy/images/9/99/FFVII-GalianBeast.png">
        </div>
        <div id="boss_box">
            MASSIVE BEAST
            <div id="boss_HP"<br>HP: 1800</div>
        </div>
    </div>
    <br>
    <footer id="text_footer">
        <div id="text_box">
            Click "PRESS TO START" to play the game!
        </div>
        <div id="for_buttons"></div>
    </footer>
</html>

<script>
    var startButton = document.getElementById("start_button")
    var gimmeText = document.getElementById("text_box");
    var buttonsBox = document.getElementById("for_buttons");
    var fighterHP = document.getElementById("fighter_HP");
    var knightHP = document.getElementById("knight_HP");
    var mageHP = document.getElementById("mage_HP");
    var bossHP = document.getElementById("boss_HP");
    var animBox = document.getElementById("attack_anim_box");
    
    function getRandomInt(min, max) {
        min = Math.ceil(min);
        max = Math.floor(max);
        return Math.floor(Math.random() * (max - min) + min);
    }

    var fighter = {
        name:"FIGHTER",
        basehp:100,
        hp:100,
        att:23,
        mag:0,
        def:15,
        spd:15
    }
    
    var knight = {
        name:"KNIGHT",
        basehp:125,
        hp:125,
        att:20,
        mag:0,
        def:18,
        spd:10
    }
    
    var mage = {
        name:"MAGE",
        basehp:80,
        hp:80,
        att:15,
        mag:20,
        def:12,
        spd:15
    }
    
    var boss = {
        name:"BOSS",
        hp:1800,
        att:15,
        mag:15,
        def:15,
        spd:5
    }
    
    var charging = false;
    
    function updateHP(guy) {
        document.getElementById((guy.name).toLowerCase() + "_HP").innerHTML = "HP: " + String(guy.hp);
    };
    
    function win() {
        document.getElementById('boss_img').style["transform"] = "rotate(-90deg)";
        textDisplay("Congratulations! You've defeated the beast and saved the village!");
        setTimeout(textDisplay, 2600, "The village is forever indebted to you for your wise leadership.");
    };
    
    function loseCheck() {
        if ((fighter.hp == 0) && (knight.hp == 0) && (mage.hp == 0)) {
            return true
        } else {
            return false
        }
    }
    
    async function attack(attacker) {
        buttonsBox.innerHTML = "";
        attackAnimation();
        var damage = Math.floor((((attacker.att * 3.5) / boss.def) * 10) + (getRandomInt(1, 10) - 5));
        boss.hp = boss.hp - damage;
        if (boss.hp <= 0) {
            boss.hp = 0;
            updateHP(boss);
            win();
            return;
        }
        updateHP(boss);
        textDisplay(attacker.name + " deals " + String(damage) + " points damage to the beast!");
        if (attacker.name == "FIGHTER") {
            setTimeout(knightTurn, 2200);
        } else if (attacker.name == "KNIGHT") {
            setTimeout(mageTurn, 2200);
        } else {
            setTimeout(bossTurn, 2200);
        }
    }

    function attackAnimation() {
        animBox.innerHTML = '<img id="attack_anim" width="150" style="filter: grayscale(100%);position:relative;left:150%" src="https://ugokawaii.com/wp-content/uploads/2022/08/flash-effect.gif">';
        setTimeout(function() {animBox.innerHTML = ""}, 500);
    };
    
    async function deathblow(attacker) {
        buttonsBox.innerHTML = "";
        var damage = Math.floor((((attacker.att * 5.5) / boss.def) * 11) + (getRandomInt(1, 10)));
        if (getRandomInt(0, 100) < 65) {
            deathblowAnimation();
            boss.hp = boss.hp - damage;
            if (boss.hp <= 0) {
                boss.hp = 0;
                updateHP(boss);
                textDisplay(attacker.name + " deals " + String(damage) + " points of critical damage to the beast!")
                setTimeout(win, 2200);
                return;
            }
            updateHP(boss);
            textDisplay(attacker.name + " deals " + String(damage) + " points of critical damage to the beast!")
        } else {
            textDisplay("Oh no! " + attacker.name + " missed!")
        }
        setTimeout(knightTurn, 2200)
    }
    
    function deathblowAnimation() {
        animBox.innerHTML = '<img id="attack_anim" width="200" style="filter: grayscale(100%);position:relative;left:135%" src="https://thumbs.gfycat.com/AridDigitalGibbon-size_restricted.gif">';
        setTimeout(function() {animBox.innerHTML = ""}, 800);
    };
    
    async function defendSetup() {
        buttonsBox.innerHTML = "";
        gimmeText.innerHTML = "Which character would you like to defend?";
        buttonsBox.innerHTML = '<button class="attack_button" onclick="defend(fighter)">FIGHTER</button> <button class="attack_button" onclick="defend(knight)">KNIGHT</button> <button class="attack_button" onclick="defend(mage)">MAGE</button>'
    }
    
    async function defend(defended) {
        var thisText = "";
        buttonsBox.innerHTML = "";
        defended.def = Math.floor(defended.def * 2.5);
        if (defended.name == "KNIGHT") {
            thisText = "himself";
        } else {
            thisText = defended.name;
        }
        textDisplay("The KNIGHT defends " + thisText + "! His defense increases greatly.")
        setTimeout(mageTurn, 2200)
    }
    
    async function healSetup() {
        buttonsBox.innerHTML = "";
        gimmeText.innerHTML = "Which character would you like to heal?";
        buttonsBox.innerHTML = '<button class="attack_button" onclick="heal(fighter)">FIGHTER</button> <button class="attack_button" onclick="heal(knight)">KNIGHT</button> <button class="attack_button" onclick="heal(mage)">MAGE</button>'
    }
    
    async function heal(recipient) {
        healAnimation();
        buttonsBox.innerHTML = "";
        var healedHP = ((mage.mag * 2.5) + (getRandomInt(1,10) - 5));
        recipient.hp += healedHP;
        if (recipient.hp > recipient.basehp) {
            recipient.hp = recipient.basehp;
        };
        updateHP(recipient);
        textDisplay(recipient.name + " recovers " + String(healedHP) + " points of damage!");
        setTimeout(bossTurn, 2200);
    }
    
    function healAnimation() {
        animBox.innerHTML = '<img id="attack_anim" width="130" style="position:relative;right:130%" src="https://static.wixstatic.com/media/8029b6_0c59dc143dc54a2f918e5b8b2dd0b361~mv2.gif">';
        setTimeout(function() {animBox.innerHTML = ""}, 600);
    };
    
    async function fighterTurn() {
        if (fighter.hp > 0) {
            gimmeText.innerHTML = "What will the FIGHTER do?";
            buttonsBox.innerHTML = '<button class="attack_button" onclick="attack(fighter)">ATTACK</button> <button class="attack_button" onclick="deathblow(fighter)">DEATHBLOW</button>'
        } else {
            knightTurn();
        }
    }
    
    async function knightTurn() {
        fighter.def = 15;
        knight.def = 20;
        mage.def = 10;
        if (knight.hp > 0) {
            gimmeText.innerHTML = "What will the KNIGHT do?";
            buttonsBox.innerHTML = '<button class="attack_button" onclick="attack(knight)">ATTACK</button> <button class="attack_button" onclick="defendSetup()">GUARD</button>'
        } else {
            mageTurn();
        }
    }
    
    async function mageTurn() {
        if (mage.hp > 0) {
            gimmeText.innerHTML = "What will the MAGE do?";
            buttonsBox.innerHTML = '<button class="attack_button" onclick="attack(mage)">ATTACK</button> <button class="attack_button" onclick="healSetup()">HEAL</button>'
        } else {
            bossTurn();
        }
    }
    
    async function bossTurn() {
        if (charging == false) {
            var bossMove = getRandomInt(1, 7);
            var targets = [fighter, knight, mage];
            if (bossMove < 4) {
                animBox.innerHTML = '<img id="attack_anim" width="150" style="filter: grayscale(100%);position:relative;right:130%" src="https://ugokawaii.com/wp-content/uploads/2022/08/flash-effect.gif">';
                setTimeout(function() {animBox.innerHTML = ""}, 500);
                var target = targets[bossMove - 1];
                var damage = Math.floor((((boss.att * 5) / target.def) * 10) + (getRandomInt(1, 10) - 5));
                target.hp -= damage;
                if (target.hp < 0) {
                    target.hp = 0;
                }
                updateHP(target);
                textDisplay("The beast attacks " + target.name + " and deals " + String(damage) + " damage!")
                if (loseCheck()) {
                    setTimeout(textDisplay, 2200, "Oh no! The beast has defeated the warriors. You lose!")
                    return;
                }
                setTimeout(fighterTurn, 2800);
            } else if (bossMove < 6) {
                charging = targets[getRandomInt(0, 3)];
                textDisplay("The beast begins charging a powerful attack! It glares at " + charging.name + ".");
                setTimeout(fighterTurn, 2800);
            } else {
                animBox.innerHTML = '<img id="attack_anim" width="130" style="position:relative;left:145%" src="https://static.wixstatic.com/media/8029b6_0c59dc143dc54a2f918e5b8b2dd0b361~mv2.gif">';
                setTimeout(function() {animBox.innerHTML = ""}, 600);
                var healedHP = (190 + getRandomInt(0, 21));
                boss.hp += healedHP;
                updateHP(boss);
                textDisplay("The beast licks its wounds, healing " + String(healedHP) + " damage.");
                setTimeout(fighterTurn, 2800);
            }
        } else {
            animBox.innerHTML = '<img id="attack_anim" width="130" style="position:relative;right:130%" src="https://i.gifer.com/origin/d7/d7ac4f38b77abe73165d85edf2cbdb9e_w200.gif">';
            setTimeout(function() {animBox.innerHTML = ""}, 1000);
            var damage = Math.floor((((boss.att * 8) / charging.def) * 10) + (getRandomInt(1, 4) + 2));
            charging.hp -= damage;
            if (charging.hp < 0) {
                charging.hp = 0;
            };
            updateHP(charging);
            textDisplay("The beast unleashes and deals " + String(damage) + " damage to " + charging.name + "!")
            charging = false;
            if (loseCheck()) {
                setTimeout(textDisplay, 2200, "Oh no! The beast has defeated the warriors. You lose!")
                return;
            }
            setTimeout(fighterTurn, 2800);
        }
    }
    
    async function startGame() {
        startButton.style = "display:none";
        textDisplay("Oh no! A massive beast is wreaking havoc in the village! Three brave warriors step up to defeat it.");
        setTimeout(textDisplay, 3500, "The battle starts!");
        setTimeout(fighterTurn, 4500);
    }

    function typeWriter(text, i) {
      var speed = 18;
      if (i < text.length) {
        gimmeText.innerHTML += text[i];
        if ((text[i] == ".") || (text[i] == "!") || (text[i] == "?")) {speed = 40};
        setTimeout(typeWriter, speed, text, i+1);
      };
    };
    
    async function textDisplay(text) {
        console.log("Text to be displayed:", text);
        gimmeText.innerHTML = "";
        typeWriter(text, 0);
    }
</script>

JAVASCRIPT RPG


FIGHTER <div id="fighter_HP"
HP: 100
KNIGHT <div id="knight_HP"
HP: 125
MAGE <div id="mage_HP"
HP: 80</div> </div>
MASSIVE BEAST <div id="boss_HP"
HP: 1800
</div> </div>
Click "PRESS TO START" to play the game!

A Different Style of Game

I wasn't satisfied with the text system in this RPG game. Because it was poorly optimized, I ended up having to use manually-selected setTimeout waiting between text scrolls where I would've much preferred to have the user progress the text. I figured out a way to do it and decided to make it.

Here is a little visual novel-style game with text interraction.

%%html
<style>
    #nametag2 {
        background-color: blue;
        padding: 10px;
        border: 3px solid;
        color: white;
        font-size: 30px;
        margin: auto;
        text-align: center;
        justify-content: center;
    }
    #display_container {
        display: flex;
        justify-content: space-between;
    }
    #mc_box {
        position: relative;
        align-items: center;
        text-align: center;
        color: white;
        font-size:20px;
        width:25%;
    }
    
    #other_box {
        position: relative;
        align-items: center;
        color: white;
        font-size:20px;
        width:25%;
    }
    
    #text_footer2 {
        border-radius: 10px;
        padding: 8px;
        padding-left:25px;
        padding-right:20px;
        background-color: rgba(0,0,0,.5);
        opacity:0;
        width: 65%;
        height: 100px;
        max-height: 16%;
        color: white;
        margin: auto;
        text-align:left;
        font-size: 20px;
        top: -10%;s
        font-family: 'trebuchet ms',sans-serif;
        position:relative;
        transform:none;
        display:none;
    }
    
    .attack_button {
        border: 2px solid;
        padding: 3px;
        background-color: white;
        color: black;
        font-size: 15px;
    }
    
    .dialogue_sel {
        background-color: rgba(0,0,0,.5);
        border-radius: 10px;
        border:0px;
        font-family: "trebuchet ms",sans-serif;
        color:white;
    }
    
    .dialogue_sel:hover {
        color:yellow;
    }
    
</style>
<html>
    <div id="nametag2">
        <p>JavaSim <button id="start_button2" onclick="startSim()" style="font-size:20px;color:white;">PRESS TO START</button><button onclick="bringBoxUp()">BringUp</button><button onclick="bringBoxDown()">BringDown</button><button onclick="textReader(sampleConvo, 1, null, 'bringBoxDown()')">Text Test</button><button onclick="coffeeJob()">Cafe</button></p>
    </div>
    <br>
    <div id="display_container">
        <div id="mc_box">
            <u>YOU</u>
        </div>
        <div id="background_image" style="border:5px solid tan">
            <img id="background_img" width="360" src="https://images.nightcafe.studio/jobs/NbZpn5mHCc4aOJnTkTgy/NbZpn5mHCc4aOJnTkTgy--1--0t64w.jpg">
        </div>
        <div id="other_box">
            <u>YOUR STATS</u><br>
            Money: $200<br>
            Food: Some<br>
            Health: Fair<br>
            Friendship: Poor
        </div>
    </div>
    <br>
    <footer id="text_footer2">
        <div id="response_buttons"></div>
        <div id="text_box2" style="font-family:'trebuchet ms', sans-serif">
            Test for text.
        </div>
    </footer>
</html>
<script>
    var homeImg = "https://images.nightcafe.studio/jobs/NbZpn5mHCc4aOJnTkTgy/NbZpn5mHCc4aOJnTkTgy--1--0t64w.jpg";
    var libraryImg = "https://images.nightcafe.studio/jobs/TEL9XQrgZjuwl6B4QZfy/TEL9XQrgZjuwl6B4QZfy--1--zhwiv.jpg";
    var restaurantImg = "https://images.nightcafe.studio/jobs/dwF793B80vPVu02QgpBr/dwF793B80vPVu02QgpBr--1--nin9p.jpg";
    var emptyMallImg = "https://images.nightcafe.studio/jobs/KMC8kBhuArYOBPh2uqOa/KMC8kBhuArYOBPh2uqOa--4--e4c1a.jpg";
    var paintStoreImg = "https://images.nightcafe.studio/jobs/zIuQs58EyZLDHaoBQczv/zIuQs58EyZLDHaoBQczv--1--ju4n7.jpg";
    var theaterImg = "https://images.nightcafe.studio/jobs/5aYVoYaeY4Emeqz4kWCK/5aYVoYaeY4Emeqz4kWCK--1--pq6zs.jpg";
    var portTownImg = "https://images.nightcafe.studio/jobs/gHRptHyVSYhtrlaeUPSG/gHRptHyVSYhtrlaeUPSG--1--m92sn.jpg";
    var coffeeShopImg = "https://images.nightcafe.studio/jobs/FSo7lDApBFLCDQ3ju93L/FSo7lDApBFLCDQ3ju93L--1--rityb.jpg";
    var groceryImg = "https://images.nightcafe.studio/jobs/xtBJjU41vSKfNsqxAugW/xtBJjU41vSKfNsqxAugW--1--g6vvi.jpg";
    var neighborhoodImg = "https://images.nightcafe.studio/jobs/uhPEXwNAF9FCXjzrfbHi/uhPEXwNAF9FCXjzrfbHi--1--mrlsz.jpg";
    var hospitalLobbyImg = "https://images.nightcafe.studio/jobs/0M0yyI648y04m4rXZR3e/0M0yyI648y04m4rXZR3e--1--xlxsn.jpg";
    var tBox = document.getElementById('text_box2');
    var tBoxCont = document.getElementById('text_footer2');
    var mcBox = document.getElementById('mc_box');
    var bgImg = document.getElementById('background_img');
    
    var day = 1;
    var money = 200; //always in dollars
    var food = 6; //2 used per day; 3=few, 6=some, 9+=lots; cap at 12
    var health = 5; //gym gives you 3, 1 removed each day; cap at 12
    var friendship = 0; //increases by 1 with each social interaction
    
    var girlFriendship = 0;
    var boyFriendship = 0;
    
    function bringBoxUp() {
        tBoxCont.style["display"] = "block";
        tBoxCont.style["top"] = "-10%"; //goal is up to "-25%"
        var posVal = 10;
        var opVal = 0.00625;
        animBoxUp = setInterval(function() {
            posVal++;
            opVal = opVal * 1.5;
            tBoxCont.style["top"] = "-" + String(posVal) + "%";
            tBoxCont.style["opacity"] = String(opVal);
            if (posVal >= 25) {
                tBoxCont.style["top"] = "-25%";
                tBoxCont.style["opacity"] = "1";
                clearInterval(animBoxUp);
            };
        }, 20);
    }
    
    function bringBoxDown() {
        tBoxCont.style["display"] = "block";
        tBoxCont.style["top"] = "-25%"; //goal is up to "-25%"
        tBoxCont.style["opacity"] = "1";
        var posVal = 25;
        var opVal = 1;
        animBoxUp = setInterval(function() {
            posVal--;
            opVal = opVal / 1.5;
            tBoxCont.style["top"] = "-" + String(posVal) + "%";
            tBoxCont.style["opacity"] = String(opVal);
            if (posVal <= 10) {
                clearInterval(animBoxUp);
                tBoxCont.style["top"] = "-10%";
                tBoxCont.style["opacity"] = "0";
                tBox.innerHTML = "";
                tBoxCont.setAttribute('onclick', '');
                tBoxCont.style["cursor"] = 'auto';
            };
        }, 20);
    }
    
    //when second index in array is true, it means that the following message is user input
    var sampleConvo = ["sampleConvo", //textReader should always start at index 1 with currentPlace
        ["There are a few things I want to remember during the making of this little story for later. ⬇", false],
        ["Remember Kendall's suggestion of the raccoon encounter. That'll be fun. ⬇", false],
        ["I wonder who the friend should be, if not multiple. What do you think?", true],
        [{"One guy.":"Just a bro thing? Sounds pretty chill. ⬇",
          "One girl.":"Sounds like brewing romance to me...but I thought of that, too. ⬇",
          "One of each.":"That could be a bit of work, but that's most ideal. ⬇"}, false],
        ["I think this was a good brainstorming exercise. ⬇", false]]
    
    async function textReader(textList, currentPlace, key, next) {//current place always starts at 1
        tBoxCont.setAttribute('onclick', '');
        tBoxCont.style["cursor"] = "pointer";
        tBox.innerHTML = "";
        mcBox.innerHTML = "<u>YOU</u>";
        if ((!(textList[currentPlace][1])) && (key === null)) {
            var nowText = textList[currentPlace][0];
            var nowLength = nowText.length;
            let i = 0;
            var intvChar = 15;
            var textLoop = setInterval(function() {
                intvChar = 15;
                tBox.innerHTML += nowText[i];
                if (nowText[i] == "?" || nowText[i] == "!") {
                    intvChar = 200;
                };
                i++;
                if (i >= nowLength) {
                    clearInterval(textLoop);
                    tBoxCont.setAttribute('onclick', "textReader(" + textList[0] + ", " + String(currentPlace + 1) + ', null, "' + next + '")');
                    tBoxCont.style["cursor"] = "pointer";
                    if ((currentPlace + 1) >= textList.length) {
                        console.log("End of text.")
                        tBoxCont.setAttribute('onclick', next);
                        return true;
                    };
                }
            }, intvChar);
            return false;
        } else if (textList[currentPlace][1]) {
            tBox.innerHTML = "";
            var nowText = textList[currentPlace][0];
            var nowLength = nowText.length;
            let i = 0;
            var intvChar = 15;
            var textLoop = setInterval(function() {
                tBox.innerHTML += nowText[i];
                i++;
                if (i >= nowLength) {
                    clearInterval(textLoop);
                    currentPlace++; //textList[currentPlace][0] is now the resp dictionary
                    for (key in textList[currentPlace][0]) {
                        mcBox.innerHTML += "<br><br>";
                        var respButton = document.createElement('button');
                        respButton.setAttribute('onclick', 'textReader(' + textList[0] + ', ' + String(currentPlace) + ', "' + key + '", "' + next + '")');
                        respButton.setAttribute('class', 'dialogue_sel');
                        respButton.innerHTML = key;
                        mcBox.appendChild(respButton);
                    };
                    return false;
                };
            }, intvChar);
        } else {
            var nowText = textList[currentPlace][0][key];
            var nowLength = nowText.length;
            let i = 0;
            var intvChar = 15;
            var textLoop = setInterval(function() {
                intvChar = 15;
                tBox.innerHTML += nowText[i];
                if (nowText[i] == "?" || nowText[i] == "!") {
                    intvChar = 200;
                };
                i++;
                if (i >= nowLength) {
                    clearInterval(textLoop);
                    tBoxCont.setAttribute('onclick', "textReader(" + textList[0] + ", " + String(currentPlace + 1) + ", null, '" + next + "')");
                    tBoxCont.style["cursor"] = "pointer";
                    if ((currentPlace + 1) >= textList.length) {
                        console.log("End of text");
                        tBoxCont.setAttribute('onclick', next);
                        return true;
                    };
                    return false;
                }
            }, intvChar);
        }
    }

    function changeBG(imageURL) {
        bgImg.style["opacity"] = "1";
        var opVal = 1;
        var fadeOut = setInterval(function() {
            opVal -= 0.1;
            bgImg.style["opacity"] = String(opVal);
            if (opVal <= 0) {
                clearInterval(fadeOut);
                bgImg.style["opacity"] = "0";
            }
        }, 50);
        setTimeout(function() {
            opVal = 0;
            bgImg.src = imageURL;
            var fadeIn = setInterval(function() {
            opVal += 0.1;
            bgImg.style["opacity"] = String(opVal);
            if (opVal >= 1) {
                clearInterval(fadeIn);
                bgImg.style["opacity"] = "1";
            }
        }, 50);
        }, 700)
    }

    function startSim() {
        var workButton = document.createElement("button");
        workButton.setAttribute('onclick', 'coffeeJob()');
        workButton.setAttribute('class', 'dialogue_sel');
        workButton.innerHTML = "Go to Work";
        mcBox.innerHTML += "<br><br>";
        mcBox.appendChild(workButton);
        var gymButton = document.createElement("button");
        gymButton.setAttribute('onclick', 'gymTrip()');
        gymButton.setAttribute('class', 'dialogue_sel');
        gymButton.innerHTML = "Go to the Gym";
        mcBox.innerHTML += "<br><br>";
        mcBox.appendChild(gymButton);
        var gymButton = document.createElement("button");
        gymButton.setAttribute('onclick', 'skipDay()');
        gymButton.setAttribute('class', 'dialogue_sel');
        gymButton.innerHTML = "File Paperwork";
        mcBox.innerHTML += "<br><br>";
        mcBox.appendChild(gymButton);
    }

    var coffeeJobTxt1 = ["coffeeJobTxt1",
        ["You've decided to work at the coffee shop today. ⬇", false],
        ["The smell of coffee and pastries in the war air never gets old. ⬇", false],
        ["Business is slow, but there are enough customers to keep you occupied. ⬇", false]]
    
    var altheaFriendshipTxt1 = ["altheaFriendshipTxt1",
        ["A couple hours into your shift, an unfamiliar employee scrambles to her post. ⬇", false],
        ["In all the years you've worked here, you've never once seen her. You usually hear about new hires. ⬇", false],
        ["She appears anxious...", true],
        [{"Greet her kindly.":'YOU: "Good morning. How are you?" ⬇',
          "Ask if she's okay.":'YOU: "Hey...are you alright?" ⬇',
          "Leave her be.":"You remain quiet, but noticing your gaze, she addresses you. ⬇"}, false],
        ['COWORKER GIRL: "Hey, sorry...I\'m okay. Just a busy morning." ⬇', false],
        ['COWORKER GIRL: "Oh, I guess we haven\'t met. I\'m Althea, Steve\'s sister." ⬇', false],
        ["Steve, the boss, had mentioned the possibility of hiring his sister. You didn't realize she'd be so young. ⬇", false],
        ["The silence is awkward...", true],
        [{"Introduce yourself.":'ALTHEA: "\'You\'... You\'re me? Oh, your name is You? That\'s a bit confusing... Oh—" ⬇',
          "Compliment her name.":'ALTHEA: "Aw, thank you! I\'ve always liked my name. It means \'wholesome,\' apparently. Oh—" ⬇',
          "Let it stay awkward.":'ALTHEA: "...Well...back to work, I guess."'}, false],
        ['ALTHEA: "I\'d better get to it. I\'ll be in the back, by the way, in case you need anything." ⬇', false],
        ["Althea heads into the back and fiddles with a coffee machine. You settle in for the rest of the shift. ⬇", false],
        ["...... ⬇", false],
        ["About twenty customers later, you clock out. Althea stays behind, but notices you on your way out. ⬇", false],
        ['ALTHEA: "See you later!"', true],
        [{"Call back to her.":'You say "Goodbye!" on your way out. You\'ve made friends with Althea! ⬇',
          "Wave politely.":'You wave back to her on your way out. You\'ve made friends with Althea! ⬇',
          "Stay quiet.":'You leave silently. You\'ve (silently) made friends with Althea! ⬇'}, false]]

    async function coffeeJob() {
        bringBoxUp();
        changeBG(coffeeShopImg);
        if (girlFriendship < 1) {
            textReader(coffeeJobTxt1, 1, null, "textReader(altheaFriendshipTxt1, 1, null, 'bringBoxDown()')");//second function goes quotation, then apostrophe for all else
            girlFriendship += 1;
            money += 120;
        } else {
            textReader(coffeeJobTxt1, 1, null, "bringBoxDown()");
            money += 120;
        }
    }
    
    async function gymTrip() {};
    
    async function skipDay() {};
         
    async function returnToMenu() {
        //use this to bring box down, then change scene to bedroom and put back menu buttons
    }
    
    async function returnHomeNight() {
        // bring box down, change scene to home, text scroll
    }
</script>

JavaSim


YOU
YOUR STATS
Money: $200
Food: Some
Health: Fair
Friendship: Poor

Test for text.
%%html
<style>
    #header_tag {
        background-color: blue;
        padding: 10px;
        border: 3px solid;
        color: white;
        font-size: 30px;
        margin: auto;
        text-align: center;
        justify-content: center;
    }
    #displays {
        display: flex;
        justify-content: space-between;
    }
    #left_box {
        position: relative;
        align-items: center;
        text-align: center;
        color: white;
        font-size:20px;
        width:25%;
    }
    
    #right_box {
        position: relative;
        align-items: center;
        color: white;
        font-size:20px;
        width:25%;
    }
    
    #bottom_text {
        border-radius: 10px;
        padding: 8px;
        padding-left:25px;
        padding-right:20px;
        background-color: rgba(0,0,0,.5);
        opacity:0;
        width: 65%;
        height: 100px;
        max-height: 16%;
        color: white;
        margin: auto;
        text-align:left;
        font-size: 20px;
        top: -10%;s
        font-family: 'trebuchet ms',sans-serif;
        position:relative;
        transform:none;
        display:none;
    }
    
    .dialogue_selection {
        background-color: rgba(0,0,0,.5);
        border-radius: 10px;
        border:0px;
        font-family: "trebuchet ms",sans-serif;
        color:white;
    }
    
    .dialogue_selection:hover {
        color:yellow;
    }
    
</style>
<html>
    <div id="header_tag">
        <p>New Text <button id="click2start" onclick="startThis()" style="font-size:20px;color:white;">PRESS TO START</button><button onclick="tboxUp()">BringUp</button><button onclick="tboxDown()">BringDown</button><button onclick="fadeBG(coffeeShopImg)">Cafe BG</button></p>
    </div>
    <br>
    <div id="displays">
        <div id="left_box">
        </div>
        <div id="bg_container" style="border:5px solid tan">
            <img id="bg_img" width="360" src="https://images.nightcafe.studio/jobs/NbZpn5mHCc4aOJnTkTgy/NbZpn5mHCc4aOJnTkTgy--1--0t64w.jpg">
        </div>
        <div id="right_box">
        </div>
    </div>
    <br>
    <footer id="bottom_text">
        <div id="textbox" style="font-family:'trebuchet ms', sans-serif">
            Test for text.
        </div>
    </footer>
</html>
<script>
    var homeImg = "https://images.nightcafe.studio/jobs/NbZpn5mHCc4aOJnTkTgy/NbZpn5mHCc4aOJnTkTgy--1--0t64w.jpg";
    var libraryImg = "https://images.nightcafe.studio/jobs/TEL9XQrgZjuwl6B4QZfy/TEL9XQrgZjuwl6B4QZfy--1--zhwiv.jpg";
    var restaurantImg = "https://images.nightcafe.studio/jobs/dwF793B80vPVu02QgpBr/dwF793B80vPVu02QgpBr--1--nin9p.jpg";
    var emptyMallImg = "https://images.nightcafe.studio/jobs/KMC8kBhuArYOBPh2uqOa/KMC8kBhuArYOBPh2uqOa--4--e4c1a.jpg";
    var paintStoreImg = "https://images.nightcafe.studio/jobs/zIuQs58EyZLDHaoBQczv/zIuQs58EyZLDHaoBQczv--1--ju4n7.jpg";
    var theaterImg = "https://images.nightcafe.studio/jobs/5aYVoYaeY4Emeqz4kWCK/5aYVoYaeY4Emeqz4kWCK--1--pq6zs.jpg";
    var portTownImg = "https://images.nightcafe.studio/jobs/gHRptHyVSYhtrlaeUPSG/gHRptHyVSYhtrlaeUPSG--1--m92sn.jpg";
    var coffeeShopImg = "https://images.nightcafe.studio/jobs/FSo7lDApBFLCDQ3ju93L/FSo7lDApBFLCDQ3ju93L--1--rityb.jpg";
    var groceryImg = "https://images.nightcafe.studio/jobs/xtBJjU41vSKfNsqxAugW/xtBJjU41vSKfNsqxAugW--1--g6vvi.jpg";
    var neighborhoodImg = "https://images.nightcafe.studio/jobs/uhPEXwNAF9FCXjzrfbHi/uhPEXwNAF9FCXjzrfbHi--1--mrlsz.jpg";
    var hospitalLobbyImg = "https://images.nightcafe.studio/jobs/0M0yyI648y04m4rXZR3e/0M0yyI648y04m4rXZR3e--1--xlxsn.jpg";
    var allScenes = [homeImg, libraryImg, restaurantImg, emptyMallImg, paintStoreImg, theaterImg, portTownImg, coffeeShopImg, groceryImg, neighborhoodImg, hospitalLobbyImg];
    var textbox = document.getElementById('textbox');
    var textboxCont = document.getElementById('bottom_text');
    var leftBox = document.getElementById('left_box');
    var bgImage = document.getElementById('bg_img');
    var rightBox = document.getElementById('right_box');
    
    function tboxUp() {
        textboxCont.style["display"] = "block";
        textboxCont.style["top"] = "-10%"; //goal is up to "-25%"
        var posVal = 10;
        var opVal = 0.00625;
        var animateBoxUp = setInterval(function() {
            posVal++;
            opVal = opVal * 1.5;
            textboxCont.style["top"] = "-" + String(posVal) + "%";
            textboxCont.style["opacity"] = String(opVal);
            if (posVal >= 25) {
                textboxCont.style["top"] = "-25%";
                textboxCont.style["opacity"] = "1";
                clearInterval(animateBoxUp);
            };
        }, 20);
    }
    
    function tboxDown() {
        textboxCont.style["display"] = "block";
        textboxCont.style["top"] = "-25%"; //goal is up to "-25%"
        textboxCont.style["opacity"] = "1";
        var posVal = 25;
        var opVal = 1;
        var animBoxDown = setInterval(function() {
            posVal--;
            opVal = opVal / 1.5;
            textboxCont.style["top"] = "-" + String(posVal) + "%";
            textboxCont.style["opacity"] = String(opVal);
            if (posVal <= 10) {
                clearInterval(animBoxDown);
                textboxCont.style["top"] = "-10%";
                textboxCont.style["opacity"] = "0";
                textbox.innerHTML = "";
                textboxCont.setAttribute('onclick', '');
                textboxCont.style["cursor"] = 'auto';
            };
        }, 20);
    }
    
    function fadeBG(imageURL) {
        return new Promise(resolve => {
            bgImage.style["opacity"] = "1";
            var opVal = 1;
            var fadeOut = setInterval(function() {
                opVal -= 0.1;
                bgImage.style["opacity"] = String(opVal);
                if (opVal <= 0) {
                    clearInterval(fadeOut);
                    bgImage.style["opacity"] = "0";
                }
            }, 50);
            setTimeout(function() {
                opVal = 0;
                bgImage.src = imageURL;
                var fadeIn = setInterval(function() {
                opVal += 0.1;
                bgImage.style["opacity"] = String(opVal);
                if (opVal >= 1) {
                    clearInterval(fadeIn);
                    bgImage.style["opacity"] = "1";
                    resolve(true);
                }
            }, 50);
            }, 700)
        })
    }
    
    var timeout = async ms => new Promise(res => setTimeout(res, ms));
    var next = false;
    
    async function waitUserInput() {
        while (next === false) await timeout(50); // pauses script
        next = false;
    }
    
    async function typeMessage(text) {
        return new Promise(resolve => {
            textbox.innerHTML = "";
            char = 0;
            string = "";
            var typeProcess = setInterval(() => {
                string += text[char];
                textbox.innerHTML = string;
                char++;
                if (char >= text.length) {
                    clearInterval(typeProcess);
                    textboxCont.onclick = function() {next = true};
                    textboxCont.style["cursor"] = "pointer";
                    resolve(true);
                }
            }, 15);
        });
    }
    
    async function typeScene(list) {
        for (let i = 0; i < list.length; i++) {
            var currentText = list[i];
            await typeMessage(currentText);
            await waitUserInput();
            textboxCont.onclick = "";
            textboxCont.style["cursor"] = "";
        };
        return new Promise(resolve => {resolve(true)});
    }

    async function startThis() {
        tboxUp();
        await typeScene(["This is the first line.", "This is the second line."]);
        tboxDown();
        for (bg of allScenes) {
            await fadeBG(bg);
        }
    }
</script>

New Text



Test for text.