Files
devil-fruit-encyclopedia/js/character.js
Claude c645ee09d2 Add original generated portrait/icon artwork for every entry
Adds js/portraits.js: procedurally generated, original SVG artwork for
all 101 characters (a role-themed line-art medallion — sword for
swordsmen, crown for royalty, compass for navigators, etc., derived
from each character's role/epithet/affiliation) and all 58 Devil
Fruits (a stylized swirled-fruit icon colored by category with the
fruit's symbolic emoji at center).

Official One Piece artwork is copyrighted (Oda/Shueisha/Toei), so
rather than scraping and re-hosting character art from the web, every
image here is drawn from scratch with basic SVG primitives — no
external assets, no copyright risk, fully offline-capable, and crisp
at any size since it's vector.

Wires the new generators into every card and detail view: character
cards/profile header, Devil Fruit cards/detail header, the Devil-Fruit
callout on character pages, and user chips on Devil Fruit pages.
Removes the now-unused plain-letter initialFor() avatar helper.
2026-08-14 23:47:39 +00:00

86 lines
3.3 KiB
JavaScript

/* Character detail page: renders a full profile from ?id= against CHARACTERS/FRUITS. */
(function () {
const root = document.getElementById('character-content');
const id = qs('id');
const character = id ? findCharacter(id) : null;
if (!character) {
root.innerHTML = `
<div class="empty-state">
Character not found. <a href="index.html">Return to the character index</a>.
</div>
`;
return;
}
document.title = `${character.name} — One Piece Wiki`;
const metaDesc = document.querySelector('meta[name="description"]');
if (metaDesc) metaDesc.setAttribute('content', `${character.name}${character.epithet ? ' "' + character.epithet + '"' : ''} — profile, abilities, and Devil Fruit on the One Piece Wiki.`);
const accent = accentFor(character);
const fruit = character.devilFruit ? findFruit(character.devilFruit) : null;
const fruitRelation = fruit ? fruit.users.find((u) => u.id === character.id) : null;
const statRows = [
['Affiliation', character.affiliation],
['Role', character.role],
['Origin', character.origin],
['Age', character.age ?? 'Unknown'],
['Height', character.height || 'Unknown'],
['Birthday', character.birthday || 'Unknown'],
['Status', character.status || 'Unknown'],
['First Appearance', character.firstAppearance || 'Unknown'],
];
root.innerHTML = `
<section class="profile-header" style="--accent:${accent}">
<div class="profile-avatar">${characterPortraitSVG(character)}</div>
<div>
<p class="profile-eyebrow">${escapeHtml(character.affiliation)} · ${escapeHtml(character.role)}</p>
<h1>${escapeHtml(character.name)}</h1>
<p class="profile-epithet">${character.epithet ? '"' + escapeHtml(character.epithet) + '"' : ''}</p>
<div class="profile-badges">
<span class="badge ${statusBadgeClass(character.status)}">${escapeHtml(character.status || 'Unknown')}</span>
<span class="badge badge--bounty">${character.bounty ? formatBounty(character.bounty) : 'Bounty Unknown'}</span>
${fruit ? `<span class="badge" data-cat="${escapeHtml(fruit.category)}" style="color:var(--cat-color)">${escapeHtml(fruit.category)} User</span>` : ''}
</div>
</div>
</section>
<div class="profile-layout">
<div class="profile-main">
${fruit ? `
<a class="fruit-callout" href="${fruitLink(fruit.id)}">
<div class="fruit-callout__icon">${fruitPortraitSVG(fruit)}</div>
<div>
<p class="fruit-callout__label">${escapeHtml(fruitRelation ? fruitRelation.status : 'Devil Fruit')}</p>
<h3>${escapeHtml(fruit.name)}</h3>
<p>${escapeHtml(fruit.tagline)}</p>
</div>
</a>
` : ''}
<div class="card">
<h2>Biography</h2>
<p>${escapeHtml(character.bio)}</p>
</div>
<div class="card">
<h2>Abilities &amp; Haki</h2>
<ul class="ability-list">
${character.abilities.map((a) => `<li>${escapeHtml(a)}</li>`).join('')}
</ul>
</div>
</div>
<aside class="profile-stats card">
<h2>Profile</h2>
<dl class="stat-list">
${statRows.map(([label, value]) => `<div><dt>${escapeHtml(label)}</dt><dd>${escapeHtml(value)}</dd></div>`).join('')}
</dl>
</aside>
</div>
`;
})();