${deadRibbon}
-
WANTED
+
${escapeHtml(characterPosterLabel(c))}
${characterPortraitSVG(c)}
${escapeHtml(c.name)}
${c.epithet ? '"' + escapeHtml(c.epithet) + '"' : ' '}
-
${c.bounty ? formatBounty(c.bounty) : 'Bounty Unknown'}
+
${escapeHtml(characterBountyOrRank(c))}
@@ -82,4 +82,6 @@
`;
+
+ hydrateProperImages(root);
})();
diff --git a/js/fruit.js b/js/fruit.js
index ea9b321..2de632c 100644
--- a/js/fruit.js
+++ b/js/fruit.js
@@ -79,4 +79,6 @@
`;
+
+ hydrateProperImages(root);
})();
diff --git a/js/fruits.js b/js/fruits.js
index bbeba43..4dce6a3 100644
--- a/js/fruits.js
+++ b/js/fruits.js
@@ -33,6 +33,7 @@
renderCardGrid(grid, results, fruitCardHTML, 'No Devil Fruits match your search.');
resultCount.textContent = `${results.length} of ${FRUITS.length} Devil Fruits`;
+ hydrateProperImages(grid);
}
chipRow.addEventListener('click', (e) => {
diff --git a/js/main.js b/js/main.js
index 9d41a5a..6382460 100644
--- a/js/main.js
+++ b/js/main.js
@@ -1,12 +1,17 @@
-/* Home page: character grid with search + affiliation + devil-fruit filters. */
+/* Home page: character grid with search + affiliation + devil-fruit + deceased filters. */
(function () {
const grid = document.getElementById('character-grid');
const searchInput = document.getElementById('search-input');
const affiliationSelect = document.getElementById('affiliation-select');
const fruitOnlyCheckbox = document.getElementById('fruit-only-checkbox');
+ const deceasedOnlyCheckbox = document.getElementById('deceased-only-checkbox');
const resultCount = document.getElementById('result-count');
+ // Browsers may restore checkbox state across reloads; start with the full roster visible.
+ fruitOnlyCheckbox.checked = false;
+ deceasedOnlyCheckbox.checked = false;
+
document.getElementById('stat-characters').textContent = CHARACTERS.length;
document.getElementById('stat-fruits').textContent = FRUITS.length;
@@ -22,9 +27,11 @@
const term = searchInput.value.trim().toLowerCase();
const affiliation = affiliationSelect.value;
const fruitOnly = fruitOnlyCheckbox.checked;
+ const deceasedOnly = deceasedOnlyCheckbox.checked;
const results = CHARACTERS.filter((c) => {
if (fruitOnly && !c.devilFruit) return false;
+ if (deceasedOnly && !/deceased/i.test(c.status || '')) return false;
if (affiliation && c.affiliation !== affiliation) return false;
if (term) {
const haystack = `${c.name} ${c.epithet || ''} ${c.affiliation} ${c.role}`.toLowerCase();
@@ -35,11 +42,13 @@
renderCardGrid(grid, results, characterCardHTML, 'No characters match your search. Try a different name or affiliation.');
resultCount.textContent = `${results.length} of ${CHARACTERS.length} characters`;
+ hydrateProperImages(grid);
}
searchInput.addEventListener('input', applyFilters);
affiliationSelect.addEventListener('change', applyFilters);
fruitOnlyCheckbox.addEventListener('change', applyFilters);
+ deceasedOnlyCheckbox.addEventListener('change', applyFilters);
applyFilters();
})();
diff --git a/js/portraits.js b/js/portraits.js
index e4ef12c..8b2586e 100644
--- a/js/portraits.js
+++ b/js/portraits.js
@@ -74,7 +74,26 @@ function iconKeyForCharacter(c) {
return 'star';
}
-function characterPortraitSVG(c) {
+function characterImageTitle(c) {
+ return c.imageTitle || c.name;
+}
+
+function fruitImageTitle(f) {
+ if (f.imageTitle) return f.imageTitle;
+ if (f.id === 'gomu-gomu-no-mi') return 'Hito Hito no Mi, Model: Nika';
+ return f.name;
+}
+
+function properImageHTML(kind, title, label, fallback) {
+ return `
+