// 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';
}