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.
This commit is contained in:
Claude
2026-08-14 23:47:39 +00:00
parent ee140dbe4d
commit c645ee09d2
9 changed files with 166 additions and 36 deletions

View File

@@ -36,11 +36,6 @@ function accentFor(entity) {
return `hsl(${hashHue(entity.name || key)}, 58%, 48%)`;
}
function initialFor(name) {
const match = (name || '?').match(/[A-Za-z]/);
return match ? match[0].toUpperCase() : '?';
}
function formatBounty(amount) {
if (amount === null || amount === undefined) return 'Unknown';
return '฿' + amount.toLocaleString('en-US');
@@ -89,7 +84,7 @@ function characterCardHTML(c) {
<div class="char-card__poster">
${deadRibbon}
<span class="char-card__wanted">WANTED</span>
<div class="char-card__avatar">${initialFor(c.name)}</div>
<div class="char-card__avatar">${characterPortraitSVG(c)}</div>
</div>
<div class="char-card__body">
<h3>${escapeHtml(c.name)}</h3>
@@ -112,7 +107,7 @@ function fruitCardHTML(f) {
<article class="fruit-card" data-cat="${escapeHtml(f.category)}">
<a href="${fruitLink(f.id)}" aria-label="View ${escapeHtml(f.name)}">
<div class="fruit-card__inner">
<div class="fruit-card__icon">${f.icon}</div>
<div class="fruit-card__icon">${fruitPortraitSVG(f)}</div>
<h3>${escapeHtml(f.name)}</h3>
<span class="fruit-card__type">${escapeHtml(f.category)}${f.subtype ? ' · ' + escapeHtml(f.subtype.split(' —')[0].split(' (')[0]) : ''}</span>
<p class="fruit-card__tagline">${escapeHtml(f.tagline)}</p>