Code viewer for World: Pokemon Trading Card Generator
// This world uses the OpenAI API for the content generation
// Documentation: https://platform.openai.com/docs/overview

// I am crediting the resources I used to create my UI below.
// Wave design: https://www.youtube.com/watch?v=MMNEEdGa5eE
// Form design: https://codepen.io/rickyeckhardt/pen/poJwRRb
// Pokemon design Card: https://codepen.io/heatherketten/pen/VXxedL
// Type Icons : http://www.rw-designer.com/icon-set/pokemon-types

document.write(`
    <style>
      @import url("https://fonts.googleapis.com/css2?family=Londrina+Solid:wght@100;300;400;900&display=swap");

      body {
        margin: 0;
        padding: 0;
        font-family: "Londrina Solid", sans-serif;
      }

      .background {
        position: sticky;
        inset: 0;
      }

      section {
        position: relative;
        width: 100%;
        height: 100vh;
        background: #54c6eb;
        overflow: hidden;
      }

      section .wave {
        position: absolute;
        bottom: 0;
        left: 0;
        width: 100%;
        height: 100px;
        background: url("https://ancientbrain.com/uploads/reeve/wave.png");
        background-size: 1000px 100px;
      }

      section .wave.wave1 {
        animation: animate1 30s linear infinite;
        z-index: 4;
        opacity: 1;
        animation-delay: 0s;
        bottom: 0;
      }
      @keyframes animate1 {
        0% {
          background-position-x: 0;
        }

        100% {
          background-position-x: 1000px;
        }
      }

      section .wave.wave2 {
        animation: animate2 15s linear infinite;
        z-index: 3;
        opacity: 0.5;
        animation-delay: -5s;
        bottom: 10px;
      }
      @keyframes animate2 {
        0% {
          background-position-x: 0;
        }

        100% {
          background-position-x: -1000px;
        }
      }

      section .wave.wave3 {
        animation: animate3 30s linear infinite;
        z-index: 2;
        opacity: 0.2;
        animation-delay: -2s;
        bottom: 15px;
      }
      @keyframes animate3 {
        0% {
          background-position-x: 0;
        }

        100% {
          background-position-x: -1000px;
        }
      }

      section .wave.wave4 {
        animation: animate4 5s linear infinite;
        z-index: 1;
        opacity: 0.7;
        animation-delay: -5s;
        bottom: 20px;
      }
      @keyframes animate4 {
        0% {
          background-position-x: 0;
        }

        100% {
          background-position-x: -1000px;
        }
      }
      
      .spinner {
          border: 8px solid #f3f3f3; /* Light grey */
          border-top: 8px solid #3498db; /* Blue */
          border-radius: 50%;
          width: 50px;
          height: 50px;
          animation: spin 1s linear infinite;
          display: none; /* Initially hidden */
        }
        @keyframes spin {
          0% { transform: rotate(0deg); }
          100% { transform: rotate(360deg); }
        }

      .content {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 100;
        width: 100%;
        height: 100vh;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
        align-items: center;
      }

      .form-group {
        margin: 1rem;
        max-width: 50%;
        background: #f8f4e5;
        padding: 30px 80px;
        border: 2px solid black;
        box-shadow: 15px 15px 1px #ffa580, 15px 15px 1px 2px black;
      }

      input {
        display: block;
        width: 100%;
        padding: 12px 0px;
        font-size: 14px;
        border: none;
        border-bottom: 5px solid black;
        background: #f8f4e5;
        min-width: 250px;
        padding-left: 5px;
        outline: none;
        color: black;
      }

      input:focus {
        border-bottom: 5px solid #ffa580;
      }

      button {
        display: block;
        margin: 0 auto;
        line-height: 24pt;
        padding: 0 20px;
        background: #ffa580;
        letter-spacing: 2px;
        transition: 0.2s all ease-in-out;
        outline: none;
        border: 1px solid black;
        box-shadow: 3px 3px 1px 1px darkgrey, 3px 3px 1px 2px black;
      }
      button:hover {
        background: black;
        color: white;
        border: 1px solid black;
      }

      .title {
        font-size: 48px;
        color: #ffa580;
        text-shadow: 2px 2px 5px black;
      }

      .subtitle {
        font-size: 28px;
        color: #ffa580;
        text-shadow: 1px 1px 1px black;
      }
    </style>

    <style>
      .grid-card-group {
        margin: 2rem;
        display: grid;
        grid-template-columns: auto auto;
        grid-gap: 1rem;
      }

      .grid-individual-card {
        /* set size of card but still allow them to be arranged higher up with grid & auto */
        width: 315px;
        height: 500px;
        /* serve as containers for card information - but with fixed sizes */
        display: inline-grid;
        /* there are 6 columns which helps with organization later */
        grid-template-columns: auto auto auto auto auto auto;
        /* thick yellow rounded border */
        border: 10px solid gold;
        border-radius: 20px;
        padding: 5px;

        box-shadow: 5px 10px 10px #4a90e2;
      }
      
      .card-background-normal {
          /* Second class to set NORMAL card backgrounds */
          background: linear-gradient(to bottom right, #dcdcdc, #b4b4b4, #8c8c8c, #646464, #8c8c8c, #b4b4b4);
        }

      .card-background-water {
        /* Second class to set BLUE card backgrounds for WATER types */
        background: linear-gradient(
          to bottom right,
          #d9e4ec,
          #adc1d1,
          #7d99af,
          #557792,
          #7d99af,
          #adc1d1
        );
      }
      .card-background-fire {
        /* Second class to set RED-ORANGE card backgrounds for FIRE types */
        background: linear-gradient(
          to bottom right,
          #ffbfba,
          #ff978f,
          #ff756b,
          #ff594c,
          #ff756b,
          #ff978f
        );
      }
      .card-background-grass {
        /* Second class to set GREEN card backgrounds for PLANT/LEAF/GRASS types */
        background: linear-gradient(
          to bottom right,
          #aae5b7,
          #78cf8b,
          #4fb665,
          #319d49,
          #4fb665,
          #78cf8b
        );
      }
      .card-background-electric {
        /* Second class to set YELLOW card backgrounds for ELECTRIC/LIGHTNING types */
        background: linear-gradient(
          to bottom right,
          #ffffa4,
          #fff870,
          #fff200,
          #fff870,
          #ffffa4,
          #fff870
        );
      }
      .card-background-ground {
        /* Second class to set GROUND card backgrounds */
        background: linear-gradient(
          to bottom right,
          #e9c895,
          #d4aa67,
          #b89241,
          #9e7a1f,
          #b89241,
          #d4aa67
        );
      }

      .card-background-sand {
        /* Second class to set SAND card backgrounds */
        background: linear-gradient(
          to bottom right,
          #f7e4bb,
          #e3cb8b,
          #cfb05f,
          #b8933a,
          #cfb05f,
          #e3cb8b
        );
      }

      .card-background-rock {
        /* Second class to set ROCK card backgrounds */
        background: linear-gradient(
          to bottom right,
          #b8b8b8,
          #a1a1a1,
          #8b8b8b,
          #747474,
          #8b8b8b,
          #a1a1a1
        );
      }

      .card-background-flying {
        /* Second class to set FLYING card backgrounds */
        background: linear-gradient(
          to bottom right,
          #d7e8f6,
          #aed0eb,
          #87b7e1,
          #5f9fd9,
          #87b7e1,
          #aed0eb
        );
      }

      .card-background-ice {
        /* Second class to set ICE card backgrounds */
        background: linear-gradient(
          to bottom right,
          #b3e1f5,
          #85cde3,
          #5ab9d1,
          #3aa0be,
          #5ab9d1,
          #85cde3
        );
      }

      .card-background-bug {
        /* Second class to set BUG card backgrounds */
        background: linear-gradient(
          to bottom right,
          #c8d7a8,
          #a3b980,
          #7f9d5e,
          #5d8040,
          #7f9d5e,
          #a3b980
        );
      }

      .card-background-electric {
        /* Second class to set ELECTRIC card backgrounds */
        background: linear-gradient(
          to bottom right,
          #ffea94,
          #ffd64f,
          #ffcd00,
          #ffd64f,
          #ffea94,
          #ffd64f
        );
      }

      .card-background-steel {
        /* Second class to set STEEL card backgrounds */
        background: linear-gradient(
          to bottom right,
          #d1d1d1,
          #b5b5b5,
          #999999,
          #7d7d7d,
          #999999,
          #b5b5b5
        );
      }

      .card-background-fighting {
        /* Second class to set FIGHTING card backgrounds */
        background: linear-gradient(
          to bottom right,
          #ffa39e,
          #ff817c,
          #ff5e5b,
          #ff3b3c,
          #ff5e5b,
          #ff817c
        );
      }

      .card-background-poison {
        /* Second class to set POISON card backgrounds */
        background: linear-gradient(
          to bottom right,
          #c18dc1,
          #a566a5,
          #864186,
          #642c64,
          #864186,
          #a566a5
        );
      }

      .card-background-dark {
        /* Second class to set DARK card backgrounds */
        background: linear-gradient(
          to bottom right,
          #727272,
          #595959,
          #404040,
          #262626,
          #404040,
          #595959
        );
      }

      .card-background-phychic {
        /* Second class to set PSYCHIC card backgrounds */
        background: linear-gradient(
          to bottom right,
          #f0aad4,
          #e085bb,
          #cd5fa6,
          #ab3a92,
          #cd5fa6,
          #e085bb
        );
      }

      .card-background-ghost {
        /* Second class to set GHOST card backgrounds */
        background: linear-gradient(
          to bottom right,
          #aa96b5,
          #857a9d,
          #615f86,
          #40406a,
          #615f86,
          #857a9d
        );
      }

      .card-background-fairy {
        /* Second class to set FAIRY card backgrounds */
        background: linear-gradient(
          to bottom right,
          #ffc3e1,
          #ffa3c0,
          #ff82a0,
          #ff6083,
          #ff82a0,
          #ffa3c0
        );
      }

      .card-background-dragon {
        /* Second class to set DRAGON card backgrounds */
        background: linear-gradient(
          to bottom right,
          #a1daff,
          #75c3ff,
          #4aa9ff,
          #288fff,
          #4aa9ff,
          #75c3ff
        );
      }

      div {
        /* Force vertical align within cells = center */
        vertical-align: center;
        /* Sets font for everything on the page */
        font-family: "Lato", "Gill Sans", "Gill Sans MT", "Calibri", sans-serif;
        font-stretch: ultra-condensed;
      }

      /* SET UP THE CARD TEXT */

      /* HEADER - ABOVE IMAGE */

      .header-basic-pokemon {
        font-size: 9px;
        font-weight: 600;
        /* span all the way across the card */
        grid-column-start: 1;
        grid-column-end: 7;
      }

      .header-pokemon-name {
        font-size: 20px;
        font-weight: 600;
        /* span first 4 columns of card grid */
        grid-column-start: 1;
        grid-column-end: 5;
      }

      .header-hp {
        /* Note: Fills 1 cell - 5th cell of row */
        /* red font color */
        color: crimson;
        font-size: 18px;
        font-weight: 600;
        text-align: right;
      }

      .header-type-icon {
        /* Note: Fills 1 cell - 6th and last cell of row */
        /* larger font size */
        margin-left: 0.25em;
        width: 1.5rem;
        height: 1.5rem;
      }

      /* IMAGE */

      .image-holder {
        /* needs to span across card */
        grid-column-start: 1;
        grid-column-end: 7;
        color: blue;
      }

      .pokemon-image {
        /* Force desired image size */
        width: 300px;
        height: 200px;
        border: 5px yellow outset;
        box-shadow: 3px 3px 10px #000;
      }

      /* DESCRIPTION BELOW IMAGE */

      .description-below-image {
        /* Note: Has 1 empty cell on either side */
        /* These have a class in the HTML but it is not used */
        /* Span from cell 2 to cell 5 */
        grid-column-start: 2;
        grid-column-end: 6;
        text-align: center;
        font-size: 10px;
        font-style: italic;
        font-weight: 600;
      }

      .description-below-image-background {
        /* Set this way because it looks a little better if it's more dynamic */
        background: linear-gradient(to right, goldenrod, yellow, goldenrod);
        padding: 2px 10px;
        margin: 2px;
      }

      /* ATTACKS */

      .attack-center {
        /* Only used if the attack does not have a description */
        text-align: center;
      }

      .attack-cost {
        /* Not necessary if using images for attack cost icons */
        font-size: 18px;
        text-align: center;
        padding: 3px;
        border-bottom: 1px solid black;
        border-left: 1px solid #aaa;
        border-top: 1px solid #aaa;
      }

      .attack-name {
        font-size: 16px;
        font-weight: 600;
      }

      .attack-description {
        /* Span from cell 2 to cell 5 */
        grid-column-start: 2;
        grid-column-end: 6;
        font-size: 13px;
        font-weight: 400;
        padding: 5px;
        border-bottom: 1px solid black;
        border-top: 1px solid #aaa;
      }

      .attack-damage {
        font-weight: 400;
        font-size: 20px;
        text-align: center;
        padding: 5px;
        border-bottom: 1px solid black;
        border-top: 1px solid #aaa;
        border-right: 1px solid #aaa;
      }

      /* WEAKNESS, RESISTANCE, RETREAT COST BOXES */

      .wrr-header {
        text-align: center;
        font-size: 10px;
        font-weight: 900;
      }

      .wrr-cost {
        text-align: center;
        font-size: 18px;
      }

      .weakness {
        grid-column-start: 1;
        grid-column-end: 3;
      }

      .resistance {
        grid-column-start: 3;
        grid-column-end: 5;
      }

      .retreat {
        grid-column-start: 5;
        grid-column-end: 7;
      }

      /* DESCRIPTION ABOVE COPYRIGHT */

      .description-above-copyright {
        grid-column-start: 1;
        grid-column-end: 7;
        font-style: italic;
        font-weight: 600;
        font-size: 11px;
      }

      ul {
        margin: 0;
        padding: 0;
      }

      .description-above-copyright-border {
        /* This is a list item for the purposes of the exercise requirements */
        list-style: none;
        margin: 0;
        padding: 5px;
        border: 1.5px ridge gold;
        border-radius: 4px;
      }

      /* COPYRIGHT - Very bottom of card */

      .copyright {
        text-align: center;
        font-size: 7.2px;
        grid-column-start: 1;
        grid-column-end: 7;
      }
    </style>
    <div class="content">
      <h1 class="title">Let's generate some Pokemons Cards!</h1>
      <div id="enterkey" class="subtitle">
        Enter API key:
        <div style="display:flex; flex-direction: column;">
      	  <input    style='width:25vw; margin-bottom: 0.5rem'    maxlength='2000'    id="apikey"       VALUE='' >  
      	  <button onclick='setkey();'  class=ab-normbutton >Set API key</button>
        </div>
      </div>
      <div class="form-group">
        <form id="myForm">
          <h3 class="subtitle">Some questions to understand the pokemon</h3>
          <input
            id="size"
            style="margin-bottom: 1em;"
            placeholder="How big should it be?"
            required=""
            type="text" />
          <input
            id="biome"
            style="margin-bottom: 1em;"
            placeholder="What biome should it be from?"
            required=""
            type="text" />
          <input
            id="personality"
            style="margin-bottom: 1em;"
            placeholder="What should its personality be like? (kind, aggressive..)"
            required=""
            type="text" />
          <button id="btn" type="submit">Submit!</button>
        </form>
      </div>

      <div style="margin-top: 4rem">
        <h1 class="title" style="text-align: center">
          Generated by ChatGPT and Dall-e-2
        </h1>
        <div id="msg">
            <p>This is gonna take a while (around 1min)...</p>
        </div>
        <div style="display: flex; justify-content: center;">
            <div class="spinner" id="loadingSpinner"></div>
        </div>
        <div id="poke" class="grid-card-group"></div>
      </div>
    </div>

    <!-- Background Wave -->
    <div class="background">
      <section>
        <div class="wave wave1"></div>
        <div class="wave wave2"></div>
        <div class="wave wave3"></div>
        <div class="wave wave4"></div>
      </section>
    </div>
`);

$(document).ready(() => {
    document.getElementById("myForm").addEventListener("submit", function (event) {
      event.preventDefault();
    
      handleFormSubmit();
    });
})

const API_BASE_URL = "https://api.openai.com/v1";
let apikey = "";

function setkey()          
{
	apikey =  $("#apikey").val();
	apikey = apikey.trim();
	$("#enterkey").html ( "<b> API key has been set. </b>" );
}

async function handleFormSubmit() {
  // Retrieve input values
  var size = document.getElementById("size").value;
  var biome = document.getElementById("biome").value;
  var personality = document.getElementById("personality").value;

  // Perform perform validation with the input values
  if (size.trim() === "" || biome.trim() === "" || personality.trim() === "") {
    alert("Fields cannot be empty");
    return;
  }
  if (size.length < 3 || biome.length < 3 || personality.length < 3) {
    alert("Input too small");
    return;
  }

  // Fetching the data
  try {
    showLoadingSpinner();
    console.log("fetching data");

    let prompt = `Given information is that the pokemon's size should be ${size}. Should be based on the biome ${biome}. Should have the personality of ${personality}.`;

    let data = {
      model: "gpt-3.5-turbo",
      temperature: 0.7,
      messages: [
        {
          role: "system",
          content:
            "Pretend you are a pokemon card designer. Create 4 Pokemons cards based on the information provided. Pokemons should be of a single type. Should have two moves. Give the output in json format {pokemons: [{name, type, description, moves: [{attack_name, description, damage_value}], length, weight, fact, animal, hp}]}",
        },
        {
          role: "user",
          content: prompt,
        },
      ],
    };

    const response = await fetch(API_BASE_URL + "/chat/completions", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: "Bearer " + apikey,
      },
      body: JSON.stringify(data),
    });
    
    document.getElementById("msg").innerHTML += "<p>fecthing data from gpt model</p>"
    
    const res = await response.json();
    const { pokemons } = JSON.parse(res.choices[0].message.content);
    
    document.getElementById("msg").innerHTML += "<p>Recieved data from gpt model</p>"
    console.log(pokemons);
    
    document.getElementById("msg").innerHTML += "<p>fecthing data from dall-e-2 model</p>"
    pokemons.forEach(async (pokemon, id) => {
      const input = {
        model: "dall-e-2",
        prompt: pokemon.description,
        n: 1,
        size: "1024x1024",
      };
        $.ajaxSetup({
          headers:
          {
                "Content-Type": "application/json",
                "Authorization": "Bearer " + apikey  
          }
        });
        let dalle_res = await $.ajax({
            type: "POST",
            url: API_BASE_URL + "/images/generations",
            data: JSON.stringify(input),
            dataType: "json",
        });

      const type = pokemon.type.toLowerCase();
      const iconUrl = "https://ancientbrain.com/uploads/reeve/" + (type == "water" ? "Water" : type) + ".png";

      document.getElementById("poke").innerHTML += `
            <div key=${id} class="grid-individual-card card-background-${type}" data-toggle="tooltip" title="${pokemon.description}">
                <div class="header-basic-pokemon">Basic Pokémon</div>
                <div class="header-pokemon-name">${pokemon.name}</div>
                <div class="header-hp">${pokemon.hp} HP</div>
                <img class="header-type-icon" src=${iconUrl} width="16px" height="16px">
                <div class="image-holder"><img class="pokemon-image" src=${dalle_res.data[0].url} alt = "Pikachu"></div>
                <div class="description-below-image-empty"><!-- empty cell to position description correctly --></div>
                <div class="description-below-image"><span class="description-below-image-background">${pokemon.animal} Pokémon. Length: ${pokemon.length}, Weight: ${pokemon.weight}.</span></div>
                <div class="description-below-image-empty"><!-- empty cell to position description correctly --></div>
                <div class="attack-cost">☻</div>
                <div class="attack-description"><span class="attack-name">${pokemon.moves[0].attack_name}</span> ${pokemon.moves[0].description}</div>
                <div class="attack-damage">${pokemon.moves[0].damage_value}</div>
                <div class="attack-cost">☻ ☻</div>
                <div class="attack-description"><span class="attack-name">${pokemon.moves[1].attack_name}</span> ${pokemon.moves[1].description}</div>
                <div class="attack-damage">${pokemon.moves[1].damage_value}</div>
                <div class="weakness wrr-header">weakness</div>
                <div class="resistance wrr-header">resistance</div>
                <div class="retreat wrr-header">retreat cost</div>
                <div class="weakness wrr-cost">☻</div>
                <div class="resistance wrr-cost"></div>
                <div class="retreat wrr-cost">☻</div>
                <div class="description-above-copyright">
                <ul>
                    <li class="description-above-copyright-border">${pokemon.fact}</li>
                </ul>
                </div>
                <div class="copyright"><strong>Illus. Mitsuhiro Arita</strong> ©1995, 96, 98 Nintendo Creatures, GAMEFREAK ©1999 Wizards. <strong>58/102 ●</strong></div>
            </div>
        `;
    });
  } catch (e) {
    document.getElementById("msg").innerHTML += "<p>error while fecthing. check console.</p>"
    console.log(e);
  } finally {
    hideLoadingSpinner();  
  }
}

function showLoadingSpinner() {
  document.getElementById('loadingSpinner').style.display = 'block';
}

function hideLoadingSpinner() {
  document.getElementById('loadingSpinner').style.display = 'none';
}