final fantasy xiv character view

This commit is contained in:
2025-12-24 14:42:22 +01:00
parent 0d0aaa5f46
commit a709eff380
2 changed files with 334 additions and 0 deletions
+1
View File
@@ -46,5 +46,6 @@ const { title, description, permalink, current } = Astro.props;
width: 100%;
display: flex;
flex-direction: column;
container-type: inline-size;
}
</style>
+333
View File
@@ -0,0 +1,333 @@
---
import BaseLayout from "../../layouts/BaseLayout.astro";
const title = 'FFXIV';
const description = 'My FFXIV character';
const permalink = Astro?.site?.href ?? '/gaming/ffxiv';
---
<BaseLayout title={title} description={description} permalink={permalink}>
<div class="character-profile">
<div class="portrait">
<img src="" alt="Portrait of FFXIV character"/>
</div>
<div class="description">
<div class="info">
<span class="name">???</span>
<span class="technical">
<span class="server">???</span>
<span class="datacenter">(???)</span>
</span>
</div>
<hr style="width:100%; margin: 10px auto;"/>
<noscript>Data is fetched live and will not appear with javascript off.</noscript>
<div>
<span>Disciple of War</span>
<div class="jobs">
<div class="job" data-job="paladin">
<span class="name">Paladin</span>
<span class="level">??</span>
</div>
<div class="job" data-job="warrior">
<span class="name">Warrior</span>
<span class="level">??</span>
</div>
<div class="job" data-job="dark knight">
<span class="name">Dark Knight</span>
<span class="level">??</span>
</div>
<div class="job" data-job="gunbreaker">
<span class="name">Gunbreaker</span>
<span class="level">??</span>
</div>
<div class="job" data-job="white mage">
<span class="name">White mage</span>
<span class="level">??</span>
</div>
<div class="job" data-job="scholar">
<span class="name">Scholar</span>
<span class="level">??</span>
</div>
<div class="job" data-job="astrologian">
<span class="name">Astrologian</span>
<span class="level">??</span>
</div>
<div class="job" data-job="sage">
<span class="name">Sage</span>
<span class="level">??</span>
</div>
<div class="job" data-job="monk">
<span class="name">Monk</span>
<span class="level">??</span>
</div>
<div class="job" data-job="dragoon">
<span class="name">Dragoon</span>
<span class="level">??</span>
</div>
<div class="job" data-job="ninja">
<span class="name">Ninja</span>
<span class="level">??</span>
</div>
<div class="job" data-job="samurai">
<span class="name">Samurai</span>
<span class="level">??</span>
</div>
<div class="job" data-job="reaper">
<span class="name">Reaper</span>
<span class="level">??</span>
</div>
<div class="job" data-job="viper">
<span class="name">Viper</span>
<span class="level">??</span>
</div>
<div class="job" data-job="bard">
<span class="name">Bard</span>
<span class="level">??</span>
</div>
<div class="job" data-job="machinist">
<span class="name">Machinist</span>
<span class="level">??</span>
</div>
<div class="job" data-job="dancer">
<span class="name">Dancer</span>
<span class="level">??</span>
</div>
<div class="job" data-job="black mage">
<span class="name">Black Mage</span>
<span class="level">??</span>
</div>
<div class="job" data-job="summoner">
<span class="name">Summoner</span>
<span class="level">??</span>
</div>
<div class="job" data-job="red mage">
<span class="name">Red Mage</span>
<span class="level">??</span>
</div>
<div class="job" data-job="pictomancer">
<span class="name">Pictomancer</span>
<span class="level">??</span>
</div>
<div class="job" data-job="blue mage (limited job)">
<span class="name">Blue Mage</span>
<span class="level">??</span>
</div>
</div>
</div>
<div>
<div>
<span>Disciple of Land</span>
<div class="jobs">
<div class="job" data-job="miner">
<span class="name">Miner</span>
<span class="level">??</span>
</div>
<div class="job" data-job="botanist">
<span class="name">Botanist</span>
<span class="level">??</span>
</div>
<div class="job" data-job="fisher">
<span class="name">Fisher</span>
<span class="level">??</span>
</div>
</div>
</div>
</div>
<div>
<span>Disciple of Hand</span>
<div class="jobs">
<div class="job" data-job="carpenter">
<span class="name">Carpenter</span>
<span class="level">??</span>
</div>
<div class="job" data-job="blacksmith">
<span class="name">Blacksmith</span>
<span class="level">??</span>
</div>
<div class="job" data-job="armorer">
<span class="name">Armorer</span>
<span class="level">??</span>
</div>
<div class="job" data-job="goldsmith">
<span class="name">Goldsmith</span>
<span class="level">??</span>
</div>
<div class="job" data-job="leatherworker">
<span class="name">Leatherworker</span>
<span class="level">??</span>
</div>
<div class="job" data-job="weaver">
<span class="name">Weaver</span>
<span class="level">??</span>
</div>
<div class="job" data-job="alchemist">
<span class="name">Alchemist</span>
<span class="level">??</span>
</div>
<div class="job" data-job="culinarian">
<span class="name">Culinarian</span>
<span class="level">??</span>
</div>
</div>
</div>
</div>
</div>
</BaseLayout>
<script>
setPlaceholders(loadDataFromLocalStorage())
const request = await fetch("https://api.youhavetrouble.me/games/ffxiv/").catch(() => null);
if (request !== null && request.ok) {
const data = await request.json();
setPlaceholders(data);
saveDataToLocalStorage(data);
}
function setPlaceholders(data: any) {
if (data === null) {
return;
}
const profilePic = document.querySelector(".character-profile .portrait img") as HTMLImageElement | null;
if (profilePic) {
profilePic.src = data?.portrait_url;
}
const nameElem = document.querySelector(".character-profile .description .info .name") as HTMLElement | null;
if (nameElem) {
nameElem.textContent = data?.name ?? "???";
}
const serverElem = document.querySelector(".character-profile .description .info .technical .server") as HTMLElement | null;
if (serverElem) {
serverElem.textContent = data?.server ?? "???";
}
const datacenterElem = document.querySelector(".character-profile .description .info .technical .datacenter") as HTMLElement | null;
if (datacenterElem) {
datacenterElem.textContent = `(${data?.datacenter ?? "???"})`;
}
const jobs = data?.jobs ?? [];
if (Array.isArray(jobs)) {
jobs.forEach((job: {name: string, level: number}) => {
const name = job.name.toLowerCase().split(" / ")[0];
const jobElem = document.querySelector(`.character-profile .description .jobs .job[data-job="${name}"]`);
if (jobElem) {
const levelElem = jobElem.querySelector(".level") as HTMLElement | null;
if (levelElem) {
levelElem.textContent = job.level.toString();
}
}
});
}
}
function saveDataToLocalStorage(data: any) {
try {
localStorage.setItem('ffxivCharacterData', JSON.stringify(data));
} catch (e) {
console.error("Error saving FFXIV character data to localStorage", e);
}
}
function loadDataFromLocalStorage() {
try {
const data = localStorage.getItem('ffxivCharacterData');
if (data) {
return JSON.parse(data);
}
} catch (e) {
console.error("Error loading FFXIV character data from localStorage", e);
}
return null;
}
</script>
<style lang="scss" scoped>
.character-profile {
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: flex-start;
justify-content: center;
padding: 2rem;
gap: 2rem;
.portrait {
width: 300px;
height: 400px;
border: 2px solid var(--primary-color);
border-radius: 0.5rem;
img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: top;
border-radius: 0.5rem;
}
}
.description {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
height: 100%;
.info {
display: flex;
flex-direction: column;
.name {
font-size: 2rem;
font-weight: bold;
line-height: 1;
}
.technical {
font-size: 1rem;
color: #9d9d9d;
display: flex;
gap: 0.5rem;
}
}
.jobs {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
flex-wrap: wrap;
gap: 0.5rem;
.job {
display: flex;
justify-content: space-between;
padding: 0.5rem;
border: 2px solid var(--primary-color);
border-radius: 4px;
.name {
font-weight: 600;
margin-right: 0.5rem;
}
}
}
}
@container (max-width: 800px) {
flex-direction: column;
align-items: center;
.portrait {
width: 100%;
}
.description {
width: 100%;
.info {
align-items: center;
}
}
}
}
</style>