@fboes/aerofly-data 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aerofly FS Airport Code Validator</title>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ margin: 20px;
11
+ text-align: center;
12
+ }
13
+ #status {
14
+ font-size: 1.5em;
15
+ margin-left: 10px;
16
+ }
17
+ </style>
18
+ </head>
19
+ <body>
20
+ <h1>Aerofly FS Airport Code Validator</h1>
21
+ <p>Enter an ICAO airport code:</p>
22
+ <input type="text" id="icaoInput" placeholder="Enter ICAO code" pattern="A-Za-z0-9" autocapitalize="on" />
23
+ <span id="status">❓</span>
24
+
25
+ <script>
26
+ // Load the airport list from the JSON file
27
+ let airportList = [];
28
+ fetch('./data/airport-list.json')
29
+ .then(response => response.json())
30
+ .then(data => {
31
+ airportList = data;
32
+ })
33
+ .catch(error => {
34
+ console.error('Error loading airport list:', error);
35
+ });
36
+
37
+ // Validate the input
38
+ const inputField = document.getElementById('icaoInput');
39
+ const status = document.getElementById('status');
40
+
41
+ inputField.addEventListener('input', () => {
42
+ const inputValue = inputField.value.toUpperCase();
43
+ if (airportList.includes(inputValue)) {
44
+ status.textContent = '✅'; // Green checkmark
45
+ } else if (inputValue.trim() === '') {
46
+ status.textContent = '❓'; // Question mark for empty input
47
+ } else {
48
+ status.textContent = '🚫'; // Red forbidden sign
49
+ }
50
+ });
51
+ </script>
52
+ </body>
53
+ </html>
@@ -0,0 +1,266 @@
1
+ #!/usr/bin/env node
2
+
3
+ //@ts-check
4
+
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+
8
+ /**
9
+ * @typedef AeroflyAircraftParsed
10
+ * @type {{
11
+ * name: string,
12
+ * nameFull: string,
13
+ * icaoCode: string,
14
+ * tags: string[],
15
+ * approachAirspeedKts: number,
16
+ * cruiseAltitudeFt: number,
17
+ * cruiseSpeedKts: number,
18
+ * maximumRangeNm: number,
19
+ * }}
20
+ */
21
+
22
+ /**
23
+ * @typedef AeroflyAircraft
24
+ * @type {AeroflyAircraftParsed & {
25
+ * aeroflyCode: string,
26
+ * liveries: {
27
+ * aeroflyCode: string,
28
+ * name: string,
29
+ * }[],
30
+ * }}
31
+ */
32
+
33
+ /**
34
+ *
35
+ * @param {string} directory
36
+ * @returns {AeroflyAircraft[]}
37
+ */
38
+ const getAeroflyAircraft = (directory) => {
39
+ return fs
40
+ .readdirSync(directory, { withFileTypes: true })
41
+ .filter((dirent) => dirent.isDirectory())
42
+ .sort()
43
+ .map((dirent) => {
44
+ const tmdFileContent = fs.readFileSync(
45
+ path.join(dirent.parentPath, dirent.name, dirent.name + ".tmc"),
46
+ "utf8"
47
+ );
48
+
49
+ const tmdOptionFileContent = fs.readFileSync(
50
+ path.join(dirent.parentPath, dirent.name, "option.tmc"),
51
+ "utf8"
52
+ );
53
+
54
+ const liveries = [
55
+ {
56
+ aeroflyCode: "default",
57
+ name: parseTmdLine(tmdOptionFileContent, "Description"),
58
+ },
59
+ ...fs
60
+ .readdirSync(path.join(dirent.parentPath, dirent.name), {
61
+ withFileTypes: true,
62
+ })
63
+ .filter((dirent) => dirent.isDirectory())
64
+ .filter((dirent) =>
65
+ fs.existsSync(
66
+ path.join(dirent.parentPath, dirent.name, "preview.ttx")
67
+ )
68
+ )
69
+ .sort()
70
+ .map((dirent) => {
71
+ const tmdFileContent = fs.readFileSync(
72
+ path.join(dirent.parentPath, dirent.name, "option.tmc"),
73
+ "utf8"
74
+ );
75
+
76
+ return {
77
+ aeroflyCode: dirent.name,
78
+ name: parseTmdLine(tmdFileContent, "Description"),
79
+ };
80
+ }),
81
+ ];
82
+
83
+ return {
84
+ ...parseAircraft(tmdFileContent),
85
+ aeroflyCode: dirent.name,
86
+ liveries,
87
+ };
88
+ });
89
+ };
90
+
91
+ /**
92
+ * @param {string} tmdFileContent
93
+ * @returns {AeroflyAircraftParsed}
94
+ */
95
+ const parseAircraft = (tmdFileContent) => {
96
+ const tags = parseTmdLine(tmdFileContent, "Tags").trim().split(" ");
97
+
98
+ // type: ;
99
+ return {
100
+ name: parseTmdLine(tmdFileContent, "DisplayName"),
101
+ nameFull: parseTmdLine(tmdFileContent, "DisplayNameFull"),
102
+ icaoCode: parseTmdLine(tmdFileContent, "ICAO"),
103
+ tags,
104
+ /*MinimumAirspeed: convertSpeed(
105
+ parseTmdLine(tmdFileContent, "MinimumAirspeed")
106
+ ),*/
107
+ approachAirspeedKts: convertSpeed(
108
+ parseTmdLine(tmdFileContent, "ApproachAirspeed")
109
+ ),
110
+ /*CruiseAirspeed: convertSpeed(
111
+ parseTmdLine(tmdFileContent, "CruiseAirspeed")
112
+ ),*/
113
+ cruiseAltitudeFt: convertAltitude(
114
+ parseTmdLine(tmdFileContent, "CruiseAltitude")
115
+ ),
116
+ cruiseSpeedKts: convertSpeed(parseTmdLine(tmdFileContent, "CruiseSpeed")),
117
+ /*MaximumAirspeed: convertSpeed(
118
+ parseTmdLine(tmdFileContent, "MaximumAirspeed")
119
+ ),
120
+ MaximumAltitude: convertAltitude(
121
+ parseTmdLine(tmdFileContent, "MaximumAltitude")
122
+ ),
123
+ MaximumSpeed: convertSpeed(parseTmdLine(tmdFileContent, "MaximumSpeed")),*/
124
+ maximumRangeNm: convertDistance(
125
+ parseTmdLine(tmdFileContent, "MaximumRange")
126
+ ),
127
+ /*FlapAirspeedRange: parseTmdLine(tmdFileContent, "FlapAirspeedRange")
128
+ .trim()
129
+ .split(" ")
130
+ .map((v) => convertSpeed(v)),
131
+ NormalAirspeedRange: parseTmdLine(tmdFileContent, "NormalAirspeedRange")
132
+ .trim()
133
+ .split(" ")
134
+ .map((v) => convertSpeed(v)),
135
+ CautionAirspeedRange: parseTmdLine(tmdFileContent, "CautionAirspeedRange")
136
+ .trim()
137
+ .split(" ")
138
+ .map((v) => convertSpeed(v)),*/
139
+ };
140
+ };
141
+
142
+ /**
143
+ *
144
+ * @param {string} tmdFileContent
145
+ * @param {string} key
146
+ * @returns {string}
147
+ */
148
+ const parseTmdLine = (tmdFileContent, key) => {
149
+ const r = new RegExp("\\[" + key + "\\]\\s*\\[(.+?)\\]");
150
+ const match = tmdFileContent.match(r);
151
+ return match && match[1] ? match[1] : "";
152
+ };
153
+
154
+ /**
155
+ *
156
+ * @param {string} speed in m/s
157
+ * @returns {number} in kts
158
+ */
159
+ const convertSpeed = (speed) => {
160
+ return Math.round(Number(speed) * 1.94384449);
161
+ };
162
+
163
+ /**
164
+ *
165
+ * @param {string} altitude in m
166
+ * @returns {number} in ft
167
+ */
168
+ const convertAltitude = (altitude) => {
169
+ return Math.round(Number(altitude) * 3.28084);
170
+ };
171
+
172
+ /**
173
+ *
174
+ * @param {string} range in m
175
+ * @returns {number} in kts
176
+ */
177
+ const convertDistance = (range) => {
178
+ return Math.round(Number(range) / 1852);
179
+ };
180
+
181
+ // -----------------------------------------------------------------------------
182
+
183
+ const inputDirectory = process.argv[2] ?? ".";
184
+ const aeroflyAircraft = getAeroflyAircraft(inputDirectory);
185
+
186
+ process.stderr
187
+ .write(`Found \x1b[92m${aeroflyAircraft.length}\x1b[0m Aerofly FS Aircraft
188
+ `);
189
+
190
+ // Ensure the output directory exists
191
+ const outputDirectory = path.join("data");
192
+ await fs.promises.mkdir(outputDirectory, { recursive: true });
193
+
194
+ // Write the full output (with liveries) to aircraft-liveries.json
195
+ const outputFilePathWithLiveries = path.join(
196
+ outputDirectory,
197
+ "aircraft-liveries.json"
198
+ );
199
+ await fs.promises.writeFile(
200
+ outputFilePathWithLiveries,
201
+ JSON.stringify(aeroflyAircraft, null, 2),
202
+ "utf-8"
203
+ );
204
+ process.stderr.write(
205
+ `Full aircraft data (with liveries) written to \x1b[92m${outputFilePathWithLiveries}\x1b[0m\n`
206
+ );
207
+
208
+ // Write the abbreviated output (without liveries) to aircraft.json
209
+ const outputFilePathWithoutLiveries = path.join(
210
+ outputDirectory,
211
+ "aircraft.json"
212
+ );
213
+ await fs.promises.writeFile(
214
+ outputFilePathWithoutLiveries,
215
+ JSON.stringify(
216
+ aeroflyAircraft,
217
+ (key, value) => (key === "liveries" ? undefined : value),
218
+ 2
219
+ ),
220
+ "utf-8"
221
+ );
222
+ process.stderr.write(
223
+ `Abbreviated aircraft data (without liveries) written to \x1b[92m${outputFilePathWithoutLiveries}\x1b[0m\n`
224
+ );
225
+
226
+ // Write summary to aircraft.md
227
+ const summaryFilePath = path.join(outputDirectory, "aircraft.md");
228
+ let summaryContent = `\
229
+ # Aerofly FS Aircraft Summary
230
+
231
+ | Aircraft Name | ICAO Code | Aerofly FS Code | Approach Speed (kts) | Cruise Altitude (ft) | Cruise Speed (kts) | Maximum Range (nm) |
232
+ | ------------------------------------ | --------- | --------------- | -------------------: | -------------------: | -----------------: | -----------------: |
233
+ `;
234
+ summaryContent += aeroflyAircraft
235
+ .map((aircraft) => {
236
+ return `| ${aircraft.nameFull} | \`${aircraft.icaoCode}\` | \`${aircraft.aeroflyCode}\` | ${aircraft.approachAirspeedKts} | ${aircraft.cruiseAltitudeFt} | ${aircraft.cruiseSpeedKts} | ${aircraft.maximumRangeNm} |`;
237
+ })
238
+ .join("\n");
239
+
240
+ await fs.promises.writeFile(summaryFilePath, summaryContent, "utf-8");
241
+ process.stderr.write(`Summary written to \x1b[92m${summaryFilePath}\x1b[0m\n`);
242
+
243
+ // Write HTML <select> options to aircraft-select.html
244
+ const selectFilePath = path.join(outputDirectory, "aircraft-select.html");
245
+ let selectContent = `\
246
+ <select id="aircraft-select">
247
+ `;
248
+
249
+ // Sort aeroflyAircraft by nameFull
250
+ const sortedAircraft = aeroflyAircraft.sort((a, b) =>
251
+ a.nameFull.localeCompare(b.nameFull)
252
+ );
253
+
254
+ selectContent += sortedAircraft
255
+ .map((aircraft) => {
256
+ return ` <option value="${aircraft.aeroflyCode}">${aircraft.nameFull}</option>`;
257
+ })
258
+ .join("\n");
259
+ selectContent += `
260
+ </select>
261
+ `;
262
+
263
+ await fs.promises.writeFile(selectFilePath, selectContent, "utf-8");
264
+ process.stderr.write(
265
+ `HTML <select> options written to \x1b[92m${selectFilePath}\x1b[0m\n`
266
+ );
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env node
2
+
3
+ //@ts-check
4
+
5
+ import GeoJSON from "@fboes/geojson";
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { parse } from "csv-parse/sync";
9
+
10
+ /**
11
+ *
12
+ * @param {string} type
13
+ * @param {boolean} isMilitary
14
+ * @param {number|undefined} lenght in Bytes
15
+ * @returns {string}
16
+ */
17
+ const geoJsonType = (type, isMilitary, lenght) => {
18
+ if (type === "heliport") {
19
+ return type;
20
+ }
21
+
22
+ if (lenght !== undefined) {
23
+ let size = "closed";
24
+ if (lenght > 1200) {
25
+ size = "large";
26
+ } else if (lenght > 1050) {
27
+ size = "medium";
28
+ } else if (lenght > 920) {
29
+ size = "small";
30
+ }
31
+
32
+ if (size === "closed") {
33
+ return size;
34
+ }
35
+
36
+ return size + "_" + (isMilitary ? "airbase" : "airport");
37
+ }
38
+
39
+ return type;
40
+ };
41
+
42
+ /**
43
+ *
44
+ * @param {string} directory
45
+ * @param {RegExp?} icaoFilter
46
+ * @returns {Map<string,number>}
47
+ */
48
+ const getAeroflyAirports = (directory, icaoFilter) => {
49
+ const aeroflyAirports = new Map();
50
+ let maxLength = 0;
51
+ let minLength = 10_000;
52
+
53
+ const files = fs
54
+ .readdirSync(directory)
55
+ .filter((fn) => fn.endsWith(".wad"))
56
+ .sort();
57
+
58
+ for (const file of files) {
59
+ const icaoCode = file.replace(/\.wad$/, "").toUpperCase();
60
+ if (!icaoFilter || icaoCode.match(icaoFilter)) {
61
+ const stats = fs.statSync(path.join(directory, file));
62
+ maxLength = Math.max(maxLength, stats.size);
63
+ minLength = Math.min(minLength, stats.size);
64
+ aeroflyAirports.set(icaoCode, stats.size);
65
+ }
66
+ }
67
+
68
+ return aeroflyAirports;
69
+ };
70
+
71
+ // -----------------------------------------------------------------------------
72
+
73
+ const inputDirectory = process.argv[2] ?? ".";
74
+ const icaoFilterArg = process.argv[3]?.replace(/[^A-Z]/, "").toUpperCase();
75
+ const icaoFilter = icaoFilterArg
76
+ ? new RegExp("^[" + icaoFilterArg + "]")
77
+ : null;
78
+
79
+ const aeroflyGeoJson = new GeoJSON.FeatureCollection();
80
+ const aeroflyAirports = getAeroflyAirports(inputDirectory, icaoFilter);
81
+ const aeroflyAirportsLength = aeroflyAirports.size;
82
+ process.stderr
83
+ .write(`Found \x1b[92m${aeroflyAirports.size}\x1b[0m Aerofly FS Airports
84
+ `);
85
+
86
+ const airportsSource = fs.readFileSync(`tmp/airports.csv`);
87
+ /** @type {string[][]} with a single CSV line from airports.csv */
88
+ const airportsRecords = parse(airportsSource, { bom: true });
89
+
90
+ let airportsRecordsProcessed = 0;
91
+
92
+ // Collect all ICAO codes
93
+ const icaoCodes = [];
94
+
95
+ for (const airportsRecord of airportsRecords) {
96
+ // 'id', 'ident',
97
+ // 'type', 'name',
98
+ // 'latitude_deg', 'longitude_deg',
99
+ // 'elevation_ft', 'continent',
100
+
101
+ // 3685,"KMIA","large_airport","Miami International Airport",25.79319953918457,-80.29060363769531,8,"NA","US","US-FL","Miami","yes","KMIA","MIA","MIA","http://www.miami-airport.com/","https://en.wikipedia.org/wiki/
102
+
103
+ const icaoCode = airportsRecord[1];
104
+ const icaoCodeAlternate = airportsRecord[12];
105
+
106
+ // EL = Europe
107
+ // K = US
108
+ if (
109
+ icaoFilter &&
110
+ !icaoCode.match(icaoFilter) &&
111
+ !icaoCodeAlternate.match(icaoFilter)
112
+ ) {
113
+ continue;
114
+ }
115
+
116
+ airportsRecordsProcessed++;
117
+
118
+ const length =
119
+ aeroflyAirports.get(icaoCode) ?? aeroflyAirports.get(icaoCodeAlternate);
120
+
121
+ if (length !== undefined) {
122
+ // Add the ICAO code to the list
123
+ icaoCodes.push(icaoCode);
124
+
125
+ // Remove airport from list of Aerofly FS4 Airports
126
+ aeroflyAirports.delete(icaoCode) ||
127
+ aeroflyAirports.delete(icaoCodeAlternate);
128
+
129
+ const isMilitary =
130
+ airportsRecord[3].match(
131
+ /\b(base|rnas|raf|naval|air\s?force|coast\s?guard|army|afs|mod)\b/i
132
+ ) !== null;
133
+ let type = airportsRecord[2];
134
+ if (isMilitary) {
135
+ type = type.replace(/port/, "base");
136
+ }
137
+
138
+ const feature = new GeoJSON.Feature(
139
+ new GeoJSON.Point(
140
+ Number(airportsRecord[5]),
141
+ Number(airportsRecord[4]),
142
+ Number(airportsRecord[6]) * 0.3048
143
+ ),
144
+ {
145
+ title: icaoCode,
146
+ type: geoJsonType(type, isMilitary, length),
147
+ description: airportsRecord[3],
148
+ elevation: Number(airportsRecord[6]),
149
+ municipality: airportsRecord[10],
150
+ fileSize: Math.ceil(length),
151
+ "marker-symbol": airportsRecord[2].match(/heliport/)
152
+ ? "heliport"
153
+ : airportsRecord[2].match(/small/)
154
+ ? "airfield"
155
+ : "airport",
156
+ "marker-color": airportsRecord[2].match(/large/)
157
+ ? "#5e6eba"
158
+ : airportsRecord[2].match(/small/)
159
+ ? "#777777"
160
+ : "#555555",
161
+ }
162
+ );
163
+ if (isMilitary) {
164
+ feature.setProperty("isMilitary", true);
165
+ }
166
+
167
+ aeroflyGeoJson.addFeature(feature);
168
+ }
169
+
170
+ if (airportsRecordsProcessed % 5000 === 0) {
171
+ const index = aeroflyAirportsLength - aeroflyAirports.size;
172
+ process.stderr
173
+ .write(` Processed \x1b[92m${String(airportsRecordsProcessed).padStart(5)}\x1b[0m airport records, found \x1b[92m${String(index).padStart(5)}\x1b[0m Aerofly FS Airports
174
+ `);
175
+ }
176
+ }
177
+
178
+ // Ensure the output directory exists
179
+ const outputDirectory = path.join("data");
180
+ if (!fs.existsSync(outputDirectory)) {
181
+ fs.mkdirSync(outputDirectory, { recursive: true });
182
+ }
183
+
184
+ // Write the GeoJSON data to a file
185
+ const outputFilePath = path.join(outputDirectory, "airports.geojson");
186
+ fs.writeFileSync(outputFilePath, JSON.stringify(aeroflyGeoJson, null, 2), "utf-8");
187
+
188
+ // Write the ICAO codes to airport-list.json
189
+ const icaoListFilePath = path.join(outputDirectory, "airport-list.json");
190
+ fs.writeFileSync(icaoListFilePath, JSON.stringify(icaoCodes, null, 2), "utf-8");
191
+
192
+ // Log a message to STDERR to confirm the files were written
193
+ process.stderr.write(`GeoJSON data written to \x1b[92m${outputFilePath}\x1b[0m\n`);
194
+ process.stderr.write(`ICAO code list written to \x1b[92m${icaoListFilePath}\x1b[0m\n`);
195
+
196
+ if (aeroflyAirports.size > 0) {
197
+ process.stderr.write(
198
+ `Missing airport matches for \x1b[92m${
199
+ aeroflyAirports.size
200
+ }\x1b[0m Aerofly FS4 Airports, \x1b[92m${(
201
+ (aeroflyAirports.size / aeroflyAirportsLength) *
202
+ 100
203
+ ).toFixed(1)}%\x1b[0m
204
+ Missing matches for Aerofly FS4 Airport codes:
205
+ \x1b[90m> ${[...aeroflyAirports]
206
+ .map((a) => {
207
+ return a[0];
208
+ })
209
+ .join(", ")}\x1b[0m
210
+ `
211
+ );
212
+ }
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="-9.4369e-16" y="3.9209e-8" width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.8521 0.66145h-0.39688c-0.13229 0-0.13229-0.13229 0-0.13229h1.0583c0.13229 0 0.13229 0.13229 0 0.13229h-0.39686s0.15875 0.11465 0.15875 0.37923v0.2646h1.1642v0.38806l-1.1642 0.38806-0.097014 0.97014 0.48507 0.25577v0.13229h-1.3582v-0.13229l0.48507-0.25577-0.097014-0.97014-1.1642-0.38806v-0.38806h1.1642v-0.26458c0-0.2646 0.15875-0.37925 0.15875-0.37925z" fill="#f9f9f9" stroke-width=".19403"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#bebebe" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect x="-9.4369e-16" y="3.9209e-8" width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.3053 1.0142c-0.19403 0-0.19403 0.19403 0 0.19403h0.77611v0.19403c-0.053746 0-0.097014 0.043268-0.097014 0.097014v0.097014h-0.69237c-0.044141-0.17073-0.19779-0.29083-0.37478-0.29104-0.21432 0-0.38806 0.17373-0.38806 0.38806s0.17373 0.38806 0.38806 0.38806c0.072217-1.94e-4 0.14296-0.020528 0.20425-0.058732l0.47484 0.64082c0.19546 0.28144 0.54681 0.29104 0.6791 0.29104h0.97014s0.19403 0 0.19403-0.19403v-0.19288c0-0.14213-0.024448-0.21962-0.097014-0.29219l-0.58208-0.58208s-0.11479-0.097014-0.24708-0.097014h-0.14098v-0.097014c0-0.053746-0.043268-0.097014-0.097014-0.097014v-0.19403h0.77611c0.19403 0 0.19403-0.19403 0-0.19403zm-0.38806 0.48507c0.10716 0 0.19403 0.086866 0.19403 0.19403 0 0.10716-0.086866 0.19403-0.19403 0.19403-0.10716 0-0.19403-0.086866-0.19403-0.19403 0-0.10716 0.086866-0.19403 0.19403-0.19403zm1.5522 0.29104c0.097014 0 0.1532 0.06269 0.19403 0.097014l0.48507 0.48507h-0.6791s-0.19403 0-0.19403-0.19403v-0.19403s0-0.19403 0.19403-0.19403z" fill="#f9f9f9" stroke-width=".19403"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#8dd35f" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
@@ -0,0 +1,4 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#5f5fd3" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
4
+ p
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#555" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m1.9844 0.49911-0.13234 0.42694 0.0093833 0.39188-0.12296 0.019041v0.32372l-0.83616 1.1235-0.073779 0.22851 0.98372 0.038084 0.049187 0.41893h0.24593l0.049186-0.41893 0.98372-0.038084-0.073779-0.22851-0.83616-1.1235v-0.32372l-0.12297-0.019041 0.0092684-0.39188-0.13223-0.42694z" fill="#fff" stroke-linecap="round" stroke-width=".008179"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><rect width="3.9688" height="3.9687" ry=".52917" fill="#fff" fill-rule="evenodd" stroke-width=".26458"/><rect x=".26458" y=".26458" width="3.4396" height="3.4396" ry=".26458" fill="#777" fill-rule="evenodd" stroke-width=".26458"/><path d="m3.4396 1.8521v0.32632l-1.2612-0.19403-0.06174 0.92604 0.54681 0.33514v0.19403l-0.6791-0.13229-0.6791 0.13229v-0.19403l0.54681-0.33514-0.06174-0.92604-1.2612 0.19403v-0.32632l1.2612-0.4498v-0.58208s0-0.29104 0.19403-0.29104 0.19403 0.29104 0.19403 0.29104v0.54681z" fill="#f9f9f9" stroke-width=".19403"/></svg>
@@ -0,0 +1,3 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+ <svg width="15" height="15" version="1.1" viewBox="0 0 3.9687 3.9688" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g transform="matrix(.64925 0 0 .64925 94.84 -35.012)" fill-rule="evenodd"><path d="m-143.02 54.168-0.17579 0.30664-2.9121 5.043h6.1777zm0 0.81445 2e-3 2e-3 -0.17578 0.30664-2.207 3.8184h4.7637l2e-3 2e-3h-4.7676l2e-3 -2e-3zm2e-3 0.81641 1.6758 2.9043h-3.3535z" color="#000000" fill="#fff" style="-inkscape-stroke:none;paint-order:stroke markers fill"/><path d="m-143.02 54.576-2.7344 4.7383h5.4707zm2e-3 0.81641 2.0273 3.5137h-4.0566z" color="#000000" fill="#1a1a1a" style="-inkscape-stroke:none;paint-order:stroke markers fill"/></g></svg>