Merge branch 'zone-types' into zoneFilter

This commit is contained in:
Azgaar 2022-02-06 23:08:10 +03:00 committed by GitHub
commit be5fd52545
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 5605 additions and 1374 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
.bat
.vscode
.idea
.idea/Fantasy-Map-Generator.iml

74
components/fill-box.js Normal file
View file

@ -0,0 +1,74 @@
// fill-box cannot use Shadow DOM as it needs access to svg hatches
// append stylesheet
{
const style = `
fill-box:not([disabled]) {
cursor: pointer;
}
fill-box > svg {
vertical-align: middle;
pointer-events: none;
}
fill-box > svg > rect {
stroke: #666666;
stroke-width: 2;
}`;
const styleElement = document.createElement("style");
styleElement.setAttribute("type", "text/css");
styleElement.innerHTML = style;
document.head.appendChild(styleElement);
}
{
const template = document.createElement("template");
template.innerHTML = `
<svg>
<rect x="0" y="0" width="100%" height="100%">
</svg>
`;
class FillBox extends HTMLElement {
constructor() {
super();
this.appendChild(template.content.cloneNode(true));
this.querySelector("rect")?.setAttribute("fill", this.fill);
this.querySelector("svg")?.setAttribute("width", this.size);
this.querySelector("svg")?.setAttribute("height", this.size);
}
static showTip() {
tip(this.tip);
}
connectedCallback() {
this.addEventListener("mousemove", this.constructor.showTip);
}
disconnectedCallback() {
this.removeEventListener("mousemove", this.constructor.showTip);
}
get fill() {
return this.getAttribute("fill") || "#333";
}
set fill(newFill) {
this.setAttribute("fill", newFill);
this.querySelector("rect")?.setAttribute("fill", newFill);
}
get size() {
return this.getAttribute("size") || "1em";
}
get tip() {
return this.dataset.tip || "Fill style. Click to change";
}
}
customElements.define("fill-box", FillBox);
}

View file

@ -29,9 +29,13 @@ input:read-only {
cursor: default;
}
input[type="radio"] {
vertical-align: bottom;
cursor: pointer;
}
textarea {
padding: 2px;
text-indent: 1px;
padding: 3px;
box-sizing: border-box;
width: 100%;
}
@ -834,75 +838,79 @@ fieldset {
}
.matrix-table {
width: 100%;
font-size: smaller;
text-align: center;
border-collapse: collapse;
max-height: 80vh;
max-width: 85vw;
scrollbar-width: thin;
overflow: auto;
}
table.matrix-table th,
table.matrix-table td {
.matrix-table > table {
text-align: center;
border-collapse: collapse;
font-size: smaller;
}
.matrix-table > table th,
.matrix-table > table td {
border: 1px solid var(--dark-solid);
height: 2em;
padding: 0.2em;
position: relative;
}
table.matrix-table th {
.matrix-table > table th {
background-color: #302a2a;
color: #ffffff;
}
table.matrix-table tr:hover th {
background: #3e3636;
}
table.matrix-table td:hover {
.matrix-table > table td:hover {
outline: 2px solid var(--dark-solid);
outline-offset: -1px;
z-index: 1;
cursor: pointer;
}
table.matrix-table td.Ally {
.matrix-table > table td.Ally {
background-color: #73ec73;
color: #000000;
}
table.matrix-table td.Friendly {
.matrix-table > table td.Friendly {
background-color: #d4f8aa;
}
table.matrix-table td.Neutral {
.matrix-table > table td.Neutral {
background-color: #d8d9d3;
}
table.matrix-table td.Suspicion {
.matrix-table > table td.Suspicion {
background-color: #eeafaa;
}
table.matrix-table td.Enemy {
.matrix-table > table td.Enemy {
background-color: #ffa39c;
color: #af0d23;
}
table.matrix-table td.Unknown {
.matrix-table > table td.Unknown {
background-color: #c1bfbf;
}
table.matrix-table td.Rival {
.matrix-table > table td.Rival {
background-color: #bd845c;
}
table.matrix-table td.Vassal {
.matrix-table > table td.Vassal {
background-color: #87cefa;
}
table.matrix-table td.Suzerain {
.matrix-table > table td.Suzerain {
background-color: #8f8fe1;
}
table.matrix-table td.x {
.matrix-table > table td.x {
background-color: #d4ca94;
cursor: initial;
}
#sizeOutput {
@ -1310,19 +1318,23 @@ div.slider .ui-slider-handle {
}
#alertMessage::-webkit-scrollbar,
.table::-webkit-scrollbar {
.table::-webkit-scrollbar,
.matrix-table::-webkit-scrollbar {
width: 6px;
height: 6px;
background-color: transparent;
}
#alertMessage::-webkit-scrollbar-thumb,
.table::-webkit-scrollbar-thumb {
.table::-webkit-scrollbar-thumb,
.matrix-table::-webkit-scrollbar-thumb {
background-color: #aaa;
border-radius: 6px;
}
#alertMessage::-webkit-scrollbar-thumb:hover,
.table::-webkit-scrollbar-thumb:hover {
.table::-webkit-scrollbar-thumb:hover,
.matrix-table::-webkit-scrollbar-thumb:hover {
background: #666;
}
@ -1504,15 +1516,14 @@ div.states > .riverType {
width: 5em;
}
div.states > .coaIcon {
.coaIcon {
stroke-width: 3;
width: 1.4em;
height: 1.4em;
margin: -0.3em 0;
cursor: pointer;
}
div.states > .coaIcon > use {
.coaIcon > use {
pointer-events: none;
}
@ -1520,6 +1531,11 @@ div.states > .coaIcon > use {
cursor: pointer;
}
.changeRelations > * {
pointer-events: none;
cursor: pointer;
}
#diplomacySelect {
width: 5em;
margin: 0.1em 0 0 -0.3em;
@ -1657,11 +1673,6 @@ div.states > div.biomeArea {
width: 5em;
}
rect.fillRect {
stroke: #666666;
stroke-width: 2;
}
#militaryHeader > div,
#regimentsHeader > div {
width: 5.2em;
@ -2000,7 +2011,7 @@ div.textual fieldset {
div.textual span,
.textual legend {
font-size: 0.8em;
font-size: 0.9em;
font-weight: bold;
}
@ -2119,8 +2130,9 @@ svg.button {
text-decoration: underline;
}
#info-line {
.info-line {
font-size: 0.9em;
font-style: italic;
color: gray;
user-select: none;
}
@ -2264,10 +2276,10 @@ svg.button {
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
max-width: 22em;
background-color: #fff;
max-width: 23em;
padding: 1.2em;
border: solid 1px #000;
background-color: var(--bg-dialogs);
border: solid 1px var(--dark-solid);
font-size: 1.2em;
z-index: 1000;
}
@ -2298,7 +2310,7 @@ svg.button {
.epgrid line {
stroke: lightgrey;
stroke-opacity: 0.7;
stroke-opacity: 0.5;
shape-rendering: crispEdges;
}

View file

@ -19,7 +19,7 @@
body {margin: 0; font-size: 10px; overflow: hidden;}
#map {position: absolute;}
#initial {fill: none; stroke: black; pointer-events: none;}
#init-rose {animation: 20s infinite spin; opacity: .7; transform-origin: center;}
#init-rose {opacity: .7; transform-origin: center; opacity: .7; animation: 20s infinite spin;}
@keyframes spin {0% {transform: rotate(0deg);} 100% {transform: rotate(359deg);}}
#loading {opacity:1; font-size: 11px; color:#fff5da; text-align:center; text-shadow:0px 1px 4px #4c3a35; width:80%; max-width:600px; position:fixed; top:50%; left:50%; transform:translate(-50%, -50%); pointer-events:none;}
#loading-text {font-size: 1.8em; margin: 0.2em 0 0 1em;}
@ -148,55 +148,6 @@
</filter>
</g>
<g id="hatching">
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch2" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch3" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch4" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch5" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch6" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch9" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch10" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch11" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5" />
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5" />
</pattern>
</g>
<g id="deftemp">
<mask id="land"></mask>
<mask id="water">
@ -226,7 +177,7 @@
<div id="loading">
<div id="titleName"><t data-t="titleName">Azgaar's</t></div>
<div id="title"><t data-t="title">Fantasy Map Generator</t></div>
<div id="version"><t data-t="version">v. </t>1.71</div>
<div id="version"><t data-t="version">v. </t>1.72</div>
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
</div>
@ -309,17 +260,9 @@
<div id="styleContent" class="tabcontent">
<p data-tip="Select a style preset. State labels may required regeneration if font is changed" style="display: inline-block">Style preset:</p>
<select data-tip="Select a style preset" id="stylePreset" onchange="changeStylePreset(this.value)" style="width:45%">
<option value="styleDefault" data-system=1 selected>Default</option>
<option value="styleAncient" data-system=1>Ancient</option>
<option value="styleGloom" data-system=1>Gloom</option>
<option value="styleClean" data-system=1>Clean</option>
<option value="styleLight" data-system=1>Light</option>
<option value="styleWatercolor" data-system=1>Watercolor</option>
<option value="styleMonochrome" data-system=1>Monochrome</option>
</select>
<select data-tip="Select a style preset" id="stylePreset" onchange="requestStylePresetChange(this.value)" style="width:45%; text-transform: capitalize;"></select>
<button id="addStyleButton" data-tip="Click to save current style as a new preset" class="icon-plus sideButton" style="display: inline-block" onclick="addStylePreset()"></button>
<button id="removeStyleButton" data-tip="Click to remove current custom style preset" class="icon-minus sideButton" style="display: none" onclick="removeStylePreset()"></button>
<button id="removeStyleButton" data-tip="Click to remove current custom style preset" class="icon-minus sideButton" style="display: none" onclick="requestRemoveStylePreset()"></button>
<p data-tip="Select an element to edit its style" style="display: inline-block;">Select element:</p>
<select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%">
@ -947,7 +890,7 @@
<tr data-tip="Map seed number. Seed produces the same map only if canvas size and options are the same">
<td>
<i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-history"></i>
<i data-tip="Show seed history to apply a previous seed" id="optionsMapHistory" class="icon-hourglass-1"></i>
</td>
<td>Map seed</td>
<td>
@ -1062,7 +1005,7 @@
<input id="regionsInput" data-stored="regions" type="range" min=0 max=99 value=13>
</td>
<td>
<input id="regionsOutput" data-stored="regions" type="number" min=0 max=99 value=13>
<input id="regionsOutput" data-stored="regions" type="number" min=0 max=999 value=13>
</td>
</tr>
@ -1446,9 +1389,21 @@
</div>
<div id="aboutContent" class="tabcontent">
<p><a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps. You may use auto-generated maps as they are, edit them or even create a new map from scratch. Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">quick start tutorial</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a> and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.</p>
<p>Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.</p>
<p>The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>. For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.</p>
<p>
<a href="https://github.com/Azgaar/Fantasy-Map-Generator" target="_blank">Fantasy Map Generator</a> is a free <a href="https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE" target="_blank">open source</a> tool which procedurally generates fantasy maps.
You may use auto-generated maps as they are, edit them or even create a new map from scratch.
Check out the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial" target="_blank">Quick start</a>, <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A" target="_blank">Q&A</a>, <a href="https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C" target="_blank">Video tutorial</a>, and <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys" target="_blank">hotkeys</a> for guidance.
</p>
<p>
Join our <a href='https://discordapp.com/invite/X7E84HU' target='_blank'>Discord server</a> and <a href="https://www.reddit.com/r/FantasyMapGenerator/" target="_blank">Reddit community</a> to ask questions, get help and share maps.
</p>
<p>
The project is under active development. Creator and main maintainer: Azgaar. To track the development progress see the <a href="https://trello.com/b/7x832DG4/fantasy-map-generator" target="_blank">devboard</a>.
For older versions see the <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog" target="_blank">changelog</a>. Please report bugs <a href="https://github.com/Azgaar/Fantasy-Map-Generator/issues" target="_blank">here</a>. You can also contact me directly via <a href="mailto:azgaar.fmg@yandex.by" target="_blank">email</a>.
</p>
<div style="background-color: #e85b46; padding: .4em; width: max-content; margin: .6em auto 0 auto; border: 1px solid #943838">
<a href="https://www.patreon.com/azgaar" target="_blank" style="color: white; text-decoration: none; font-family: sans-serif">
<div>
@ -1461,7 +1416,10 @@
</div>
</a>
</div>
<p>Special thanks to <a data-tip="Click to see list of supporters" onclick="showSupporters()">all supporters</a> on Patreon!</p>
<p>
Special thanks to <a data-tip="Click to see list of supporters" onclick="showSupporters()">all supporters</a> on Patreon!
</p>
<ul class="share-buttons">
<li><a href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator%2F&quote=" data-tip="Share on Facebook" target="_blank"><img alt="Share on Facebook" src="images/Facebook.png" /></a></li>
@ -1784,7 +1742,7 @@
<span data-tip="Set curve profile">Curve:
<select id="epCurve">
<option>Linear</option>
<option selected>Basis spline</option>
<option selected>Basis spline</option>
<option>Bundle</option>
<option>Cubic Catmull-Rom</option>
<option>Monotone X</option>
@ -1975,69 +1933,73 @@
<div id="burgBody" style="padding-bottom: .3em">
<div style="display: flex; align-items: center">
<svg data-tip="Burg emblem. Click to edit" class="pointer" viewBox="0 0 200 200" width="13em" height="13em"><use id="burgEmblem"></use></svg>
<div>
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div style="display: grid; grid-auto-rows: minmax(1.6em, auto)">
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div>
<div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div>
<div>
<div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
<span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div>
<div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div>
<select id="burgType" style="width: 8em">
<option value="Generic">Generic</option>
<option value="River">River</option>
<option value="Lake">Lake</option>
<option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option>
<option value="Highland">Highland</option>
</select>
</div>
<div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div>
<select id="burgType" style="width: 8em">
<option value="Generic">Generic</option>
<option value="River">River</option>
<option value="Lake">Lake</option>
<option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option>
<option value="Highland">Highland</option>
</select>
</div>
<div data-tip="Select dominant culture">
<div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div>
<div data-tip="Select dominant culture">
<div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div>
<div data-tip="Set burg population">
<div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div>
<div data-tip="Set burg population">
<div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div>
<div>
<div class="label">Features:</div>
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
</div>
<div>
<div class="label">Features:</div>
<span id="burgCapital" data-tip="Shows whether the burg is a state capital. Click to toggle" data-feature="capital" class="burgFeature icon-star"></span>
<span id="burgPort" data-tip="Shows whether the burg is a port. Click to toggle" data-feature="port" class="burgFeature icon-anchor"></span>
<span id="burgCitadel" data-tip="Shows whether the burg has a citadel (castle). Click to toggle" data-feature="citadel" class="burgFeature icon-chess-rook" style="font-size: 1.1em"></span>
<span id="burgWalls" data-tip="Shows whether the burg is walled. Click to toggle" data-feature="walls" class="burgFeature icon-fort-awesome"></span>
<span id="burgPlaza" data-tip="Shows whether the burg is a trade center (has big marketplace). Click to toggle" data-feature="plaza" class="burgFeature icon-store" style="font-size: 1em"></span>
<span id="burgTemple" data-tip="Shows whether the burg is a religious center. Click to toggle" data-feature="temple" class="burgFeature icon-chess-bishop" style="font-size: 1.1em; margin-left: 3px"></span>
<span id="burgShanty" data-tip="Shows whether the burg has a shanty town. Click to toggle" data-feature="shanty" class="burgFeature icon-campground" style="font-size: 1em"></span>
</div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
</div>
<div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span>
<i id="burgTemperatureGraph" data-tip="Show temperature graph for the burg" class="icon-chart-area pointer"></i>
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
<div data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level
</div>
</div>
</div>
<div id="mfcgPreviewSection" data-tip="Burg preview in the Medieval Fantasy City Generator. Default seed is a combination of map seed and burg id" style="display: flex; flex-direction: column">
<div>
See in <a id="mfcgLink" target="_blank">City Generator by Watabou</a>.
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" />
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i>
<div id="mfcgBurgSeedSection">
Seed: <input id="mfcgBurgSeed" style="width: 10em" type="number" min=1 max="1e13" step="1" />
<i id="regenerateMFCGBurgSeed" data-tip="Randomize Medieval Fantasy City Generator burg seed" class="icon-arrows-cw pointer" style="margin-left: .1em"></i>
</div>
<i id="addCustomMFCGBurgLink" data-tip="Provide custom link to the burg map" class="icon-pencil pointer" style="margin-left: .1em"></i>
</div>
<iframe id="mfcgPreview" sandbox="allow-scripts allow-same-origin"></iframe>
</div>
@ -2618,8 +2580,10 @@
<option value="Sultanate">Sultanate</option>
<option value="Tsardom">Tsardom</option>
<option value="Ulus">Ulus</option>
<option value="Viceroyalty">Viceroyalty</option>
</optgroup>
<optgroup label="Republic">
<option value="Chancellery">Chancellery</option>
<option value="City-state">City-state</option>
<option value="Diarchy">Diarchy</option>
<option value="Federation">Federation</option>
@ -2657,8 +2621,10 @@
<option value="Divine Kingdom">Divine Kingdom</option>
<option value="Divine Empire">Divine Empire</option>
<option value="Eparchy">Eparchy</option>
<option value="Exarchate">Exarchate</option>
<option value="Holy State">Holy State</option>
<option value="Imamah">Imamah</option>
<option value="Patriarchate">Patriarchate</option>
<option value="Theocracy">Theocracy</option>
</optgroup>
<optgroup label="Anarchy">
@ -2735,33 +2701,27 @@
<div id="diplomacyEditor" class="dialog stable" style="display: none">
<div id="diplomacyHeader" class="header">
<div style="left:.2em" data-tip="Click to sort by state name" class="sortable alphabetically" data-sortby="name">State&nbsp;</div>
<div style="left:13.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations&nbsp;</div>
<div style="left:14.4em" data-tip="Click to sort by diplomatical relations" class="sortable alphabetically" data-sortby="relations">Relations&nbsp;</div>
</div>
<div id="diplomacyBodySection" class="table"></div>
<div id="diplomacySelect">
<div data-tip="Ally means states formed a defensive pact and will protect each other in case of third party aggression">Ally</div>
<div data-tip="State is friendly to anouther state when they share some common interests">Friendly</div>
<div data-tip="Neutral means states relations are neither positive nor negative">Neutral</div>
<div data-tip="Suspicion means state has a cautious distrust of another state">Suspicion</div>
<div data-tip="Enemies are states at war with each other">Enemy</div>
<div data-tip="Relations are unknown if states do not have enough information about each other">Unknown</div>
<div data-tip="Rivalry is a state of competing for dominance in the region">Rival</div>
<div data-tip="Vassal is a state having obligation to its suzerain">Vassal</div>
<div data-tip="Suzerain is a state having some control over its vassals">Suzerain</div>
</div>
<div class="info-line">Click on state name to see relations.<br>Click on relations name to change it</div>
<div id="diplomacyBottom" style="margin-top: .1em">
<button id="diplomacyEditorRefresh" data-tip="Refresh the Editor" class="icon-cw"></button>
<button id="diplomacyEditStyle" data-tip="Edit states (including diplomacy view) style in Style Editor" class="icon-adjust"></button>
<button id="diplomacyRegenerate" data-tip="Regenerate diplomatical relations" class="icon-retweet"></button>
<button id="diplomacyReset" data-tip="Reset diplomatical relations of selected state to Neutral" class="icon-eraser"></button>
<button id="diplomacyHistory" data-tip="Show relations history" class="icon-hourglass-1"></button>
<button id="diplomacyMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button>
<button id="diplomacyShowMatrix" data-tip="Show relations matrix" class="icon-list-bullet"></button>
<button id="diplomacyExport" data-tip="Save state relations matrix as a text file (.csv)" class="icon-download"></button>
</div>
</div>
<div id="diplomacyMatrix" class="dialog" style="display: none">
<div id="diplomacyMatrixBody" class="matrix-table"></div>
</div>
<div id="provinceNameEditor" class="dialog" data-province="0" style="display: none">
<div>
<div data-tip="Province short name" class="label">Short name:</div>
@ -2779,6 +2739,8 @@
<option value="Autonomy">Autonomy</option>
<option value="Barony">Barony</option>
<option value="Canton">Canton</option>
<option value="Captaincy">Captaincy</option>
<option value="Chiefdom">Chiefdom</option>
<option value="Clan">Clan</option>
<option value="Colony">Colony</option>
<option value="Council">Council</option>
@ -2786,6 +2748,7 @@
<option value="Deanery">Deanery</option>
<option value="Department">Department</option>
<option value="Dependency">Dependency</option>
<option value="Diaconate">Diaconate</option>
<option value="District">District</option>
<option value="Earldom">Earldom</option>
<option value="Governorate">Governorate</option>
@ -2795,6 +2758,7 @@
<option value="Landgrave">Landgrave</option>
<option value="Mandate">Mandate</option>
<option value="Margrave">Margrave</option>
<option value="Municipality">Municipality</option>
<option value="Occupation zone">Occupation zone</option>
<option value="Parish">Parish</option>
<option value="Prefecture">Prefecture</option>
@ -2802,6 +2766,7 @@
<option value="Region">Region</option>
<option value="Republic">Republic</option>
<option value="Reservation">Reservation</option>
<option value="Seneschalty">Seneschalty</option>
<option value="Shire">Shire</option>
<option value="State">State</option>
<option value="Territory">Territory</option>
@ -2869,21 +2834,22 @@
<div id="namesbaseBasesTop">
<span>Select base: </span>
<select id="namesbaseSelect" data-tip="Select base to edit" style="width: 12em" value="0"></select>
<span style="margin-left: 2px">Names data: </span>
</div>
<div id="namesbaseBody">
<span>Names data:</span><br>
<textarea id="namesbaseTextarea" data-base="0" rows=12 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<br>
<div id="namesbaseBody" style="margin-block: 2px">
<textarea id="namesbaseTextarea" data-base="0" rows=13 data-tip="Names data: a comma separated list of source names used for names generation" placeholder="Provide a names data: a comma separated list of source names" autocorrect="off" spellcheck="false"></textarea>
<div>
<span>Name: </span>
<input id="namesbaseName" data-tip="Type to change a base name" placeholder="Base name" autocorrect="off" spellcheck="false" style="width:12em"/>
<span>Length: </span>
<input id="namesbaseMin" data-tip="Recommended minimum name length" type="number" min=2 max=100>
<input id="namesbaseMax" data-tip="Recommended maximum name length" type="number" min=2 value=10>
<span>Double: </span>
<span>Doubled: </span>
<input id="namesbaseDouble" data-tip="Populate with letters that can used twice in a row (geminates)" autocorrect="off" spellcheck="false" style="width:10em">
</div>
<fieldset>
<legend>Generated examples: </legend>
<div id="namesbaseExamples" data-tip="Examples. Click to re-generate"></div>
@ -2892,12 +2858,12 @@
<div id="namesbaseBottom">
<button id="namesbaseUpdateExamples" data-tip="Re-generate examples based on provided data" class="icon-arrows-cw"></button>
<button id="namesbaseAnalize" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseAdd" data-tip="Add new namesbase" class="icon-plus"></button>
<button id="namesbaseDefault" data-tip="Restore default namesbase" class="icon-cancel"></button>
<button id="namesbaseDownload" data-tip="Download namesbase to PC" class="icon-download"></button>
<button id="namesbaseUpload" data-tip="Upload a namesbase from PC" class="icon-upload"></button>
<button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass" onclick="openURL('https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/')"></button>
<button id="namesbaseCA" data-tip="Find or share custom namesbase on Cartography Assets portal" class="icon-drafting-compass"></button>
<button id="namesbaseAnalyze" data-tip="Analyze namesbase to get a validity and quality overview" class="icon-flask"></button>
<button id="namesbaseSpeak" data-tip="Speak the examples. You can change voice and language in options" class="icon-voice"></button>
</div>
</div>
@ -3310,6 +3276,7 @@
<button id="addNewBurg" data-tip="Add a new burg. Hold Shift to add multiple" class="icon-plus"></button>
<button id="burgsExport" data-tip="Save burgs-related data as a text file (.csv)" class="icon-download"></button>
<button id="burgNamesImport" data-tip="Rename burgs in bulk" class="icon-upload"></button>
<button id="burgsLockAll" data-tip="Lock or unlock all burgs" class="icon-lock"></button>
<button id="burgsRemoveAll" data-tip="Remove all unlocked burgs except for capitals. To remove a capital remove its state first" class="icon-trash"></button>
</div>
</div>
@ -3609,7 +3576,19 @@
</div>
<p>GeoJSON format is used in GIS tools such as QGIS. Check out <a href="https://github.com/Azgaar/Fantasy-Map-Generator/wiki/GIS-data-export" target="_blank">wiki-page</a> for guidance.</p>
<p>Generator uses pop-up window to download files. Please ensure your browser does not block popups.</p>
<p>It's also possible to export map to <i>Foundry VTT</i>, see <a href="https://github.com/Ethck/azgaar-foundry" target="_blank">the module.</a></p>
<div style="margin: 1em 0 .3em; font-weight: bold">Export To JSON</div>
<div>
<button onclick="exportToJson('Full')" data-tip="Download full data as in JSON format">full</button>
<button onclick="exportToJson('Minimal')" data-tip="Download minimal data as in JSON format">minimal</button>
<button onclick="exportToJson('PackCells')" data-tip="Download map metadata and pack cells data as in JSON format">pack cells</button>
<button onclick="exportToJson('GridCells')" data-tip="Download map metadata and grid cells data as in JSON format">grid cells</button>
</div>
<p>Export in JSON format can be used as an API replacement.</p>
<div>
<p>It's also possible to export map to <i>Foundry VTT</i>, see <a href="https://github.com/Ethck/azgaar-foundry" target="_blank">the module.</a></p>
</div>
</div>
<div id="saveMapData" style="display: none" class="dialog">
@ -3662,8 +3641,7 @@
<div data-tip="Image scale relative to image size (e.g. 5x)" style="margin-bottom: .3em">
<div class="label">Scale:</div>
<input id="tileScaleInput" data-stored="tileScale" type="range" min=1 max=4 value=1 style="width: 10em">
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1
>
<input id="tileScaleOutput" data-stored="tileScale" type="number" min=1 value=1 >
</div>
<div data-tip="Calculated size of image if combined" style="margin-bottom: .3em">
<div class="label">Total size:</div>
@ -3679,7 +3657,7 @@
<div id="prompt" style="display: none" class="dialog">
<form id="promptForm">
<div id="promptText"></div>
<input id="promptInput" type="number" step=.01 placeholder="type value" autocomplete="off" required>
<input id="promptInput" type="number" step=.01 placeholder="type value" autocomplete="off">
<button type="submit">Confirm</button>
<button type="button" id="promptCancel" formnovalidate>Cancel</button>
</form>
@ -4355,6 +4333,153 @@
<path d="M 43.4,0 36.2,12.5 43.4,25 M 21.7,12.5 H 36.2 Z M 0,0 H 14.5 L 21.7,12.5 14.5,25 H 0"/>
</pattern>
</g>
<g id="defs-hatching">
<pattern id="hatch0" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch1" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch2" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch3" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch4" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch5" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch6" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch7" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch8" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch9" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch10" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch11" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:2.5"/>
</pattern>
<pattern id="hatch12" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch13" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch14" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch15" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch16" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch17" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch18" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch19" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch20" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch21" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch22" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch23" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch24" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch25" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch26" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch27" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch28" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="2" y1="0" x2="0" y2="2" style="stroke:black; stroke-width:2"/>
</pattern>
<pattern id="hatch29" patternTransform="rotate(30 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="1" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch30" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch31" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="1" y1="0" x2="0" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="1" y1="0" x2="3" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch32" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="0.5" style="fill: black"/>
</pattern>
<pattern id="hatch33" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="2.5" cy="2.5" r="1.5" style="fill: black"/>
</pattern>
<pattern id="hatch34" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="3" cy="3" r="1" style="fill: black"/>
<circle cx="1" cy="1" r="1" style="fill: black"/>
</pattern>
<pattern id="hatch35" patternUnits="userSpaceOnUse" width="5" height="5">
<circle cx="3" cy="3" r="1.5" style="fill: black"/>
<circle cx="1" cy="1" r="1.5" style="fill: black"/>
</pattern>
<pattern id="hatch36" patternTransform="rotate(-45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch37" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="0" y2="3" transform="rotate(-45 0 0)" style="stroke:black; stroke-width:1.5" />
</pattern>
<pattern id="hatch38" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch39" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="4" y2="4" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch40" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
<pattern id="hatch41" patternTransform="rotate(45 0 0)" patternUnits="userSpaceOnUse" width="4" height="4">
<line x1="0" y1="0" x2="3" y2="3" style="stroke:black; stroke-width:1.5"/>
<line x1="0" y1="0" x2="4" y2="0" style="stroke:black; stroke-width:1.5"/>
</pattern>
</g>
</defs>
</svg>
@ -4395,18 +4520,20 @@
<script src="modules/fonts.js"></script>
<script src="modules/ui/layers.js"></script>
<script src="modules/ui/measurers.js"></script>
<script src="modules/ui/stylePresets.js"></script>
<!-- <script src="libs/umami.js"></script> -->
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<script defer src="modules/ui/general.js"></script>
<script defer src="modules/ui/options.js"></script>
<script defer src="modules/ui/style.js"></script>
<script src="modules/ui/general.js"></script>
<script src="modules/ui/options.js"></script>
<script src="main.js"></script>
<script defer src="modules/load.js"></script>
<script defer src="modules/cloud.js"></script>
<script defer src="main.js"></script>
<script defer src="modules/export-json.js"></script>
<script defer src="modules/save.js"></script>
<script defer src="modules/export.js"></script>
<script defer src="modules/relief-icons.js"></script>
<script defer src="modules/ui/style.js"></script>
<script defer src="modules/ui/tools.js"></script>
<script defer src="modules/ui/world-configurator.js"></script>
<script defer src="modules/ui/editors.js"></script>
@ -4417,6 +4544,7 @@
<script defer src="modules/ui/cultures-editor.js"></script>
<script defer src="modules/ui/namesbase-editor.js"></script>
<script defer src="modules/ui/elevation-profile.js"></script>
<script defer src="modules/ui/temperature-graph.js"></script>
<script defer src="modules/ui/routes-editor.js"></script>
<script defer src="modules/ui/ice-editor.js"></script>
<script defer src="modules/ui/lakes-editor.js"></script>
@ -4426,7 +4554,6 @@
<script defer src="modules/ui/rivers-creator.js"></script>
<script defer src="modules/ui/relief-editor.js"></script>
<script defer src="modules/ui/religions-editor.js"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/burg-editor.js"></script>
<script defer src="modules/ui/units-editor.js"></script>
<script defer src="modules/ui/notes-editor.js"></script>
@ -4439,13 +4566,18 @@
<script defer src="modules/ui/markers-overview.js"></script>
<script defer src="modules/ui/regiment-editor.js"></script>
<script defer src="modules/ui/battle-screen.js"></script>
<script defer src="modules/coa-renderer.js"></script>
<script defer src="modules/ui/emblems-editor.js"></script>
<script defer src="modules/ui/markers-editor.js"></script>
<script defer src="modules/ui/3d.js"></script>
<script defer src="modules/ui/hotkeys.js"></script>
<script defer src="modules/coa-renderer.js"></script>
<script defer src="libs/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.min.js"></script>
<script defer src="libs/pell.min.js"></script>
<script defer src="libs/jszip.min.js"></script>
<script defer src="https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js"></script>
<!-- Web Components -->
<script defer src="components/fill-box.js"></script>
</body>
</html>

92
main.js
View file

@ -1,11 +1,11 @@
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2021. MIT License
// Azgaar (azgaar.fmg@yandex.com). Minsk, 2017-2022. MIT License
// https://github.com/Azgaar/Fantasy-Map-Generator
"use strict";
const version = "1.71"; // generator version
const version = "1.73"; // generator version
document.title += " v" + version;
// Switches to disable/enable logging features
// switches to disable/enable logging features
const PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
const DEBUG = localStorage.getItem("debug");
const INFO = DEBUG || !PRODUCTION;
@ -109,7 +109,7 @@ scaleBar.on("mousemove", () => tip("Click to open Units Editor")).on("click", ()
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
// main data variables
let grid = {}; // initial grapg based on jittered square grid and data
let grid = {}; // initial graph based on jittered square grid and data
let pack = {}; // packed graph and data
let seed;
let mapId;
@ -161,7 +161,7 @@ let urbanDensity = +document.getElementById("urbanDensityInput").value;
applyStoredOptions();
// voronoi graph extention, cannot be changed arter generation
// voronoi graph extension, cannot be changed after generation
let graphWidth = +mapWidthInput.value;
let graphHeight = +mapHeightInput.value;
@ -173,14 +173,37 @@ landmass.append("rect").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr
oceanPattern.append("rect").attr("fill", "url(#oceanic)").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
// remove loading screen
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
if (!location.hostname) {
const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
alertMessage.innerHTML = `Fantasy Map Generator cannot run serverless.
Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can easily run a local web-server`;
$("#alert").dialog({
resizable: false,
title: "Loading error",
width: "28em",
position: {my: "center center-4em", at: "center", of: "svg"},
buttons: {
OK: function () {
$(this).dialog("close");
}
}
});
d3.select("#loading-text").transition().duration(1000).style("opacity", 0);
d3.select("#init-rose").transition().duration(4000).style("opacity", 0);
} else {
checkLoadParameters();
// remove loading screen
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove();
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove();
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
}
// decide which map should be loaded or generated on page load
void (function checkLoadParameters() {
function checkLoadParameters() {
const url = new URL(window.location.href);
const params = url.searchParams;
@ -225,10 +248,10 @@ void (function checkLoadParameters() {
WARN && console.warn("Generate random map");
generateMapOnLoad();
})();
}
function generateMapOnLoad() {
applyStyleOnLoad(); // apply default or previously selected style
async function generateMapOnLoad() {
await applyStyleOnLoad(); // apply previously selected default or custom style
generate(); // generate map
focusOn(); // based on searchParams focus on point, cell or burg from MFCG
applyPreset(); // apply saved layers preset
@ -412,20 +435,18 @@ function showWelcomeMessage() {
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>.
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
<ul>Main changes:
<li>Ability to limit military units by biome, state, culture and religion</li>
<li>New marker types</li>
<li>New markers editor</li>
<li>Markers overview screen</li>
<li>Markers regeneration menu</li>
<li>Burg editor update</li>
<li>Editable theme color</li>
<li>Add font dialog</li>
<li>Save to Dropbox</li>
<ul><b>Latest changes:</b>
<li>Color picker: new hatchings</li>
<li>New style presets: Cyberpunk and Atlas</li>
<li>Burg temperature graph</li>
<li>4 new textures</li>
<li>Province capture logic rework</li>
<li>Button to release all provinces</li>
<li>Limit military units by biome, state, culture and religion</li>
</ul>
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p>
<span>Thanks for all supporters on <a href="https://www.patreon.com/azgaar" target="_blank">Patreon</a>!</i></span>`;
<span><i>Thanks for all supporters on ${patreon}!</i></span>`;
$("#alert").dialog({
resizable: false,
@ -493,7 +514,7 @@ function invokeActiveZooming() {
coastline.select("#sea_island").attr("filter", filter);
}
// rescale lables on zoom
// rescale labels on zoom
if (labels.style("display") !== "none") {
labels.selectAll("g").each(function () {
if (this.id === "burgLabels") return;
@ -668,7 +689,7 @@ function generate() {
const parsedError = parseError(error);
clearMainTip();
alertMessage.innerHTML = `An error is occured on map generation. Please retry.
alertMessage.innerHTML = `An error has occurred on map generation. Please retry.
<br>If error is critical, clear the stored data and try again.
<p id="errorBox">${parsedError}</p>`;
$("#alert").dialog({
@ -980,7 +1001,10 @@ function generatePrecipitation() {
prec.selectAll("*").remove();
const {cells, cellsX, cellsY} = grid;
cells.prec = new Uint8Array(cells.i.length); // precipitation array
const modifier = precInput.value / 100; // user's input
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const precInputModifier = precInput.value / 100;
const modifier = cellsNumberModifier * precInputModifier;
const westerly = [];
const easterly = [];
@ -996,14 +1020,14 @@ function generatePrecipitation() {
// x2 = 60-70 latitude: wet summer (rising zone), dry winter (sinking zone)
// x1 = 70-85 latitude: dry all year (sinking zone)
// x0.5 = 85-90 latitude: dry all year (sinking zone)
const lalitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5];
const latitudeModifier = [4, 2, 2, 2, 1, 1, 2, 2, 2, 2, 3, 3, 2, 2, 1, 1, 1, 0.5];
const MAX_PASSABLE_ELEVATION = 85;
// define wind directions based on cells latitude and prevailing winds there
d3.range(0, cells.i.length, cellsX).forEach(function (c, i) {
const lat = mapCoordinates.latN - (i / cellsY) * mapCoordinates.latT;
const latBand = ((Math.abs(lat) - 1) / 5) | 0;
const latMod = lalitudeModifier[latBand];
const latMod = latitudeModifier[latBand];
const windTier = (Math.abs(lat - 89) / 30) | 0; // 30d tiers from 0 to 5 from N to S
const {isWest, isEast, isNorth, isSouth} = getWindDirections(windTier);
@ -1020,14 +1044,14 @@ function generatePrecipitation() {
const vertT = southerly + northerly;
if (northerly) {
const bandN = ((Math.abs(mapCoordinates.latN) - 1) / 5) | 0;
const latModN = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandN];
const latModN = mapCoordinates.latT > 60 ? d3.mean(latitudeModifier) : latitudeModifier[bandN];
const maxPrecN = (northerly / vertT) * 60 * modifier * latModN;
passWind(d3.range(0, cellsX, 1), maxPrecN, cellsX, cellsY);
}
if (southerly) {
const bandS = ((Math.abs(mapCoordinates.latS) - 1) / 5) | 0;
const latModS = mapCoordinates.latT > 60 ? d3.mean(lalitudeModifier) : lalitudeModifier[bandS];
const latModS = mapCoordinates.latT > 60 ? d3.mean(latitudeModifier) : latitudeModifier[bandS];
const maxPrecS = (southerly / vertT) * 60 * modifier * latModS;
passWind(d3.range(cells.i.length - cellsX, cells.i.length, 1), maxPrecS, -cellsX, cellsY);
}
@ -1181,7 +1205,7 @@ function reGraph() {
TIME && console.timeEnd("reGraph");
}
// Detect and draw the coasline
// Detect and draw the coastline
function drawCoastline() {
TIME && console.time("drawCoastline");
reMarkFeatures();
@ -1190,7 +1214,7 @@ function drawCoastline() {
vertices = pack.vertices,
n = cells.i.length,
features = pack.features;
const used = new Uint8Array(features.length); // store conneted features
const used = new Uint8Array(features.length); // store connected features
const largestLand = d3.scan(
features.map(f => (f.land ? f.cells : 0)),
(a, b) => b - a

View file

@ -31,7 +31,7 @@ window.BurgsAndStates = (function () {
function placeCapitals() {
TIME && console.time("placeCapitals");
let count = +regionsInput.value;
let count = +regionsOutput.value;
let burgs = [0];
const rand = () => 0.5 + Math.random() * 0.5;
@ -240,7 +240,7 @@ window.BurgsAndStates = (function () {
b.citadel = b.capital || (pop > 50 && P(0.75)) || P(0.5) ? 1 : 0;
b.plaza = pop > 50 || (pop > 30 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.25) ? 1 : 0;
b.walls = b.capital || pop > 30 || (pop > 20 && P(0.75)) || (pop > 10 && P(0.5)) || P(0.2) ? 1 : 0;
b.shanty = pop > 30 || (pop > 20 && P(0.75)) || (b.walls && P(0.75)) ? 1 : 0;
b.shanty = pop > 60 || (pop > 40 && P(0.75)) || (pop > 20 && b.walls && P(0.4)) ? 1 : 0;
const religion = cells.religion[b.cell];
const theocracy = pack.states[b.state].form === "Theocracy";
b.temple = (religion && theocracy) || pop > 50 || (pop > 35 && P(0.75)) || (pop > 20 && P(0.5)) ? 1 : 0;
@ -726,7 +726,7 @@ window.BurgsAndStates = (function () {
TIME && console.time("assignColors");
const colors = ["#66c2a5", "#fc8d62", "#8da0cb", "#e78ac3", "#a6d854", "#ffd92f"]; // d3.schemeSet2;
// assin basic color using greedy coloring algorithm
// assign basic color using greedy coloring algorithm
pack.states.forEach(s => {
if (!s.i || s.removed) return;
const neibs = s.neighbors;
@ -962,12 +962,12 @@ window.BurgsAndStates = (function () {
const republic = {
Republic: 75,
Federation: 4,
Oligarchy: 2,
"Trade Company": 4,
"Most Serene Republic": 2,
Oligarchy: 2,
Tetrarchy: 1,
Triumvirate: 1,
Diarchy: 1,
"Trade Company": 4,
Junta: 1
}; // weighted random
const union = {Union: 3, League: 4, Confederation: 1, "United Kingdom": 1, "United Republic": 1, "United Provinces": 2, Commonwealth: 1, Heptarchy: 1}; // weighted random
@ -997,7 +997,7 @@ window.BurgsAndStates = (function () {
const form = monarchy[tier];
// Default name depends on exponent tier, some culture bases have special names for tiers
if (s.diplomacy) {
if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal dutchies on borderland
if (form === "Duchy" && s.neighbors.length > 1 && rand(6) < s.neighbors.length && s.diplomacy.includes("Vassal")) return "Marches"; // some vassal duchies on borderland
if (base === 1 && P(0.3) && s.diplomacy.includes("Vassal")) return "Dominion"; // English vassals
if (P(0.3) && s.diplomacy.includes("Vassal")) return "Protectorate"; // some vassals
}
@ -1037,7 +1037,12 @@ window.BurgsAndStates = (function () {
if (tier < 2 && P(0.5)) return "Diocese";
if (tier < 2 && P(0.5)) return "Bishopric";
}
if (tier < 2 && P(0.9) && [7, 5].includes(base)) return "Eparchy"; // Greek, Ruthenian
if (P(0.9) && [7, 5].includes(base)) {
// Greek, Ruthenian
if (tier < 2) return "Eparchy";
if (tier === 2) return "Exarchate";
if (tier > 2) return "Patriarchate";
}
if (P(0.9) && [21, 16].includes(base)) return "Imamah"; // Nigerian, Turkish
if (tier > 2 && P(0.8) && [18, 17, 28].includes(base)) return "Caliphate"; // Arabic, Berber, Swahili
return rw(theocracy);
@ -1093,7 +1098,7 @@ window.BurgsAndStates = (function () {
const max = percentage == 100 ? 1000 : gauss(20, 5, 5, 100) * percentage ** 0.5; // max growth
const forms = {
Monarchy: {County: 11, Earldom: 3, Shire: 1, Landgrave: 1, Margrave: 1, Barony: 1},
Monarchy: {County: 22, Earldom: 6, Shire: 2, Landgrave: 2, Margrave: 2, Barony: 2, Captaincy: 1, Seneschalty: 1},
Republic: {Province: 6, Department: 2, Governorate: 2, District: 1, Canton: 1, Prefecture: 1},
Theocracy: {Parish: 3, Deanery: 1},
Union: {Province: 1, State: 1, Canton: 1, Republic: 1, County: 1, Council: 1},

View file

@ -976,7 +976,7 @@ window.COA = (function () {
if (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
if (pack.cultures[culture].shield) return pack.cultures[culture].shield;
console.error("Shield shape is not defined on culture level", pack.cultures[culture]);
ERROR && console.error("Shield shape is not defined on culture level", pack.cultures[culture]);
return "heater";
};

View file

@ -1938,7 +1938,9 @@ window.COArenderer = (function () {
g.setAttribute("id", charge + "_" + id);
return g.outerHTML;
})
.catch(err => console.error(err));
.catch(err => {
ERROR && console.error(err);
});
return fetched;
}

209
modules/export-json.js Normal file
View file

@ -0,0 +1,209 @@
function exportToJson(type) {
if (customization) return tip("Map cannot be saved when edit mode is active, please exit the mode and retry", false, "error");
closeDialogs("#alert");
const typeMap = {
Full: getFullDataJson,
Minimal: getMinimalDataJson,
PackCells: getPackCellsDataJson,
GridCells: getGridCellsDataJson,
};
const mapData = typeMap[type]();
const blob = new Blob([mapData], {type: "application/json"});
const URL = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.download = getFileName(type) + ".json";
link.href = URL;
link.click();
tip(`${link.download} is saved. Open "Downloads" screen (CTRL + J) to check`, true, "success", 7000);
window.URL.revokeObjectURL(URL);
}
function getMapInfo() {
const info = {
version,
description: "Azgaar's Fantasy Map Generator output: azgaar.github.io/Fantasy-map-generator",
exportedAt: new Date().toISOString(),
mapName: mapName.value,
seed,
mapId
};
return info;
}
function getSettings() {
const settings = {
distanceUnit: distanceUnitInput.value,
distanceScale: distanceScaleInput.value,
areaUnit: areaUnit.value,
heightUnit: heightUnit.value,
heightExponent: heightExponentInput.value,
temperatureScale: temperatureScale.value,
barSize: barSizeInput.value,
barLabel: barLabel.value,
barBackOpacity: barBackOpacity.value,
barBackColor: barBackColor.value,
barPosX: barPosX.value,
barPosY: barPosY.value,
populationRate: populationRate,
urbanization: urbanization,
mapSize: mapSizeOutput.value,
latitudeO: latitudeOutput.value,
temperatureEquator: temperatureEquatorOutput.value,
temperaturePole: temperaturePoleOutput.value,
prec: precOutput.value,
options: options,
mapName: mapName.value,
hideLabels: hideLabels.checked,
stylePreset: stylePreset.value,
rescaleLabels: rescaleLabels.checked,
urbanDensity: urbanDensity
};
return settings;
}
function getPackCellsData() {
const cellConverted = {
i: Array.from(pack.cells.i),
v: pack.cells.v,
c: pack.cells.c,
p: pack.cells.p,
g: Array.from(pack.cells.g),
h: Array.from(pack.cells.h),
area: Array.from(pack.cells.area),
f: Array.from(pack.cells.f),
t: Array.from(pack.cells.t),
haven: Array.from(pack.cells.haven),
harbor: Array.from(pack.cells.harbor),
fl: Array.from(pack.cells.fl),
r: Array.from(pack.cells.r),
conf: Array.from(pack.cells.conf),
biome: Array.from(pack.cells.biome),
s: Array.from(pack.cells.s),
pop: Array.from(pack.cells.pop),
culture: Array.from(pack.cells.culture),
burg: Array.from(pack.cells.burg),
road: Array.from(pack.cells.road),
crossroad: Array.from(pack.cells.crossroad),
state: Array.from(pack.cells.state),
religion: Array.from(pack.cells.religion),
province: Array.from(pack.cells.province)
};
const cellObjArr = [];
{
cellConverted.i.forEach(value => {
const cellobj = {
i: value,
v: cellConverted.v[value],
c: cellConverted.c[value],
p: cellConverted.p[value],
g: cellConverted.g[value],
h: cellConverted.h[value],
area: cellConverted.area[value],
f: cellConverted.f[value],
t: cellConverted.t[value],
haven: cellConverted.haven[value],
harbor: cellConverted.harbor[value],
fl: cellConverted.fl[value],
r: cellConverted.r[value],
conf: cellConverted.conf[value],
biome: cellConverted.biome[value],
s: cellConverted.s[value],
pop: cellConverted.pop[value],
culture: cellConverted.culture[value],
burg: cellConverted.burg[value],
road: cellConverted.road[value],
crossroad: cellConverted.crossroad[value],
state: cellConverted.state[value],
religion: cellConverted.religion[value],
province: cellConverted.province[value]
};
cellObjArr.push(cellobj);
});
}
const cellsData = {
cells: cellObjArr,
features: pack.features,
cultures: pack.cultures,
burgs: pack.burgs,
states: pack.states,
provinces: pack.provinces,
religions: pack.religions,
rivers: pack.rivers,
markers: pack.markers
};
return cellsData;
}
//data only containing graphical appearance
function getGridCellsData(){
const gridData = {
spacing: grid.spacing,
cellsY: grid.cellsY,
cellsX: grid.cellsX,
points: grid.points,
boundary: grid.boundary
}
return gridData
}
function getFullDataJson() {
TIME && console.time("getFullDataJson");
const info = getMapInfo();
const settings = getSettings();
const cells = getPackCellsData();
const exportData = {info, settings, coords: mapCoordinates, cells, biomes: biomesData, notes, nameBases};
TIME && console.timeEnd("getFullDataJson");
return JSON.stringify(exportData);
}
// data excluding cells
function getMinimalDataJson() {
TIME && console.time("getMinimalDataJson");
const info = getMapInfo();
const settings = getSettings();
const packData = {
features: pack.features,
cultures: pack.cultures,
burgs: pack.burgs,
states: pack.states,
provinces: pack.provinces,
religions: pack.religions,
rivers: pack.rivers,
markers: pack.markers
};
const exportData = {info, settings, coords: mapCoordinates, pack: packData, biomes: biomesData, notes, nameBases};
TIME && console.timeEnd("getMinimalDataJson");
return JSON.stringify(exportData);
}
function getPackCellsDataJson() {
TIME && console.time("getCellsDataJson");
const info = getMapInfo();
const cells = getPackCellsData();
const exportData = {info, cells};
TIME && console.timeEnd("getCellsDataJson");
return JSON.stringify(exportData);
}
function getGridCellsDataJson() {
TIME && console.time("getGridCellsDataJson");
const info = getMapInfo();
const gridCells = getGridCellsData()
const exportData = {info,gridCells};
TIME && console.log("getGridCellsDataJson");
return JSON.stringify(exportData);
}

View file

@ -262,19 +262,19 @@ async function getMapURL(type, options = {}) {
if (pattern) cloneDefs.appendChild(pattern.cloneNode(true));
}
if (!cloneEl.getElementById("hatching").children.length) cloneEl.getElementById("hatching")?.remove(); // remove unused hatching group
if (!cloneEl.getElementById("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
// add armies style
if (cloneEl.getElementById("armies"))
if (cloneEl.getElementById("armies")) {
cloneEl.insertAdjacentHTML(
"afterbegin",
"<style>#armies text {stroke: none; fill: #fff; text-shadow: 0 0 4px #000; dominant-baseline: central; text-anchor: middle; font-family: Helvetica; fill-opacity: 1;}#armies text.regimentIcon {font-size: .8em;}</style>"
);
}
// add xlink: for href to support svg1.1
// add xlink: for href to support svg 1.1
if (type === "svg") {
cloneEl.querySelectorAll("[href]").forEach(el => {
const href = el.getAttribute("href");
@ -283,6 +283,16 @@ async function getMapURL(type, options = {}) {
});
}
// add hatchings
const hatchingUsers = cloneEl.querySelectorAll(`[fill^='url(#hatch']`);
const hatchingFills = unique(Array.from(hatchingUsers).map(el => el.getAttribute("fill")));
const hatchingIds = hatchingFills.map(fill => fill.slice(5, -1));
for (const hatchingId of hatchingIds) {
const hatching = svgDefs.getElementById(hatchingId);
if (hatching) cloneDefs.appendChild(hatching.cloneNode(true));
}
// load fonts
const usedFonts = getUsedFonts(cloneEl);
const fontsToLoad = usedFonts.filter(font => font.src);
if (fontsToLoad.length) {
@ -405,8 +415,8 @@ function saveGeoJSON_Cells() {
json.features.push(feature);
});
const name = getFileName("Cells") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const fileName = getFileName("Cells") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
}
function saveGeoJSON_Routes() {
@ -421,30 +431,25 @@ function saveGeoJSON_Routes() {
json.features.push(feature);
});
const name = getFileName("Routes") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const fileName = getFileName("Routes") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
}
function saveGeoJSON_Rivers() {
const json = {type: "FeatureCollection", features: []};
rivers.selectAll("path").each(function () {
const coordinates = getRiverPoints(this);
const id = this.id;
const width = +this.dataset.increment;
const increment = +this.dataset.increment;
const river = pack.rivers.find(r => r.i === +id.slice(5));
const name = river ? river.name : "";
const type = river ? river.type : "";
const i = river ? river.i : "";
const basin = river ? river.basin : "";
const river = pack.rivers.find(r => r.i === +this.id.slice(5));
if (!river) return;
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties: {id, i, basin, name, type, width, increment}};
const coordinates = getRiverPoints(this);
const properties = {...river, id: this.id};
const feature = {type: "Feature", geometry: {type: "LineString", coordinates}, properties};
json.features.push(feature);
});
const name = getFileName("Rivers") + ".geojson";
downloadFile(JSON.stringify(json), name, "application/json");
const fileName = getFileName("Rivers") + ".geojson";
downloadFile(JSON.stringify(json), fileName, "application/json");
}
function saveGeoJSON_Markers() {

View file

@ -14,12 +14,14 @@ const fonts = [
{
family: "Almendra SC",
src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Amatic SC",
src: "url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Architects Daughter",
@ -34,7 +36,8 @@ const fonts = [
{
family: "Caesar Dressing",
src: "url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Cinzel",
@ -49,7 +52,8 @@ const fonts = [
{
family: "Fredericka the Great",
src: "url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Gloria Hallelujah",
@ -74,12 +78,14 @@ const fonts = [
{
family: "MedievalSharp",
src: "url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Metamorphous",
src: "url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Montez",
@ -89,7 +95,8 @@ const fonts = [
{
family: "Nova Script",
src: "url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Orbitron",
@ -109,12 +116,14 @@ const fonts = [
{
family: "Uncial Antiqua",
src: "url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Underdog",
src: "url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)",
unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
unicodeRange:
"U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
},
{
family: "Yellowtail",
@ -243,7 +252,7 @@ async function addGoogleFont(family) {
})
.catch(err => {
tip(`Failed to load Google font ${family}`, true, "error", 4000);
console.error(err);
ERROR && console.error(err);
});
}

View file

@ -145,7 +145,7 @@ function parseLoadedResult(result) {
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
return [mapData, mapVersion];
} catch (error) {
console.error(error);
ERROR && console.error(error);
return [null, null];
}
}
@ -190,7 +190,6 @@ function parseLoadedData(data) {
if (customizationMenu.offsetParent) styleTab.click();
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
void (function parseParameters() {
const params = data[0].split("|");
@ -426,16 +425,16 @@ function parseLoadedData(data) {
void (function resolveVersionConflicts() {
const version = parseFloat(data[0].split("|")[0]);
if (version < 0.9) {
// 0.9 has additional relief icons to be included into older maps
// v0.9 has additional relief icons to be included into older maps
document.getElementById("defs-relief").innerHTML = reliefIcons;
}
if (version < 1) {
// 1.0 adds a new religions layer
// v1.0 adds a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate();
// 1.0 adds a legend box
// v1.0 adds a legend box
legend = svg.append("g").attr("id", "legend");
legend
.attr("font-family", "Almendra SC")
@ -448,7 +447,7 @@ function parseLoadedData(data) {
.attr("stroke-dasharray", "0 4 10 4")
.attr("stroke-linecap", "round");
// 1.0 separated drawBorders fron drawStates()
// v1.0 separated drawBorders fron drawStates()
stateBorders = borders.append("g").attr("id", "stateBorders");
provinceBorders = borders.append("g").attr("id", "provinceBorders");
borders
@ -461,7 +460,7 @@ function parseLoadedData(data) {
stateBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 1).attr("stroke-dasharray", "2").attr("stroke-linecap", "butt");
provinceBorders.attr("opacity", 0.8).attr("stroke", "#56566d").attr("stroke-width", 0.5).attr("stroke-dasharray", "1").attr("stroke-linecap", "butt");
// 1.0 adds state relations, provinces, forms and full names
// v1.0 adds state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
BurgsAndStates.collectStatistics();
BurgsAndStates.generateCampaigns();
@ -473,10 +472,7 @@ function parseLoadedData(data) {
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
// 1.0 adds hatching
document.getElementsByTagName("defs")[0].appendChild(hatching);
// 1.0 adds zones layer
// v1.0 adds zones layer
zones = viewbox.insert("g", "#borders").attr("id", "zones").attr("display", "none");
zones.attr("opacity", 0.6).attr("stroke", null).attr("stroke-width", 0).attr("stroke-dasharray", null).attr("stroke-linecap", "butt");
addZones();
@ -485,35 +481,35 @@ function parseLoadedData(data) {
turnButtonOn("toggleMarkers");
}
// 1.0 add fogging layer (state focus)
// v1.0 add fogging layer (state focus)
fogging = viewbox.insert("g", "#ruler").attr("id", "fogging-cont").attr("mask", "url(#fog)").append("g").attr("id", "fogging").style("display", "none");
fogging.append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%");
defs.append("mask").attr("id", "fog").append("rect").attr("x", 0).attr("y", 0).attr("width", "100%").attr("height", "100%").attr("fill", "white");
// 1.0 changes states opacity bask to regions level
// v1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null);
}
// 1.0 changed labels to multi-lined
// v1.0 changed labels to multi-lined
labels.selectAll("textPath").each(function () {
const text = this.textContent;
const shift = this.getComputedTextLength() / -1.5;
this.innerHTML = `<tspan x="${shift}">${text}</tspan>`;
});
// 1.0 added new biome - Wetland
// v1.0 added new biome - Wetland
biomesData.name.push("Wetland");
biomesData.color.push("#0b9131");
biomesData.habitability.push(12);
}
if (version < 1.1) {
// v 1.0 initial code had a bug with religion layer id
// v1.0 initial code had a bug with religion layer id
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// v 1.0 initially has Sympathy status then relaced with Friendly
// v1.0 initially has Sympathy status then relaced with Friendly
for (const s of pack.states) {
if (!s.diplomacy) continue;
s.diplomacy = s.diplomacy.map(r => (r === "Sympathy" ? "Friendly" : r));
@ -522,7 +518,7 @@ function parseLoadedData(data) {
// labels should be toggled via style attribute, so remove display attribute
labels.attr("display", null);
// v 1.0 added religions heirarchy tree
// v1.0 added religions heirarchy tree
if (pack.religions[1] && !pack.religions[1].code) {
pack.religions
.filter(r => r.i)
@ -542,7 +538,7 @@ function parseLoadedData(data) {
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null);
}
// v 1.1 added new lake and coast groups
// v1.1 added new lake and coast groups
if (!document.getElementById("sinkhole")) {
lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen");
@ -557,7 +553,7 @@ function parseLoadedData(data) {
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
}
// v 1.1 features stores more data
// v1.1 features stores more data
defs.select("#land").selectAll("path").remove();
defs.select("#water").selectAll("path").remove();
coastline.selectAll("path").remove();
@ -566,13 +562,13 @@ function parseLoadedData(data) {
}
if (version < 1.11) {
// v 1.11 added new attributes
// v1.11 added new attributes
terrs.attr("scheme", "bright").attr("terracing", 0).attr("skip", 5).attr("relax", 0).attr("curve", 0);
svg.select("#oceanic > *").attr("id", "oceanicPattern");
oceanLayers.attr("layers", "-6,-3,-1");
gridOverlay.attr("type", "pointyHex").attr("size", 10);
// v 1.11 added cultures heirarchy tree
// v1.11 added cultures heirarchy tree
if (pack.cultures[1] && !pack.cultures[1].code) {
pack.cultures
.filter(c => c.i)
@ -582,17 +578,17 @@ function parseLoadedData(data) {
});
}
// v 1.11 had an issue with fogging being displayed on load
// v1.11 had an issue with fogging being displayed on load
unfog();
// v 1.2 added new terrain attributes
// v1.2 added new terrain attributes
if (!terrain.attr("set")) terrain.attr("set", "simple");
if (!terrain.attr("size")) terrain.attr("size", 1);
if (!terrain.attr("density")) terrain.attr("density", 0.4);
}
if (version < 1.21) {
// v 1.11 replaced "display" attribute by "display" style
// v1.11 replaced "display" attribute by "display" style
viewbox.selectAll("g").each(function () {
if (this.hasAttribute("display")) {
this.removeAttribute("display");
@ -600,7 +596,7 @@ function parseLoadedData(data) {
}
});
// v 1.21 added rivers data to pack
// v1.21 added rivers data to pack
pack.rivers = []; // rivers data
rivers.selectAll("path").each(function () {
const i = +this.id.slice(5);
@ -616,12 +612,12 @@ function parseLoadedData(data) {
}
if (version < 1.22) {
// v 1.22 changed state neighbors from Set object to array
// v1.22 changed state neighbors from Set object to array
BurgsAndStates.collectStatistics();
}
if (version < 1.3) {
// v 1.3 added global options object
// v1.3 added global options object
const winds = options.slice(); // previostly wind was saved in settings[19]
const year = rand(100, 2000);
const era = Names.getBaseShort(P(0.7) ? 1 : rand(nameBases.length)) + " Era";
@ -629,10 +625,10 @@ function parseLoadedData(data) {
const military = Military.getDefaultOptions();
options = {winds, year, era, eraShort, military};
// v 1.3 added campaings data for all states
// v1.3 added campaings data for all states
BurgsAndStates.generateCampaigns();
// v 1.3 added militry layer
// v1.3 added militry layer
armies = viewbox.insert("g", "#icons").attr("id", "armies");
armies.attr("opacity", 1).attr("fill-opacity", 1).attr("font-size", 6).attr("box-size", 3).attr("stroke", "#000").attr("stroke-width", 0.3);
turnButtonOn("toggleMilitary");
@ -640,18 +636,18 @@ function parseLoadedData(data) {
}
if (version < 1.4) {
// v 1.35 added dry lakes
// v1.35 added dry lakes
if (!lakes.select("#dry").size()) {
lakes.append("g").attr("id", "dry");
lakes.select("#dry").attr("opacity", 1).attr("fill", "#c9bfa7").attr("stroke", "#8e816f").attr("stroke-width", 0.7).attr("filter", null);
}
// v 1.4 added ice layer
// v1.4 added ice layer
ice = viewbox.insert("g", "#coastline").attr("id", "ice").style("display", "none");
ice.attr("opacity", null).attr("fill", "#e8f0f6").attr("stroke", "#e8f0f6").attr("stroke-width", 1).attr("filter", "url(#dropShadow05)");
drawIce();
// v 1.4 added icon and power attributes for units
// v1.4 added icon and power attributes for units
for (const unit of options.military) {
if (!unit.icon) unit.icon = getUnitIcon(unit.type);
if (!unit.power) unit.power = unit.crew;
@ -668,7 +664,7 @@ function parseLoadedData(data) {
else return "⚔️";
}
// 1.4 added state reference for regiments
// v1.4 added state reference for regiments
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
}
@ -679,19 +675,19 @@ function parseLoadedData(data) {
localStorage.removeItem("styleAncient");
localStorage.removeItem("styleMonochrome");
// v 1.5 cultures has shield attribute
// v1.5 cultures has shield attribute
pack.cultures.forEach(culture => {
if (culture.removed) return;
culture.shield = Cultures.getRandomShield();
});
// v 1.5 added burg type value
// v1.5 added burg type value
pack.burgs.forEach(burg => {
if (!burg.i || burg.removed) return;
burg.type = BurgsAndStates.getType(burg.cell, burg.port);
});
// v 1.5 added emblems
// v1.5 added emblems
defs.append("g").attr("id", "defs-emblems");
emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none");
emblems.append("g").attr("id", "burgEmblems");
@ -700,7 +696,7 @@ function parseLoadedData(data) {
regenerateEmblems();
toggleEmblems();
// v 1.5 changed releif icons data
// v1.5 changed releif icons data
terrain.selectAll("use").each(function () {
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
this.removeAttribute("xlink:href");
@ -711,7 +707,7 @@ function parseLoadedData(data) {
}
if (version < 1.6) {
// v 1.6 changed rivers data
// v1.6 changed rivers data
for (const river of pack.rivers) {
const el = document.getElementById("river" + river.i);
if (el) {
@ -726,7 +722,7 @@ function parseLoadedData(data) {
}
}
// v 1.6 changed lakes data
// v1.6 changed lakes data
for (const f of pack.features) {
if (f.type !== "lake") continue;
if (f.evaporation) continue;
@ -743,7 +739,7 @@ function parseLoadedData(data) {
}
if (version < 1.61) {
// v 1.61 changed rulers data
// v1.61 changed rulers data
ruler.style("display", null);
rulers = new Rulers();
@ -797,12 +793,12 @@ function parseLoadedData(data) {
}
if (version < 1.62) {
// v 1.62 changed grid data
// v1.62 changed grid data
gridOverlay.attr("size", null);
}
if (version < 1.63) {
// v.1.63 changed ocean pattern opacity element
// v1.63 changed ocean pattern opacity element
const oceanPattern = document.getElementById("oceanPattern");
if (oceanPattern) oceanPattern.removeAttribute("opacity");
const oceanicPattern = document.getElementById("oceanicPattern");
@ -816,7 +812,7 @@ function parseLoadedData(data) {
}
if (version < 1.64) {
// v.1.64 change states style
// v1.64 change states style
const opacity = regions.attr("opacity");
const filter = regions.attr("filter");
statesBody.attr("opacity", opacity).attr("filter", filter);
@ -825,9 +821,10 @@ function parseLoadedData(data) {
}
if (version < 1.65) {
// v 1.65 changed rivers data
// v1.65 changed rivers data
d3.select("#rivers").attr("style", null); // remove style to unhide layer
const {cells, rivers} = pack;
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
for (const river of rivers) {
const node = document.getElementById("river" + river.i);
@ -856,7 +853,7 @@ function parseLoadedData(data) {
river.points = riverPoints;
}
river.widthFactor = 1;
river.widthFactor = defaultWidthFactor;
cells.i.forEach(i => {
const riverInWater = cells.r[i] && cells.h[i] < 20;
@ -872,7 +869,7 @@ function parseLoadedData(data) {
}
if (version < 1.7) {
// v 1.7 changed markers data
// v1.7 changed markers data
const defs = document.getElementById("defs-markers");
const markersGroup = document.getElementById("markers");
@ -928,6 +925,22 @@ function parseLoadedData(data) {
if (layerIsOn("markers")) drawMarkers();
}
}
if (version < 1.72) {
// v1.72 renamed custom style presets
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith("style"));
storedStyles.forEach(styleName => {
const style = localStorage.getItem(styleName);
const newStyleName = styleName.replace(/^style/, customPresetPrefix);
localStorage.setItem(newStyleName, style);
localStorage.removeItem(styleName);
});
}
if (version < 1.73) {
// v1.73 moved the hatching patterns out of the user's SVG
document.getElementById("hatching")?.remove();
}
})();
void (function checkDataIntegrity() {
@ -1013,6 +1026,31 @@ function parseLoadedData(data) {
ERROR && console.error("Data Integrity Check. Province", p.i, "is linked to removed state", p.state);
p.removed = true; // remove incorrect province
});
{
const markerIds = [];
let nextId = last(pack.markers)?.i + 1 || 0;
pack.markers.forEach(marker => {
if (markerIds[marker.i]) {
ERROR && console.error("Data Integrity Check. Marker", marker.i, "has non-unique id. Changing to", nextId);
const domElements = document.querySelectorAll("#marker" + marker.i);
if (domElements[1]) domElements[1].id = "marker" + nextId; // rename 2nd dom element
const noteElements = notes.filter(note => note.id === "marker" + marker.i);
if (noteElements[1]) noteElements[1].id = "marker" + nextId; // rename 2nd note
marker.i = nextId;
nextId += 1;
} else {
markerIds[marker.i] = true;
}
});
// sort markers by index
pack.markers.sort((a, b) => a.i - b.i);
}
})();
changeMapSize();

View file

@ -97,7 +97,7 @@ window.Markers = (function () {
}
function addMarker({cell, type, icon, dx, dy, px}) {
const i = pack.markers.length;
const i = last(pack.markers)?.i + 1 || 0;
const [x, y] = getMarkerCoordinates(cell);
const marker = {i, icon, type, x, y, cell};
if (dx) marker.dx = dx;

View file

@ -30,63 +30,59 @@ window.Religions = (function () {
const base = {
number: ["One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Eleven", "Twelve"],
being: [
"God",
"Goddess",
"Lord",
"Lady",
"Deity",
"Creator",
"Maker",
"Overlord",
"Ruler",
"Chief",
"Master",
"Spirit",
"Ancestor",
"Ancient",
"Brother",
"Chief",
"Council",
"Creator",
"Deity",
"Elder",
"Father",
"Forebear",
"Forefather",
"Mother",
"Brother",
"Sister",
"Elder",
"Numen",
"Ancient",
"Virgin",
"Giver",
"Council",
"God",
"Goddess",
"Guardian",
"Reaper"
"Lady",
"Lord",
"Maker",
"Master",
"Mother",
"Numen",
"Overlord",
"Reaper",
"Ruler",
"Sister",
"Spirit",
"Virgin"
],
animal: [
"Dragon",
"Wyvern",
"Phoenix",
"Unicorn",
"Sphinx",
"Centaur",
"Pegasus",
"Kraken",
"Basilisk",
"Chimera",
"Cyclope",
"Antelope",
"Ape",
"Badger",
"Basilisk",
"Bear",
"Beaver",
"Bison",
"Boar",
"Buffalo",
"Camel",
"Cat",
"Centaur",
"Chimera",
"Cobra",
"Crane",
"Crocodile",
"Crow",
"Cyclope",
"Deer",
"Dog",
"Dragon",
"Eagle",
"Elk",
"Falcon",
"Fox",
"Goat",
"Goose",
@ -94,10 +90,12 @@ window.Religions = (function () {
"Hawk",
"Heron",
"Horse",
"Hound",
"Hyena",
"Ibis",
"Jackal",
"Jaguar",
"Kraken",
"Lark",
"Leopard",
"Lion",
@ -107,177 +105,179 @@ window.Religions = (function () {
"Mule",
"Narwhal",
"Owl",
"Ox",
"Panther",
"Pegasus",
"Phoenix",
"Rat",
"Raven",
"Rook",
"Scorpion",
"Serpent",
"Shark",
"Sheep",
"Snake",
"Sphinx",
"Spider",
"Swan",
"Tiger",
"Turtle",
"Unicorn",
"Viper",
"Vulture",
"Walrus",
"Wolf",
"Wolverine",
"Worm",
"Camel",
"Falcon",
"Hound",
"Ox",
"Serpent"
"Wyvern"
],
adjective: [
"New",
"Good",
"High",
"Old",
"Great",
"Big",
"Young",
"Major",
"Strong",
"Happy",
"Last",
"Main",
"Huge",
"Far",
"Beautiful",
"Wild",
"Fair",
"Prime",
"Crazy",
"Ancient",
"Proud",
"Secret",
"Lucky",
"Sad",
"Silent",
"Latter",
"Severe",
"Fat",
"Holy",
"Pure",
"Aggressive",
"Honest",
"Giant",
"Mad",
"Pregnant",
"Distant",
"Lost",
"Broken",
"Almighty",
"Ancient",
"Beautiful",
"Benevolent",
"Big",
"Blind",
"Friendly",
"Unknown",
"Sleeping",
"Slumbering",
"Loud",
"Hungry",
"Wise",
"Worried",
"Sacred",
"Magical",
"Superior",
"Patient",
"Blond",
"Bloody",
"Brave",
"Broken",
"Brutal",
"Burning",
"Calm",
"Cheerful",
"Crazy",
"Cruel",
"Dead",
"Deadly",
"Peaceful",
"Grateful",
"Frozen",
"Evil",
"Scary",
"Burning",
"Divine",
"Bloody",
"Dying",
"Waking",
"Brutal",
"Unhappy",
"Calm",
"Cruel",
"Favorable",
"Blond",
"Explicit",
"Disturbing",
"Devastating",
"Brave",
"Sunny",
"Troubled",
"Flying",
"Sustainable",
"Marine",
"Fatal",
"Inherent",
"Selected",
"Naval",
"Cheerful",
"Almighty",
"Benevolent",
"Distant",
"Disturbing",
"Divine",
"Dying",
"Eternal",
"Evil",
"Explicit",
"Fair",
"Far",
"Fat",
"Fatal",
"Favorable",
"Flying",
"Friendly",
"Frozen",
"Giant",
"Good",
"Grateful",
"Great",
"Happy",
"High",
"Holy",
"Honest",
"Huge",
"Hungry",
"Immutable",
"Infallible"
"Infallible",
"Inherent",
"Last",
"Latter",
"Lost",
"Loud",
"Lucky",
"Mad",
"Magical",
"Main",
"Major",
"Marine",
"Naval",
"New",
"Old",
"Patient",
"Peaceful",
"Pregnant",
"Prime",
"Proud",
"Pure",
"Sacred",
"Sad",
"Scary",
"Secret",
"Selected",
"Severe",
"Silent",
"Sleeping",
"Slumbering",
"Strong",
"Sunny",
"Superior",
"Sustainable",
"Troubled",
"Unhappy",
"Unknown",
"Waking",
"Wild",
"Wise",
"Worried",
"Young"
],
genitive: [
"Day",
"Life",
"Death",
"Night",
"Home",
"Fog",
"Snow",
"Winter",
"Summer",
"Cold",
"Springs",
"Gates",
"Nature",
"Thunder",
"Lightning",
"War",
"Ice",
"Frost",
"Fire",
"Day",
"Death",
"Doom",
"Fate",
"Pain",
"Fire",
"Fog",
"Frost",
"Gates",
"Heaven",
"Home",
"Ice",
"Justice",
"Life",
"Light",
"Lightning",
"Love",
"Nature",
"Night",
"Pain",
"Snow",
"Springs",
"Summer",
"Thunder",
"Time",
"Victory"
"Victory",
"War",
"Winter"
],
theGenitive: [
"World",
"Word",
"South",
"West",
"North",
"East",
"Sun",
"Moon",
"Peak",
"Fall",
"Dawn",
"Eclipse",
"Abyss",
"Blood",
"Tree",
"Dawn",
"Earth",
"East",
"Eclipse",
"Fall",
"Harvest",
"Moon",
"North",
"Peak",
"Rainbow",
"Sea",
"Sky",
"South",
"Stars",
"Storm",
"Sun",
"Tree",
"Underworld",
"Wild"
"West",
"Wild",
"Word",
"World"
],
color: ["Dark", "Light", "Bright", "Golden", "White", "Black", "Red", "Pink", "Purple", "Blue", "Green", "Yellow", "Amber", "Orange", "Brown", "Grey"]
color: ["Amber", "Black", "Blue", "Bright", "Brown", "Dark", "Golden", "Green", "Grey", "Light", "Orange", "Pink", "Purple", "Red", "White", "Yellow"]
};
const forms = {
@ -308,10 +308,10 @@ window.Religions = (function () {
Monotheism: {Religion: 1, Church: 1},
"Non-theism": {Beliefs: 3, Spirits: 1},
Cult: {Cult: 4, Sect: 4, Worship: 1, Orden: 1, Coterie: 1, Arcanum: 1},
"Dark Cult": {Cult: 2, Sect: 2, Occultism: 1, Idols: 1, Coven: 1, Circle: 1, Blasphemy: 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},
Heresy: {Heresy: 3, Sect: 2, Schism: 1, Dissenters: 1, Circle: 1, Brotherhood: 1, Society: 1, Iconoclasm: 1, Dissent: 1, Apostates: 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 () {

View file

@ -23,22 +23,27 @@ window.Rivers = (function () {
resolveDepressions(h);
drainWater();
defineRivers();
calculateConfluenceFlux();
Lakes.cleanupLakeData();
if (allowErosion) cells.h = Uint8Array.from(h); // apply changed heights as basic one
if (allowErosion) {
cells.h = Uint8Array.from(h); // apply gradient
downcutRivers(); // downcut river beds
}
TIME && console.timeEnd("generateRivers");
function drainWater() {
const MIN_FLUX_TO_FORM_RIVER = 30;
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const prec = grid.cells.prec;
const area = pack.cells.area;
const land = cells.i.filter(i => h[i] >= 20).sort((a, b) => h[b] - h[a]);
const lakeOutCells = Lakes.setClimateData(h);
land.forEach(function (i) {
cells.fl[i] += (prec[cells.g[i]] * area[i]) / 100; // add flux from precipitation
cells.fl[i] += prec[cells.g[i]] / cellsNumberModifier; // add flux from precipitation
// create lake outlet if lake is not in deep depression and flux > evaporation
const lakes = lakeOutCells[i] ? features.filter(feature => i === feature.outCell && feature.flux > feature.evaporation) : [];
@ -90,6 +95,15 @@ window.Rivers = (function () {
// cells is depressed
if (h[i] <= h[min]) return;
// debug
// .append("line")
// .attr("x1", pack.cells.p[i][0])
// .attr("y1", pack.cells.p[i][1])
// .attr("x2", pack.cells.p[min][0])
// .attr("y2", pack.cells.p[min][1])
// .attr("stroke", "#333")
// .attr("stroke-width", 0.2);
if (cells.fl[i] < MIN_FLUX_TO_FORM_RIVER) {
// flux is too small to operate as a river
if (h[min] >= 20) cells.fl[min] += cells.fl[i];
@ -149,6 +163,9 @@ window.Rivers = (function () {
cells.conf = new Uint16Array(cells.i.length);
pack.rivers = [];
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const mainStemWidthFactor = defaultWidthFactor * 1.2;
for (const key in riversData) {
const riverCells = riversData[key];
if (riverCells.length < 3) continue; // exclude tiny rivers
@ -166,7 +183,7 @@ window.Rivers = (function () {
const mouth = riverCells[riverCells.length - 2];
const parent = riverParents[key] || 0;
const widthFactor = !parent || parent === riverId ? 1.2 : 1;
const widthFactor = !parent || parent === riverId ? mainStemWidthFactor : defaultWidthFactor;
const meanderedPoints = addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second
const length = getApproximateLength(meanderedPoints);
@ -176,6 +193,22 @@ window.Rivers = (function () {
}
}
function downcutRivers() {
const MAX_DOWNCUT = 5;
for (const i of pack.cells.i) {
if (cells.h[i] < 35) continue; // don't donwcut lowlands
if (!cells.fl[i]) continue;
const higherCells = cells.c[i].filter(c => cells.h[c] > cells.h[i]);
const higherFlux = higherCells.reduce((acc, c) => acc + cells.fl[c], 0) / higherCells.length;
if (!higherFlux) continue;
const downcut = Math.floor(cells.fl[i] / higherFlux);
if (downcut) cells.h[i] -= Math.min(downcut, MAX_DOWNCUT);
}
}
function calculateConfluenceFlux() {
for (const i of cells.i) {
if (!cells.conf[i]) continue;
@ -344,14 +377,14 @@ window.Rivers = (function () {
const LENGTH_PROGRESSION = [1, 1, 2, 3, 5, 8, 13, 21, 34].map(n => n / LENGTH_FACTOR);
const MAX_PROGRESSION = last(LENGTH_PROGRESSION);
const getOffset = (flux, pointNumber, widthFactor = 1, startingWidth = 0) => {
const getOffset = (flux, pointNumber, widthFactor, startingWidth = 0) => {
const fluxWidth = Math.min(flux ** 0.9 / FLUX_FACTOR, MAX_FLUX_WIDTH);
const lengthWidth = pointNumber * STEP_WIDTH + (LENGTH_PROGRESSION[pointNumber] || MAX_PROGRESSION);
return widthFactor * (lengthWidth + fluxWidth) + startingWidth;
};
// build polygon from a list of points and calculated offset (width)
const getRiverPath = function (points, widthFactor = 1, startingWidth = 0) {
const getRiverPath = function (points, widthFactor, startingWidth = 0) {
const riverPointsLeft = [];
const riverPointsRight = [];
@ -444,5 +477,20 @@ window.Rivers = (function () {
return getBasin(parent);
};
return {generate, alterHeights, resolveDepressions, addMeandering, getRiverPath, specify, getName, getType, getBasin, getWidth, getOffset, getApproximateLength, getRiverPoints, remove};
return {
generate,
alterHeights,
resolveDepressions,
addMeandering,
getRiverPath,
specify,
getName,
getType,
getBasin,
getWidth,
getOffset,
getApproximateLength,
getRiverPoints,
remove
};
})();

View file

@ -145,7 +145,7 @@ async function saveToDropbox() {
await Cloud.providers.dropbox.save(filename, mapData);
tip("Map is saved to your Dropbox", true, "success", 8000);
} catch (msg) {
console.error(msg);
ERROR && console.error(msg);
tip("Cannot save .map to your Dropbox", true, "error", 8000);
}
}

View file

@ -141,8 +141,8 @@ class Battle {
const state = pack.states[regiment.state];
const distance = (Math.hypot(this.y - regiment.by, this.x - regiment.bx) * distanceScaleInput.value) | 0; // distance between regiment and its base
const color = state.color[0] === "#" ? state.color : "#999";
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em">
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em; stroke: #333">
<rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
const body = `<tbody id="battle${state.i}-${regiment.i}">`;
@ -183,7 +183,7 @@ class Battle {
dist = added ? "0 " + distanceUnitInput.value : distance(r);
return `<div ${added ? "class='inactive'" : ""} data-s=${s.i} data-i=${r.i} data-state=${s.name} data-regiment=${r.name}
data-total=${r.a} data-distance=${dist} data-tip="Click to select regiment">
<svg width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
<svg width=".9em" height=".9em" style="margin-bottom:-1px; stroke: #333"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" ></svg>
<div style="width:6em">${s.name.slice(0, 11)}</div>
<div style="width:1.2em">${r.icon}</div>
<div style="width:13em">${r.name.slice(0, 24)}</div>

View file

@ -37,9 +37,9 @@ function editBiomes() {
document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
body.addEventListener("click", function (ev) {
const el = ev.target,
cl = el.classList;
if (cl.contains("fillRect")) biomeChangeColor(el);
const el = ev.target;
const cl = el.classList;
if (el.tagName === "FILL-BOX") biomeChangeColor(el);
else if (cl.contains("icon-info-circled")) openWiki(el);
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
if (customization === 6) selectBiomeOnLineClick(el);
@ -94,9 +94,7 @@ function editBiomes() {
lines += `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability="${b.habitability[i]}"
data-cells=${b.cells[i]} data-area=${area} data-population=${population} data-color=${b.color[i]}>
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
b.color[i]
}" class="fillRect pointer"></svg>
<fill-box fill="${b.color[i]}"></fill-box>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
<span data-tip="Biome habitability percent" class="hide">%</span>
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 class="biomeHabitability hide" value=${
@ -158,15 +156,15 @@ function editBiomes() {
function biomeChangeColor(el) {
const currentFill = el.getAttribute("fill");
const biome = +el.parentNode.parentNode.dataset.id;
const biome = +el.parentNode.dataset.id;
const callback = function (fill) {
el.setAttribute("fill", fill);
biomesData.color[biome] = fill;
const callback = newFill => {
el.fill = newFill;
biomesData.color[biome] = newFill;
biomes
.select("#biome" + biome)
.attr("fill", fill)
.attr("stroke", fill);
.attr("fill", newFill)
.attr("stroke", newFill);
};
openPicker(currentFill, callback);
@ -270,7 +268,7 @@ function editBiomes() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const line = `<div class="states biomes" data-id="${i}" data-name="${b.name[i]}" data-habitability=${b.habitability[i]} data-cells=0 data-area=0 data-population=0 data-color=${b.color[i]}>
<svg data-tip="Biomes fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${b.color[i]}" class="fillRect pointer"></svg>
<fill-box fill="${b.color[i]}"></fill-box>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false">
<span data-tip="Biome habitability percent" class="hide">%</span>
<input data-tip="Biome habitability percent. Click and set new value to change" type="number" min=0 max=9999 step=1 class="biomeHabitability hide" value=${b.habitability[i]}>

View file

@ -37,6 +37,7 @@ function editBurg(id) {
burgBody.querySelectorAll(".burgFeature").forEach(el => el.addEventListener("click", toggleFeature));
document.getElementById("mfcgBurgSeed").addEventListener("change", changeSeed);
document.getElementById("regenerateMFCGBurgSeed").addEventListener("click", randomizeSeed);
document.getElementById("addCustomMFCGBurgLink").addEventListener("click", addCustomMfcgLink);
document.getElementById("burgStyleShow").addEventListener("click", showStyleSection);
document.getElementById("burgStyleHide").addEventListener("click", hideStyleSection);
@ -51,6 +52,7 @@ function editBurg(id) {
document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
function updateBurgValues() {
const id = +elSelected.attr("data-id");
@ -112,7 +114,13 @@ function editBurg(id) {
if (options.showMFCGMap) {
document.getElementById("mfcgPreviewSection").style.display = "block";
updateMFCGFrame(b);
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
if (b.link) {
document.getElementById("mfcgBurgSeedSection").style.display = "none";
} else {
document.getElementById("mfcgBurgSeedSection").style.display = "inline-block";
document.getElementById("mfcgBurgSeed").value = getBurgSeed(b);
}
} else {
document.getElementById("mfcgPreviewSection").style.display = "none";
}
@ -347,22 +355,25 @@ function editBurg(id) {
function toggleFeature() {
const id = +elSelected.attr("data-id");
const b = pack.burgs[id];
const burg = pack.burgs[id];
const feature = this.dataset.feature;
const turnOn = this.classList.contains("inactive");
if (feature === "port") togglePort(id);
else if (feature === "capital") toggleCapital(id);
else b[feature] = +turnOn;
if (b[feature]) this.classList.remove("inactive");
else if (!b[feature]) this.classList.add("inactive");
else burg[feature] = +turnOn;
if (burg[feature]) this.classList.remove("inactive");
else if (!burg[feature]) this.classList.add("inactive");
if (b.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block";
if (burg.port) document.getElementById("burgEditAnchorStyle").style.display = "inline-block";
else document.getElementById("burgEditAnchorStyle").style.display = "none";
updateMFCGFrame(burg);
}
function toggleBurgLockButton() {
const id = +elSelected.attr("data-id");
toggleBurgLock(id);
const burg = pack.burgs[id];
burg.lock = !burg.lock;
updateBurgLockIcon();
}
@ -426,6 +437,17 @@ function editBurg(id) {
document.getElementById("mfcgBurgSeed").value = burgSeed;
}
function addCustomMfcgLink() {
const id = +elSelected.attr("data-id");
const burg = pack.burgs[id];
const message = "Enter custom link to the burg map. It can be a link to Medieval Fantasy City Generator or other tool. Keep empty to use MFCG seed";
prompt(message, {default: burg.link || "", required: false}, link => {
if (link) burg.link = link;
else delete burg.link;
updateMFCGFrame(burg);
});
}
function openEmblemEdit() {
const id = +elSelected.attr("data-id"),
burg = pack.burgs[id];
@ -522,6 +544,11 @@ function editBurg(id) {
editNotes("burg" + id, name);
}
function showTemperatureGraph() {
const id = elSelected.attr("data-id");
showBurgTemperatureGraph(id);
}
function removeSelectedBurg() {
const id = +elSelected.attr("data-id");
if (pack.burgs[id].capital) {

View file

@ -7,6 +7,7 @@ function overviewBurgs() {
const body = document.getElementById("burgsBody");
updateFilter();
updateLockAllIcon();
burgsOverviewAddLines();
$("#burgsOverview").dialog();
@ -33,6 +34,7 @@ function overviewBurgs() {
document.getElementById("burgsListToLoad").addEventListener("change", function () {
uploadFile(this, importBurgNames);
});
document.getElementById("burgsLockAll").addEventListener("click", toggleLockAll);
document.getElementById("burgsRemoveAll").addEventListener("click", triggerAllBurgsRemove);
document.getElementById("burgsInvertLock").addEventListener("click", invertLock);
@ -87,7 +89,7 @@ function overviewBurgs() {
<input data-tip="Burg name. Click and type to change" class="burgName" value="${b.name}" autocorrect="off" spellcheck="false">
<input data-tip="Burg province" class="burgState" value="${province}" disabled>
<input data-tip="Burg state" class="burgState" value="${state}" disabled>
<select data-tip="Dominant culture. Click to change burg culture (to change cell cultrure use Cultures Editor)" class="stateCulture">${getCultureOptions(
<select data-tip="Dominant culture. Click to change burg culture (to change cell culture use Cultures Editor)" class="stateCulture">${getCultureOptions(
b.culture
)}</select>
<span data-tip="Burg population" class="icon-male"></span>
@ -195,8 +197,11 @@ function overviewBurgs() {
}
function toggleBurgLockStatus() {
const burg = +this.parentNode.dataset.id;
toggleBurgLock(burg);
const burgId = +this.parentNode.dataset.id;
const burg = pack.burgs[burgId];
burg.lock = !burg.lock;
if (this.classList.contains("icon-lock")) {
this.classList.remove("icon-lock");
this.classList.add("icon-lock-open");
@ -478,9 +483,9 @@ function overviewBurgs() {
}
function renameBurgsInBulk() {
const message = `Download burgs list as a text file, make changes and re-upload the file.
alertMessage.innerHTML = `Download burgs list as a text file, make changes and re-upload the file.
Make sure the file is a plain text document with each name on its own line (the dilimiter is CRLF).
If you do not want to change the name, just leave it as is`;
alertMessage.innerHTML = message;
$("#alert").dialog({
title: "Burgs bulk renaming",
@ -562,4 +567,21 @@ function overviewBurgs() {
pack.burgs = pack.burgs.map(burg => ({...burg, lock: !burg.lock}));
burgsOverviewAddLines();
}
function toggleLockAll() {
const activeBurgs = pack.burgs.filter(b => b.i && !b.removed);
const allLocked = activeBurgs.every(burg => burg.lock);
pack.burgs.forEach(burg => {
burg.lock = !allLocked;
});
burgsOverviewAddLines();
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock" : "icon-lock-open";
}
function updateLockAllIcon() {
const allLocked = pack.burgs.every(({lock, i, removed}) => lock || !i || removed);
document.getElementById("burgsLockAll").className = allLocked ? "icon-lock-open" : "icon-lock";
}
}

View file

@ -108,9 +108,7 @@ function editCultures() {
lines += `<div class="states cultures" data-id=${c.i} data-name="${c.name}" data-color="${c.color}" data-cells=${c.cells}
data-area=${area} data-population=${population} data-base=${c.base} data-type=${c.type} data-expansionism=${c.expansionism} data-emblems="${c.shield}">
<svg data-tip="Culture fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px">
<rect x="0" y="0" width="100%" height="100%" fill="${c.color}" class="fillRect pointer">
</svg>
<fill-box fill="${c.color}"></fill-box>
<input data-tip="Culture name. Click and type to change" class="cultureName" value="${c.name}" autocorrect="off" spellcheck="false">
<span data-tip="Regenerate culture name" class="icon-cw hiddenIcon" style="visibility: hidden"></span>
<span data-tip="Cells count" class="icon-check-empty hide"></span>
@ -148,7 +146,7 @@ function editCultures() {
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseenter", ev => cultureHighlightOn(ev)));
body.querySelectorAll("div.cultures").forEach(el => el.addEventListener("mouseleave", ev => cultureHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectCultureOnLineClick));
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", cultureChangeColor));
body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", cultureChangeColor));
body.querySelectorAll("div > input.cultureName").forEach(el => el.addEventListener("input", cultureChangeName));
body.querySelectorAll("div > span.icon-cw").forEach(el => el.addEventListener("click", cultureRegenerateName));
body.querySelectorAll("div > input.statePower").forEach(el => el.addEventListener("input", cultureChangeExpansionism));
@ -248,16 +246,16 @@ function editCultures() {
function cultureChangeColor() {
const el = this;
const currentFill = el.getAttribute("fill");
const culture = +el.parentNode.parentNode.dataset.id;
const culture = +el.parentNode.dataset.id;
const callback = function (fill) {
el.setAttribute("fill", fill);
pack.cultures[culture].color = fill;
const callback = newFill => {
el.fill = newFill;
pack.cultures[culture].color = newFill;
cults
.select("#culture" + culture)
.attr("fill", fill)
.attr("stroke", fill);
debug.select("#cultureCenter" + culture).attr("fill", fill);
.attr("fill", newFill)
.attr("stroke", newFill);
debug.select("#cultureCenter" + culture).attr("fill", newFill);
};
openPicker(currentFill, callback);

View file

@ -1,10 +1,9 @@
"use strict";
function editDiplomacy() {
if (customization) return;
if (pack.states.filter(s => s.i && !s.removed).length < 2) {
tip("There should be at least 2 states to edit the diplomacy", false, "error");
return;
}
if (pack.states.filter(s => s.i && !s.removed).length < 2) return tip("There should be at least 2 states to edit the diplomacy", false, "error");
const body = document.getElementById("diplomacyBodySection");
closeDialogs("#diplomacyEditor, .stable");
if (!layerIsOn("toggleStates")) toggleStates();
@ -14,21 +13,29 @@ function editDiplomacy() {
if (layerIsOn("toggleBiomes")) toggleBiomes();
if (layerIsOn("toggleReligions")) toggleReligions();
const body = document.getElementById("diplomacyBodySection");
const statuses = ["Ally", "Friendly", "Neutral", "Suspicion", "Enemy", "Unknown", "Rival", "Vassal", "Suzerain"];
const description = [" is an ally of ", " is friendly to ", " is neutral to ", " is suspicious of ",
" is at war with ", " does not know about ", " is a rival of ", " is a vassal of ", " is suzerain to "];
const colors = ["#00b300", "#d4f8aa", "#edeee8", "#eeafaa", "#e64b40", "#a9a9a9", "#ad5a1f", "#87CEFA", "#00008B"];
refreshDiplomacyEditor();
const relations = {
Ally: {inText: "is an ally of", color: "#00b300", tip: "Allies formed a defensive pact and protect each other in case of third party aggression"},
Friendly: {inText: "is friendly to", color: "#d4f8aa", tip: "State is friendly to anouther state when they share some common interests"},
Neutral: {inText: "is neutral to", color: "#edeee8", tip: "Neutral means states relations are neither positive nor negative"},
Suspicion: {inText: "is suspicious of", color: "#eeafaa", tip: "Suspicion means state has a cautious distrust of another state"},
Enemy: {inText: "is at war with", color: "#e64b40", tip: "Enemies are states at war with each other"},
Unknown: {inText: "does not know about", color: "#a9a9a9", tip: "Relations are unknown if states do not have enough information about each other"},
Rival: {inText: "is a rival of", color: "#ad5a1f", tip: "Rivalry is a state of competing for dominance in the region"},
Vassal: {inText: "is a vassal of", color: "#87CEFA", tip: "Vassal is a state having obligation to its suzerain"},
Suzerain: {inText: "is suzerain to", color: "#00008B", tip: "Suzerain is a state having some control over its vassals"}
};
tip("Click on a state to see its diplomatic relations", false, "warning");
refreshDiplomacyEditor();
viewbox.style("cursor", "crosshair").on("click", selectStateOnMapClick);
if (modules.editDiplomacy) return;
modules.editDiplomacy = true;
$("#diplomacyEditor").dialog({
title: "Diplomacy Editor", resizable: false, width: fitContent(), close: closeDiplomacyEditor,
title: "Diplomacy Editor",
resizable: false,
width: fitContent(),
close: closeDiplomacyEditor,
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
});
@ -36,10 +43,30 @@ function editDiplomacy() {
document.getElementById("diplomacyEditorRefresh").addEventListener("click", refreshDiplomacyEditor);
document.getElementById("diplomacyEditStyle").addEventListener("click", () => editStyle("regions"));
document.getElementById("diplomacyRegenerate").addEventListener("click", regenerateRelations);
document.getElementById("diplomacyMatrix").addEventListener("click", showRelationsMatrix);
document.getElementById("diplomacyReset").addEventListener("click", resetRelations);
document.getElementById("diplomacyShowMatrix").addEventListener("click", showRelationsMatrix);
document.getElementById("diplomacyHistory").addEventListener("click", showRelationsHistory);
document.getElementById("diplomacyExport").addEventListener("click", downloadDiplomacyData);
document.getElementById("diplomacySelect").addEventListener("mouseup", diplomacyChangeRelations);
body.addEventListener("click", function (ev) {
const el = ev.target;
if (el.parentElement.classList.contains("Self")) return;
if (el.classList.contains("changeRelations")) {
const line = el.parentElement;
const subjectId = +line.dataset.id;
const objectId = +body.querySelector("div.Self").dataset.id;
const currentRelation = line.dataset.relations;
selectRelation(subjectId, objectId, currentRelation);
return;
}
// select state of clicked line
body.querySelector("div.Self").classList.remove("Self");
el.parentElement.classList.add("Self");
refreshDiplomacyEditor();
});
function refreshDiplomacyEditor() {
diplomacyEditorAddLines();
@ -50,33 +77,34 @@ function editDiplomacy() {
function diplomacyEditorAddLines() {
const states = pack.states;
const selectedLine = body.querySelector("div.Self");
const sel = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
const selName = states[sel].fullName;
diplomacySelect.style.display = "none";
const selectedId = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
const selectedName = states[selectedId].name;
COArenderer.trigger("stateCOA"+sel, states[sel].coa);
let lines = `<div class="states Self" data-id=${sel} data-tip="List below shows relations to ${selName}">
<div style="width: max-content">${selName}</div>
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${sel}"></use></svg>
COArenderer.trigger("stateCOA" + selectedId, states[selectedId].coa);
let lines = `<div class="states Self" data-id=${selectedId} data-tip="List below shows relations to ${selectedName}">
<div style="width: max-content">${states[selectedId].fullName}</div>
<svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${selectedId}"></use></svg>
</div>`;
for (const s of states) {
if (!s.i || s.removed || s.i === sel) continue;
const relation = s.diplomacy[sel];
const index = statuses.indexOf(relation);
const color = colors[index];
const tip = s.fullName + description[index] + selName;
const tipSelect = `${tip}. Click to see relations to ${s.name}`;
const tipChange = `${tip}. Click to change relations to ${selName}`;
COArenderer.trigger("stateCOA"+s.i, s.coa);
for (const state of states) {
if (!state.i || state.removed || state.i === selectedId) continue;
const relation = state.diplomacy[selectedId];
const {color, inText} = relations[relation];
lines += `<div class="states" data-id=${s.i} data-name="${s.fullName}" data-relations="${relation}">
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
<div data-tip="${tipSelect}" style="width:12em">${s.fullName}</div>
<svg data-tip="${tipChange}" width=".9em" height=".9em" style="margin-bottom:-1px" class="changeRelations">
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect pointer" style="pointer-events: none"></rect>
</svg>
<input data-tip="${tipChange}" class="changeRelations diplomacyRelations" value="${relation}" readonly/>
const tip = `${state.name} ${inText} ${selectedName}`;
const tipSelect = `${tip}. Click to see relations to ${state.name}`;
const tipChange = `Click to change relations. ${tip}`;
const name = state.fullName.length < 23 ? state.fullName : state.name;
COArenderer.trigger("stateCOA" + state.i, state.coa);
lines += `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}">
<svg data-tip="${tipSelect}" class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${state.i}"></use></svg>
<div data-tip="${tipSelect}" style="width: 12em">${name}</div>
<div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
<fill-box fill="${color}" size=".9em"></fill-box>
${relation}
</div>
</div>`;
}
body.innerHTML = lines;
@ -84,8 +112,6 @@ function editDiplomacy() {
// add listeners
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseenter", ev => stateHighlightOn(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("mouseleave", ev => stateHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectStateOnLineClick));
body.querySelectorAll(".changeRelations").forEach(el => el.addEventListener("click", toggleDiplomacySelect));
applySorting(diplomacyHeader);
$("#diplomacyEditor").dialog();
@ -95,19 +121,31 @@ function editDiplomacy() {
if (!layerIsOn("toggleStates")) return;
const state = +event.target.dataset.id;
if (customization || !state) return;
const d = regions.select("#state"+state).attr("d");
const d = regions.select("#state" + state).attr("d");
const path = debug.append("path").attr("class", "highlight").attr("d", d)
.attr("fill", "none").attr("stroke", "red").attr("stroke-width", 1).attr("opacity", 1)
const path = debug
.append("path")
.attr("class", "highlight")
.attr("d", d)
.attr("fill", "none")
.attr("stroke", "red")
.attr("stroke-width", 1)
.attr("opacity", 1)
.attr("filter", "url(#blur1)");
const l = path.node().getTotalLength(), dur = (l + 5000) / 2;
const l = path.node().getTotalLength(),
dur = (l + 5000) / 2;
const i = d3.interpolateString("0," + l, l + "," + l);
path.transition().duration(dur).attrTween("stroke-dasharray", function() {return t => i(t)});
path
.transition()
.duration(dur)
.attrTween("stroke-dasharray", function () {
return t => i(t);
});
}
function stateHighlightOff(event) {
debug.selectAll(".highlight").each(function() {
debug.selectAll(".highlight").each(function () {
d3.select(this).transition().duration(1000).attr("opacity", 0).remove();
});
}
@ -118,22 +156,17 @@ function editDiplomacy() {
if (!sel) return;
if (!layerIsOn("toggleStates")) toggleStates();
statesBody.selectAll("path").each(function() {
statesBody.selectAll("path").each(function () {
if (this.id.slice(0, 9) === "state-gap") return; // exclude state gap element
const id = +this.id.slice(5); // state id
const index = statuses.indexOf(pack.states[id].diplomacy[sel]); // status index
const clr = index !== -1 ? colors[index] : "#4682b4"; // Self (bluish)
this.setAttribute("fill", clr);
statesBody.select("#state-gap"+id).attr("stroke", clr);
statesHalo.select("#state-border"+id).attr("stroke", d3.color(clr).darker().hex());
});
}
function selectStateOnLineClick() {
if (this.classList.contains("Self")) return;
body.querySelector("div.Self").classList.remove("Self");
this.classList.add("Self");
refreshDiplomacyEditor();
const relation = pack.states[id].diplomacy[sel];
const color = relations[relation]?.color || "#4682b4";
this.setAttribute("fill", color);
statesBody.select("#state-gap" + id).attr("stroke", color);
statesHalo.select("#state-border" + id).attr("stroke", d3.color(color).darker().hex());
});
}
function selectStateOnMapClick() {
@ -145,42 +178,61 @@ function editDiplomacy() {
if (+selectedLine.dataset.id === state) return;
selectedLine.classList.remove("Self");
body.querySelector("div[data-id='"+state+"']").classList.add("Self");
body.querySelector("div[data-id='" + state + "']").classList.add("Self");
refreshDiplomacyEditor();
}
function toggleDiplomacySelect(event) {
event.stopPropagation();
const select = document.getElementById("diplomacySelect");
const show = select.style.display === "none";
if (!show) {select.style.display = "none"; return;}
select.style.display = "block";
const input = event.target.closest("div").querySelector("input");
select.style.left = input.getBoundingClientRect().left + "px";
select.style.top = input.getBoundingClientRect().bottom + "px";
body.dataset.state = event.target.closest("div.states").dataset.id;
function selectRelation(subjectId, objectId, currentRelation) {
const states = pack.states;
const subject = states[subjectId];
const header = `<div style="margin-bottom: 0.3em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${subject.i}"></use></svg> <b>${subject.fullName}</b></div>`;
const options = Object.entries(relations)
.map(
([relation, {color, inText, tip}]) =>
`<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
<fill-box fill="${color}" size=".8em"></fill-box>
${inText}
</label></div>`
)
.join("");
const object = states[objectId];
const footer = `<div style="margin-top: 0.7em"><svg class="coaIcon" viewBox="0 0 200 200"><use href="#stateCOA${object.i}"></use></svg> <b>${object.fullName}</b></div>`;
alertMessage.innerHTML = `<div style="overflow: hidden">${header} ${options} ${footer}</div>`;
$("#alert").dialog({
width: fitContent(),
title: `Change relations`,
buttons: {
Apply: function () {
const newRelation = document.querySelector('input[name="relationSelect"]:checked')?.value;
changeRelation(subjectId, objectId, currentRelation, newRelation);
$(this).dialog("close");
},
Cancel: function () {
$(this).dialog("close");
}
}
});
}
function diplomacyChangeRelations(event) {
event.stopPropagation();
diplomacySelect.style.display = "none";
const subject = +body.dataset.state;
const rel = event.target.innerHTML;
function changeRelation(subjectId, objectId, oldRelation, newRelation) {
if (newRelation === oldRelation) return;
const states = pack.states;
const chronicle = states[0].diplomacy;
const states = pack.states, chronicle = states[0].diplomacy;
const selectedLine = body.querySelector("div.Self");
const object = selectedLine ? +selectedLine.dataset.id : states.find(s => s.i && !s.removed).i;
if (!object) return;
const objectName = states[object].name; // object of relations change
const subjectName = states[subject].name; // subject of relations change - actor
const subjectName = states[subjectId].name;
const objectName = states[objectId].name;
const oldRel = states[subject].diplomacy[object];
if (rel === oldRel) return;
states[subject].diplomacy[object] = rel;
states[object].diplomacy[subject] = rel === "Vassal" ? "Suzerain" : rel === "Suzerain" ? "Vassal" : rel;
states[subjectId].diplomacy[objectId] = newRelation;
states[objectId].diplomacy[subjectId] = newRelation === "Vassal" ? "Suzerain" : newRelation === "Suzerain" ? "Vassal" : newRelation;
// update relation history
const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${rel.toLowerCase()}`];
const change = () => [`Relations change`, `${subjectName}-${getAdjective(objectName)} relations changed to ${newRelation.toLowerCase()}`];
const ally = () => [`Defence pact`, `${subjectName} entered into defensive pact with ${objectName}`];
const vassal = () => [`Vassalization`, `${subjectName} became a vassal of ${objectName}`];
const suzerain = () => [`Vassalization`, `${subjectName} vassalized ${objectName}`];
@ -189,24 +241,33 @@ function editDiplomacy() {
const war = () => [`War declaration`, `${subjectName} declared a war on its enemy ${objectName}`];
const peace = () => {
const treaty = `${subjectName} and ${objectName} agreed to cease fire and signed a peace treaty`;
const changed = rel === "Ally" ? ally()
: rel === "Vassal" ? vassal()
: rel === "Suzerain" ? suzerain()
: rel === "Unknown" ? unknown()
: change();
const changed =
newRelation === "Ally"
? ally()
: newRelation === "Vassal"
? vassal()
: newRelation === "Suzerain"
? suzerain()
: newRelation === "Unknown"
? unknown()
: change();
return [`War termination`, treaty, changed[1]];
}
};
if (oldRel === "Enemy") chronicle.push(peace()); else
if (rel === "Enemy") chronicle.push(war()); else
if (rel === "Vassal") chronicle.push(vassal()); else
if (rel === "Suzerain") chronicle.push(suzerain()); else
if (rel === "Ally") chronicle.push(ally()); else
if (rel === "Unknown") chronicle.push(unknown()); else
if (rel === "Rival") chronicle.push(rival()); else
chronicle.push(change());
if (oldRelation === "Enemy") chronicle.push(peace());
else if (newRelation === "Enemy") chronicle.push(war());
else if (newRelation === "Vassal") chronicle.push(vassal());
else if (newRelation === "Suzerain") chronicle.push(suzerain());
else if (newRelation === "Ally") chronicle.push(ally());
else if (newRelation === "Unknown") chronicle.push(unknown());
else if (newRelation === "Rival") chronicle.push(rival());
else chronicle.push(change());
refreshDiplomacyEditor();
if (diplomacyMatrix.offsetParent) {
document.getElementById("diplomacyMatrixBody").innerHTML = "";
showRelationsMatrix();
}
}
function regenerateRelations() {
@ -214,28 +275,52 @@ function editDiplomacy() {
refreshDiplomacyEditor();
}
function resetRelations() {
const selectedId = +body.querySelector("div.Self")?.dataset?.id;
if (!selectedId) return;
const states = pack.states;
states[selectedId].diplomacy.forEach((relations, index) => {
if (relations !== "x") {
states[selectedId].diplomacy[index] = "Neutral";
states[index].diplomacy[selectedId] = "Neutral";
}
});
refreshDiplomacyEditor();
}
function showRelationsHistory() {
const chronicle = pack.states[0].diplomacy;
if (!chronicle.length) {tip("Relations history is blank", false, "error"); return;}
if (!chronicle.length) return tip("Relations history is blank", false, "error");
let message = `<div autocorrect="off" spellcheck="false">`;
chronicle.forEach((e, d) => {
chronicle.forEach((entry, d) => {
message += `<div>`;
e.forEach((l, i) => message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`);
entry.forEach((l, i) => {
message += `<div contenteditable="true" data-id="${d}-${i}"${i ? "" : " style='font-weight:bold'"}>${l}</div>`;
});
message += `&#8205;</div>`;
});
alertMessage.innerHTML = message + `</div><i id="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</i>`;
alertMessage.innerHTML = message + `</div><div class="info-line">Type to edit. Press Enter to add a new line, empty the element to remove it</div>`;
alertMessage.querySelectorAll("div[contenteditable='true']").forEach(el => el.addEventListener("input", changeReliationsHistory));
$("#alert").dialog({title: "Relations history", position: {my: "center", at: "center", of: "svg"},
$("#alert").dialog({
title: "Relations history",
position: {my: "center", at: "center", of: "svg"},
buttons: {
Save: function() {
Save: function () {
const data = this.querySelector("div").innerText.split("\n").join("\r\n");
const name = getFileName("Relations history") + ".txt";
downloadFile(data, name);
},
Clear: function() {pack.states[0].diplomacy = []; $(this).dialog("close");},
Close: function() {$(this).dialog("close");}
Clear: function () {
pack.states[0].diplomacy = [];
$(this).dialog("close");
},
Close: function () {
$(this).dialog("close");
}
}
});
}
@ -251,22 +336,48 @@ function editDiplomacy() {
function showRelationsMatrix() {
const states = pack.states.filter(s => s.i && !s.removed);
const valid = states.map(s => s.i);
const valid = states.map(state => state.i);
const diplomacyMatrixBody = document.getElementById("diplomacyMatrixBody");
let message = `<table class="matrix-table"><tr><th data-tip='&#8205;'></th>`;
message += states.map(s => `<th data-tip='See relations to ${s.fullName}'>${s.name}</th>`).join("") + `</tr>`; // headers
states.forEach(s => {
message += `<tr><th data-tip='See relations of ${s.fullName}'>${s.name}</th>` + s.diplomacy
.filter((v, i) => valid.includes(i)).map((r, i) => {
const desc = description[statuses.indexOf(r)];
const tip = desc ? s.fullName + desc + pack.states[valid[i]].fullName : '&#8205;';
return `<td data-tip='${tip}' class='${r}'>${r}</td>`
}).join("") + "</tr>";
let table = `<table><thead><tr><th data-tip='&#8205;'></th>`;
table += states.map(state => `<th data-tip='Relations to ${state.fullName}'>${state.name}</th>`).join("") + `</tr>`;
table += `<tbody>`;
states.forEach(state => {
table +=
`<tr data-id=${state.i}><th data-tip='Relations of ${state.fullName}'>${state.name}</th>` +
state.diplomacy
.filter((v, i) => valid.includes(i))
.map((relation, index) => {
const relationObj = relations[relation];
if (!relationObj) return `<td class='${relation}'>${relation}</td>`;
const objectState = pack.states[valid[index]];
const tip = `${state.fullName} ${relationObj.inText} ${objectState.fullName}`;
return `<td data-id=${objectState.i} data-tip='${tip}' class='${relation}'>${relation}</td>`;
})
.join("") +
"</tr>";
});
message += `</table>`;
alertMessage.innerHTML = message;
$("#alert").dialog({title: "Relations matrix", width: fitContent(), position: {my: "center", at: "center", of: "svg"}, buttons: {}});
table += `</tbody></table>`;
diplomacyMatrixBody.innerHTML = table;
const tableEl = diplomacyMatrixBody.querySelector("table");
tableEl.addEventListener("click", function (event) {
const el = event.target;
if (el.tagName !== "TD") return;
const currentRelation = el.innerText;
if (!relations[currentRelation]) return;
const subjectId = +el.closest("tr")?.dataset?.id;
const objectId = +el?.dataset?.id;
selectRelation(subjectId, objectId, currentRelation);
});
$("#diplomacyMatrix").dialog({title: "Relations matrix", position: {my: "center", at: "center", of: "svg"}, buttons: {}});
}
function downloadDiplomacyData() {
@ -288,7 +399,8 @@ function editDiplomacy() {
clearMainTip();
const selected = body.querySelector("div.Self");
if (selected) selected.classList.remove("Self");
if (layerIsOn("toggleStates")) drawStates(); else toggleStates();
if (layerIsOn("toggleStates")) drawStates();
else toggleStates();
debug.selectAll(".highlight").remove();
}
}

View file

@ -265,41 +265,48 @@ function getBurgSeed(burg) {
}
function getMFCGlink(burg) {
if (burg.link) return burg.link;
const {cells} = pack;
const {name, population, cell} = burg;
const burgSeed = getBurgSeed(burg);
const sizeRaw = 2.13 * Math.pow((population * populationRate) / urbanDensity, 0.385);
const {i, name, population: burgPopulation, cell} = burg;
const seed = getBurgSeed(burg);
const sizeRaw = 2.13 * Math.pow((burgPopulation * populationRate) / urbanDensity, 0.385);
const size = minmax(Math.ceil(sizeRaw), 6, 100);
const people = rn(population * populationRate * urbanization);
const population = rn(burgPopulation * populationRate * urbanization);
const river = cells.r[cell] ? 1 : 0;
const coast = Number(burg.port > 0);
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : null;
const biome = cells.biome[cell];
const arableBiomes = river ? [1, 2, 3, 4, 5, 6, 7, 8] : [5, 6, 7, 8];
const farms = +arableBiomes.includes(biome);
const citadel = +burg.citadel;
const urban_castle = +(citadel && each(2)(i));
const hub = +cells.road[cell] > 50;
const river = cells.r[cell] ? 1 : 0;
const coast = +burg.port;
const citadel = +burg.citadel;
const walls = +burg.walls;
const plaza = +burg.plaza;
const temple = +burg.temple;
const shanty = +burg.shanty;
const shantytown = +burg.shanty;
const sea = coast && cells.haven[cell] ? getSeaDirections(cell) : "";
function getSeaDirections(i) {
const p1 = cells.p[i];
const p2 = cells.p[cells.haven[i]];
let deg = (Math.atan2(p2[1] - p1[1], p2[0] - p1[0]) * 180) / Math.PI - 90;
if (deg < 0) deg += 360;
const norm = rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
return "&sea=" + norm;
return rn(normalize(deg, 0, 360) * 2, 2); // 0 = south, 0.5 = west, 1 = north, 1.5 = east
}
const baseURL = "https://watabou.github.io/city-generator/?random=0&continuous=0";
const url = `${baseURL}&name=${name}&population=${people}&size=${size}&seed=${burgSeed}&hub=${hub}&river=${river}&coast=${coast}&citadel=${citadel}&plaza=${plaza}&temple=${temple}&walls=${walls}&shantytown=${shanty}${sea}`;
return url;
}
const parameters = {name, population, size, seed, river, coast, farms, citadel, urban_castle, hub, plaza, temple, walls, shantytown, gates: -1};
const url = new URL("https://watabou.github.io/city-generator");
url.search = new URLSearchParams(parameters);
if (sea) url.searchParams.append("sea", sea);
function toggleBurgLock(burg) {
const b = pack.burgs[burg];
b.lock = b.lock ? 0 : 1;
return url.toString();
}
// draw legend box
@ -418,10 +425,10 @@ function clearLegend() {
function createPicker() {
const pos = () => tip("Drag to change the picker position");
const cl = () => tip("Click to close the picker");
const closePicker = () => contaiter.style("display", "none");
const closePicker = () => container.style("display", "none");
const contaiter = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
contaiter
const container = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
container
.append("rect")
.attr("x", 0)
.attr("y", 0)
@ -430,7 +437,7 @@ function createPicker() {
.attr("opacity", 0.2)
.on("mousemove", cl)
.on("click", closePicker);
const picker = contaiter
const picker = container
.append("g")
.attr("id", "picker")
.call(
@ -487,7 +494,7 @@ function createPicker() {
const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333");
const hatches = picker.append("g").attr("id", "pickerHatches").attr("stroke", "#333333");
const hatching = d3.selectAll("g#hatching > pattern");
const hatching = d3.selectAll("g#defs-hatching > pattern");
const number = hatching.size();
const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
@ -497,8 +504,8 @@ function createPicker() {
.attr("id", "picker_" + d)
.attr("fill", d)
.attr("class", i ? "" : "selected")
.attr("x", i * 22 + 4)
.attr("y", 40)
.attr("x", (i % 14) * 22 + 4)
.attr("y", 40 + Math.floor(i / 14)*20)
.attr("width", 16)
.attr("height", 16);
});
@ -508,20 +515,20 @@ function createPicker() {
.append("rect")
.attr("id", "picker_" + this.id)
.attr("fill", "url(#" + this.id + ")")
.attr("x", i * 22 + 4)
.attr("y", 61)
.attr("x", (i % 14) * 22 + 4)
.attr("y", Math.floor(i / 14)*20 + 20 + (number * 2))
.attr("width", 16)
.attr("height", 16);
.attr("height", 16)
});
colors
.selectAll("rect")
.on("click", pickerFillClicked)
.on("mousemove", () => tip("Click to fill with the color"));
.on("mouseover", () => tip("Click to fill with the color"));
hatches
.selectAll("rect")
.on("click", pickerFillClicked)
.on("mousemove", () => tip("Click to fill with the hatching"));
.on("mouseover", function() { tip("Click to fill with the hatching " + this.id) });
// append box
const bbox = picker.node().getBBox();
@ -537,10 +544,10 @@ function createPicker() {
.attr("fill", "#ffffff")
.attr("stroke", "#5d4651")
.on("mousemove", pos);
picker.insert("text", ":first-child").attr("x", 291).attr("y", -10).attr("id", "pickerCloseText").text("✕");
picker.insert("text", ":first-child").attr("x", width-20).attr("y", -10).attr("id", "pickerCloseText").text("✕");
picker
.insert("rect", ":first-child")
.attr("x", 288)
.attr("x", width-23)
.attr("y", -21)
.attr("id", "pickerCloseRect")
.attr("width", 14)

View file

@ -460,32 +460,31 @@ function showInfo() {
const Discord = link("https://discordapp.com/invite/X7E84HU", "Discord");
const Reddit = link("https://www.reddit.com/r/FantasyMapGenerator", "Reddit");
const Patreon = link("https://www.patreon.com/azgaar", "Patreon");
const Trello = link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Trello");
const Armoria = link("https://azgaar.github.io/Armoria", "Armoria");
const QuickStart = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Quick-Start-Tutorial", "Quick start tutorial");
const QAA = link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Q&A", "Q&A page");
const VideoTutorial = link("https://youtube.com/playlist?list=PLtgiuDC8iVR2gIG8zMTRn7T_L0arl9h1C", "Video tutorial");
alertMessage.innerHTML = `
<b>Fantasy Map Generator</b> (FMG) is an open-source application, it means the code is published an anyone can use it.
In case of FMG is also means that you own all created maps and can use them as you wish, you can even sell them.
<b>Fantasy Map Generator</b> (FMG) is a free open-source application.
It means that you own all created maps and can use them as you wish.
<p>The development is supported by community, you can donate on ${Patreon}.
<p>The development is community-backed, you can donate on ${Patreon}.
You can also help creating overviews, tutorials and spreding the word about the Generator.</p>
<p>The best way to get help is to contact the community on ${Discord} and ${Reddit}.
Before asking questions, please check out the ${QuickStart} and the ${QAA}.</p>
Before asking questions, please check out the ${QuickStart}, the ${QAA}, and ${VideoTutorial}.</p>
<p>Track the development process on ${Trello}.</p>
<p>Check out our another project: ${Armoria} heraldry generator and editor.</p>
<p>Check out our new project: ${Armoria}, heraldry generator and editor.</p>
<b>Links:</b>
<ul style="columns:2">
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator", "GitHub repository")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/blob/master/LICENSE", "License")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Changelog", "Changelog")}</li>
<li>${link("https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Hotkeys", "Hotkeys")}</li>
<li>${link("https://trello.com/b/7x832DG4/fantasy-map-generator", "Devboard")}</li>
<li><a href="mailto:azgaar.fmg@yandex.by" target="_blank">Contact Azgaar</a></li>
</ul>`;
$("#alert").dialog({

View file

@ -470,23 +470,26 @@ function togglePrec(event) {
function drawPrec() {
prec.selectAll("circle").remove();
const cells = grid.cells,
p = grid.points;
const {cells, points} = grid;
prec.style("display", "block");
const show = d3.transition().duration(800).ease(d3.easeSinIn);
prec.selectAll("text").attr("opacity", 0).transition(show).attr("opacity", 1);
const cellsNumberModifier = (pointsInput.dataset.cells / 10000) ** 0.25;
const data = cells.i.filter(i => cells.h[i] >= 20 && cells.prec[i]);
const getRadius = prec => rn(Math.sqrt(prec / 4) / cellsNumberModifier, 2);
prec
.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", d => p[d][0])
.attr("cy", d => p[d][1])
.attr("cx", d => points[d][0])
.attr("cy", d => points[d][1])
.attr("r", 0)
.transition(show)
.attr("r", d => rn(Math.max(Math.sqrt(cells.prec[d] * 0.5), 0.8), 2));
.attr("r", d => getRadius(cells.prec[d]));
}
function togglePopulation(event) {

View file

@ -75,12 +75,9 @@ function overviewMilitary() {
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="State ${u.name} units number">${getForces(u)}</div>`).join(" ");
lines += `<div class="states" data-id=${s.i} data-state="${
s.name
}" ${sortData} data-total="${total}" data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" class="fillRect"></svg>
lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}"
data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>

View file

@ -17,18 +17,24 @@ function editNamesbase() {
document.getElementById("namesbaseMax").addEventListener("input", updateBaseMax);
document.getElementById("namesbaseDouble").addEventListener("input", updateBaseDublication);
document.getElementById("namesbaseAdd").addEventListener("click", namesbaseAdd);
document.getElementById("namesbaseAnalize").addEventListener("click", analizeNamesbase);
document.getElementById("namesbaseAnalyze").addEventListener("click", analyzeNamesbase);
document.getElementById("namesbaseDefault").addEventListener("click", namesbaseRestoreDefault);
document.getElementById("namesbaseDownload").addEventListener("click", namesbaseDownload);
document.getElementById("namesbaseUpload").addEventListener("click", () => namesbaseToLoad.click());
document.getElementById("namesbaseToLoad").addEventListener("change", function() {uploadFile(this, namesbaseUpload)});
document.getElementById("namesbaseUpload").addEventListener("click", () => document.getElementById("namesbaseToLoad").click());
document.getElementById("namesbaseToLoad").addEventListener("change", function () {
uploadFile(this, namesbaseUpload);
});
document.getElementById("namesbaseCA").addEventListener("click", () => {
openURL("https://cartographyassets.com/asset-category/specific-assets/azgaars-generator/namebases/");
});
document.getElementById("namesbaseSpeak").addEventListener("click", () => speak(namesbaseExamples.textContent));
createBasesList();
updateInputs();
$("#namesbaseEditor").dialog({
title: "Namesbase Editor", width: "42.5em",
title: "Namesbase Editor",
width: "auto",
position: {my: "center", at: "center", of: "svg"}
});
@ -40,7 +46,10 @@ function editNamesbase() {
function updateInputs() {
const base = +document.getElementById("namesbaseSelect").value;
if (!nameBases[base]) {tip(`Namesbase ${base} is not defined`, false, "error"); return;}
if (!nameBases[base]) {
tip(`Namesbase ${base} is not defined`, false, "error");
return;
}
document.getElementById("namesbaseTextarea").value = nameBases[base].b;
document.getElementById("namesbaseName").value = nameBases[base].name;
document.getElementById("namesbaseMin").value = nameBases[base].min;
@ -52,7 +61,7 @@ function editNamesbase() {
function updateExamples() {
const base = +document.getElementById("namesbaseSelect").value;
let examples = "";
for (let i=0; i < 10; i++) {
for (let i = 0; i < 10; i++) {
const example = Names.getBase(base);
if (example === undefined) {
examples = "Cannot generate examples. Please verify the data";
@ -84,13 +93,19 @@ function editNamesbase() {
function updateBaseMin() {
const base = +document.getElementById("namesbaseSelect").value;
if (+this.value > nameBases[base].max) {tip("Minimal length cannot be greater than maximal", false, "error"); return;}
if (+this.value > nameBases[base].max) {
tip("Minimal length cannot be greater than maximal", false, "error");
return;
}
nameBases[base].min = +this.value;
}
function updateBaseMax() {
const base = +document.getElementById("namesbaseSelect").value;
if (+this.value < nameBases[base].min) {tip("Maximal length should be greater than minimal", false, "error"); return;}
if (+this.value < nameBases[base].min) {
tip("Maximal length should be greater than minimal", false, "error");
return;
}
nameBases[base].max = +this.value;
}
@ -99,59 +114,70 @@ function editNamesbase() {
nameBases[base].d = this.value;
}
function analizeNamesbase() {
const string = document.getElementById("namesbaseTextarea").value;
if (!string) {tip("Names data field should not be empty", false, "error"); return;}
const base = string.toLowerCase();
const array = base.split(",");
const l = array.length;
if (!l) {tip("Names data should not be empty", false, "error"); return;}
function analyzeNamesbase() {
const namesSourceString = document.getElementById("namesbaseTextarea").value;
const namesArray = namesSourceString.toLowerCase().split(",");
const length = namesArray.length;
if (!namesSourceString || !length) return tip("Names data should not be empty", false, "error");
const wordsLength = array.map(n => n.length);
const multi = rn(d3.mean(array.map(n => (n.match(/ /i)||[]).length)) * 100, 2);
const geminate = array.map(name => name.match(/[^\w\s]|(.)(?=\1)/g)||[]).flat();
const doubled = ([...new Set(geminate)].filter(l => geminate.filter(d => d === l).length > 3)||["none"]).join("");
const chain = Names.calculateChain(string);
const depth = rn(d3.mean(Object.keys(chain).map(key => chain[key].filter(c => c !== " ").length)));
const nonLatin = (string.match(/[^\u0000-\u007f]/g)||["none"]).join("");
const chain = Names.calculateChain(namesSourceString);
const variety = rn(d3.mean(Object.values(chain).map(keyValue => keyValue.length)));
const lengthStat =
l < 30 ? "<span style='color:red'>[not enough]</span>" :
l < 150 ? "<span style='color:darkred'>[low]</span>" :
l < 150 ? "<span style='color:orange'>[low]</span>" :
l < 400 ? "<span style='color:green'>[good]</span>" :
l < 600 ? "<span style='color:orange'>[overmuch]</span>" :
"<span style='color:darkred'>[overmuch]</span>";
const wordsLength = namesArray.map(n => n.length);
const rangeStat =
l < 10 ? "<span style='color:red'>[low]</span>" :
l < 15 ? "<span style='color:darkred'>[low]</span>" :
l < 20 ? "<span style='color:orange'>[low]</span>" :
"<span style='color:green'>[good]</span>";
const nonLatin = namesSourceString.match(/[^\u0000-\u007f]/g);
const nonBasicLatinChars = nonLatin
? unique(
namesSourceString
.match(/[^\u0000-\u007f]/g)
.join("")
.toLowerCase()
).join("")
: "none";
const depthStat =
l < 15 ? "<span style='color:red'>[low]</span>" :
l < 20 ? "<span style='color:darkred'>[low]</span>" :
l < 25 ? "<span style='color:orange'>[low]</span>" :
"<span style='color:green'>[good]</span>";
const geminate = namesArray.map(name => name.match(/[^\w\s]|(.)(?=\1)/g) || []).flat();
const doubled = unique(geminate).filter(char => geminate.filter(doudledChar => doudledChar === char).length > 3) || ["none"];
const duplicates = unique(namesArray.filter((e, i, a) => a.indexOf(e) !== i)).join(", ") || "none";
const multiwordRate = d3.mean(namesArray.map(n => +n.includes(" ")));
const getLengthQuality = () => {
if (length < 30) return "<span data-tip='Namesbase contains < 30 names - not enough to generate reasonable data' style='color:red'>[not enough]</span>";
if (length < 100) return "<span data-tip='Namesbase contains < 100 names - not enough to generate good names' style='color:darkred'>[low]</span>";
if (length <= 400) return "<span data-tip='Namesbase contains a reasonable number of samples' style='color:green'>[good]</span>";
return "<span data-tip='Namesbase contains > 400 names. That is too much, try to reduce it to ~300 names' style='color:darkred'>[overmuch]</span>";
};
const getVarietyLevel = () => {
if (variety < 15) return "<span data-tip='Namesbase average variety < 15 - generated names will be too repetitive' style='color:red'>[low]</span>";
if (variety < 30) return "<span data-tip='Namesbase average variety < 30 - names can be too repetitive' style='color:orange'>[mean]</span>";
return "<span data-tip='Namesbase variety is good' style='color:green'>[good]</span>";
};
alertMessage.innerHTML = `<div style="line-height: 1.6em; max-width: 20em">
<div>Namesbase length: ${l} ${lengthStat}</div>
<div>Namesbase range: ${Object.keys(chain).length-1} ${rangeStat}</div>
<div>Namesbase depth: ${depth} ${depthStat}</div>
<div>Non-basic chars: ${nonLatin}</div>
<div data-tip="Number of names provided">Namesbase length: ${length} ${getLengthQuality()}</div>
<div data-tip="Average number of generation variants for each key in the chain">Namesbase variety: ${variety} ${getVarietyLevel()}</div>
<hr>
<div>Min name length: ${d3.min(wordsLength)}</div>
<div>Max name length: ${d3.max(wordsLength)}</div>
<div>Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
<div>Median name length: ${d3.median(wordsLength)}</div>
<div>Doubled chars: ${doubled}</div>
<div>Multi-word names: ${multi}%</div>
<div data-tip="The shortest name length">Min name length: ${d3.min(wordsLength)}</div>
<div data-tip="The longest name length">Max name length: ${d3.max(wordsLength)}</div>
<div data-tip="Average name length">Mean name length: ${rn(d3.mean(wordsLength), 1)}</div>
<div data-tip="Common name length">Median name length: ${d3.median(wordsLength)}</div>
<hr>
<div data-tip="Characters outside of Basic Latin have bad font support">Non-basic chars: ${nonBasicLatinChars}</div>
<div data-tip="Characters that are frequently (more than 3 times) doubled">Doubled chars: ${doubled.join("")}</div>
<div data-tip="Names used more than one time">Duplicates: ${duplicates}</div>
<div data-tip="Percentage of names containing space character">Multi-word names: ${rn(multiwordRate * 100, 2)}%</div>
</div>`;
$("#alert").dialog({
resizable: false, title: "Data Analysis",
resizable: false,
title: "Data Analysis",
position: {my: "left top-30", at: "right+10 top", of: "#namesbaseEditor"},
buttons: {OK: function() {$(this).dialog("close");}}
buttons: {
OK: function () {
$(this).dialog("close");
}
}
});
}
@ -171,35 +197,42 @@ function editNamesbase() {
function namesbaseRestoreDefault() {
alertMessage.innerHTML = `Are you sure you want to restore default namesbase?`;
$("#alert").dialog({resizable: false, title: "Restore default data",
$("#alert").dialog({
resizable: false,
title: "Restore default data",
buttons: {
Restore: function() {
Restore: function () {
$(this).dialog("close");
Names.clearChains();
nameBases = Names.getNameBases();
createBasesList();
updateInputs();
},
Cancel: function() {$(this).dialog("close");}
Cancel: function () {
$(this).dialog("close");
}
}
});
}
function namesbaseDownload() {
const data = nameBases.map((b,i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n");
const data = nameBases.map((b, i) => `${b.name}|${b.min}|${b.max}|${b.d}|${b.m}|${b.b}`).join("\r\n");
const name = getFileName("Namesbase") + ".txt";
downloadFile(data, name);
}
function namesbaseUpload(dataLoaded) {
const data = dataLoaded.split("\r\n");
if (!data || !data[0]) {tip("Cannot load a namesbase. Please check the data format", false, "error"); return;}
if (!data || !data[0]) {
tip("Cannot load a namesbase. Please check the data format", false, "error");
return;
}
Names.clearChains();
nameBases = [];
data.forEach(d => {
const e = d.split("|");
nameBases.push({name:e[0], min:e[1], max:e[2], d:e[3], m:e[4], b:e[5]});
nameBases.push({name: e[0], min: e[1], max: e[2], d: e[3], m: e[4], b: e[5]});
});
createBasesList();

View file

@ -102,7 +102,9 @@ function showSupporters() {
Dominick Ormsby,Linn Browning,Václav Švec,Alan Buehne,George J.Lekkas,Alexandre Boivin,Tommy Mayfield,Skylar Mangum-Turner,Karen Blythe,Stefan Gugerel,
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky,
Dario Spadavecchia,Bas Kroot,John Patrick Callahan Jr,Alexandra Vesey,D,Exp1nt,james,Braxton Istace,w,Rurikid,AntiBlock,Redsauz,BigE0021,
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger`;
Jonathan Williams,ojacid .,Brian Wilson,A Patreon of the Ahts,Shubham Jakhotiya,www15o,Jan Bundesmann,Angelique Badger,Joshua Xiong,Moist mongol,
Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
Jordan,William Markus,Sidr Dim`;
const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "")
@ -287,18 +289,16 @@ function generateMapWithSeed(source) {
}
function showSeedHistoryDialog() {
const alert = mapHistory
.map(function (h, i) {
const created = new Date(h.created).toLocaleTimeString();
const button = `<i data-tip"Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`;
return `<div>${i + 1}. Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</div>`;
})
.join("");
alertMessage.innerHTML = alert;
const lines = mapHistory.map((h, i) => {
const created = new Date(h.created).toLocaleTimeString();
const button = `<i data-tip="Click to generate a map with this seed" onclick="restoreSeed(${i})" class="icon-history optionsSeedRestore"></i>`;
return `<li>Seed: ${h.seed} ${button}. Size: ${h.width}x${h.height}. Template: ${h.template}. Created: ${created}</li>`;
});
alertMessage.innerHTML = `<ol style="margin: 0; padding-left: 1.5em">${lines.join("")}</ol>`;
$("#alert").dialog({
resizable: false,
title: "Seed history",
width: fitContent(),
position: {my: "center", at: "center", of: "svg"}
});
}

View file

@ -44,7 +44,8 @@ function editProvinces() {
cl = el.classList,
line = el.parentNode,
p = +line.dataset.id;
if (cl.contains("fillRect")) changeFill(el);
if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("name")) editProvinceName(p);
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
@ -133,11 +134,9 @@ function editProvinces() {
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${
p.color
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}>
<svg data-tip="Province fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
p.color
}" class="fillRect pointer"></svg>
<fill-box fill="${p.color}"></fill-box>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly>
<svg data-tip="Click to show and edit province emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
<svg data-tip="Click to show and edit province emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#provinceCOA${p.i}"></use></svg>
<input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
<span data-tip="Province capital. Click to zoom into view" class="icon-star-empty pointer hide ${p.burg ? "" : "placeholder"}"></span>
<select data-tip="Province capital. Click to select from burgs within the state. No capital means the province is governed from the state capital" class="cultureBase hide ${
@ -215,14 +214,14 @@ function editProvinces() {
function changeFill(el) {
const currentFill = el.getAttribute("fill");
const p = +el.parentNode.parentNode.dataset.id;
const p = +el.parentNode.dataset.id;
const callback = function (fill) {
el.setAttribute("fill", fill);
pack.provinces[p].color = fill;
const callback = newFill => {
el.fill = newFill;
pack.provinces[p].color = newFill;
const g = provs.select("#provincesBody");
g.select("#province" + p).attr("fill", fill);
g.select("#province-gap" + p).attr("stroke", fill);
g.select("#province" + p).attr("fill", newFill);
g.select("#province-gap" + p).attr("stroke", newFill);
};
openPicker(currentFill, callback);

View file

@ -14,7 +14,9 @@ function overviewRegiments(state) {
updateHeaders();
$("#regimentsOverview").dialog({
title: "Regiments Overview", resizable: false, width: fitContent(),
title: "Regiments Overview",
resizable: false,
width: fitContent(),
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
});
@ -31,11 +33,13 @@ function overviewRegiments(state) {
header.querySelectorAll(".removable").forEach(el => el.remove());
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) {
const label = capitalize(u.name.replace(/_/g, ' '));
const label = capitalize(u.name.replace(/_/g, " "));
insert(`<div data-tip="Regiment ${u.name} units number. Click to sort" class="sortable removable" data-sortby="${u.name}">${label}&nbsp;</div>`);
}
header.querySelectorAll(".removable").forEach(function(e) {
e.addEventListener("click", function() {sortLines(this);});
header.querySelectorAll(".removable").forEach(function (e) {
e.addEventListener("click", function () {
sortLines(this);
});
});
}
@ -51,11 +55,13 @@ function overviewRegiments(state) {
if (state !== -1 && s.i !== state) continue; // specific state is selected
for (const r of s.military) {
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).join(" ");
const lineData = options.military.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name]||0}</div>`).join(" ");
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name] || 0}`).join(" ");
const lineData = options.military
.map(u => `<div data-type="${u.name}" data-tip="${capitalize(u.name)} units number">${r.u[u.name] || 0}</div>`)
.join(" ");
lines += `<div class="states" data-id=${r.i} data-s="${s.i}" data-state="${s.name}" data-name="${r.name}" ${sortData} data-total="${r.a}">
<svg data-tip="${s.fullName}" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${s.color}" class="fillRect"></svg>
<fill-box data-tip="${s.fullName}" fill="${s.color}" disabled></fill-box>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span>
<input data-tip="Regiment's name" style="width:13em" value="${r.name}" readonly>
@ -70,12 +76,15 @@ function overviewRegiments(state) {
lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div>
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name]||0)))}</div>`).join(" ")}
${options.military.map(u => `<div style="width:5em">${si(d3.sum(regiments.map(r => r.u[u.name] || 0)))}</div>`).join(" ")}
<div style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
</div>`;
body.insertAdjacentHTML("beforeend", lines);
if (body.dataset.type === "percentage") {body.dataset.type = "absolute"; togglePercentageMode();}
if (body.dataset.type === "percentage") {
body.dataset.type = "absolute";
togglePercentageMode();
}
applySorting(regimentsHeader);
// add listeners
@ -87,7 +96,7 @@ function overviewRegiments(state) {
const filter = document.getElementById("regimentsFilter");
filter.options.length = 0; // remove all options
filter.options.add(new Option(`all`, -1, false, state === -1));
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name) ? 1 : -1);
const statesSorted = pack.states.filter(s => s.i && !s.removed).sort((a, b) => (a.name > b.name ? 1 : -1));
statesSorted.forEach(s => filter.options.add(new Option(s.name, s.i, false, s.i == state)));
}
@ -108,19 +117,20 @@ function overviewRegiments(state) {
if (body.dataset.type === "absolute") {
body.dataset.type = "percentage";
const lines = body.querySelectorAll(":scope > div:not(.totalLine)");
const array = Array.from(lines), cache = [];
const array = Array.from(lines),
cache = [];
const total = function(type) {
const total = function (type) {
if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type]));
return cache[type];
}
};
lines.forEach(function(el) {
el.querySelectorAll("div").forEach(function(div) {
lines.forEach(function (el) {
el.querySelectorAll("div").forEach(function (div) {
const type = div.dataset.type;
if (type === "rate") return;
div.textContent = total(type) ? rn(+el.dataset[type] / total(type) * 100) + "%" : "0%";
div.textContent = total(type) ? rn((+el.dataset[type] / total(type)) * 100) + "%" : "0%";
});
});
} else {
@ -145,15 +155,19 @@ function overviewRegiments(state) {
function addRegimentOnClick() {
const state = +regimentsFilter.value;
if (state === -1) {tip("Please select state from the list", false, "error"); return;}
if (state === -1) {
tip("Please select state from the list", false, "error");
return;
}
const point = d3.mouse(this);
const cell = findCell(point[0], point[1]);
const x = pack.cells.p[cell][0], y = pack.cells.p[cell][1];
const x = pack.cells.p[cell][0],
y = pack.cells.p[cell][1];
const military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0;
const n = +(pack.cells.h[cell] < 20); // naval or land
const reg = {a:0, cell, i, n, u:{}, x, y, bx:x, by:y, state, icon:"🛡️"};
const reg = {a: 0, cell, i, n, u: {}, x, y, bx: x, by: y, state, icon: "🛡️"};
reg.name = Military.getName(reg, military);
military.push(reg);
Military.generateNote(reg, pack.states[state]); // add legend
@ -163,9 +177,9 @@ function overviewRegiments(state) {
function downloadRegimentsData() {
const units = options.military.map(u => u.name);
let data = "State,Id,Name,"+units.map(u => capitalize(u)).join(",")+",Total\n"; // headers
let data = "State,Id,Name," + units.map(u => capitalize(u)).join(",") + ",Total\n"; // headers
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function(el) {
body.querySelectorAll(":scope > div:not(.totalLine)").forEach(function (el) {
data += el.dataset.state + ",";
data += el.dataset.id + ",";
data += el.dataset.name + ",";
@ -176,5 +190,4 @@ function overviewRegiments(state) {
const name = getFileName("Regiments") + ".csv";
downloadFile(data, name);
}
}

View file

@ -79,7 +79,7 @@ function editReligions() {
if (r.i) {
lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area}
data-population=${population} data-type=${r.type} data-form=${r.form} data-deity="${r.deity ? r.deity : ""}" data-expansionism=${r.expansionism}>
<svg data-tip="Religion fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${r.color}" class="fillRect pointer"></svg>
<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">
<select data-tip="Religion type" class="religionType">${getTypeOptions(r.type)}</select>
<input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false">
@ -93,7 +93,9 @@ function editReligions() {
</div>`;
} else {
// No religion (neutral) line
lines += `<div class="states" data-id=${r.i} data-name="${r.name}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism="">
lines += `<div class="states" data-id=${r.i} data-name="${
r.name
}" data-color="" data-area=${area} data-population=${population} data-type="" data-form="" data-deity="" data-expansionism="">
<svg width="9" height="9" class="placeholder"></svg>
<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">${getTypeOptions(r.type)}</select>
@ -124,7 +126,7 @@ function editReligions() {
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseenter", ev => religionHighlightOn(ev)));
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(ev)));
body.querySelectorAll("div.states").forEach(el => el.addEventListener("click", selectReligionOnLineClick));
body.querySelectorAll("rect.fillRect").forEach(el => el.addEventListener("click", religionChangeColor));
body.querySelectorAll("fill-box").forEach(el => el.addEventListener("click", religionChangeColor));
body.querySelectorAll("div > input.religionName").forEach(el => el.addEventListener("input", religionChangeName));
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType));
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm));
@ -215,13 +217,13 @@ function editReligions() {
function religionChangeColor() {
const el = this;
const currentFill = el.getAttribute("fill");
const religion = +el.parentNode.parentNode.dataset.id;
const religion = +el.parentNode.dataset.id;
const callback = function (fill) {
el.setAttribute("fill", fill);
pack.religions[religion].color = fill;
relig.select("#religion" + religion).attr("fill", fill);
debug.select("#religionsCenter" + religion).attr("fill", fill);
const callback = newFill => {
el.fill = newFill;
pack.religions[religion].color = newFill;
relig.select("#religion" + religion).attr("fill", newFill);
debug.select("#religionsCenter" + religion).attr("fill", newFill);
};
openPicker(currentFill, callback);
@ -459,7 +461,13 @@ function editReligions() {
// prepare svg
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>";
const svg = d3.select("#alertMessage").insert("svg", "#religionInfo").attr("id", "hierarchy").attr("width", width).attr("height", height).style("text-anchor", "middle");
const svg = d3
.select("#alertMessage")
.insert("svg", "#religionInfo")
.attr("id", "hierarchy")
.attr("width", width)
.attr("height", height)
.style("text-anchor", "middle");
const graph = svg.append("g").attr("transform", `translate(10, -45)`);
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
const nodes = graph.append("g");
@ -473,7 +481,24 @@ function editReligions() {
.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;
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
@ -578,7 +603,11 @@ function editReligions() {
$("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}});
tip("Click on religion to select, drag the circle to change religion", true);
viewbox.style("cursor", "crosshair").on("click", selectReligionOnMapClick).call(d3.drag().on("start", dragReligionBrush)).on("touchmove mousemove", moveReligionBrush);
viewbox
.style("cursor", "crosshair")
.on("click", selectReligionOnMapClick)
.call(d3.drag().on("start", dragReligionBrush))
.on("touchmove mousemove", moveReligionBrush);
body.querySelector("div").classList.add("selected");
}

View file

@ -89,7 +89,8 @@ function createRiver() {
const source = riverCells[0];
const mouth = parent === riverId ? last(riverCells) : riverCells[riverCells.length - 2];
const sourceWidth = 0.05;
const widthFactor = 1.2;
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = 1.2 * defaultWidthFactor;
const meanderedPoints = addMeandering(riverCells);

View file

@ -45,7 +45,7 @@ function editStates() {
cl = el.classList,
line = el.parentNode,
state = +line.dataset.id;
if (cl.contains("fillRect")) stateChangeFill(el);
if (el.tagName === "FILL-BOX") stateChangeFill(el);
else if (cl.contains("name")) editStateName(state);
else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]);
else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state);
@ -102,7 +102,7 @@ function editStates() {
// Neutral line
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area}
data-population=${population} data-burgs=${s.burgs} data-color="" data-form="" data-capital="" data-culture="" data-type="" data-expansionism="">
<svg width="9" height="9" class="placeholder"></svg>
<svg width="1em" height="1em" class="placeholder"></svg>
<input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly>
<svg class="coaIcon placeholder hide"></svg>
<input class="stateForm placeholder" value="none">
@ -126,17 +126,12 @@ function editStates() {
const capital = pack.burgs[s.capital].name;
COArenderer.trigger("stateCOA" + s.i, s.coa);
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}" data-color="${
s.color
}" data-cells=${s.cells}
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${
s.type
} data-expansionism=${s.expansionism}>
<svg data-tip="State fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${
s.color
}" class="fillRect pointer"></svg>
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}"
data-color="${s.color}" data-cells=${s.cells} data-area=${area} data-population=${population} data-burgs=${s.burgs}
data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
<fill-box fill="${s.color}"></fill-box>
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly>
<svg data-tip="Click to show and edit state emblem" class="coaIcon hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
<svg data-tip="Click to show and edit state emblem" class="coaIcon pointer hide" viewBox="0 0 200 200"><use href="#stateCOA${s.i}"></use></svg>
<input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
<span data-tip="State capital. Click to zoom into view" class="icon-star-empty pointer hide"></span>
<input data-tip="Capital name. Click and type to rename" class="stateCapital hide" value="${capital}" autocorrect="off" spellcheck="false"/>
@ -149,9 +144,8 @@ function editStates() {
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div>
<select data-tip="State type. Defines growth model. Click to change" class="cultureType ${hidden} show hide">${getTypeOptions(s.type)}</select>
<span data-tip="State expansionism" class="icon-resize-full ${hidden} show hide"></span>
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value" class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${
s.expansionism
}>
<input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value"
class="statePower ${hidden} show hide" type="number" min=0 max=99 step=.1 value=${s.expansionism}>
<span data-tip="Cells count" class="icon-check-empty ${hidden} show hide"></span>
<div data-tip="Cells count" class="stateCells ${hidden} show hide">${s.cells}</div>
<span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
@ -237,18 +231,18 @@ function editStates() {
function stateChangeFill(el) {
const currentFill = el.getAttribute("fill");
const state = +el.parentNode.parentNode.dataset.id;
const state = +el.parentNode.dataset.id;
const callback = function (fill) {
el.setAttribute("fill", fill);
pack.states[state].color = fill;
statesBody.select("#state" + state).attr("fill", fill);
statesBody.select("#state-gap" + state).attr("stroke", fill);
const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666";
const callback = function (newFill) {
el.fill = newFill;
pack.states[state].color = newFill;
statesBody.select("#state" + state).attr("fill", newFill);
statesBody.select("#state-gap" + state).attr("stroke", newFill);
const halo = d3.color(newFill) ? d3.color(newFill).darker().hex() : "#666666";
statesHalo.select("#state-border" + state).attr("stroke", halo);
// recolor regiments
const solidColor = fill[0] === "#" ? fill : "#999";
const solidColor = newFill[0] === "#" ? newFill : "#999";
const darkerColor = d3.color(solidColor).darker().hex();
armies.select("#army" + state).attr("fill", solidColor);
armies

File diff suppressed because one or more lines are too long

311
modules/ui/stylePresets.js Normal file
View file

@ -0,0 +1,311 @@
// UI module to control the style presets
"use strict";
const systemPresets = ["default", "ancient", "gloom", "light", "watercolor", "clean", "atlas", "cyberpunk", "monochrome"];
const customPresetPrefix = "fmgStyle_";
// add style presets to list
{
const systemOptions = systemPresets.map(styleName => `<option value="${styleName}">${styleName}</option>`);
const storedStyles = Object.keys(localStorage).filter(key => key.startsWith(customPresetPrefix));
const customOptions = storedStyles.map(styleName => `<option value="${styleName}">${styleName.replace(customPresetPrefix, "")} [custom]</option>`);
const options = systemOptions.join("") + customOptions.join("");
document.getElementById("stylePreset").innerHTML = options;
}
async function applyStyleOnLoad() {
const desiredPreset = localStorage.getItem("presetStyle") || "default";
const styleData = await getStylePreset(desiredPreset);
const [appliedPreset, style] = styleData;
applyStyle(style);
updateMapFilter();
stylePreset.value = stylePreset.dataset.old = appliedPreset;
setPresetRemoveButtonVisibiliy();
}
async function getStylePreset(desiredPreset) {
let presetToLoad = desiredPreset;
const isCustom = !systemPresets.includes(desiredPreset);
if (isCustom) {
const storedStyleJSON = localStorage.getItem(desiredPreset);
if (!storedStyleJSON) {
ERROR && console.error(`Custom style ${desiredPreset} in not found in localStorage. Applying default style`);
presetToLoad = "default";
} else {
const isValid = JSON.isValid(storedStyleJSON);
if (isValid) return [desiredPreset, JSON.parse(storedStyleJSON)];
ERROR && console.error(`Custom style ${desiredPreset} stored in localStorage is not valid. Applying default style`);
presetToLoad = "default";
}
}
const style = await fetchSystemPreset(presetToLoad);
return [presetToLoad, style];
}
async function fetchSystemPreset(preset) {
const style = await fetch(`./styles/${preset}.json`)
.then(res => res.json())
.catch(err => {
ERROR && console.error("Error on loading style preset", preset, err);
return null;
});
if (!style) throw new Error("Cannot fetch style preset", preset);
return style;
}
function applyStyle(style) {
for (const selector in style) {
const el = document.querySelector(selector);
if (!el) continue;
for (const attribute in style[selector]) {
const value = style[selector][attribute];
if (value === "null" || value === null) {
el.removeAttribute(attribute);
continue;
}
if (attribute === "text-shadow") {
el.style[attribute] = value;
} else {
el.setAttribute(attribute, value);
}
}
}
}
function requestStylePresetChange(preset) {
const isConfirmed = sessionStorage.getItem("styleChangeConfirmed");
if (isConfirmed) {
changeStyle(preset);
return;
}
confirmationDialog({
title: "Change style preset",
message: "Are you sure you want to change the style preset? All unsaved style changes will be lost",
confirm: "Change",
onConfirm: () => {
sessionStorage.setItem("styleChangeConfirmed", true);
changeStyle(preset);
},
onCancel: () => {
stylePreset.value = stylePreset.dataset.old;
}
});
}
async function changeStyle(desiredPreset) {
const styleData = await getStylePreset(desiredPreset);
const [appliedPreset, style] = styleData;
localStorage.setItem("presetStyle", appliedPreset);
applyStyleWithUiRefresh(style);
}
function applyStyleWithUiRefresh(style) {
applyStyle(style);
updateElements();
selectStyleElement(); // re-select element to trigger values update
updateMapFilter();
stylePreset.dataset.old = stylePreset.value;
invokeActiveZooming();
setPresetRemoveButtonVisibiliy();
}
function addStylePreset() {
$("#styleSaver").dialog({title: "Style Saver", width: "26em", position: {my: "center", at: "center", of: "svg"}});
const styleName = stylePreset.value.replace(customPresetPrefix, "");
document.getElementById("styleSaverName").value = styleName;
styleSaverJSON.value = JSON.stringify(collectStyleData(), null, 2);
checkName();
if (modules.saveStyle) return;
modules.saveStyle = true;
// add listeners
document.getElementById("styleSaverName").addEventListener("input", checkName);
document.getElementById("styleSaverSave").addEventListener("click", saveStyle);
document.getElementById("styleSaverDownload").addEventListener("click", styleDownload);
document.getElementById("styleSaverLoad").addEventListener("click", () => styleToLoad.click());
document.getElementById("styleToLoad").addEventListener("change", loadStyleFile);
function collectStyleData() {
const style = {};
const attributes = {
"#map": ["background-color", "filter", "data-filter"],
"#armies": ["font-size", "box-size", "stroke", "stroke-width", "fill-opacity", "filter"],
"#biomes": ["opacity", "filter", "mask"],
"#stateBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#provinceBorders": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#cells": ["opacity", "stroke", "stroke-width", "filter", "mask"],
"#gridOverlay": ["opacity", "scale", "dx", "dy", "type", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "transform", "filter", "mask"],
"#coordinates": ["opacity", "data-size", "font-size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#compass": ["opacity", "transform", "filter", "mask", "shape-rendering"],
"#rose": ["transform"],
"#relig": ["opacity", "stroke", "stroke-width", "filter"],
"#cults": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#landmass": ["opacity", "fill", "filter"],
"#markers": ["opacity", "rescale", "filter"],
"#prec": ["opacity", "stroke", "stroke-width", "fill", "filter"],
"#population": ["opacity", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#rural": ["stroke"],
"#urban": ["stroke"],
"#freshwater": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#salt": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#sinkhole": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#frozen": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#lava": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#dry": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#sea_island": ["opacity", "stroke", "stroke-width", "filter", "auto-filter"],
"#lake_island": ["opacity", "stroke", "stroke-width", "filter"],
"#terrain": ["opacity", "set", "size", "density", "filter", "mask"],
"#rivers": ["opacity", "filter", "fill"],
"#ruler": ["opacity", "filter"],
"#roads": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#trails": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#searoutes": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#statesBody": ["opacity", "filter"],
"#statesHalo": ["opacity", "data-width", "stroke-width", "filter"],
"#provs": ["opacity", "fill", "font-size", "font-family", "filter"],
"#temperature": ["opacity", "font-size", "fill", "fill-opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter"],
"#ice": ["opacity", "fill", "stroke", "stroke-width", "filter"],
"#emblems": ["opacity", "stroke-width", "filter"],
"#texture": ["opacity", "filter", "mask"],
"#textureImage": ["x", "y"],
"#zones": ["opacity", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "filter", "mask"],
"#oceanLayers": ["filter", "layers"],
"#oceanBase": ["fill"],
"#oceanicPattern": ["href", "opacity"],
"#terrs": ["opacity", "scheme", "terracing", "skip", "relax", "curve", "filter", "mask"],
"#legend": ["data-size", "font-size", "font-family", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap", "data-x", "data-y", "data-columns"],
"#legendBox": ["fill", "fill-opacity"],
"#burgLabels > #cities": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
"#burgIcons > #cities": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
"#anchors > #cities": ["opacity", "fill", "size", "stroke", "stroke-width"],
"#burgLabels > #towns": ["opacity", "fill", "text-shadow", "data-size", "font-size", "font-family"],
"#burgIcons > #towns": ["opacity", "fill", "fill-opacity", "size", "stroke", "stroke-width", "stroke-dasharray", "stroke-linecap"],
"#anchors > #towns": ["opacity", "fill", "size", "stroke", "stroke-width"],
"#labels > #states": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
"#labels > #addedLabels": ["opacity", "fill", "stroke", "stroke-width", "text-shadow", "data-size", "font-size", "font-family", "filter"],
"#fogging": ["opacity", "fill", "filter"]
};
for (const selector in attributes) {
const el = document.querySelector(selector);
if (!el) continue;
style[selector] = {};
for (const attr of attributes[selector]) {
let value = el.style[attr] || el.getAttribute(attr);
if (attr === "font-size" && el.hasAttribute("data-size")) value = el.getAttribute("data-size");
style[selector][attr] = parseValue(value);
}
}
function parseValue(value) {
if (value === "null" || value === null) return null;
if (value === "") return "";
if (!isNaN(+value)) return +value;
return value;
}
return style;
}
function checkName() {
const styleName = customPresetPrefix + styleSaverName.value;
const isSystem = systemPresets.includes(styleName) || systemPresets.includes(styleSaverName.value);
if (isSystem) return (styleSaverTip.innerHTML = "default");
const isExisting = Array.from(stylePreset.options).some(option => option.value == styleName);
if (isExisting) return (styleSaverTip.innerHTML = "existing");
styleSaverTip.innerHTML = "new";
}
function saveStyle() {
const styleJSON = styleSaverJSON.value;
const desiredName = styleSaverName.value;
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
if (!desiredName) return tip("Please provide a preset name", false, "error");
if (styleSaverTip.innerHTML === "default") return tip("You cannot overwrite default preset, please change the name", false, "error");
const presetName = customPresetPrefix + desiredName;
applyOption(stylePreset, presetName, desiredName + " [custom]");
localStorage.setItem("presetStyle", presetName);
localStorage.setItem(presetName, styleJSON);
applyStyleWithUiRefresh(JSON.parse(styleJSON));
tip("Style preset is saved and applied", false, "success", 4000);
$("#styleSaver").dialog("close");
}
function styleDownload() {
const styleJSON = styleSaverJSON.value;
const styleName = styleSaverName.value;
if (!styleJSON) return tip("Please provide a style JSON", false, "error");
if (!JSON.isValid(styleJSON)) return tip("JSON string is not valid, please check the format", false, "error");
if (!styleName) return tip("Please provide a preset name", false, "error");
downloadFile(styleJSON, styleName + ".json", "application/json");
}
function loadStyleFile() {
const fileName = this.files[0]?.name.replace(/\.[^.]*$/, "");
uploadFile(this, styleUpload);
function styleUpload(dataLoaded) {
if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
const isValid = JSON.isValid(dataLoaded);
if (!isValid) return tip("Loaded data is not a valid JSON, please check the format", false, "error");
styleSaverJSON.value = JSON.stringify(JSON.parse(dataLoaded), null, 2);
styleSaverName.value = fileName;
checkName();
tip("Style preset is uploaded", false, "success", 4000);
}
}
}
function requestRemoveStylePreset() {
const isDefault = systemPresets.includes(stylePreset.value);
if (isDefault) return tip("Cannot remove system preset", false, "error");
confirmationDialog({
title: "Remove style preset",
message: "Are you sure you want to remove the style preset? This action cannot be undone.",
confirm: "Remove",
onConfirm: removeStylePreset
});
}
function removeStylePreset() {
localStorage.removeItem("presetStyle");
localStorage.removeItem(stylePreset.value);
stylePreset.selectedOptions[0].remove();
changeStyle("default");
}
function updateMapFilter() {
const filter = svg.attr("data-filter");
mapFilters.querySelectorAll(".pressed").forEach(button => button.classList.remove("pressed"));
if (!filter) return;
mapFilters.querySelector("#" + filter).classList.add("pressed");
}
function setPresetRemoveButtonVisibiliy() {
const isDefault = systemPresets.includes(stylePreset.value);
removeStyleButton.style.display = isDefault ? "none" : "inline-block";
}

View file

@ -0,0 +1,161 @@
"use strict";
function showBurgTemperatureGraph(id) {
const b = pack.burgs[id];
const lat = mapCoordinates.latN - (b.y / graphHeight) * mapCoordinates.latT;
const burgTemp = grid.cells.temp[pack.cells.g[b.cell]];
const prec = grid.cells.prec[pack.cells.g[b.cell]];
// prettier-ignore
const weights = [
[
[10.782752257744338, 2.7100404240962126], [-2.8226802110591462, 51.62920138583541], [-6.6250956268643835, 4.427939197315455], [-59.64690518541339, 41.89084162654791], [-1.3302059550553835, -3.6964487738450913],
[-2.5844898544535497, 0.09879268612455298], [-5.58528252533573, -0.23426224364501905], [26.94531337690372, 20.898158905988907], [3.816397481634785, -0.19045424064580757], [-4.835697931609101, -10.748232783636434]
],
[
[-2.478952081870123, 0.6405800134306895, -7.136785640930911, -0.2186529024764509, 3.6568435212735424, 31.446026153530838, -19.91005187482281, 0.2543395274783306, -7.036924569659988, -0.7721371621651565],
[-197.10583739743538, 6.889921141533474, 0.5058941504631129, 7.7667203434606416, -53.74180550086929, -15.717331715167001, -61.32068414155791, -2.259728220978728, 35.84049189540032, 94.6157364730977],
[-5.312011591880851, -0.09923148954215096, -1.7132477487917586, -22.55559652066422, 0.4806107280554336, -26.5583974109492, 2.0558257347014863, 25.815645234787432, -18.569029876991156, -2.6792003366730035],
[20.706518520569514, 18.344297403881875, 99.52244671131733, -58.53124969563653, -60.74384321042212, -80.57540534651835, 7.884792406540866, -144.33871131678563, 80.134199744324, 20.50745285622448],
[-52.88299538575159, -15.782505343805528, 16.63316001054924, 88.09475330556671, -17.619552086641818, -19.943999528182427, -120.46286026828177, 19.354752020806302, 43.49422099308949, 28.733924806541363],
[-2.4621368711159897, -1.2074759925679757, -1.5133898639835084, 2.173715352424188, -5.988707597991683, 3.0234147182203843, 3.3284199340000797, -1.8805161326360575, 5.151910934121654, -1.2540553911612116]
],
[
[-0.3357437479474717, 0.01430651794222215, -0.7927524256670906, 0.2121636229648523, 1.0587803023358318, -3.759288325505095],
[-1.1988028704442968, 1.3768997508052783, -3.8480086358278816, 0.5289387340947143, 0.5769459339961177, -1.2528318145750772],
[1.0074966649240946, 1.155301164699459, -2.974254371052421, 0.47408176553219467, 0.5939042688615264, -0.7631976947131744]
]
];
// From (-∞, ∞) to ~[-1, 1]
const In1 = [(Math.abs(lat) - 26.950680212887473) / 48.378128506956, (prec - 12.229929140832644) / 29.94402033696607];
let lastIn = In1;
let lstOut = [];
for (let levelN = 0; levelN < weights.length; levelN++) {
const layerN = weights[levelN];
for (let i = 0; i < layerN.length; i++) {
lstOut[i] = 0;
for (let j = 0; j < layerN[i].length; j++) {
lstOut[i] = lstOut[i] + lastIn[j] * layerN[i][j];
}
// sigmoid
lstOut[i] = 1 / (1 + Math.exp(-lstOut[i]));
}
lastIn = lstOut.slice(0);
}
// Standard deviation for average temperature for the year from [0, 1] to [min, max]
const yearSig = lstOut[0] * 62.9466411977018 + 0.28613807855649165;
// Standard deviation for the difference between the minimum and maximum temperatures for the year
const yearDelTmpSig = lstOut[1] * 13.541688670361175 + 0.1414213562373084 > yearSig ? yearSig : lstOut[1] * 13.541688670361175 + 0.1414213562373084;
// Expected value for the difference between the minimum and maximum temperatures for the year
const yearDelTmpMu = lstOut[2] * 15.266666666666667 + 0.6416666666666663;
// Temperature change shape
const delT = yearDelTmpMu / 2 + (0.5 * yearDelTmpSig) / 2;
const minT = burgTemp - Math.max(yearSig + delT, 15);
const maxT = burgTemp + (burgTemp - minT);
const chartWidth = Math.max(window.innerWidth / 2, 580);
const chartHeight = 300;
// drawing starting point from top-left (y = 0) of SVG
const xOffset = 60;
const yOffset = 10;
const year = new Date().getFullYear(); // use current year
const startDate = new Date(year, 0, 1);
const endDate = new Date(year, 11, 31);
const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
const xscale = d3.scaleTime().domain([startDate, endDate]).range([0, chartWidth]);
const yscale = d3.scaleLinear().domain([minT, maxT]).range([chartHeight, 0]);
const tempMean = [];
const tempMin = [];
const tempMax = [];
months.forEach((month, index) => {
const rate = index / 11;
let formTmp = Math.cos(rate * 2 * Math.PI) / 2;
if (lat > 0) formTmp = -formTmp;
const x = rate * chartWidth + xOffset;
const tempAverage = formTmp * yearSig + burgTemp;
const tempDelta = yearDelTmpMu / 2 + (formTmp * yearDelTmpSig) / 2;
tempMean.push([x, yscale(tempAverage) + yOffset]);
tempMin.push([x, yscale(tempAverage - tempDelta) + yOffset]);
tempMax.push([x, yscale(tempAverage + tempDelta) + yOffset]);
});
drawGraph();
$("#alert").dialog({title: "Annual temperature in " + b.name, width: "auto", position: {my: "center", at: "center", of: "svg"}});
function drawGraph() {
alertMessage.innerHTML = "";
const getCurve = data => round(d3.line().curve(d3.curveBasis)(data), 2);
const legendSize = 60;
const chart = d3
.select("#alertMessage")
.append("svg")
.attr("width", chartWidth + 120)
.attr("height", chartHeight + yOffset + legendSize);
const legend = chart.append("g");
const legendY = chartHeight + yOffset + legendSize * 0.8;
const legendX = n => (chartWidth * n) / 4;
const legendTextX = n => legendX(n) + 10;
legend.append("circle").attr("cx", legendX(1)).attr("cy", legendY).attr("r", 4).style("fill", "red");
legend.append("text").attr("x", legendTextX(1)).attr("y", legendY).attr("alignment-baseline", "central").text("Day temperature");
legend.append("circle").attr("cx", legendX(2)).attr("cy", legendY).attr("r", 4).style("fill", "orange");
legend.append("text").attr("x", legendTextX(2)).attr("y", legendY).attr("alignment-baseline", "central").text("Mean temperature");
legend.append("circle").attr("cx", legendX(3)).attr("cy", legendY).attr("r", 4).style("fill", "blue");
legend.append("text").attr("x", legendTextX(3)).attr("y", legendY).attr("alignment-baseline", "central").text("Night temperature");
const xGrid = d3.axisBottom(xscale).ticks().tickSize(-chartHeight);
const yGrid = d3.axisLeft(yscale).ticks(5).tickSize(-chartWidth);
const grid = chart.append("g").attr("class", "epgrid").attr("stroke-dasharray", "4 1");
grid.append("g").attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`).call(xGrid); // prettier-ignore
grid.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yGrid);
grid.selectAll("text").remove();
// add zero degree line
if (minT < 0 && maxT > 0) {
grid
.append("line")
.attr("x1", xOffset)
.attr("y1", yscale(0) + yOffset)
.attr("x2", chartWidth + xOffset)
.attr("y2", yscale(0) + yOffset)
.attr("stroke", "gray");
}
const xAxis = d3.axisBottom(xscale).ticks().tickFormat(d3.timeFormat("%B"));
const yAxis = d3.axisLeft(yscale).ticks(5).tickFormat(convertTemperature);
const axis = chart.append("g");
axis
.append("g")
.attr("transform", `translate(${xOffset}, ${chartHeight + yOffset})`)
.call(xAxis);
axis.append("g").attr("transform", `translate(${xOffset}, ${yOffset})`).call(yAxis);
axis.select("path.domain").attr("d", `M0.5,0.5 H${chartWidth + 0.5}`);
const curves = chart.append("g").attr("fill", "none").style("stroke-width", 2.5);
curves.append("path").attr("d", getCurve(tempMean)).attr("data-type", "daily").attr("stroke", "orange").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMin)).attr("data-type", "night").attr("stroke", "blue").on("mousemove", printVal);
curves.append("path").attr("d", getCurve(tempMax)).attr("data-type", "day").attr("stroke", "red").on("mousemove", printVal);
function printVal() {
const [x, y] = d3.mouse(this);
const type = this.getAttribute("data-type");
const temp = convertTemperature(yscale.invert(y - yOffset));
const month = months[rn(((x - xOffset) / chartWidth) * 12)] || months[0];
tip(`Average ${type} temperature in ${month}: ${temp}`);
}
}
}

View file

@ -143,7 +143,7 @@ function regenerateStates() {
const localSeed = Math.floor(Math.random() * 1e9); // new random seed
Math.random = aleaPRNG(localSeed);
const statesCount = +regionsInput.value;
const statesCount = +regionsOutput.value;
const burgs = pack.burgs.filter(b => b.i && !b.removed);
if (!burgs.length) return tip("There are no any burgs to generate states. Please create burgs first", false, "error");
if (burgs.length < statesCount) tip(`Not enough burgs to generate ${statesCount} states. Will generate only ${burgs.length} states`, false, "warn");
@ -624,7 +624,9 @@ function addRiverOnClick() {
const source = riverCells[0];
const mouth = riverCells[riverCells.length - 2];
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? 1.2 : 1);
const defaultWidthFactor = rn(1 / (pointsInput.dataset.cells / 10000) ** 0.25, 2);
const widthFactor = river?.widthFactor || (!parent || parent === riverId ? defaultWidthFactor * 1.2 : defaultWidthFactor);
const meanderedPoints = addMeandering(riverCells);
const discharge = cells.fl[mouth]; // m3 in second

View file

@ -37,26 +37,12 @@ function editZones() {
const el = ev.target,
cl = el.classList,
zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) {
changePopulation(zone);
return;
}
if (cl.contains("icon-trash-empty")) {
zoneRemove(zone);
return;
}
if (cl.contains("icon-eye")) {
toggleVisibility(el);
return;
}
if (cl.contains("icon-pin")) {
toggleFog(zone, cl);
return;
}
if (cl.contains("fillRect")) {
changeFill(el);
return;
}
if (el.tagName === "FILL-BOX") changeFill(el);
else if (cl.contains("culturePopulation")) changePopulation(zone);
else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
else if (cl.contains("icon-eye")) toggleVisibility(el);
else if (cl.contains("icon-pin")) toggleFog(zone, cl);
if (customization) selectZone(el);
});
@ -131,8 +117,9 @@ function editZones() {
const inactive = this.style.display === "none";
const focused = defs.select("#fog #focus" + this.id).size();
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}" data-cells=${c.length} data-area=${area} data-population=${population}>
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
lines += `<div class="states" data-id="${this.id}" data-fill="${fill}" data-description="${description}"
data-cells=${c.length} data-area=${area} data-population=${population}>
<fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
<span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.length}</div>
@ -337,9 +324,9 @@ function editZones() {
function changeFill(el) {
const fill = el.getAttribute("fill");
const callback = function (fill) {
el.setAttribute("fill", fill);
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill);
const callback = newFill => {
el.fill = newFill;
document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill);
};
openPicker(fill, callback);
@ -411,12 +398,12 @@ function editZones() {
function addZonesLayer() {
const id = getNextId("zone");
const description = "Unknown zone";
const fill = "url(#hatch" + (id.slice(4) % 14) + ")";
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill).attr("data-type", zoneTypes[0]);
const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill);
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0>
<svg data-tip="Zone fill style. Click to change" width=".9em" height=".9em" style="margin-bottom:-1px"><rect x="0" y="0" width="100%" height="100%" fill="${fill}" class="fillRect pointer"></svg>
<fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false">
<span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">0</div>

View file

@ -1,3 +0,0 @@
start chrome.exe http://localhost:8000/
@echo off
php -S localhost:8000

389
styles/ancient.json Normal file
View file

@ -0,0 +1,389 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.2,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.2,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.5,
"transform": null,
"filter": "url(#filter-sepia)",
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#e3dfce",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": ""
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.6,
"fill": "#c8d6e0",
"stroke": "#968d6e",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#339482",
"stroke": "#836a34",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#c3d6df",
"stroke": "#b29062",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#a04e18",
"stroke": "#835520",
"stroke-width": 2,
"filter": "url(#paper)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c6b795",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": "",
"fill": "#a69b7d"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.7,
"stroke": "#8d502a",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "inherit",
"filter": "",
"mask": null
},
"#trails": {
"opacity": 0.7,
"stroke": "#924217",
"stroke-width": 0.5,
"stroke-dasharray": "1 2",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#b16925",
"stroke-width": 0.8,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.2,
"filter": "url(#filter-sepia)"
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(6px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.35,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 3,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.8,
"stroke-width": 0.8,
"filter": "url(#dropShadow05)"
},
"#texture": {
"opacity": 0.6,
"filter": "",
"mask": ""
},
"#textureImage": {
"x": 0,
"y": 0
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#c99f64"
},
"#oceanicPattern": {
"href": "./images/kiwiroo.png",
"opacity": 0.4
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 2,
"relax": 1,
"curve": 0,
"filter": "url(#blur3)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 12,
"font-size": 12,
"font-family": "Great Vibes"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#fdfab9",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#6f4e1f",
"stroke-width": 0.3,
"stroke-dasharray": ".3 .4",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 5,
"font-size": 5,
"font-family": "Great Vibes"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#fef4d8",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#72472c",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Great Vibes",
"filter": "url(#filter-sepia)"
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Times New Roman",
"filter": "url(#filter-sepia)"
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

385
styles/atlas.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "u rl(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#000000",
"stroke-width": 1.01,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#000000",
"stroke-width": 0.69,
"stroke-dasharray": 0,
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 1,
"scale": 7.99,
"dx": -2,
"dy": 3,
"type": "square",
"stroke": "#000000",
"stroke-width": 0.05,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "u rl(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "u rl(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#salt": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#cae3f7",
"stroke": "#0089ca",
"stroke-width": 1.01,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "u rl(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#028ac9",
"stroke-width": 1.01,
"filter": "",
"auto-filter": 0
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#0089ca"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#ff2c2c",
"stroke-width": 1.05,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#9f5122",
"stroke-width": 0.43,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 1,
"stroke": "#0089ca",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.49,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 1,
"fill": "#000000",
"font-size": 10,
"font-family": "Times New Roman",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "14px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": ""
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "u rl(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "u rl(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "none"
},
"#oceanBase": {
"fill": "#b4d2f3"
},
"#oceanicPattern": {
"href": "",
"opacity": 1
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "u rl(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#000000",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "Questrial"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#000000",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#000000",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "round"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#000000",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Questrial"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#000000",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#000000",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "round"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 0.7,
"fill": "#000000",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 16,
"font-size": 16,
"font-family": "Times New Roman",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#000000",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Questrial",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

388
styles/clean.json Normal file
View file

@ -0,0 +1,388 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.5,
"filter": "url(#blur7)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#414141",
"stroke-width": 0.7,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#414141",
"stroke-width": 0.45,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.09,
"filter": null,
"mask": "url(#land)"
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#414141",
"stroke-width": 0.45,
"stroke-dasharray": 3,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eeedeb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": null,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0,
"fill": "#0080ff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 2.58,
"stroke-dasharray": 0,
"stroke-linecap": "butt",
"filter": "url(#blur3)"
},
"#rural": {
"stroke": "#ff0000"
},
"#urban": {
"stroke": "#800000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#aadaff",
"stroke": "#5f799d",
"stroke-width": 0,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#595959",
"stroke-width": 0.4,
"filter": null,
"auto-filter": 0
},
"#lake_island": {
"opacity": 0,
"stroke": "#7c8eaf",
"stroke-width": 0,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#aadaff"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#f6d068",
"stroke-width": 0.7,
"stroke-dasharray": 0,
"stroke-linecap": "inherit",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#ffffff",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#4f82c6",
"stroke-width": 0.45,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": "url(#water)"
},
"#statesBody": {
"opacity": 0.3,
"filter": null
},
"#statesHalo": {
"opacity": 0.5,
"data-width": 1,
"stroke-width": 1,
"filter": null
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow01)"
},
"#emblems": {
"opacity": 1,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#textureImage": {},
"#zones": {
"opacity": 0.7,
"stroke": "#ff6262",
"stroke-width": 0,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "none"
},
"#oceanBase": {
"fill": "#aadaff"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.2
},
"#terrs": {
"opacity": 0.5,
"scheme": "bright",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 12.74,
"font-size": 12.74,
"font-family": "Arial",
"stroke": "#909090",
"stroke-width": 1.13,
"stroke-dasharray": 0,
"stroke-linecap": "round",
"data-x": 98.39,
"data-y": 12.67,
"data-columns": null
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#414141",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Arial"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#303030",
"stroke-width": 1.7
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#414141",
"data-size": 3,
"font-size": 3,
"font-family": "Arial"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.06
},
"#labels > #states": {
"opacity": 1,
"fill": "#292929",
"stroke": "#303030",
"stroke-width": 0,
"text-shadow": "white 0 0 2px",
"data-size": 10,
"font-size": 10,
"font-family": "Arial",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#414141",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Arial",
"filter": null
},
"#fogging": {
"opacity": 1,
"fill": "#ffffff",
"filter": null
}
}

385
styles/cyberpunk.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000000",
"stroke-width": 0.6,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.7,
"filter": "",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#ffffff",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "round",
"filter": ""
},
"#provinceBorders": {
"opacity": 0.5,
"stroke": "#ffffff",
"stroke-width": 0.3,
"stroke-dasharray": 1,
"stroke-linecap": "round",
"filter": ""
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 14,
"font-size": 14,
"stroke": "#4a4a4a",
"stroke-width": 1,
"stroke-dasharray": 6,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.9,
"transform": null,
"filter": null,
"mask": "",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.5,
"stroke": "#404040",
"stroke-width": 2,
"filter": "url(#splotch)"
},
"#cults": {
"opacity": 0.35,
"stroke": "#777777",
"stroke-width": 2,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": "url(#splotch)"
},
"#landmass": {
"opacity": 1,
"fill": "#04011e",
"filter": null
},
"#markers": {
"opacity": 0.8,
"rescale": 1,
"filter": "url(#dropShadow05)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "square",
"filter": null
},
"#rural": {
"stroke": "#5294ff"
},
"#urban": {
"stroke": "#5cdeff"
},
"#freshwater": {
"opacity": 0.9,
"fill": "#381579",
"stroke": "#47228c",
"stroke-width": 3,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.9,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#6738bc"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#c44ac0",
"stroke-width": 0.9,
"stroke-dasharray": "2 3",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#df2654",
"stroke-width": 0.2,
"stroke-dasharray": ".5 1",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#a890c6",
"stroke-width": 0.6,
"stroke-dasharray": "1.2 2.4",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0,
"filter": null
},
"#statesHalo": {
"opacity": 1,
"data-width": 13,
"stroke-width": 13,
"filter": "blur(8.25px)"
},
"#provs": {
"opacity": 0.2,
"fill": "#933e3e",
"font-size": 8,
"font-family": "Orbitron",
"filter": ""
},
"#temperature": {
"opacity": 0.8,
"font-size": "22px",
"fill": "#551282",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 3,
"stroke-dasharray": 2,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.3,
"fill": "#919191",
"stroke": "#949494",
"stroke-width": 0,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.75,
"stroke-width": 0.5,
"filter": ""
},
"#texture": {
"opacity": 0.14,
"filter": null,
"mask": "url(#water)"
},
"#zones": {
"opacity": 0.7,
"stroke": "#ffffff",
"stroke-width": 0.3,
"stroke-dasharray": null,
"stroke-linecap": "inherit",
"filter": "url(#dropShadow05)",
"mask": null
},
"#oceanLayers": {
"filter": "",
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#05001f"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.15
},
"#terrs": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 6,
"skip": 0,
"relax": 2,
"curve": 0,
"filter": "",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#ffffff",
"text-shadow": "white 0px 0px 4px",
"data-size": 8,
"font-size": 8,
"font-family": "Orbitron"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 2,
"stroke": "#444444",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 4,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#ffffff",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Orbitron"
},
"#burgIcons > #towns": {
"opacity": 0.95,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.8,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1.6,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#labels > #states": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Orbitron",
"filter": ""
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#1b1423",
"filter": null
}
}

385
styles/default.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.5,
"stroke-dasharray": "0 2",
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#5d97bb"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.7,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.25,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#466eab"
},
"#oceanicPattern": {
"href": "./images/pattern1.png",
"opacity": 0.2
},
"#terrs": {
"opacity": null,
"scheme": "bright",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Almendra SC"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "Almendra SC"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

391
styles/gloom.json Normal file
View file

@ -0,0 +1,391 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": "url(#blur5)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 0.3,
"stroke-dasharray": ".7 1",
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 14,
"font-size": 14,
"stroke": "#4a4a4a",
"stroke-width": 1,
"stroke-dasharray": 6,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.6,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(100 100) scale(0.3)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 1,
"filter": null
},
"#cults": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 1.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#e0e0e0",
"filter": null
},
"#markers": {
"opacity": 0.8,
"rescale": 1,
"filter": "url(#dropShadow05)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000aa"
},
"#urban": {
"stroke": "#9d0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 0.7,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.6,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.9,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#779582"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 1,
"stroke": "#8b4418",
"stroke-width": 0.9,
"stroke-dasharray": "2 3",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 1,
"stroke": "#844017",
"stroke-width": 0.2,
"stroke-dasharray": ".5 1",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#5e1865",
"stroke-width": 0.6,
"stroke-dasharray": "1.2 2.4",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.5,
"data-width": 12,
"stroke-width": 12,
"filter": "blur(10px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": 1,
"font-size": "11px",
"fill": "#62001b",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 2,
"stroke-dasharray": 2,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.6,
"stroke-width": 0.5,
"filter": null
},
"#texture": {
"opacity": null,
"filter": null,
"mask": "url(#land)"
},
"#textureImage": {
"x": 0,
"y": 0
},
"#zones": {
"opacity": 0.5,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": "url(#dropShadow01)",
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#4e6964"
},
"#oceanicPattern": {
"href": "./images/pattern3.png",
"opacity": 0.2
},
"#terrs": {
"opacity": 1,
"scheme": "bright",
"terracing": 0,
"skip": 0,
"relax": 1,
"curve": 1,
"filter": "url(#filter-grayscale)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Bitter"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 2,
"stroke": "#444444",
"stroke-width": 0.25,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 0.8,
"fill": "#ffffff",
"size": 4,
"stroke": "#3e3e4b",
"stroke-width": 1
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"data-size": 3,
"font-size": 3,
"font-family": "Bitter"
},
"#burgIcons > #towns": {
"opacity": 0.95,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.8,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1.6,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#4e4e4e",
"stroke": "#b5b5b5",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#1b1423",
"filter": null
}
}

385
styles/light.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.02,
"fill-opacity": 0.8,
"filter": null
},
"#biomes": {
"opacity": 0.5,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.8,
"stroke": "#4c483e",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "square",
"filter": null
},
"#provinceBorders": {
"opacity": 0.8,
"stroke": "#56566d",
"stroke-width": 0.2,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.5,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 1,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 0.7,
"data-size": 15,
"font-size": 15,
"stroke": "#734d37",
"stroke-width": 1.5,
"stroke-dasharray": 5,
"stroke-linecap": "square",
"filter": null,
"mask": ""
},
"#compass": {
"opacity": 0.6,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.5,
"stroke": null,
"stroke-width": 0,
"filter": null
},
"#cults": {
"opacity": 0.5,
"stroke": "#777777",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#f9f2ea",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": null
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#2554ef",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#98cdc4",
"stroke": "#719892",
"stroke-width": 0.46,
"filter": "url(#dropShadow05)"
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#5e5e5e",
"stroke-width": 0.4,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 0.6,
"set": "colored",
"size": 1,
"density": 0.3,
"filter": null,
"mask": ""
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#6d94ba"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#3c1d0b",
"stroke-width": 1.37,
"stroke-dasharray": 2,
"stroke-linecap": "inherit",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#95481a",
"stroke-width": 0.88,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.2,
"filter": null
},
"#statesHalo": {
"opacity": 0.3,
"data-width": 25,
"stroke-width": 25,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.4,
"fill": "#000000",
"font-size": 5,
"font-family": "IM Fell English",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.5,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1.5,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.9,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": 0.39,
"filter": null,
"mask": ""
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": "url(#dropShadow05)",
"layers": "-6,-3,-1"
},
"#oceanBase": {
"fill": "#8dc1c8"
},
"#oceanicPattern": {
"href": "./images/pattern1.png",
"opacity": 0.2
},
"#terrs": {
"opacity": 0.4,
"scheme": "light",
"terracing": 10,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": "url(#turbulence)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 54.73,
"data-y": 62.98,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3a3a3a",
"text-shadow": "white 0px 0px 4px",
"data-size": 8,
"font-size": 8,
"font-family": "IM Fell English"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 3,
"stroke": "#3e3e4b",
"stroke-width": 0.4,
"stroke-dasharray": "0.5 0.25",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 5.5,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 4,
"font-size": 4,
"font-family": "IM Fell English"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1.2,
"stroke": "#3e3e4b",
"stroke-width": 0.2,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 2.2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e3e",
"stroke": "#000000",
"stroke-width": 0.3,
"text-shadow": "white 0px 0px 6px",
"data-size": 14,
"font-size": 14,
"font-family": "IM Fell English",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#f24706",
"stroke": "#701b05",
"stroke-width": 0.1,
"text-shadow": "white 0px 0px 4px",
"data-size": 6,
"font-size": 6,
"font-family": "IM Fell English",
"filter": null
},
"#fogging": {
"opacity": 1,
"fill": "#30426f",
"filter": null
}
}

381
styles/monochrome.json Normal file
View file

@ -0,0 +1,381 @@
{
"#map": {
"background-color": "#000000",
"filter": "url(#filter-grayscale)",
"data-filter": "grayscale"
},
"#armies": {
"font-size": 6,
"box-size": 3,
"stroke": "#000",
"stroke-width": 0.3,
"opacity": 1,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": null,
"filter": "url(#blur5)",
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 1,
"stroke": "#56566d",
"stroke-width": 0.4,
"stroke-dasharray": 1,
"stroke-linecap": "butt",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": "0",
"type": "pointyHex",
"stroke": "#808080",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": null
},
"#relig": {
"opacity": 0.7,
"stroke": "#404040",
"stroke-width": 0.7,
"filter": null
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#landmass": {
"opacity": 1,
"fill": "#000000",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 1,
"fill": "#000000",
"stroke": "#515151",
"stroke-width": 0,
"filter": null
},
"#salt": {
"opacity": 1,
"fill": "#000000",
"stroke": "#484848",
"stroke-width": 0,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#000000",
"stroke": "#5f5f5f",
"stroke-width": 0.5,
"filter": null
},
"#frozen": {
"opacity": 1,
"fill": "#000000",
"stroke": "#6f6f6f",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 1,
"fill": "#000000",
"stroke": "#5d5d5d",
"stroke-width": 0,
"filter": null
},
"#sea_island": {
"opacity": 1,
"stroke": "#1f3846",
"stroke-width": 0,
"filter": null,
"auto-filter": 0
},
"#lake_island": {
"opacity": 0,
"stroke": "#7c8eaf",
"stroke-width": 0,
"filter": null
},
"#terrain": {
"opacity": null,
"set": "simple",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": 0.2,
"filter": "url(#blur1)",
"fill": "#000000"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.7,
"stroke-dasharray": 2,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#d06324",
"stroke-width": 0.25,
"stroke-dasharray": ".8 1.6",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.8,
"stroke": "#ffffff",
"stroke-width": 0.45,
"stroke-dasharray": "1 2",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.4,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 10,
"stroke-width": 10,
"filter": "blur(5px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"data-size": 10,
"font-size": 10,
"font-family": "Georgia",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.9,
"fill": "#e8f0f6",
"stroke": "#e8f0f6",
"stroke-width": 1,
"filter": "url(#dropShadow05)"
},
"#texture": {
"opacity": 1,
"filter": null,
"mask": "url(#land)"
},
"#emblems": {
"opacity": 0.5,
"stroke-width": 0.5,
"filter": null
},
"#textureImage": {},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "none"
},
"#oceanBase": {
"fill": "#000000"
},
"#oceanicPattern": {
"href": "",
"opacity": 0.2
},
"#terrs": {
"opacity": 1,
"scheme": "monochrome",
"terracing": 0,
"skip": 5,
"relax": 0,
"curve": 0,
"filter": "url(#blur3)",
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#legendBox": {},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0 0 4px",
"data-size": 7,
"font-size": 7,
"font-family": "Almendra SC"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"data-size": 4,
"font-size": 4,
"font-family": "Almendra SC"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 22,
"font-size": 22,
"font-family": "Almendra SC",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0 0 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Almendra SC",
"filter": null
},
"#fogging": {
"opacity": 0.98,
"fill": "#30426f",
"filter": null
}
}

385
styles/watercolor.json Normal file
View file

@ -0,0 +1,385 @@
{
"#map": {
"background-color": "#000000",
"filter": null,
"data-filter": null
},
"#armies": {
"font-size": 8,
"box-size": 4,
"stroke": "#000",
"stroke-width": 0.2,
"fill-opacity": 1,
"filter": null
},
"#biomes": {
"opacity": 0.6,
"filter": null,
"mask": "url(#land)"
},
"#stateBorders": {
"opacity": 0.6,
"stroke": "#56566d",
"stroke-width": 1,
"stroke-dasharray": 3,
"stroke-linecap": "butt",
"filter": null
},
"#provinceBorders": {
"opacity": 0.5,
"stroke": "#56566d",
"stroke-width": 0.5,
"stroke-dasharray": "0 2",
"stroke-linecap": "round",
"filter": null
},
"#cells": {
"opacity": null,
"stroke": "#808080",
"stroke-width": 0.1,
"filter": null,
"mask": null
},
"#gridOverlay": {
"opacity": 0.8,
"scale": 1,
"dx": 0,
"dy": 0,
"type": "pointyHex",
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"transform": null,
"filter": null,
"mask": null
},
"#coordinates": {
"opacity": 1,
"data-size": 12,
"font-size": 12,
"stroke": "#d4d4d4",
"stroke-width": 1,
"stroke-dasharray": 5,
"stroke-linecap": null,
"filter": null,
"mask": null
},
"#compass": {
"opacity": 0.8,
"transform": null,
"filter": null,
"mask": "url(#water)",
"shape-rendering": "optimizespeed"
},
"#rose": {
"transform": "translate(80 80) scale(.25)"
},
"#relig": {
"opacity": 0.7,
"stroke": "#777777",
"stroke-width": 0,
"filter": "url(#bluredSplotch)"
},
"#cults": {
"opacity": 0.6,
"stroke": "#777777",
"stroke-width": 0.5,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": "url(#splotch)"
},
"#landmass": {
"opacity": 1,
"fill": "#eef6fb",
"filter": null
},
"#markers": {
"opacity": null,
"rescale": 1,
"filter": "url(#dropShadow01)"
},
"#prec": {
"opacity": null,
"stroke": "#000000",
"stroke-width": 0.1,
"fill": "#003dff",
"filter": null
},
"#population": {
"opacity": null,
"stroke-width": 1.6,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null
},
"#rural": {
"stroke": "#0000ff"
},
"#urban": {
"stroke": "#ff0000"
},
"#freshwater": {
"opacity": 0.5,
"fill": "#a6c1fd",
"stroke": "#5f799d",
"stroke-width": 0.7,
"filter": null
},
"#salt": {
"opacity": 0.5,
"fill": "#409b8a",
"stroke": "#388985",
"stroke-width": 0.7,
"filter": null
},
"#sinkhole": {
"opacity": 1,
"fill": "#5bc9fd",
"stroke": "#53a3b0",
"stroke-width": 0.7,
"filter": null
},
"#frozen": {
"opacity": 0.95,
"fill": "#cdd4e7",
"stroke": "#cfe0eb",
"stroke-width": 0,
"filter": null
},
"#lava": {
"opacity": 0.7,
"fill": "#90270d",
"stroke": "#f93e0c",
"stroke-width": 2,
"filter": "url(#crumpled)"
},
"#dry": {
"opacity": 1,
"fill": "#c9bfa7",
"stroke": "#8e816f",
"stroke-width": 0.7,
"filter": null
},
"#sea_island": {
"opacity": 0.5,
"stroke": "#1f3846",
"stroke-width": 0.7,
"filter": "url(#dropShadow)",
"auto-filter": 1
},
"#lake_island": {
"opacity": 1,
"stroke": "#7c8eaf",
"stroke-width": 0.35,
"filter": null
},
"#terrain": {
"opacity": 1,
"set": "gray",
"size": 1,
"density": 0.4,
"filter": null,
"mask": null
},
"#rivers": {
"opacity": null,
"filter": null,
"fill": "#2e89c2"
},
"#ruler": {
"opacity": null,
"filter": null
},
"#roads": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.7,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#trails": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.4,
"stroke-dasharray": "",
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#searoutes": {
"opacity": 0.9,
"stroke": "#969696",
"stroke-width": 0.7,
"stroke-dasharray": "",
"stroke-linecap": "round",
"filter": null,
"mask": null
},
"#statesBody": {
"opacity": 0.05,
"filter": null
},
"#statesHalo": {
"opacity": 0.4,
"data-width": 8,
"stroke-width": 8,
"filter": "blur(2px)"
},
"#provs": {
"opacity": 0.7,
"fill": "#000000",
"font-size": 4,
"font-family": "Comfortaa",
"filter": null
},
"#temperature": {
"opacity": null,
"font-size": "8px",
"fill": "#000000",
"fill-opacity": 0.3,
"stroke": null,
"stroke-width": 1.8,
"stroke-dasharray": null,
"stroke-linecap": null,
"filter": null
},
"#ice": {
"opacity": 0.7,
"fill": "#dfe8ec",
"stroke": "#000000",
"stroke-width": 0,
"filter": "url(#dropShadow05)"
},
"#emblems": {
"opacity": 0.95,
"stroke-width": 1,
"filter": null
},
"#texture": {
"opacity": 0.2,
"filter": null,
"mask": "url(#land)"
},
"#zones": {
"opacity": 0.6,
"stroke": "#333333",
"stroke-width": 0,
"stroke-dasharray": null,
"stroke-linecap": "butt",
"filter": null,
"mask": null
},
"#oceanLayers": {
"filter": null,
"layers": "-6,-4,-2"
},
"#oceanBase": {
"fill": "#2d788b"
},
"#oceanicPattern": {
"href": "./images/kiwiroo.png",
"opacity": 0.5
},
"#terrs": {
"opacity": 0.5,
"scheme": "light",
"terracing": 0,
"skip": 5,
"relax": 1,
"curve": 0,
"filter": null,
"mask": "url(#land)"
},
"#legend": {
"data-size": 13,
"font-size": 13,
"font-family": "Almendra SC",
"stroke": "#812929",
"stroke-width": 2.5,
"stroke-dasharray": "0 4 10 4",
"stroke-linecap": "round",
"data-x": 99,
"data-y": 93,
"data-columns": 8
},
"#burgLabels > #cities": {
"opacity": 1,
"fill": "#043449",
"text-shadow": "white 0px 0px 2px",
"data-size": 5,
"font-size": 5,
"font-family": "Comfortaa"
},
"#burgIcons > #cities": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 0.24,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #cities": {
"opacity": 1,
"fill": "#ffffff",
"size": 2,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#burgLabels > #towns": {
"opacity": 1,
"fill": "#3e3e4b",
"text-shadow": "white 0px 0px 4px",
"data-size": 3,
"font-size": 3,
"font-family": "Comfortaa"
},
"#burgIcons > #towns": {
"opacity": 1,
"fill": "#ffffff",
"fill-opacity": 0.7,
"size": 0.5,
"stroke": "#3e3e4b",
"stroke-width": 0.12,
"stroke-dasharray": "",
"stroke-linecap": "butt"
},
"#anchors > #towns": {
"opacity": 1,
"fill": "#ffffff",
"size": 1,
"stroke": "#3e3e4b",
"stroke-width": 1.2
},
"#labels > #states": {
"opacity": 1,
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": 0.15,
"text-shadow": "black 1px 1px 2px",
"data-size": 20,
"font-size": 20,
"font-family": "Gloria Hallelujah",
"filter": null
},
"#labels > #addedLabels": {
"opacity": 1,
"fill": "#3e3e4b",
"stroke": "#3a3a3a",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 18,
"font-size": 18,
"font-family": "Comfortaa",
"filter": null
},
"#fogging": {
"opacity": 0.97,
"fill": "#8398ce",
"filter": null
}
}

View file

@ -154,18 +154,23 @@ void (function () {
const prompt = document.getElementById("prompt");
const form = prompt.querySelector("#promptForm");
window.prompt = function (promptText = "Please provide an input", options = {default: 1, step: 0.01, min: 0, max: 100}, callback) {
if (options.default === undefined) {
ERROR && console.error("Prompt: options object does not have default value defined");
return;
}
const defaultText = "Please provide an input";
const defaultOptions = {default: 1, step: 0.01, min: 0, max: 100, required: true};
window.prompt = function (promptText = defaultText, options = defaultOptions, callback) {
if (options.default === undefined) return ERROR && console.error("Prompt: options object does not have default value defined");
const input = prompt.querySelector("#promptInput");
prompt.querySelector("#promptText").innerHTML = promptText;
const type = typeof options.default === "number" ? "number" : "text";
input.type = type;
if (options.step !== undefined) input.step = options.step;
if (options.min !== undefined) input.min = options.min;
if (options.max !== undefined) input.max = options.max;
input.required = options.required === false ? false : true;
input.placeholder = "type a " + type;
input.value = options.default;
prompt.style.display = "block";
@ -173,9 +178,9 @@ void (function () {
form.addEventListener(
"submit",
event => {
event.preventDefault();
prompt.style.display = "none";
const v = type === "number" ? +input.value : input.value;
event.preventDefault();
if (callback) callback(v);
},
{once: true}
@ -183,7 +188,9 @@ void (function () {
};
const cancel = prompt.querySelector("#promptCancel");
cancel.addEventListener("click", () => (prompt.style.display = "none"));
cancel.addEventListener("click", () => {
prompt.style.display = "none";
});
})();
// indexedDB; ldb object

View file

@ -5,7 +5,7 @@
function convertTemperature(temp) {
switch (temperatureScale.value) {
case "°C":
return temp + "°C";
return rn(temp) + "°C";
case "°F":
return rn((temp * 9) / 5 + 32) + "°F";
case "K":
@ -21,7 +21,7 @@ function convertTemperature(temp) {
case "°Rø":
return rn((temp * 21) / 40 + 7.5) + "°Rø";
default:
return temp + "°C";
return rn(temp) + "°C";
}
}