Ron Au
Initial D
5c239ba
raw history blame
No virus
6.25 kB
/* HTML generation */
const TYPES = {
Grass: '🍃',
Fire: '🔥',
Water: '💧',
Lightning: '⚡',
Fighting: '✊',
Psychic: '👁️',
Colorless: '⭐',
Darkness: '🌑',
Metal: '⚙️',
Dragon: '🐲',
Fairy: '🧚',
};
const energyHTML = (type, types = TYPES) => {
return `<span title="${type} energy" class="energy ${type.toLowerCase()}">${types[type]}</span>`;
};
const attackDescriptionHTML = (text) => {
if (!text) {
return '';
}
let fontSize;
if (text.length > 185) {
fontSize = 0.7;
} else if (text.length > 140) {
fontSize = 0.8;
} else if (text.length > 90) {
fontSize = 0.9;
}
return `<span class="attack-details"${fontSize ? ` style="font-size: ${fontSize.toString()}em"` : ''}>${text}</span>`;
};
const attackRowsHTML = (attacks) => {
return attacks
.map((attack) => {
const { cost, damage, name, text } = attack;
return `
<li class="attacks-row grid-three">
<div class="attack-cost">
${cost.map((energy) => energyHTML(energy)).join('')}
</div>
<span class="attack-text">
<span class="attack-name">${name}</span>
${attackDescriptionHTML(text)}
</span>
<span class="attack-damage">${damage ? damage : ''}</span>
</li>
<hr role="presentation" />`;
})
.join('');
};
const cardHTML = (details) => {
const { hp, energy_type, species, length, weight, attacks, weakness, resistance, retreat, description, rarity } =
details;
const poke_name = details.name; // `name` would be reserved JS word
return `
<div class="pokecard ${energy_type.toLowerCase()}" data-displayed="true">
<p class="evolves">Basic Pokémon</p>
<header>
<h1 class="name">${poke_name}</h1>
<div>
<span class="hp">${hp} HP</span>
${energyHTML(energy_type)}
</div>
</header>
<img class="picture frame" alt="AI generated Pokémon called ${poke_name}" />
<div class="species frame">
${species} Pokémon. Length: ${length.feet}'${length.inches}", Weight: ${weight}
</div>
<ul class="attacks">
${attackRowsHTML(attacks)}
</ul>
<div class="multipliers">
<div class="weakness">
<span>weakness</span>
${weakness ? energyHTML(weakness) : ''}
</div>
<div class="resistance">
<span>resistance</span>
${resistance ? energyHTML(resistance) : ''}
<span class="resistance-total"
>${resistance ? '-30' : ''}</span
>
</div>
<div class="retreat-cost">
<span>retreat cost</span>
<div>${energyHTML('Colorless').repeat(retreat)}</div>
</div>
</div>
<p class="description frame">${description}</p>
<div class="footer grid-three">
<span
><a
href="https://huggingface.co/minimaxir/ai-generated-pokemon-rudalle"
>Illus. Max Woolf</a
></span
>
<span><a href="">2022 Hugging Face</a></span>
<span>${rarity}</span>
</div>
</div>`;
};
/* Utility */
const getBasePath = () => {
return document.location.origin + document.location.pathname;
};
const generateDetails = async () => {
const details = await fetch(`${getBasePath()}/details`);
return await details.json();
};
const createTask = async (prompt) => {
const taskResponse = await fetch(`${getBasePath()}task/create?prompt=${prompt}`);
const task = await taskResponse.json();
return task;
};
const queueTask = (task_id) => {
fetch(`${getBasePath()}task/queue?task_id=${task_id}`);
};
const pollTask = async (task) => {
const taskResponse = await fetch(`${getBasePath()}task/poll?task_id=${task.task_id}`);
return await taskResponse.json();
};
const longPollTask = async (task, interval = 5_000, max) => {
if (task.status === 'complete' || (max && task.poll_count > max)) {
return task;
}
const taskResponse = await fetch(`${getBasePath()}task/poll?task_id=${task.task_id}`);
task = await taskResponse.json();
if (task.status === 'complete' || task.poll_count > max) {
return task;
}
await new Promise((resolve) => setTimeout(resolve, interval));
return await longPollTask(task, interval, max);
};
/* DOM */
const generateButton = document.querySelector('button.generate');
const rotateCard = () => {
const RANGE = 0.1;
const INTERVAL = 13; // ~75 per second
let previousTime = 0;
// Throttle closure
return (card, containerMouseEvent) => {
const currentTime = performance.now();
if (currentTime - previousTime > INTERVAL) {
previousTime = currentTime;
const rect = card.getBoundingClientRect();
const rotateX = (containerMouseEvent.clientY - rect.y - rect.height / 2) * RANGE;
const rotateY = -(containerMouseEvent.clientX - rect.x - rect.width / 2) * RANGE;
card.style.setProperty('--rotate-x', rotateX + 'deg');
card.style.setProperty('--rotate-y', rotateY + 'deg');
}
};
};
const cardRotationInitiator = (renderSection) => {
let currentCard;
return () => {
let handleMouseMove;
if (currentCard) {
handleMouseMove = rotateCard().bind(null, currentCard);
renderSection.removeEventListener('mousemove', handleMouseMove, true);
}
const newCard = document.querySelector('.pokecard');
currentCard = newCard;
handleMouseMove = rotateCard().bind(null, newCard);
renderSection.addEventListener('mousemove', handleMouseMove, true);
};
};
generateButton.addEventListener('click', async () => {
const details = await generateDetails();
console.log({ details });
const renderSection = document.querySelector('section.render');
const durationSeconds = document.querySelector('.duration > .seconds');
const initialiseCardRotation = cardRotationInitiator(renderSection);
let duration = 0.0;
try {
const task = await createTask(details.energy_type);
queueTask(task.task_id);
const incrementSeconds = setInterval(() => {
duration += 0.1;
durationSeconds.textContent = duration.toFixed(1).toString();
}, 100);
const completedTask = await longPollTask(task);
clearInterval(incrementSeconds);
renderSection.innerHTML = cardHTML(details);
const picture = document.querySelector('img.picture');
picture.src = completedTask.value;
initialiseCardRotation();
} catch (err) {
console.error(err);
}
});