feat: multi-parental tree for religions

This commit is contained in:
Azgaar 2022-06-05 18:26:28 +03:00
parent 23ff50bd76
commit ff1d3566c4
4 changed files with 346 additions and 220 deletions

View file

@ -618,17 +618,15 @@ export function resolveVersionConflicts(version) {
} }
if (version < 1.86) { if (version < 1.86) {
// v1.86.0 added support of multi-origin culture and religion hierarchy trees // v1.86.0 added multi-origin culture and religion hierarchy trees
for (const culture of pack.cultures) { for (const culture of pack.cultures) {
const origin = culture.origin; culture.origins = [culture.origin];
delete culture.origin; delete culture.origin;
culture.origins = [origin];
} }
for (const religion of pack.religions) { for (const religion of pack.religions) {
const origin = religion.origin; religion.origins = [religion.origin];
delete religion.origin; delete religion.origin;
religion.origins = [origin];
} }
} }
} }

View file

@ -295,15 +295,14 @@ function getShapeOptions(selectShape, selected) {
} }
function cultureHighlightOn(event) { function cultureHighlightOn(event) {
const culture = Number(event.id || event.target.dataset.id); const cultureId = Number(event.id || event.target.dataset.id);
const $info = byId("cultureInfo"); const $info = byId("cultureInfo");
if ($info) { if ($info) {
d3.select("#hierarchy").select(`g[data-id='${culture}']`).classed("selected", 1); d3.select("#hierarchy").select(`g[data-id='${cultureId}']`).classed("selected", 1);
const c = pack.cultures[culture]; const {name, type, rural, urban} = pack.cultures[cultureId];
const rural = c.rural * populationRate; const population = rural * populationRate + urban * populationRate * urbanization;
const urban = c.urban * populationRate * urbanization; const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
const population = rural + urban > 0 ? si(rn(rural + urban)) + " people" : "Extinct"; $info.innerHTML = `${name} culture. ${type}. ${populationText}`;
$info.innerHTML = `${c.name} culture. ${c.type}. ${population}`;
tip("Drag to other node to add parent, click to edit"); tip("Drag to other node to add parent, click to edit");
} }
@ -312,13 +311,13 @@ function cultureHighlightOn(event) {
const animate = d3.transition().duration(2000).ease(d3.easeSinIn); const animate = d3.transition().duration(2000).ease(d3.easeSinIn);
cults cults
.select("#culture" + culture) .select("#culture" + cultureId)
.raise() .raise()
.transition(animate) .transition(animate)
.attr("stroke-width", 2.5) .attr("stroke-width", 2.5)
.attr("stroke", "#d0240f"); .attr("stroke", "#d0240f");
debug debug
.select("#cultureCenter" + culture) .select("#cultureCenter" + cultureId)
.raise() .raise()
.transition(animate) .transition(animate)
.attr("r", 8) .attr("r", 8)
@ -326,23 +325,23 @@ function cultureHighlightOn(event) {
} }
function cultureHighlightOff(event) { function cultureHighlightOff(event) {
const culture = Number(event.id || event.target.dataset.id); const cultureId = Number(event.id || event.target.dataset.id);
const $info = byId("cultureInfo"); const $info = byId("cultureInfo");
if ($info) { if ($info) {
d3.select("#hierarchy").select(`g[data-id='${culture}']`).classed("selected", 0); d3.select("#hierarchy").select(`g[data-id='${cultureId}']`).classed("selected", 0);
$info.innerHTML = "&#8205;"; $info.innerHTML = "&#8205;";
tip(""); tip("");
} }
if (!layerIsOn("toggleCultures")) return; if (!layerIsOn("toggleCultures")) return;
cults cults
.select("#culture" + culture) .select("#culture" + cultureId)
.transition() .transition()
.attr("stroke-width", null) .attr("stroke-width", null)
.attr("stroke", null); .attr("stroke", null);
debug debug
.select("#cultureCenter" + culture) .select("#cultureCenter" + cultureId)
.transition() .transition()
.attr("r", 6) .attr("r", 6)
.attr("stroke", null); .attr("stroke", null);
@ -769,7 +768,6 @@ function showHierarchy() {
$("#alert").dialog({ $("#alert").dialog({
title: "Cultures tree", title: "Cultures tree",
width: fitContent(), width: fitContent(),
minWidth: "20vw",
resizable: false, resizable: false,
position: {my: "left center", at: "left+10 center", of: "svg"}, position: {my: "left center", at: "left+10 center", of: "svg"},
buttons: null, buttons: null,
@ -822,10 +820,10 @@ function showHierarchy() {
if (!selected.size()) return; if (!selected.size()) return;
const cultureId = d.data.i; const cultureId = d.data.i;
const newOrigin = Number(selected.datum().data.i); const newOrigin = selected.datum().data.i;
if (cultureId === newOrigin) return; // dragged to itself if (cultureId === newOrigin) return; // dragged to itself
if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node
if (newOrigin && d.descendants().some(node => node.id === newOrigin)) return; // cannot be a child of its own child if (d.descendants().some(node => node.data.i === newOrigin)) return; // cannot be a child of its own child
const culture = pack.cultures[cultureId]; const culture = pack.cultures[cultureId];
if (culture.origins[0] === 0) culture.origins = []; if (culture.origins[0] === 0) culture.origins = [];

View file

@ -277,7 +277,24 @@ window.Religions = (function () {
"Word", "Word",
"World" "World"
], ],
color: ["Amber", "Black", "Blue", "Bright", "Brown", "Dark", "Golden", "Green", "Grey", "Light", "Orange", "Pink", "Purple", "Red", "White", "Yellow"] color: [
"Amber",
"Black",
"Blue",
"Bright",
"Brown",
"Dark",
"Golden",
"Green",
"Grey",
"Light",
"Orange",
"Pink",
"Purple",
"Red",
"White",
"Yellow"
]
}; };
const forms = { const forms = {
@ -311,7 +328,18 @@ window.Religions = (function () {
Cult: {Cult: 4, Sect: 4, Arcanum: 1, Coterie: 1, Order: 1, Worship: 1}, Cult: {Cult: 4, Sect: 4, Arcanum: 1, Coterie: 1, Order: 1, Worship: 1},
"Dark Cult": {Cult: 2, Sect: 2, Blasphemy: 1, Circle: 1, Coven: 1, Idols: 1, Occultism: 1}, "Dark Cult": {Cult: 2, Sect: 2, Blasphemy: 1, Circle: 1, Coven: 1, Idols: 1, Occultism: 1},
Heresy: {Heresy: 3, Sect: 2, Apostates: 1, Brotherhood: 1, Circle: 1, Dissent: 1, Dissenters: 1, Iconoclasm: 1, Schism: 1, Society: 1} Heresy: {
Heresy: 3,
Sect: 2,
Apostates: 1,
Brotherhood: 1,
Circle: 1,
Dissent: 1,
Dissenters: 1,
Iconoclasm: 1,
Schism: 1,
Society: 1
}
}; };
const generate = function () { const generate = function () {
@ -324,25 +352,27 @@ window.Religions = (function () {
// add folk religions // add folk religions
pack.cultures.forEach(c => { pack.cultures.forEach(c => {
if (!c.i) { if (!c.i) return religions.push({i: 0, name: "No religion"});
religions.push({i: 0, name: "No religion"});
return;
}
if (c.removed) { if (c.removed) {
religions.push({i: c.i, name: "Extinct religion for " + c.name, color: getMixedColor(c.color, 0.1, 0), removed: true}); religions.push({
i: c.i,
name: "Extinct religion for " + c.name,
color: getMixedColor(c.color, 0.1, 0),
removed: true
});
return; return;
} }
const form = rw(forms.Folk); const form = rw(forms.Folk);
const name = c.name + " " + rw(types[form]); const name = c.name + " " + rw(types[form]);
const deity = form === "Animism" ? null : getDeityName(c.i); const deity = form === "Animism" ? null : getDeityName(c.i);
const color = getMixedColor(c.color, 0.1, 0); // `url(#hatch${rand(8,13)})`; const color = getMixedColor(c.color, 0.1, 0); // `url(#hatch${rand(8,13)})`;
religions.push({i: c.i, name, color, culture: c.i, type: "Folk", form, deity, center: c.center, origin: 0}); religions.push({i: c.i, name, color, culture: c.i, type: "Folk", form, deity, center: c.center, origins: [0]});
}); });
if (religionsInput.value == 0 || pack.cultures.length < 2) { if (religionsInput.value == 0 || pack.cultures.length < 2)
religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name))); return religions.filter(r => r.i).forEach(r => (r.code = abbreviate(r.name)));
return;
}
const burgs = pack.burgs.filter(b => b.i && !b.removed); const burgs = pack.burgs.filter(b => b.i && !b.removed);
const sorted = const sorted =
@ -354,6 +384,12 @@ window.Religions = (function () {
const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value); const cultsCount = Math.floor((rand(10, 40) / 100) * religionsInput.value);
const count = +religionsInput.value - cultsCount + religions.length; const count = +religionsInput.value - cultsCount + religions.length;
function getReligionsInRadius({x, y, r, max}) {
const cellsInRadius = findAll(x, y, r);
const religions = unique(cellsInRadius.map(i => cells.religion[i]).filter(r => r));
return religions.length ? religions.slice(0, max) : [0];
}
// generate organized religions // generate organized religions
for (let i = 0; religions.length < count && i < 1000; i++) { for (let i = 0; religions.length < count && i < 1000; i++) {
let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center let center = sorted[biased(0, sorted.length - 1, 5)]; // religion center
@ -369,21 +405,35 @@ window.Religions = (function () {
if (expansion === "state" && Math.random() > 0.5) center = states[state].center; if (expansion === "state" && Math.random() > 0.5) center = states[state].center;
if (expansion === "culture" && Math.random() > 0.5) center = cultures[culture].center; if (expansion === "culture" && Math.random() > 0.5) center = cultures[culture].center;
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]); if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
const x = cells.p[center][0], center = cells.c[center].find(c => cells.burg[c]);
y = cells.p[center][1]; const [x, y] = cells.p[center];
const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform const s = spacing * gauss(1, 0.3, 0.2, 2, 2); // randomize to make the placement not uniform
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
// add "Old" to name of the folk religion on this culture // add "Old" to name of the folk religion on this culture
const folk = religions.find(r => r.culture === culture && r.type === "Folk"); const isFolkBased = expansion === "culture" || P(0.5);
const folk = isFolkBased && religions.find(r => r.culture === culture && r.type === "Folk");
if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name; if (folk && expansion === "culture" && folk.name.slice(0, 3) !== "Old") folk.name = "Old " + folk.name;
const origin = folk ? folk.i : 0;
const origins = folk ? [folk.i] : getReligionsInRadius({x, y, r: 30, max: 2});
const expansionism = rand(3, 8); const expansionism = rand(3, 8);
const color = getMixedColor(religions[origin].color, 0.3, 0); // `url(#hatch${rand(0,5)})`; const baseColor = religions[culture]?.color || states[state]?.color || getRandomColor();
religions.push({i: religions.length, name, color, culture, type: "Organized", form, deity, expansion, expansionism, center, origin}); const color = getMixedColor(baseColor, 0.3, 0);
religions.push({
i: religions.length,
name,
color,
culture,
type: "Organized",
form,
deity,
expansion,
expansionism,
center,
origins
});
religionsTree.add([x, y]); religionsTree.add([x, y]);
} }
@ -391,23 +441,34 @@ window.Religions = (function () {
for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) { for (let i = 0; religions.length < count + cultsCount && i < 1000; i++) {
const form = rw(forms.Cult); const form = rw(forms.Cult);
let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center let center = sorted[biased(0, sorted.length - 1, 1)]; // religion center
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]); if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
const x = cells.p[center][0], center = cells.c[center].find(c => cells.burg[c]);
y = cells.p[center][1]; const [x, y] = cells.p[center];
const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform const s = spacing * gauss(2, 0.3, 1, 3, 2); // randomize to make the placement not uniform
if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion if (religionsTree.find(x, y, s) !== undefined) continue; // to close to existing religion
const culture = cells.culture[center]; const culture = cells.culture[center];
const folk = religions.find(r => r.culture === culture && r.type === "Folk"); const origins = getReligionsInRadius({x, y, r: 75, max: rand(0, 4)});
const origin = folk ? folk.i : 0;
const deity = getDeityName(culture); const deity = getDeityName(culture);
const name = getCultName(form, center); const name = getCultName(form, center);
const expansionism = gauss(1.1, 0.5, 0, 5); const expansionism = gauss(1.1, 0.5, 0, 5);
const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)"; const color = getMixedColor(cultures[culture].color, 0.5, 0); // "url(#hatch7)";
religions.push({i: religions.length, name, color, culture, type: "Cult", form, deity, expansion: "global", expansionism, center, origin}); religions.push({
i: religions.length,
name,
color,
culture,
type: "Cult",
form,
deity,
expansion: "global",
expansionism,
center,
origins
});
religionsTree.add([x, y]); religionsTree.add([x, y]);
//debug.append("circle").attr("cx", x).attr("cy", y).attr("r", 2).attr("fill", "red");
} }
expandReligions(); expandReligions();
@ -419,11 +480,13 @@ window.Religions = (function () {
if (r.expansionism < 3) return; if (r.expansionism < 3) return;
const count = gauss(0, 1, 0, 3); const count = gauss(0, 1, 0, 3);
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
let center = ra(cells.i.filter(i => cells.religion[i] === r.i && cells.c[i].some(c => cells.religion[c] !== r.i))); let center = ra(
cells.i.filter(i => cells.religion[i] === r.i && cells.c[i].some(c => cells.religion[c] !== r.i))
);
if (!center) continue; if (!center) continue;
if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c])) center = cells.c[center].find(c => cells.burg[c]); if (!cells.burg[center] && cells.c[center].some(c => cells.burg[c]))
const x = cells.p[center][0], center = cells.c[center].find(c => cells.burg[c]);
y = cells.p[center][1]; const [x, y] = cells.p[center];
if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other if (religionsTree.find(x, y, spacing / 10) !== undefined) continue; // to close to other
const culture = cells.culture[center]; const culture = cells.culture[center];
@ -441,7 +504,7 @@ window.Religions = (function () {
expansion: "global", expansion: "global",
expansionism, expansionism,
center, center,
origin: r.i origins: [r.i]
}); });
religionsTree.add([x, y]); religionsTree.add([x, y]);
} }
@ -461,7 +524,8 @@ window.Religions = (function () {
const culture = cells.culture[center]; const culture = cells.culture[center];
const color = getMixedColor(religions[r].color, 0.3, 0); const color = getMixedColor(religions[r].color, 0.3, 0);
const type = religions[r].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2}); const type =
religions[r].type === "Organized" ? rw({Organized: 4, Cult: 1, Heresy: 2}) : rw({Organized: 5, Cult: 2});
const form = rw(forms[type]); const form = rw(forms[type]);
const deity = type === "Heresy" ? religions[r].deity : form === "Non-theism" ? null : getDeityName(culture); const deity = type === "Heresy" ? religions[r].deity : form === "Non-theism" ? null : getDeityName(culture);
@ -491,7 +555,7 @@ window.Religions = (function () {
area: 0, area: 0,
rural: 0, rural: 0,
urban: 0, urban: 0,
origin: r, origins: [r],
code code
}); });
cells.religion[center] = i; cells.religion[center] = i;
@ -534,7 +598,9 @@ window.Religions = (function () {
const populationCost = Math.max(rn(popCost - cells.pop[e]), 0); const populationCost = Math.max(rn(popCost - cells.pop[e]), 0);
const heightCost = Math.max(cells.h[e], 20) - 20; const heightCost = Math.max(cells.h[e], 20) - 20;
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0; const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
const totalCost = p + (cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism; const totalCost =
p +
(cultureCost + stateCost + biomeCost + populationCost + heightCost + waterCost) / religions[r].expansionism;
if (totalCost > neutral) return; if (totalCost > neutral) return;
if (!cost[e] || totalCost < cost[e]) { if (!cost[e] || totalCost < cost[e]) {
@ -576,7 +642,8 @@ window.Religions = (function () {
const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]]; const biomeCost = cells.road[e] ? 0 : biomesData.cost[cells.biome[e]];
const heightCost = Math.max(cells.h[e], 20) - 20; const heightCost = Math.max(cells.h[e], 20) - 20;
const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0; const waterCost = cells.h[e] < 20 ? (cells.road[e] ? 50 : 1000) : 0;
const totalCost = p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1); const totalCost =
p + (religionCost + biomeCost + heightCost + waterCost) / Math.max(religions[r].expansionism, 0.1);
if (totalCost > neutral) return; if (totalCost > neutral) return;
@ -641,35 +708,34 @@ window.Religions = (function () {
if (a === "Being + of + Genitive") return ra(base.being) + " of " + ra(base.genitive); if (a === "Being + of + Genitive") return ra(base.being) + " of " + ra(base.genitive);
if (a === "Being + of the + Genitive") return ra(base.being) + " of the " + ra(base.theGenitive); if (a === "Being + of the + Genitive") return ra(base.being) + " of the " + ra(base.theGenitive);
if (a === "Animal + of + Genitive") return ra(base.animal) + " of " + ra(base.genitive); if (a === "Animal + of + Genitive") return ra(base.animal) + " of " + ra(base.genitive);
if (a === "Adjective + Being + of + Genitive") return ra(base.adjective) + " " + ra(base.being) + " of " + ra(base.genitive); if (a === "Adjective + Being + of + Genitive")
if (a === "Adjective + Animal + of + Genitive") return ra(base.adjective) + " " + ra(base.animal) + " of " + ra(base.genitive); return ra(base.adjective) + " " + ra(base.being) + " of " + ra(base.genitive);
if (a === "Adjective + Animal + of + Genitive")
return ra(base.adjective) + " " + ra(base.animal) + " of " + ra(base.genitive);
} }
function getReligionName(form, deity, center) { function getReligionName(form, deity, center) {
const cells = pack.cells; const {cells, cultures, burgs, states} = pack;
const random = function () {
return Names.getCulture(cells.culture[center], null, null, "", 0); const random = () => Names.getCulture(cells.culture[center], null, null, "", 0);
}; const type = () => rw(types[form]);
const type = function () { const supreme = () => deity.split(/[ ,]+/)[0];
return rw(types[form]); const culture = () => cultures[cells.culture[center]].name;
}; const place = adj => {
const supreme = function () { const burgId = cells.burg[center];
return deity.split(/[ ,]+/)[0]; const stateId = cells.state[center];
};
const place = function (adj) { const base = burgId ? burgs[burgId].name : states[stateId].name;
const base = cells.burg[center] ? pack.burgs[cells.burg[center]].name : pack.states[cells.state[center]].name;
let name = trimVowels(base.split(/[ ,]+/)[0]); let name = trimVowels(base.split(/[ ,]+/)[0]);
return adj ? getAdjective(name) : name; return adj ? getAdjective(name) : name;
}; };
const culture = function () {
return pack.cultures[cells.culture[center]].name;
};
const m = rw(methods); const m = rw(methods);
if (m === "Random + type") return [random() + " " + type(), "global"]; if (m === "Random + type") return [random() + " " + type(), "global"];
if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"]; if (m === "Random + ism") return [trimVowels(random()) + "ism", "global"];
if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"]; if (m === "Supreme + ism" && deity) return [trimVowels(supreme()) + "ism", "global"];
if (m === "Faith of + Supreme" && deity) return [ra(["Faith", "Way", "Path", "Word", "Witnesses"]) + " of " + supreme(), "global"]; if (m === "Faith of + Supreme" && deity)
return [ra(["Faith", "Way", "Path", "Word", "Witnesses"]) + " of " + supreme(), "global"];
if (m === "Place + ism") return [place() + "ism", "state"]; if (m === "Place + ism") return [place() + "ism", "state"];
if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"]; if (m === "Culture + ism") return [trimVowels(culture()) + "ism", "culture"];
if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"]; if (m === "Place + ian + type") return [place("adj") + " " + type(), "state"];

View file

@ -8,7 +8,7 @@ function editReligions() {
if (layerIsOn("toggleBiomes")) toggleBiomes(); if (layerIsOn("toggleBiomes")) toggleBiomes();
if (layerIsOn("toggleProvinces")) toggleProvinces(); if (layerIsOn("toggleProvinces")) toggleProvinces();
const body = document.getElementById("religionsBody"); const body = byId("religionsBody");
const animate = d3.transition().duration(1500).ease(d3.easeSinIn); const animate = d3.transition().duration(1500).ease(d3.easeSinIn);
refreshReligionsEditor(); refreshReligionsEditor();
drawReligionCenters(); drawReligionCenters();
@ -25,17 +25,17 @@ function editReligions() {
}); });
// add listeners // add listeners
document.getElementById("religionsEditorRefresh").addEventListener("click", refreshReligionsEditor); byId("religionsEditorRefresh").on("click", refreshReligionsEditor);
document.getElementById("religionsEditStyle").addEventListener("click", () => editStyle("relig")); byId("religionsEditStyle").on("click", () => editStyle("relig"));
document.getElementById("religionsLegend").addEventListener("click", toggleLegend); byId("religionsLegend").on("click", toggleLegend);
document.getElementById("religionsPercentage").addEventListener("click", togglePercentageMode); byId("religionsPercentage").on("click", togglePercentageMode);
document.getElementById("religionsHeirarchy").addEventListener("click", showHierarchy); byId("religionsHeirarchy").on("click", showHierarchy);
document.getElementById("religionsExtinct").addEventListener("click", toggleExtinct); byId("religionsExtinct").on("click", toggleExtinct);
document.getElementById("religionsManually").addEventListener("click", enterReligionsManualAssignent); byId("religionsManually").on("click", enterReligionsManualAssignent);
document.getElementById("religionsManuallyApply").addEventListener("click", applyReligionsManualAssignent); byId("religionsManuallyApply").on("click", applyReligionsManualAssignent);
document.getElementById("religionsManuallyCancel").addEventListener("click", () => exitReligionsManualAssignment()); byId("religionsManuallyCancel").on("click", () => exitReligionsManualAssignment());
document.getElementById("religionsAdd").addEventListener("click", enterAddReligionMode); byId("religionsAdd").on("click", enterAddReligionMode);
document.getElementById("religionsExport").addEventListener("click", downloadReligionsData); byId("religionsExport").on("click", downloadReligionsData);
function refreshReligionsEditor() { function refreshReligionsEditor() {
religionsCollectStatistics(); religionsCollectStatistics();
@ -72,7 +72,9 @@ function editReligions() {
const urban = r.urban * populationRate * urbanization; const urban = r.urban * populationRate * urbanization;
const population = rn(rural + urban); const population = rn(rural + urban);
if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions if (r.i && !r.cells && body.dataset.extinct !== "show") continue; // hide extinct religions
const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(urban)}. Click to change`; const populationTip = `Believers: ${si(population)}; Rural areas: ${si(rural)}; Urban areas: ${si(
urban
)}. Click to change`;
totalArea += area; totalArea += area;
totalPopulation += population; totalPopulation += population;
@ -90,13 +92,19 @@ function editReligions() {
data-expansionism=${r.expansionism} data-expansionism=${r.expansionism}
> >
<fill-box fill="${r.color}"></fill-box> <fill-box fill="${r.color}"></fill-box>
<input data-tip="Religion name. Click and type to change" class="religionName" value="${r.name}" autocorrect="off" spellcheck="false" /> <input data-tip="Religion name. Click and type to change" class="religionName" value="${
r.name
}" autocorrect="off" spellcheck="false" />
<select data-tip="Religion type" class="religionType"> <select data-tip="Religion type" class="religionType">
${getTypeOptions(r.type)} ${getTypeOptions(r.type)}
</select> </select>
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false" /> <input data-tip="Religion form" class="religionForm hide" value="${
r.form
}" autocorrect="off" spellcheck="false" />
<span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw hide"></span> <span data-tip="Click to re-generate supreme deity" class="icon-arrows-cw hide"></span>
<input data-tip="Religion supreme deity" class="religionDeity hide" value="${r.deity ? r.deity : ""}" autocorrect="off" spellcheck="false" /> <input data-tip="Religion supreme deity" class="religionDeity hide" value="${
r.deity ? r.deity : ""
}" autocorrect="off" spellcheck="false" />
<span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span> <span data-tip="Religion area" style="padding-right: 4px" class="icon-map-o hide"></span>
<div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div> <div data-tip="Religion area" class="biomeArea hide">${si(area) + unit}</div>
<span data-tip="${populationTip}" class="icon-male hide"></span> <span data-tip="${populationTip}" class="icon-male hide"></span>
@ -118,7 +126,9 @@ function editReligions() {
data-expansionism="" data-expansionism=""
> >
<svg width="11" height="11" class="placeholder"></svg> <svg width="11" height="11" class="placeholder"></svg>
<input data-tip="Religion name. Click and type to change" class="religionName italic" value="${r.name}" autocorrect="off" spellcheck="false" /> <input data-tip="Religion name. Click and type to change" class="religionName italic" value="${
r.name
}" autocorrect="off" spellcheck="false" />
<select data-tip="Religion type" class="religionType placeholder"> <select data-tip="Religion type" class="religionType placeholder">
${getTypeOptions(r.type)} ${getTypeOptions(r.type)}
</select> </select>
@ -146,17 +156,17 @@ function editReligions() {
religionsFooterPopulation.dataset.population = totalPopulation; religionsFooterPopulation.dataset.population = totalPopulation;
// add listeners // add listeners
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev))); body.querySelectorAll("div.religions").forEach(el => el.on("mouseenter", religionHighlightOn));
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev))); body.querySelectorAll("div.religions").forEach(el => el.on("mouseleave", religionHighlightOff));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick)); body.querySelectorAll("div.states").forEach(el => el.on("click", selectReligionOnLineClick));
body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", religionChangeColor)); body.querySelectorAll("fill-box").forEach(el => el.on("click", religionChangeColor));
body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName)); body.querySelectorAll("div > input.religionName").forEach(el => el.on("input", religionChangeName));
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType)); body.querySelectorAll("div > select.religionType").forEach(el => el.on("change", religionChangeType));
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); body.querySelectorAll("div > input.religionForm").forEach(el => el.on("input", religionChangeForm));
body.querySelectorAll("div > input.religionDeity").forEach(el => el.addEventListener("input", religionChangeDeity)); body.querySelectorAll("div > input.religionDeity").forEach(el => el.on("input", religionChangeDeity));
body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.addEventListener("click", regenerateDeity)); body.querySelectorAll("div > span.icon-arrows-cw").forEach(el => el.on("click", regenerateDeity));
body.querySelectorAll("div > div.culturePopulation").forEach(el => el.addEventListener("click", changePopulation)); body.querySelectorAll("div > div.culturePopulation").forEach(el => el.on("click", changePopulation));
body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.addEventListener("click", religionRemove)); body.querySelectorAll("div > span.icon-trash-empty").forEach(el => el.on("click", religionRemove));
if (body.dataset.type === "percentage") { if (body.dataset.type === "percentage") {
body.dataset.type = "absolute"; body.dataset.type = "absolute";
@ -174,35 +184,39 @@ function editReligions() {
} }
function religionHighlightOn(event) { function religionHighlightOn(event) {
const religion = +event.target.dataset.id; const religionId = Number(event.id || event.target.dataset.id);
const info = document.getElementById("religionInfo"); const $info = byId("religionInfo");
if (info) { if ($info) {
d3.select("#hierarchy") d3.select("#hierarchy").select(`g[data-id='${religionId}']`).classed("selected", 1);
.select("g[data-id='" + religion + "'] > path") const {name, type, form, rural, urban} = pack.religions[religionId];
.classed("selected", 1);
const r = pack.religions[religion]; const getTypeText = () => {
const type = r.name.includes(r.type) ? "" : r.type === "Folk" || r.type === "Organized" ? ". " + r.type + " religion" : ". " + r.type; if (name.includes(type)) return "";
const form = r.form === r.type || r.name.includes(r.form) ? "" : ". " + r.form; if (form.includes(type)) return "";
const rural = r.rural * populationRate; if (type === "Folk" || type === "Organized") return `. ${type} religion`;
const urban = r.urban * populationRate * urbanization; return `. ${type}`;
const population = rural + urban > 0 ? ". " + si(rn(rural + urban)) + " believers" : ". Extinct"; };
info.innerHTML = /* html */ `${r.name}${type}${form}${population}`; const formText = form === type ? "" : ". " + form;
tip("Drag to change parent, drag to itself to move to the top level. Hold CTRL and click to change abbreviation"); const population = rural * populationRate + urban * populationRate * urbanization;
const populationText = population > 0 ? si(rn(population)) + " people" : "Extinct";
$info.innerHTML = `${name}${getTypeText()}${formText}. ${populationText}`;
tip("Drag to other node to add parent, click to edit");
} }
const el = body.querySelector(`div[data-id='${religion}']`); const el = body.querySelector(`div[data-id='${religionId}']`);
if (el) el.classList.add("active"); if (el) el.classList.add("active");
if (!layerIsOn("toggleReligions")) return; if (!layerIsOn("toggleReligions")) return;
if (customization) return; if (customization) return;
relig relig
.select("#religion" + religion) .select("#religion" + religionId)
.raise() .raise()
.transition(animate) .transition(animate)
.attr("stroke-width", 2.5) .attr("stroke-width", 2.5)
.attr("stroke", "#c13119"); .attr("stroke", "#c13119");
debug debug
.select("#religionsCenter" + religion) .select("#religionsCenter" + religionId)
.raise() .raise()
.transition(animate) .transition(animate)
.attr("r", 8) .attr("r", 8)
@ -211,26 +225,24 @@ function editReligions() {
} }
function religionHighlightOff(event) { function religionHighlightOff(event) {
const religion = +event.target.dataset.id; const religionId = Number(event.id || event.target.dataset.id);
const info = document.getElementById("religionInfo"); const $info = byId("religionInfo");
if (info) { if ($info) {
d3.select("#hierarchy") d3.select("#hierarchy").select(`g[data-id='${religionId}']`).classed("selected", 0);
.select("g[data-id='" + religion + "'] > path") $info.innerHTML = "&#8205;";
.classed("selected", 0);
info.innerHTML = "&#8205;";
tip(""); tip("");
} }
const el = body.querySelector(`div[data-id='${religion}']`); const el = body.querySelector(`div[data-id='${religionId}']`);
if (el) el.classList.remove("active"); if (el) el.classList.remove("active");
relig relig
.select("#religion" + religion) .select("#religion" + religionId)
.transition() .transition()
.attr("stroke-width", null) .attr("stroke-width", null)
.attr("stroke", null); .attr("stroke", null);
debug debug
.select("#religionsCenter" + religion) .select("#religionsCenter" + religionId)
.transition() .transition()
.attr("r", 4) .attr("r", 4)
.attr("stroke-width", 1.2) .attr("stroke-width", 1.2)
@ -309,8 +321,12 @@ function editReligions() {
> >
</p> </p>
Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban: Rural: <input type="number" min="0" step="1" id="ruralPop" value=${rural} style="width:6em" /> Urban:
<input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${burgs.length ? "" : "disabled"} /> <input type="number" min="0" step="1" id="urbanPop" value=${urban} style="width:6em" ${
<p>Total believers: ${l(total)} <span id="totalPop">${l(total)}</span> (<span id="totalPopPerc">100</span>%)</p>`; burgs.length ? "" : "disabled"
} />
<p>Total believers: ${l(total)} <span id="totalPop">${l(
total
)}</span> (<span id="totalPopPerc">100</span>%)</p>`;
const update = function () { const update = function () {
const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber; const totalNew = ruralPop.valueAsNumber + urbanPop.valueAsNumber;
@ -367,7 +383,7 @@ function editReligions() {
function religionRemove() { function religionRemove() {
if (customization) return; if (customization) return;
const religion = +this.parentNode.dataset.id; const religionId = +this.parentNode.dataset.id;
alertMessage.innerHTML = "Are you sure you want to remove the religion? <br>This action cannot be reverted"; alertMessage.innerHTML = "Are you sure you want to remove the religion? <br>This action cannot be reverted";
$("#alert").dialog({ $("#alert").dialog({
@ -375,17 +391,20 @@ function editReligions() {
title: "Remove religion", title: "Remove religion",
buttons: { buttons: {
Remove: function () { Remove: function () {
relig.select("#religion" + religion).remove(); relig.select("#religion" + religionId).remove();
relig.select("#religion-gap" + religion).remove(); relig.select("#religion-gap" + religionId).remove();
debug.select("#religionsCenter" + religion).remove(); debug.select("#religionsCenter" + religionId).remove();
pack.cells.religion.forEach((r, i) => { pack.cells.religion.forEach((r, i) => {
if (r === religion) pack.cells.religion[i] = 0; if (r === religionId) pack.cells.religion[i] = 0;
}); });
pack.religions[religion].removed = true; pack.religions[religionId].removed = true;
const origin = pack.religions[religion].origin;
pack.religions.forEach(r => { pack.religions
if (r.origin === religion) r.origin = origin; .filter(r => r.i && !r.removed)
.forEach(r => {
r.origins = r.origins.filter(origin => origin !== religionId);
if (!r.origins.length) r.origins = [0];
}); });
refreshReligionsEditor(); refreshReligionsEditor();
@ -400,7 +419,12 @@ function editReligions() {
function drawReligionCenters() { function drawReligionCenters() {
debug.select("#religionCenters").remove(); debug.select("#religionCenters").remove();
const religionCenters = debug.append("g").attr("id", "religionCenters").attr("stroke-width", 1.2).attr("stroke", "#444444").style("cursor", "move"); const religionCenters = debug
.append("g")
.attr("id", "religionCenters")
.attr("stroke-width", 1.2)
.attr("stroke", "#444444")
.style("cursor", "move");
const data = pack.religions.filter(r => r.i && r.center && r.cells && !r.removed); const data = pack.religions.filter(r => r.i && r.center && r.cells && !r.removed);
religionCenters religionCenters
@ -466,67 +490,90 @@ function editReligions() {
function showHierarchy() { function showHierarchy() {
// build hierarchy tree // build hierarchy tree
pack.religions[0].origin = null; pack.religions[0].origins = [null];
const religions = pack.religions.filter(r => !r.removed); const validReligions = pack.religions.filter(r => !r.removed);
if (religions.length < 3) { if (validReligions.length < 3) return tip("Not enough religions to show hierarchy", false, "error");
tip("Not enough religions to show hierarchy", false, "error");
return;
}
const root = d3 const root = d3
.stratify() .stratify()
.id(d => d.i) .id(d => d.i)
.parentId(d => d.origin)(religions); .parentId(d => d.origins[0])(validReligions);
const treeWidth = root.leaves().length; const treeWidth = root.leaves().length;
const treeHeight = root.height; const treeHeight = root.height;
const width = treeWidth * 40, const width = Math.max(treeWidth * 40, 300);
height = treeHeight * 60; const height = treeHeight * 60;
const margin = {top: 10, right: 10, bottom: -5, left: 10}; const margin = {top: 10, right: 10, bottom: -5, left: 10};
const w = width - margin.left - margin.right; const w = width - margin.left - margin.right;
const h = height + 30 - margin.top - margin.bottom; const h = height + 30 - margin.top - margin.bottom;
const treeLayout = d3.tree().size([w, h]); const treeLayout = d3.tree().size([w, h]);
alertMessage.innerHTML = /* html */ `<div id="religionChartDetails" class='chartInfo'>
<div id='religionInfo' style="display: block">&#8205;</div>
<div id='religionSelected' style="display: none">
<span><span id='religionSelectedName'></span>. </span>
<span data-name="Type religion short name (abbreviation)">Abbreviation: <input id='religionSelectedCode' type='text' maxlength='3' size='3' /></span>
<button data-tip='Clear origin, religion will be linked to top level' id='religionSelectedClear'>Clear</button>
<button data-tip='Close edit mode' id='religionSelectedClose'>Close</button>
</div>
</div>`;
// prepare svg // prepare svg
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>";
const svg = d3 const svg = d3
.select("#alertMessage") .select("#alertMessage")
.insert("svg", "#religionInfo") .insert("svg", "#religionChartDetails")
.attr("id", "hierarchy") .attr("id", "hierarchy")
.attr("width", width) .attr("width", width)
.attr("height", height) .attr("height", height)
.style("text-anchor", "middle"); .style("text-anchor", "middle");
const graph = svg.append("g").attr("transform", `translate(10, -45)`); const graph = svg.append("g").attr("transform", `translate(10, -45)`);
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
const primaryLinks = links.append("g");
const secondaryLinks = links.append("g").attr("stroke-dasharray", 1);
const nodes = graph.append("g"); const nodes = graph.append("g");
// render helper functions
const getLinkPath = d => {
const {
source: {x: sx, y: sy},
target: {x: tx, y: ty}
} = d;
return `M${sx},${sy} C${sx},${(sy * 3 + ty) / 4} ${tx},${(sy * 2 + ty) / 3} ${tx},${ty}`;
};
const getSecondaryLinks = root => {
const nodes = root.descendants();
const links = [];
for (const node of nodes) {
const origins = node.data.origins;
if (node.depth < 2) continue;
for (let i = 1; i < origins.length; i++) {
const source = nodes.find(n => n.data.i === origins[i]);
if (source) links.push({source, target: node});
}
}
return links;
};
const nodePathMap = {
undefined: "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0", // small circle
Folk: "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0", // circle
Organized: "M-11,-11h22v22h-22Z", // square
Cult: "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z", // hexagon
Heresy: "M0,-14L14,0L0,14L-14,0Z" // diamond
};
const getNodePath = d => nodePathMap[d.data.type];
renderTree(); renderTree();
function renderTree() { function renderTree() {
treeLayout(root); treeLayout(root);
links
.selectAll("path") primaryLinks.selectAll("path").data(root.links()).enter().append("path").attr("d", getLinkPath);
.data(root.links()) secondaryLinks.selectAll("path").data(getSecondaryLinks(root)).enter().append("path").attr("d", getLinkPath);
.enter()
.append("path")
.attr("d", d => {
return (
"M" +
d.source.x +
"," +
d.source.y +
"C" +
d.source.x +
"," +
(d.source.y * 3 + d.target.y) / 4 +
" " +
d.target.x +
"," +
(d.source.y * 2 + d.target.y) / 3 +
" " +
d.target.x +
"," +
d.target.y
);
});
const node = nodes const node = nodes
.selectAll("g") .selectAll("g")
@ -536,30 +583,21 @@ function editReligions() {
.attr("data-id", d => d.data.i) .attr("data-id", d => d.data.i)
.attr("stroke", "#333333") .attr("stroke", "#333333")
.attr("transform", d => `translate(${d.x}, ${d.y})`) .attr("transform", d => `translate(${d.x}, ${d.y})`)
.on("mouseenter", () => religionHighlightOn(event)) .on("mouseenter", religionHighlightOn)
.on("mouseleave", () => religionHighlightOff(event)) .on("mouseleave", religionHighlightOff)
.call(d3.drag().on("start", d => dragToReorigin(d))); .on("click", religionSelect)
.call(d3.drag().on("start", dragToReorigin));
node node
.append("path") .append("path")
.attr("d", d => { .attr("d", getNodePath)
if (d.data.type === "Folk") return "M11.3,0A11.3,11.3,0,1,1,-11.3,0A11.3,11.3,0,1,1,11.3,0"; .attr("fill", d => d.data.color || "#ffffff")
// circle
else if (d.data.type === "Heresy") return "M0,-14L14,0L0,14L-14,0Z";
// diamond
else if (d.data.type === "Cult") return "M-6.5,-11.26l13,0l6.5,11.26l-6.5,11.26l-13,0l-6.5,-11.26Z";
// hex
else if (!d.data.i) return "M5,0A5,5,0,1,1,-5,0A5,5,0,1,1,5,0";
// small circle
else return "M-11,-11h22v22h-22Z"; // square
})
.attr("fill", d => (d.data.i ? d.data.color : "#ffffff"))
.attr("stroke-dasharray", d => (d.data.cells ? "null" : "1")); .attr("stroke-dasharray", d => (d.data.cells ? "null" : "1"));
node node
.append("text") .append("text")
.attr("dy", ".35em") .attr("dy", ".35em")
.text(d => (d.data.i ? d.data.code : "")); .text(d => d.data.code || "");
} }
$("#alert").dialog({ $("#alert").dialog({
@ -573,12 +611,38 @@ function editReligions() {
} }
}); });
function dragToReorigin(d) { function religionSelect(d) {
if (isCtrlClick(d3.event.sourceEvent)) { d3.event.stopPropagation();
changeCode(d);
return; nodes.selectAll("g").style("outline", "none");
this.style.outline = "1px solid #c13119";
byId("religionSelected").style.display = "block";
byId("religionInfo").style.display = "none";
const religion = d.data;
byId("religionSelectedName").innerText = religion.name;
byId("religionSelectedCode").value = religion.code;
byId("religionSelectedCode").onchange = function () {
if (this.value.length > 3) return tip("Abbreviation must be 3 characters or less", false, "error", 3000);
if (!this.value.length) return tip("Abbreviation cannot be empty", false, "error", 3000);
nodes.select(`g[data-id="${d.id}"] > text`).text(this.value);
religion.code = this.value;
};
byId("religionSelectedClear").onclick = () => {
religion.origins = [0];
showHierarchy();
};
byId("religionSelectedClose").onclick = () => {
this.style.outline = "none";
byId("religionSelected").style.display = "none";
byId("religionInfo").style.display = "block";
};
} }
function dragToReorigin(d) {
const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`); const originLine = graph.append("path").attr("class", "dragLine").attr("d", `M${d.x},${d.y}L${d.x},${d.y}`);
d3.event.on("drag", () => { d3.event.on("drag", () => {
@ -587,26 +651,20 @@ function editReligions() {
d3.event.on("end", () => { d3.event.on("end", () => {
originLine.remove(); originLine.remove();
const selected = graph.select("path.selected"); const selected = graph.select("g.selected");
if (!selected.size()) return; if (!selected.size()) return;
const religion = d.data.i;
const oldOrigin = d.data.origin;
let newOrigin = selected.datum().data.i;
if (newOrigin == oldOrigin) return; // already a child of the selected node
if (newOrigin == religion) newOrigin = 0; // move to top
if (newOrigin && d.descendants().some(node => node.id == newOrigin)) return; // cannot be a child of its own child
pack.religions[religion].origin = d.data.origin = newOrigin; // change data
showHierarchy(); // update hierarchy
});
}
function changeCode(d) { const religionId = d.data.i;
prompt(`Please provide an abbreviation for ${d.data.name}`, {default: d.data.code}, v => { const newOrigin = selected.datum().data.i;
pack.religions[d.data.i].code = v; if (religionId === newOrigin) return; // dragged to itself
nodes if (d.data.origins.includes(newOrigin)) return; // already a child of the selected node
.select("g[data-id='" + d.data.i + "']") if (d.descendants().some(node => node.data.i === newOrigin)) return; // cannot be a child of its own child
.select("text")
.text(v); const religion = pack.religions[religionId];
if (religion.origins[0] === 0) religion.origins = [];
religion.origins.push(newOrigin);
showHierarchy();
}); });
} }
} }
@ -621,7 +679,7 @@ function editReligions() {
customization = 7; customization = 7;
relig.append("g").attr("id", "temp"); relig.append("g").attr("id", "temp");
document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "none")); document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "none"));
document.getElementById("religionsManuallyButtons").style.display = "inline-block"; byId("religionsManuallyButtons").style.display = "inline-block";
debug.select("#religionCenters").style("display", "none"); debug.select("#religionCenters").style("display", "none");
religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
@ -685,7 +743,13 @@ function editReligions() {
// change of append new element // change of append new element
if (exists.size()) exists.attr("data-religion", r).attr("fill", color); if (exists.size()) exists.attr("data-religion", r).attr("fill", color);
else temp.append("polygon").attr("data-cell", i).attr("data-religion", r).attr("points", getPackPolygon(i)).attr("fill", color); else
temp
.append("polygon")
.attr("data-cell", i)
.attr("data-religion", r)
.attr("points", getPackPolygon(i))
.attr("fill", color);
}); });
} }
@ -717,7 +781,7 @@ function editReligions() {
relig.select("#temp").remove(); relig.select("#temp").remove();
removeCircle(); removeCircle();
document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "inline-block")); document.querySelectorAll("#religionsBottom > button").forEach(el => (el.style.display = "inline-block"));
document.getElementById("religionsManuallyButtons").style.display = "none"; byId("religionsManuallyButtons").style.display = "none";
religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden")); religionsEditor.querySelectorAll(".hide").forEach(el => el.classList.remove("hidden"));
religionsFooter.style.display = "block"; religionsFooter.style.display = "block";