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

@@ -367,18 +367,18 @@ p { margin: 0 0 1em; color: var(--text-muted); }
margin-bottom: 0.5rem;
}
.portrait-svg {
display: block;
width: 100%;
height: 100%;
}
.char-card__avatar {
width: 84px;
height: 84px;
margin: 0 auto 0.5rem;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 2.1rem;
color: #fff;
background: linear-gradient(145deg, var(--accent, var(--gold-deep)), color-mix(in srgb, var(--accent, var(--gold-deep)) 60%, black));
overflow: hidden;
border: 3px solid var(--ink);
box-shadow: inset 0 0 0 2px rgba(255,255,255,0.25);
}
@@ -464,8 +464,12 @@ p { margin: 0 0 1em; color: var(--text-muted); }
.fruit-card__inner { padding: 1.1rem 1.15rem; }
.fruit-card__icon {
font-size: 2rem;
margin-bottom: 0.6rem;
width: 56px;
height: 56px;
border-radius: 50%;
overflow: hidden;
margin-bottom: 0.7rem;
box-shadow: 0 0 0 1px var(--surface-border);
}
.fruit-card__inner h3 {
@@ -536,13 +540,7 @@ p { margin: 0 0 1em; color: var(--text-muted); }
height: 110px;
flex-shrink: 0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 2.6rem;
color: #fff;
background: linear-gradient(145deg, var(--accent, var(--gold)), color-mix(in srgb, var(--accent, var(--gold)) 55%, black));
overflow: hidden;
border: 4px solid var(--bg);
box-shadow: 0 0 0 3px var(--accent, var(--gold)), var(--shadow-sm);
}
@@ -625,7 +623,14 @@ p { margin: 0 0 1em; color: var(--text-muted); }
.fruit-callout:hover { transform: translateX(2px); border-color: var(--gold); }
.fruit-callout__icon { font-size: 2.4rem; flex-shrink: 0; }
.fruit-callout__icon {
width: 68px;
height: 68px;
flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 0 1px var(--surface-border-strong);
}
.fruit-callout__label {
font-size: 0.72rem;
@@ -708,13 +713,7 @@ p { margin: 0 0 1em; color: var(--text-muted); }
width: 38px;
height: 38px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--font-display);
font-size: 1.1rem;
color: #fff;
background: linear-gradient(145deg, var(--accent, var(--gold)), color-mix(in srgb, var(--accent, var(--gold)) 55%, black));
overflow: hidden;
flex-shrink: 0;
}
@@ -735,8 +734,12 @@ p { margin: 0 0 1em; color: var(--text-muted); }
}
.fruit-detail-header__icon {
font-size: 4rem;
width: 100px;
height: 100px;
flex-shrink: 0;
border-radius: 50%;
overflow: hidden;
box-shadow: 0 0 0 3px var(--cat-color, var(--gold)), var(--shadow-sm);
}
.fruit-detail-header h1 { margin-bottom: 0.2rem; }