Merge branch 'upstream' into dev-submaps

This commit is contained in:
Mészáros Gergely 2022-03-07 13:04:25 +01:00
commit 7e8f77d0b3
76 changed files with 5756 additions and 1688 deletions

2
.gitignore vendored
View file

@ -1,2 +1,4 @@
.bat .bat
.vscode .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

@ -7,35 +7,34 @@
</head> </head>
<body> <body>
<script> <script>
/* // open this page in a new window without query parameter to start auth
open this page in a new window without query parameter to start auth // setDropBoxToken(token) will be called on the opener window
window.opener.setDropBoxToken(token) will be called on the opener
window.
*/
const REDIRECT_URI = window.location.origin + window.location.pathname; const REDIRECT_URI = window.location.origin + window.location.pathname;
const dbxAuth = new Dropbox.DropboxAuth({clientId: "pdr9ae64ip0qno4"}); const auth = new Dropbox.DropboxAuth({clientId: "pdr9ae64ip0qno4"});
const spObj = new URLSearchParams(window.location.search); const params = new URLSearchParams(window.location.search);
const searchParams = Object.fromEntries(spObj.entries()); const code = params.get("code");
const error = params.get("error");
if (searchParams.code) getToken(); if (code) getToken();
else doAuth(); // start authentication else if (error) window.opener.Cloud.providers.dropbox.returnError(params.get("error_description"));
else startAuth();
function doAuth() { function startAuth() {
dbxAuth auth
.getAuthenticationUrl(REDIRECT_URI, undefined, "code", "offline", undefined, undefined, true) .getAuthenticationUrl(REDIRECT_URI, undefined, "code", "offline", undefined, undefined, true)
.then(authUrl => { .then(authUrl => {
window.sessionStorage.clear(); window.sessionStorage.clear();
window.sessionStorage.setItem("codeVerifier", dbxAuth.codeVerifier); window.sessionStorage.setItem("codeVerifier", auth.codeVerifier);
window.location.href = authUrl; window.location.href = authUrl;
}) })
.catch(error => console.error(error)); .catch(error => console.error(error));
} }
function getToken() { function getToken() {
dbxAuth.setCodeVerifier(window.sessionStorage.getItem("codeVerifier")); auth.setCodeVerifier(window.sessionStorage.getItem("codeVerifier"));
dbxAuth auth
.getAccessTokenFromCode(REDIRECT_URI, searchParams.code) .getAccessTokenFromCode(REDIRECT_URI, code)
.then(resp => { .then(resp => {
const token = resp.result.access_token; const token = resp.result.access_token;
window.opener.Cloud.providers.dropbox.setDropBoxToken(token); window.opener.Cloud.providers.dropbox.setDropBoxToken(token);

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

BIN
heightmaps/arabia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
heightmaps/atlantics.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

BIN
heightmaps/britain.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

BIN
heightmaps/caribbean.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

BIN
heightmaps/east-asia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
heightmaps/eurasia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

BIN
heightmaps/europe-north.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
heightmaps/europe.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

BIN
heightmaps/greenland.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

BIN
heightmaps/hellenica.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
heightmaps/iceland.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View file

@ -0,0 +1,8 @@
To get heightmap with correct height scale:
1. Open tangrams.github.io
2. Toggle off auto-exposure
3. Set max elevation to 2000
4. Set min elevation to -500
5. Find region you like
6. Render image
7. Optionally rescale image to a smaller size (e.g. 500x300px) as high resolution is not used

BIN
heightmaps/indian-ocean.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
heightmaps/middle-east.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

BIN
heightmaps/us-centric.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

BIN
heightmaps/us-mainland.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

BIN
heightmaps/world.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
images/Discord.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 869 B

View file

@ -1531,6 +1531,11 @@ div.states > .riverType {
cursor: pointer; cursor: pointer;
} }
.changeRelations > * {
pointer-events: none;
cursor: pointer;
}
#diplomacySelect { #diplomacySelect {
width: 5em; width: 5em;
margin: 0.1em 0 0 -0.3em; margin: 0.1em 0 0 -0.3em;
@ -1668,11 +1673,6 @@ div.states > div.biomeArea {
width: 5em; width: 5em;
} }
rect.fillRect {
stroke: #666666;
stroke-width: 2;
}
#militaryHeader > div, #militaryHeader > div,
#regimentsHeader > div { #regimentsHeader > div {
width: 5.2em; width: 5.2em;
@ -2036,7 +2036,7 @@ div.textual span,
fill: none; fill: none;
} }
div#notes { #notes {
display: none; display: none;
position: fixed; position: fixed;
width: 28vw; width: 28vw;
@ -2046,22 +2046,47 @@ div#notes {
border: 1px solid #5e4fa2; border: 1px solid #5e4fa2;
background: rgba(255, 250, 228, 0.7); background: rgba(255, 250, 228, 0.7);
box-shadow: 2px 2px 5px -3px #3a2804; box-shadow: 2px 2px 5px -3px #3a2804;
white-space: pre-line;
} }
div#notesHeader { @media screen and (max-width: 600px) {
#notes {
width: 50vw;
}
}
#notesHeader {
font-weight: bold; font-weight: bold;
font-size: 1.3em; font-size: 1.3em;
padding: 0 0 4px 14px; padding: 16px 0 4px 12px;
border-bottom: 1px solid #5e4fa2; border-bottom: 1px solid #5e4fa2;
} }
div#notesBody { #notesBody {
padding: 0 1em; padding: 14px 12px;
max-height: 80vh; max-height: 80vh;
overflow: auto; overflow: auto;
} }
#notesBody > iframe {
pointer-events: none;
user-select: none;
}
#notesBody p {
margin: 4px;
}
#notesLegend {
height: 87%;
outline: 0;
overflow-y: auto;
padding: 0.6em;
font-family: Copperplate, monospace;
background-color: #fff;
border: 1px solid #dedede;
color: #000;
}
svg.button { svg.button {
position: relative; position: relative;
background-color: transparent; background-color: transparent;
@ -2310,7 +2335,7 @@ svg.button {
.epgrid line { .epgrid line {
stroke: lightgrey; stroke: lightgrey;
stroke-opacity: 0.7; stroke-opacity: 0.5;
shape-rendering: crispEdges; shape-rendering: crispEdges;
} }
@ -2318,45 +2343,6 @@ svg.button {
stroke-width: 0; stroke-width: 0;
} }
.pell {
border: 1px solid hsla(0, 0%, 4%, 0.1);
}
.pell,
.pell-content {
box-sizing: border-box;
}
.pell-content {
height: 14em;
outline: 0;
overflow-y: auto;
padding: 0.6em;
font-family: Copperplate, monospace;
background-color: #fff;
border: 1px solid #dedede;
}
.pell-actionbar {
background-color: #fff;
border: 1px solid #dedede;
border-bottom: 0;
}
.pell-button {
background-color: transparent;
border: none;
cursor: pointer;
height: 30px;
outline: 0;
width: 30px;
vertical-align: bottom;
}
.pell-button-selected {
background-color: #f0f0f0;
}
#debug { #debug {
font-size: 1px; font-size: 1px;
opacity: 0.8; opacity: 0.8;

View file

@ -19,7 +19,7 @@
body {margin: 0; font-size: 10px; overflow: hidden;} body {margin: 0; font-size: 10px; overflow: hidden;}
#map {position: absolute;} #map {position: absolute;}
#initial {fill: none; stroke: black; pointer-events: none;} #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);}} @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 {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;} #loading-text {font-size: 1.8em; margin: 0.2em 0 0 1em;}
@ -148,55 +148,6 @@
</filter> </filter>
</g> </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"> <g id="deftemp">
<mask id="land"></mask> <mask id="land"></mask>
<mask id="water"> <mask id="water">
@ -211,14 +162,14 @@
</g> </g>
<pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse"> <pattern id="oceanic" width="100" height="100" patternUnits="userSpaceOnUse">
<image id="oceanicPattern" href="./images/pattern1.png"></image> <image id="oceanicPattern" href="./images/pattern1.png" opacity="0.2"></image>
</pattern> </pattern>
</defs> </defs>
<g id="viewbox"></g> <g id="viewbox"></g>
<g id="scaleBar"></g> <g id="scaleBar"></g>
<g id="initial" opacity=1> <g id="initial" opacity=1>
<rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"/> <rect x="-1%" y="-1%" width="102%" height="102%" fill="#466eab"/>
<rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)" opacity=".2"/> <rect x="-1%" y="-1%" width="102%" height="102%" fill="url(#oceanic)"/>
<use href="#rose" id="init-rose" x="50%" y="50%"/> <use href="#rose" id="init-rose" x="50%" y="50%"/>
</g> </g>
</svg> </svg>
@ -226,7 +177,7 @@
<div id="loading"> <div id="loading">
<div id="titleName"><t data-t="titleName">Azgaar's</t></div> <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="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.73</div>
<p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p> <p id="loading-text"><t data-t="loading">LOADING</t><span>.</span><span>.</span><span>.</span></p>
</div> </div>
@ -309,17 +260,9 @@
<div id="styleContent" class="tabcontent"> <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> <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%"> <select data-tip="Select a style preset" id="stylePreset" onchange="requestStylePresetChange(this.value)" style="width:45%; text-transform: capitalize;"></select>
<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>
<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="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> <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%"> <select data-tip="Select an element to edit its style (list is ordered alphabetically)" id="styleElementSelect" style="width:42%">
@ -996,25 +939,52 @@
</td> </td>
</tr> </tr>
<tr data-tip="Select template to be used for a Heightmap generation"> <tr data-tip="Select heightmap generation template or precreated heightmap">
<td> <td>
<i data-locked=0 id="lock_template" class="icon-lock-open"></i> <i data-locked=0 id="lock_template" class="icon-lock-open"></i>
</td> </td>
<td>Map template</td> <td>Heightmap</td>
<td> <td>
<select id="templateInput" data-stored="template"> <select id="templateInput" data-stored="template">
<option value="volcano">Volcano</option> <optgroup label="Template">
<option value="highIsland">High Island</option> <option value="volcano">Volcano</option>
<option value="lowIsland">Low Island</option> <option value="highIsland">High Island</option>
<option value="continents">Two Continents</option> <option value="lowIsland">Low Island</option>
<option value="archipelago">Archipelago</option> <option value="continents">Two Continents</option>
<option value="atoll">Atoll</option> <option value="archipelago">Archipelago</option>
<option value="mediterranean">Mediterranean</option> <option value="atoll">Atoll</option>
<option value="peninsula">Peninsula</option> <option value="mediterranean">Mediterranean</option>
<option value="pangea">Pangea</option> <option value="peninsula">Peninsula</option>
<option value="isthmus">Isthmus</option> <option value="pangea">Pangea</option>
<option value="shattered">Shattered</option> <option value="isthmus">Isthmus</option>
<option value="taklamakan">Taklamakan</option> <option value="shattered">Shattered</option>
<option value="taklamakan">Taklamakan</option>
</optgroup>
<optgroup label="Specific">
<option value="africa-centric">Africa Centric</option>
<option value="arabia">Arabia</option>
<option value="atlantics">Atlantics</option>
<option value="britain">Britain</option>
<option value="caribbean">Caribbean</option>
<option value="east-asia">East Asia</option>
<option value="eurasia">Eurasia</option>
<option value="europe">Europe</option>
<option value="europe-accented">Europe Accented</option>
<option value="europe-and-central-asia">Europe and Central Asia</option>
<option value="europe-central">Europe Central</option>
<option value="europe-north">Europe North</option>
<option value="greenland">Greenland</option>
<option value="hellenica">Hellenica</option>
<option value="iceland">Iceland</option>
<option value="indian-ocean">Indian Ocean</option>
<option value="mediterranean-sea">Mediterranean Sea</option>
<option value="middle-east">Middle East</option>
<option value="north-america">North America</option>
<option value="us-centric">US-centric</option>
<option value="us-mainland">US Mainland</option>
<option value="world">World</option>
<option value="world-from-pacific">World from Pacific</option>
</optgroup>
</select> </select>
</td> </td>
<td></td> <td></td>
@ -1489,6 +1459,7 @@
<li><a href="http://www.tumblr.com/share?v=3&u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Post to Tumblr"><img alt="Post to Tumblr" src="images/Tumblr.png" /></a></li> <li><a href="http://www.tumblr.com/share?v=3&u=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Post to Tumblr"><img alt="Post to Tumblr" src="images/Tumblr.png" /></a></li>
<li><a href="http://pinterest.com/pin/create/button/?url=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Pin it"><img alt="Pin it" src="images/Pinterest.png" /></a></li> <li><a href="http://pinterest.com/pin/create/button/?url=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Pin it"><img alt="Pin it" src="images/Pinterest.png" /></a></li>
<li><a href="http://www.reddit.com/submit?url=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Submit to Reddit"><img alt="Submit to Reddit" src="images/Reddit.png" /></a></li> <li><a href="http://www.reddit.com/submit?url=https%3A%2F%2Fazgaar.github.io%2FFantasy-Map-Generator" target="_blank" data-tip="Submit to Reddit"><img alt="Submit to Reddit" src="images/Reddit.png" /></a></li>
<li><a href="https://discord.gg/X7E84HU" target="_blank" data-tip="Join Discord server"><img alt="Join Discord server" src="images/Discord.png" /></a></li>
</ul> </ul>
</div> </div>
@ -1649,7 +1620,7 @@
<button id="labelSizeHide" data-tip="Hide the font size section" class="icon-text-height"></button> <button id="labelSizeHide" data-tip="Hide the font size section" class="icon-text-height"></button>
<input id="labelStartOffset" data-tip="Set starting offset for the particular label" type="range" min=20 max=80 style="width:8em"> <input id="labelStartOffset" data-tip="Set starting offset for the particular label" type="range" min=20 max=80 style="width:8em">
<i class="icon-text-height"></i> <i class="icon-text-height"></i>
<input id="labelRelativeSize" data-tip="Set relative size for the particular label" type="number" min=30 max=300 step=1 style="width:3.2em"> <input id="labelRelativeSize" data-tip="Set relative size for the particular label" type="number" min=30 max=300 step=1 style="width:4.5em">
</div> </div>
<button id="labelAlign" data-tip="Turn text path into a straight line" class="icon-resize-horizontal"></button> <button id="labelAlign" data-tip="Turn text path into a straight line" class="icon-resize-horizontal"></button>
@ -1804,7 +1775,7 @@
<span data-tip="Set curve profile">Curve: <span data-tip="Set curve profile">Curve:
<select id="epCurve"> <select id="epCurve">
<option>Linear</option> <option>Linear</option>
<option selected>Basis spline</option> <option selected>Basis spline</option>
<option>Bundle</option> <option>Bundle</option>
<option>Cubic Catmull-Rom</option> <option>Cubic Catmull-Rom</option>
<option>Monotone X</option> <option>Monotone X</option>
@ -1995,61 +1966,62 @@
<div id="burgBody" style="padding-bottom: .3em"> <div id="burgBody" style="padding-bottom: .3em">
<div style="display: flex; align-items: center"> <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> <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 style="display: grid; grid-auto-rows: minmax(1.6em, auto)">
<div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div> <div id="burgProvinceAndState" style="font-style: italic; max-width: 16em"></div>
<div> <div>
<div class="label">Name:</div> <div class="label">Name:</div>
<input id="burgName" data-tip="Type to rename the burg" autocorrect="off" spellcheck="false" style="width: 8em"> <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 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> <span id="burgNameReRandom" data-tip="Generate random name for the burg" class="icon-globe pointer"></span>
</div> </div>
<div data-tip="Select burg type. Type slightly affects emblem generation"> <div data-tip="Select burg type. Type slightly affects emblem generation">
<div class="label">Type:</div> <div class="label">Type:</div>
<select id="burgType" style="width: 8em"> <select id="burgType" style="width: 8em">
<option value="Generic">Generic</option> <option value="Generic">Generic</option>
<option value="River">River</option> <option value="River">River</option>
<option value="Lake">Lake</option> <option value="Lake">Lake</option>
<option value="Naval">Naval</option> <option value="Naval">Naval</option>
<option value="Nomadic">Nomadic</option> <option value="Nomadic">Nomadic</option>
<option value="Hunting">Hunting</option> <option value="Hunting">Hunting</option>
<option value="Highland">Highland</option> <option value="Highland">Highland</option>
</select> </select>
</div> </div>
<div data-tip="Select dominant culture"> <div data-tip="Select dominant culture">
<div class="label">Culture:</div> <div class="label">Culture:</div>
<select id="burgCulture" style="width: 8em"></select> <select id="burgCulture" style="width: 8em"></select>
<span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span> <span id="burgNameReCulture" data-tip="Generate culture-specific name for the burg" class="icon-book pointer"></span>
</div> </div>
<div data-tip="Set burg population"> <div data-tip="Set burg population">
<div class="label">Population:</div> <div class="label">Population:</div>
<input id="burgPopulation" type="number" min=0 step=1 style="width: 8em"> <input id="burgPopulation" type="number" min=0 step=1 style="width: 8em">
</div> </div>
<div> <div>
<div class="label">Features:</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="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="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="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="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="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="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> <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 data-tip="Burg mean annual temperature and real-world city for comparison"> <div data-tip="Burg mean annual temperature and real-world city for comparison">
<div class="label">Temperature:</div> <div class="label">Temperature:</div>
<span id="burgTemperature"></span>, like in <span id="burgTemperature"></span>, like in
<span id="burgTemperatureLikeIn"></span> <span id="burgTemperatureLikeIn"></span>
</div> <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 data-tip="Burg height above mean sea level">
<div class="label">Elevation:</div> <div class="label">Elevation:</div>
<span id="burgElevation"></span> above sea level <span id="burgElevation"></span> above sea level
</div> </div>
</div> </div>
</div> </div>
@ -2932,9 +2904,10 @@
<div id="zonesEditor" class="dialog stable" style="display: none"> <div id="zonesEditor" class="dialog stable" style="display: none">
<div id="customHeader" class="header"> <div id="customHeader" class="header">
<div style="left:1.8em" data-tip="Zone description">Description&nbsp;</div> <div style="left:1.8em" data-tip="Zone description">Description&nbsp;</div>
<div style="left:13em" data-tip="Zone cells count" class="hide">Cells&nbsp;</div> <div style="left:13em" data-tip="Zone type">Type&nbsp;</div>
<div style="left:19em" data-tip="Zone area" class="hide">Area&nbsp;</div> <div style="left:19em" data-tip="Zone cells count" class="hide">Cells&nbsp;</div>
<div style="left:24em" data-tip="Zone population" class="hide">Population&nbsp;</div> <div style="left:23.6em" data-tip="Zone area" class="hide">Area&nbsp;</div>
<div style="left:30.6em" data-tip="Zone population" class="hide">Population&nbsp;</div>
</div> </div>
<div id="zonesBodySection" class="table" data-type="absolute"></div> <div id="zonesBodySection" class="table" data-type="absolute"></div>
@ -2955,35 +2928,35 @@
<button id="zonesManually" data-tip="Re-assign zones" class="icon-brush"></button> <button id="zonesManually" data-tip="Re-assign zones" class="icon-brush"></button>
<div id="zonesManuallyButtons" style="display: none"> <div id="zonesManuallyButtons" style="display: none">
<label data-tip="Change brush size. Shortcut: + (increase), (decrease)" class="italic">Brush: <label data-tip="Change brush size. Shortcut: + (increase), (decrease)" class="italic">Brush:
<input id="zonesBrush" oninput="tip('Brush size: '+this.value); zonesBrushNumber.value = this.value" type="range" min=5 max=25 value=7 style="width:7em"> <input id="zonesBrush" oninput="tip('Brush size: '+this.value); zonesBrushNumber.value = this.value" type="range" min=5 max=50 value=7 style="width:9em">
<input id="zonesBrushNumber" oninput="tip('Brush size: '+this.value); zonesBrush.value = this.value" type="number" min=5 max=25 value=7> <input id="zonesBrushNumber" oninput="tip('Brush size: '+this.value); zonesBrush.value = this.value" type="number" min=5 max=50 value=7>
</label><br> </label><br>
<button id="zonesManuallyApply" data-tip="Apply assignment" class="icon-check"></button> <button id="zonesManuallyApply" data-tip="Apply assignment" class="icon-check"></button>
<button id="zonesManuallyCancel" data-tip="Cancel assignment" class="icon-cancel"></button> <button id="zonesManuallyCancel" data-tip="Cancel assignment" class="icon-cancel"></button>
<button id="zonesRemove" data-tip="Click to toggle the removal mode on brush dragging. Shortcut: ctrl" class="icon-eraser"></button> <button id="zonesRemove" data-tip="Click to toggle the removal mode on brush dragging. Shortcut: ctrl" class="icon-eraser"></button>
</div> </div>
<button id="zonesAdd" data-tip="Add a new zone layer" class="icon-plus"></button> <button id="zonesAdd" data-tip="Add new zone layer" class="icon-plus"></button>
<button id="zonesExport" data-tip="Download zones-related data" class="icon-download"></button> <button id="zonesExport" data-tip="Download zones-related data" class="icon-download"></button>
<div id="zonesFilters" data-tip="Show only zones of selected type" style="display: inline-block">Type: <select id="zonesFilterType"></select></div>
</div> </div>
</div> </div>
<div id="notesEditor" class="dialog stable textual" style="display: none"> <div id="notesEditor" class="dialog stable" style="display: none">
<div> <div style="margin-bottom: 0.3em">
<span>Select object: </span> <strong>Element: </strong>
<select id="notesSelect" data-tip="Select object" style="width: 12em"></select> <select id="notesSelect" data-tip="Select element id" style="width: 12em"></select>
<span>Object name: </span> <strong>Element name: </strong>
<input id="notesName" data-tip="Type to change object name" autocorrect="off" spellcheck="false" style="width: 16em"> <input id="notesName" data-tip="Set element name" autocorrect="off" spellcheck="false" style="width: 16em">
<span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span> <span data-tip="Speak the name. You can change voice and language in options" class="speaker">🔊</span>
</div> </div>
<div id="notesText" data-tip="Type and style object description" style="padding: .4em 0"></div> <div id="notesLegend" contenteditable="true"></div>
<div> <div style="margin-top: 0.3em">
<button id="notesSpeak" data-tip="Speak the note. You can change voice and language in options" class="icon-voice"></button>
<button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button> <button id="notesFocus" data-tip="Focus on selected object" class="icon-target"></button>
<button id="notesPin" data-tip="Toggle notes box dispay: hide or do not hide the box on mouse move" class="icon-pin"></button> <button id="notesPin" data-tip="Toggle notes box dispay: hide or do not hide the box on mouse move" class="icon-pin"></button>
<button id="notesDownload" data-tip="Download notes to PC" class="icon-download"></button> <button id="notesDownload" data-tip="Download notes to PC" class="icon-download"></button>
<button id="notesUpload" data-tip="Upload notes from PC" class="icon-upload"></button> <button id="notesUpload" data-tip="Upload notes from PC" class="icon-upload"></button>
<button id="notesClearStyle" data-tip="Remove all styling, get plain text only" class="icon-eraser"></button>
<button id="notesRemove" data-tip="Remove this note" class="icon-trash fastDelete"></button> <button id="notesRemove" data-tip="Remove this note" class="icon-trash fastDelete"></button>
</div> </div>
</div> </div>
@ -3611,7 +3584,19 @@
</div> </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>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>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>
<div id="saveMapData" style="display: none" class="dialog"> <div id="saveMapData" style="display: none" class="dialog">
@ -3632,12 +3617,15 @@
<button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button> <button onclick="quickLoad()" data-tip="Load map from browser storage (if saved before)">storage</button>
</div> </div>
<div id="loadFromDropbox"> <div id="loadFromDropbox">
<p style="margin-bottom: .3em">From your Dropbox account</p> <p style="margin-bottom: .3em">
<select id="loadFromDropboxSelect" style="width: 22em"></select> From your Dropbox account
<button id="dropboxConnectButton" onclick="connectToDropbox()" data-tip="Connect your Dropbox account to be able to load maps from it">Connect</button>
</p>
<div id="loadFromDropboxButtons" style="margin-bottom: .3em"> <select id="loadFromDropboxSelect" style="width: 22em"></select>
<button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Open</button> <div id="loadFromDropboxButtons" style="margin-bottom: .6em">
<button onclick="createSharableDropboxLink()" data-tip="Select file and create a link to share with your friends">Create link</button> <button onclick="loadFromDropbox()" data-tip="Load .map file from your Dropbox">Load</button>
<button onclick="createSharableDropboxLink()" data-tip="Select file and create a link to share with your friends">Share</button>
</div> </div>
<div style="margin-top: .3em"> <div style="margin-top: .3em">
@ -4394,6 +4382,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"/> <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> </pattern>
</g> </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> </defs>
</svg> </svg>
@ -4436,18 +4571,15 @@
<script src="modules/fonts.js"></script> <script src="modules/fonts.js"></script>
<script src="modules/ui/layers.js"></script> <script src="modules/ui/layers.js"></script>
<script src="modules/ui/measurers.js"></script> <script src="modules/ui/measurers.js"></script>
<script src="modules/ui/stylePresets.js"></script>
<!-- <script src="libs/umami.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 src="modules/ui/general.js"></script>
<script defer src="modules/ui/general.js"></script> <script src="modules/ui/options.js"></script>
<script defer src="modules/ui/options.js"></script> <script src="main.js"></script>
<script defer src="modules/ui/style.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/save.js"></script>
<script defer src="modules/export.js"></script>
<script defer src="modules/relief-icons.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/tools.js"></script>
<script defer src="modules/ui/world-configurator.js"></script> <script defer src="modules/ui/world-configurator.js"></script>
<script defer src="modules/ui/editors.js"></script> <script defer src="modules/ui/editors.js"></script>
@ -4458,6 +4590,7 @@
<script defer src="modules/ui/cultures-editor.js"></script> <script defer src="modules/ui/cultures-editor.js"></script>
<script defer src="modules/ui/namesbase-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/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/routes-editor.js"></script>
<script defer src="modules/ui/ice-editor.js"></script> <script defer src="modules/ui/ice-editor.js"></script>
<script defer src="modules/ui/lakes-editor.js"></script> <script defer src="modules/ui/lakes-editor.js"></script>
@ -4467,7 +4600,6 @@
<script defer src="modules/ui/rivers-creator.js"></script> <script defer src="modules/ui/rivers-creator.js"></script>
<script defer src="modules/ui/relief-editor.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/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/burg-editor.js"></script>
<script defer src="modules/ui/units-editor.js"></script> <script defer src="modules/ui/units-editor.js"></script>
<script defer src="modules/ui/notes-editor.js"></script> <script defer src="modules/ui/notes-editor.js"></script>
@ -4480,14 +4612,23 @@
<script defer src="modules/ui/markers-overview.js"></script> <script defer src="modules/ui/markers-overview.js"></script>
<script defer src="modules/ui/regiment-editor.js"></script> <script defer src="modules/ui/regiment-editor.js"></script>
<script defer src="modules/ui/battle-screen.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/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/3d.js"></script>
<script defer src="modules/ui/submap.js"></script> <script defer src="modules/ui/submap.js"></script>
<script defer src="modules/ui/hotkeys.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/rgbquant.min.js"></script>
<script defer src="libs/jquery.ui.touch-punch.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="libs/jszip.min.js"></script>
<script defer src="modules/io/save.js"></script>
<script defer src="modules/io/load.js"></script>
<script defer src="modules/io/cloud.js"></script>
<script defer src="modules/io/export.js"></script>
<script defer src="modules/io/export-json.js"></script>
<!-- Web Components -->
<script defer src="components/fill-box.js"></script>
</body> </body>
</html> </html>

2
libs/pell.min.js vendored
View file

@ -1,2 +0,0 @@
// https://github.com/jaredreich/pell, MIT License
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Pell=t()}(this,function(){"use strict";const e=(e,t,n)=>e.addEventListener(t,n),t=(e,t)=>e.appendChild(t),n=e=>document.createElement(e),i=e=>document.queryCommandState(e),o=(e,t=null)=>document.execCommand(e,!1,t),l={bold:{icon:"<b>B</b>",title:"Bold",state:()=>i("bold"),result:()=>o("bold")},italic:{icon:"<i>I</i>",title:"Italic",state:()=>i("italic"),result:()=>o("italic")},underline:{icon:"<u>U</u>",title:"Underline",state:()=>i("underline"),result:()=>o("underline")},strikethrough:{icon:"<strike>S</strike>",title:"Strike-through",state:()=>i("strikeThrough"),result:()=>o("strikeThrough")},heading1:{icon:"<b>H<sub>1</sub></b>",title:"Heading 1",result:()=>o("formatBlock","<h1>")},heading2:{icon:"<b>H<sub>2</sub></b>",title:"Heading 2",result:()=>o("formatBlock","<h2>")},paragraph:{icon:"&#182;",title:"Paragraph",result:()=>o("formatBlock","<p>")},quote:{icon:"&#8220; &#8221;",title:"Quote",result:()=>o("formatBlock","<blockquote>")},olist:{icon:"&#35;",title:"Ordered List",result:()=>o("insertOrderedList")},ulist:{icon:"&#8226;",title:"Unordered List",result:()=>o("insertUnorderedList")},code:{icon:"&lt;/&gt;",title:"Code",result:()=>o("formatBlock","<pre>")},line:{icon:"&#8213;",title:"Horizontal Line",result:()=>o("insertHorizontalRule")},link:{icon:"&#128279;",title:"Link",result:()=>navigator.clipboard.readText().then(e=>o("createLink",e))},image:{icon:"&#128247;",title:"Image",result:()=>{navigator.clipboard.readText().then(e=>o("insertImage",e)),o("enableObjectResizing")}}},r={actionbar:"pell-actionbar",button:"pell-button",content:"pell-content",selected:"pell-button-selected"};return{exec:o,init:i=>{const a=i.actions?i.actions.map(e=>"string"==typeof e?l[e]:l[e.name]?{...l[e.name],...e}:e):Object.keys(l).map(e=>l[e]),s={...r,...i.classes},c=i.defaultParagraphSeparator||"div",u=n("div");u.className=s.actionbar,t(i.element,u);const d=i.element.content=n("div");return d.contentEditable=!0,d.className=s.content,d.oninput=(({target:{firstChild:e}})=>{e&&3===e.nodeType?o("formatBlock",`<${c}>`):"<br>"===d.innerHTML&&(d.innerHTML=""),i.onChange(d.innerHTML)}),d.onkeydown=(e=>{"Enter"===e.key&&"blockquote"===(e=>document.queryCommandValue(e))("formatBlock")&&setTimeout(()=>o("formatBlock",`<${c}>`),0)}),t(i.element,d),a.forEach(i=>{const o=n("button");if(o.className=s.button,o.innerHTML=i.icon,o.title=i.title,o.setAttribute("type","button"),o.onclick=(()=>i.result()&&d.focus()),i.state){const t=()=>o.classList[i.state()?"add":"remove"](s.selected);e(d,"keyup",t),e(d,"mouseup",t),e(o,"click",t)}t(u,o)}),i.styleWithCSS&&o("styleWithCSS"),o("defaultParagraphSeparator",c),i.element}}});

165
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 // https://github.com/Azgaar/Fantasy-Map-Generator
"use strict"; "use strict";
const version = "1.71"; // generator version const version = "1.732"; // generator version
document.title += " v" + 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 PRODUCTION = location.hostname && location.hostname !== "localhost" && location.hostname !== "127.0.0.1";
const DEBUG = localStorage.getItem("debug"); const DEBUG = localStorage.getItem("debug");
const INFO = DEBUG || !PRODUCTION; const INFO = DEBUG || !PRODUCTION;
@ -173,14 +173,46 @@ 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); 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); oceanLayers.append("rect").attr("id", "oceanBase").attr("x", 0).attr("y", 0).attr("width", graphWidth).attr("height", graphHeight);
// remove loading screen if (!location.hostname) {
d3.select("#loading").transition().duration(4000).style("opacity", 0).remove(); const wiki = "https://github.com/Azgaar/Fantasy-Map-Generator/wiki/Run-FMG-locally";
d3.select("#initial").transition().duration(4000).attr("opacity", 0).remove(); alertMessage.innerHTML = `Fantasy Map Generator cannot run serverless.
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1); Follow the <a href="${wiki}" target="_blank">instructions</a> on how you can easily run a local web-server`;
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
$("#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 {
hideLoading();
checkLoadParameters();
}
function hideLoading() {
d3.select("#loading").transition().duration(4000).style("opacity", 0);
d3.select("#initial").transition().duration(4000).attr("opacity", 0);
d3.select("#optionsContainer").transition().duration(3000).style("opacity", 1);
d3.select("#tooltip").transition().duration(4000).style("opacity", 1);
}
function showLoading() {
d3.select("#loading").transition().duration(200).style("opacity", 1);
d3.select("#initial").transition().duration(200).attr("opacity", 1);
d3.select("#optionsContainer").transition().duration(100).style("opacity", 0);
d3.select("#tooltip").transition().duration(200).style("opacity", 0);
}
// decide which map should be loaded or generated on page load // decide which map should be loaded or generated on page load
void (function checkLoadParameters() { function checkLoadParameters() {
const url = new URL(window.location.href); const url = new URL(window.location.href);
const params = url.searchParams; const params = url.searchParams;
@ -191,7 +223,9 @@ void (function checkLoadParameters() {
const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/; const pattern = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
const valid = pattern.test(maplink); const valid = pattern.test(maplink);
if (valid) { if (valid) {
loadMapFromURL(maplink, 1); setTimeout(() => {
loadMapFromURL(maplink, 1);
}, 1000);
return; return;
} else showUploadErrorMessage("Map link is not a valid URL", maplink); } else showUploadErrorMessage("Map link is not a valid URL", maplink);
} }
@ -225,11 +259,11 @@ void (function checkLoadParameters() {
WARN && console.warn("Generate random map"); WARN && console.warn("Generate random map");
generateMapOnLoad(); generateMapOnLoad();
})(); }
function generateMapOnLoad() { async function generateMapOnLoad() {
applyStyleOnLoad(); // apply default or previously selected style await applyStyleOnLoad(); // apply previously selected default or custom style
generate(); // generate map await generate(); // generate map
focusOn(); // based on searchParams focus on point, cell or burg from MFCG focusOn(); // based on searchParams focus on point, cell or burg from MFCG
applyPreset(); // apply saved layers preset applyPreset(); // apply saved layers preset
} }
@ -410,22 +444,22 @@ function showWelcomeMessage() {
const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server"); const discord = link("https://discordapp.com/invite/X7E84HU", "Discord server");
const patreon = link("https://www.patreon.com/azgaar", "Patreon"); const patreon = link("https://www.patreon.com/azgaar", "Patreon");
alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <b>${version}</b>. alertMessage.innerHTML = `The Fantasy Map Generator is updated up to version <strong>${version}</strong>.
This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated. This version is compatible with ${changelog}, loaded <i>.map</i> files will be auto-updated.
<ul>Main changes: <ul><strong>Latest changes:</strong>
<li>Ability to limit military units by biome, state, culture and religion</li> <li>Pre-defined heightmaps</li>
<li>New marker types</li> <li>Advanced notes editor</li>
<li>New markers editor</li> <li>Zones editor: filter by type</li>
<li>Markers overview screen</li> <li>Color picker: new hatchings</li>
<li>Markers regeneration menu</li> <li>New style presets: Cyberpunk and Atlas</li>
<li>Burg editor update</li> <li>Burg temperature graph</li>
<li>Editable theme color</li> <li>4 new textures</li>
<li>Add font dialog</li> <li>Province capture logic rework</li>
<li>Save to Dropbox</li> <li>Button to release all provinces</li>
</ul> </ul>
<p>Join our ${discord} and ${reddit} to ask questions, share maps, discuss the Generator and Worlbuilding, report bugs and propose new features.</p> <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({ $("#alert").dialog({
resizable: false, resizable: false,
@ -448,7 +482,7 @@ function doWorkOnZoom(isScaleChanged, isPositionChanged) {
if (isScaleChanged) { if (isScaleChanged) {
invokeActiveZooming(); invokeActiveZooming();
drawScaleBar(); drawScaleBar(scale);
} }
// zoom image converter overlay // zoom image converter overlay
@ -609,7 +643,7 @@ void (function addDragToUpload() {
}); });
})(); })();
function generate() { async function generate() {
try { try {
const timeStart = performance.now(); const timeStart = performance.now();
invokeActiveZooming(); invokeActiveZooming();
@ -619,8 +653,8 @@ function generate() {
randomizeOptions(); randomizeOptions();
placePoints(); placePoints();
calculateVoronoi(grid, grid.points); calculateVoronoi(grid, grid.points);
drawScaleBar(); drawScaleBar(scale);
HeightmapGenerator.generate(); await HeightmapGenerator.generate();
markFeatures(); markFeatures();
markupGridOcean(); markupGridOcean();
addLakesInDeepDepressions(); addLakesInDeepDepressions();
@ -907,6 +941,31 @@ function defineMapSize() {
function getSizeAndLatitude() { function getSizeAndLatitude() {
const template = document.getElementById("templateInput").value; // heightmap template const template = document.getElementById("templateInput").value; // heightmap template
if (template === "africa-centric") return [45, 53];
if (template === "arabia") return [20, 35];
if (template === "atlantics") return [42, 23];
if (template === "britain") return [7, 20];
if (template === "caribbean") return [15, 40];
if (template === "east-asia") return [11, 28];
if (template === "eurasia") return [38, 19];
if (template === "europe") return [20, 16];
if (template === "europe-accented") return [14, 22];
if (template === "europe-and-central-asia") return [25, 10];
if (template === "europe-central") return [11, 22];
if (template === "europe-north") return [7, 18];
if (template === "greenland") return [22, 7];
if (template === "hellenica") return [8, 27];
if (template === "iceland") return [2, 15];
if (template === "indian-ocean") return [45, 55];
if (template === "mediterranean-sea") return [10, 29];
if (template === "middle-east") return [8, 31];
if (template === "north-america") return [37, 17];
if (template === "us-centric") return [66, 27];
if (template === "us-mainland") return [16, 30];
if (template === "world") return [78, 27];
if (template === "world-from-pacific") return [75, 32];
const part = grid.features.some(f => f.land && f.border); // if land goes over map borders const part = grid.features.some(f => f.land && f.border); // if land goes over map borders
const max = part ? 80 : 100; // max size const max = part ? 80 : 100; // max size
const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift const lat = () => gauss(P(0.5) ? 40 : 60, 15, 25, 75); // latitude shift
@ -1485,14 +1544,12 @@ function rankCells() {
TIME && console.timeEnd("rankCells"); TIME && console.timeEnd("rankCells");
} }
// regenerate some zones // generate zones
function addZones(number = 1) { function addZones(number = 1) {
TIME && console.time("addZones"); TIME && console.time("addZones");
const data = [], const {cells, states, burgs} = pack;
cells = pack.cells,
states = pack.states,
burgs = pack.burgs;
const used = new Uint8Array(cells.i.length); // to store used cells const used = new Uint8Array(cells.i.length); // to store used cells
const zonesData = [];
for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands for (let i = 0; i < rn(Math.random() * 1.8 * number); i++) addInvasion(); // invasion of enemy lands
for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border for (let i = 0; i < rn(Math.random() * 1.6 * number); i++) addRebels(); // rebels along a state border
@ -1506,6 +1563,8 @@ function addZones(number = 1) {
for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFlood(); // flood on river banks for (let i = 0; i < rn(Math.random() * 1.4 * number); i++) addFlood(); // flood on river banks
for (let i = 0; i < rn(Math.random() * 1.2 * number); i++) addTsunami(); // tsunami starting near coast for (let i = 0; i < rn(Math.random() * 1.2 * number); i++) addTsunami(); // tsunami starting near coast
drawZones();
function addInvasion() { function addInvasion() {
const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy")); const atWar = states.filter(s => s.diplomacy && s.diplomacy.some(d => d === "Enemy"));
if (!atWar.length) return; if (!atWar.length) return;
@ -1546,7 +1605,7 @@ function addZones(number = 1) {
Intervention: 1 Intervention: 1
}); });
const name = getAdjective(invader.name) + " " + invasion; const name = getAdjective(invader.name) + " " + invasion;
data.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"}); zonesData.push({name, type: "Invasion", cells: cellsArray, fill: "url(#hatch1)"});
} }
function addRebels() { function addRebels() {
@ -1582,7 +1641,7 @@ function addZones(number = 1) {
const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2}); const rebels = rw({Rebels: 5, Insurgents: 2, Mutineers: 1, Rioters: 1, Separatists: 1, Secessionists: 1, Insurrection: 2, Rebellion: 1, Conspiracy: 2});
const name = getAdjective(states[neib].name) + " " + rebels; const name = getAdjective(states[neib].name) + " " + rebels;
data.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"}); zonesData.push({name, type: "Rebels", cells: cellsArray, fill: "url(#hatch3)"});
} }
function addProselytism() { function addProselytism() {
@ -1612,7 +1671,7 @@ function addZones(number = 1) {
} }
const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism"; const name = getAdjective(organized.name.split(" ")[0]) + " Proselytism";
data.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"}); zonesData.push({name, type: "Proselytism", cells: cellsArray, fill: "url(#hatch6)"});
} }
function addCrusade() { function addCrusade() {
@ -1624,7 +1683,7 @@ function addZones(number = 1) {
cellsArray.forEach(i => (used[i] = 1)); cellsArray.forEach(i => (used[i] = 1));
const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade"; const name = getAdjective(heresy.name.split(" ")[0]) + " Crusade";
data.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"}); zonesData.push({name, type: "Crusade", cells: cellsArray, fill: "url(#hatch6)"});
} }
function addDisease() { function addDisease() {
@ -1661,7 +1720,7 @@ function addZones(number = 1) {
const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2}); const type = rw({Fever: 5, Pestilence: 2, Flu: 2, Pox: 2, Smallpox: 2, Plague: 4, Cholera: 2, Dropsy: 1, Leprosy: 2});
const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type; const name = rw({[color()]: 4, [animal()]: 2, [adjective()]: 1}) + " " + type;
data.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"}); zonesData.push({name, type: "Disease", cells: cellsArray, fill: "url(#hatch12)"});
} }
function addDisaster() { function addDisaster() {
@ -1693,7 +1752,7 @@ function addZones(number = 1) {
const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1}); const type = rw({Famine: 5, Dearth: 1, Drought: 3, Earthquake: 3, Tornadoes: 1, Wildfires: 1});
const name = getAdjective(burg.name) + " " + type; const name = getAdjective(burg.name) + " " + type;
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
} }
function addEruption() { function addEruption() {
@ -1724,7 +1783,7 @@ function addZones(number = 1) {
}); });
} }
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch7)"});
} }
function addAvalanche() { function addAvalanche() {
@ -1749,7 +1808,7 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Avalanche"; const name = proper + " Avalanche";
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch5)"});
} }
function addFault() { function addFault() {
@ -1774,7 +1833,7 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Fault"; const name = proper + " Fault";
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch2)"});
} }
function addFlood() { function addFlood() {
@ -1804,7 +1863,7 @@ function addZones(number = 1) {
} }
const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood"; const name = getAdjective(burgs[cells.burg[cell]].name) + " Flood";
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
} }
function addTsunami() { function addTsunami() {
@ -1832,13 +1891,13 @@ function addZones(number = 1) {
const proper = getAdjective(Names.getCultureShort(cells.culture[cell])); const proper = getAdjective(Names.getCultureShort(cells.culture[cell]));
const name = proper + " Tsunami"; const name = proper + " Tsunami";
data.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"}); zonesData.push({name, type: "Disaster", cells: cellsArray, fill: "url(#hatch13)"});
} }
void (function drawZones() { function drawZones() {
zones zones
.selectAll("g") .selectAll("g")
.data(data) .data(zonesData)
.enter() .enter()
.append("g") .append("g")
.attr("id", (d, i) => "zone" + i) .attr("id", (d, i) => "zone" + i)
@ -1854,7 +1913,7 @@ function addZones(number = 1) {
.attr("id", function (d) { .attr("id", function (d) {
return this.parentNode.id + "_" + d; return this.parentNode.id + "_" + d;
}); });
})(); }
TIME && console.timeEnd("addZones"); TIME && console.timeEnd("addZones");
} }
@ -1881,16 +1940,18 @@ function showStatistics() {
INFO && console.log(stats); INFO && console.log(stats);
} }
const regenerateMap = debounce(function () { const regenerateMap = debounce(async function () {
WARN && console.warn("Generate new random map"); WARN && console.warn("Generate new random map");
showLoading();
closeDialogs("#worldConfigurator, #options3d"); closeDialogs("#worldConfigurator, #options3d");
customization = 0; customization = 0;
undraw();
resetZoom(1000); resetZoom(1000);
generate(); undraw();
await generate();
restoreLayers(); restoreLayers();
if (ThreeD.options.isOn) ThreeD.redraw(); if (ThreeD.options.isOn) ThreeD.redraw();
if ($("#worldConfigurator").is(":visible")) editWorld(); if ($("#worldConfigurator").is(":visible")) editWorld();
hideLoading();
}, 1000); }, 1000);
// clear the map // clear the map

View file

@ -595,7 +595,7 @@ window.BurgsAndStates = (function () {
g.select("#stateLabel" + id).remove(); g.select("#stateLabel" + id).remove();
} }
const path = p[1].length > 1 ? lineGen(p[1]) : `M${p[1][0][0] - 50},${p[1][0][1]}h${100}`; const path = p[1].length > 1 ? round(lineGen(p[1])) : `M${p[1][0][0] - 50},${p[1][0][1]}h${100}`;
const textPath = t const textPath = t
.append("path") .append("path")
.attr("d", path) .attr("d", path)
@ -621,7 +621,7 @@ window.BurgsAndStates = (function () {
const spans = lines.map((l, d) => { const spans = lines.map((l, d) => {
example.text(l); example.text(l);
const left = example.node().getBBox().width / -2; // x offset const left = example.node().getBBox().width / -2; // x offset
return `<tspan x="${left}px" dy="${d ? 1 : top}em">${l}</tspan>`; return `<tspan x=${rn(left, 1)} dy="${d ? 1 : top}em">${l}</tspan>`;
}); });
const el = g const el = g

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 (emblemShape.value === "state" && state && pack.states[state].coa) return pack.states[state].coa.shield;
if (pack.cultures[culture].shield) return pack.cultures[culture].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"; return "heater";
}; };

View file

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

View file

@ -14,12 +14,14 @@ const fonts = [
{ {
family: "Almendra SC", family: "Almendra SC",
src: "url(https://fonts.gstatic.com/s/almendrasc/v13/Iure6Yx284eebowr7hbyTaZOrLQ.woff2)", 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", family: "Amatic SC",
src: "url(https://fonts.gstatic.com/s/amaticsc/v11/TUZ3zwprpvBS1izr_vOMscGKfrUC.woff2)", 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", family: "Architects Daughter",
@ -34,7 +36,8 @@ const fonts = [
{ {
family: "Caesar Dressing", family: "Caesar Dressing",
src: "url(https://fonts.gstatic.com/s/caesardressing/v6/yYLx0hLa3vawqtwdswbotmK4vrRHdrz7.woff2)", 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", family: "Cinzel",
@ -49,7 +52,8 @@ const fonts = [
{ {
family: "Fredericka the Great", family: "Fredericka the Great",
src: "url(https://fonts.gstatic.com/s/frederickathegreat/v6/9Bt33CxNwt7aOctW2xjbCstzwVKsIBVV--Sjxbc.woff2)", 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", family: "Gloria Hallelujah",
@ -74,12 +78,14 @@ const fonts = [
{ {
family: "MedievalSharp", family: "MedievalSharp",
src: "url(https://fonts.gstatic.com/s/medievalsharp/v9/EvOJzAlL3oU5AQl2mP5KdgptMqhwMg.woff2)", 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", family: "Metamorphous",
src: "url(https://fonts.gstatic.com/s/metamorphous/v7/Wnz8HA03aAXcC39ZEX5y133EOyqs.woff2)", 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", family: "Montez",
@ -89,7 +95,8 @@ const fonts = [
{ {
family: "Nova Script", family: "Nova Script",
src: "url(https://fonts.gstatic.com/s/novascript/v10/7Au7p_IpkSWSTWaFWkumvlQKGFw.woff2)", 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", family: "Orbitron",
@ -109,12 +116,14 @@ const fonts = [
{ {
family: "Uncial Antiqua", family: "Uncial Antiqua",
src: "url(https://fonts.gstatic.com/s/uncialantiqua/v5/N0bM2S5WOex4OUbESzoESK-i-MfWQZQ.woff2)", 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", family: "Underdog",
src: "url(https://fonts.gstatic.com/s/underdog/v6/CHygV-jCElj7diMroWSlWV8.woff2)", 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", family: "Yellowtail",
@ -243,7 +252,7 @@ async function addGoogleFont(family) {
}) })
.catch(err => { .catch(err => {
tip(`Failed to load Google font ${family}`, true, "error", 4000); tip(`Failed to load Google font ${family}`, true, "error", 4000);
console.error(err); ERROR && console.error(err);
}); });
} }

View file

@ -3,13 +3,44 @@
window.HeightmapGenerator = (function () { window.HeightmapGenerator = (function () {
let cells, p; let cells, p;
const generate = function () { const generate = async function () {
TIME && console.time("generateHeightmap");
cells = grid.cells; cells = grid.cells;
p = grid.points; p = grid.points;
cells.h = new Uint8Array(grid.points.length); cells.h = new Uint8Array(grid.points.length);
const template = document.getElementById("templateInput").value; const input = document.getElementById("templateInput");
const selectedId = input.selectedIndex >= 0 ? input.selectedIndex : 0;
const type = input.options[selectedId]?.parentElement?.label;
if (type === "Specific") {
// pre-defined heightmap
TIME && console.time("defineHeightmap");
return new Promise(resolve => {
// create canvas where 1px correcponds to a cell
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const {cellsX, cellsY} = grid;
canvas.width = cellsX;
canvas.height = cellsY;
// load heightmap into image and render to canvas
const img = new Image();
img.src = `./heightmaps/${input.value}.png`;
img.onload = function () {
ctx.drawImage(img, 0, 0, cellsX, cellsY);
const imageData = ctx.getImageData(0, 0, cellsX, cellsY);
assignColorsToHeight(imageData.data);
canvas.remove();
img.remove();
TIME && console.timeEnd("defineHeightmap");
resolve();
};
});
}
// heightmap template
TIME && console.time("generateHeightmap");
const template = input.value;
const templateString = HeightmapTemplates[template]; const templateString = HeightmapTemplates[template];
const steps = templateString.split("\n"); const steps = templateString.split("\n");
@ -79,8 +110,8 @@ window.HeightmapGenerator = (function () {
function addOneHill() { function addOneHill() {
const change = new Uint8Array(cells.h.length); const change = new Uint8Array(cells.h.length);
let limit = 0, let limit = 0;
start; let start;
let h = lim(getNumberInRange(height)); let h = lim(getNumberInRange(height));
do { do {
@ -410,5 +441,13 @@ window.HeightmapGenerator = (function () {
return rand(min * length, max * length); return rand(min * length, max * length);
} }
function assignColorsToHeight(imageData) {
for (let i = 0; i < cells.i.length; i++) {
const lightness = imageData[i * 4] / 255;
const powered = lightness < 0.2 ? lightness : 0.2 + (lightness - 0.2) ** 0.8;
cells.h[i] = minmax(Math.floor(powered * 100), 0, 100);
}
}
return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify}; return {generate, addHill, addRange, addTrough, addStrait, addPit, smooth, modify};
})(); })();

523
modules/io/auto-update.js Normal file
View file

@ -0,0 +1,523 @@
"use strict";
// update old .map version to the current one
export function resolveVersionConflicts(version) {
if (version < 1) {
// v1.0 added a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate();
// v1.0 added a legend box
legend = svg.append("g").attr("id", "legend");
legend
.attr("font-family", "Almendra SC")
.attr("font-size", 13)
.attr("data-size", 13)
.attr("data-x", 99)
.attr("data-y", 93)
.attr("stroke-width", 2.5)
.attr("stroke", "#812929")
.attr("stroke-dasharray", "0 4 10 4")
.attr("stroke-linecap", "round");
// v1.0 separated drawBorders fron drawStates()
stateBorders = borders.append("g").attr("id", "stateBorders");
provinceBorders = borders.append("g").attr("id", "provinceBorders");
borders
.attr("opacity", null)
.attr("stroke", null)
.attr("stroke-width", null)
.attr("stroke-dasharray", null)
.attr("stroke-linecap", null)
.attr("filter", null);
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");
// v1.0 added state relations, provinces, forms and full names
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
BurgsAndStates.collectStatistics();
BurgsAndStates.generateCampaigns();
BurgsAndStates.generateDiplomacy();
BurgsAndStates.defineStateForms();
drawStates();
BurgsAndStates.generateProvinces();
drawBorders();
if (!layerIsOn("toggleBorders")) $("#borders").fadeOut();
if (!layerIsOn("toggleStates")) regions.attr("display", "none").selectAll("path").remove();
// v1.0 added 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();
if (!markers.selectAll("*").size()) {
Markers.generate();
turnButtonOn("toggleMarkers");
}
// 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");
// v1.0 changes states opacity bask to regions level
if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null);
}
// 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>`;
});
// v1.0 added new biome - Wetland
biomesData.name.push("Wetland");
biomesData.color.push("#0b9131");
biomesData.habitability.push(12);
}
if (version < 1.1) {
// v1.0 initial code had a bug with religion layer id
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// 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));
}
// labels should be toggled via style attribute, so remove display attribute
labels.attr("display", null);
// v1.0 added religions heirarchy tree
if (pack.religions[1] && !pack.religions[1].code) {
pack.religions
.filter(r => r.i)
.forEach(r => {
r.origin = 0;
r.code = r.name.slice(0, 2);
});
}
if (!document.getElementById("freshwater")) {
lakes.append("g").attr("id", "freshwater");
lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null);
}
if (!document.getElementById("salt")) {
lakes.append("g").attr("id", "salt");
lakes.select("#salt").attr("opacity", 0.5).attr("fill", "#409b8a").attr("stroke", "#388985").attr("stroke-width", 0.7).attr("filter", null);
}
// v1.1 added new lake and coast groups
if (!document.getElementById("sinkhole")) {
lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava");
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null);
lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island");
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)");
coastline.select("#lake_island").attr("opacity", 1).attr("stroke", "#7c8eaf").attr("stroke-width", 0.35).attr("filter", null);
}
// v1.1 features stores more data
defs.select("#land").selectAll("path").remove();
defs.select("#water").selectAll("path").remove();
coastline.selectAll("path").remove();
lakes.selectAll("path").remove();
drawCoastline();
}
if (version < 1.11) {
// 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);
// v1.11 added cultures heirarchy tree
if (pack.cultures[1] && !pack.cultures[1].code) {
pack.cultures
.filter(c => c.i)
.forEach(c => {
c.origin = 0;
c.code = c.name.slice(0, 2);
});
}
// v1.11 had an issue with fogging being displayed on load
unfog();
// 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) {
// v1.11 replaced "display" attribute by "display" style
viewbox.selectAll("g").each(function () {
if (this.hasAttribute("display")) {
this.removeAttribute("display");
this.style.display = "none";
}
});
// v1.21 added rivers data to pack
pack.rivers = []; // rivers data
rivers.selectAll("path").each(function () {
const i = +this.id.slice(5);
const length = this.getTotalLength() / 2;
const s = this.getPointAtLength(length),
e = this.getPointAtLength(0);
const source = findCell(s.x, s.y),
mouth = findCell(e.x, e.y);
const name = Rivers.getName(mouth);
const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type});
});
}
if (version < 1.22) {
// v1.22 changed state neighbors from Set object to array
BurgsAndStates.collectStatistics();
}
if (version < 1.3) {
// 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";
const eraShort = era[0] + "E";
const military = Military.getDefaultOptions();
options = {winds, year, era, eraShort, military};
// v1.3 added campaings data for all states
BurgsAndStates.generateCampaigns();
// 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");
Military.generate();
}
if (version < 1.4) {
// 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);
}
// 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();
// 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;
}
function getUnitIcon(type) {
if (type === "naval") return "🌊";
if (type === "ranged") return "🏹";
if (type === "mounted") return "🐴";
if (type === "machinery") return "💣";
if (type === "armored") return "🐢";
if (type === "aviation") return "🦅";
if (type === "magical") return "🔮";
else return "⚔️";
}
// v1.4 added state reference for regiments
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
}
if (version < 1.5) {
// not need to store default styles from v 1.5
localStorage.removeItem("styleClean");
localStorage.removeItem("styleGloom");
localStorage.removeItem("styleAncient");
localStorage.removeItem("styleMonochrome");
// v1.5 cultures has shield attribute
pack.cultures.forEach(culture => {
if (culture.removed) return;
culture.shield = Cultures.getRandomShield();
});
// v1.5 added burg type value
pack.burgs.forEach(burg => {
if (!burg.i || burg.removed) return;
burg.type = BurgsAndStates.getType(burg.cell, burg.port);
});
// 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");
emblems.append("g").attr("id", "provinceEmblems");
emblems.append("g").attr("id", "stateEmblems");
regenerateEmblems();
toggleEmblems();
// 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");
this.removeAttribute("data-type");
this.removeAttribute("data-size");
this.setAttribute("href", type);
});
}
if (version < 1.6) {
// v1.6 changed rivers data
for (const river of pack.rivers) {
const el = document.getElementById("river" + river.i);
if (el) {
river.widthFactor = +el.getAttribute("data-width");
el.removeAttribute("data-width");
el.removeAttribute("data-increment");
river.discharge = pack.cells.fl[river.mouth] || 1;
river.width = rn(river.length / 100, 2);
river.sourceWidth = 0.1;
} else {
Rivers.remove(river.i);
}
}
// v1.6 changed lakes data
for (const f of pack.features) {
if (f.type !== "lake") continue;
if (f.evaporation) continue;
f.flux = f.flux || f.cells * 3;
f.temp = grid.cells.temp[pack.cells.g[f.firstCell]];
f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20));
const height = (f.height - 18) ** heightExponentInput.value;
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp);
f.evaporation = rn(evaporation * f.cells);
f.name = f.name || Lakes.getName(f);
delete f.river;
}
}
if (version < 1.61) {
// v1.61 changed rulers data
ruler.style("display", null);
rulers = new Rulers();
ruler.selectAll(".ruler > .white").each(function () {
const x1 = +this.getAttribute("x1");
const y1 = +this.getAttribute("y1");
const x2 = +this.getAttribute("x2");
const y2 = +this.getAttribute("y2");
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return;
const points = [
[x1, y1],
[x2, y2]
];
rulers.create(Ruler, points);
});
ruler.selectAll("g.opisometer").each(function () {
const pointsString = this.dataset.points;
if (!pointsString) return;
const points = JSON.parse(pointsString);
rulers.create(Opisometer, points);
});
ruler.selectAll("path.planimeter").each(function () {
const length = this.getTotalLength();
if (length < 30) return;
const step = length > 1000 ? 40 : length > 400 ? 20 : 10;
const increment = length / Math.ceil(length / step);
const points = [];
for (let i = 0; i <= length; i += increment) {
const point = this.getPointAtLength(i);
points.push([point.x | 0, point.y | 0]);
}
rulers.create(Planimeter, points);
});
ruler.selectAll("*").remove();
if (rulers.data.length) {
turnButtonOn("toggleRulers");
rulers.draw();
} else turnButtonOff("toggleRulers");
// 1.61 changed oceanicPattern from rect to image
const pattern = document.getElementById("oceanic");
const filter = pattern.firstElementChild.getAttribute("filter");
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100" opacity="0.2"></image>`;
}
if (version < 1.62) {
// v1.62 changed grid data
gridOverlay.attr("size", null);
}
if (version < 1.63) {
// v1.63 changed ocean pattern opacity element
const oceanPattern = document.getElementById("oceanPattern");
if (oceanPattern) oceanPattern.removeAttribute("opacity");
const oceanicPattern = document.getElementById("oceanicPattern");
if (!oceanicPattern.getAttribute("opacity")) oceanicPattern.setAttribute("opacity", 0.2);
// v 1.63 moved label text-shadow from css to editable inline style
burgLabels.select("#cities").style("text-shadow", "white 0 0 4px");
burgLabels.select("#towns").style("text-shadow", "white 0 0 4px");
labels.select("#states").style("text-shadow", "white 0 0 4px");
labels.select("#addedLabels").style("text-shadow", "white 0 0 4px");
}
if (version < 1.64) {
// v1.64 change states style
const opacity = regions.attr("opacity");
const filter = regions.attr("filter");
statesBody.attr("opacity", opacity).attr("filter", filter);
statesHalo.attr("opacity", opacity).attr("filter", "blur(5px)");
regions.attr("opacity", null).attr("filter", null);
}
if (version < 1.65) {
// 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);
if (node && !river.cells) {
const riverCells = [];
const riverPoints = [];
const length = node.getTotalLength() / 2;
if (!length) continue;
const segments = Math.ceil(length / 6);
const increment = length / segments;
for (let i = 0; i <= segments; i++) {
const shift = increment * i;
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
const x = rn((x1 + x2) / 2, 1);
const y = rn((y1 + y2) / 2, 1);
const cell = findCell(x, y);
riverPoints.push([x, y]);
riverCells.push(cell);
}
river.cells = riverCells;
river.points = riverPoints;
}
river.widthFactor = defaultWidthFactor;
cells.i.forEach(i => {
const riverInWater = cells.r[i] && cells.h[i] < 20;
if (riverInWater) cells.r[i] = 0;
});
}
}
if (version < 1.652) {
// remove style to unhide layers
rivers.attr("style", null);
borders.attr("style", null);
}
if (version < 1.7) {
// v1.7 changed markers data
const defs = document.getElementById("defs-markers");
const markersGroup = document.getElementById("markers");
if (defs && markersGroup) {
const markerElements = markersGroup.querySelectorAll("use");
const rescale = +markersGroup.getAttribute("rescale");
pack.markers = Array.from(markerElements).map((el, i) => {
const id = el.getAttribute("id");
const note = notes.find(note => note.id === id);
if (note) note.id = `marker${i}`;
let x = +el.dataset.x;
let y = +el.dataset.y;
const transform = el.getAttribute("transform");
if (transform) {
const [dx, dy] = parseTransform(transform);
if (dx) x += +dx;
if (dy) y += +dy;
}
const cell = findCell(x, y);
const size = rn(rescale ? el.dataset.size * 30 : el.getAttribute("width"), 1);
const href = el.href.baseVal;
const type = href.replace("#marker_", "");
const symbol = defs?.querySelector(`symbol${href}`);
const text = symbol?.querySelector("text");
const circle = symbol?.querySelector("circle");
const icon = text?.innerHTML;
const px = text && Number(text.getAttribute("font-size")?.replace("px", ""));
const dx = text && Number(text.getAttribute("x")?.replace("%", ""));
const dy = text && Number(text.getAttribute("y")?.replace("%", ""));
const fill = circle && circle.getAttribute("fill");
const stroke = circle && circle.getAttribute("stroke");
const marker = {i, icon, type, x, y, size, cell};
if (size && size !== 30) marker.size = size;
if (!isNaN(px) && px !== 12) marker.px = px;
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
if (!isNaN(dy) && dy !== 50) marker.dy = dy;
if (fill && fill !== "#ffffff") marker.fill = fill;
if (stroke && stroke !== "#000000") marker.stroke = stroke;
if (circle?.getAttribute("opacity") === "0") marker.pin = "no";
return marker;
});
markersGroup.style.display = null;
defs?.remove();
markerElements.forEach(el => el.remove());
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();
// v1.73 added zone type to UI, ensure type is populated
const zones = Array.from(document.querySelectorAll("#zones > g"));
zones.forEach(zone => {
if (!zone.dataset.type) zone.dataset.type = "Unknown";
});
}
}

View file

@ -12,7 +12,6 @@ async load(filename): load filename from provider
async list(): list available filenames at provider async list(): list available filenames at provider
async getLink(filePath): get shareable link for file async getLink(filePath): get shareable link for file
restore(): restore access tokens from storage if possible restore(): restore access tokens from storage if possible
*/ */
window.Cloud = (function () { window.Cloud = (function () {
@ -32,38 +31,40 @@ window.Cloud = (function () {
token: null, // Access token token: null, // Access token
api: null, api: null,
restore() {
this.token = getToken(this.name);
if (this.token) this.connect(this.token);
},
async call(name, param) { async call(name, param) {
try { try {
if (!this.api) await this.initialize();
return await this.api[name](param); return await this.api[name](param);
} catch (e) { } catch (e) {
if (e.name !== "DropboxResponseError") throw e; if (e.name !== "DropboxResponseError") throw e;
// retry with auth await this.auth(); // retry with auth
await this.auth();
return await this.api[name](param); return await this.api[name](param);
} }
}, },
connect(token) { initialize() {
const clientId = this.clientId; const token = getToken(this.name);
const auth = new Dropbox.DropboxAuth({clientId}); if (token) {
return this.connect(token);
} else {
return this.auth();
}
},
async connect(token) {
await import("https://unpkg.com/dropbox@10.8.0/dist/Dropbox-sdk.min.js");
const auth = new Dropbox.DropboxAuth({clientId: this.clientId});
auth.setAccessToken(token); auth.setAccessToken(token);
this.api = new Dropbox.Dropbox({auth}); this.api = new Dropbox.Dropbox({auth});
}, },
async save(fileName, contents) { async save(fileName, contents) {
if (!this.api) await this.auth(); const resp = await this.call("filesUpload", {path: "/" + fileName, contents});
const resp = this.call("filesUpload", {path: "/" + fileName, contents});
DEBUG && console.log("Dropbox response:", resp); DEBUG && console.log("Dropbox response:", resp);
return true; return true;
}, },
async load(path) { async load(path) {
if (!this.api) await this.auth();
const resp = await this.call("filesDownload", {path}); const resp = await this.call("filesDownload", {path});
const blob = resp.result.fileBlob; const blob = resp.result.fileBlob;
if (!blob) throw new Error("Invalid response from dropbox."); if (!blob) throw new Error("Invalid response from dropbox.");
@ -71,22 +72,23 @@ window.Cloud = (function () {
}, },
async list() { async list() {
if (!this.api) return null;
const resp = await this.call("filesListFolder", {path: ""}); const resp = await this.call("filesListFolder", {path: ""});
return resp.result.entries.map(e => ({name: e.name, path: e.path_lower})); return resp.result.entries.map(e => ({name: e.name, path: e.path_lower}));
}, },
auth() { auth() {
const url = window.location.origin + window.location.pathname + "dropbox.html"; const width = 640;
this.authWindow = window.open(url, "auth", "width=640,height=480"); const height = 480;
// child window expected to call const left = window.innerWidth / 2 - width / 2;
// window.opener.Cloud.providers.dropbox.setDropBoxToken (see below) const top = window.innerHeight / 2 - height / 2.5;
this.authWindow = window.open("./dropbox.html", "auth", `width=640, height=${height}, top=${top}, left=${left}}`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const watchDog = () => { const watchDog = setTimeout(() => {
this.authWindow.close(); this.authWindow.close();
reject(new Error("Timeout. No auth for dropbox.")); reject(new Error("Timeout. No auth for Dropbox"));
}; }, 120 * 1000);
setTimeout(watchDog, 120 * 1000);
window.addEventListener("dropboxauth", e => { window.addEventListener("dropboxauth", e => {
clearTimeout(watchDog); clearTimeout(watchDog);
resolve(); resolve();
@ -94,46 +96,34 @@ window.Cloud = (function () {
}); });
}, },
// Callback function for auth window. // Callback function for auth window
setDropBoxToken(token) { async setDropBoxToken(token) {
DEBUG && console.log("Access token:", token); DEBUG && console.log("Access token:", token);
setToken(this.name, token); setToken(this.name, token);
this.connect(token); await this.connect(token);
this.authWindow.close(); this.authWindow.close();
window.dispatchEvent(new Event("dropboxauth")); window.dispatchEvent(new Event("dropboxauth"));
}, },
returnError(errorDescription) {
console.error(errorDescription);
tip(errorDescription.replaceAll("+", " "), true, "error", 4000);
this.authWindow.close();
},
async getLink(path) { async getLink(path) {
if (!this.api) await this.auth(); // return existitng shared link
let resp; const sharedLinks = await this.call("sharingListSharedLinks", {path});
if (sharedLinks.result.links.length) return resp.result.links[0].url;
// already exists? // create new shared link
resp = await this.call("sharingListSharedLinks", {path}); const settings = {require_password: false, audience: "public", access: "viewer", requested_visibility: "public", allow_download: true};
if (resp.result.links.length) return resp.result.links[0].url; const resp = await this.call("sharingCreateSharedLinkWithSettings", {path, settings});
// create new
resp = await this.call("sharingCreateSharedLinkWithSettings", {
path,
settings: {
require_password: false,
audience: "public",
access: "viewer",
requested_visibility: "public",
allow_download: true
}
});
DEBUG && console.log("Dropbox link object:", resp.result); DEBUG && console.log("Dropbox link object:", resp.result);
return resp.result.url; return resp.result.url;
} }
}; };
// register providers here: const providers = {dropbox: DBP};
const providers = {
dropbox: DBP
};
// restore all providers at startup
for (const p of Object.values(providers)) p.restore();
return {providers}; return {providers};
})(); })();

209
modules/io/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 (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("fogging-cont")) cloneEl.getElementById("fog")?.remove(); // remove unused fog
if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths if (!cloneEl.getElementById("regions")) cloneEl.getElementById("statePaths")?.remove(); // removed unused statePaths
if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths if (!cloneEl.getElementById("labels")) cloneEl.getElementById("textPaths")?.remove(); // removed unused textPaths
// add armies style // add armies style
if (cloneEl.getElementById("armies")) if (cloneEl.getElementById("armies")) {
cloneEl.insertAdjacentHTML( cloneEl.insertAdjacentHTML(
"afterbegin", "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>" "<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") { if (type === "svg") {
cloneEl.querySelectorAll("[href]").forEach(el => { cloneEl.querySelectorAll("[href]").forEach(el => {
const href = el.getAttribute("href"); 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 usedFonts = getUsedFonts(cloneEl);
const fontsToLoad = usedFonts.filter(font => font.src); const fontsToLoad = usedFonts.filter(font => font.src);
if (fontsToLoad.length) { if (fontsToLoad.length) {

View file

@ -145,7 +145,7 @@ function parseLoadedResult(result) {
const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]); const mapVersion = parseFloat(mapData[0].split("|")[0] || mapData[0]);
return [mapData, mapVersion]; return [mapData, mapVersion];
} catch (error) { } catch (error) {
console.error(error); ERROR && console.error(error);
return [null, null]; return [null, null];
} }
} }
@ -182,18 +182,15 @@ function showUploadMessage(type, mapData, mapVersion) {
$("#alert").dialog({title, buttons}); $("#alert").dialog({title, buttons});
} }
function parseLoadedData(data) { async function parseLoadedData(data) {
try { try {
// exit customization // exit customization
if (window.closeDialogs) closeDialogs(); if (window.closeDialogs) closeDialogs();
customization = 0; customization = 0;
if (customizationMenu.offsetParent) styleTab.click(); if (customizationMenu.offsetParent) styleTab.click();
const reliefIcons = document.getElementById("defs-relief").innerHTML; // save relief icons const params = data[0].split("|");
const hatching = document.getElementById("hatching").cloneNode(true); // save hatching
void (function parseParameters() { void (function parseParameters() {
const params = data[0].split("|");
if (params[3]) { if (params[3]) {
seed = params[3]; seed = params[3];
optionsSeed.value = seed; optionsSeed.value = seed;
@ -423,513 +420,12 @@ function parseLoadedData(data) {
legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend()); legend.on("mousemove", () => tip("Drag to change the position. Click to hide the legend")).on("click", () => clearLegend());
})(); })();
void (function resolveVersionConflicts() { {
const version = parseFloat(data[0].split("|")[0]); // dynamically import and run auto-udpdate script
if (version < 0.9) { const version = parseFloat(params[0]);
// 0.9 has additional relief icons to be included into older maps const {resolveVersionConflicts} = await import("./auto-update.js");
document.getElementById("defs-relief").innerHTML = reliefIcons; resolveVersionConflicts(version);
} }
if (version < 1) {
// 1.0 adds a new religions layer
relig = viewbox.insert("g", "#terrain").attr("id", "relig");
Religions.generate();
// 1.0 adds a legend box
legend = svg.append("g").attr("id", "legend");
legend
.attr("font-family", "Almendra SC")
.attr("font-size", 13)
.attr("data-size", 13)
.attr("data-x", 99)
.attr("data-y", 93)
.attr("stroke-width", 2.5)
.attr("stroke", "#812929")
.attr("stroke-dasharray", "0 4 10 4")
.attr("stroke-linecap", "round");
// 1.0 separated drawBorders fron drawStates()
stateBorders = borders.append("g").attr("id", "stateBorders");
provinceBorders = borders.append("g").attr("id", "provinceBorders");
borders
.attr("opacity", null)
.attr("stroke", null)
.attr("stroke-width", null)
.attr("stroke-dasharray", null)
.attr("stroke-linecap", null)
.attr("filter", null);
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
provs = viewbox.insert("g", "#borders").attr("id", "provs").attr("opacity", 0.6);
BurgsAndStates.collectStatistics();
BurgsAndStates.generateCampaigns();
BurgsAndStates.generateDiplomacy();
BurgsAndStates.defineStateForms();
drawStates();
BurgsAndStates.generateProvinces();
drawBorders();
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
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();
if (!markers.selectAll("*").size()) {
Markers.generate();
turnButtonOn("toggleMarkers");
}
// 1.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
if (statesBody.attr("opacity")) {
regions.attr("opacity", statesBody.attr("opacity"));
statesBody.attr("opacity", null);
}
// 1.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
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
if (!relig.size()) relig = viewbox.insert("g", "#terrain").attr("id", "relig");
// v 1.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));
}
// labels should be toggled via style attribute, so remove display attribute
labels.attr("display", null);
// v 1.0 added religions heirarchy tree
if (pack.religions[1] && !pack.religions[1].code) {
pack.religions
.filter(r => r.i)
.forEach(r => {
r.origin = 0;
r.code = r.name.slice(0, 2);
});
}
if (!document.getElementById("freshwater")) {
lakes.append("g").attr("id", "freshwater");
lakes.select("#freshwater").attr("opacity", 0.5).attr("fill", "#a6c1fd").attr("stroke", "#5f799d").attr("stroke-width", 0.7).attr("filter", null);
}
if (!document.getElementById("salt")) {
lakes.append("g").attr("id", "salt");
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
if (!document.getElementById("sinkhole")) {
lakes.append("g").attr("id", "sinkhole");
lakes.append("g").attr("id", "frozen");
lakes.append("g").attr("id", "lava");
lakes.select("#sinkhole").attr("opacity", 1).attr("fill", "#5bc9fd").attr("stroke", "#53a3b0").attr("stroke-width", 0.7).attr("filter", null);
lakes.select("#frozen").attr("opacity", 0.95).attr("fill", "#cdd4e7").attr("stroke", "#cfe0eb").attr("stroke-width", 0).attr("filter", null);
lakes.select("#lava").attr("opacity", 0.7).attr("fill", "#90270d").attr("stroke", "#f93e0c").attr("stroke-width", 2).attr("filter", "url(#crumpled)");
coastline.append("g").attr("id", "sea_island");
coastline.append("g").attr("id", "lake_island");
coastline.select("#sea_island").attr("opacity", 0.5).attr("stroke", "#1f3846").attr("stroke-width", 0.7).attr("filter", "url(#dropShadow)");
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
defs.select("#land").selectAll("path").remove();
defs.select("#water").selectAll("path").remove();
coastline.selectAll("path").remove();
lakes.selectAll("path").remove();
drawCoastline();
}
if (version < 1.11) {
// v 1.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
if (pack.cultures[1] && !pack.cultures[1].code) {
pack.cultures
.filter(c => c.i)
.forEach(c => {
c.origin = 0;
c.code = c.name.slice(0, 2);
});
}
// v 1.11 had an issue with fogging being displayed on load
unfog();
// v 1.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
viewbox.selectAll("g").each(function () {
if (this.hasAttribute("display")) {
this.removeAttribute("display");
this.style.display = "none";
}
});
// v 1.21 added rivers data to pack
pack.rivers = []; // rivers data
rivers.selectAll("path").each(function () {
const i = +this.id.slice(5);
const length = this.getTotalLength() / 2;
const s = this.getPointAtLength(length),
e = this.getPointAtLength(0);
const source = findCell(s.x, s.y),
mouth = findCell(e.x, e.y);
const name = Rivers.getName(mouth);
const type = length < 25 ? rw({Creek: 9, River: 3, Brook: 3, Stream: 1}) : "River";
pack.rivers.push({i, parent: 0, length, source, mouth, basin: i, name, type});
});
}
if (version < 1.22) {
// v 1.22 changed state neighbors from Set object to array
BurgsAndStates.collectStatistics();
}
if (version < 1.3) {
// v 1.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";
const eraShort = era[0] + "E";
const military = Military.getDefaultOptions();
options = {winds, year, era, eraShort, military};
// v 1.3 added campaings data for all states
BurgsAndStates.generateCampaigns();
// v 1.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");
Military.generate();
}
if (version < 1.4) {
// v 1.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
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
for (const unit of options.military) {
if (!unit.icon) unit.icon = getUnitIcon(unit.type);
if (!unit.power) unit.power = unit.crew;
}
function getUnitIcon(type) {
if (type === "naval") return "🌊";
if (type === "ranged") return "🏹";
if (type === "mounted") return "🐴";
if (type === "machinery") return "💣";
if (type === "armored") return "🐢";
if (type === "aviation") return "🦅";
if (type === "magical") return "🔮";
else return "⚔️";
}
// 1.4 added state reference for regiments
pack.states.filter(s => s.military).forEach(s => s.military.forEach(r => (r.state = s.i)));
}
if (version < 1.5) {
// not need to store default styles from v 1.5
localStorage.removeItem("styleClean");
localStorage.removeItem("styleGloom");
localStorage.removeItem("styleAncient");
localStorage.removeItem("styleMonochrome");
// v 1.5 cultures has shield attribute
pack.cultures.forEach(culture => {
if (culture.removed) return;
culture.shield = Cultures.getRandomShield();
});
// v 1.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
defs.append("g").attr("id", "defs-emblems");
emblems = viewbox.insert("g", "#population").attr("id", "emblems").style("display", "none");
emblems.append("g").attr("id", "burgEmblems");
emblems.append("g").attr("id", "provinceEmblems");
emblems.append("g").attr("id", "stateEmblems");
regenerateEmblems();
toggleEmblems();
// v 1.5 changed releif icons data
terrain.selectAll("use").each(function () {
const type = this.getAttribute("data-type") || this.getAttribute("xlink:href");
this.removeAttribute("xlink:href");
this.removeAttribute("data-type");
this.removeAttribute("data-size");
this.setAttribute("href", type);
});
}
if (version < 1.6) {
// v 1.6 changed rivers data
for (const river of pack.rivers) {
const el = document.getElementById("river" + river.i);
if (el) {
river.widthFactor = +el.getAttribute("data-width");
el.removeAttribute("data-width");
el.removeAttribute("data-increment");
river.discharge = pack.cells.fl[river.mouth] || 1;
river.width = rn(river.length / 100, 2);
river.sourceWidth = 0.1;
} else {
Rivers.remove(river.i);
}
}
// v 1.6 changed lakes data
for (const f of pack.features) {
if (f.type !== "lake") continue;
if (f.evaporation) continue;
f.flux = f.flux || f.cells * 3;
f.temp = grid.cells.temp[pack.cells.g[f.firstCell]];
f.height = f.height || d3.min(pack.cells.c[f.firstCell].map(c => pack.cells.h[c]).filter(h => h >= 20));
const height = (f.height - 18) ** heightExponentInput.value;
const evaporation = ((700 * (f.temp + 0.006 * height)) / 50 + 75) / (80 - f.temp);
f.evaporation = rn(evaporation * f.cells);
f.name = f.name || Lakes.getName(f);
delete f.river;
}
}
if (version < 1.61) {
// v 1.61 changed rulers data
ruler.style("display", null);
rulers = new Rulers();
ruler.selectAll(".ruler > .white").each(function () {
const x1 = +this.getAttribute("x1");
const y1 = +this.getAttribute("y1");
const x2 = +this.getAttribute("x2");
const y2 = +this.getAttribute("y2");
if (isNaN(x1) || isNaN(y1) || isNaN(x2) || isNaN(y2)) return;
const points = [
[x1, y1],
[x2, y2]
];
rulers.create(Ruler, points);
});
ruler.selectAll("g.opisometer").each(function () {
const pointsString = this.dataset.points;
if (!pointsString) return;
const points = JSON.parse(pointsString);
rulers.create(Opisometer, points);
});
ruler.selectAll("path.planimeter").each(function () {
const length = this.getTotalLength();
if (length < 30) return;
const step = length > 1000 ? 40 : length > 400 ? 20 : 10;
const increment = length / Math.ceil(length / step);
const points = [];
for (let i = 0; i <= length; i += increment) {
const point = this.getPointAtLength(i);
points.push([point.x | 0, point.y | 0]);
}
rulers.create(Planimeter, points);
});
ruler.selectAll("*").remove();
if (rulers.data.length) {
turnButtonOn("toggleRulers");
rulers.draw();
} else turnButtonOff("toggleRulers");
// 1.61 changed oceanicPattern from rect to image
const pattern = document.getElementById("oceanic");
const filter = pattern.firstElementChild.getAttribute("filter");
const href = filter ? "./images/" + filter.replace("url(#", "").replace(")", "") + ".png" : "";
pattern.innerHTML = `<image id="oceanicPattern" href=${href} width="100" height="100" opacity="0.2"></image>`;
}
if (version < 1.62) {
// v 1.62 changed grid data
gridOverlay.attr("size", null);
}
if (version < 1.63) {
// v.1.63 changed ocean pattern opacity element
const oceanPattern = document.getElementById("oceanPattern");
if (oceanPattern) oceanPattern.removeAttribute("opacity");
const oceanicPattern = document.getElementById("oceanicPattern");
if (!oceanicPattern.getAttribute("opacity")) oceanicPattern.setAttribute("opacity", 0.2);
// v 1.63 moved label text-shadow from css to editable inline style
burgLabels.select("#cities").style("text-shadow", "white 0 0 4px");
burgLabels.select("#towns").style("text-shadow", "white 0 0 4px");
labels.select("#states").style("text-shadow", "white 0 0 4px");
labels.select("#addedLabels").style("text-shadow", "white 0 0 4px");
}
if (version < 1.64) {
// v.1.64 change states style
const opacity = regions.attr("opacity");
const filter = regions.attr("filter");
statesBody.attr("opacity", opacity).attr("filter", filter);
statesHalo.attr("opacity", opacity).attr("filter", "blur(5px)");
regions.attr("opacity", null).attr("filter", null);
}
if (version < 1.65) {
// v 1.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);
if (node && !river.cells) {
const riverCells = [];
const riverPoints = [];
const length = node.getTotalLength() / 2;
if (!length) continue;
const segments = Math.ceil(length / 6);
const increment = length / segments;
for (let i = 0; i <= segments; i++) {
const shift = increment * i;
const {x: x1, y: y1} = node.getPointAtLength(length + shift);
const {x: x2, y: y2} = node.getPointAtLength(length - shift);
const x = rn((x1 + x2) / 2, 1);
const y = rn((y1 + y2) / 2, 1);
const cell = findCell(x, y);
riverPoints.push([x, y]);
riverCells.push(cell);
}
river.cells = riverCells;
river.points = riverPoints;
}
river.widthFactor = defaultWidthFactor;
cells.i.forEach(i => {
const riverInWater = cells.r[i] && cells.h[i] < 20;
if (riverInWater) cells.r[i] = 0;
});
}
}
if (version < 1.652) {
// remove style to unhide layers
rivers.attr("style", null);
borders.attr("style", null);
}
if (version < 1.7) {
// v 1.7 changed markers data
const defs = document.getElementById("defs-markers");
const markersGroup = document.getElementById("markers");
if (defs && markersGroup) {
const markerElements = markersGroup.querySelectorAll("use");
const rescale = +markersGroup.getAttribute("rescale");
pack.markers = Array.from(markerElements).map((el, i) => {
const id = el.getAttribute("id");
const note = notes.find(note => note.id === id);
if (note) note.id = `marker${i}`;
let x = +el.dataset.x;
let y = +el.dataset.y;
const transform = el.getAttribute("transform");
if (transform) {
const [dx, dy] = parseTransform(transform);
if (dx) x += +dx;
if (dy) y += +dy;
}
const cell = findCell(x, y);
const size = rn(rescale ? el.dataset.size * 30 : el.getAttribute("width"), 1);
const href = el.href.baseVal;
const type = href.replace("#marker_", "");
const symbol = defs?.querySelector(`symbol${href}`);
const text = symbol?.querySelector("text");
const circle = symbol?.querySelector("circle");
const icon = text?.innerHTML;
const px = text && Number(text.getAttribute("font-size")?.replace("px", ""));
const dx = text && Number(text.getAttribute("x")?.replace("%", ""));
const dy = text && Number(text.getAttribute("y")?.replace("%", ""));
const fill = circle && circle.getAttribute("fill");
const stroke = circle && circle.getAttribute("stroke");
const marker = {i, icon, type, x, y, size, cell};
if (size && size !== 30) marker.size = size;
if (!isNaN(px) && px !== 12) marker.px = px;
if (!isNaN(dx) && dx !== 50) marker.dx = dx;
if (!isNaN(dy) && dy !== 50) marker.dy = dy;
if (fill && fill !== "#ffffff") marker.fill = fill;
if (stroke && stroke !== "#000000") marker.stroke = stroke;
if (circle?.getAttribute("opacity") === "0") marker.pin = "no";
return marker;
});
markersGroup.style.display = null;
defs?.remove();
markerElements.forEach(el => el.remove());
if (layerIsOn("markers")) drawMarkers();
}
}
})();
void (function checkDataIntegrity() { void (function checkDataIntegrity() {
const cells = pack.cells; const cells = pack.cells;
@ -1070,6 +566,7 @@ function parseLoadedData(data) {
alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load, alertMessage.innerHTML = `An error is occured on map loading. Select a different file to load,
<br>generate a new random map or cancel the loading <br>generate a new random map or cancel the loading
<p id="errorBox">${parseError(error)}</p>`; <p id="errorBox">${parseError(error)}</p>`;
$("#alert").dialog({ $("#alert").dialog({
resizable: false, resizable: false,
title: "Loading error", title: "Loading error",

View file

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

View file

@ -519,7 +519,7 @@ window.Markers = (function () {
const dungeonSeed = `${seed}${cell}`; const dungeonSeed = `${seed}${cell}`;
const name = "Dungeon"; const name = "Dungeon";
const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe style="height: 33vh" src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`; const legend = `<div>Undiscovered dungeon. See <a href="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" target="_blank">One page dungeon</a></div><iframe src="https://watabou.github.io/one-page-dungeon/?seed=${dungeonSeed}" sandbox="allow-scripts allow-same-origin"></iframe>`;
notes.push({id, name, legend}); notes.push({id, name, legend});
quantity--; quantity--;
} }

View file

@ -141,8 +141,8 @@ class Battle {
const state = pack.states[regiment.state]; 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 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 color = state.color[0] === "#" ? state.color : "#999";
const icon = `<svg width="1.4em" height="1.4em" style="margin-bottom: -.6em"> 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}" class="fillRect"></rect> <rect x="0" y="0" width="100%" height="100%" fill="${color}"></rect>
<text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`; <text x="0" y="1.04em" style="">${regiment.icon}</text></svg>`;
const body = `<tbody id="battle${state.i}-${regiment.i}">`; const body = `<tbody id="battle${state.i}-${regiment.i}">`;
@ -183,7 +183,7 @@ class Battle {
dist = added ? "0 " + distanceUnitInput.value : distance(r); 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} 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"> 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:6em">${s.name.slice(0, 11)}</div>
<div style="width:1.2em">${r.icon}</div> <div style="width:1.2em">${r.icon}</div>
<div style="width:13em">${r.name.slice(0, 24)}</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); document.getElementById("biomesExport").addEventListener("click", downloadBiomesData);
body.addEventListener("click", function (ev) { body.addEventListener("click", function (ev) {
const el = ev.target, const el = ev.target;
cl = el.classList; const cl = el.classList;
if (cl.contains("fillRect")) biomeChangeColor(el); if (el.tagName === "FILL-BOX") biomeChangeColor(el);
else if (cl.contains("icon-info-circled")) openWiki(el); else if (cl.contains("icon-info-circled")) openWiki(el);
else if (cl.contains("icon-trash-empty")) removeCustomBiome(el); else if (cl.contains("icon-trash-empty")) removeCustomBiome(el);
if (customization === 6) selectBiomeOnLineClick(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]}" 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]}> 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="${ <fill-box fill="${b.color[i]}"></fill-box>
b.color[i]
}" class="fillRect pointer"></svg>
<input data-tip="Biome name. Click and type to change" class="biomeName" value="${b.name[i]}" autocorrect="off" spellcheck="false"> <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> <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=${ <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) { function biomeChangeColor(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const biome = +el.parentNode.parentNode.dataset.id; const biome = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
biomesData.color[biome] = fill; biomesData.color[biome] = newFill;
biomes biomes
.select("#biome" + biome) .select("#biome" + biome)
.attr("fill", fill) .attr("fill", newFill)
.attr("stroke", fill); .attr("stroke", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);
@ -270,7 +268,7 @@ function editBiomes() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; 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]}> 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"> <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> <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]}> <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

@ -52,6 +52,7 @@ function editBurg(id) {
document.getElementById("burglLegend").addEventListener("click", editBurgLegend); document.getElementById("burglLegend").addEventListener("click", editBurgLegend);
document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton); document.getElementById("burgLock").addEventListener("click", toggleBurgLockButton);
document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg); document.getElementById("burgRemove").addEventListener("click", removeSelectedBurg);
document.getElementById("burgTemperatureGraph").addEventListener("click", showTemperatureGraph);
function updateBurgValues() { function updateBurgValues() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
@ -543,6 +544,11 @@ function editBurg(id) {
editNotes("burg" + id, name); editNotes("burg" + id, name);
} }
function showTemperatureGraph() {
const id = elSelected.attr("data-id");
showBurgTemperatureGraph(id);
}
function removeSelectedBurg() { function removeSelectedBurg() {
const id = +elSelected.attr("data-id"); const id = +elSelected.attr("data-id");
if (pack.burgs[id].capital) { if (pack.burgs[id].capital) {

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

View file

@ -101,10 +101,8 @@ function editDiplomacy() {
lines += `<div class="states" data-id=${state.i} data-name="${name}" data-relations="${relation}"> 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> <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="${tipSelect}" style="width: 12em">${name}</div>
<div data-tip="${tipChange}" class="changeRelations pointer" style="width: 6em"> <div data-tip="${tipChange}" class="changeRelations" style="width: 6em">
<svg width=".9em" height=".9em" style="margin-bottom:-1px"> <fill-box fill="${color}" size=".9em"></fill-box>
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect"></rect>
</svg>
${relation} ${relation}
</div> </div>
</div>`; </div>`;
@ -195,9 +193,7 @@ function editDiplomacy() {
([relation, {color, inText, tip}]) => ([relation, {color, inText, tip}]) =>
`<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer"> `<div style="margin-block: 0.2em" data-tip="${tip}"><label class="pointer">
<input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} > <input type="radio" name="relationSelect" value="${relation}" ${currentRelation === relation && "checked"} >
<svg width=".9em" height=".9em" style="margin-bottom:-1px"> <fill-box fill="${color}" size=".8em"></fill-box>
<rect x="0" y="0" width="100%" height="100%" fill="${color}" class="fillRect" />
</svg>
${inText} ${inText}
</label></div>` </label></div>`
) )

View file

@ -425,10 +425,10 @@ function clearLegend() {
function createPicker() { function createPicker() {
const pos = () => tip("Drag to change the picker position"); const pos = () => tip("Drag to change the picker position");
const cl = () => tip("Click to close the picker"); 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%"); const container = d3.select("body").append("svg").attr("id", "pickerContainer").attr("width", "100%").attr("height", "100%");
contaiter container
.append("rect") .append("rect")
.attr("x", 0) .attr("x", 0)
.attr("y", 0) .attr("y", 0)
@ -437,7 +437,7 @@ function createPicker() {
.attr("opacity", 0.2) .attr("opacity", 0.2)
.on("mousemove", cl) .on("mousemove", cl)
.on("click", closePicker); .on("click", closePicker);
const picker = contaiter const picker = container
.append("g") .append("g")
.attr("id", "picker") .attr("id", "picker")
.call( .call(
@ -494,7 +494,7 @@ function createPicker() {
const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333"); const colors = picker.append("g").attr("id", "pickerColors").attr("stroke", "#333333");
const hatches = picker.append("g").attr("id", "pickerHatches").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 number = hatching.size();
const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex()); const clr = d3.range(number).map(i => d3.hsl((i / number) * 360, 0.7, 0.7).hex());
@ -504,8 +504,8 @@ function createPicker() {
.attr("id", "picker_" + d) .attr("id", "picker_" + d)
.attr("fill", d) .attr("fill", d)
.attr("class", i ? "" : "selected") .attr("class", i ? "" : "selected")
.attr("x", i * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", 40) .attr("y", 40 + Math.floor(i / 14)*20)
.attr("width", 16) .attr("width", 16)
.attr("height", 16); .attr("height", 16);
}); });
@ -515,20 +515,20 @@ function createPicker() {
.append("rect") .append("rect")
.attr("id", "picker_" + this.id) .attr("id", "picker_" + this.id)
.attr("fill", "url(#" + this.id + ")") .attr("fill", "url(#" + this.id + ")")
.attr("x", i * 22 + 4) .attr("x", (i % 14) * 22 + 4)
.attr("y", 61) .attr("y", Math.floor(i / 14)*20 + 20 + (number * 2))
.attr("width", 16) .attr("width", 16)
.attr("height", 16); .attr("height", 16)
}); });
colors colors
.selectAll("rect") .selectAll("rect")
.on("click", pickerFillClicked) .on("click", pickerFillClicked)
.on("mousemove", () => tip("Click to fill with the color")); .on("mouseover", () => tip("Click to fill with the color"));
hatches hatches
.selectAll("rect") .selectAll("rect")
.on("click", pickerFillClicked) .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 // append box
const bbox = picker.node().getBBox(); const bbox = picker.node().getBBox();
@ -544,10 +544,10 @@ function createPicker() {
.attr("fill", "#ffffff") .attr("fill", "#ffffff")
.attr("stroke", "#5d4651") .attr("stroke", "#5d4651")
.on("mousemove", pos); .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 picker
.insert("rect", ":first-child") .insert("rect", ":first-child")
.attr("x", 288) .attr("x", width-23)
.attr("y", -21) .attr("y", -21)
.attr("id", "pickerCloseRect") .attr("id", "pickerCloseRect")
.attr("width", 14) .attr("width", 14)

View file

@ -70,7 +70,8 @@ function mouseMove() {
const point = d3.mouse(this); const point = d3.mouse(this);
const i = findCell(point[0], point[1]); // pack cell id const i = findCell(point[0], point[1]); // pack cell id
if (i === undefined) return; if (i === undefined) return;
showNotes(d3.event, i);
showNotes(d3.event);
const g = findGridCell(point[0], point[1]); // grid cell id const g = findGridCell(point[0], point[1]); // grid cell id
if (tooltip.dataset.main) showMainTip(); if (tooltip.dataset.main) showMainTip();
else showMapTooltip(point, d3.event, i, g); else showMapTooltip(point, d3.event, i, g);
@ -78,7 +79,7 @@ function mouseMove() {
} }
// show note box on hover (if any) // show note box on hover (if any)
function showNotes(e, i) { function showNotes(e) {
if (notesEditor.offsetParent) return; if (notesEditor.offsetParent) return;
let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id; let id = e.target.id || e.target.parentNode.id || e.target.parentNode.parentNode.id;
if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id; if (e.target.parentNode.parentNode.id === "burgLabels") id = "burg" + e.target.dataset.id;

View file

@ -913,11 +913,9 @@ function editHeightmap() {
function uploadTemplate(dataLoaded) { function uploadTemplate(dataLoaded) {
const steps = dataLoaded.split("\r\n"); const steps = dataLoaded.split("\r\n");
if (!steps.length) { if (!steps.length) return tip("Cannot parse the template, please check the file", false, "error");
tip("Cannot parse the template, please check the file", false, "error");
return;
}
templateBody.innerHTML = ""; templateBody.innerHTML = "";
for (const s of steps) { for (const s of steps) {
const step = s.split(" "); const step = s.split(" ");
if (step.length !== 5) { if (step.length !== 5) {
@ -1318,10 +1316,10 @@ function editHeightmap() {
img.onload = function () { img.onload = function () {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d"); const ctx = canvas.getContext("2d");
canvas.width = svgWidth; canvas.width = graphWidth;
canvas.height = svgHeight; canvas.height = graphHeight;
document.body.insertBefore(canvas, optionsContainer); document.body.insertBefore(canvas, optionsContainer);
ctx.drawImage(img, 0, 0, svgWidth, svgHeight); ctx.drawImage(img, 0, 0, graphWidth, graphHeight);
const imgBig = canvas.toDataURL("image/png"); const imgBig = canvas.toDataURL("image/png");
const link = document.createElement("a"); const link = document.createElement("a");
link.download = getFileName("Heightmap") + ".png"; link.download = getFileName("Heightmap") + ".png";

View file

@ -536,12 +536,11 @@ class Planimeter extends Measurer {
} }
// Scale bar // Scale bar
function drawScaleBar(requestedScale) { function drawScaleBar(scaleLevel) {
if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element if (scaleBar.style("display") === "none") return; // no need to re-draw hidden element
scaleBar.selectAll("*").remove(); // fully redraw every time scaleBar.selectAll("*").remove(); // fully redraw every time
const scaleLevel = requestedScale || scale;
const distanceScale = distanceScaleInput.value; const distanceScale = +distanceScaleInput.value;
const unit = distanceUnitInput.value; const unit = distanceUnitInput.value;
const size = +barSizeInput.value; const size = +barSizeInput.value;

View file

@ -75,12 +75,9 @@ function overviewMilitary() {
const sortData = options.military.map(u => `data-${u.name}="${getForces(u)}"`).join(" "); 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(" "); 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="${ lines += `<div class="states" data-id=${s.i} data-state="${s.name}" ${sortData} data-total="${total}"
s.name data-population="${population}" data-rate="${rate}" data-alert="${s.alert}">
}" ${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>
<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>
<input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
${lineData} ${lineData}
<div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div> <div data-type="total" data-tip="Total state military personnel (considering crew)" style="font-weight: bold">${si(total)}</div>

View file

@ -1,25 +1,22 @@
"use strict"; "use strict";
function editNotes(id, name) { function editNotes(id, name) {
// elements
const notesLegend = document.getElementById("notesLegend");
const notesName = document.getElementById("notesName");
const notesSelect = document.getElementById("notesSelect");
const notesPin = document.getElementById("notesPin");
// update list of objects // update list of objects
const select = document.getElementById("notesSelect"); notesSelect.options.length = 0;
select.options.length = 0;
for (const note of notes) { for (const note of notes) {
select.options.add(new Option(note.id, note.id)); notesSelect.options.add(new Option(note.id, note.id));
} }
// initiate pell (html editor) // update pin notes icon
const notesText = document.getElementById("notesText"); const notesArePinned = options.pinNotes;
notesText.innerHTML = ""; if (notesArePinned) notesPin.classList.add("pressed");
const editor = Pell.init({ else notesPin.classList.remove("pressed");
element: notesText,
onChange: html => {
const note = notes.find(note => note.id === select.value);
if (!note) return;
note.legend = html;
showNote(note);
}
});
// select an object // select an object
if (notes.length || id) { if (notes.length || id) {
@ -29,136 +26,161 @@ function editNotes(id, name) {
if (!name) name = id; if (!name) name = id;
note = {id, name, legend: ""}; note = {id, name, legend: ""};
notes.push(note); notes.push(note);
select.options.add(new Option(id, id)); notesSelect.options.add(new Option(id, id));
} }
select.value = id;
notesSelect.value = id;
notesName.value = note.name; notesName.value = note.name;
editor.content.innerHTML = note.legend; notesLegend.innerHTML = note.legend;
showNote(note); initEditor();
updateNotesBox(note);
} else { } else {
editor.content.innerHTML = "There are no added notes. Click on element (e.g. label) and add a free text note"; // if notes array is empty
document.getElementById("notesName").value = ""; notesName.value = "";
notesLegend.innerHTML = "No notes added. Click on an element (e.g. label or marker) and add a free text note";
} }
// open a dialog
$("#notesEditor").dialog({ $("#notesEditor").dialog({
title: "Notes Editor", title: "Notes Editor",
minWidth: "40em", width: "70vw",
width: "50vw", height: window.innerHeight * 0.75,
position: {my: "center", at: "center", of: "svg"} position: {my: "center", at: "center", of: "svg"},
close: removeEditor
}); });
$("[aria-describedby='notesEditor']").css("top", "10vh");
if (modules.editNotes) return; if (modules.editNotes) return;
modules.editNotes = true; modules.editNotes = true;
// add listeners // add listeners
document.getElementById("notesSelect").addEventListener("change", changeObject); document.getElementById("notesSelect").addEventListener("change", changeElement);
document.getElementById("notesName").addEventListener("input", changeName); document.getElementById("notesName").addEventListener("input", changeName);
document.getElementById("notesPin").addEventListener("click", () => (options.pinNotes = !options.pinNotes)); document.getElementById("notesLegend").addEventListener("blur", updateLegend);
document.getElementById("notesSpeak").addEventListener("click", () => speak(editor.content.innerHTML)); document.getElementById("notesPin").addEventListener("click", toggleNotesPin);
document.getElementById("notesFocus").addEventListener("click", validateHighlightElement); document.getElementById("notesFocus").addEventListener("click", validateHighlightElement);
document.getElementById("notesDownload").addEventListener("click", downloadLegends); document.getElementById("notesDownload").addEventListener("click", downloadLegends);
document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click()); document.getElementById("notesUpload").addEventListener("click", () => legendsToLoad.click());
document.getElementById("legendsToLoad").addEventListener("change", function () { document.getElementById("legendsToLoad").addEventListener("change", function () {
uploadFile(this, uploadLegends); uploadFile(this, uploadLegends);
}); });
document.getElementById("notesClearStyle").addEventListener("click", clearStyle);
document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove); document.getElementById("notesRemove").addEventListener("click", triggerNotesRemove);
function showNote(note) { async function initEditor() {
document.getElementById("notes").style.display = "block"; if (!window.tinymce) {
const url = "https://cdn.tiny.cloud/1/4i6a79ymt2y0cagke174jp3meoi28vyecrch12e5puyw3p9a/tinymce/5/tinymce.min.js";
try {
await import(url);
} catch (error) {
// error may be caused by failed request being cached, try again with random hash
try {
const hash = Math.random().toString(36).substring(2, 15);
await import(`${url}#${hash}`);
} catch (error) {
console.error(error);
}
}
}
if (window.tinymce) {
tinymce.init({
selector: "#notesLegend",
height: "90%",
menubar: false,
plugins: `autolink lists link charmap print formatpainter casechange code fullscreen image link media table paste hr checklist wordcount`,
toolbar: `code | undo redo | bold italic strikethrough | forecolor backcolor | formatpainter removeformat | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media table | fontselect fontsizeselect | blockquote hr casechange checklist charmap | print fullscreen`,
media_alt_source: false,
media_poster: false,
setup: editor => {
editor.on("Change", updateLegend);
}
});
}
}
function updateLegend() {
const note = notes.find(note => note.id === notesSelect.value);
if (!note) return tip("Note element is not found", true, "error", 4000);
const isTinyEditorActive = window.tinymce?.activeEditor;
note.legend = isTinyEditorActive ? tinymce.activeEditor.getContent() : notesLegend.innerHTML;
updateNotesBox(note);
}
function updateNotesBox(note) {
document.getElementById("notesHeader").innerHTML = note.name; document.getElementById("notesHeader").innerHTML = note.name;
document.getElementById("notesBody").innerHTML = note.legend; document.getElementById("notesBody").innerHTML = note.legend;
} }
function changeObject() { function changeElement() {
const note = notes.find(note => note.id === this.value); const note = notes.find(note => note.id === this.value);
if (!note) return; if (!note) return tip("Note element is not found", true, "error", 4000);
notesName.value = note.name; notesName.value = note.name;
editor.content.innerHTML = note.legend; notesLegend.innerHTML = note.legend;
updateNotesBox(note);
if (window.tinymce) tinymce.activeEditor.setContent(note.legend);
} }
function changeName() { function changeName() {
const id = document.getElementById("notesSelect").value; const note = notes.find(note => note.id === notesSelect.value);
const note = notes.find(note => note.id === id); if (!note) return tip("Note element is not found", true, "error", 4000);
if (!note) return;
note.name = this.value; note.name = this.value;
showNote(note);
} }
function validateHighlightElement() { function validateHighlightElement() {
const select = document.getElementById("notesSelect"); const element = document.getElementById(notesSelect.value);
const element = document.getElementById(select.value); if (element) return highlightElement(element, 3);
// if element is not found confirmationDialog({
if (element === null) { title: "Element not found",
alertMessage.innerHTML = "Related element is not found. Would you like to remove the note?"; message: "Note element is not found. Would you like to remove the note?",
$("#alert").dialog({ confirm: "Remove",
resizable: false, cancel: "Keep",
title: "Element not found", onConfirm: removeLegend
buttons: { });
Remove: function () {
$(this).dialog("close");
removeLegend();
},
Keep: function () {
$(this).dialog("close");
}
}
});
return;
}
highlightElement(element, 3); // if element is found
} }
function downloadLegends() { function downloadLegends() {
const data = JSON.stringify(notes); const notesData = JSON.stringify(notes);
const name = getFileName("Notes") + ".txt"; const name = getFileName("Notes") + ".txt";
downloadFile(data, name); downloadFile(notesData, name);
} }
function uploadLegends(dataLoaded) { function uploadLegends(dataLoaded) {
if (!dataLoaded) { if (!dataLoaded) return tip("Cannot load the file. Please check the data format", false, "error");
tip("Cannot load the file. Please check the data format", false, "error");
return;
}
notes = JSON.parse(dataLoaded); notes = JSON.parse(dataLoaded);
document.getElementById("notesSelect").options.length = 0; notesSelect.options.length = 0;
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
function clearStyle() {
editor.content.innerHTML = editor.content.textContent;
}
function triggerNotesRemove() { function triggerNotesRemove() {
alertMessage.innerHTML = "Are you sure you want to remove the selected note?"; confirmationDialog({
$("#alert").dialog({
resizable: false,
title: "Remove note", title: "Remove note",
buttons: { message: "Are you sure you want to remove the selected note? There is no way to undo this action",
Remove: function () { confirm: "Remove",
$(this).dialog("close"); onConfirm: removeLegend
removeLegend();
},
Keep: function () {
$(this).dialog("close");
}
}
}); });
} }
function removeLegend() { function removeLegend() {
const select = document.getElementById("notesSelect"); const index = notes.findIndex(n => n.id === notesSelect.value);
const index = notes.findIndex(n => n.id === select.value);
notes.splice(index, 1); notes.splice(index, 1);
select.options.length = 0; notesSelect.options.length = 0;
if (!notes.length) { if (!notes.length) {
$("#notesEditor").dialog("close"); $("#notesEditor").dialog("close");
return; return;
} }
notesText.innerHTML = "";
editNotes(notes[0].id, notes[0].name); editNotes(notes[0].id, notes[0].name);
} }
function toggleNotesPin() {
options.pinNotes = !options.pinNotes;
this.classList.toggle("pressed");
}
function removeEditor() {
if (window.tinymce) tinymce.remove();
}
} }

View file

@ -103,7 +103,9 @@ function showSupporters() {
Mike Conley,Xavier privé,Hope You're Well,Mark Sprietsma,Robert Landry,Nick Mowry,steve hall,Markell,Josh Wren,Neutrix,BLRageQuit,Rocky, 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, 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,Joshua Xiong,Moist mongol, 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`; Frank Fewkes,jason baldrick,Game Master Pro,Andrew Kircher,Preston Mitchell,Chris Kohut,Emarandzeb,Trentin Bergeron,Damon Gallaty,Pleaseworkforonce,
Jordan,William Markus,Sidr Dim,Alexander Whittaker,The Next Level,Patrick Valverde,Markus Peham,Daniel Cooper,the Beagles of Neorbus,Marley Moule,
Maximilian Schielke,Johnathan Xavier Hutchinson,Ele,Rita`;
const array = supporters const array = supporters
.replace(/(?:\r\n|\r|\n)/g, "") .replace(/(?:\r\n|\r|\n)/g, "")
@ -304,10 +306,8 @@ function showSeedHistoryDialog() {
// generate map with historical seed // generate map with historical seed
function restoreSeed(id) { function restoreSeed(id) {
if (mapHistory[id].seed == seed) { if (mapHistory[id].seed == seed) return tip("The current map is already generated with this seed", null, "error");
tip("The current map is already generated with this seed", null, "error");
return;
}
optionsSeed.value = mapHistory[id].seed; optionsSeed.value = mapHistory[id].seed;
mapWidthInput.value = mapHistory[id].width; mapWidthInput.value = mapHistory[id].width;
mapHeightInput.value = mapHistory[id].height; mapHeightInput.value = mapHistory[id].height;
@ -545,7 +545,7 @@ function randomizeOptions() {
// 'Options' settings // 'Options' settings
if (randomize || !locked("template")) randomizeHeightmapTemplate(); if (randomize || !locked("template")) randomizeHeightmapTemplate();
if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(15, 3, 2, 30); if (randomize || !locked("regions")) regionsInput.value = regionsOutput.value = gauss(18, 5, 2, 30);
if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100); if (randomize || !locked("provinces")) provincesInput.value = provincesOutput.value = gauss(20, 10, 20, 100);
if (randomize || !locked("manors")) { if (randomize || !locked("manors")) {
manorsInput.value = 1000; manorsInput.value = 1000;
@ -736,24 +736,43 @@ async function showLoadPane() {
} }
}); });
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons"); // already connected to Dropbox: list saved maps
const fileSelect = document.getElementById("loadFromDropboxSelect"); if (Cloud.providers.dropbox.api) {
const files = await Cloud.providers.dropbox.list(); document.getElementById("dropboxConnectButton").style.display = "none";
document.getElementById("loadFromDropboxSelect").style.display = "block";
const loadFromDropboxButtons = document.getElementById("loadFromDropboxButtons");
const fileSelect = document.getElementById("loadFromDropboxSelect");
fileSelect.innerHTML = `<option value="" disabled selected>Loading...</option>`;
const files = await Cloud.providers.dropbox.list();
if (!files) {
loadFromDropboxButtons.style.display = "none";
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
return;
}
loadFromDropboxButtons.style.display = "block";
fileSelect.innerHTML = "";
files.forEach(file => {
const opt = document.createElement("option");
opt.innerText = file.name;
opt.value = file.path;
fileSelect.appendChild(opt);
});
if (!files) {
loadFromDropboxButtons.style.display = "none";
fileSelect.innerHTML = `<option value="" disabled selected>Save files to Dropbox first</option>`;
return; return;
} }
loadFromDropboxButtons.style.display = "block"; // not connected to Dropbox: show connect button
fileSelect.innerHTML = ""; document.getElementById("dropboxConnectButton").style.display = "inline-block";
files.forEach(file => { document.getElementById("loadFromDropboxButtons").style.display = "none";
const opt = document.createElement("option"); document.getElementById("loadFromDropboxSelect").style.display = "none";
opt.innerText = file.name; }
opt.value = file.path;
fileSelect.appendChild(opt); async function connectToDropbox() {
}); await Cloud.providers.dropbox.initialize();
if (Cloud.providers.dropbox.api) showLoadPane();
} }
function loadURL() { function loadURL() {

View file

@ -44,7 +44,8 @@ function editProvinces() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
p = +line.dataset.id; 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("name")) editProvinceName(p);
else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]); else if (cl.contains("coaIcon")) editEmblem("province", "provinceCOA" + p, pack.provinces[p]);
else if (cl.contains("icon-star-empty")) capitalZoomIn(p); else if (cl.contains("icon-star-empty")) capitalZoomIn(p);
@ -133,9 +134,7 @@ function editProvinces() {
lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${ lines += `<div class="states" data-id=${p.i} data-name="${p.name}" data-form="${p.formName}" data-color="${
p.color p.color
}" data-capital="${capital}" data-state="${stateName}" data-area=${area} data-population=${population}> }" 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="${ <fill-box fill="${p.color}"></fill-box>
p.color
}" class="fillRect pointer"></svg>
<input data-tip="Province name. Click to change" class="name pointer" value="${p.name}" readonly> <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 pointer 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> <input data-tip="Province form name. Click to change" class="name pointer hide" value="${p.formName}" readonly>
@ -215,14 +214,14 @@ function editProvinces() {
function changeFill(el) { function changeFill(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const p = +el.parentNode.parentNode.dataset.id; const p = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
pack.provinces[p].color = fill; pack.provinces[p].color = newFill;
const g = provs.select("#provincesBody"); const g = provs.select("#provincesBody");
g.select("#province" + p).attr("fill", fill); g.select("#province" + p).attr("fill", newFill);
g.select("#province-gap" + p).attr("stroke", fill); g.select("#province-gap" + p).attr("stroke", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);

View file

@ -14,7 +14,9 @@ function overviewRegiments(state) {
updateHeaders(); updateHeaders();
$("#regimentsOverview").dialog({ $("#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"} 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()); header.querySelectorAll(".removable").forEach(el => el.remove());
const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html); const insert = html => document.getElementById("regimentsTotal").insertAdjacentHTML("beforebegin", html);
for (const u of options.military) { 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>`); 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) { header.querySelectorAll(".removable").forEach(function (e) {
e.addEventListener("click", function() {sortLines(this);}); e.addEventListener("click", function () {
sortLines(this);
});
}); });
} }
@ -51,11 +55,13 @@ function overviewRegiments(state) {
if (state !== -1 && s.i !== state) continue; // specific state is selected if (state !== -1 && s.i !== state) continue; // specific state is selected
for (const r of s.military) { for (const r of s.military) {
const sortData = options.military.map(u => `data-${u.name}=${r.u[u.name]||0}`).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(" "); 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}"> 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> <input data-tip="${s.fullName}" style="width:6em" value="${s.name}" readonly>
<span data-tip="Regiment's emblem" style="width:1em">${r.icon}</span> <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> <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"> lines += `<div id="regimentsTotalLine" class="totalLine" data-tip="Total of all displayed regiments">
<div style="width: 21em; margin-left: 1em">Regiments: ${regiments.length}</div> <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 style="width:5em">${si(d3.sum(regiments.map(r => r.a)))}</div>
</div>`; </div>`;
body.insertAdjacentHTML("beforeend", lines); 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); applySorting(regimentsHeader);
// add listeners // add listeners
@ -87,7 +96,7 @@ function overviewRegiments(state) {
const filter = document.getElementById("regimentsFilter"); const filter = document.getElementById("regimentsFilter");
filter.options.length = 0; // remove all options filter.options.length = 0; // remove all options
filter.options.add(new Option(`all`, -1, false, state === -1)); 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))); 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") { if (body.dataset.type === "absolute") {
body.dataset.type = "percentage"; body.dataset.type = "percentage";
const lines = body.querySelectorAll(":scope > div:not(.totalLine)"); 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]; if (cache[type]) cache[type];
cache[type] = d3.sum(array.map(el => +el.dataset[type])); cache[type] = d3.sum(array.map(el => +el.dataset[type]));
return cache[type]; return cache[type];
} };
lines.forEach(function(el) { lines.forEach(function (el) {
el.querySelectorAll("div").forEach(function(div) { el.querySelectorAll("div").forEach(function (div) {
const type = div.dataset.type; const type = div.dataset.type;
if (type === "rate") return; 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 { } else {
@ -145,15 +155,19 @@ function overviewRegiments(state) {
function addRegimentOnClick() { function addRegimentOnClick() {
const state = +regimentsFilter.value; 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 point = d3.mouse(this);
const cell = findCell(point[0], point[1]); 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 military = pack.states[state].military;
const i = military.length ? last(military).i + 1 : 0; const i = military.length ? last(military).i + 1 : 0;
const n = +(pack.cells.h[cell] < 20); // naval or land 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); reg.name = Military.getName(reg, military);
military.push(reg); military.push(reg);
Military.generateNote(reg, pack.states[state]); // add legend Military.generateNote(reg, pack.states[state]); // add legend
@ -163,9 +177,9 @@ function overviewRegiments(state) {
function downloadRegimentsData() { function downloadRegimentsData() {
const units = options.military.map(u => u.name); 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.state + ",";
data += el.dataset.id + ","; data += el.dataset.id + ",";
data += el.dataset.name + ","; data += el.dataset.name + ",";
@ -176,5 +190,4 @@ function overviewRegiments(state) {
const name = getFileName("Regiments") + ".csv"; const name = getFileName("Regiments") + ".csv";
downloadFile(data, name); downloadFile(data, name);
} }
} }

View file

@ -79,7 +79,7 @@ function editReligions() {
if (r.i) { if (r.i) {
lines += `<div class="states religions" data-id=${r.i} data-name="${r.name}" data-color="${r.color}" data-area=${area} 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}> 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"> <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> <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"> <input data-tip="Religion form" class="religionForm hide" value="${r.form}" autocorrect="off" spellcheck="false">
@ -93,7 +93,9 @@ function editReligions() {
</div>`; </div>`;
} else { } else {
// No religion (neutral) line // 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> <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"> <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> <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("mouseenter", ev => religionHighlightOn(ev)));
body.querySelectorAll("div.religions").forEach(el => el.addEventListener("mouseleave", ev => religionHighlightOff(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("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 > input.religionName").forEach(el => el.addEventListener("input", religionChangeName));
body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType)); body.querySelectorAll("div > select.religionType").forEach(el => el.addEventListener("change", religionChangeType));
body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm)); body.querySelectorAll("div > input.religionForm").forEach(el => el.addEventListener("input", religionChangeForm));
@ -215,13 +217,13 @@ function editReligions() {
function religionChangeColor() { function religionChangeColor() {
const el = this; const el = this;
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const religion = +el.parentNode.parentNode.dataset.id; const religion = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
pack.religions[religion].color = fill; pack.religions[religion].color = newFill;
relig.select("#religion" + religion).attr("fill", fill); relig.select("#religion" + religion).attr("fill", newFill);
debug.select("#religionsCenter" + religion).attr("fill", fill); debug.select("#religionsCenter" + religion).attr("fill", newFill);
}; };
openPicker(currentFill, callback); openPicker(currentFill, callback);
@ -459,7 +461,13 @@ function editReligions() {
// prepare svg // prepare svg
alertMessage.innerHTML = "<div id='religionInfo' class='chartInfo'>&#8205;</div>"; 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 graph = svg.append("g").attr("transform", `translate(10, -45)`);
const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa"); const links = graph.append("g").attr("fill", "none").attr("stroke", "#aaaaaa");
const nodes = graph.append("g"); const nodes = graph.append("g");
@ -473,7 +481,24 @@ function editReligions() {
.enter() .enter()
.append("path") .append("path")
.attr("d", d => { .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 const node = nodes
@ -578,7 +603,11 @@ function editReligions() {
$("#religionsEditor").dialog({position: {my: "right top", at: "right-10 top+10", of: "svg"}}); $("#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); 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"); body.querySelector("div").classList.add("selected");
} }

View file

@ -45,7 +45,7 @@ function editStates() {
cl = el.classList, cl = el.classList,
line = el.parentNode, line = el.parentNode,
state = +line.dataset.id; 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("name")) editStateName(state);
else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]); else if (cl.contains("coaIcon")) editEmblem("state", "stateCOA" + state, pack.states[state]);
else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state); else if (cl.contains("icon-star-empty")) stateCapitalZoomIn(state);
@ -102,7 +102,7 @@ function editStates() {
// Neutral line // Neutral line
lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-cells=${s.cells} data-area=${area} 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=""> 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> <input data-tip="Neutral lands name. Click to change" class="stateName name pointer italic" value="${s.name}" readonly>
<svg class="coaIcon placeholder hide"></svg> <svg class="coaIcon placeholder hide"></svg>
<input class="stateForm placeholder" value="none"> <input class="stateForm placeholder" value="none">
@ -126,15 +126,10 @@ function editStates() {
const capital = pack.burgs[s.capital].name; const capital = pack.burgs[s.capital].name;
COArenderer.trigger("stateCOA" + s.i, s.coa); 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="${ lines += `<div class="states" data-id=${s.i} data-name="${s.name}" data-form="${s.formName}" data-capital="${capital}"
s.color data-color="${s.color}" data-cells=${s.cells} data-area=${area} data-population=${population} data-burgs=${s.burgs}
}" data-cells=${s.cells} data-culture=${pack.cultures[s.culture].name} data-type=${s.type} data-expansionism=${s.expansionism}>
data-area=${area} data-population=${population} data-burgs=${s.burgs} data-culture=${pack.cultures[s.culture].name} data-type=${ <fill-box fill="${s.color}"></fill-box>
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>
<input data-tip="State name. Click to change" class="stateName name pointer" value="${s.name}" readonly> <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 pointer 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> <input data-tip="State form name. Click to change" class="stateForm name pointer" value="${s.formName}" readonly>
@ -149,9 +144,8 @@ function editStates() {
<div data-tip="${populationTip}" class="culturePopulation hide">${si(population)}</div> <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> <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> <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=${ <input data-tip="Expansionism (defines competitive size). Change to re-calculate states based on new value"
s.expansionism 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> <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> <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> <span data-tip="Toggle state focus" class="icon-pin ${focused ? "" : " inactive"} hide"></span>
@ -237,18 +231,18 @@ function editStates() {
function stateChangeFill(el) { function stateChangeFill(el) {
const currentFill = el.getAttribute("fill"); const currentFill = el.getAttribute("fill");
const state = +el.parentNode.parentNode.dataset.id; const state = +el.parentNode.dataset.id;
const callback = function (fill) { const callback = function (newFill) {
el.setAttribute("fill", fill); el.fill = newFill;
pack.states[state].color = fill; pack.states[state].color = newFill;
statesBody.select("#state" + state).attr("fill", fill); statesBody.select("#state" + state).attr("fill", newFill);
statesBody.select("#state-gap" + state).attr("stroke", fill); statesBody.select("#state-gap" + state).attr("stroke", newFill);
const halo = d3.color(fill) ? d3.color(fill).darker().hex() : "#666666"; const halo = d3.color(newFill) ? d3.color(newFill).darker().hex() : "#666666";
statesHalo.select("#state-border" + state).attr("stroke", halo); statesHalo.select("#state-border" + state).attr("stroke", halo);
// recolor regiments // recolor regiments
const solidColor = fill[0] === "#" ? fill : "#999"; const solidColor = newFill[0] === "#" ? newFill : "#999";
const darkerColor = d3.color(solidColor).darker().hex(); const darkerColor = d3.color(solidColor).darker().hex();
armies.select("#army" + state).attr("fill", solidColor); armies.select("#army" + state).attr("fill", solidColor);
armies 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

@ -11,6 +11,8 @@ function editUnits() {
position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"} position: {my: "right top", at: "right-10 top+10", of: "svg", collision: "fit"}
}); });
const drawBar = () => drawScaleBar(scale);
// add listeners // add listeners
document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit); document.getElementById("distanceUnitInput").addEventListener("change", changeDistanceUnit);
document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale); document.getElementById("distanceScaleOutput").addEventListener("input", changeDistanceScale);
@ -19,9 +21,9 @@ function editUnits() {
document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent); document.getElementById("heightExponentInput").addEventListener("input", changeHeightExponent);
document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent); document.getElementById("heightExponentOutput").addEventListener("input", changeHeightExponent);
document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale); document.getElementById("temperatureScale").addEventListener("change", changeTemperatureScale);
document.getElementById("barSizeOutput").addEventListener("input", drawScaleBar); document.getElementById("barSizeOutput").addEventListener("input", drawBar);
document.getElementById("barSizeInput").addEventListener("input", drawScaleBar); document.getElementById("barSizeInput").addEventListener("input", drawBar);
document.getElementById("barLabel").addEventListener("input", drawScaleBar); document.getElementById("barLabel").addEventListener("input", drawBar);
document.getElementById("barPosX").addEventListener("input", fitScaleBar); document.getElementById("barPosX").addEventListener("input", fitScaleBar);
document.getElementById("barPosY").addEventListener("input", fitScaleBar); document.getElementById("barPosY").addEventListener("input", fitScaleBar);
document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity); document.getElementById("barBackOpacity").addEventListener("input", changeScaleBarOpacity);
@ -46,19 +48,18 @@ function editUnits() {
prompt("Provide a custom name for a distance unit", {default: ""}, custom => { prompt("Provide a custom name for a distance unit", {default: ""}, custom => {
this.options.add(new Option(custom, custom, false, true)); this.options.add(new Option(custom, custom, false, true));
lock("distanceUnit"); lock("distanceUnit");
drawScaleBar(); drawScaleBar(scale);
calculateFriendlyGridSize(); calculateFriendlyGridSize();
}); });
return; return;
} }
drawScaleBar(); drawScaleBar(scale);
calculateFriendlyGridSize(); calculateFriendlyGridSize();
} }
function changeDistanceScale() { function changeDistanceScale() {
distanceScale = +document.getElementById("distanceScaleInput").value; drawScaleBar(scale);
drawScaleBar();
calculateFriendlyGridSize(); calculateFriendlyGridSize();
} }
@ -138,7 +139,7 @@ function editUnits() {
localStorage.removeItem("barBackColor"); localStorage.removeItem("barBackColor");
localStorage.removeItem("barPosX"); localStorage.removeItem("barPosX");
localStorage.removeItem("barPosY"); localStorage.removeItem("barPosY");
drawScaleBar(); drawScaleBar(scale);
// population // population
populationRate = populationRateOutput.value = populationRateInput.value = 1000; populationRate = populationRateOutput.value = populationRateInput.value = 1000;

View file

@ -4,6 +4,8 @@ function editZones() {
closeDialogs(); closeDialogs();
if (!layerIsOn("toggleZones")) toggleZones(); if (!layerIsOn("toggleZones")) toggleZones();
const body = document.getElementById("zonesBodySection"); const body = document.getElementById("zonesBodySection");
updateFilters();
zonesEditorAddLines(); zonesEditorAddLines();
if (modules.editZones) return; if (modules.editZones) return;
@ -18,6 +20,8 @@ function editZones() {
}); });
// add listeners // add listeners
document.getElementById("zonesFilterType").addEventListener("click", updateFilters);
document.getElementById("zonesFilterType").addEventListener("change", filterZonesByType);
document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines); document.getElementById("zonesEditorRefresh").addEventListener("click", zonesEditorAddLines);
document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones")); document.getElementById("zonesEditStyle").addEventListener("click", () => editStyle("zones"));
document.getElementById("zonesLegend").addEventListener("click", toggleLegend); document.getElementById("zonesLegend").addEventListener("click", toggleLegend);
@ -33,55 +37,60 @@ function editZones() {
const el = ev.target, const el = ev.target,
cl = el.classList, cl = el.classList,
zone = el.parentNode.dataset.id; zone = el.parentNode.dataset.id;
if (cl.contains("culturePopulation")) { if (el.tagName === "FILL-BOX") changeFill(el);
changePopulation(zone); else if (cl.contains("culturePopulation")) changePopulation(zone);
return; else if (cl.contains("icon-trash-empty")) zoneRemove(zone);
} else if (cl.contains("icon-eye")) toggleVisibility(el);
if (cl.contains("icon-trash-empty")) { else if (cl.contains("icon-pin")) toggleFog(zone, cl);
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 (customization) selectZone(el); if (customization) selectZone(el);
}); });
body.addEventListener("input", function (ev) { body.addEventListener("input", function (ev) {
const el = ev.target, const el = ev.target;
zone = el.parentNode.dataset.id; const zone = zones.select("#" + el.parentNode.dataset.id);
if (el.classList.contains("religionName")) zones.select("#" + zone).attr("data-description", el.value);
if (el.classList.contains("zoneName")) zone.attr("data-description", el.value);
else if (el.classList.contains("zoneType")) zone.attr("data-type", el.value);
}); });
// update type filter with a list of used types
function updateFilters() {
const zones = Array.from(document.querySelectorAll("#zones > g"));
const types = unique(zones.map(zone => zone.dataset.type));
const filterSelect = document.getElementById("zonesFilterType");
const typeToFilterBy = types.includes(zonesFilterType.value) ? zonesFilterType.value : "all";
filterSelect.innerHTML = "<option value='all'>all</option>" + types.map(type => `<option value="${type}">${type}</option>`).join("");
filterSelect.value = typeToFilterBy;
}
// add line for each zone // add line for each zone
function zonesEditorAddLines() { function zonesEditorAddLines() {
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value;
let lines = "";
zones.selectAll("g").each(function () { const typeToFilterBy = document.getElementById("zonesFilterType").value;
const c = this.dataset.cells ? this.dataset.cells.split(",").map(c => +c) : []; const zones = Array.from(document.querySelectorAll("#zones > g"));
const description = this.dataset.description; const filteredZones = typeToFilterBy === "all" ? zones : zones.filter(zone => zone.dataset.type === typeToFilterBy);
const fill = this.getAttribute("fill");
const lines = filteredZones.map(zoneEl => {
const c = zoneEl.dataset.cells ? zoneEl.dataset.cells.split(",").map(c => +c) : [];
const description = zoneEl.dataset.description;
const type = zoneEl.dataset.type;
const fill = zoneEl.getAttribute("fill");
const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2; const area = d3.sum(c.map(i => pack.cells.area[i])) * distanceScaleInput.value ** 2;
const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate; const rural = d3.sum(c.map(i => pack.cells.pop[i])) * populationRate;
const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization; const urban = d3.sum(c.map(i => pack.cells.burg[i]).map(b => pack.burgs[b].population)) * populationRate * urbanization;
const population = rural + urban; const population = rural + urban;
const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`; const populationTip = `Total population: ${si(population)}; Rural population: ${si(rural)}; Urban population: ${si(urban)}. Click to change`;
const inactive = this.style.display === "none"; const inactive = zoneEl.style.display === "none";
const focused = defs.select("#fog #focus" + this.id).size(); const focused = defs.select("#fog #focus" + zoneEl.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}> return `<div class="states" data-id="${zoneEl.id}" data-fill="${fill}" data-description="${description}"
<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> data-type="${type}" data-cells=${c.length} data-area=${area} data-population=${population}>
<input data-tip="Zone description. Click and type to change" class="religionName" value="${description}" autocorrect="off" spellcheck="false"> <fill-box fill="${fill}"></fill-box>
<input data-tip="Zone description. Click and type to change" style="width: 11em" class="zoneName" value="${description}" autocorrect="off" spellcheck="false">
<input data-tip="Zone type. Click and type to change" class="zoneType" value="${type}">
<span data-tip="Cells count" class="icon-check-empty hide"></span> <span data-tip="Cells count" class="icon-check-empty hide"></span>
<div data-tip="Cells count" class="stateCells hide">${c.length}</div> <div data-tip="Cells count" class="stateCells hide">${c.length}</div>
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span> <span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
@ -95,13 +104,13 @@ function editZones() {
</div>`; </div>`;
}); });
body.innerHTML = lines; body.innerHTML = lines.join("");
// update footer // update footer
const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2); const totalArea = (zonesFooterArea.dataset.area = graphWidth * graphHeight * distanceScaleInput.value ** 2);
const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate; const totalPop = (d3.sum(pack.cells.pop) + d3.sum(pack.burgs.filter(b => !b.removed).map(b => b.population)) * urbanization) * populationRate;
zonesFooterPopulation.dataset.population = totalPop; zonesFooterPopulation.dataset.population = totalPop;
zonesFooterNumber.innerHTML = zones.selectAll("g").size(); zonesFooterNumber.innerHTML = `${filteredZones.length} of ${zones.length}`;
zonesFooterCells.innerHTML = pack.cells.i.length; zonesFooterCells.innerHTML = pack.cells.i.length;
zonesFooterArea.innerHTML = si(totalArea) + unit; zonesFooterArea.innerHTML = si(totalArea) + unit;
zonesFooterPopulation.innerHTML = si(totalPop); zonesFooterPopulation.innerHTML = si(totalPop);
@ -127,6 +136,19 @@ function editZones() {
zones.select("#" + zone).style("outline", null); zones.select("#" + zone).style("outline", null);
} }
function filterZonesByType() {
const typeToFilterBy = this.value;
const zones = Array.from(document.querySelectorAll("#zones > g"));
for (const zone of zones) {
const type = zone.dataset.type;
const visible = typeToFilterBy === "all" || type === typeToFilterBy;
zone.style.display = visible ? "block" : "none";
}
zonesEditorAddLines();
}
$(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone}); $(body).sortable({items: "div.states", handle: ".icon-resize-vertical", containment: "parent", axis: "y", update: movezone});
function movezone(ev, ui) { function movezone(ev, ui) {
const zone = $("#" + ui.item.attr("data-id")); const zone = $("#" + ui.item.attr("data-id"));
@ -142,7 +164,7 @@ function editZones() {
function enterZonesManualAssignent() { function enterZonesManualAssignent() {
if (!layerIsOn("toggleZones")) toggleZones(); if (!layerIsOn("toggleZones")) toggleZones();
customization = 10; customization = 10;
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "none")); document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "none"));
document.getElementById("zonesManuallyButtons").style.display = "inline-block"; document.getElementById("zonesManuallyButtons").style.display = "inline-block";
zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden")); zonesEditor.querySelectorAll(".hide").forEach(el => el.classList.add("hidden"));
@ -256,7 +278,7 @@ function editZones() {
function exitZonesManualAssignment(close) { function exitZonesManualAssignment(close) {
customization = 0; customization = 0;
removeCircle(); removeCircle();
document.querySelectorAll("#zonesBottom > button").forEach(el => (el.style.display = "inline-block")); document.querySelectorAll("#zonesBottom > *").forEach(el => (el.style.display = "inline-block"));
document.getElementById("zonesManuallyButtons").style.display = "none"; document.getElementById("zonesManuallyButtons").style.display = "none";
zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden")); zonesEditor.querySelectorAll(".hide:not(.show)").forEach(el => el.classList.remove("hidden"));
@ -275,9 +297,9 @@ function editZones() {
function changeFill(el) { function changeFill(el) {
const fill = el.getAttribute("fill"); const fill = el.getAttribute("fill");
const callback = function (fill) { const callback = newFill => {
el.setAttribute("fill", fill); el.fill = newFill;
document.getElementById(el.parentNode.parentNode.dataset.id).setAttribute("fill", fill); document.getElementById(el.parentNode.dataset.id).setAttribute("fill", newFill);
}; };
openPicker(fill, callback); openPicker(fill, callback);
@ -344,37 +366,22 @@ function editZones() {
function addZonesLayer() { function addZonesLayer() {
const id = getNextId("zone"); const id = getNextId("zone");
const description = "Unknown zone"; const description = "Unknown zone";
const fill = "url(#hatch" + (id.slice(4) % 14) + ")"; const type = "Unknown";
zones.append("g").attr("id", id).attr("data-description", description).attr("data-cells", "").attr("fill", fill); const fill = "url(#hatch" + (id.slice(4) % 42) + ")";
const unit = areaUnit.value === "square" ? " " + distanceUnitInput.value + "²" : " " + areaUnit.value; zones.append("g").attr("id", id).attr("data-description", description).attr("data-type", type).attr("data-cells", "").attr("fill", fill);
const line = `<div class="states" data-id="${id}" data-fill="${fill}" data-description="${description}" data-cells=0 data-area=0 data-population=0> zonesEditorAddLines();
<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>
<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>
<span data-tip="Zone area" style="padding-right:4px" class="icon-map-o hide"></span>
<div data-tip="Zone area" class="biomeArea hide">0 ${unit}</div>
<span class="icon-male hide"></span>
<div class="culturePopulation hide">0</div>
<span data-tip="Drag to raise or lower the zone" class="icon-resize-vertical hide"></span>
<span data-tip="Toggle zone focus" class="icon-pin inactive hide placeholder"></span>
<span data-tip="Toggle zone visibility" class="icon-eye hide placeholder"></span>
<span data-tip="Remove zone" class="icon-trash-empty hide"></span>
</div>`;
body.insertAdjacentHTML("beforeend", line);
zonesFooterNumber.innerHTML = zones.selectAll("g").size();
} }
function downloadZonesData() { function downloadZonesData() {
const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value; const unit = areaUnit.value === "square" ? distanceUnitInput.value + "2" : areaUnit.value;
let data = "Id,Fill,Description,Cells,Area " + unit + ",Population\n"; // headers let data = "Id,Fill,Description,Type,Cells,Area " + unit + ",Population\n"; // headers
body.querySelectorAll(":scope > div").forEach(function (el) { body.querySelectorAll(":scope > div").forEach(function (el) {
data += el.dataset.id + ","; data += el.dataset.id + ",";
data += el.dataset.fill + ","; data += el.dataset.fill + ",";
data += el.dataset.description + ","; data += el.dataset.description + ",";
data += el.dataset.type + ",";
data += el.dataset.cells + ","; data += el.dataset.cells + ",";
data += el.dataset.area + ","; data += el.dataset.area + ",";
data += el.dataset.population + "\n"; data += el.dataset.population + "\n";

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": "url(#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": "url(#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": "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": "#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": "url(#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": "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": "",
"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": "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": "#000000",
"text-shadow": "white 0px 0px 4px",
"data-size": 5,
"font-size": 5,
"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": 4,
"font-size": 4,
"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,
"fill": "#000000",
"stroke": "#000000",
"stroke-width": 0,
"text-shadow": "white 0px 0px 4px",
"data-size": 21,
"font-size": 21,
"font-family": "Questrial",
"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

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