Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efddc55ad0 | ||
|
|
33be95ef59 | ||
|
|
ede21454a2 | ||
|
|
9c80a20f3e | ||
|
|
43785a074b | ||
|
|
b0c93d10d7 | ||
|
|
3106f8c103 | ||
|
|
8eada07869 | ||
|
|
5d86cb1d04 | ||
|
|
b7618edc25 | ||
|
|
bd46b72144 | ||
|
|
6126b5055b | ||
|
|
9edc2d9534 | ||
|
|
eada464fb3 | ||
|
|
ca7772dd72 | ||
|
|
4c95927231 | ||
|
|
c6e4a455b6 | ||
|
|
079ac37787 | ||
|
|
b35ace9734 | ||
|
|
714795d52b | ||
|
|
cc3d8ca5f3 | ||
|
|
6f1577f957 | ||
|
|
9c45e7275c | ||
|
|
c894706140 | ||
|
|
21988c61da | ||
|
|
f18e5c19d3 | ||
|
|
237e5b2e54 | ||
|
|
3f9c46e846 | ||
|
|
17d1adedbc | ||
|
|
cd02b03502 | ||
|
|
5c3151c2f5 | ||
|
|
f5eb9a4a8d | ||
|
|
f09b5de6c2 | ||
|
|
0080ac6170 | ||
|
|
78362d51f2 | ||
|
|
d09d953cbf | ||
|
|
b54020e23b | ||
|
|
2878270187 | ||
|
|
007dee7341 | ||
|
|
077b44ece1 | ||
|
|
619f2293df | ||
|
|
a84e6ae22f | ||
|
|
920a1d2d36 | ||
|
|
d08ed41185 | ||
|
|
0874e8f8ed | ||
|
|
9223410b40 | ||
|
|
4a3862189f | ||
|
|
53c9a82af2 | ||
|
|
f89dea5805 | ||
|
|
d041c21270 | ||
|
|
bbe9f38bda | ||
|
|
d72f8a3c94 | ||
|
|
cd56ebe0a1 | ||
|
|
fba8ca10a2 | ||
|
|
b4a1c6c0cc | ||
|
|
23a84ac185 | ||
|
|
c8b6dd2fb1 | ||
|
|
a5adb39770 | ||
|
|
b5466b53e4 | ||
|
|
6911e67c81 | ||
|
|
254e9b4c61 | ||
|
|
c9561a5489 | ||
|
|
a8d58abaca | ||
|
|
dce5fd87da | ||
|
|
c5b4a9ffb2 | ||
|
|
787df5478d | ||
|
|
47e3f9c7e8 | ||
|
|
ea9cd5fc40 | ||
|
|
7c664578af | ||
|
|
d9cc468478 | ||
|
|
2c3dd4534e | ||
|
|
92cc0c87ed | ||
|
|
6e0b67f2a0 | ||
|
|
1733fc7c1b | ||
|
|
72ffd0ef18 | ||
|
|
2b49eda7cb | ||
|
|
5ff9b113d9 | ||
|
|
9997a8a8c0 | ||
|
|
4f022ec43a | ||
|
|
76e3239d54 | ||
|
|
c66ae8d2d0 | ||
|
|
24ac5931f8 | ||
|
|
7cd3171998 | ||
|
|
5576d2edf1 | ||
|
|
465a644365 | ||
|
|
2a22906a68 | ||
|
|
b0576f3725 | ||
|
|
b5b91d0df5 | ||
|
|
aafb4cb4a0 | ||
|
|
db58468187 | ||
|
|
f53c74e06f | ||
|
|
aa2b96ac70 | ||
|
|
5caf865fc8 | ||
|
|
a98b8dbd47 | ||
|
|
8d090b0774 | ||
|
|
707e71caf1 | ||
|
|
77a4c96b1c | ||
|
|
f49e44800a | ||
|
|
8ca5ca9bc1 | ||
|
|
53b0e8961b | ||
|
|
bc9ea58614 | ||
|
|
d1f2e7376e | ||
|
|
5c9516d038 | ||
|
|
4982f04258 | ||
|
|
a908cd7abc | ||
|
|
049d54e44e | ||
|
|
e4764c64e4 | ||
|
|
0c4ee97368 | ||
|
|
141b7a08ce | ||
|
|
67585cd99a | ||
|
|
d9879bad60 | ||
|
|
f0ac8ac5d4 | ||
|
|
6e5676c890 | ||
|
|
aec665179f | ||
|
|
64a85afe6a | ||
|
|
3e81ee8015 | ||
|
|
61ba86c6e6 | ||
|
|
4aafe332a7 | ||
|
|
ca224e95c1 | ||
|
|
1b4463a3c1 | ||
|
|
45567fc520 |
No files matched your search
@@ -6,7 +6,14 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check:
|
check:
|
||||||
runs-on: ubuntu-latest
|
# brick-build's job container now points at Gitea's own official
|
||||||
|
# runner image (docker.gitea.com/runner-images:ubuntu-latest, has
|
||||||
|
# Node out of the box) — the earlier opensuse/leap:16.0 image that
|
||||||
|
# failed here on 2026-09-12 (no Node, every step below is a JS-based
|
||||||
|
# Action) got repointed. Pinned to brick specifically again, rather
|
||||||
|
# than the shared ubuntu-latest label "Gitea Runner on Cube" also
|
||||||
|
# answers to.
|
||||||
|
runs-on: brick-build
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -18,7 +25,7 @@ jobs:
|
|||||||
bundler-cache: true
|
bundler-cache: true
|
||||||
|
|
||||||
- name: Ruby syntax check
|
- name: Ruby syntax check
|
||||||
run: find app.rb config lib -name "*.rb" -print0 | xargs -0 -n1 ruby -c
|
run: find app.rb config lib bin -name "*.rb" -print0 | xargs -0 -n1 ruby -c
|
||||||
|
|
||||||
- name: App loads without errors
|
- name: App loads without errors
|
||||||
run: bundle exec ruby -e 'require_relative "app"'
|
run: bundle exec ruby -e 'require_relative "app"'
|
||||||
@@ -34,13 +41,13 @@ jobs:
|
|||||||
run: find public/js -name "*.js" -print0 | xargs -0 -n1 node --check
|
run: find public/js -name "*.js" -print0 | xargs -0 -n1 node --check
|
||||||
|
|
||||||
# Handled by a separate, native (non-Docker) runner registered with the
|
# Handled by a separate, native (non-Docker) runner registered with the
|
||||||
# single "cube-host" label (see docs/deployment.adoc) — it runs directly
|
# single "brick-host" label (see docs/deployment.adoc) — it runs directly
|
||||||
# on the server, so it has docker/git/etc. from the host, no socket/bind
|
# on the server, so it has podman/git/etc. from the host, no socket/bind
|
||||||
# mount tricks needed. The Docker-based runner keeps handling `check`.
|
# mount tricks needed. The Docker-based runner keeps handling `check`.
|
||||||
deploy:
|
deploy:
|
||||||
needs: check
|
needs: check
|
||||||
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
||||||
runs-on: cube-host
|
runs-on: brick-host
|
||||||
steps:
|
steps:
|
||||||
# Not actions/checkout@v4: keeping this plain-git checkout from when
|
# Not actions/checkout@v4: keeping this plain-git checkout from when
|
||||||
# the job ran in a Node-less container — no reason to add a Node
|
# the job ran in a Node-less container — no reason to add a Node
|
||||||
@@ -62,21 +69,26 @@ jobs:
|
|||||||
- name: Write REVISION
|
- name: Write REVISION
|
||||||
run: git rev-parse --short HEAD > REVISION
|
run: git rev-parse --short HEAD > REVISION
|
||||||
|
|
||||||
# Clears out everything in the target except data/ (uploaded GPX
|
# The checkout lives in build/, wiped and fully replaced every
|
||||||
# files, offline.pmtiles) and copies the fresh checkout over, dropping
|
# deploy — data/ and .env live one level up, in /srv/podman/maptool/
|
||||||
# .git afterwards. Dotfiles like .env (deployment secrets) are left
|
# itself, so they're never inside the directory this nukes and never
|
||||||
# alone too — the glob below doesn't match them — and neither is ever
|
# need special-casing here (an earlier version of this step had to
|
||||||
# part of the git checkout in the first place, since both are
|
# skip data/ by name for exactly that reason). docker-compose.yaml
|
||||||
# gitignored.
|
# itself is symlinked up to /srv/podman/maptool/ (relative target, so
|
||||||
- name: Sync to /srv/docker/maptool
|
# it survives /srv/podman itself ever being renamed) purely for
|
||||||
|
# discoverability — a human poking around expects to find it at the
|
||||||
|
# top, not a level down — and because that's what makes `./data` in
|
||||||
|
# it resolve to the sibling directory: compose resolves a relative
|
||||||
|
# volume path against the *symlink's* location, not build/ (verified
|
||||||
|
# against plain `docker-compose config`), so podman-compose@.service's
|
||||||
|
# own WorkingDirectory=/srv/podman/maptool needs no change either.
|
||||||
|
- name: Sync to /srv/podman/maptool/build
|
||||||
run: |
|
run: |
|
||||||
mkdir -p /srv/docker/maptool
|
rm -rf /srv/podman/maptool/build
|
||||||
for f in /srv/docker/maptool/*; do
|
mkdir -p /srv/podman/maptool/build
|
||||||
[ "$(basename "$f")" = "data" ] && continue
|
cp -a . /srv/podman/maptool/build/
|
||||||
rm -rf "$f"
|
rm -rf /srv/podman/maptool/build/.git
|
||||||
done
|
ln -sf build/docker-compose.yaml /srv/podman/maptool/docker-compose.yaml
|
||||||
cp -a . /srv/docker/maptool/
|
|
||||||
rm -rf /srv/docker/maptool/.git
|
|
||||||
|
|
||||||
# Regenerated on every deploy from repo secrets (Settings -> Actions
|
# Regenerated on every deploy from repo secrets (Settings -> Actions
|
||||||
# -> Secrets) rather than created once by hand on the server — see
|
# -> Secrets) rather than created once by hand on the server — see
|
||||||
@@ -88,16 +100,50 @@ jobs:
|
|||||||
# actually reads.
|
# actually reads.
|
||||||
- name: Write .env
|
- name: Write .env
|
||||||
run: |
|
run: |
|
||||||
cat > /srv/docker/maptool/.env <<EOF
|
cat > /srv/podman/maptool/.env <<EOF
|
||||||
SESSION_SECRET=${{ secrets.SESSION_SECRET }}
|
SESSION_SECRET=${{ secrets.SESSION_SECRET }}
|
||||||
GITEA_OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
|
GITEA_OAUTH_CLIENT_ID=${{ secrets.OAUTH_CLIENT_ID }}
|
||||||
GITEA_OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
|
GITEA_OAUTH_CLIENT_SECRET=${{ secrets.OAUTH_CLIENT_SECRET }}
|
||||||
MAPTOOL_ALLOWED_GITEA_USER=${{ secrets.MAPTOOL_ALLOWED_GITEA_USER }}
|
MAPTOOL_ALLOWED_GITEA_USER=${{ secrets.MAPTOOL_ALLOWED_GITEA_USER }}
|
||||||
LIVETRACK_WEBHOOK_SECRET=${{ secrets.LIVETRACK_WEBHOOK_SECRET }}
|
LIVETRACK_WEBHOOK_SECRET=${{ secrets.LIVETRACK_WEBHOOK_SECRET }}
|
||||||
|
P4N_USER=${{ secrets.P4N_USER }}
|
||||||
|
P4N_PASSWORD=${{ secrets.P4N_PASSWORD }}
|
||||||
|
P4N_SESSION_COOKIE=${{ secrets.P4N_SESSION_COOKIE }}
|
||||||
EOF
|
EOF
|
||||||
chmod 600 /srv/docker/maptool/.env
|
chmod 600 /srv/podman/maptool/.env
|
||||||
|
|
||||||
|
# podman-compose@maptool.service (/etc/systemd/system/podman-compose@.service)
|
||||||
|
# owns the container lifecycle — its ExecStartPre already tears down
|
||||||
|
# the previous containers/networks/volumes on every start, and its
|
||||||
|
# ExecStart runs `podman compose up --build` (mirroring
|
||||||
|
# docker-compose@.service's own `--build`), so a plain restart always
|
||||||
|
# picks up the code just synced above while still reusing unchanged
|
||||||
|
# layers (apt-get/bundle install) as cache — unlike explicitly
|
||||||
|
# removing the image first, which would delete that cache along with
|
||||||
|
# it. sudo is needed since the unit is root's, not gitea-runner's own
|
||||||
|
# (gitea-runner has passwordless sudo for this on brick-host).
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
|
run: sudo systemctl restart podman-compose@maptool.service
|
||||||
|
|
||||||
|
# systemctl restart above only confirms the process forked
|
||||||
|
# (Type=simple, no readiness protocol) — not that `podman compose up
|
||||||
|
# --build` actually finished and Puma is answering. Polls the health
|
||||||
|
# endpoint (brick-host itself, same machine the container's port is
|
||||||
|
# published on) instead of trusting that — checking for this
|
||||||
|
# deploy's own revision, not just any 200, so a stuck old container
|
||||||
|
# that never got torn down can't pass as a successful deploy of the
|
||||||
|
# new one. 5 minutes covers even a from-scratch rebuild (Gemfile
|
||||||
|
# change, ~3.5 minutes measured) with room to spare; a cached
|
||||||
|
# rebuild is normally up within a minute.
|
||||||
|
- name: Wait for it to come up
|
||||||
run: |
|
run: |
|
||||||
cd /srv/docker/maptool
|
expected=$(git rev-parse --short HEAD)
|
||||||
docker compose -p maptool up --build -d
|
for i in $(seq 1 30); do
|
||||||
|
if curl -sf http://localhost:4567/api/health | grep -q "\"revision\":\"$expected\""; then
|
||||||
|
echo "Healthy at $expected after $(( (i - 1) * 10 ))s"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
sleep 10
|
||||||
|
done
|
||||||
|
echo "::error::maptool did not report revision $expected within 5 minutes"
|
||||||
|
exit 1
|
||||||
@@ -8,17 +8,25 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
renovate:
|
renovate:
|
||||||
# The native deploy runner (see docs/deployment.adoc) already has
|
# Same reasoning as check's runs-on in ci.yml: brick-build's job
|
||||||
# Docker access — Renovate itself is a one-shot batch job, not a
|
# container now has Node (repointed at Gitea's own official runner
|
||||||
# long-running service, so `docker run`, not docker-compose.
|
# image), which setup-node/npm below need.
|
||||||
runs-on: deploy
|
runs-on: brick-build
|
||||||
steps:
|
steps:
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
|
||||||
|
- name: Install Renovate
|
||||||
|
run: npm install -g renovate
|
||||||
|
|
||||||
- name: Run Renovate
|
- name: Run Renovate
|
||||||
run: |
|
run: renovate
|
||||||
docker run --rm \
|
env:
|
||||||
-e RENOVATE_PLATFORM=gitea \
|
RENOVATE_PLATFORM: gitea
|
||||||
-e RENOVATE_ENDPOINT="${{ gitea.server_url }}/api/v1/" \
|
RENOVATE_ENDPOINT: "${{ gitea.server_url }}/api/v1/"
|
||||||
-e RENOVATE_TOKEN="${{ secrets.RENOVATE_TOKEN }}" \
|
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
|
||||||
-e RENOVATE_REPOSITORIES="${{ gitea.repository }}" \
|
RENOVATE_REPOSITORIES: "${{ gitea.repository }}"
|
||||||
-e RENOVATE_ONBOARDING=false \
|
RENOVATE_ONBOARDING: "false"
|
||||||
renovate/renovate
|
GITHUB_COM_TOKEN: ${{ secrets.GITHUB_COM_TOKEN }}
|
||||||
@@ -12,6 +12,22 @@
|
|||||||
# see docs/development.adoc)
|
# see docs/development.adoc)
|
||||||
/data/*.pmtiles
|
/data/*.pmtiles
|
||||||
|
|
||||||
|
# Per-country .osm.pbf cache for offline.pmtiles rebuilds (also large,
|
||||||
|
# also local-only — see PmtilesBuilder.cached_extract)
|
||||||
|
/data/geofabrik_cache/
|
||||||
|
|
||||||
|
# Planetiler's own auxiliary-source cache and scratch working directory
|
||||||
|
# (water polygons/Natural Earth/lake centerlines, --download; temp feature
|
||||||
|
# DBs, --tmpdir) — relative to the app's CWD, which lands them here
|
||||||
|
# alongside the rest of data/ (see extract_offline's --download comment)
|
||||||
|
/data/sources/
|
||||||
|
/data/tmp/
|
||||||
|
|
||||||
|
# Offline Wikipedia ZIM for kiwix-serve (also large, also local-only —
|
||||||
|
# same story, see docs/development.adoc)
|
||||||
|
/data/kiwix/*
|
||||||
|
!/data/kiwix/.gitkeep
|
||||||
|
|
||||||
# User-uploaded GPX files
|
# User-uploaded GPX files
|
||||||
/data/gpx/*
|
/data/gpx/*
|
||||||
!/data/gpx/.gitkeep
|
!/data/gpx/.gitkeep
|
||||||
@@ -19,6 +35,12 @@
|
|||||||
# Garmin LiveTrack session IDs received via webhook
|
# Garmin LiveTrack session IDs received via webhook
|
||||||
/data/livetrack.json
|
/data/livetrack.json
|
||||||
|
|
||||||
|
# Private import plugins (see lib/maptool/plugins.rb's PLUGINS_DIRS,
|
||||||
|
# docs/deployment.adoc) — a plugin whose ToS forbids living in this public
|
||||||
|
# repo belongs here instead, outside git entirely.
|
||||||
|
/data/plugins/*
|
||||||
|
!/data/plugins/.gitkeep
|
||||||
|
|
||||||
# Editor / OS
|
# Editor / OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.swp
|
*.swp
|
||||||
|
|||||||
@@ -1,11 +1,49 @@
|
|||||||
FROM ruby:4.0.6-slim-bookworm
|
# trixie, not bookworm: Planetiler needs Java 21+ (UnsupportedClassVersionError
|
||||||
|
# otherwise) and bookworm's own default-jre-headless is only Java 17 —
|
||||||
|
# bookworm-backports doesn't carry a newer one either, only trixie's does.
|
||||||
|
FROM ruby:4.0.6-slim-trixie
|
||||||
|
|
||||||
# build-essential: puma has a native (C) extension; harmless if the
|
# build-essential: puma has a native (C) extension; harmless if the
|
||||||
# platform-specific gem in Gemfile.lock already has a precompiled binary
|
# platform-specific gem in Gemfile.lock already has a precompiled binary
|
||||||
# matching this base image, but keeps the build working if it doesn't.
|
# matching this base image, but keeps the build working if it doesn't.
|
||||||
RUN apt-get update && apt-get install --no-install-recommends -y build-essential \
|
# ca-certificates/curl: needed for the go-pmtiles/planetiler.jar downloads
|
||||||
|
# below (also handy for anyone debugging inside a running container).
|
||||||
|
# default-jre-headless: Planetiler is a JVM tool (see PLANETILER_VERSION
|
||||||
|
# below) — headless since this never touches a display; trixie's version
|
||||||
|
# of this package is Java 21, see above.
|
||||||
|
# osmium-tool: merges multiple countries' OSM extracts into one file before
|
||||||
|
# handing them to Planetiler (lib/maptool/pmtiles_builder.rb) — a normal
|
||||||
|
# Debian package, no separate download/pinning needed like the other two.
|
||||||
|
RUN apt-get update && apt-get install --no-install-recommends -y build-essential ca-certificates curl default-jre-headless osmium-tool \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# The `pmtiles` CLI (lib/maptool/pmtiles_builder.rb's "Regenerate for
|
||||||
|
# current view" — see docs/development.adoc) — no Debian package exists,
|
||||||
|
# so this pulls the static binary straight from its own GitHub releases.
|
||||||
|
# Architecture-matched (Debian's dpkg arch name translated to the release
|
||||||
|
# asset's own naming) rather than hardcoded amd64, so this still works
|
||||||
|
# building on an arm64 host. Pinned, like every other dependency in this
|
||||||
|
# repo (Gemfile, docs/third-party-notices.adoc) — bump PMTILES_VERSION
|
||||||
|
# deliberately, not via `latest`.
|
||||||
|
ARG PMTILES_VERSION=1.31.2
|
||||||
|
RUN set -eux; \
|
||||||
|
case "$(dpkg --print-architecture)" in \
|
||||||
|
amd64) pmtiles_arch=x86_64 ;; \
|
||||||
|
arm64) pmtiles_arch=arm64 ;; \
|
||||||
|
*) echo "unsupported architecture for go-pmtiles: $(dpkg --print-architecture)" >&2; exit 1 ;; \
|
||||||
|
esac; \
|
||||||
|
curl -fsSL "https://github.com/protomaps/go-pmtiles/releases/download/v${PMTILES_VERSION}/go-pmtiles_${PMTILES_VERSION}_Linux_${pmtiles_arch}.tar.gz" \
|
||||||
|
| tar xz -C /usr/local/bin pmtiles
|
||||||
|
|
||||||
|
# Planetiler (lib/maptool/pmtiles_builder.rb's offline.pmtiles build,
|
||||||
|
# replacing an earlier Protomaps-CDN-based pipeline — see
|
||||||
|
# docs/development.adoc) — a single JVM fat jar, no per-arch variant needed
|
||||||
|
# unlike go-pmtiles above. Default PLANETILER_JAR path (pmtiles_builder.rb)
|
||||||
|
# expects it here.
|
||||||
|
ARG PLANETILER_VERSION=0.10.2
|
||||||
|
RUN curl -fsSL "https://github.com/onthegomap/planetiler/releases/download/v${PLANETILER_VERSION}/planetiler.jar" \
|
||||||
|
-o /usr/local/lib/planetiler.jar
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY Gemfile Gemfile.lock ./
|
COPY Gemfile Gemfile.lock ./
|
||||||
|
|||||||
@@ -15,3 +15,5 @@ group :test do
|
|||||||
gem "minitest", "~> 6.0"
|
gem "minitest", "~> 6.0"
|
||||||
gem "rake", "~> 13.0"
|
gem "rake", "~> 13.0"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
gem "faraday-cookie_jar", "~> 0.0.8"
|
||||||
@@ -7,11 +7,15 @@ GEM
|
|||||||
version_gem (~> 1.1, >= 1.1.14)
|
version_gem (~> 1.1, >= 1.1.14)
|
||||||
base64 (0.3.0)
|
base64 (0.3.0)
|
||||||
bigdecimal (4.1.2)
|
bigdecimal (4.1.2)
|
||||||
|
domain_name (0.6.20260902)
|
||||||
drb (2.2.3)
|
drb (2.2.3)
|
||||||
faraday (2.14.3)
|
faraday (2.14.3)
|
||||||
faraday-net_http (>= 2.0, < 3.5)
|
faraday-net_http (>= 2.0, < 3.5)
|
||||||
json
|
json
|
||||||
logger
|
logger
|
||||||
|
faraday-cookie_jar (0.0.8)
|
||||||
|
faraday (>= 0.8.0)
|
||||||
|
http-cookie (>= 1.0.0)
|
||||||
faraday-net_http (3.4.4)
|
faraday-net_http (3.4.4)
|
||||||
net-http (~> 0.5)
|
net-http (~> 0.5)
|
||||||
ffi (1.17.4-aarch64-linux-gnu)
|
ffi (1.17.4-aarch64-linux-gnu)
|
||||||
@@ -26,6 +30,8 @@ GEM
|
|||||||
ffi (1.17.4-x86_64-linux-musl)
|
ffi (1.17.4-x86_64-linux-musl)
|
||||||
hashie (5.1.0)
|
hashie (5.1.0)
|
||||||
logger
|
logger
|
||||||
|
http-cookie (1.1.6)
|
||||||
|
domain_name (~> 0.5)
|
||||||
json (2.21.2)
|
json (2.21.2)
|
||||||
jwt (3.2.0)
|
jwt (3.2.0)
|
||||||
base64
|
base64
|
||||||
@@ -106,6 +112,7 @@ PLATFORMS
|
|||||||
x86_64-linux-musl
|
x86_64-linux-musl
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
faraday-cookie_jar (~> 0.0.8)
|
||||||
minitest (~> 6.0)
|
minitest (~> 6.0)
|
||||||
oauth2 (~> 2.0)
|
oauth2 (~> 2.0)
|
||||||
puma (~> 8.0)
|
puma (~> 8.0)
|
||||||
@@ -122,8 +129,10 @@ CHECKSUMS
|
|||||||
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
||||||
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
||||||
bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac
|
bundler (4.0.18) sha256=02d9a17429de1847b4e0c9f27a9ee4b20c0a74c0a641b4e77195d6019e3618ac
|
||||||
|
domain_name (0.6.20260902) sha256=4fac7f8e0a77dc85ec02f6534fdeff38e143d434ab8a86528fe68ca5bc45a042
|
||||||
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
||||||
faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
|
faraday (2.14.3) sha256=1882247e6766615c8220b4392bf1d27f6ebb63d8e28267587cef1fb0bf37f278
|
||||||
|
faraday-cookie_jar (0.0.8) sha256=0140605823f8cc63c7028fccee486aaed8e54835c360cffc1f7c8c07c4299dbb
|
||||||
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
|
faraday-net_http (3.4.4) sha256=0e78af151747ed1b00f33e25973b4bc220d7f16c00c39676817c8b12331eb588
|
||||||
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
ffi (1.17.4-aarch64-linux-gnu) sha256=b208f06f91ffd8f5e1193da3cae3d2ccfc27fc36fba577baf698d26d91c080df
|
||||||
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
ffi (1.17.4-aarch64-linux-musl) sha256=9286b7a615f2676245283aef0a0a3b475ae3aae2bb5448baace630bb77b91f39
|
||||||
@@ -136,6 +145,7 @@ CHECKSUMS
|
|||||||
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
ffi (1.17.4-x86_64-linux-gnu) sha256=9d3db14c2eae074b382fa9c083fe95aec6e0a1451da249eab096c34002bc752d
|
||||||
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
ffi (1.17.4-x86_64-linux-musl) sha256=3fdf9888483de005f8ef8d1cf2d3b20d86626af206cbf780f6a6a12439a9c49e
|
||||||
hashie (5.1.0) sha256=c266471896f323c446ea8207f8ffac985d2718df0a0ba98651a3057096ca3870
|
hashie (5.1.0) sha256=c266471896f323c446ea8207f8ffac985d2718df0a0ba98651a3057096ca3870
|
||||||
|
http-cookie (1.1.6) sha256=ba4b82be64de61dc281243dac70e3c382c45142f20268ed9276a3670c93feaa9
|
||||||
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
||||||
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
|
jwt (3.2.0) sha256=5419b1fe37b1da0982bd07051f573a8b8789ab724c2aa7e785e4784a3ed217d7
|
||||||
listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2
|
listen (3.10.0) sha256=c6e182db62143aeccc2e1960033bebe7445309c7272061979bb098d03760c9d2
|
||||||
|
|||||||
@@ -6,3 +6,66 @@ Rake::TestTask.new(:test) do |t|
|
|||||||
end
|
end
|
||||||
|
|
||||||
task default: :test
|
task default: :test
|
||||||
|
|
||||||
|
namespace :overpass do
|
||||||
|
desc "Fetch camper-relevant POIs (water, dump stations, LPG, toilets, laundry, shops, springs) " \
|
||||||
|
"for a bbox from Overpass and write GeoJSON. Usage: rake overpass:pois[south,west,north,east,out.geojson]"
|
||||||
|
task :pois, [:south, :west, :north, :east, :out] do |_t, args|
|
||||||
|
%i[south west north east out].each { |key| abort("usage: rake overpass:pois[south,west,north,east,out.geojson]") unless args[key] }
|
||||||
|
|
||||||
|
require_relative "lib/maptool/overpass_pois"
|
||||||
|
bbox = [args[:south], args[:west], args[:north], args[:east]].map(&:to_f)
|
||||||
|
|
||||||
|
puts "Querying Overpass for #{bbox.join(",")}..."
|
||||||
|
response = Maptool::OverpassPois.fetch(bbox)
|
||||||
|
geojson = Maptool::OverpassPois.to_geojson(response)
|
||||||
|
File.write(args[:out], JSON.generate(geojson))
|
||||||
|
puts "Wrote #{geojson["features"].size} POIs to #{args[:out]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :hiking do
|
||||||
|
desc "Extract route=hiking relations (name/colour/network/operator, full geometry) from a local " \
|
||||||
|
".osm.pbf (e.g. one of PmtilesBuilder's own data/geofabrik_cache/*.osm.pbf) and write GeoJSON. " \
|
||||||
|
"Prototype — see lib/maptool/hiking_trails.rb. Usage: rake hiking:trails[input.osm.pbf,out.geojson]"
|
||||||
|
task :trails, %i[pbf out] do |_t, args|
|
||||||
|
%i[pbf out].each { |key| abort("usage: rake hiking:trails[input.osm.pbf,out.geojson]") unless args[key] }
|
||||||
|
|
||||||
|
require_relative "lib/maptool/hiking_trails"
|
||||||
|
|
||||||
|
puts "Extracting hiking routes from #{args[:pbf]}..."
|
||||||
|
geojson = Maptool::HikingTrails.extract(args[:pbf])
|
||||||
|
File.write(args[:out], JSON.generate(geojson))
|
||||||
|
puts "Wrote #{geojson["features"].size} route(s) to #{args[:out]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :gpx do
|
||||||
|
desc "Backfill properties.id on Note-category features saved before edit/delete (and its id targeting) existed"
|
||||||
|
task :backfill_note_ids do
|
||||||
|
require "json"
|
||||||
|
require "securerandom"
|
||||||
|
|
||||||
|
data_dir = ENV.fetch("MAPTOOL_DATA_DIR") { File.join(__dir__, "data") }
|
||||||
|
count = 0
|
||||||
|
Dir.glob(File.join(data_dir, "gpx", "**", "*.geojson")).each do |file|
|
||||||
|
# ".gpx.geojson"/".fit.geojson" are GpxStore's own cache sidecars for
|
||||||
|
# an uploaded GPX/FIT (regenerated from the source on demand, see
|
||||||
|
# GpxStore#geojson) — never hand-drawn notes, not worth touching.
|
||||||
|
next if file.end_with?(".gpx.geojson", ".fit.geojson")
|
||||||
|
|
||||||
|
collection = JSON.parse(File.read(file))
|
||||||
|
changed = false
|
||||||
|
(collection["features"] || []).each do |feature|
|
||||||
|
properties = (feature["properties"] ||= {})
|
||||||
|
next unless properties["category"] == "Note" && !properties["id"]
|
||||||
|
|
||||||
|
properties["id"] = SecureRandom.uuid
|
||||||
|
changed = true
|
||||||
|
count += 1
|
||||||
|
end
|
||||||
|
File.write(file, JSON.generate(collection)) if changed
|
||||||
|
end
|
||||||
|
puts "Backfilled #{count} note id(s)"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
= TODO — implementation ideas
|
||||||
|
|
||||||
|
Ideas for offline use in a camper van. Implement incrementally, grouped by topic.
|
||||||
|
|
||||||
|
The local server has access to both the vehicle's hardware and offline data — the
|
||||||
|
vision is to grow it into a complete offline control center: resources, data, and
|
||||||
|
independence from connectivity.
|
||||||
|
|
||||||
|
== Offline data and cartography
|
||||||
|
|
||||||
|
* [x] Offline vector tiles (PMTiles/MBTiles) for Europe/world via Planetiler or MapTiler — Geofabrik per-country extracts, `osmium merge`, Planetiler's default OpenMapTiles profile straight to PMTiles (`lib/maptool/pmtiles_builder.rb`); offline now shares the exact same style/palette as online (`ONLINE_STYLE_PATCH`) instead of a separate protomaps-schema theme
|
||||||
|
* [x] Shaded relief / DEM layer — hillshade toggle in the sidebar, sourced from public Terrarium tiles online; reads `data/terrain.pmtiles` for offline if one's been generated (see docs/development.adoc), otherwise the toggle is online-only for now
|
||||||
|
** [ ] Terrain slope readout for parking (numeric, not just visual shading)
|
||||||
|
* [ ] Contour lines from OSM for hikes and mountain passes
|
||||||
|
* [x] Hiking trail overlay — Waymarked Trails raster tiles online (`public/js/hiking_trails.js`), colored per-route by the OSM `colour` tag they already render (the KST/KČT paint-blaze colors in Slovakia/Czechia, plain red elsewhere). Click queries a one-off Overpass `around` search (racing 3 public mirrors) for the route's name/colour/network/operator/distance
|
||||||
|
** [x] Offline copy wired in for real, no custom Planetiler profile needed — `Maptool::HikingTrails` (`lib/maptool/hiking_trails.rb`) runs `osmium tags-filter r/route=hiking` + `add-locations-to-ways` + `cat -f opl` against the *same* per-country Geofabrik `.osm.pbf` already downloaded for `offline.pmtiles`, no new download. One `data/hiking_trails/<CODE>.geojson` per country (not one combined file), generated as the last step of every offline build and cached like `geofabrik_cache` (skipped once already newer than its source PBF) — a many-country selection no longer means one giant file, since `GET /api/hiking-trails` lists available countries and the Overlays panel's own checklist only fetches `GET /tiles/hiking_trails/<CODE>.geojson` for whichever ones get ticked. Offline click uses a local `queryRenderedFeatures` hit-test instead of Overpass — instant, no network. See docs/development.adoc
|
||||||
|
* [x] Overpass/OSM scripts to extract POIs (drinking water, dump stations, LPG, public toilets, laundromats, shops, springs) — `rake overpass:pois`, see docs/development.adoc; writes a `.geojson` to drop in `data/gpx/` or upload. Also runnable from inside the app itself now, see the import plugins entry below (`plugins/overpass-pois` wraps this same query). Each category now carries a real `POI_ICONS` glyph and color (not p4n2gpx's plain red marker) plus a popup description built from opening_hours/operator/website tags
|
||||||
|
* [x] Import plugins: a pluggable way to add more data sources (Park4Night, iOverlander, Freecampsites, ...) without their scrapers necessarily living in this (public) repo — sidebar icon (logged in only) lists whatever's under `plugins/`, "Run for this area" uses the current map viewport as the bbox, no separate area-picker needed. Contract: a `plugin.json` manifest + a script that reads `--south`/`--west`/`--north`/`--east` and prints a GeoJSON `FeatureCollection` to stdout (`lib/maptool/plugins.rb`, `GET /api/plugins`, `POST /api/plugins/:name/run`) — output saved the same way an upload would be. `plugins/overpass-pois` ships as the reference example; a private/ToS-sensitive scraper just drops its own directory in locally and is never `git add`ed
|
||||||
|
** [x] Park4Night — private plugin (`data/plugins/p4n2gpx/`, outside this repo per the "ToS-sensitive scraper" note above); iOverlander/Freecampsites still open, same story
|
||||||
|
** [x] A private plugin's own dependencies now have somewhere to live too: `data/plugins/` is under the persistent `data/` bind mount (survives a redeploy, unlike `build/`), and `docs/deployment.adoc` documents the convention
|
||||||
|
* [x] Kiwix/ZIM offline Wikipedia linked to POI GPS coordinates — proxied through this app (`GET /kiwix/*` in app.rb) rather than a direct link to kiwix-serve — production sits behind a reverse proxy that only forwards to this app, so no address exists for a browser to reach kiwix-serve at directly; kiwix-serve runs with `--urlRootLocation=/kiwix` so its own emitted links already match. `KIWIX_URL` + `GET /api/kiwix` reachability check, same optional-integration pattern as offline.pmtiles/terrain.pmtiles; `docker-compose.yaml` now runs a `kiwix` service, ZIM not included (see docs/development.adoc for where to get one)
|
||||||
|
** [x] Replaced the old per-popup "Nearby on Wikipedia" link (one POI at a time, easy to miss) with a standalone "Wikipedia" layer under Tracks & Points — a live `GET /api/kiwix/nearby` refetch on every pan/zoom, not a saved file. kiwix-serve's own geo search never echoes back a result's coordinates, so `Maptool::Kiwix.nearby` fetches each matching article's own page too and reads its `<meta name="geo.position">` tag — verified against the real production ZIM (~1.6s for a 30-result page; tried parallelizing those fetches with threads, no measured gain, kept sequential)
|
||||||
|
* [x] Cache weather forecast when temporarily connected — Open-Meteo 3-day forecast in the Radar flyout, last successful fetch cached in localStorage as a fallback when offline
|
||||||
|
** [x] Cache the radar tile *images* too — `public/sw.js`, network-falling-back-to-cache for RainViewer tile requests, registered from layout.erb
|
||||||
|
|
||||||
|
== NPS visual style
|
||||||
|
|
||||||
|
* [x] Map Park4Night/OSM place types to NPS pictograms (campground, primitive camp, drinking water, dump station, viewpoint, picnic table) for instant recognition without reading text — verified against real p4n2gpx exports (`data/gpx/p4n_*.gpx`, ~5000 waypoints, 15 distinct `<type>` values): every one already resolves to a registered `POI_ICONS` key, 0 waypoints fall back iconless. Drinking water/dump station aren't standalone p4n place types (they're description tags on a camping spot, already shown via `PARK4NIGHT_TAGS`); viewpoint doesn't appear in these exports but has an icon (`tourism_viewpoint`) ready if it ever does
|
||||||
|
* [ ] Restyle popovers, side panels, and point detail views after the NPS Unigrid layout system (Massimo Vignelli) with Frutiger/Rawlinson Roadside-style typography
|
||||||
|
** [x] Top black banner (full width): "MAPTOOL" on the left, live map-center coordinates in the center, offline-data status dot on the right, white sans-serif type — DEM elevation and a hamburger menu skipped (no DEM layer yet; sidebar isn't mobile-collapsed, so nothing for a hamburger to reveal)
|
||||||
|
** [ ] Point detail/sidepanel: black header banner with white location name and an NPS pictogram in the corner; amenities (water, waste, power, slope) shown as a pictogram grid, no extra text; action buttons for "route here" (Valhalla), "start leveling", "save offline"
|
||||||
|
** [x] Filter panel as a "Unigrid Matrix" — an icon grid to toggle categories on/off, styled like the legend on an official NPS paper map, instead of plain dropdowns
|
||||||
|
* [ ] Custom vector style (MVT/MapLibre) using the NPS color palette — muted natural tones for forests, water, and contours — so POIs and routes stand out at high zoom
|
||||||
|
** [ ] Landuse & protections: distinct styling for national parks, nature reserves (translucent green/gray-green), military zones, and private land (important for wild camping)
|
||||||
|
** [ ] OpenTopoMap-style `fill-pattern` texture for forest (tree icons) and meadow/pasture (grass texture) landcover, instead of flat fill colors — Liberty's own sprite (`public/vendor/liberty-sprite/`) only has a `wetland` dot pattern, no tree/grass, so this needs custom pattern images added to the sprite sheet; works offline and online alike since it's just a style/sprite change, unlike pulling in OpenTopoMap's own raster tiles (online-only, restrictive tile-usage policy, wouldn't match the NPS palette)
|
||||||
|
** [x] Elevation-tinted terrain — a `color-relief` layer (hypsometric tint, this app's own green→brown→gray palette) under the hillshade layer, same "Terrain shading" toggle
|
||||||
|
** [x] Road hierarchy: clearly distinguish paved roads from unpaved/gravel/track — critical for knowing if the camper can get there
|
||||||
|
|
||||||
|
== Main menu reorganization (4 modules)
|
||||||
|
|
||||||
|
* [ ] Explorer: place filters (Park4Night + OSM layers: water, waste, LPG, showers); map layers (contours, hillshade, satellite cache)
|
||||||
|
* [ ] Navigation & route: offline router (Valhalla) with vehicle dimensions (height, weight, width); route elevation profile and isochrones
|
||||||
|
* [ ] Offline hub: manager for downloaded PMTiles (country vector maps); downloaded Wikipedia archives; cached radar and weather
|
||||||
|
* [ ] Camper & HW: level gauge (wheel-chock leveling); solar calculator (sun path on the map); telemetry (Victron / Home Assistant)
|
||||||
|
|
||||||
|
== Custom layers & data
|
||||||
|
|
||||||
|
* [x] GPX/GeoJSON drag-and-drop import (Wikiloc, Komoot, own GPX tracks — also FIT), auto-styled by category with a generated elevation profile — KML not supported (only GPX/FIT/GeoJSON/.zip)
|
||||||
|
* [x] User overlay: offline drawing of custom points with notes (e.g. "turning space for a long vehicle", "narrow gate", "good swimming spot") — one click on the map, pick or name a `.geojson` layer, saved into the same GpxStore the Tracks panel already reads (so visibility/popup come for free)
|
||||||
|
** [x] Save a distance/area shape as a note too — Measure already draws these by hand, so a "Save" button there (not a second click-to-draw UI in the notes tool) reuses that instead of duplicating it; both share one `NoteSaveForm` (notes.js). `gpx.js`'s `_addLayers` now renders Polygon and note-flagged LineString features (fill/outline, dashed line), not just points
|
||||||
|
** [x] Edit/delete individual notes from their popup (logged-in only) — targets a feature by a client-generated `properties.id` (`GpxStore#update_feature`/`#delete_feature`), not array position
|
||||||
|
** [x] Measure's vertices are draggable (mousedown + move, `event.preventDefault()` to stop the map's own drag-pan) and deletable (click to select — shown in yellow — then Delete/Backspace)
|
||||||
|
** [x] Fixed: a note saved before `properties.id` existed (the earliest test notes) had no Edit/Delete-able target — the popup no longer offers them without an id, and `rake gpx:backfill_note_ids` assigns one to any pre-existing Note feature missing it
|
||||||
|
** [x] Title + body split (name in the black header, body in a separate field) with light self-authored markdown (`**bold**`, `*italic*`, auto-linked URLs) in the body — a *new*, separate `formatNoteBody` (not `formatDescription`, which stays plain/untrusted-safe for uploaded GPX/park4night content per docs/nps-resources.adoc's earlier markdown-rendering call)
|
||||||
|
** [x] Gated behind login: NotesControl isn't added to the map at all when logged out, Measure's "Save as note" button is hidden (Measure itself stays usable either way)
|
||||||
|
** [x] The clicked point stays marked on the map while the note form is open (a plain `Marker`, removed on save/cancel/deactivate) — it was disappearing back into the flat map with nothing showing where the note would land
|
||||||
|
** [x] Icon picker for point notes (a grid in the save form) — extends `POI_ICONS` with more of the symbols `PARK4NIGHT_TAGS` already vendors (water, dump station, toilets, playground, viewpoint, swimming, fishing, climbing, hiking, …), reusable as real marker-badge glyphs, not just inline tag icons. Hidden for a Measure-drawn line/polygon note — no single spot for a badge to sit at
|
||||||
|
** [x] Fixed: the Save button's own blue background was losing to `.notes-panel__actions button`'s gray — same class-vs-class+element specificity trap as `.gpx-panel__action[hidden]` already has a comment about elsewhere in this file, just not yet applied here
|
||||||
|
** [x] Fixed: the Unigrid Matrix category filter only re-sliced the *points* source (`_applyCategoryFilter`) — toggling the "Note" category off hid point notes but not polygon/line ones sharing that category. `_linesByPath` now tracks those alongside `_pointsByPath`, re-sliced/scanned the same way
|
||||||
|
* [ ] Offline satellite tile caching (e.g. ESRI) for a selected route area — key for judging surface quality and obstacles when wild camping
|
||||||
|
|
||||||
|
== Nice to have (later)
|
||||||
|
|
||||||
|
Deprioritized for now — revisit after the sections above land.
|
||||||
|
|
||||||
|
=== Vanlife features
|
||||||
|
|
||||||
|
* [ ] Offline routing (Valhalla/OSRM) with vehicle parameters (height, weight, width)
|
||||||
|
* [ ] Solar calculator — sun path for location/date, shadows from surroundings, camper orientation
|
||||||
|
|
||||||
|
=== HW integration
|
||||||
|
|
||||||
|
* [ ] Level gauge from IMU (ESP32/RPi) — camper tilt, wheel-chock leveling calculation
|
||||||
|
* [ ] Victron/Home Assistant via MQTT or WebSocket — battery SoC, tank levels, temperatures
|
||||||
|
* [ ] Plot GPS track with historical battery/speed data
|
||||||
|
|
||||||
|
== Admin / ops tooling
|
||||||
|
|
||||||
|
* [ ] Offline build log readability — a failed pmtiles/terrain build currently surfaces as one
|
||||||
|
~500k-character line; format it for the web UI instead (e.g. per-country/state name + status,
|
||||||
|
not the raw combined stdout/stderr dump)
|
||||||
|
* [ ] Expose the build log via an API endpoint, not just embedded in the admin page
|
||||||
|
* [ ] Document all API endpoints somewhere and make them easy to browse (status of each) —
|
||||||
|
maybe a status page, login-gated
|
||||||
@@ -10,8 +10,45 @@ require_relative "lib/maptool/gpx_store"
|
|||||||
require_relative "lib/maptool/gpsd_client"
|
require_relative "lib/maptool/gpsd_client"
|
||||||
require_relative "lib/maptool/livetrack_store"
|
require_relative "lib/maptool/livetrack_store"
|
||||||
require_relative "lib/maptool/livetrack_client"
|
require_relative "lib/maptool/livetrack_client"
|
||||||
|
require_relative "lib/maptool/kiwix"
|
||||||
|
require_relative "lib/maptool/plugins"
|
||||||
|
require_relative "lib/maptool/pmtiles_builder"
|
||||||
|
|
||||||
module Maptool
|
module Maptool
|
||||||
|
# Only /login, /auth/callback and /logout ever write to session
|
||||||
|
# (oauth_state, user, session.clear) — everything else only ever reads
|
||||||
|
# it, if it touches session at all. That still matters:
|
||||||
|
# Rack::Session::Cookie re-serializes and re-sets the cookie on *every*
|
||||||
|
# response that so much as read session (Rack::Protection's own CSRF
|
||||||
|
# check does, on nearly every request) — reflecting whatever cookie
|
||||||
|
# that particular request happened to arrive with, whether or not
|
||||||
|
# anything changed. A HAR capture caught the actual failure mode this
|
||||||
|
# caused: seconds after a successful login, a background poll fired the
|
||||||
|
# instant the page loads was still carrying the browser's pre-login
|
||||||
|
# cookie (a client-side timing race across the OAuth redirect chain,
|
||||||
|
# not something this server controls), and its own re-issued Set-Cookie
|
||||||
|
# landed *after* the login's, silently overwriting session[:user] with
|
||||||
|
# nothing logged anywhere to explain it.
|
||||||
|
#
|
||||||
|
# A plain Rack middleware, not a Sinatra `before` filter: static assets
|
||||||
|
# (public_folder — every JS/CSS/font/vendor file) are served by
|
||||||
|
# Sinatra's own built-in static-file shortcut, which bypasses route and
|
||||||
|
# filter dispatch entirely but is still wrapped by this, same as every
|
||||||
|
# other response — each one was independently capable of winning the
|
||||||
|
# clobbering race a `before` filter alone left uncovered.
|
||||||
|
class SkipSessionWrite
|
||||||
|
WRITE_PATHS = %w[/login /auth/callback /logout].freeze
|
||||||
|
|
||||||
|
def initialize(app)
|
||||||
|
@app = app
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(env)
|
||||||
|
env["rack.session.options"][:skip] = true unless WRITE_PATHS.include?(env["PATH_INFO"])
|
||||||
|
@app.call(env)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
class App < Sinatra::Base
|
class App < Sinatra::Base
|
||||||
set :public_folder, File.join(__dir__, "public")
|
set :public_folder, File.join(__dir__, "public")
|
||||||
set :views, File.join(__dir__, "views")
|
set :views, File.join(__dir__, "views")
|
||||||
@@ -30,6 +67,7 @@ module Maptool
|
|||||||
# don't survive a restart there, which is fine outside production.
|
# don't survive a restart there, which is fine outside production.
|
||||||
set :sessions, httponly: true, secure: production?, same_site: :lax,
|
set :sessions, httponly: true, secure: production?, same_site: :lax,
|
||||||
secret: ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) }
|
secret: ENV.fetch("SESSION_SECRET") { SecureRandom.hex(32) }
|
||||||
|
use SkipSessionWrite
|
||||||
|
|
||||||
# Cache-busts /js and /css requests: browsers (and a caching proxy in
|
# Cache-busts /js and /css requests: browsers (and a caching proxy in
|
||||||
# front of production, see docs/deployment.adoc) hang onto static
|
# front of production, see docs/deployment.adoc) hang onto static
|
||||||
@@ -58,9 +96,28 @@ module Maptool
|
|||||||
DATA_DIR = ENV.fetch("MAPTOOL_DATA_DIR") { File.join(__dir__, "data") }
|
DATA_DIR = ENV.fetch("MAPTOOL_DATA_DIR") { File.join(__dir__, "data") }
|
||||||
|
|
||||||
OFFLINE_PMTILES_PATH = File.join(DATA_DIR, "offline.pmtiles")
|
OFFLINE_PMTILES_PATH = File.join(DATA_DIR, "offline.pmtiles")
|
||||||
|
TERRAIN_PMTILES_PATH = File.join(DATA_DIR, "terrain.pmtiles")
|
||||||
GPX_STORE = GpxStore.new(File.join(DATA_DIR, "gpx"))
|
GPX_STORE = GpxStore.new(File.join(DATA_DIR, "gpx"))
|
||||||
LIVETRACK_STORE = LiveTrackStore.new(File.join(DATA_DIR, "livetrack.json"))
|
LIVETRACK_STORE = LiveTrackStore.new(File.join(DATA_DIR, "livetrack.json"))
|
||||||
|
|
||||||
|
# Not under DATA_DIR: unlike everything else there (uploads, PMTiles,
|
||||||
|
# the kiwix ZIM), a plugin is code, not local data — plugins/overpass-pois
|
||||||
|
# ships checked into this repo as the reference example of the
|
||||||
|
# contract. Overridable so tests can point it at a throwaway fixture
|
||||||
|
# directory. In production this whole directory is baked into the
|
||||||
|
# image (see Dockerfile's `COPY . .`) and wiped/replaced on every
|
||||||
|
# deploy along with the rest of the checkout (docs/deployment.adoc) —
|
||||||
|
# fine for a plugin checked into this repo, not for a private one.
|
||||||
|
PLUGINS_DIR = ENV.fetch("MAPTOOL_PLUGINS_DIR") { File.join(__dir__, "plugins") }
|
||||||
|
|
||||||
|
# A second, optional plugin directory under DATA_DIR — the same
|
||||||
|
# persistent bind mount as gpx/kiwix (docs/deployment.adoc), so a
|
||||||
|
# private/scraper plugin (one whose ToS forbids living in this public
|
||||||
|
# repo) dropped in data/plugins/ survives a redeploy the way PLUGINS_DIR
|
||||||
|
# above can't. Checked after PLUGINS_DIR (see Plugins.list/run), so a
|
||||||
|
# same-named private plugin never shadows the built-in one.
|
||||||
|
PLUGINS_DIRS = [PLUGINS_DIR, File.join(DATA_DIR, "plugins")].freeze
|
||||||
|
|
||||||
# Shared secret for the Garmin LiveTrack webhook (a Google Apps Script
|
# Shared secret for the Garmin LiveTrack webhook (a Google Apps Script
|
||||||
# watching Gmail for LiveTrack emails, see docs/deployment.adoc) — not a
|
# watching Gmail for LiveTrack emails, see docs/deployment.adoc) — not a
|
||||||
# Gitea-authenticated user, so it can't use the OAuth2 login flow above.
|
# Gitea-authenticated user, so it can't use the OAuth2 login flow above.
|
||||||
@@ -68,6 +125,16 @@ module Maptool
|
|||||||
# OAuth2 secrets: fine outside production, nothing works without it set.
|
# OAuth2 secrets: fine outside production, nothing works without it set.
|
||||||
LIVETRACK_WEBHOOK_SECRET = ENV.fetch("LIVETRACK_WEBHOOK_SECRET", "")
|
LIVETRACK_WEBHOOK_SECRET = ENV.fetch("LIVETRACK_WEBHOOK_SECRET", "")
|
||||||
|
|
||||||
|
# An external kiwix-serve instance (see docs/development.adoc), not
|
||||||
|
# something this app runs itself — blank disables the "Nearby on
|
||||||
|
# Wikipedia" popup link the same way the other optional-integration
|
||||||
|
# blanks above do. Only ever reached server-side (the Docker-network
|
||||||
|
# service name in production, e.g. "http://kiwix:8080") — production
|
||||||
|
# sits behind a reverse proxy (map.priesol.net) that only forwards to
|
||||||
|
# this app, so a browser can't reach kiwix-serve directly by any
|
||||||
|
# hostname/port of its own. GET /kiwix/* below proxies to it instead.
|
||||||
|
KIWIX_URL = ENV.fetch("KIWIX_URL", "")
|
||||||
|
|
||||||
# Login delegates to the Gitea instance the user already has an account
|
# Login delegates to the Gitea instance the user already has an account
|
||||||
# on, rather than maintaining a local username/password — see
|
# on, rather than maintaining a local username/password — see
|
||||||
# docs/architecture.adoc. Only users listed in ALLOWED_GITEA_USERS may
|
# docs/architecture.adoc. Only users listed in ALLOWED_GITEA_USERS may
|
||||||
@@ -94,6 +161,13 @@ module Maptool
|
|||||||
halt 401, json(error: "login required", login_url: "/login") unless logged_in?
|
halt 401, json(error: "login required", login_url: "/login") unless logged_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def pmtiles_file_status(path, meta)
|
||||||
|
return { available: false } unless File.exist?(path)
|
||||||
|
|
||||||
|
status = { available: true, size: File.size(path), mtime: File.mtime(path).utc.iso8601 }
|
||||||
|
meta ? status.merge(meta: meta) : status
|
||||||
|
end
|
||||||
|
|
||||||
def callback_url
|
def callback_url
|
||||||
"#{request.base_url}/auth/callback"
|
"#{request.base_url}/auth/callback"
|
||||||
end
|
end
|
||||||
@@ -169,11 +243,11 @@ module Maptool
|
|||||||
end
|
end
|
||||||
|
|
||||||
get "/api/health" do
|
get "/api/health" do
|
||||||
json status: "ok"
|
json status: "ok", revision: REVISION
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/offline-tiles" do
|
get "/api/offline-tiles" do
|
||||||
json available: File.exist?(OFFLINE_PMTILES_PATH)
|
json pmtiles_file_status(OFFLINE_PMTILES_PATH, PmtilesBuilder.offline_meta(DATA_DIR))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Served as a plain range-request-capable file, not decoded server-side:
|
# Served as a plain range-request-capable file, not decoded server-side:
|
||||||
@@ -185,6 +259,204 @@ module Maptool
|
|||||||
send_file OFFLINE_PMTILES_PATH, type: "application/octet-stream"
|
send_file OFFLINE_PMTILES_PATH, type: "application/octet-stream"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Compares the recorded build (offline.pmtiles.meta.json) against the
|
||||||
|
# current latest Protomaps build without rebuilding anything —
|
||||||
|
# update_available is null (not true/false) if there's no local build
|
||||||
|
# yet, or the live index couldn't be reached, so the frontend can tell
|
||||||
|
# "don't know" apart from "no, up to date". Not folded into GET
|
||||||
|
# /api/offline-tiles above: that route is on the hot path (map.js's own
|
||||||
|
# topbar-dot check on every page load), this one only gets called when
|
||||||
|
# the Offline data panel is actually opened.
|
||||||
|
get "/api/offline-tiles/check-update" do
|
||||||
|
json update_available: PmtilesBuilder.offline_update_available?(DATA_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Lists the selectable countries and which of them are currently
|
||||||
|
# selected (data/offline_regions.json) — read-only, no login needed,
|
||||||
|
# same tier as the plain availability checks above.
|
||||||
|
get "/api/offline-regions" do
|
||||||
|
json available: PmtilesBuilder.available_regions, selected: PmtilesBuilder.selected_regions(DATA_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Just updates the selection — does not itself start a build, so
|
||||||
|
# ticking several countries doesn't fire off several rebuilds. The
|
||||||
|
# frontend follows this with the two build routes below once the user
|
||||||
|
# is done choosing.
|
||||||
|
post "/api/offline-regions" do
|
||||||
|
require_login!
|
||||||
|
codes = Array(params[:codes])
|
||||||
|
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.set_selected_regions(DATA_DIR, codes)
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 400, json(error: e.message)
|
||||||
|
end
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Regenerates offline.pmtiles for the currently selected regions (see
|
||||||
|
# POST /api/offline-regions above) by downloading each from Geofabrik
|
||||||
|
# and tiling them with Planetiler — see lib/maptool/pmtiles_builder.rb.
|
||||||
|
# Runs in the background; poll build-status below for progress.
|
||||||
|
post "/api/offline-tiles/build" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.build_offline(data_dir: DATA_DIR)
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 409, json(error: e.message)
|
||||||
|
end
|
||||||
|
json status: "started"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/api/offline-tiles/build-status" do
|
||||||
|
json PmtilesBuilder.offline_status
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deletes offline.pmtiles plus its whole per-country download cache —
|
||||||
|
# a manual "reclaim the space"/"start over" action, distinct from
|
||||||
|
# unticking countries in the region picker (which only changes what a
|
||||||
|
# future build covers, doesn't free anything already on disk).
|
||||||
|
delete "/api/offline-tiles" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.delete_offline_data(DATA_DIR)
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 409, json(error: e.message)
|
||||||
|
end
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
# What's actually in the per-country download cache right now (not the
|
||||||
|
# same as offline.pmtiles' own meta.sources — see cached_extracts) —
|
||||||
|
# read-only, same tier as GET /api/offline-regions above, so the region
|
||||||
|
# picker can show a size + delete affordance next to each cached
|
||||||
|
# country regardless of whether it's still selected or already built.
|
||||||
|
get "/api/offline-tiles/cache" do
|
||||||
|
json extracts: PmtilesBuilder.cached_extracts(DATA_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deletes one country's cached Geofabrik extract only — e.g. to force a
|
||||||
|
# clean re-download of just that one, or reclaim space for a country
|
||||||
|
# that's no longer selected (the cache otherwise keeps it indefinitely).
|
||||||
|
delete "/api/offline-tiles/cache/:code" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.delete_cached_extract(DATA_DIR, params[:code])
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 409, json(error: e.message)
|
||||||
|
end
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same optional-local-file story as offline.pmtiles above — see
|
||||||
|
# docs/development.adoc for how to generate one, until then hillshade
|
||||||
|
# just stays unavailable while the offline basemap is active.
|
||||||
|
get "/api/terrain-tiles" do
|
||||||
|
json pmtiles_file_status(TERRAIN_PMTILES_PATH, PmtilesBuilder.terrain_meta(DATA_DIR))
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/tiles/terrain.pmtiles" do
|
||||||
|
halt 404 unless File.exist?(TERRAIN_PMTILES_PATH)
|
||||||
|
|
||||||
|
send_file TERRAIN_PMTILES_PATH, type: "application/octet-stream"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same reasoning as GET /api/offline-tiles/check-update above.
|
||||||
|
get "/api/terrain-tiles/check-update" do
|
||||||
|
json update_available: PmtilesBuilder.terrain_update_available?(DATA_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/api/terrain-tiles/build" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.build_terrain(data_dir: DATA_DIR)
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 409, json(error: e.message)
|
||||||
|
end
|
||||||
|
json status: "started"
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/api/terrain-tiles/build-status" do
|
||||||
|
json PmtilesBuilder.terrain_status
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same reasoning as DELETE /api/offline-tiles above.
|
||||||
|
delete "/api/terrain-tiles" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
PmtilesBuilder.delete_terrain_data(DATA_DIR)
|
||||||
|
rescue PmtilesBuilder::BuildError => e
|
||||||
|
halt 409, json(error: e.message)
|
||||||
|
end
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generated as a step inside POST /api/offline-tiles/build (see
|
||||||
|
# PmtilesBuilder.extract_hiking_trails) rather than its own separate
|
||||||
|
# build — it's derived from the exact same per-country .osm.pbf cache
|
||||||
|
# offline.pmtiles itself already downloads, so there's nothing of its
|
||||||
|
# own to trigger; DELETE /api/offline-tiles removes it too. One file
|
||||||
|
# per country (not one combined file) so a viewer only ever fetches
|
||||||
|
# the country they actually pick in the Hiking trails overlay's own
|
||||||
|
# checklist (public/js/hiking_trails.js), not every selected country's
|
||||||
|
# trails at once — `countries` lists which ones exist to build that
|
||||||
|
# checklist from.
|
||||||
|
get "/api/hiking-trails" do
|
||||||
|
json countries: PmtilesBuilder.hiking_trails_countries(DATA_DIR)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Fetched client-side only for a country the viewer actually ticks in
|
||||||
|
# the Hiking trails checklist, not on page load. :code is constrained
|
||||||
|
# to exactly what hiking_trails_countries can ever return (two
|
||||||
|
# uppercase letters) — otherwise this would accept an arbitrary
|
||||||
|
# ".."-laden path segment.
|
||||||
|
get "/tiles/hiking_trails/:code.geojson" do
|
||||||
|
halt 404 unless params[:code].match?(/\A[A-Z]{2}\z/)
|
||||||
|
|
||||||
|
path = PmtilesBuilder.hiking_trails_country_path(DATA_DIR, params[:code])
|
||||||
|
halt 404 unless File.exist?(path)
|
||||||
|
|
||||||
|
send_file path, type: "application/geo+json"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pinged once per page load (public/js/map.js) — url is a same-origin
|
||||||
|
# path (not kiwix-serve's own address, which the browser can't reach
|
||||||
|
# directly behind the reverse proxy in front of this app), proxied by
|
||||||
|
# GET /kiwix/* below.
|
||||||
|
get "/api/kiwix" do
|
||||||
|
reachable = Kiwix.reachable?(KIWIX_URL)
|
||||||
|
json available: reachable, url: (reachable ? "/kiwix" : "")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Backs the "Wikipedia" map layer (public/js/gpx.js) — replaces the old
|
||||||
|
# per-popup "Nearby on Wikipedia" link, which only ever pointed at one
|
||||||
|
# POI at a time. Read-only and login-free, same as the tree/plugin
|
||||||
|
# listing routes below: viewing the map needs no account, only writing
|
||||||
|
# to it does.
|
||||||
|
get "/api/kiwix/nearby" do
|
||||||
|
halt 404 if KIWIX_URL.empty?
|
||||||
|
bbox = %w[south west north east].map { |key| params[key].to_s }
|
||||||
|
halt 400, json(error: "south, west, north, east are required") if bbox.any?(&:empty?)
|
||||||
|
|
||||||
|
json Kiwix.nearby(KIWIX_URL, bbox.map(&:to_f))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Proxies kiwix-serve's own search UI and content pages through this
|
||||||
|
# app instead of linking the browser to it directly (see KIWIX_URL) —
|
||||||
|
# kiwix-serve is started with --urlRootLocation=/kiwix
|
||||||
|
# (docker-compose.yaml), so every link/asset it emits already carries
|
||||||
|
# this same /kiwix prefix and Just Works through this passthrough. GET
|
||||||
|
# only: browsing a read-only ZIM archive never needs to write anything.
|
||||||
|
get "/kiwix/*" do
|
||||||
|
halt 404 if KIWIX_URL.empty?
|
||||||
|
response = Kiwix.proxy(KIWIX_URL, "/kiwix/#{params[:splat].first}", request.query_string)
|
||||||
|
halt 502 unless response
|
||||||
|
|
||||||
|
content_type response["content-type"] || "application/octet-stream"
|
||||||
|
status response.code.to_i
|
||||||
|
body response.body
|
||||||
|
end
|
||||||
|
|
||||||
get "/api/gpx/tree" do
|
get "/api/gpx/tree" do
|
||||||
json GPX_STORE.tree
|
json GPX_STORE.tree
|
||||||
end
|
end
|
||||||
@@ -212,6 +484,112 @@ module Maptool
|
|||||||
json paths: paths, tree: GPX_STORE.tree
|
json paths: paths, tree: GPX_STORE.tree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# A hand-drawn point/note (see notes.js) — unlike the upload above, this
|
||||||
|
# grows one existing/new .geojson "layer" file in place (GpxStore#
|
||||||
|
# append_feature) rather than always landing as its own new file.
|
||||||
|
post "/api/gpx/notes" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
feature = JSON.parse(params[:feature].to_s)
|
||||||
|
rescue JSON::ParserError
|
||||||
|
halt 400, json(error: "invalid feature")
|
||||||
|
end
|
||||||
|
halt 400, json(error: "invalid feature") unless feature.is_a?(Hash) && feature["geometry"]
|
||||||
|
|
||||||
|
begin
|
||||||
|
path = GPX_STORE.append_feature(
|
||||||
|
feature: feature,
|
||||||
|
path: params[:path],
|
||||||
|
folder: params[:folder].to_s,
|
||||||
|
name: params[:name].to_s
|
||||||
|
)
|
||||||
|
rescue GpxStore::InvalidPath
|
||||||
|
halt 400, json(error: "invalid path")
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
halt 404, json(error: "file not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
json path: path, tree: GPX_STORE.tree
|
||||||
|
end
|
||||||
|
|
||||||
|
# Editing/deleting one note (see the popup's Edit/Delete actions,
|
||||||
|
# gpx.js) — targets a feature by properties["id"], not array position
|
||||||
|
# (see GpxStore#update_feature/#delete_feature for why).
|
||||||
|
put "/api/gpx/notes" do
|
||||||
|
require_login!
|
||||||
|
begin
|
||||||
|
feature = JSON.parse(params[:feature].to_s)
|
||||||
|
rescue JSON::ParserError
|
||||||
|
halt 400, json(error: "invalid feature")
|
||||||
|
end
|
||||||
|
halt 400, json(error: "invalid feature") unless feature.is_a?(Hash) && feature["geometry"]
|
||||||
|
halt 400, json(error: "path and id are required") if params[:path].to_s.empty? || params[:id].to_s.empty?
|
||||||
|
|
||||||
|
begin
|
||||||
|
GPX_STORE.update_feature(path: params[:path], id: params[:id], feature: feature)
|
||||||
|
rescue GpxStore::InvalidPath
|
||||||
|
halt 400, json(error: "invalid path")
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
halt 404, json(error: "note not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
delete "/api/gpx/notes" do
|
||||||
|
require_login!
|
||||||
|
halt 400, json(error: "path and id are required") if params[:path].to_s.empty? || params[:id].to_s.empty?
|
||||||
|
|
||||||
|
begin
|
||||||
|
GPX_STORE.delete_feature(path: params[:path], id: params[:id])
|
||||||
|
rescue GpxStore::InvalidPath
|
||||||
|
halt 400, json(error: "invalid path")
|
||||||
|
rescue Errno::ENOENT
|
||||||
|
halt 404, json(error: "note not found")
|
||||||
|
end
|
||||||
|
|
||||||
|
json ok: true
|
||||||
|
end
|
||||||
|
|
||||||
|
# Lists whatever import plugins are on disk (see lib/maptool/plugins.rb
|
||||||
|
# for the contract) — read-only, doesn't need require_login! the way
|
||||||
|
# actually running one does below.
|
||||||
|
get "/api/plugins" do
|
||||||
|
json Plugins.list(PLUGINS_DIRS)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Runs one plugin for the given bbox (the current map viewport, from
|
||||||
|
# the frontend's "Run for this area" button) and saves its GeoJSON
|
||||||
|
# output as a new file the same way an uploaded .geojson would be
|
||||||
|
# saved — reusing GpxStore#save rather than #append_feature since this
|
||||||
|
# writes a whole FeatureCollection at once, not one feature into an
|
||||||
|
# existing one.
|
||||||
|
post "/api/plugins/:name/run" do
|
||||||
|
require_login!
|
||||||
|
bbox = %w[south west north east].map { |key| params[key].to_s }
|
||||||
|
halt 400, json(error: "south, west, north, east are required") if bbox.any?(&:empty?)
|
||||||
|
|
||||||
|
south, west, north, east = bbox.map(&:to_f)
|
||||||
|
begin
|
||||||
|
geojson = Plugins.run(PLUGINS_DIRS, params[:name], [south, west, north, east])
|
||||||
|
rescue Plugins::RunError => e
|
||||||
|
halt 502, json(error: e.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Filename carries the area's center and the run's timestamp (same
|
||||||
|
# idea as a p4n2gpx export's own "p4n_area_<lat>_<lon>_<date>_<time>"
|
||||||
|
# naming) — without it, running this again for a different area just
|
||||||
|
# overwrote/renamed-with-a-counter the same "overpass-pois.geojson",
|
||||||
|
# with nothing in the file tree to tell one run's POIs from another's.
|
||||||
|
center_lat = format("%.2f", (south + north) / 2)
|
||||||
|
center_lon = format("%.2f", (west + east) / 2)
|
||||||
|
timestamp = Time.now.strftime("%y%m%d_%H%M")
|
||||||
|
filename = "#{params[:name]}_#{center_lat}_#{center_lon}_#{timestamp}.geojson"
|
||||||
|
|
||||||
|
path = GPX_STORE.save(folder: "", filename: filename, io: StringIO.new(JSON.generate(geojson)))
|
||||||
|
json path: path, tree: GPX_STORE.tree
|
||||||
|
end
|
||||||
|
|
||||||
# ?download=geojson exports the file's GeoJSON as an attachment instead
|
# ?download=geojson exports the file's GeoJSON as an attachment instead
|
||||||
# of rendering it on the map — the only way to get a .gpx/.fit's
|
# of rendering it on the map — the only way to get a .gpx/.fit's
|
||||||
# converted form out of the app, since GpxStore#geojson's cache is
|
# converted form out of the app, since GpxStore#geojson's cache is
|
||||||
@@ -325,6 +703,25 @@ module Maptool
|
|||||||
json sessions: LIVETRACK_STORE.list
|
json sessions: LIVETRACK_STORE.list
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Garmin stops serving a session's live-track page some while after the
|
||||||
|
# activity ends (its own session/token URL, not the API — see
|
||||||
|
# LiveTrackClient) — the exact cutoff isn't documented, ~24h is what's
|
||||||
|
# been observed. Ended sessions old enough to likely be past that are
|
||||||
|
# verified once at startup (LiveTrackStore#stale_ended already skips
|
||||||
|
# ones already flagged, so this doesn't re-hit Garmin for the same
|
||||||
|
# dead session on every restart), so the panel can grey them out up
|
||||||
|
# front instead of only failing after a user clicks one and waits on
|
||||||
|
# a fetch.
|
||||||
|
LIVETRACK_MAP_STALE_AFTER = 24 * 60 * 60 # seconds
|
||||||
|
|
||||||
|
Thread.new do
|
||||||
|
LIVETRACK_STORE.stale_ended(LIVETRACK_MAP_STALE_AFTER).each do |session|
|
||||||
|
LiveTrackClient.fetch(session["id"], session["token"])
|
||||||
|
rescue LiveTrackClient::FetchError
|
||||||
|
LIVETRACK_STORE.mark_unavailable(session["id"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Garmin's own API (livetrack.garmin.com/api/v2/...) 403s a plain server
|
# Garmin's own API (livetrack.garmin.com/api/v2/...) 403s a plain server
|
||||||
# request no matter what headers it gets — see LiveTrackClient — so this
|
# request no matter what headers it gets — see LiveTrackClient — so this
|
||||||
# fetches the session page itself and pulls the track out of that.
|
# fetches the session page itself and pulls the track out of that.
|
||||||
@@ -346,10 +743,8 @@ module Maptool
|
|||||||
halt 502, json(error: "track has no positions") if coordinates.empty?
|
halt 502, json(error: "track has no positions") if coordinates.empty?
|
||||||
last = points.last
|
last = points.last
|
||||||
|
|
||||||
json(
|
features = [
|
||||||
type: "FeatureCollection",
|
{ type: "Feature", geometry: { type: "LineString", coordinates: coordinates }, properties: { "kind" => "actual" } },
|
||||||
features: [
|
|
||||||
{ type: "Feature", geometry: { type: "LineString", coordinates: coordinates }, properties: {} },
|
|
||||||
{
|
{
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
geometry: { type: "Point", coordinates: coordinates.last },
|
geometry: { type: "Point", coordinates: coordinates.last },
|
||||||
@@ -363,10 +758,25 @@ module Maptool
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
)
|
|
||||||
|
# Only present when the activity was started from a saved Garmin
|
||||||
|
# course (see LiveTrackClient#fetch) — plotted separately from the
|
||||||
|
# actual track ("kind" distinguishes the two LineStrings) since it's
|
||||||
|
# the planned route, not where the person has actually been.
|
||||||
|
course_coordinates = result.course&.[]("geoPoints")&.filter_map { |p| p.dig("position", "lon") && p.dig("position", "lat") && [p["position"]["lon"], p["position"]["lat"]] }
|
||||||
|
if course_coordinates&.any?
|
||||||
|
features << {
|
||||||
|
type: "Feature",
|
||||||
|
geometry: { type: "LineString", coordinates: course_coordinates },
|
||||||
|
properties: { "kind" => "planned", "name" => result.course["name"] },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
json(type: "FeatureCollection", features: features)
|
||||||
end
|
end
|
||||||
|
|
||||||
get "/api/gnss/position" do
|
get "/api/gnss/position" do
|
||||||
|
require_login!
|
||||||
GNSS_LATEST_MUTEX.synchronize do
|
GNSS_LATEST_MUTEX.synchronize do
|
||||||
received_at = GNSS_LATEST[:received_at]
|
received_at = GNSS_LATEST[:received_at]
|
||||||
fresh = received_at && (Time.now.to_f - received_at) < GNSS_STALE_AFTER
|
fresh = received_at && (Time.now.to_f - received_at) < GNSS_STALE_AFTER
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# Speaks gpsd's own JSON wire protocol (https://gpsd.io/gpsd_json.html)
|
||||||
|
# directly on gpsd's default port — GpsdClient (lib/maptool/gpsd_client.rb)
|
||||||
|
# connects to it exactly as it would to real gpsd, so no app code or config
|
||||||
|
# changes are needed. Unlike docs/development.adoc's fake-NMEA-source
|
||||||
|
# example, this doesn't need gpsd itself installed/running at all, and
|
||||||
|
# actually moves (a fixed point isn't much of a "GNSS position" to look
|
||||||
|
# at) — drives a loop around a center point at a driving-ish pace, one TPV
|
||||||
|
# report per second like a real receiver.
|
||||||
|
#
|
||||||
|
# Usage: bin/gnss_simulator.rb [lat] [lon] [radius_m] [port]
|
||||||
|
require "socket"
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
$stdout.sync = true
|
||||||
|
|
||||||
|
LAT = (ARGV[0] || 48.1486).to_f
|
||||||
|
LON = (ARGV[1] || 17.1077).to_f
|
||||||
|
RADIUS_M = (ARGV[2] || 300).to_f
|
||||||
|
PORT = (ARGV[3] || 2947).to_i
|
||||||
|
LOOP_SECONDS = 120 # time to complete one full loop
|
||||||
|
EARTH_RADIUS_M = 6_371_000
|
||||||
|
|
||||||
|
def point_on_loop(center_lat, center_lon, radius_m, angle_rad)
|
||||||
|
# Flat-plane offset, not geodesic — fine at this radius (metres, not km),
|
||||||
|
# same reasoning as measure.js's own approximations at this scale.
|
||||||
|
lat_offset = (radius_m * Math.sin(angle_rad)) / EARTH_RADIUS_M
|
||||||
|
lon_offset = (radius_m * Math.cos(angle_rad)) / (EARTH_RADIUS_M * Math.cos(center_lat * Math::PI / 180))
|
||||||
|
[center_lat + lat_offset * 180 / Math::PI, center_lon + lon_offset * 180 / Math::PI]
|
||||||
|
end
|
||||||
|
|
||||||
|
server = TCPServer.new("127.0.0.1", PORT)
|
||||||
|
puts "[gnss-simulator] driving a #{RADIUS_M.to_i}m loop around #{LAT},#{LON} — listening on 127.0.0.1:#{PORT}"
|
||||||
|
|
||||||
|
loop do
|
||||||
|
socket = server.accept
|
||||||
|
puts "[gnss-simulator] client connected"
|
||||||
|
start = Time.now
|
||||||
|
|
||||||
|
begin
|
||||||
|
loop do
|
||||||
|
elapsed = Time.now - start
|
||||||
|
angle = (elapsed / LOOP_SECONDS) * 2 * Math::PI
|
||||||
|
lat, lon = point_on_loop(LAT, LON, RADIUS_M, angle)
|
||||||
|
|
||||||
|
# Bearing of travel: tangent to the circle, 90 degrees ahead of the
|
||||||
|
# radius vector at this point.
|
||||||
|
track = (angle * 180 / Math::PI + 90) % 360
|
||||||
|
speed = (2 * Math::PI * RADIUS_M) / LOOP_SECONDS # m/s, constant pace
|
||||||
|
|
||||||
|
report = {
|
||||||
|
class: "TPV",
|
||||||
|
mode: 3,
|
||||||
|
lat: lat,
|
||||||
|
lon: lon,
|
||||||
|
altHAE: 150.0,
|
||||||
|
speed: speed,
|
||||||
|
track: track,
|
||||||
|
eph: 3.0,
|
||||||
|
time: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3NZ"),
|
||||||
|
}
|
||||||
|
socket.puts(JSON.generate(report))
|
||||||
|
sleep 1
|
||||||
|
end
|
||||||
|
rescue Errno::EPIPE, IOError
|
||||||
|
puts "[gnss-simulator] client disconnected"
|
||||||
|
ensure
|
||||||
|
socket.close
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
services:
|
services:
|
||||||
maptool:
|
maptool:
|
||||||
build: .
|
# "./build", not ".": the compose file's own context ("." would mean
|
||||||
|
# the symlink's directory, /srv/podman/maptool/ — see docs/deployment.adoc)
|
||||||
|
# is one level above where the actual checkout (and its Dockerfile)
|
||||||
|
# lives since the previous commit moved it into build/. Volume paths
|
||||||
|
# below resolve against that same symlink directory too, but a relative
|
||||||
|
# *build context* specifically needs pointing at build/ itself — the
|
||||||
|
# gap this fixes: production's compose up --build failed outright
|
||||||
|
# ("lstat .../maptool/Dockerfile: no such file or directory") until this
|
||||||
|
# was caught, since only the volume-path resolution got dry-run tested
|
||||||
|
# before the previous commit shipped, not this one.
|
||||||
|
build: ./build
|
||||||
|
image: maptool:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
- "4567:4567"
|
- "4567:4567"
|
||||||
@@ -20,9 +31,101 @@ services:
|
|||||||
# Optional: blank disables the LiveTrack webhook endpoint rather than
|
# Optional: blank disables the LiveTrack webhook endpoint rather than
|
||||||
# failing to start, since it's not needed for the app's core function.
|
# failing to start, since it's not needed for the app's core function.
|
||||||
LIVETRACK_WEBHOOK_SECRET: ${LIVETRACK_WEBHOOK_SECRET:-}
|
LIVETRACK_WEBHOOK_SECRET: ${LIVETRACK_WEBHOOK_SECRET:-}
|
||||||
|
# Optional, only read by data/plugins/p4n2gpx/'s own p4n2gpx.rb (not
|
||||||
|
# part of this repo — see docs/deployment.adoc) if that private
|
||||||
|
# plugin is present; blank just means it fails with its own "could
|
||||||
|
# not authenticate" error when run, same non-fatal-to-startup story
|
||||||
|
# as the other optional secrets here. P4N_USER/P4N_PASSWORD alone
|
||||||
|
# don't actually work here — park4night's login form is behind
|
||||||
|
# invisible reCAPTCHA, which no browserless login can satisfy — so
|
||||||
|
# P4N_SESSION_COOKIE (a real logged-in browser's session cookie,
|
||||||
|
# copied out by hand and refreshed whenever it expires) is what
|
||||||
|
# p4n2gpx.rb's make_session actually ends up using.
|
||||||
|
P4N_USER: ${P4N_USER:-}
|
||||||
|
P4N_PASSWORD: ${P4N_PASSWORD:-}
|
||||||
|
P4N_SESSION_COOKIE: ${P4N_SESSION_COOKIE:-}
|
||||||
|
# The kiwix service below, reached by its compose service name (both
|
||||||
|
# are on the same default compose network) — port matches its own
|
||||||
|
# --port=8080 in the command below. Not made conditional on the ZIM
|
||||||
|
# actually being present: GET /api/kiwix pings it and just reports
|
||||||
|
# unavailable if the container isn't up/reachable, same story as
|
||||||
|
# every other optional-local-file feature (offline.pmtiles, etc).
|
||||||
|
# Reached server-side only — production sits behind a reverse proxy
|
||||||
|
# that only forwards to maptool itself, so app.rb's GET /kiwix/*
|
||||||
|
# proxies to this instead of handing the browser this address.
|
||||||
|
KIWIX_URL: "http://kiwix:8080"
|
||||||
volumes:
|
volumes:
|
||||||
# Uploaded GPX files and the offline PMTiles extract — kept outside
|
# Uploaded GPX files and the offline PMTiles extract — kept outside
|
||||||
# the image so a rebuild/redeploy doesn't wipe them (see
|
# the image so a rebuild/redeploy doesn't wipe them (see
|
||||||
# docs/development.adoc for how the deploy job treats this
|
# docs/development.adoc for how the deploy job treats this
|
||||||
# directory).
|
# directory). A private plugin (app.rb's PLUGINS_DIRS) also belongs
|
||||||
- ./data:/app/data
|
# under here, at data/plugins/<name>/ — nothing further to mount,
|
||||||
|
# this one bind already covers the whole data/ tree. An offline.pmtiles
|
||||||
|
# build's own scratch work (Dir.mktmpdir, lib/maptool/pmtiles_builder.rb)
|
||||||
|
# happens outside this mount, in the container's own filesystem — a
|
||||||
|
# large country's .osm.pbf plus Planetiler's working set can be
|
||||||
|
# several GB, worth knowing if the host is ever short on space there.
|
||||||
|
# :Z (not :z — this path is only ever mounted into this one
|
||||||
|
# container) relabels the host directory for SELinux, since Podman
|
||||||
|
# on an SELinux-enforcing host otherwise denies the container write
|
||||||
|
# access to a bind-mounted directory regardless of Unix permissions
|
||||||
|
# — harmless/ignored on a host without SELinux.
|
||||||
|
- ./data:/app/data:Z
|
||||||
|
|
||||||
|
# Fakes a GNSS fix (a loop around Bratislava, see
|
||||||
|
# bin/gnss_simulator.rb/docs/development.adoc) since there's no real
|
||||||
|
# receiver attached here — the "GNSS" control would otherwise just show
|
||||||
|
# "no fix" always. Remove this service (and nothing else) whenever a real
|
||||||
|
# receiver/gpsd setup replaces it. network_mode ties it to maptool's own
|
||||||
|
# network namespace rather than a bridge network, since GpsdClient
|
||||||
|
# connects to a hardcoded 127.0.0.1:2947 — same as gpsd itself normally
|
||||||
|
# being local to the machine it serves.
|
||||||
|
gnss-simulator:
|
||||||
|
image: maptool:latest
|
||||||
|
depends_on:
|
||||||
|
- maptool
|
||||||
|
restart: unless-stopped
|
||||||
|
network_mode: "service:maptool"
|
||||||
|
command: ["ruby", "bin/gnss_simulator.rb"]
|
||||||
|
|
||||||
|
# Serves an offline Wikipedia ZIM for the popup's "Nearby on Wikipedia"
|
||||||
|
# link (see docs/development.adoc for where to get one and drop it in —
|
||||||
|
# same "not part of the repo, place it under ./data locally" story as
|
||||||
|
# offline.pmtiles/terrain.pmtiles). On the default compose network (not
|
||||||
|
# network_mode: "service:maptool" — unlike gnss-simulator this doesn't
|
||||||
|
# need a hardcoded localhost port, KIWIX_URL above reaches it by this
|
||||||
|
# service's own name), so maptool and kiwix stay independent containers.
|
||||||
|
# Keeps restarting (harmlessly) if data/kiwix/wikipedia.zim isn't there
|
||||||
|
# yet — GET /api/kiwix just reports unavailable until it comes up.
|
||||||
|
#
|
||||||
|
# The volume binds the exact *file* path, not the data/kiwix/ directory —
|
||||||
|
# deliberately, so data/kiwix/wikipedia.zim can be a symlink to wherever
|
||||||
|
# the (large) real ZIM actually lives. A bind mount resolves a symlink at
|
||||||
|
# its *source* path once, at mount time (plain mount(2) semantics, same
|
||||||
|
# as Podman); a symlink sitting inside an already-mounted directory
|
||||||
|
# doesn't get that treatment — the container would have to resolve it
|
||||||
|
# itself, inside its own filesystem, where the host path it points to
|
||||||
|
# doesn't exist.
|
||||||
|
kiwix:
|
||||||
|
image: ghcr.io/kiwix/kiwix-serve:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
# No --port here: the image's own start.sh already prepends
|
||||||
|
# --port=8080 (its default) before appending this command array, so
|
||||||
|
# passing it again produced "Unexpected argument: --port=8080,
|
||||||
|
# --port=8080, /data/wikipedia.zim" and kiwix-serve refused to start.
|
||||||
|
#
|
||||||
|
# --urlRootLocation=/kiwix makes every link/asset kiwix-serve itself
|
||||||
|
# emits (search results, article pages, its CSS/JS) carry a /kiwix
|
||||||
|
# prefix, matching where app.rb's GET /kiwix/* proxies requests in
|
||||||
|
# from (confirmed against libkiwix's internalServer.cpp: it strips
|
||||||
|
# this same prefix off incoming request paths, so both ends agree on
|
||||||
|
# it) — no ports: mapping needed, nothing outside the compose network
|
||||||
|
# ever talks to this container directly.
|
||||||
|
command: ["--urlRootLocation=/kiwix", "/data/wikipedia.zim"]
|
||||||
|
volumes:
|
||||||
|
# :Z, same SELinux reasoning as maptool's own ./data mount above.
|
||||||
|
- ./data/kiwix/wikipedia.zim:/data/wikipedia.zim:Z
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
enable_ipv6: true
|
||||||
@@ -44,18 +44,33 @@ opened in a browser.
|
|||||||
|
|
||||||
Map data is vector, not raster: the app ships
|
Map data is vector, not raster: the app ships
|
||||||
https://maplibre.org[MapLibre GL JS] (vendored, see below) and the map
|
https://maplibre.org[MapLibre GL JS] (vendored, see below) and the map
|
||||||
view (`public/js/map.js`) offers two styles:
|
view (`public/js/map.js`) offers two styles, both **the same
|
||||||
|
OpenMapTiles-schema Liberty style** (https://tiles.openfreemap.org/styles/liberty[OpenFreeMap
|
||||||
|
"liberty"]), just pointed at different vector sources:
|
||||||
|
|
||||||
* *Online* — a full hosted vector style,
|
* *Online* — Liberty's own hosted tiles, `tiles.openfreemap.org`,
|
||||||
https://tiles.openfreemap.org/styles/liberty[OpenFreeMap "liberty"],
|
|
||||||
requires internet access.
|
requires internet access.
|
||||||
* *Offline* — built at runtime from a local
|
* *Offline* — a local https://github.com/protomaps/PMTiles[PMTiles]
|
||||||
https://github.com/protomaps/PMTiles[PMTiles] archive at
|
archive at `data/offline.pmtiles`, built by
|
||||||
`data/offline.pmtiles`, served as-is (see below) at
|
https://github.com/onthegomap/planetiler[Planetiler] from an OSM
|
||||||
`/tiles/offline.pmtiles`, decoded client-side by
|
extract (see `docs/development.adoc`), served as-is at
|
||||||
|
`/tiles/offline.pmtiles` and decoded client-side by
|
||||||
https://github.com/protomaps/PMTiles[pmtiles.js] registered as a
|
https://github.com/protomaps/PMTiles[pmtiles.js] registered as a
|
||||||
`pmtiles://` protocol handler, and styled with base layers from
|
`pmtiles://` protocol handler.
|
||||||
https://github.com/protomaps/basemaps[protomaps-themes-base].
|
|
||||||
|
Using the same schema both ways is deliberate: an earlier version of
|
||||||
|
this app built offline tiles from Protomaps' own basemap schema
|
||||||
|
instead, which needed a *separate* hand-tuned theme
|
||||||
|
(`protomaps-themes-base` + a since-removed `OFFLINE_THEME`) to
|
||||||
|
approximate the online look — two independently-maintained palettes
|
||||||
|
that drifted out of sync (building opacity, road casings, park
|
||||||
|
boundaries) every time one side changed. Planetiler's default profile
|
||||||
|
*is* OpenMapTiles, so `offlineStyle()` now just clones the same parsed
|
||||||
|
Liberty JSON `ONLINE_STYLE` already fetched, repoints its `sources`,
|
||||||
|
`glyphs`, and `sprite` at local copies, and applies the exact same
|
||||||
|
`ONLINE_STYLE_PATCH` recolor (below) — one palette, one place to tune
|
||||||
|
it, correct on both sides by construction rather than by re-checking
|
||||||
|
every time something changes.
|
||||||
|
|
||||||
`GET /api/offline-tiles` reports whether `data/offline.pmtiles` is
|
`GET /api/offline-tiles` reports whether `data/offline.pmtiles` is
|
||||||
present (`{"available": true|false}`); the frontend uses it once, on
|
present (`{"available": true|false}`); the frontend uses it once, on
|
||||||
@@ -72,28 +87,35 @@ support) just returns byte ranges of the file; pmtiles.js in the
|
|||||||
browser reads the archive's own directory and requests only the bytes
|
browser reads the archive's own directory and requests only the bytes
|
||||||
it needs for the tiles on screen.
|
it needs for the tiles on screen.
|
||||||
|
|
||||||
=== Offline text labels and color palette
|
No PMTiles file ships with the repository — it is built locally or
|
||||||
|
in-app (see `docs/development.adoc`) and stays gitignored because such
|
||||||
|
extracts are typically large binary files. Geofabrik's per-country
|
||||||
|
extracts are OSM data, ODbL-licensed, same attribution
|
||||||
|
(`© OpenStreetMap contributors`) the map already shows either way — no
|
||||||
|
separate licensing story the way the old Protomaps-basemap pipeline's
|
||||||
|
NPS-Park-Tiles color research needed.
|
||||||
|
|
||||||
|
=== Offline text labels, sprite, and color palette
|
||||||
|
|
||||||
Label rendering in MapLibre needs a "glyphs" server serving font PBF
|
Label rendering in MapLibre needs a "glyphs" server serving font PBF
|
||||||
ranges (256 Unicode code points each); `data/offline.pmtiles`'s fields
|
ranges (256 Unicode code points each). The glyphs Liberty's style
|
||||||
(`name`, `name:en`, `script`, ...) match what
|
references are vendored under `public/vendor/fonts/` — Noto Sans
|
||||||
`protomaps-themes-base`'s `layers(source, theme, { lang: "en" })`
|
Regular/Italic/Medium, ranges `0-255` and `256-511` (Basic Latin +
|
||||||
expects for its label layers (verified against the actual PMTiles
|
Latin-1 Supplement + Latin Extended-A, SIL OFL licensed, from
|
||||||
vector layer schema), so passing that option turns labels on. The
|
|
||||||
glyphs themselves are vendored under `public/vendor/fonts/` — Noto
|
|
||||||
Sans Regular/Italic/Medium, ranges `0-255` and `256-511`
|
|
||||||
(Basic Latin + Latin-1 Supplement + Latin Extended-A, SIL OFL
|
|
||||||
licensed, from
|
|
||||||
https://github.com/protomaps/basemaps-assets[protomaps/basemaps-assets]),
|
https://github.com/protomaps/basemaps-assets[protomaps/basemaps-assets]),
|
||||||
covering accented Latin place names (Slovak, Czech, Polish, German,
|
covering accented Latin place names (Slovak, Czech, Polish, German,
|
||||||
...). Labels in other scripts (Cyrillic, Greek, CJK, ...) won't render
|
...). Labels in other scripts (Cyrillic, Greek, CJK, ...) won't render
|
||||||
offline, since those glyph ranges aren't vendored.
|
offline, since those glyph ranges aren't vendored. Liberty's icon
|
||||||
|
sprite (shields, POI icons) is likewise vendored as a one-time
|
||||||
|
snapshot under `public/vendor/liberty-sprite/` — both `offlineStyle()`
|
||||||
|
and the online style otherwise share identical `glyphs`/`sprite`
|
||||||
|
roles, just resolved to local vs. `tiles.openfreemap.org` URLs.
|
||||||
|
|
||||||
`public/js/map.js`'s `OFFLINE_THEME` nudges a couple dozen color roles
|
`public/js/map.js`'s `ONLINE_STYLE_PATCH` — applied to whichever
|
||||||
from `protomaps-themes-base`'s stock "light" theme towards the
|
source is loaded, online or offline (see above) — nudges Liberty's
|
||||||
National Park Service's own "Park Tiles" cartography — a warm,
|
OpenMapTiles layer ids towards a warm, National Park Service
|
||||||
Harpers-Ferry-Center-derived look. A few of these values are the
|
"Park Tiles"-inspired look. A few of these values are the genuine
|
||||||
genuine paint colors pulled from a public Park Tiles style JSON
|
paint colors pulled from a public Park Tiles style JSON
|
||||||
(`nps.gov/lib/npmap.js/style/ck72fwp2642dv07o7tbqinvz4.json`, the
|
(`nps.gov/lib/npmap.js/style/ck72fwp2642dv07o7tbqinvz4.json`, the
|
||||||
"Imagery" variant — the only one of NPS's four styles served from a
|
"Imagery" variant — the only one of NPS's four styles served from a
|
||||||
plain, unauthenticated URL; "Standard"/"Slate"/"Light" resolve through
|
plain, unauthenticated URL; "Standard"/"Slate"/"Light" resolve through
|
||||||
@@ -101,50 +123,36 @@ Mapbox Studio's private API with an access token, which isn't
|
|||||||
something to piggyback on even though it's technically visible in
|
something to piggyback on even though it's technically visible in
|
||||||
their own JS bundle) — background, the park/protected-area green, the
|
their own JS bundle) — background, the park/protected-area green, the
|
||||||
two road-importance tiers, and the park-boundary line color. The rest
|
two road-importance tiers, and the park-boundary line color. The rest
|
||||||
are reasoned to match that same warm family (olive greens, muted blue)
|
are reasoned to match that same warm family (olive greens, muted blue,
|
||||||
rather than sourced. This is deliberately partial — labels, casings,
|
brown road casings) rather than sourced. This is deliberately
|
||||||
and roles with no clean real-color anchor are left at theme
|
partial — labels, halos, rail, and minor overlays (cemetery/hospital/
|
||||||
defaults — not the kind of full per-layer recolor a previous iteration
|
school/pitch/track shading) are left at Liberty's own defaults.
|
||||||
of this app did (and removed, along with any dependency on OsmAnd's
|
|
||||||
own tile server, which this app cannot use per OsmAnd's terms
|
|
||||||
restricting tile use to "personal, non-commercial use within OsmAnd
|
|
||||||
applications and platforms").
|
|
||||||
+
|
+
|
||||||
Recreating the *color choices* is fine — that's not the kind of thing
|
Recreating the *color choices* is fine — that's not the kind of thing
|
||||||
a data license restricts — but the actual Park Tiles *service* stays
|
a data license restricts — but the actual Park Tiles *service* stays
|
||||||
off-limits: its OSM/terrain layers are licensed through Mapbox Atlas
|
off-limits: its OSM/terrain layers are licensed through Mapbox Atlas
|
||||||
(a commercial, non-sublicensable data service scoped to NPS), and it's
|
(a commercial, non-sublicensable data service scoped to NPS), and it's
|
||||||
built for park visitors specifically (park-unit and trailhead
|
built for park visitors specifically (park-unit and trailhead
|
||||||
labeling prioritized), not general-purpose worldwide cartography.
|
labeling prioritized), not general-purpose worldwide cartography —
|
||||||
|
irrelevant to this app either way now, since it never talks to NPS's
|
||||||
|
tile service, only ever OpenFreeMap/Geofabrik.
|
||||||
|
|
||||||
No PMTiles file ships with the repository — it is expected to be
|
`loadOnlineStyle` (`public/js/map.js`) fetches Liberty's live
|
||||||
supplied locally (see `docs/development.adoc`) and stays gitignored
|
`style.json` once, patches it in place, and uses the resulting object
|
||||||
because such extracts are typically large binary files.
|
everywhere `ONLINE_STYLE` used to be a plain URL string — MapLibre
|
||||||
|
accepts a style object anywhere it accepts a style URL, so
|
||||||
The Online map (OpenFreeMap Liberty) gets the same treatment via
|
`applyStyle`'s `map.setStyle(... : ONLINE_STYLE)` needed no change.
|
||||||
`ONLINE_STYLE_PATCH`, a separate hand-built table — Liberty is an
|
That fetch runs before the map (and the rest of the page) is
|
||||||
OpenMapTiles-schema style, with its own layer ids (`landcover_wood`,
|
constructed — a top-level `await` — so there's no flash of Liberty's
|
||||||
`road_secondary_tertiary`, `boundary_2`, ...) that don't correspond to
|
own colors before the patch lands; a 3s timeout and a catch-all fall
|
||||||
protomaps' theme roles, so the two palettes can't share one object,
|
back to the plain URL (letting MapLibre fetch and render it unpatched)
|
||||||
only the same underlying color choices. `loadOnlineStyle`
|
rather than block the whole page's startup on a remote style server.
|
||||||
(`public/js/map.js`) fetches Liberty's `style.json` once, patches it
|
Offline mode can't rely on that same live fetch succeeding — the field
|
||||||
in place, and uses the resulting object everywhere `ONLINE_STYLE` used
|
usage this app is built for is exactly when it won't — so
|
||||||
to be a plain URL string — MapLibre accepts a style object anywhere it
|
`offlineStyle()` instead clones a vendored, point-in-time snapshot of
|
||||||
accepts a style URL, so `applyStyle`'s `map.setStyle(... : ONLINE_STYLE)`
|
the same Liberty JSON (`public/vendor/liberty-style/liberty.json`),
|
||||||
needed no change. That fetch runs before the map (and the rest of the
|
patches it the same way, and repoints it at the local PMTiles/glyphs/
|
||||||
page) is constructed — a top-level `await` — so there's no flash of
|
sprite described above.
|
||||||
Liberty's own colors before the patch lands; a 3s timeout and a
|
|
||||||
catch-all fall back to the plain URL (letting MapLibre fetch and
|
|
||||||
render it unpatched) rather than block the whole page's startup on a
|
|
||||||
remote style server, since this app is meant to keep working offline
|
|
||||||
in the field, independent of whether the online map itself loads.
|
|
||||||
Only the layers that dominate the map's overall color read are
|
|
||||||
patched (land cover, water, parks, roads, boundaries, buildings) —
|
|
||||||
same deliberately-partial scope as `OFFLINE_THEME`, not a full
|
|
||||||
per-layer recolor. Road casings are the one addition with no
|
|
||||||
protomaps-role equivalent to reuse, since offline roads don't get a
|
|
||||||
separate outline color there; `#c9a66b` is reasoned to fit the family
|
|
||||||
rather than sourced from anywhere real.
|
|
||||||
|
|
||||||
== Map permalink
|
== Map permalink
|
||||||
|
|
||||||
@@ -166,8 +174,8 @@ sits top-left on the map. It fits to `SLOVAKIA_BOUNDS` (a country-wide
|
|||||||
bounding box) rather than the plain `CENTER`/`ZOOM` point the map
|
bounding box) rather than the plain `CENTER`/`ZOOM` point the map
|
||||||
itself starts at — "home" is meant as a full-country overview, not a
|
itself starts at — "home" is meant as a full-country overview, not a
|
||||||
re-centering on Bratislava. It resets to that fixed default view, not
|
re-centering on Bratislava. It resets to that fixed default view, not
|
||||||
the visitor's own location — that's `GeolocateControl`, a separate
|
the visitor's own location — that's `LocateControl`, a separate button
|
||||||
button in the same stack (below).
|
in the same stack (below).
|
||||||
|
|
||||||
That Home button is the top of a small top-left stack — Home, zoom
|
That Home button is the top of a small top-left stack — Home, zoom
|
||||||
in/out (`NavigationControl`, `showCompass: false` since this map never
|
in/out (`NavigationControl`, `showCompass: false` since this map never
|
||||||
@@ -175,11 +183,12 @@ rotates), a passive zoom-level readout (`ZoomLevelControl`, a plain div
|
|||||||
updated on the map's `zoom` event — not a button, since it has no
|
updated on the map's `zoom` event — not a button, since it has no
|
||||||
click behavior of its own), a distance/area measuring tool
|
click behavior of its own), a distance/area measuring tool
|
||||||
(`MeasureControl`, `public/js/measure.js`), locate-me
|
(`MeasureControl`, `public/js/measure.js`), locate-me
|
||||||
(`GeolocateControl`), and a "Base map" button — matching the National
|
(`LocateControl`, `public/js/locate.js`), and a "Base map" button —
|
||||||
Park Service's own map control layout (grouped together on the map
|
matching the National Park Service's own map control layout (grouped
|
||||||
itself, rather than split across corners or between the map and a side
|
together on the map itself, rather than split across corners or
|
||||||
panel). The last of those, `MapLayersButton` (`public/js/map.js`), is
|
between the map and a side panel). The last of those,
|
||||||
a second entry point into the exact same Online/Offline panel
|
`MapLayersButton` (`public/js/map.js`), is a second entry point into
|
||||||
|
the exact same Online/Offline panel
|
||||||
`LayerToggleControl` already manages in the page's own sidebar — both
|
`LayerToggleControl` already manages in the page's own sidebar — both
|
||||||
call the same `LayerToggleControl#toggle()`, so there's one source of
|
call the same `LayerToggleControl#toggle()`, so there's one source of
|
||||||
truth for the panel's open/closed state either way.
|
truth for the panel's open/closed state either way.
|
||||||
@@ -220,6 +229,62 @@ long any one leg was on its own. Area mode has no equivalent yet;
|
|||||||
a polygon's edges are its perimeter, not a path, so the same treatment
|
a polygon's edges are its perimeter, not a path, so the same treatment
|
||||||
may not even read the same way there.
|
may not even read the same way there.
|
||||||
|
|
||||||
|
=== Finding your location
|
||||||
|
|
||||||
|
`LocateControl` (`public/js/locate.js`) replaces MapLibre's own
|
||||||
|
`GeolocateControl` — same button, reusing its exact CSS classes/icon
|
||||||
|
(`.maplibregl-ctrl-geolocate`) rather than a new one, so it's a visual
|
||||||
|
drop-in — but sourced differently. This app has its own GNSS receiver
|
||||||
|
(gpsd, possibly `bin/gnss_simulator.rb`'s simulated one — see
|
||||||
|
`docs/development.adoc`/`docs/deployment.adoc`), and "find my
|
||||||
|
location" should mean that receiver, not a device's separate (and
|
||||||
|
often less accurate, e.g. wifi-based) location service — but only
|
||||||
|
once it's actually turned on, via the sidebar's "GNSS" icon
|
||||||
|
(`GnssControl`, `public/js/gnss.js`), not just whenever a receiver
|
||||||
|
happens to have a fix. `GnssControl` itself now recenters the map on
|
||||||
|
every fix while enabled (`flyTo` for the first one, `easeTo` — a
|
||||||
|
plainer follow, no zoom swoop — for each update after), the same way
|
||||||
|
LocateControl's own browser-geolocation fallback follows below;
|
||||||
|
clicking Locate while GNSS is already on just jumps to
|
||||||
|
`GnssControl#getPosition()`'s current fix for the same instant
|
||||||
|
feedback the button has always given, reusing its existing
|
||||||
|
`.gnss-marker` rather than placing a second one — GNSS following the
|
||||||
|
map is `GnssControl`'s job for as long as it's enabled, regardless of
|
||||||
|
whether Locate was ever clicked. Both controls are constructed
|
||||||
|
together in `map.js` (`GnssControl` first, `LocateControl` holding
|
||||||
|
that same instance) specifically so `LocateControl` can call
|
||||||
|
`isEnabled()`/`getPosition()` on it. Only when GNSS is off does
|
||||||
|
Locate fall back to the browser's own
|
||||||
|
`navigator.geolocation.watchPosition` — GeolocateControl's own
|
||||||
|
mechanism — with its own toggle/marker/recentering-per-fix, since
|
||||||
|
that's the one case this control does own the tracking itself;
|
||||||
|
clicking again in that case stops the watch and removes the marker.
|
||||||
|
Deliberately *not* `.gnss-marker` (`GnssControl`'s green dot) for that
|
||||||
|
marker — reusing it for a browser-estimated position too made it look
|
||||||
|
like this control was somehow still using GNSS data even when it very
|
||||||
|
much wasn't (a real report from testing this: a browser's own
|
||||||
|
location, wifi/IP-estimated on a machine with no GPS chip, can jump
|
||||||
|
around noticeably between updates, and a green dot doing that reads
|
||||||
|
as "the GNSS simulator, leaking somehow"). Uses MapLibre's own blue
|
||||||
|
pulsing `.maplibregl-user-location-dot` (`maplibre-gl.css`) instead —
|
||||||
|
the same look GeolocateControl itself had here before, and visually
|
||||||
|
distinct from GNSS's green on purpose.
|
||||||
|
+
|
||||||
|
GNSS turning on *while* the browser fallback is already mid-track
|
||||||
|
switches over immediately rather than waiting for another click —
|
||||||
|
`GnssControl#onEnabledChange` (a small internal callback list, not a
|
||||||
|
full pub/sub system — nothing else needs one) notifies `LocateControl`
|
||||||
|
the moment the sidebar toggle changes; if it was actively tracking via
|
||||||
|
the browser at that point, it stops that and jumps to GNSS's position
|
||||||
|
instead. That jump doesn't alert if GNSS has no fix yet (a
|
||||||
|
`{ alertIfNoFix }` option on the shared jump-to-GNSS helper, `true`
|
||||||
|
only for a deliberate click) — `GnssControl` only just turned on and
|
||||||
|
almost certainly hasn't gotten its first poll response back yet at
|
||||||
|
the exact moment this fires, so alerting here would pop a blocking
|
||||||
|
dialog (window.alert() halts the whole page until dismissed) on every
|
||||||
|
single enable; its own next poll shows/recenters once it actually has
|
||||||
|
something, same as it would either way.
|
||||||
|
|
||||||
=== Debug inspector (Alt+click)
|
=== Debug inspector (Alt+click)
|
||||||
|
|
||||||
Alt+click anywhere on the map opens a popup listing every rendered
|
Alt+click anywhere on the map opens a popup listing every rendered
|
||||||
@@ -338,34 +403,37 @@ app, and not yet opened. Badging the common case would just be noise,
|
|||||||
so the file tree's icon (`PENDING_ICON`, `public/js/icons.js`) marks
|
so the file tree's icon (`PENDING_ICON`, `public/js/icons.js`) marks
|
||||||
that exception instead of the (near-universal) ready state.
|
that exception instead of the (near-universal) ready state.
|
||||||
+
|
+
|
||||||
Once a `.gpx`/`.fit` is cached, the tree shows it under its converted
|
The tree always shows a `.gpx`/`.fit` file under its real, as-uploaded
|
||||||
name instead — `"ride.gpx"` reads as `"ride.gpx.geojson"`, matching
|
name, even once cached — it briefly grew a `.geojson` suffix there
|
||||||
the real cache file sitting next to it on disk — since the GeoJSON,
|
(matching the real cache file's name on disk) back when the name
|
||||||
not the original, is what's actually rendered on the map from that
|
doubled as a GeoJSON-only download link and the suffix explained what
|
||||||
point on. The original is never deleted or re-parsed once cached, just
|
clicking it would get you; now that downloading is the download
|
||||||
not re-shown as itself, so both forms need a way out of the app as an
|
button's job (below), the suffix was just confusing; the file itself
|
||||||
actual file: the file name itself (`GET /api/gpx/file/<path>?download=geojson`)
|
is still named `"ride.gpx"`. The original is never deleted or
|
||||||
is a real link, not a `<span>` — clicking it downloads the GeoJSON
|
re-parsed once cached, just not re-shown as itself once the GeoJSON
|
||||||
rather than also toggling the row's checkbox, since a native
|
becomes what's actually rendered on the map, so both forms need a way
|
||||||
`<a>`/`<button>` nested inside a `<label>` claims its own click
|
out of the app as an actual file: one download button, next to
|
||||||
instead of the label forwarding it to the associated control — and a
|
delete, opens a small menu ("Original file" / "GeoJSON",
|
||||||
second button next to delete (`?download=original`) downloads the
|
`_downloadButton`/`_downloadMenuItem`). Only one menu open at a time
|
||||||
untouched, as-uploaded file, since the name alone only covers half of
|
(`_toggleDownloadMenu`/`_closeDownloadMenu`), closed by a
|
||||||
what's now downloadable and isn't an obvious click target regardless.
|
document-level click listener the same way an outside click closes a
|
||||||
A directly-uploaded `.geojson` keeps its own name and both buttons
|
`sidebar-flyout` dropdown, just scoped to this one control rather than
|
||||||
download the same (identical) content — redundant there, not wrong.
|
threaded through `onlyOneSidePanelOpen`. A directly-uploaded `.geojson`
|
||||||
Both kinds of download require login, same as upload/move/delete/
|
keeps its own name and both menu items download the same (identical)
|
||||||
create-folder — `GET /api/gpx/file` itself stays open to anyone
|
content — redundant there, not wrong. Both downloads (`GET
|
||||||
(viewing a file on the map isn't a write action), just not
|
/api/gpx/file/<path>?download=original`/`?download=geojson`) require
|
||||||
`?download=...` on it. Since both links are real `<a href>`s (so
|
login, same as upload/move/delete/create-folder, so the whole button —
|
||||||
right-click/middle-click still do something sensible), a logged-out
|
not just its menu items — is left out of the DOM when logged out
|
||||||
click can't just be a plain navigation the way delete/upload already
|
rather than shown and failing on click (see "Authentication" below);
|
||||||
aren't — `_download` (`gpx.js`) fetches instead, checks for a 401 the
|
`GET /api/gpx/file` itself stays open to anyone otherwise (viewing a
|
||||||
same way `redirectIfUnauthorized` already does for every other write
|
file on the map isn't a write action), just not `?download=...` on it.
|
||||||
action, and only then turns the response into a blob and clicks a
|
Each menu item is a real `<a href>` (so right-click/middle-click still
|
||||||
throwaway `<a download>` to trigger the actual save — a plain
|
do something sensible), but a plain navigation would land a logged-out
|
||||||
navigation would otherwise land on a raw JSON error page instead of
|
click on a raw JSON error page instead of being sent to log in —
|
||||||
being sent to log in. `Gpx` parses the GPX with REXML: `<wpt>`
|
`_download` (`gpx.js`) fetches instead, checks for a 401 the same way
|
||||||
|
`redirectIfUnauthorized` already does for every other write action,
|
||||||
|
and only then turns the response into a blob and clicks a throwaway
|
||||||
|
`<a download>` to trigger the actual save. `Gpx` parses the GPX with REXML: `<wpt>`
|
||||||
waypoints become `Point` features (with `name`, `desc` and `time`
|
waypoints become `Point` features (with `name`, `desc` and `time`
|
||||||
carried through as properties), each `<trk>` becomes one
|
carried through as properties), each `<trk>` becomes one
|
||||||
`MultiLineString` feature (one line per `<trkseg>`), and each `<rte>`
|
`MultiLineString` feature (one line per `<trkseg>`), and each `<rte>`
|
||||||
@@ -638,9 +706,10 @@ toggling is a cheap re-render of just that strip.
|
|||||||
|
|
||||||
A "GNSS" map control (`public/js/gnss.js`) shows the fix from a local
|
A "GNSS" map control (`public/js/gnss.js`) shows the fix from a local
|
||||||
GNSS receiver (a real one, plugged into the machine running the
|
GNSS receiver (a real one, plugged into the machine running the
|
||||||
server — distinct from `NavigationControl`'s browser
|
server, or `bin/gnss_simulator.rb`'s simulated one — distinct from
|
||||||
`navigator.geolocation`, which the OS may or may not derive from such
|
`LocateControl`'s browser-geolocation fallback, `navigator.geolocation`,
|
||||||
a receiver at all).
|
which the OS may or may not derive from such a receiver at all —
|
||||||
|
see "Finding your location" above).
|
||||||
|
|
||||||
`lib/maptool/gpsd_client.rb` is a small client for
|
`lib/maptool/gpsd_client.rb` is a small client for
|
||||||
https://gpsd.io[gpsd]'s JSON protocol over TCP (port 2947 by default):
|
https://gpsd.io[gpsd]'s JSON protocol over TCP (port 2947 by default):
|
||||||
@@ -700,6 +769,20 @@ threading a separate flag through, since that's already there and
|
|||||||
login state can't change without a full page reload anyway. Delete
|
login state can't change without a full page reload anyway. Delete
|
||||||
exists on both file and folder rows — both are gated, easy to miss
|
exists on both file and folder rows — both are gated, easy to miss
|
||||||
one and only catch it by testing an actually-logged-out session.
|
one and only catch it by testing an actually-logged-out session.
|
||||||
|
`isLoggedIn()` itself lives in `public/js/auth.js` — shared with
|
||||||
|
`GnssControl` (below), the only other place gating on login.
|
||||||
|
|
||||||
|
`GET /api/gnss/position` requires login too, same reasoning — it
|
||||||
|
reveals where the receiver physically is, not something to leave open
|
||||||
|
to anyone with the URL. `GnssControl`'s sidebar icon is hidden
|
||||||
|
entirely when logged out (`isLoggedIn()`, same pattern as the tree
|
||||||
|
UI's write controls above) rather than shown and failing to ever get
|
||||||
|
a fix. `LocateControl` needs no gating of its own here: it only ever
|
||||||
|
reaches GNSS through `GnssControl#isEnabled()`, which can never turn
|
||||||
|
true for a logged-out visitor if the toggle that flips it is never on
|
||||||
|
the page to click — its browser-geolocation fallback stays open to
|
||||||
|
everyone, since that's a visitor's own device asking for their own
|
||||||
|
location, nothing to gate.
|
||||||
|
|
||||||
Login delegates to the Gitea instance at `git.priesol.net` via OAuth2
|
Login delegates to the Gitea instance at `git.priesol.net` via OAuth2
|
||||||
(`GET /login` redirects to Gitea's authorize endpoint, `GET
|
(`GET /login` redirects to Gitea's authorize endpoint, `GET
|
||||||
|
|||||||
@@ -2,15 +2,26 @@
|
|||||||
:toc:
|
:toc:
|
||||||
:toclevels: 2
|
:toclevels: 2
|
||||||
|
|
||||||
MapTool deploys as a Docker container on the same server the Gitea Actions
|
MapTool deploys as a Podman container (via `podman compose`, driven by a
|
||||||
runners run on. Two separate runners are registered against the Gitea
|
`podman-compose@.service` systemd template — see below) on the same server
|
||||||
instance:
|
the Gitea Actions runners run on. Two separate runners are registered
|
||||||
|
against the Gitea instance:
|
||||||
|
|
||||||
* The existing Docker-based runner (`gitea/runner:3`), labels
|
* The existing Docker-based runner (`gitea/runner:3`), label
|
||||||
`ubuntu-latest`/`ubuntu-24.04`/`ubuntu-22.04` — runs the `check` job
|
`brick-build` — runs the `check` and `renovate` jobs in a fresh,
|
||||||
in a fresh, isolated container per job, same as before.
|
isolated container per job. Every label on this runner (including
|
||||||
|
`ubuntu-latest`/`ubuntu-24.04`/`ubuntu-22.04`, still registered but no
|
||||||
|
longer what `check`/`renovate` pin to) now maps to Gitea's own
|
||||||
|
official runner image (`docker.gitea.com/runner-images:ubuntu-latest`,
|
||||||
|
has Node out of the box) — an earlier `opensuse/leap:16.0` mapping for
|
||||||
|
`brick-build` specifically had no Node, which every step in
|
||||||
|
`check`/`renovate` needs (they're JS-based Actions), and briefly broke
|
||||||
|
both jobs (2026-09-12) until reverted to the shared `ubuntu-latest`
|
||||||
|
label; now that the image itself is fixed, both are pinned back to
|
||||||
|
`brick-build` — this machine specifically, not whichever runner
|
||||||
|
happens to answer to the shared label (e.g. "Gitea Runner on Cube").
|
||||||
* A dedicated native runner, installed directly on the host (no Docker
|
* A dedicated native runner, installed directly on the host (no Docker
|
||||||
around it), label `cube-host` — runs only the `deploy` job.
|
around it), label `brick-host` — runs only the `deploy` job.
|
||||||
|
|
||||||
They're separate because the Docker-based runner's jobs (whether in
|
They're separate because the Docker-based runner's jobs (whether in
|
||||||
its usual isolated per-job containers, or tried directly inside its
|
its usual isolated per-job containers, or tried directly inside its
|
||||||
@@ -24,14 +35,48 @@ whatever's installed on the server.
|
|||||||
|
|
||||||
== Layout on the server
|
== Layout on the server
|
||||||
|
|
||||||
`/srv/docker/maptool/` holds:
|
`/srv/podman/maptool/` (`/srv/podman` is a symlink to `/srv/docker`, a
|
||||||
|
leftover from before the Docker → Podman migration — kept around so
|
||||||
|
existing paths/tooling that still assume `/srv/docker` keep working;
|
||||||
|
expect this to flip the other way, `/srv/docker` symlinked to
|
||||||
|
`/srv/podman`, once nothing depends on the old name anymore) holds:
|
||||||
|
|
||||||
* A checkout of this repository (kept in sync by the deploy job, see
|
* `build/` — a checkout of this repository (kept in sync by the
|
||||||
below) — `Dockerfile`, `docker-compose.yaml`, application code.
|
deploy job, see below), wiped and fully replaced every deploy —
|
||||||
* `data/`, *not* part of that sync — uploaded GPX files and
|
`Dockerfile`, `docker-compose.yaml`, application code.
|
||||||
`offline.pmtiles`. Persisted via a bind mount (`./data:/app/data` in
|
* `docker-compose.yaml` — a relative symlink to `build/docker-compose.yaml`,
|
||||||
`docker-compose.yaml`), so a redeploy (`docker compose up --build`)
|
purely so it's where a human poking around expects to find it, at
|
||||||
never touches it.
|
the top rather than a level down. Also what makes the layout work at
|
||||||
|
all: `podman compose`/`docker compose` resolve a relative volume
|
||||||
|
path (`./data` below) against the *symlink's own* directory, not
|
||||||
|
where it points — confirmed against plain `docker-compose config`,
|
||||||
|
not assumed — so `./data:/app/data` in the compose file keeps
|
||||||
|
meaning `/srv/podman/maptool/data`, and
|
||||||
|
`podman-compose@.service`'s `WorkingDirectory=/srv/podman/maptool`
|
||||||
|
needs no change either.
|
||||||
|
* `data/`, *not* part of the checkout sync (nothing in `build/`
|
||||||
|
references it, and the sync step doesn't need to special-case it
|
||||||
|
the way an earlier version of this layout did) — uploaded GPX
|
||||||
|
files, `offline.pmtiles`, `terrain.pmtiles`, and
|
||||||
|
`kiwix/wikipedia.zim` (see `docs/development.adoc` for each).
|
||||||
|
Persisted via that bind mount, so a redeploy never touches it.
|
||||||
|
Also where a private import plugin belongs (`data/plugins/<name>/`,
|
||||||
|
see `lib/maptool/plugins.rb`'s `PLUGINS_DIRS`) — `build/plugins/`
|
||||||
|
only holds what's checked into this public repo, and gets wiped and
|
||||||
|
replaced by the checkout sync on every deploy same as the rest of
|
||||||
|
`build/`.
|
||||||
|
`data/` (and the kiwix ZIM path) carries a `:Z` SELinux relabel in
|
||||||
|
`docker-compose.yaml`'s bind mounts — without it, Podman on an
|
||||||
|
SELinux-enforcing host denies the container write access to the
|
||||||
|
bind-mounted directory regardless of its Unix permissions (surfaces
|
||||||
|
as a plain `Permission denied` in the app's own logs, nothing
|
||||||
|
SELinux-labeled unless you go looking in `audit.log`); harmless/
|
||||||
|
ignored on a host where SELinux isn't enforcing.
|
||||||
|
* `.env` — regenerated every deploy (see below), lives here rather
|
||||||
|
than in `build/` for the same reason as the symlink: `docker
|
||||||
|
compose`/`podman compose` auto-load `.env` from the compose file's
|
||||||
|
own directory (as invoked, i.e. following the symlink's location,
|
||||||
|
same story as the volume path above).
|
||||||
|
|
||||||
== The native deploy runner
|
== The native deploy runner
|
||||||
|
|
||||||
@@ -40,8 +85,14 @@ https://docs.gitea.com/runner/installation/binary/), running as a
|
|||||||
systemd service (`gitea-runner.service`) under a dedicated
|
systemd service (`gitea-runner.service`) under a dedicated
|
||||||
`gitea-runner` system user, which needs:
|
`gitea-runner` system user, which needs:
|
||||||
|
|
||||||
* Docker access (member of the `docker` group).
|
* Passwordless `sudo` for managing any `podman-compose@` instance —
|
||||||
* Write access to `/srv/docker/maptool`.
|
`/etc/sudoers.d/gitea-runner-podman` grants `NOPASSWD` on
|
||||||
|
`systemctl {start,stop,restart} podman-compose@*.service` and
|
||||||
|
`podman rmi -f *` (root-owned systemd units, same idea as the
|
||||||
|
`docker` group membership gitea-runner already has for
|
||||||
|
`docker-compose@` services, just scoped explicitly since Podman has
|
||||||
|
no daemon/socket-group equivalent to grant blanket access through).
|
||||||
|
* Write access to `/srv/podman/maptool`.
|
||||||
|
|
||||||
Its `config.yaml` (`/etc/gitea-runner/config.yaml`) declares a single
|
Its `config.yaml` (`/etc/gitea-runner/config.yaml`) declares a single
|
||||||
label:
|
label:
|
||||||
@@ -50,7 +101,7 @@ label:
|
|||||||
----
|
----
|
||||||
runner:
|
runner:
|
||||||
labels:
|
labels:
|
||||||
- "cube-host"
|
- "brick-host"
|
||||||
----
|
----
|
||||||
|
|
||||||
The `host` scheme means jobs run directly via the runner process's own
|
The `host` scheme means jobs run directly via the runner process's own
|
||||||
@@ -61,20 +112,22 @@ purpose deploy runner needs.
|
|||||||
|
|
||||||
`config/puma.rb` binds to `127.0.0.1` by default (see
|
`config/puma.rb` binds to `127.0.0.1` by default (see
|
||||||
`docs/architecture.adoc`) — fine for local dev, but inside a container
|
`docs/architecture.adoc`) — fine for local dev, but inside a container
|
||||||
that would make the app unreachable through Docker's published port
|
that would make the app unreachable through the published port
|
||||||
(loopback is scoped to the container's own network namespace).
|
(loopback is scoped to the container's own network namespace).
|
||||||
`docker-compose.yaml` sets `BIND_HOST=0.0.0.0` to override that for
|
`docker-compose.yaml` sets `BIND_HOST="[::]"` to override that for the
|
||||||
the deployed container specifically; Docker's own port publishing
|
deployed container specifically — the IPv6 wildcard, which (dual-stack
|
||||||
(`ports:` in the compose file) is the network boundary there instead
|
being the Linux default) also accepts IPv4 connections, covering both
|
||||||
of Puma's bind address.
|
with a single listener; the compose file's own port publishing
|
||||||
|
(`ports:`) is the network boundary there instead of Puma's bind
|
||||||
|
address.
|
||||||
|
|
||||||
== Secrets (`.env`)
|
== Secrets (`.env`)
|
||||||
|
|
||||||
`docker-compose.yaml` reads `SESSION_SECRET`, `GITEA_OAUTH_CLIENT_ID`,
|
`docker-compose.yaml` reads `SESSION_SECRET`, `GITEA_OAUTH_CLIENT_ID`,
|
||||||
`GITEA_OAUTH_CLIENT_SECRET` and `MAPTOOL_ALLOWED_GITEA_USER` from the
|
`GITEA_OAUTH_CLIENT_SECRET` and `MAPTOOL_ALLOWED_GITEA_USER` from the
|
||||||
environment (see `docs/architecture.adoc` for what they're for) —
|
environment (see `docs/architecture.adoc` for what they're for) —
|
||||||
docker compose loads these automatically from a `.env` file next to
|
`podman compose` loads these automatically from a `.env` file next to
|
||||||
`docker-compose.yaml`, i.e. `/srv/docker/maptool/.env`. That file is
|
`docker-compose.yaml`, i.e. `/srv/podman/maptool/.env`. That file is
|
||||||
gitignored and dockerignored on purpose: nothing here ever puts
|
gitignored and dockerignored on purpose: nothing here ever puts
|
||||||
secrets in the repository or a built image layer.
|
secrets in the repository or a built image layer.
|
||||||
|
|
||||||
@@ -107,6 +160,18 @@ id/secret to store as the two `OAUTH_CLIENT_*` secrets above.
|
|||||||
usernames, e.g. `randy,friend1,friend2` — not their emails, the
|
usernames, e.g. `randy,friend1,friend2` — not their emails, the
|
||||||
`login` field from Gitea's `/api/v1/user`.
|
`login` field from Gitea's `/api/v1/user`.
|
||||||
|
|
||||||
|
== GNSS simulator
|
||||||
|
|
||||||
|
`docker-compose.yaml`'s `gnss-simulator` service (`bin/gnss_simulator.rb`,
|
||||||
|
also documented for local dev in `docs/development.adoc`) runs
|
||||||
|
alongside `maptool` in production too, sharing its network namespace
|
||||||
|
(`network_mode: "service:maptool"`) so it's reachable at the
|
||||||
|
hardcoded `127.0.0.1:2947` `Maptool::GpsdClient` expects — there's no
|
||||||
|
real GNSS receiver attached to the server, so without this the "GNSS"
|
||||||
|
control would just always show "no fix". It drives a loop around
|
||||||
|
Bratislava; remove the service block (nothing else needs to change)
|
||||||
|
whenever a real receiver/gpsd setup replaces it.
|
||||||
|
|
||||||
== Garmin LiveTrack session webhook
|
== Garmin LiveTrack session webhook
|
||||||
|
|
||||||
`POST /api/livetrack/session` (`session_id`, `session_token`, `secret` form
|
`POST /api/livetrack/session` (`session_id`, `session_token`, `secret` form
|
||||||
@@ -147,7 +212,7 @@ const PROCESSED_LABEL = "livetrack-processed";
|
|||||||
|
|
||||||
function checkLiveTrackEmail() {
|
function checkLiveTrackEmail() {
|
||||||
const label = GmailApp.getUserLabelByName(PROCESSED_LABEL) || GmailApp.createLabel(PROCESSED_LABEL);
|
const label = GmailApp.getUserLabelByName(PROCESSED_LABEL) || GmailApp.createLabel(PROCESSED_LABEL);
|
||||||
const threads = GmailApp.search(`from:noreply@garmin.com subject:LiveTrack -label:${PROCESSED_LABEL}`);
|
const threads = GmailApp.search(`from:noreply@garmin.com -label:${PROCESSED_LABEL}`);
|
||||||
|
|
||||||
threads.forEach(thread => {
|
threads.forEach(thread => {
|
||||||
// getBody() (raw HTML), not getPlainBody(): the email has no text/plain
|
// getBody() (raw HTML), not getPlainBody(): the email has no text/plain
|
||||||
@@ -170,9 +235,14 @@ function checkLiveTrackEmail() {
|
|||||||
|
|
||||||
The Gmail label (rather than marking the message read/unread) tracks which
|
The Gmail label (rather than marking the message read/unread) tracks which
|
||||||
threads are already handled without touching the user's own inbox state.
|
threads are already handled without touching the user's own inbox state.
|
||||||
`subject:LiveTrack` matches regardless of the account's language — Garmin
|
No `subject:LiveTrack` filter on the search — despite Gmail's own
|
||||||
keeps "LiveTrack" as-is in the subject even in a localized
|
substring-match documentation for `subject:`, that filter empirically
|
||||||
("Sledujte LiveTrack používateľa ...") one.
|
returned zero threads against a real, currently-arriving LiveTrack email
|
||||||
|
whose subject does contain the word ("Sledujte LiveTrack používateľa
|
||||||
|
...", confirmed 2026-09-20), so it's left out entirely — the regex match
|
||||||
|
below is what actually narrows this down to real LiveTrack emails with a
|
||||||
|
usable session link, `from:noreply@garmin.com` alone is deliberately
|
||||||
|
broad.
|
||||||
|
|
||||||
Set the trigger from the editor: clock icon on the left (Triggers) → *Add
|
Set the trigger from the editor: clock icon on the left (Triggers) → *Add
|
||||||
Trigger* → function `checkLiveTrackEmail`, time-driven, "Minutes timer",
|
Trigger* → function `checkLiveTrackEmail`, time-driven, "Minutes timer",
|
||||||
@@ -189,16 +259,45 @@ webhook simply won't receive that session; nothing breaks.
|
|||||||
== CI deploy job
|
== CI deploy job
|
||||||
|
|
||||||
`.gitea/workflows/ci.yml`'s `deploy` job runs after `check` passes, only
|
`.gitea/workflows/ci.yml`'s `deploy` job runs after `check` passes, only
|
||||||
on a push to `main`, with `runs-on: cube-host`:
|
on a push to `main`, with `runs-on: brick-host`:
|
||||||
|
|
||||||
. Checks out the repo with plain `git` (`fetch` + `checkout`,
|
. Checks out the repo with plain `git` (`fetch` + `checkout`,
|
||||||
authenticated via a one-off `http.extraHeader`) rather than
|
authenticated via a one-off `http.extraHeader`) rather than
|
||||||
`actions/checkout@v4` — a leftover from when this job ran in a
|
`actions/checkout@v4` — a leftover from when this job ran in a
|
||||||
Node-less container; kept as-is since there's no reason to bring
|
Node-less container; kept as-is since there's no reason to bring
|
||||||
Node back in for something this already does.
|
Node back in for something this already does.
|
||||||
. Copies the checkout into `/srv/docker/maptool/` using plain `cp`/`rm`:
|
. Wipes and replaces `/srv/podman/maptool/build/` with the fresh
|
||||||
clears everything in the target except `data/`, copies the fresh
|
checkout (plain `cp`/`rm`, dropping the copied `.git`), then
|
||||||
checkout over, then drops the copied `.git`.
|
`ln -sf`s `docker-compose.yaml` up to it — self-healing (harmless
|
||||||
. `cd /srv/docker/maptool && docker compose -p maptool up --build -d`
|
to redo every deploy) and, being a fresh checkout each time, nothing
|
||||||
— rebuilds the image and recreates the container if anything
|
in `build/` needs preserving the way `data/`/`.env` (both a level
|
||||||
changed; a no-op if nothing did.
|
up, see "Layout on the server" above) do.
|
||||||
|
. `sudo systemctl restart podman-compose@maptool.service`. The service
|
||||||
|
(`/etc/systemd/system/podman-compose@.service`, a systemd template
|
||||||
|
instantiated as `podman-compose@maptool.service`) owns the actual
|
||||||
|
container lifecycle — its `ExecStartPre` tears down the previous
|
||||||
|
containers/networks/volumes on every start, and its `ExecStart` runs
|
||||||
|
`podman compose up --build` (mirroring `docker-compose@.service`'s
|
||||||
|
own `--build`), so a plain restart always rebuilds from the code
|
||||||
|
just synced above while still reusing any layer whose inputs didn't
|
||||||
|
change (base image, `apt-get install`, `bundle install` when
|
||||||
|
`Gemfile`/`Gemfile.lock` are unchanged) as cache.
|
||||||
|
+
|
||||||
|
CAUTION: An earlier version of this step explicitly removed the
|
||||||
|
`maptool:latest` image (`podman rmi -f`) before restarting, to force a
|
||||||
|
rebuild against a service whose `ExecStart` didn't have `--build` yet.
|
||||||
|
That "worked" but also destroyed the build cache along with the image
|
||||||
|
— since nothing else referenced those layers, Podman fully deleted
|
||||||
|
them rather than leaving them as reusable cache the way Docker leaves
|
||||||
|
a retagged image's old layers behind — turning every deploy into a
|
||||||
|
full `apt-get`/`bundle install` from scratch (~3 minutes measured,
|
||||||
|
vs. ~20s before). Fixed by adding `--build` to the unit instead (see
|
||||||
|
above); don't reintroduce the `podman rmi` step.
|
||||||
|
. Polls `http://localhost:4567/api/health` for *this deploy's own*
|
||||||
|
revision (up to 5 minutes, the job fails if it never shows up)
|
||||||
|
before calling the deploy done — the restart above only confirms
|
||||||
|
`podman compose up --build` was launched (`Type=simple` has no
|
||||||
|
readiness protocol), not that the build actually succeeded and the
|
||||||
|
new code is what's answering; checking the revision specifically,
|
||||||
|
not just any 200, also catches a stuck old container that never got
|
||||||
|
torn down.
|
||||||
@@ -27,22 +27,296 @@ reload (e.g. Ctrl/Cmd+Shift+R) if that happens.
|
|||||||
|
|
||||||
To enable the offline style, place a PMTiles file at
|
To enable the offline style, place a PMTiles file at
|
||||||
`data/offline.pmtiles` (see `docs/architecture.adoc` for how it is
|
`data/offline.pmtiles` (see `docs/architecture.adoc` for how it is
|
||||||
served and styled). It is not part of the repository — obtain or
|
served and styled — the same OpenMapTiles schema/style the online map
|
||||||
build one locally:
|
uses, just from local data). Logged-in users can generate this from
|
||||||
|
the app itself — see "In-app regeneration" below — or it can be built
|
||||||
|
by hand:
|
||||||
|
|
||||||
* The simplest source is a pre-built extract from
|
* Download one or more country extracts from
|
||||||
https://app.protomaps.com[protomaps.com's extract builder] — pick a
|
https://download.geofabrik.de[Geofabrik] (`.osm.pbf`, ODbL/OSM
|
||||||
region and download it as `.pmtiles`. Its schema is what
|
attribution) — e.g.
|
||||||
`protomaps-themes-base` (the styling library used for the offline
|
`curl -O https://download.geofabrik.de/europe/slovakia-latest.osm.pbf`.
|
||||||
style) expects.
|
* If more than one country, merge them with
|
||||||
* To convert some other vector tile source (e.g. an `.mbtiles` file)
|
https://github.com/osmcode/osmium-tool[osmium-tool]:
|
||||||
into PMTiles, use the
|
`osmium merge slovakia-latest.osm.pbf austria-latest.osm.pbf -o merged.osm.pbf`
|
||||||
https://github.com/protomaps/go-pmtiles[go-pmtiles] CLI:
|
(each Geofabrik file is already clipped to that country's real
|
||||||
`pmtiles convert in.mbtiles data/offline.pmtiles`.
|
border, so no separate bbox/polygon clip step is needed either
|
||||||
|
before or after — skip this step entirely for a single country).
|
||||||
|
* Tile it with https://github.com/onthegomap/planetiler[Planetiler]'s
|
||||||
|
default profile, which *is* OpenMapTiles — no `--profile` flag
|
||||||
|
needed — writing PMTiles directly:
|
||||||
|
`java -jar planetiler.jar --osm-path=merged.osm.pbf --output=data/offline.pmtiles`
|
||||||
|
(needs Java 21+; `planetiler.jar` from
|
||||||
|
https://github.com/onthegomap/planetiler/releases/latest[the latest release]).
|
||||||
|
|
||||||
Once the file is in place, `GET /api/offline-tiles` reports
|
Once the file is in place, `GET /api/offline-tiles` reports
|
||||||
`available: true` and the map defaults to the offline style.
|
`available: true` and the map defaults to the offline style.
|
||||||
|
|
||||||
|
== Offline terrain data (hillshade)
|
||||||
|
|
||||||
|
The sidebar's "Terrain shading" toggle works online out of the box
|
||||||
|
(public https://github.com/tilezen/joerd[Terrarium] elevation tiles,
|
||||||
|
no API key). For it to work offline too, place a PMTiles archive of
|
||||||
|
Terrarium-encoded `raster-dem` tiles at `data/terrain.pmtiles`
|
||||||
|
(`GET /api/terrain-tiles` reports `available: true` once it's there,
|
||||||
|
same pattern as offline map data above). Also generatable in-app (see
|
||||||
|
below) or by hand, the same `go-pmtiles extract` mechanism as
|
||||||
|
`data/offline.pmtiles` above — but from
|
||||||
|
https://mapterhorn.com[Mapterhorn], a Protomaps-affiliated project
|
||||||
|
publishing pre-built Terrarium-encoded terrain as PMTiles (a global
|
||||||
|
`planet.pmtiles`, z0-12, plus ~450 regional chunks covering z13 and up
|
||||||
|
— see https://mapterhorn.com/data-access/[their own data-access page]
|
||||||
|
for the full manifest and per-source license/attribution details, CC-BY-4.0
|
||||||
|
and similar, varying by region):
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
pmtiles extract https://download.mapterhorn.com/planet.pmtiles planet-extract.pmtiles --bbox=west,south,east,north --maxzoom=13 --overfetch=0
|
||||||
|
# only if a regional chunk covers the bbox too (see download_urls.json) —
|
||||||
|
# most bboxes hit exactly one, a bbox straddling a chunk boundary hits two:
|
||||||
|
pmtiles extract https://download.mapterhorn.com/<chunk>.pmtiles chunk-extract.pmtiles --bbox=west,south,east,north --maxzoom=13 --overfetch=0
|
||||||
|
pmtiles merge planet-extract.pmtiles chunk-extract.pmtiles data/terrain.pmtiles
|
||||||
|
----
|
||||||
|
|
||||||
|
`--overfetch=0`: the default (0.05) merges nearby byte ranges into fewer,
|
||||||
|
bigger requests to save round-trips — for `planet.pmtiles` (global z0-12,
|
||||||
|
tens of GB) over a bbox spanning several countries this can collapse the
|
||||||
|
whole extract into a *single* multi-GB request with no way to resume after
|
||||||
|
a mid-transfer HTTP/2 stream reset. `--overfetch=0` trades that one
|
||||||
|
fragile mega-request for many small ones instead (spread over
|
||||||
|
`--download-threads`, default 4) — slower end to end, but a dropped
|
||||||
|
connection only loses one small request's worth of progress, not the
|
||||||
|
whole extract.
|
||||||
|
|
||||||
|
`--maxzoom=13`: raster DEM tiles don't compress away like vector ones do
|
||||||
|
(every tile is a real image, even over flat/empty terrain), so each extra
|
||||||
|
zoom level roughly quadruples storage — the app's own build
|
||||||
|
(`PmtilesBuilder::TERRAIN_MAX_ZOOM`) caps at 13, each regional chunk's
|
||||||
|
own native minzoom, rather than the online hillshade layer's z15. A
|
||||||
|
12-country selection at z15 was headed well past 181GB; z13 is still
|
||||||
|
plenty for the hillshade toggle and the planned slope readout.
|
||||||
|
|
||||||
|
Tiles are 512px WebP, not the 256px PNG this app's *online* Terrarium
|
||||||
|
source uses — `public/js/dem.js`'s own point-elevation lookups
|
||||||
|
(`shadow.js`'s terrain-shadow overlay) read the actual decoded tile
|
||||||
|
size rather than assuming one, so this isn't a problem either way.
|
||||||
|
|
||||||
|
== Offline hiking trails
|
||||||
|
|
||||||
|
The sidebar's "Hiking trails" overlay works online out of the box
|
||||||
|
(waymarkedtrails.org raster tiles, `public/js/hiking_trails.js`) —
|
||||||
|
clicking a trail fires a one-off Overpass query for its name/colour/
|
||||||
|
network/distance. For it to work offline too, one `data/hiking_trails/
|
||||||
|
<CODE>.geojson` per country is generated automatically as the last step
|
||||||
|
of every `offline.pmtiles` build (`Maptool::HikingTrails`,
|
||||||
|
`lib/maptool/hiking_trails.rb`), straight from the *same* per-country
|
||||||
|
`.osm.pbf` files already downloaded for `offline.pmtiles` itself — no
|
||||||
|
separate download, no third-party service:
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
osmium tags-filter country.osm.pbf r/route=hiking -o hiking.osm.pbf
|
||||||
|
osmium add-locations-to-ways hiking.osm.pbf -o hiking-located.osm.pbf
|
||||||
|
osmium cat hiking-located.osm.pbf -f opl,locations_on_ways=true,add_metadata=false -o hiking.opl
|
||||||
|
----
|
||||||
|
|
||||||
|
`add-locations-to-ways` matters: a plain Geofabrik PBF's ways only carry
|
||||||
|
bare node *references*, not coordinates, so skipping this step silently
|
||||||
|
produces ways with empty x/y in the OPL output and every route gets
|
||||||
|
dropped for having no usable geometry — not an error, just wrong output
|
||||||
|
(verified against a real extract before this was caught). No general OSM
|
||||||
|
tool stitches a route relation's member ways into one line for you
|
||||||
|
(routes are a semantic grouping, not a geometry type), so
|
||||||
|
`HikingTrails.parse_opl` does that walk itself — the same thing
|
||||||
|
waymarkedtrails.org and this app's own online Overpass click handler
|
||||||
|
already do.
|
||||||
|
|
||||||
|
Best-effort per country: an extraction problem on one country's PBF logs
|
||||||
|
a warning and is skipped rather than failing the whole `offline.pmtiles`
|
||||||
|
build, or even the other countries in the same selection, over what's a
|
||||||
|
bonus overlay, not the core map (`PmtilesBuilder.extract_hiking_trails`).
|
||||||
|
Cached the same way `geofabrik_cache` is (skipped once a country's own
|
||||||
|
extract is already newer than its source `.osm.pbf`), so growing the
|
||||||
|
region selection by one country only extracts that one. Coordinates are
|
||||||
|
rounded to 5 decimal places (~1m, plenty for a rendered line) — Slovakia
|
||||||
|
alone is ~3000 routes, ~30MB unrounded.
|
||||||
|
|
||||||
|
One file per country rather than one combined file: `GET /api/hiking-trails`
|
||||||
|
lists which country codes have an extract (`countries: [...]`), and the
|
||||||
|
Overlays panel's "Hiking trails" item turns that into a checklist —
|
||||||
|
each ticked country fetches its own `GET /tiles/hiking_trails/<CODE>.geojson`
|
||||||
|
only then, not on page load and not for countries never ticked. Offline
|
||||||
|
click hit-tests the loaded vector layer(s) directly
|
||||||
|
(`queryRenderedFeatures`) instead of querying Overpass, so it resolves
|
||||||
|
instantly with no network at all. `rake hiking:trails[pbf,out]` runs the
|
||||||
|
extraction by hand against one `.osm.pbf` for testing.
|
||||||
|
|
||||||
|
`DELETE /api/offline-tiles` removes the whole `data/hiking_trails/`
|
||||||
|
directory along with `offline.pmtiles` and the Geofabrik cache — there's
|
||||||
|
no separate delete/rebuild route for it, since it's not an independent
|
||||||
|
build.
|
||||||
|
|
||||||
|
== In-app regeneration
|
||||||
|
|
||||||
|
A logged-in user can also generate both files above from the app
|
||||||
|
itself, no local tooling needed — the sidebar's "Offline data" icon
|
||||||
|
(Offline hub group) shows a checklist of European countries
|
||||||
|
(`lib/maptool/data/europe_countries.geojson`, Natural Earth 110m
|
||||||
|
admin-0, public domain) plus each file's status (size, last updated).
|
||||||
|
Ticking countries and clicking "Apply changes" saves the selection
|
||||||
|
(`data/offline_regions.json`, an array of ISO A2 codes) and rebuilds
|
||||||
|
both files to cover it.
|
||||||
|
|
||||||
|
There's no standalone per-country *built* file — `offline.pmtiles`/
|
||||||
|
`terrain.pmtiles` are always one combined extract for the *whole
|
||||||
|
current selection*. "Remove a country" is just "change the selection,
|
||||||
|
then Apply" (unticking a country and applying rebuilds without it).
|
||||||
|
terrain.pmtiles re-extracts its selection from scratch on
|
||||||
|
every Apply — fine given that extraction is byte-range requests
|
||||||
|
against a remote archive, not full downloads. offline.pmtiles, by
|
||||||
|
contrast, downloads a whole country's `.osm.pbf` from Geofabrik (can be
|
||||||
|
multi-GB), so it keeps a per-country cache under
|
||||||
|
`data/geofabrik_cache/` instead of re-fetching on every Apply — a
|
||||||
|
country already cached is only re-downloaded if Geofabrik's own
|
||||||
|
`Last-Modified` for it has moved on since (checked with a cheap `HEAD`,
|
||||||
|
see `PmtilesBuilder.cached_extract`), so growing or shrinking the
|
||||||
|
selection only touches whatever's actually new to it.
|
||||||
|
|
||||||
|
This wraps exactly the manual workflows documented above
|
||||||
|
(`lib/maptool/pmtiles_builder.rb`). offline.pmtiles downloads each
|
||||||
|
selected country's Geofabrik extract, `osmium merge`s them if there's
|
||||||
|
more than one, then tiles the result with Planetiler — the server
|
||||||
|
needs `osmium` and `java` + `planetiler.jar` installed and on `PATH`
|
||||||
|
(override with the `OSMIUM_BIN`/`JAVA_BIN`/`PLANETILER_JAR` env vars if
|
||||||
|
they live somewhere else; the Docker image installs all three, see
|
||||||
|
`Dockerfile`). terrain.pmtiles still uses `go-pmtiles`
|
||||||
|
(`PMTILES_BIN`) against Mapterhorn, unrelated to this — it extracts
|
||||||
|
`planet.pmtiles` (always) plus whichever Mapterhorn regional chunks
|
||||||
|
intersect the selection, `merge`s them if there was more than one —
|
||||||
|
always safe regardless of how many/which countries are selected, since
|
||||||
|
each chunk is Mapterhorn's own pre-partitioned, non-overlapping source
|
||||||
|
file (extracting the *same* source once per country and merging those,
|
||||||
|
by contrast, would fail — adjacent countries' region-extracts share
|
||||||
|
border tiles, since extraction copies whole tiles rather than clipping
|
||||||
|
vector content to the polygon, and `pmtiles merge` refuses any
|
||||||
|
overlapping tile id between inputs).
|
||||||
|
|
||||||
|
A build runs in the background (`GET /api/offline-regions` to list/see
|
||||||
|
the current selection, `POST` to change it, then `POST
|
||||||
|
/api/offline-tiles/build` / `/api/terrain-tiles/build`, polled via the
|
||||||
|
matching `*/build-status` route) — only one of each kind at a time;
|
||||||
|
starting a second while one's running gets a 409, as does starting
|
||||||
|
either with nothing selected yet. While running, `build-status` also
|
||||||
|
includes a `step` string (e.g. `"fetching PL (3/4) — done: SK, CZ —
|
||||||
|
pending: HU"`, or `"tiling with planetiler"`) — see
|
||||||
|
`PmtilesBuilder.run_job`'s `report_step`.
|
||||||
|
|
||||||
|
`DELETE /api/offline-tiles` removes offline.pmtiles, its sidecar meta,
|
||||||
|
and the whole per-country download cache (`data/geofabrik_cache/`) —
|
||||||
|
`DELETE /api/terrain-tiles` does the same for terrain.pmtiles. Both are
|
||||||
|
a manual "reclaim the space" / "start over" action (409 while that
|
||||||
|
kind's build is running), separate from the region picker's Apply,
|
||||||
|
which only changes what a *future* build covers and never frees
|
||||||
|
anything already on disk. `DELETE
|
||||||
|
/api/offline-tiles/cache/:code` removes just one country's cached
|
||||||
|
extract, e.g. to force a clean re-download of it without touching the
|
||||||
|
rest of the cache. `GET /api/offline-tiles/cache` lists what's
|
||||||
|
actually in that cache right now (code/size/Last-Modified per file) —
|
||||||
|
not the same thing as offline.pmtiles' own `meta.sources`, which only
|
||||||
|
reflects the last successful build: a country can be cached without
|
||||||
|
ever having been built (ticked, not yet Applied) or built from without
|
||||||
|
still being selected (unticked since). The region picker shows this
|
||||||
|
next to each country's checkbox, with its own delete button, rather
|
||||||
|
than only offering "delete everything" on the whole offline.pmtiles
|
||||||
|
row.
|
||||||
|
|
||||||
|
Each successful build also writes a small sidecar (`offline.pmtiles.
|
||||||
|
meta.json` — each source country's code/Geofabrik slug/Last-Modified;
|
||||||
|
`terrain.pmtiles.meta.json` — the Mapterhorn source names/md5sums)
|
||||||
|
recording what was actually built, since the `.pmtiles` file itself
|
||||||
|
carries no such record. `GET /api/offline-tiles`/`/api/terrain-tiles`
|
||||||
|
include it as `meta`, shown in the panel next to the existing
|
||||||
|
size/updated line. `GET .../check-update` compares that recorded build
|
||||||
|
against each source's live upstream state (a Geofabrik `HEAD` per
|
||||||
|
country for offline, the Mapterhorn manifest for terrain — without
|
||||||
|
rebuilding anything) and reports `update_available: true/false/null` —
|
||||||
|
`null` means "don't know" (no local build yet, or a check itself
|
||||||
|
failed), kept as its own route rather than folded into the plain
|
||||||
|
status endpoints above since those are on the page-load hot path (the
|
||||||
|
topbar's offline-status dot) and this one reaches an external service,
|
||||||
|
only worth doing when the Offline data panel is actually open.
|
||||||
|
|
||||||
|
== Camper-relevant POIs (Overpass)
|
||||||
|
|
||||||
|
`rake overpass:pois[south,west,north,east,out.geojson]` queries
|
||||||
|
https://overpass-api.de[Overpass] for drinking water, dump stations,
|
||||||
|
LPG refill, public toilets, laundromats, supermarkets/convenience
|
||||||
|
stores, and springs within a bbox, and writes the result as a
|
||||||
|
`.geojson` file (`lib/maptool/overpass_pois.rb`). Nodes only (nothing
|
||||||
|
that's mapped as a way/relation, e.g. a supermarket traced as a
|
||||||
|
building outline rather than a single point). Also reachable from
|
||||||
|
inside the running app itself, without the command line — see Import
|
||||||
|
plugins below, which wraps this exact query as `plugins/overpass-pois`.
|
||||||
|
|
||||||
|
== Import plugins
|
||||||
|
|
||||||
|
The sidebar's import icon (logged in only) lists whatever's under
|
||||||
|
`plugins/` and runs one for the current map viewport with a click — no
|
||||||
|
area-picker to fill in, the visible area *is* the area. Each plugin is
|
||||||
|
a directory with a `plugin.json` manifest (`{"label": "...", "command":
|
||||||
|
"./run.sh"}`) plus whatever script that command names; the entire
|
||||||
|
interface a plugin needs to know about maptool is "read
|
||||||
|
`--south`/`--west`/`--north`/`--east`, print a GeoJSON
|
||||||
|
`FeatureCollection` to stdout" (`lib/maptool/plugins.rb`) — the result
|
||||||
|
is saved as a new `.geojson` file the same way an upload would be.
|
||||||
|
|
||||||
|
`plugins/overpass-pois` ships as the reference example (a thin wrapper
|
||||||
|
around the `rake overpass:pois` logic above). A plugin that scrapes a
|
||||||
|
site whose terms of service forbid that — Park4Night, iOverlander,
|
||||||
|
Freecampsites — doesn't have to live in this (public) repo to work
|
||||||
|
here: drop its directory under `plugins/` locally and just don't
|
||||||
|
`git add` it, same as any other local-only file this app already
|
||||||
|
treats that way (`data/offline.pmtiles`, the kiwix ZIM, ...).
|
||||||
|
|
||||||
|
== Offline Wikipedia (kiwix-serve)
|
||||||
|
|
||||||
|
A point popup's "Nearby on Wikipedia" link needs
|
||||||
|
https://github.com/kiwix/kiwix-tools[kiwix-serve] running with a
|
||||||
|
Wikipedia https://library.kiwix.org[ZIM] loaded — set `KIWIX_URL` to
|
||||||
|
wherever *the server* can reach it (`docker-compose.yaml` already runs
|
||||||
|
a `kiwix` service pointed at `data/kiwix/wikipedia.zim` and wires
|
||||||
|
`KIWIX_URL` to it via its compose service name; blank/unset disables
|
||||||
|
the link, same as every other optional integration here). Used only
|
||||||
|
server-side: production sits behind a reverse proxy that forwards only
|
||||||
|
to this app, so there's no address a browser could reach kiwix-serve
|
||||||
|
at directly — `GET /kiwix/*` (`app.rb`) proxies to it instead, and the
|
||||||
|
`kiwix` service runs with `--urlRootLocation=/kiwix` so its own
|
||||||
|
emitted links/assets already carry that same prefix. Not part of the
|
||||||
|
repo — a ZIM is a large, self-contained archive, obtain one locally:
|
||||||
|
|
||||||
|
* https://library.kiwix.org/#lang=eng&category=wikipedia[Kiwix's own
|
||||||
|
library] has pre-built Wikipedia ZIMs — a `_maxi` variant per
|
||||||
|
language/region includes images, `_nopic` is much smaller if
|
||||||
|
browsing images offline doesn't matter here.
|
||||||
|
* Download it to `data/kiwix/wikipedia.zim` (the exact filename
|
||||||
|
`docker-compose.yaml`'s `kiwix` service expects — rename after
|
||||||
|
downloading, Kiwix's own filenames are much more specific). A symlink
|
||||||
|
there works too (`ln -s /wherever/the-real-file.zim
|
||||||
|
data/kiwix/wikipedia.zim`), so the real file doesn't have to sit inside
|
||||||
|
this repo's checkout at all — the compose file's volume binds that exact
|
||||||
|
path, not the `data/kiwix/` directory, specifically so this resolves
|
||||||
|
correctly (a symlink *inside* an already-mounted directory wouldn't: the
|
||||||
|
container would have to resolve it itself, in its own filesystem, where
|
||||||
|
the host path it points to doesn't exist).
|
||||||
|
|
||||||
|
Once it's there, `docker compose up -d kiwix` (or a full restart)
|
||||||
|
picks it up — `GET /api/kiwix` reports `available: true` and the
|
||||||
|
"Nearby on Wikipedia" link appears, pointed at kiwix-serve's own geo
|
||||||
|
search (`GET /kiwix/search?pattern=&latitude=&longitude=&distance=`)
|
||||||
|
through this app's own proxy rather than at kiwix-serve directly — it
|
||||||
|
already has a full search UI of its own, this just makes it reachable
|
||||||
|
through the same origin/reverse proxy as the rest of the app.
|
||||||
|
|
||||||
== GNSS position (gpsd)
|
== GNSS position (gpsd)
|
||||||
|
|
||||||
The "GNSS" control needs https://gpsd.io[gpsd] running and pointed at
|
The "GNSS" control needs https://gpsd.io[gpsd] running and pointed at
|
||||||
@@ -52,9 +326,28 @@ up as). On openSUSE, `gpsd` is a package with systemd units
|
|||||||
package manager. No app-side configuration is needed — it just
|
package manager. No app-side configuration is needed — it just
|
||||||
connects to `127.0.0.1:2947`, gpsd's default port.
|
connects to `127.0.0.1:2947`, gpsd's default port.
|
||||||
|
|
||||||
To test without hardware, feed gpsd a fake NMEA stream over TCP
|
To test without hardware or gpsd itself installed/running, run
|
||||||
instead of a real serial device — gpsd accepts a `tcp://host:port`
|
`bin/gnss_simulator.rb` — it speaks gpsd's own JSON wire protocol
|
||||||
source:
|
directly on gpsd's default port (`127.0.0.1:2947`), so
|
||||||
|
`Maptool::GpsdClient` connects to it exactly as it would to real
|
||||||
|
gpsd, no app-side changes needed. Drives a loop around a center point
|
||||||
|
(Bratislava by default) at a driving-ish pace instead of sitting at a
|
||||||
|
fixed position, since a stationary "GNSS position" isn't much to look
|
||||||
|
at on the map:
|
||||||
|
|
||||||
|
[source,shell]
|
||||||
|
----
|
||||||
|
bin/gnss_simulator.rb # loop around Bratislava, 300m radius
|
||||||
|
bin/gnss_simulator.rb 48.85 2.35 500 # custom center/radius (Paris, 500m)
|
||||||
|
----
|
||||||
|
|
||||||
|
`GET /api/gnss/position` should report a fix within a couple of
|
||||||
|
seconds, moving on every poll.
|
||||||
|
|
||||||
|
To test against gpsd itself instead (e.g. to exercise the actual
|
||||||
|
NMEA-parsing path, not just gpsd's JSON output), feed it a fake NMEA
|
||||||
|
stream over TCP instead of a real serial device — gpsd accepts a
|
||||||
|
`tcp://host:port` source:
|
||||||
|
|
||||||
[source,shell]
|
[source,shell]
|
||||||
----
|
----
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ checking here before searching from zero next time.
|
|||||||
| MIT (per `package.json`); the whole `npmap5-plugins` repo is separately
|
| MIT (per `package.json`); the whole `npmap5-plugins` repo is separately
|
||||||
public domain as a US government work
|
public domain as a US government work
|
||||||
|
|
||||||
| Park Tiles color palette (offline + online basemap themes)
|
| Park Tiles color palette (shared offline + online basemap theme)
|
||||||
| `public/js/map.js`'s `OFFLINE_THEME` and `ONLINE_STYLE_PATCH`
|
| `public/js/map.js`'s `ONLINE_STYLE_PATCH`
|
||||||
| Colors only — see <<park-tiles>> for why the actual tile service isn't used
|
| Colors only — see <<park-tiles>> for why the actual tile service isn't used
|
||||||
|
|
||||||
| `overview` (top-right minimap/inset control)
|
| `overview` (top-right minimap/inset control)
|
||||||
@@ -187,7 +187,7 @@ why this app uses OpenFreeMap Liberty as its actual basemap, not Park
|
|||||||
Tiles — see `docs/architecture.adoc`'s "Offline maps" section.
|
Tiles — see `docs/architecture.adoc`'s "Offline maps" section.
|
||||||
|
|
||||||
Recreating the *color choices* is a different matter — not the kind of
|
Recreating the *color choices* is a different matter — not the kind of
|
||||||
thing a data license restricts — and is what `OFFLINE_THEME` in
|
thing a data license restricts — and is what `ONLINE_STYLE_PATCH` in
|
||||||
`public/js/map.js` actually does (see `docs/architecture.adoc`).
|
`public/js/map.js` actually does (see `docs/architecture.adoc`).
|
||||||
|
|
||||||
=== Public style JSON (source for the real colors we did pull)
|
=== Public style JSON (source for the real colors we did pull)
|
||||||
@@ -219,11 +219,11 @@ embedded credentials, even when technically reachable:
|
|||||||
|===
|
|===
|
||||||
|
|
||||||
Colors actually pulled from the Imagery style JSON and used in
|
Colors actually pulled from the Imagery style JSON and used in
|
||||||
`OFFLINE_THEME`: background (`hsl(40, 37%, 97%)`, a warm cream), the
|
`ONLINE_STYLE_PATCH`: background (`hsl(40, 37%, 97%)`, a warm cream), the
|
||||||
park/protected-area green (`#b3e378`), the two road-importance tiers
|
park/protected-area green (`#b3e378`), the two road-importance tiers
|
||||||
(`#fdefbe`/`#fef7dd`), and the park-boundary line color (`#546619`, dark
|
(`#fdefbe`/`#fef7dd`), and the park-boundary line color (`#546619`, dark
|
||||||
olive). The rest of `OFFLINE_THEME` extends that same warm/olive family
|
olive). The rest of `ONLINE_STYLE_PATCH` extends that same warm/olive
|
||||||
by eye rather than from a sourced value.
|
family by eye rather than from a sourced value.
|
||||||
|
|
||||||
=== Legend / cartographic hierarchy
|
=== Legend / cartographic hierarchy
|
||||||
|
|
||||||
@@ -259,6 +259,20 @@ own work (a map viewer, not a publisher of NPS-branded content):
|
|||||||
own UI chrome (currently plain system fonts, `public/css/style.css`) —
|
own UI chrome (currently plain system fonts, `public/css/style.css`) —
|
||||||
no current use case, but no licensing blocker either, worth
|
no current use case, but no licensing blocker either, worth
|
||||||
remembering if that comes up.
|
remembering if that comes up.
|
||||||
|
* *Another open alternative, for display use*: https://nationalparktypeface.com[National Park]
|
||||||
|
— a separate, unrelated project (Ben Hoepner, Andrea Herstowski, Jeremy
|
||||||
|
Shellhorn / DO Studio), also SIL OFL licensed, also original ("inspired
|
||||||
|
by" park signage, not a Rawlinson reproduction). Ships as variable
|
||||||
|
`.ttf`, `.woff`/`.woff2`, and `.otf` — self-hostable via `@font-face`
|
||||||
|
the same way. Where it differs from Sequoia Sans above: it's a
|
||||||
|
router-carved-signage *display* face (the chunky all-caps look of
|
||||||
|
actual trailhead signs), not a general body/UI sans — best fit would be
|
||||||
|
a wordmark/heading use (e.g. the topbar's "MAPTOOL" text) alongside a
|
||||||
|
more legible sans for body copy, not a full typeface swap. Now used for
|
||||||
|
exactly that: `.topbar__brand` only (`public/css/style.css`), Bold
|
||||||
|
weight vendored under `public/vendor/national-park-typeface/` — see
|
||||||
|
`docs/third-party-notices.adoc`. Body copy elsewhere still plain system
|
||||||
|
fonts, unchanged.
|
||||||
* *Hawaiian typography*: NPS Rawlinson includes vowels with Hawaiian
|
* *Hawaiian typography*: NPS Rawlinson includes vowels with Hawaiian
|
||||||
macrons (ā, ē, ī, ō, ū) and the okina (ʻ); the regular weight of
|
macrons (ā, ē, ī, ō, ū) and the okina (ʻ); the regular weight of
|
||||||
Frutiger doesn't, so NPS uses a separate "HFrutiger" weight for
|
Frutiger doesn't, so NPS uses a separate "HFrutiger" weight for
|
||||||
@@ -272,6 +286,50 @@ own work (a map viewer, not a publisher of NPS-branded content):
|
|||||||
resource — included here only because it was shared alongside the
|
resource — included here only because it was shared alongside the
|
||||||
typography notes above.
|
typography notes above.
|
||||||
|
|
||||||
|
== Arrowhead (shield) logo colors
|
||||||
|
|
||||||
|
Unrelated to anything this app renders — noted here only because it came up
|
||||||
|
in conversation (wanting exact colors to 3D-print the NPS arrowhead) and
|
||||||
|
this doc is already the dumping ground for NPS-ecosystem color/graphic-identity
|
||||||
|
facts. Per NPS's own graphic identity standards (Harpers Ferry Center), the
|
||||||
|
arrowhead uses three official Pantone spot colors plus white:
|
||||||
|
|
||||||
|
[cols="2,2,1,1"]
|
||||||
|
|===
|
||||||
|
| Element | Pantone | Hex (converted) | Nearest RAL Classic
|
||||||
|
|
||||||
|
| Mountain
|
||||||
|
| Dark Rust, PMS 1615 C
|
||||||
|
| `#8b4720`
|
||||||
|
| RAL 2013 Pearl orange (`#923E25`)
|
||||||
|
|
||||||
|
| Sky
|
||||||
|
| Medium Rust, PMS 1605 C
|
||||||
|
| `#a65523`
|
||||||
|
| RAL 8023 Orange brown (`#A45729`)
|
||||||
|
|
||||||
|
| Trees / grass
|
||||||
|
| Dark Green, PMS 553 C
|
||||||
|
| `#284734`
|
||||||
|
| RAL 6012 Black green (`#2F3D3A`)
|
||||||
|
|
||||||
|
| Bison, lake, snowfield, type
|
||||||
|
| White
|
||||||
|
| `#ffffff`
|
||||||
|
| RAL 9016 Traffic white (`#F1F1EA`) — RAL has no pure white
|
||||||
|
|===
|
||||||
|
|
||||||
|
Hex values are third-party Pantone→RGB conversions, not something NPS
|
||||||
|
publishes directly (their style guide only specifies PMS codes). RAL codes
|
||||||
|
are the closest match by RGB distance against the RAL Classic chart — same
|
||||||
|
caveat, not an official NPS-endorsed mapping, just the nearest available
|
||||||
|
code for filament/paint shopping. Source: NPS Graphic Identity and Style
|
||||||
|
Guides (https://www.nps.gov/subjects/hfc/nps-graphic-identity-and-style-guides.htm),
|
||||||
|
FAQs About Arrowhead Artwork
|
||||||
|
(https://www.nps.gov/subjects/hfc/faqs-about-arrowhead-artwork.htm). Use of
|
||||||
|
the arrowhead symbol itself is controlled by law/regulation/policy — see
|
||||||
|
the FAQ page before printing/distributing anything bearing it.
|
||||||
|
|
||||||
== NPMap4 (the older, Leaflet-based generation)
|
== NPMap4 (the older, Leaflet-based generation)
|
||||||
|
|
||||||
https://www.nps.gov/lib/npmap.js/4.2.1/ — predecessor to npmap5,
|
https://www.nps.gov/lib/npmap.js/4.2.1/ — predecessor to npmap5,
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ BSD-3-Clause licensed.
|
|||||||
| 4.5.0
|
| 4.5.0
|
||||||
| https://github.com/protomaps/PMTiles
|
| https://github.com/protomaps/PMTiles
|
||||||
|
|
||||||
| protomaps-themes-base
|
| maplibre-contour (Overlays panel's "Contour lines", `public/js/contour.js`)
|
||||||
| 4.5.0
|
| 0.1.0
|
||||||
| https://github.com/protomaps/basemaps
|
| https://github.com/onthegomap/maplibre-contour
|
||||||
|===
|
|===
|
||||||
|
|
||||||
Also vendored under `public/vendor/`, but not BSD-3-Clause like the
|
Also vendored under `public/vendor/`, but not BSD-3-Clause like the
|
||||||
@@ -51,6 +51,34 @@ overview's polar pan limit, but MapLibre 6's `Map` no longer exposes
|
|||||||
`.transform` at all — guarded to skip that (cosmetic) adjustment
|
`.transform` at all — guarded to skip that (cosmetic) adjustment
|
||||||
instead of throwing when it's absent.
|
instead of throwing when it's absent.
|
||||||
|
|
||||||
|
Also vendored under `public/vendor/` — MIT-licensed, attribution
|
||||||
|
required (`LICENSE` included alongside):
|
||||||
|
|
||||||
|
[cols="1,1,2"]
|
||||||
|
|===
|
||||||
|
| Library | Version | Source
|
||||||
|
|
||||||
|
| maplibre-gl-lanes (offline hiking trails, `public/js/hiking_trails.js`
|
||||||
|
— trails sharing a path drawn as parallel lanes); its `dist/*.mjs`
|
||||||
|
build, minus the trailing `sourceMappingURL` comment
|
||||||
|
| 1.3.0
|
||||||
|
| https://github.com/c0nsumer/maplibre-gl-lanes
|
||||||
|
|===
|
||||||
|
|
||||||
|
Also vendored under `public/vendor/` — MIT-licensed (© 2013 Panayiotis
|
||||||
|
Lipiridis), attribution required (this notice satisfies that):
|
||||||
|
|
||||||
|
[cols="1,1,2"]
|
||||||
|
|===
|
||||||
|
| Library | Version | Source
|
||||||
|
|
||||||
|
| flag-icons (just the square `1x1/` crop, only the 39 European
|
||||||
|
countries `lib/maptool/data/europe_countries.geojson` lists — see
|
||||||
|
`public/js/offline_data.js`)
|
||||||
|
| 7.5.0
|
||||||
|
| https://github.com/lipis/flag-icons
|
||||||
|
|===
|
||||||
|
|
||||||
Offline map text labels need font glyphs too (see
|
Offline map text labels need font glyphs too (see
|
||||||
`docs/architecture.adoc`), vendored under `public/vendor/fonts/` —
|
`docs/architecture.adoc`), vendored under `public/vendor/fonts/` —
|
||||||
SIL Open Font License, not BSD-3-Clause like the above:
|
SIL Open Font License, not BSD-3-Clause like the above:
|
||||||
@@ -63,13 +91,72 @@ SIL Open Font License, not BSD-3-Clause like the above:
|
|||||||
`256-511` only)
|
`256-511` only)
|
||||||
| https://github.com/protomaps/basemaps-assets[protomaps/basemaps-assets]
|
| https://github.com/protomaps/basemaps-assets[protomaps/basemaps-assets]
|
||||||
(`fonts/`, `OFL.txt` included alongside)
|
(`fonts/`, `OFL.txt` included alongside)
|
||||||
|
|
||||||
|
| National Park (Bold weight only, `.woff2`), topbar wordmark
|
||||||
|
(`.topbar__brand`, `public/css/style.css`) — see
|
||||||
|
`docs/nps-resources.adoc`
|
||||||
|
| https://nationalparktypeface.com[nationalparktypeface.com] (Ben
|
||||||
|
Hoepner, Andrea Herstowski, Jeremy Shellhorn / DO Studio) — "published
|
||||||
|
for free use under the SIL Open Font License" per the site; no
|
||||||
|
`OFL.txt` ships in its own download zips, unlike Noto Sans above
|
||||||
|===
|
|===
|
||||||
|
|
||||||
The online map style (not vendored, fetched at runtime when the
|
The map style — https://tiles.openfreemap.org/styles/liberty[OpenFreeMap
|
||||||
"Online" layer is selected) is
|
"liberty"], built from https://www.openstreetmap.org[OpenStreetMap]
|
||||||
https://tiles.openfreemap.org/styles/liberty[OpenFreeMap "liberty"],
|
data — is used two ways (see `docs/architecture.adoc`): fetched live
|
||||||
built from https://www.openstreetmap.org[OpenStreetMap] data.
|
at runtime for the "Online" layer, and as a one-time vendored snapshot
|
||||||
|
(`public/vendor/liberty-style/liberty.json`) that the "Offline" layer
|
||||||
|
repoints at local data instead. Its icon sprite is vendored the same
|
||||||
|
way, under `public/vendor/liberty-sprite/` (`ofm.json`/`ofm.png` +
|
||||||
|
`@2x` variants, from `tiles.openfreemap.org/sprites/`). Neither has
|
||||||
|
been re-checked against a specific upstream version pin the way the
|
||||||
|
table above does — re-fetch both from `tiles.openfreemap.org` if
|
||||||
|
Liberty's own style/sprite ever visibly drifts from what's vendored.
|
||||||
|
|
||||||
|
`data/offline.pmtiles` (`lib/maptool/pmtiles_builder.rb`'s in-app
|
||||||
|
regeneration, or the manual recipe in `docs/development.adoc`) is
|
||||||
|
built at build time, not vendored, from
|
||||||
|
https://download.geofabrik.de[Geofabrik]'s per-country OpenStreetMap
|
||||||
|
extracts (ODbL).
|
||||||
|
|
||||||
|
`terrain.pmtiles` (same regeneration mechanism) is extracted at build
|
||||||
|
time, not vendored, from https://mapterhorn.com[Mapterhorn] — a
|
||||||
|
Protomaps-affiliated project publishing pre-built Terrarium-encoded
|
||||||
|
terrain as PMTiles. Per-region source licenses vary (CC-BY-4.0 and
|
||||||
|
similar); see
|
||||||
|
https://download.mapterhorn.com/attribution.json[their own attribution
|
||||||
|
list] for the full provenance.
|
||||||
|
|
||||||
|
Not a frontend asset like the above, but also baked into this app's own
|
||||||
|
Docker image (not a separate container the way kiwix-serve/gpsd are —
|
||||||
|
see `docs/deployment.adoc`/`docs/development.adoc`): three CLI tools
|
||||||
|
`lib/maptool/pmtiles_builder.rb` shells out to for offline.pmtiles/
|
||||||
|
terrain.pmtiles regeneration.
|
||||||
|
|
||||||
|
[cols="1,1,1,2"]
|
||||||
|
|===
|
||||||
|
| Tool | Version | License | Source
|
||||||
|
|
||||||
|
| go-pmtiles (`pmtiles` CLI)
|
||||||
|
| 1.31.2
|
||||||
|
| BSD-3-Clause
|
||||||
|
| https://github.com/protomaps/go-pmtiles
|
||||||
|
|
||||||
|
| Planetiler (`planetiler.jar`)
|
||||||
|
| 0.10.2
|
||||||
|
| Apache-2.0
|
||||||
|
| https://github.com/onthegomap/planetiler
|
||||||
|
|
||||||
|
| osmium-tool (`osmium`, installed via the `osmium-tool` Debian package)
|
||||||
|
| (Debian trixie's packaged version)
|
||||||
|
| GPL-3.0
|
||||||
|
| https://github.com/osmcode/osmium-tool
|
||||||
|
|===
|
||||||
|
|
||||||
To update a vendored library: download its package (e.g.
|
To update a vendored library: download its package (e.g.
|
||||||
`npm pack <name>@<version>`), replace the corresponding files under
|
`npm pack <name>@<version>`), replace the corresponding files under
|
||||||
`public/vendor/<name>/`, and update the version in this file.
|
`public/vendor/<name>/`, and update the version in this file. To
|
||||||
|
update go-pmtiles or Planetiler, bump `PMTILES_VERSION`/
|
||||||
|
`PLANETILER_VERSION` in the `Dockerfile` and the version here —
|
||||||
|
osmium-tool tracks whatever version Debian trixie currently packages,
|
||||||
|
nothing to pin on this side.
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"name":"Albania","iso_a2":"AL","iso_a3":"ALB"},"geometry":{"type":"Polygon","coordinates":[[[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.371768,41.877551],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855409],[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727]]]}},{"type":"Feature","properties":{"name":"Austria","iso_a2":"AT","iso_a3":"AUT"},"geometry":{"type":"Polygon","coordinates":[[[16.979667,48.123497],[16.903754,47.714866],[16.340584,47.712902],[16.534268,47.496171],[16.202298,46.852386],[16.011664,46.683611],[15.137092,46.658703],[14.632472,46.431817],[13.806475,46.509306],[12.376485,46.767559],[12.153088,47.115393],[11.164828,46.941579],[11.048556,46.751359],[10.442701,46.893546],[9.932448,46.920728],[9.47997,47.10281],[9.632932,47.347601],[9.594226,47.525058],[9.896068,47.580197],[10.402084,47.302488],[10.544504,47.566399],[11.426414,47.523766],[12.141357,47.703083],[12.62076,47.672388],[12.932627,47.467646],[13.025851,47.637584],[12.884103,48.289146],[13.243357,48.416115],[13.595946,48.877172],[14.338898,48.555305],[14.901447,48.964402],[15.253416,49.039074],[16.029647,48.733899],[16.499283,48.785808],[16.960288,48.596982],[16.879983,48.470013],[16.979667,48.123497]]]}},{"type":"Feature","properties":{"name":"Belarus","iso_a2":"BY","iso_a3":"BLR"},"geometry":{"type":"Polygon","coordinates":[[[28.176709,56.16913],[29.229513,55.918344],[29.371572,55.670091],[29.896294,55.789463],[30.873909,55.550976],[30.971836,55.081548],[30.757534,54.811771],[31.384472,54.157056],[31.791424,53.974639],[31.731273,53.794029],[32.405599,53.618045],[32.693643,53.351421],[32.304519,53.132726],[31.49764,53.16743],[31.305201,53.073996],[31.540018,52.742052],[31.78597,52.10168],[31.785992,52.101678],[30.927549,52.042353],[30.619454,51.822806],[30.555117,51.319503],[30.157364,51.416138],[29.254938,51.368234],[28.992835,51.602044],[28.617613,51.427714],[28.241615,51.572227],[27.454066,51.592303],[26.337959,51.832289],[25.327788,51.910656],[24.553106,51.888461],[24.005078,51.617444],[23.527071,51.578454],[23.508002,52.023647],[23.199494,52.486977],[23.799199,52.691099],[23.804935,53.089731],[23.527536,53.470122],[23.484128,53.912498],[24.450684,53.905702],[25.536354,54.282423],[25.768433,54.846963],[26.588279,55.167176],[26.494331,55.615107],[27.10246,55.783314],[28.176709,56.16913]]]}},{"type":"Feature","properties":{"name":"Belgium","iso_a2":"BE","iso_a3":"BEL"},"geometry":{"type":"Polygon","coordinates":[[[6.156658,50.803721],[6.043073,50.128052],[5.782417,50.090328],[5.674052,49.529484],[4.799222,49.985373],[4.286023,49.907497],[3.588184,50.378992],[3.123252,50.780363],[2.658422,50.796848],[2.513573,51.148506],[3.314971,51.345781],[3.315011,51.345777],[3.314971,51.345755],[4.047071,51.267259],[4.973991,51.475024],[5.606976,51.037298],[6.156658,50.803721]]]}},{"type":"Feature","properties":{"name":"Bosnia and Herz.","iso_a2":"BA","iso_a3":"BIH"},"geometry":{"type":"Polygon","coordinates":[[[18.56,42.65],[17.674922,43.028563],[17.297373,43.446341],[16.916156,43.667722],[16.456443,44.04124],[16.23966,44.351143],[15.750026,44.818712],[15.959367,45.233777],[16.318157,45.004127],[16.534939,45.211608],[17.002146,45.233777],[17.861783,45.06774],[18.553214,45.08159],[19.005485,44.860234],[19.00548,44.86023],[19.36803,44.863],[19.11761,44.42307],[19.59976,44.03847],[19.454,43.5681],[19.21852,43.52384],[19.03165,43.43253],[18.70648,43.20011],[18.56,42.65]]]}},{"type":"Feature","properties":{"name":"Bulgaria","iso_a2":"BG","iso_a3":"BGR"},"geometry":{"type":"Polygon","coordinates":[[[22.65715,44.234923],[22.944832,43.823785],[23.332302,43.897011],[24.100679,43.741051],[25.569272,43.688445],[26.065159,43.943494],[27.2424,44.175986],[27.970107,43.812468],[28.558081,43.707462],[28.039095,43.293172],[27.673898,42.577892],[27.99672,42.007359],[27.135739,42.141485],[26.117042,41.826905],[26.106138,41.328899],[25.197201,41.234486],[24.492645,41.583896],[23.692074,41.309081],[22.952377,41.337994],[22.881374,41.999297],[22.380526,42.32026],[22.545012,42.461362],[22.436595,42.580321],[22.604801,42.898519],[22.986019,43.211161],[22.500157,43.642814],[22.410446,44.008063],[22.65715,44.234923]]]}},{"type":"Feature","properties":{"name":"Croatia","iso_a2":"HR","iso_a3":"HRV"},"geometry":{"type":"Polygon","coordinates":[[[16.564808,46.503751],[16.882515,46.380632],[17.630066,45.951769],[18.456062,45.759481],[18.829825,45.908872],[19.072769,45.521511],[19.390476,45.236516],[19.005485,44.860234],[18.553214,45.08159],[17.861783,45.06774],[17.002146,45.233777],[16.534939,45.211608],[16.318157,45.004127],[15.959367,45.233777],[15.750026,44.818712],[Line truncated
|
||||||
@@ -128,6 +128,64 @@ module Maptool
|
|||||||
File.read(cache)
|
File.read(cache)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Appends one GeoJSON Feature (a hand-drawn point/note, see notes.js) to
|
||||||
|
# the FeatureCollection at `path` — or, if `path` is nil, to a new or
|
||||||
|
# existing file named `name` inside `folder`. Deliberately overwrites in
|
||||||
|
# place rather than disambiguating like save() does: repeat calls with
|
||||||
|
# the same path/name are meant to grow one running "layer" file, not
|
||||||
|
# spawn "notes-1.geojson", "notes-2.geojson", ... siblings. Returns the
|
||||||
|
# store-relative path written to, so a caller that didn't already know
|
||||||
|
# it (the new-file case) can show/select it afterwards.
|
||||||
|
def append_feature(feature:, path: nil, folder: "", name: "")
|
||||||
|
if path && !path.to_s.strip.empty?
|
||||||
|
file = resolve_file(path)
|
||||||
|
raise Errno::ENOENT, file unless File.file?(file)
|
||||||
|
else
|
||||||
|
dir = resolve_dir(folder.to_s)
|
||||||
|
FileUtils.mkdir_p(dir)
|
||||||
|
filename = safe_geojson_filename(name)
|
||||||
|
file = File.join(dir, filename)
|
||||||
|
path = folder.to_s.empty? ? filename : "#{folder}/#{filename}"
|
||||||
|
end
|
||||||
|
|
||||||
|
collection = File.file?(file) ? JSON.parse(File.read(file)) : { "type" => "FeatureCollection", "features" => [] }
|
||||||
|
(collection["features"] ||= []) << feature
|
||||||
|
File.write(file, JSON.generate(collection))
|
||||||
|
path
|
||||||
|
end
|
||||||
|
|
||||||
|
# Replaces the feature whose properties["id"] matches `id` (see
|
||||||
|
# append_feature/notes.js — every hand-drawn note gets a client-
|
||||||
|
# generated id specifically so it can be found again later, since nothing
|
||||||
|
# else here is a stable identifier: array position shifts as other
|
||||||
|
# features are added/removed, and a note's own text is exactly the thing
|
||||||
|
# being edited). Raises Errno::ENOENT for a missing file *or* a missing
|
||||||
|
# feature — same "nothing to act on" story either way.
|
||||||
|
def update_feature(path:, id:, feature:)
|
||||||
|
file = resolve_file(path)
|
||||||
|
raise Errno::ENOENT, file unless File.file?(file)
|
||||||
|
|
||||||
|
collection = JSON.parse(File.read(file))
|
||||||
|
index = (collection["features"] || []).find_index { |f| f.dig("properties", "id") == id }
|
||||||
|
raise Errno::ENOENT, "#{path}##{id}" unless index
|
||||||
|
|
||||||
|
collection["features"][index] = feature
|
||||||
|
File.write(file, JSON.generate(collection))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same id-matching story as update_feature above.
|
||||||
|
def delete_feature(path:, id:)
|
||||||
|
file = resolve_file(path)
|
||||||
|
raise Errno::ENOENT, file unless File.file?(file)
|
||||||
|
|
||||||
|
collection = JSON.parse(File.read(file))
|
||||||
|
before = (collection["features"] || []).length
|
||||||
|
collection["features"] = (collection["features"] || []).reject { |f| f.dig("properties", "id") == id }
|
||||||
|
raise Errno::ENOENT, "#{path}##{id}" if collection["features"].length == before
|
||||||
|
|
||||||
|
File.write(file, JSON.generate(collection))
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def resolve_dir(folder)
|
def resolve_dir(folder)
|
||||||
@@ -163,6 +221,17 @@ module Maptool
|
|||||||
base
|
base
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Same path-safety idea as safe_filename above, but for a brand-new
|
||||||
|
# notes layer: always .geojson (never falls back to .gpx), and named
|
||||||
|
# after the user's own "Parking spots"-style label rather than an
|
||||||
|
# uploaded file's original name.
|
||||||
|
def safe_geojson_filename(name)
|
||||||
|
base = File.basename(name.to_s.strip)
|
||||||
|
base = "notes" if base.empty? || base == "." || base == ".."
|
||||||
|
base = base.sub(/\.geojson\z/i, "")
|
||||||
|
"#{base}.geojson"
|
||||||
|
end
|
||||||
|
|
||||||
def unique_filename(dir, name)
|
def unique_filename(dir, name)
|
||||||
return name unless File.exist?(File.join(dir, name))
|
return name unless File.exist?(File.join(dir, name))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,181 @@
|
|||||||
|
require "open3"
|
||||||
|
require "tmpdir"
|
||||||
|
require "json"
|
||||||
|
require_relative "track_stats"
|
||||||
|
|
||||||
|
module Maptool
|
||||||
|
# Prototype: extracts route=hiking relations (name/colour/network/operator,
|
||||||
|
# full member-way geometry) straight out of a Geofabrik .osm.pbf — the same
|
||||||
|
# file PmtilesBuilder already downloads per country for offline.pmtiles, so
|
||||||
|
# this needs no separate download or third-party service. Three osmium
|
||||||
|
# subprocess steps do the heavy lifting (osmium is already a server
|
||||||
|
# dependency, see PmtilesBuilder/docs/development.adoc):
|
||||||
|
#
|
||||||
|
# 1. `osmium tags-filter FILE r/route=hiking -o hiking.osm.pbf` — pulls
|
||||||
|
# out just the hiking relations *and* (by default, no -R) every way/
|
||||||
|
# node they reference, so the output is small and self-contained.
|
||||||
|
# 2. `osmium add-locations-to-ways` — a plain Geofabrik PBF's ways only
|
||||||
|
# carry bare node *references* (ids), not coordinates; this resolves
|
||||||
|
# each way's node ids against the node objects in the same file and
|
||||||
|
# rewrites the way with real coordinates attached. Verified against a
|
||||||
|
# real extract (Macedonia) that skipping this step silently produces
|
||||||
|
# ways with empty x/y (`n123xy`, no numbers) in the OPL output below —
|
||||||
|
# not an error, just wrong output, so easy to miss without checking.
|
||||||
|
# 3. `osmium cat hiking.osm.pbf -f opl,locations_on_ways=true,add_metadata=false`
|
||||||
|
# — OPL is a plain-text, one-object-per-line format (see
|
||||||
|
# https://osmcode.org/opl-file-format/); locations_on_ways (now that
|
||||||
|
# step 2 actually populated them) embeds each way's node coordinates
|
||||||
|
# directly in its own line, so no separate node-id -> [lon,lat] table
|
||||||
|
# needs building, just two passes over text.
|
||||||
|
#
|
||||||
|
# No general OSM tool stitches a route relation's member ways into one
|
||||||
|
# line for you (routes are a semantic grouping, not a geometry type) —
|
||||||
|
# every hiking-trail renderer, including this app's own Overpass-based
|
||||||
|
# click handler (public/js/hiking_trails.js) and waymarkedtrails.org
|
||||||
|
# itself, does this same member-way walk by hand.
|
||||||
|
module HikingTrails
|
||||||
|
class ExtractError < StandardError; end
|
||||||
|
|
||||||
|
ROUTE_TAGS = %w[name description ref colour network operator].freeze
|
||||||
|
|
||||||
|
def self.osmium_bin
|
||||||
|
ENV.fetch("OSMIUM_BIN", "osmium")
|
||||||
|
end
|
||||||
|
|
||||||
|
# pbf_path: a Geofabrik-style .osm.pbf, e.g. one of
|
||||||
|
# PmtilesBuilder.cached_extracts' own files. Returns a GeoJSON
|
||||||
|
# FeatureCollection (MultiLineString per relation, one sub-line per
|
||||||
|
# member way — not one big concatenated LineString, since a relation's
|
||||||
|
# ways aren't always contiguous/ordered and jumping across a gap would
|
||||||
|
# draw a spurious straight connector).
|
||||||
|
def self.extract(pbf_path)
|
||||||
|
Dir.mktmpdir("maptool-hiking-trails") do |dir|
|
||||||
|
filtered = File.join(dir, "hiking.osm.pbf")
|
||||||
|
run!(osmium_bin, "tags-filter", pbf_path, "r/route=hiking", "-o", filtered)
|
||||||
|
|
||||||
|
located = File.join(dir, "hiking-located.osm.pbf")
|
||||||
|
run!(osmium_bin, "add-locations-to-ways", filtered, "-o", located)
|
||||||
|
|
||||||
|
opl_path = File.join(dir, "hiking.opl")
|
||||||
|
run!(osmium_bin, "cat", located, "-f", "opl,locations_on_ways=true,add_metadata=false", "-o", opl_path)
|
||||||
|
|
||||||
|
parse_opl(File.read(opl_path))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pure — no subprocess — so it's cheaply testable against a canned OPL
|
||||||
|
# fixture, same "keep the real logic testable without the real tool
|
||||||
|
# installed" reasoning OverpassPois.to_geojson already gives for staying
|
||||||
|
# separate from OverpassPois.fetch.
|
||||||
|
def self.parse_opl(text)
|
||||||
|
ways = {} # way id -> [[lon, lat], ...]
|
||||||
|
relations = [] # { tags:, members: [{type:, id:}, ...] }
|
||||||
|
|
||||||
|
text.each_line do |line|
|
||||||
|
fields = line.chomp.split(" ")
|
||||||
|
head = fields.first
|
||||||
|
next unless head
|
||||||
|
|
||||||
|
case head[0]
|
||||||
|
when "w"
|
||||||
|
ways[head[1..].to_i] = way_coordinates(fields)
|
||||||
|
when "r"
|
||||||
|
tags = tags_field(fields)
|
||||||
|
relations << { id: head[1..].to_i, tags: tags, members: members_field(fields) } if tags["route"] == "hiking"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
features = relations.filter_map { |relation| relation_feature(relation, ways) }
|
||||||
|
{ "type" => "FeatureCollection", "features" => features }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.relation_feature(relation, ways)
|
||||||
|
lines = relation[:members]
|
||||||
|
.select { |member| member[:type] == "w" }
|
||||||
|
.filter_map { |member| ways[member[:id]] }
|
||||||
|
.select { |coords| coords.size >= 2 }
|
||||||
|
return nil if lines.empty?
|
||||||
|
|
||||||
|
distance = lines.sum { |coords| TrackStats.compute(coords)["distance_meters"] }
|
||||||
|
tags = relation[:tags]
|
||||||
|
properties = {
|
||||||
|
"id" => relation[:id],
|
||||||
|
"name" => tags["name"] || tags["description"],
|
||||||
|
"distance_meters" => distance.round(1),
|
||||||
|
}
|
||||||
|
ROUTE_TAGS.each { |key| properties[key] ||= tags[key] }
|
||||||
|
|
||||||
|
{
|
||||||
|
"type" => "Feature",
|
||||||
|
"properties" => properties.compact,
|
||||||
|
# Rounded to 5 decimal places (~1m) — full OSM float precision
|
||||||
|
# (~1cm) is pointless for a rendered line and roughly doubles the
|
||||||
|
# size of every coordinate pair once this many-country GeoJSON is
|
||||||
|
# actually served to a browser.
|
||||||
|
"geometry" => { "type" => "MultiLineString", "coordinates" => round_coordinates(lines) },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
private_class_method :relation_feature
|
||||||
|
|
||||||
|
def self.round_coordinates(lines)
|
||||||
|
lines.map { |coords| coords.map { |lon, lat| [lon.round(5), lat.round(5)] } }
|
||||||
|
end
|
||||||
|
private_class_method :round_coordinates
|
||||||
|
|
||||||
|
def self.field(fields, letter)
|
||||||
|
fields.find { |f| f.start_with?(letter) }
|
||||||
|
end
|
||||||
|
private_class_method :field
|
||||||
|
|
||||||
|
def self.tags_field(fields)
|
||||||
|
raw = field(fields, "T")&.delete_prefix("T") || ""
|
||||||
|
return {} if raw.empty?
|
||||||
|
|
||||||
|
raw.split(",").to_h { |pair| pair.split("=", 2).map { |part| unescape(part.to_s) } }
|
||||||
|
end
|
||||||
|
private_class_method :tags_field
|
||||||
|
|
||||||
|
def self.members_field(fields)
|
||||||
|
raw = field(fields, "M")&.delete_prefix("M") || ""
|
||||||
|
return [] if raw.empty?
|
||||||
|
|
||||||
|
raw.split(",").map do |member|
|
||||||
|
{ type: member[0], id: member[1..].split("@", 2).first.to_i }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :members_field
|
||||||
|
|
||||||
|
# "n123x14.123y49.456,n124x14.130y49.460,..." -> [[14.123, 49.456], ...].
|
||||||
|
# A member id with no embedded location (shouldn't happen with
|
||||||
|
# locations_on_ways=true and -R omitted, but a way with a dangling/
|
||||||
|
# missing node reference is possible in real-world data) is skipped
|
||||||
|
# rather than raising — one bad node shouldn't drop the whole route.
|
||||||
|
def self.way_coordinates(fields)
|
||||||
|
raw = field(fields, "N")&.delete_prefix("N") || ""
|
||||||
|
return [] if raw.empty?
|
||||||
|
|
||||||
|
raw.split(",").filter_map do |node_ref|
|
||||||
|
match = node_ref.match(/\An\d+x(?<lon>-?[\d.]+)y(?<lat>-?[\d.]+)\z/)
|
||||||
|
[match[:lon].to_f, match[:lat].to_f] if match
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :way_coordinates
|
||||||
|
|
||||||
|
# OPL escapes space/newline/comma/equals/@/% as %<hex>% (see
|
||||||
|
# https://osmcode.org/opl-file-format/#_encoding) — only tag keys/
|
||||||
|
# values and usernames can contain them; the field-letter prefixes and
|
||||||
|
# id/comma/@ separators we split on above are never themselves escaped.
|
||||||
|
def self.unescape(str)
|
||||||
|
str.gsub(/%([0-9a-fA-F]{1,6})%/) { [Regexp.last_match(1).to_i(16)].pack("U") }
|
||||||
|
end
|
||||||
|
private_class_method :unescape
|
||||||
|
|
||||||
|
def self.run!(*args)
|
||||||
|
stdout, stderr, status = Open3.capture3(*args)
|
||||||
|
raise ExtractError, "#{args.first} exited with status #{status.exitstatus}: #{[stdout, stderr].map(&:strip).reject(&:empty?).join("\n")}" unless status.success?
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
raise ExtractError, "could not run '#{args.first}' (#{e.message}) — is osmium-tool installed and on PATH?"
|
||||||
|
end
|
||||||
|
private_class_method :run!
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
require "net/http"
|
||||||
|
require "rexml/document"
|
||||||
|
|
||||||
|
module Maptool
|
||||||
|
# A reachability check plus a passthrough proxy for an external
|
||||||
|
# kiwix-serve instance serving an offline Wikipedia ZIM (see
|
||||||
|
# docs/development.adoc for how to set one up). A POI popup's "Nearby on
|
||||||
|
# Wikipedia" link (public/js/gpx.js) used to point straight at
|
||||||
|
# kiwix-serve itself — fine on a LAN, broken once this app started
|
||||||
|
# running behind a reverse proxy (map.priesol.net) that only forwards to
|
||||||
|
# *this* app, leaving no address a browser could reach kiwix-serve at
|
||||||
|
# directly. GET /kiwix/* in app.rb calls .proxy below instead.
|
||||||
|
module Kiwix
|
||||||
|
# <meta name="geo.position" content="LAT;LON"> — a standard tag
|
||||||
|
# Wikipedia's own MediaWiki geo extension puts on every geotagged
|
||||||
|
# article's page (confirmed live against a real ZIM), semicolon rather
|
||||||
|
# than comma-separated. Far simpler to match than the human-facing
|
||||||
|
# "48°08′46″N 17°10′38″E / 48.14611°N 17.17722°E" markup the article
|
||||||
|
# body itself renders (DMS *and* decimal, in varying wrapper markup).
|
||||||
|
GEO_POSITION_PATTERN = /<meta name="geo\.position" content="(-?[\d.]+);(-?[\d.]+)"/
|
||||||
|
def self.reachable?(url, open_timeout: 1, read_timeout: 2)
|
||||||
|
return false if url.to_s.strip.empty?
|
||||||
|
|
||||||
|
uri = URI(url)
|
||||||
|
Net::HTTP.start(uri.host, uri.port, open_timeout: open_timeout, read_timeout: read_timeout) { |http| http.head("/") }
|
||||||
|
true
|
||||||
|
rescue StandardError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
# path already carries the /kiwix prefix kiwix-serve itself expects
|
||||||
|
# (started with --urlRootLocation=/kiwix, see docker-compose.yaml) —
|
||||||
|
# this is a plain passthrough, not a rewrite, so its emitted links stay
|
||||||
|
# correct without this having to touch response bodies at all. Returns
|
||||||
|
# nil (halted as a 502 by the caller) rather than raising, same
|
||||||
|
# not-reachable-isn't-exceptional stance as .reachable? above.
|
||||||
|
#
|
||||||
|
# path arrives already URL-*decoded* — Sinatra decodes a route's splat
|
||||||
|
# before handing it over — so an article title with non-ASCII
|
||||||
|
# characters (e.g. "/kiwix/content/wikipedia/Dlhé_diely") has to be
|
||||||
|
# re-escaped before URI#path= will accept it at all; it raises
|
||||||
|
# URI::InvalidComponentError on raw UTF-8 bytes rather than escaping
|
||||||
|
# them itself. Article links otherwise 502'd for exactly this reason.
|
||||||
|
def self.proxy(base_url, path, query_string, open_timeout: 2, read_timeout: 10)
|
||||||
|
uri = URI(base_url)
|
||||||
|
uri.path = URI::RFC2396_PARSER.escape(path)
|
||||||
|
uri.query = query_string unless query_string.to_s.empty?
|
||||||
|
Net::HTTP.start(uri.host, uri.port, open_timeout: open_timeout, read_timeout: read_timeout) { |http| http.request(Net::HTTP::Get.new(uri)) }
|
||||||
|
rescue StandardError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
# bbox: [south, west, north, east] — the "Wikipedia" map layer
|
||||||
|
# (public/js/gpx.js) refetches this on every pan/zoom for whatever's
|
||||||
|
# currently on screen. kiwix-serve's own geo search only *filters* by
|
||||||
|
# distance from a point, it doesn't echo each result's own coordinates
|
||||||
|
# back (confirmed live: neither its HTML nor its ?format=xml output
|
||||||
|
# carries one) — so every matching article's page is fetched too, and
|
||||||
|
# its coordinates read from GEO_POSITION_PATTERN above. Cheap since
|
||||||
|
# kiwix-serve serves its ZIM locally with no network round trip, unlike
|
||||||
|
# this same fetch-every-result shape would be against live Wikipedia.
|
||||||
|
def self.nearby(base_url, bbox, limit: 30, open_timeout: 2, read_timeout: 10)
|
||||||
|
south, west, north, east = bbox
|
||||||
|
lat = (south + north) / 2.0
|
||||||
|
lng = (west + east) / 2.0
|
||||||
|
radius_m = (haversine_km(lat, lng, north, east) * 1000).round
|
||||||
|
|
||||||
|
query = URI.encode_www_form(pattern: "", latitude: lat, longitude: lng, distance: radius_m, pageLength: limit, format: "xml")
|
||||||
|
response = proxy(base_url, "/kiwix/search", query, open_timeout: open_timeout, read_timeout: read_timeout)
|
||||||
|
return empty_collection unless response&.code == "200"
|
||||||
|
|
||||||
|
# One request per result — tried running these in parallel threads
|
||||||
|
# first (each is blocking I/O, so MRI's GIL releases during the
|
||||||
|
# actual wait), but measured no improvement against the real
|
||||||
|
# production ZIM (kiwix-serve itself appears to serialize incoming
|
||||||
|
# requests regardless), so plain sequential stayed. ~1.6s for a
|
||||||
|
# 30-result page on that box — fine for a refetch-on-pan-end layer,
|
||||||
|
# not for anything tighter; lower `limit` first if that's ever not
|
||||||
|
# enough headroom, before reaching for something more involved.
|
||||||
|
features = parse_search_results(response.body).filter_map { |result| point_feature(base_url, result, open_timeout, read_timeout) }
|
||||||
|
{ "type" => "FeatureCollection", "features" => features }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.empty_collection
|
||||||
|
{ "type" => "FeatureCollection", "features" => [] }
|
||||||
|
end
|
||||||
|
private_class_method :empty_collection
|
||||||
|
|
||||||
|
def self.parse_search_results(xml)
|
||||||
|
doc = REXML::Document.new(xml)
|
||||||
|
(doc.root&.get_elements("channel/item") || []).filter_map do |item|
|
||||||
|
title = item.elements["title"]&.text
|
||||||
|
link = item.elements["link"]&.text
|
||||||
|
{ title: title, link: link } if title && link
|
||||||
|
end
|
||||||
|
rescue REXML::ParseException
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
private_class_method :parse_search_results
|
||||||
|
|
||||||
|
# result[:link] arrives already percent-encoded, straight out of
|
||||||
|
# kiwix-serve's own XML (e.g. "/kiwix/content/wikipedia/%C5%A0tadi%C3%B3n_Rapid")
|
||||||
|
# — decoded first so .proxy's own escaping (built for Sinatra's
|
||||||
|
# already-*decoded* splat, see its own comment above) doesn't double-
|
||||||
|
# encode the "%" and 404 the request.
|
||||||
|
def self.point_feature(base_url, result, open_timeout, read_timeout)
|
||||||
|
response = proxy(base_url, URI::RFC2396_PARSER.unescape(result[:link]), nil, open_timeout: open_timeout, read_timeout: read_timeout)
|
||||||
|
return nil unless response&.code == "200"
|
||||||
|
|
||||||
|
match = response.body.match(GEO_POSITION_PATTERN)
|
||||||
|
return nil unless match
|
||||||
|
|
||||||
|
{
|
||||||
|
"type" => "Feature",
|
||||||
|
"properties" => { "name" => result[:title], "link" => result[:link] },
|
||||||
|
"geometry" => { "type" => "Point", "coordinates" => [match[2].to_f, match[1].to_f] },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
private_class_method :point_feature
|
||||||
|
|
||||||
|
def self.haversine_km(lat1, lng1, lat2, lng2)
|
||||||
|
earth_radius_km = 6371.0
|
||||||
|
dlat = (lat2 - lat1) * Math::PI / 180
|
||||||
|
dlng = (lng2 - lng1) * Math::PI / 180
|
||||||
|
a = (Math.sin(dlat / 2)**2) + (Math.cos(lat1 * Math::PI / 180) * Math.cos(lat2 * Math::PI / 180) * (Math.sin(dlng / 2)**2))
|
||||||
|
2 * earth_radius_km * Math.asin(Math.sqrt(a))
|
||||||
|
end
|
||||||
|
private_class_method :haversine_km
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
require "net/http"
|
require "net/http"
|
||||||
require "json"
|
require "json"
|
||||||
|
require "time"
|
||||||
|
|
||||||
module Maptool
|
module Maptool
|
||||||
# Fetches a Garmin LiveTrack session's track and metadata.
|
# Fetches a Garmin LiveTrack session's track and metadata.
|
||||||
@@ -18,7 +19,7 @@ module Maptool
|
|||||||
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
|
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36"
|
||||||
PUSH_PATTERN = /self\.__next_f\.push\((\[.*?\])\)<\/script>/m
|
PUSH_PATTERN = /self\.__next_f\.push\((\[.*?\])\)<\/script>/m
|
||||||
|
|
||||||
Result = Struct.new(:points, :session, keyword_init: true)
|
Result = Struct.new(:points, :session, :course, keyword_init: true)
|
||||||
|
|
||||||
def self.fetch(session_id, token)
|
def self.fetch(session_id, token)
|
||||||
new.fetch(session_id, token)
|
new.fetch(session_id, token)
|
||||||
@@ -29,6 +30,10 @@ module Maptool
|
|||||||
# if that part wasn't found even though points were (seen in practice:
|
# if that part wasn't found even though points were (seen in practice:
|
||||||
# session details are consistently present alongside points, but nothing
|
# session details are consistently present alongside points, but nothing
|
||||||
# here depends on that holding forever).
|
# here depends on that holding forever).
|
||||||
|
# `course`: the planned route (name/geoPoints), or nil — only present
|
||||||
|
# when the activity was actually started from a saved Garmin course
|
||||||
|
# (session["courseIds"] is how the page itself knows to fetch one; a
|
||||||
|
# plain unplanned activity has neither).
|
||||||
def fetch(session_id, token)
|
def fetch(session_id, token)
|
||||||
payloads = parse_payloads(fetch_html(session_id, token))
|
payloads = parse_payloads(fetch_html(session_id, token))
|
||||||
|
|
||||||
@@ -36,18 +41,42 @@ module Maptool
|
|||||||
raise FetchError, "no track points found (session invalid or expired?)" unless points
|
raise FetchError, "no track points found (session invalid or expired?)" unless points
|
||||||
|
|
||||||
session = search(payloads) { |node| node if node["sessionName"] }
|
session = search(payloads) { |node| node if node["sessionName"] }
|
||||||
|
# First of possibly several — a LiveTrack session only ever follows
|
||||||
|
# the one course it was started from.
|
||||||
|
course = search(payloads) { |node| node["courses"].first if node["courses"].is_a?(Array) && !node["courses"].empty? }
|
||||||
|
|
||||||
Result.new(points: clean(points), session: clean(session))
|
Result.new(points: clean(points), session: clean(session), course: clean(course))
|
||||||
end
|
end
|
||||||
|
|
||||||
# Distance/duration/activity type come from the last track point (its
|
# Distance/duration/activity type come from the last track point (its
|
||||||
# totals are cumulative for the whole session), name/type from the
|
# totals are cumulative for the whole session), name/type from the
|
||||||
# session details — nil-safe throughout since either can be missing.
|
# session details — nil-safe throughout since either can be missing.
|
||||||
#
|
#
|
||||||
# "active": Garmin doesn't set a session's `end` until the activity is
|
# "active": originally `session["end"].nil?`, on the assumption Garmin
|
||||||
# actually stopped, so its absence means the session is still recording
|
# doesn't set `end` until the activity actually stops — disproven
|
||||||
# — inferred from the one (already-ended) session this was built
|
# against a real still-recording session (2026-09-20): `end` was
|
||||||
# against, not verified against a session actually caught mid-activity.
|
# already non-nil, set to exactly `start + 24h`, the share link's own
|
||||||
|
# expiry, not an actual end time. Two signals now, both verified live
|
||||||
|
# against that same session:
|
||||||
|
#
|
||||||
|
# * `end` itself, once it's actually in the past — watched the same
|
||||||
|
# session update it from that 24h placeholder to within a second of
|
||||||
|
# the last point's own time, immediately after the activity was
|
||||||
|
# saved on the watch. Authoritative and near-instant when present,
|
||||||
|
# so this is checked first.
|
||||||
|
# * failing that (still showing the future placeholder — an abandoned
|
||||||
|
# session never gets saved, so `end` never moves), recency of the
|
||||||
|
# last track point: Garmin posts one roughly every
|
||||||
|
# `postTrackPointFrequency` seconds (~15, observed) while genuinely
|
||||||
|
# still recording, so one older than ACTIVE_WITHIN means signal was
|
||||||
|
# lost a while ago (same "looks ended until it isn't" behavior
|
||||||
|
# Garmin's own LiveTrack page shows in that case).
|
||||||
|
#
|
||||||
|
# Both must agree the session is live — `end` already in the past
|
||||||
|
# means stopped even if the last point is only seconds old (exactly
|
||||||
|
# the save-immediately-after-the-last-point case above).
|
||||||
|
ACTIVE_WITHIN = 30 * 60 # seconds
|
||||||
|
|
||||||
def self.summarize(result)
|
def self.summarize(result)
|
||||||
last = result.points.last || {}
|
last = result.points.last || {}
|
||||||
session = result.session || {}
|
session = result.session || {}
|
||||||
@@ -56,12 +85,23 @@ module Maptool
|
|||||||
"activity_type" => last["activityType"],
|
"activity_type" => last["activityType"],
|
||||||
"distance_meters" => last["totalDistanceMeters"],
|
"distance_meters" => last["totalDistanceMeters"],
|
||||||
"duration_secs" => last["totalDurationSecs"],
|
"duration_secs" => last["totalDurationSecs"],
|
||||||
"active" => session["end"].nil?,
|
"active" => active?(last["dateTime"], session["end"]),
|
||||||
"start" => session["start"],
|
"start" => session["start"],
|
||||||
"end" => session["end"],
|
"end" => session["end"],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.active?(last_point_time, session_end)
|
||||||
|
return false unless last_point_time
|
||||||
|
|
||||||
|
now = Time.now.utc
|
||||||
|
return false if session_end && Time.parse(session_end) <= now
|
||||||
|
|
||||||
|
Time.parse(last_point_time) > now - ACTIVE_WITHIN
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
# The RSC payload serializes an absent field as the literal string
|
# The RSC payload serializes an absent field as the literal string
|
||||||
|
|||||||
@@ -55,6 +55,31 @@ module Maptool
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Ended sessions (summary["active"] == false) that ended more than
|
||||||
|
# `stale_after` seconds ago and aren't already flagged unavailable —
|
||||||
|
# i.e. ones Garmin has very likely stopped serving the live-track page
|
||||||
|
# for, but that haven't been confirmed yet (see app.rb's startup
|
||||||
|
# check). Excludes already-flagged ones so that check doesn't re-hit
|
||||||
|
# Garmin, on every restart, for a session it already told us is gone.
|
||||||
|
def stale_ended(stale_after)
|
||||||
|
cutoff = Time.now.utc - stale_after
|
||||||
|
list.select { |s| stale_ended?(s, cutoff) }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Flags a session's live-track page as no longer served by Garmin, once
|
||||||
|
# confirmed (see app.rb's startup check) — the summary itself (small,
|
||||||
|
# and a record of the activity, see the class comment) is left alone.
|
||||||
|
def mark_unavailable(session_id)
|
||||||
|
@mutex.synchronize do
|
||||||
|
sessions = read
|
||||||
|
session = sessions.find { |s| s["id"] == session_id }
|
||||||
|
return unless session
|
||||||
|
|
||||||
|
session["map_available"] = false
|
||||||
|
write(sessions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Most recently received first.
|
# Most recently received first.
|
||||||
def list
|
def list
|
||||||
prune(read)
|
prune(read)
|
||||||
@@ -77,6 +102,20 @@ module Maptool
|
|||||||
Time.now.utc # malformed/missing timestamp: don't let it block pruning
|
Time.now.utc # malformed/missing timestamp: don't let it block pruning
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stale_ended?(session, cutoff)
|
||||||
|
summary = session["summary"]
|
||||||
|
return false unless summary && summary["active"] == false && session["map_available"] != false
|
||||||
|
|
||||||
|
ended = ended_at(summary)
|
||||||
|
ended && ended < cutoff
|
||||||
|
end
|
||||||
|
|
||||||
|
def ended_at(summary)
|
||||||
|
Time.parse(summary["end"])
|
||||||
|
rescue ArgumentError, TypeError
|
||||||
|
nil # malformed/missing "end": can't tell how long it's been, so don't guess
|
||||||
|
end
|
||||||
|
|
||||||
def write(sessions)
|
def write(sessions)
|
||||||
File.write(@path, JSON.pretty_generate(sessions))
|
File.write(@path, JSON.pretty_generate(sessions))
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,150 @@
|
|||||||
|
require "net/http"
|
||||||
|
require "json"
|
||||||
|
|
||||||
|
module Maptool
|
||||||
|
# Originally a dev-time-only tool (the "overpass:pois" Rake task) kept
|
||||||
|
# off the running server so it never depended on Overpass's public
|
||||||
|
# instance being up, or ate into its (fairly strict, shared) rate limits,
|
||||||
|
# on every page load. plugins/overpass-pois now also calls this from the
|
||||||
|
# running app — but only ever on an explicit, login-gated "Run for this
|
||||||
|
# area" click (see lib/maptool/plugins.rb), never automatically, so that
|
||||||
|
# concern doesn't apply there the way it would if this ran on page load.
|
||||||
|
module OverpassPois
|
||||||
|
class FetchError < StandardError; end
|
||||||
|
|
||||||
|
# Same three public mirrors public/js/hiking_trails.js races — tried in
|
||||||
|
# turn here instead, falling through to the next on any failure: the
|
||||||
|
# shared free instances regularly 504/429 under load, and a plugin run
|
||||||
|
# is a one-off click, not worth threads to race them.
|
||||||
|
OVERPASS_APIS = [
|
||||||
|
"https://overpass-api.de/api/interpreter",
|
||||||
|
"https://overpass.kumi.systems/api/interpreter",
|
||||||
|
"https://overpass.private.coffee/api/interpreter",
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# Each entry: an Overpass tag filter -> { label:, icon:, color: }, used
|
||||||
|
# both to build the query (unioning every filter) and to label/style
|
||||||
|
# results back (matched by scanning an element's own tags for the same
|
||||||
|
# key/value, not by which branch of the query found it — Overpass's
|
||||||
|
# `out body` doesn't say which union member matched). `icon` names one
|
||||||
|
# of the marker glyphs public/js/icons.js's POI_ICONS already vendors
|
||||||
|
# for the GPX/p4n upload path — reused here rather than drawn again —
|
||||||
|
# so these render the same way a p4n2gpx waypoint of the same kind
|
||||||
|
# does, instead of every point falling back to gpx.js's plain red
|
||||||
|
# unlabeled marker (its TRACK_COLOR default for a point with no color
|
||||||
|
# of its own). shop_supermarket/shop_convenience both point at the same
|
||||||
|
# NPS "store" glyph (icons.js's STORE_ICON) — that set has no separate
|
||||||
|
# grocery-specific pictogram, so this is the honest match for both.
|
||||||
|
CATEGORIES = {
|
||||||
|
{ "amenity" => "drinking_water" } => { label: "Drinking water", icon: "amenity_drinking_water", color: "#2e86de" },
|
||||||
|
{ "amenity" => "sanitary_dump_station" } => { label: "Dump station", icon: "amenity_sanitary_dump_station", color: "#8d6e63" },
|
||||||
|
{ "amenity" => "fuel", "fuel:lpg" => "yes" } => { label: "LPG refill", icon: "shop_gas", color: "#e67e22" },
|
||||||
|
{ "shop" => "gas" } => { label: "LPG refill", icon: "shop_gas", color: "#e67e22" },
|
||||||
|
{ "amenity" => "toilets" } => { label: "Public toilet", icon: "amenity_toilets", color: "#8d6e63" },
|
||||||
|
{ "shop" => "laundry" } => { label: "Laundry", icon: "shop_laundry", color: "#5b9bd5" },
|
||||||
|
{ "shop" => "supermarket" } => { label: "Supermarket", icon: "shop_supermarket", color: "#27ae60" },
|
||||||
|
{ "shop" => "convenience" } => { label: "Convenience store", icon: "shop_convenience", color: "#27ae60" },
|
||||||
|
{ "natural" => "spring" } => { label: "Spring", icon: "amenity_drinking_water", color: "#2e86de" },
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# Tags worth surfacing in a popup, beyond the bare category label —
|
||||||
|
# each rendered as its own "Label: value" line, which gpx.js's
|
||||||
|
# formatDescription (written for p4n2gpx's own "Label: value" export
|
||||||
|
# lines) already turns into a distinct row with no changes needed on
|
||||||
|
# that end. website/contact:website are emitted as a bare URL line
|
||||||
|
# instead, since formatDescription also already auto-links those.
|
||||||
|
DESCRIPTION_TAGS = [
|
||||||
|
["opening_hours", "Opening hours"],
|
||||||
|
["operator", "Operator"],
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# bbox: [south, west, north, east] (Overpass's own axis order).
|
||||||
|
def self.fetch(bbox, timeout: 60)
|
||||||
|
query = build_query(bbox, timeout: timeout)
|
||||||
|
errors = OVERPASS_APIS.map do |url|
|
||||||
|
return post(URI(url), query, timeout)
|
||||||
|
rescue FetchError, Net::OpenTimeout, Net::ReadTimeout, SystemCallError, SocketError, OpenSSL::SSL::SSLError, JSON::ParserError => e
|
||||||
|
"#{URI(url).host}: #{e.message}"
|
||||||
|
end
|
||||||
|
raise FetchError, errors.join("; ")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.post(uri, query, timeout)
|
||||||
|
# overpass-api.de answers Net::HTTP's default "Ruby" User-Agent with
|
||||||
|
# a 406 (verified 2026-10-09) — any identifying UA gets through.
|
||||||
|
request = Net::HTTP::Post.new(uri, "User-Agent" => "maptool (Overpass camper POIs plugin)")
|
||||||
|
request.set_form_data("data" => query)
|
||||||
|
# read_timeout past the query's own [timeout:] — Net::HTTP's default
|
||||||
|
# 60s equals it, so a big viewport could hit Net::ReadTimeout just as
|
||||||
|
# Overpass was about to answer (a country-sized bbox measured ~57s).
|
||||||
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: timeout + 30) { |http| http.request(request) }
|
||||||
|
raise FetchError, "unexpected response #{response.code}" unless response.is_a?(Net::HTTPSuccess)
|
||||||
|
|
||||||
|
JSON.parse(response.body)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.build_query(bbox, timeout: 60)
|
||||||
|
bbox_str = bbox.join(",")
|
||||||
|
filters = CATEGORIES.keys.map do |tags|
|
||||||
|
conditions = tags.map { |k, v| %(["#{k}"="#{v}"]) }.join
|
||||||
|
"node#{conditions}(#{bbox_str});"
|
||||||
|
end.join("\n ")
|
||||||
|
"[out:json][timeout:#{timeout}];\n(\n #{filters}\n);\nout body;"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Pure — no network — so it's cheaply testable against a canned
|
||||||
|
# Overpass response. Only `type: "node"` elements are handled: ways/
|
||||||
|
# relations (a mapped-as-an-area supermarket, say) would need their own
|
||||||
|
# centroid computation, and the categories above are overwhelmingly
|
||||||
|
# mapped as plain nodes in practice.
|
||||||
|
def self.to_geojson(overpass_response)
|
||||||
|
features = (overpass_response["elements"] || []).filter_map do |element|
|
||||||
|
next unless element["type"] == "node"
|
||||||
|
|
||||||
|
tags = element["tags"] || {}
|
||||||
|
category = category_for(tags)
|
||||||
|
next unless category
|
||||||
|
|
||||||
|
{
|
||||||
|
"type" => "Feature",
|
||||||
|
"properties" => {
|
||||||
|
"name" => tags["name"] || category[:label],
|
||||||
|
"category" => category[:label],
|
||||||
|
"icon" => category[:icon],
|
||||||
|
"color" => category[:color],
|
||||||
|
"description" => description_for(tags),
|
||||||
|
}.compact,
|
||||||
|
"geometry" => { "type" => "Point", "coordinates" => [element["lon"], element["lat"]] },
|
||||||
|
}
|
||||||
|
end
|
||||||
|
{ "type" => "FeatureCollection", "features" => features }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.category_for(tags)
|
||||||
|
CATEGORIES.each do |filter, category|
|
||||||
|
return category if filter.all? { |k, v| tags[k] == v }
|
||||||
|
end
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
private_class_method :category_for
|
||||||
|
|
||||||
|
def self.description_for(tags)
|
||||||
|
lines = DESCRIPTION_TAGS.filter_map { |tag, label| "#{label}: #{clean_tag(tags[tag])}" if tags[tag] }
|
||||||
|
website = tags["website"] || tags["contact:website"]
|
||||||
|
lines << clean_tag(website) if website
|
||||||
|
lines.empty? ? nil : lines.join("\n")
|
||||||
|
end
|
||||||
|
private_class_method :description_for
|
||||||
|
|
||||||
|
# An OSM tag value is free text from a world-editable map — an embedded
|
||||||
|
# newline could otherwise inject an extra "line" of its own into the
|
||||||
|
# description, e.g. an operator tag ending in "\nhttps://evil.example"
|
||||||
|
# would render as its own separate, legitimate-looking link line once
|
||||||
|
# gpx.js's formatDescription (written for p4n2gpx's own well-formed
|
||||||
|
# "Label: value" lines) splits on "\n".
|
||||||
|
def self.clean_tag(value)
|
||||||
|
value.to_s.tr("\n\r", " ").strip
|
||||||
|
end
|
||||||
|
private_class_method :clean_tag
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
require "json"
|
||||||
|
require "open3"
|
||||||
|
|
||||||
|
module Maptool
|
||||||
|
# Runs an external "importer" script that turns a bbox into a GeoJSON
|
||||||
|
# FeatureCollection, and lists what's available. Each plugin is a
|
||||||
|
# directory under one of `dirs` with a plugin.json manifest ({"label":
|
||||||
|
# "...", "command": "./run.rb"}) plus whatever script it names — the
|
||||||
|
# entire interface a plugin needs to know about maptool is "read
|
||||||
|
# --south/--west/--north/--east, print GeoJSON to stdout". Kept this thin
|
||||||
|
# on purpose: it's what lets an importer that scrapes a site whose ToS
|
||||||
|
# forbids that live in its own private repo, dropped into plugins/
|
||||||
|
# locally, never touching this (public) one — see plugins/overpass-pois
|
||||||
|
# for a real, in-repo example of the contract itself.
|
||||||
|
#
|
||||||
|
# `dirs` takes either one directory or an array of them (wrapped with
|
||||||
|
# Array() below) — app.rb passes both the built-in plugins/ (baked into
|
||||||
|
# the image, wiped and replaced on every deploy along with the rest of
|
||||||
|
# the checkout) and an optional second directory under the persistent
|
||||||
|
# data/ bind mount, so a private plugin dropped there survives a
|
||||||
|
# redeploy the same way an uploaded GPX file does.
|
||||||
|
module Plugins
|
||||||
|
class RunError < StandardError; end
|
||||||
|
|
||||||
|
# A bare directory name, same rule Sinatra's own :name route param
|
||||||
|
# already enforces (no "/"), but ".." alone would still resolve one
|
||||||
|
# level up via File.join — reject that too before it ever reaches the
|
||||||
|
# filesystem, same spirit as GpxStore's own path-escape guard.
|
||||||
|
NAME_PATTERN = /\A[A-Za-z0-9_-]+\z/
|
||||||
|
|
||||||
|
def self.list(dirs)
|
||||||
|
Array(dirs).flat_map do |dir|
|
||||||
|
Dir.glob(File.join(dir, "*", "plugin.json")).filter_map do |manifest_path|
|
||||||
|
name = File.basename(File.dirname(manifest_path))
|
||||||
|
manifest = JSON.parse(File.read(manifest_path))
|
||||||
|
{ "name" => name, "label" => manifest["label"] || name }
|
||||||
|
rescue JSON::ParserError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end.uniq { |plugin| plugin["name"] }.sort_by { |p| p["label"] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# bbox: [south, west, north, east]. Raises RunError with a short
|
||||||
|
# message on anything that isn't a clean run — an unknown plugin, a
|
||||||
|
# non-zero exit, or output that isn't a GeoJSON FeatureCollection — so
|
||||||
|
# the route can turn it into a single error response without knowing
|
||||||
|
# any of these details itself. When the same name exists under more
|
||||||
|
# than one of `dirs`, the first one that has it wins — same precedence
|
||||||
|
# as list's `uniq` above.
|
||||||
|
def self.run(dirs, name, bbox)
|
||||||
|
raise RunError, "unknown plugin" unless name =~ NAME_PATTERN
|
||||||
|
|
||||||
|
plugin_dir = Array(dirs).map { |dir| File.join(dir, name) }.find { |dir| File.file?(File.join(dir, "plugin.json")) }
|
||||||
|
raise RunError, "unknown plugin" unless plugin_dir
|
||||||
|
|
||||||
|
manifest_path = File.join(plugin_dir, "plugin.json")
|
||||||
|
|
||||||
|
command = begin
|
||||||
|
JSON.parse(File.read(manifest_path))["command"]
|
||||||
|
rescue JSON::ParserError
|
||||||
|
raise RunError, "plugin has an invalid plugin.json"
|
||||||
|
end
|
||||||
|
raise RunError, "plugin has no command" unless command
|
||||||
|
|
||||||
|
south, west, north, east = bbox
|
||||||
|
args = ["--south=#{south}", "--west=#{west}", "--north=#{north}", "--east=#{east}"]
|
||||||
|
|
||||||
|
stdout, stderr, status = Open3.capture3(command, *args, chdir: plugin_dir)
|
||||||
|
# First stderr line too — for a Ruby plugin that's the exception
|
||||||
|
# message (e.g. "...: unexpected response 406"), the only clue to why.
|
||||||
|
unless status.success?
|
||||||
|
reason = stderr.lines.first&.strip
|
||||||
|
raise RunError, ["plugin exited with status #{status.exitstatus}", reason].compact.reject(&:empty?).join(": ")
|
||||||
|
end
|
||||||
|
|
||||||
|
geojson = JSON.parse(stdout)
|
||||||
|
raise RunError, "plugin did not print a GeoJSON FeatureCollection" unless geojson.is_a?(Hash) && geojson["type"] == "FeatureCollection"
|
||||||
|
|
||||||
|
geojson
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
raise RunError, e.message
|
||||||
|
rescue JSON::ParserError
|
||||||
|
raise RunError, "plugin did not print valid JSON"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,745 @@
|
|||||||
|
require "open3"
|
||||||
|
require "net/http"
|
||||||
|
require "uri"
|
||||||
|
require "json"
|
||||||
|
require "tmpdir"
|
||||||
|
require "fileutils"
|
||||||
|
require "etc"
|
||||||
|
require_relative "hiking_trails"
|
||||||
|
|
||||||
|
module Maptool
|
||||||
|
# Regenerates data/offline.pmtiles and data/terrain.pmtiles for the
|
||||||
|
# currently *selected* regions (data/offline_regions.json, an array of
|
||||||
|
# Natural Earth iso_a2 country codes — see available_regions/
|
||||||
|
# selected_regions/set_selected_regions below). No job queue: each build
|
||||||
|
# runs on its own background Thread and reports into a Mutex-guarded
|
||||||
|
# status Hash a route can poll, the same shape app.rb's own GNSS poller
|
||||||
|
# (GNSS_LATEST/GNSS_LATEST_MUTEX) already uses.
|
||||||
|
module PmtilesBuilder
|
||||||
|
class BuildError < StandardError; end
|
||||||
|
|
||||||
|
# Natural Earth (public domain, no attribution required) 110m admin-0
|
||||||
|
# countries, filtered to Europe and trimmed to name/iso_a2/iso_a3 —
|
||||||
|
# see the plan this was built from for the exact filter. 110m is
|
||||||
|
# deliberately coarse (a km or so of border precision, not
|
||||||
|
# survey-grade) — fine for "which country to grab," a same-schema swap
|
||||||
|
# to Natural Earth's 50m/10m cut if that ever needs to be tighter.
|
||||||
|
REGIONS_FILE = File.join(__dir__, "data", "europe_countries.geojson")
|
||||||
|
|
||||||
|
# Every iso_a2 code in REGIONS_FILE mapped to its Geofabrik download
|
||||||
|
# slug (https://download.geofabrik.de/<slug>-latest.osm.pbf) — Geofabrik
|
||||||
|
# names regions, not ISO codes, and doesn't always split the way a
|
||||||
|
# country border does, so this table exists rather than deriving the
|
||||||
|
# slug from the country name. Two known quirks worth knowing about,
|
||||||
|
# not worth working around:
|
||||||
|
# - RU has no "europe/" prefix (Russia is its own top-level index,
|
||||||
|
# and unlike every other entry here is a single ~4GB file covering
|
||||||
|
# the whole country rather than a European-only slice).
|
||||||
|
# - GB's "great-britain" slug is England/Scotland/Wales only —
|
||||||
|
# Northern Ireland lives in "ireland-and-northern-ireland" instead
|
||||||
|
# (bundled with IE, not GB). Selecting GB alone won't include NI.
|
||||||
|
GEOFABRIK_SLUGS = {
|
||||||
|
"AL" => "europe/albania",
|
||||||
|
"AT" => "europe/austria",
|
||||||
|
"BY" => "europe/belarus",
|
||||||
|
"BE" => "europe/belgium",
|
||||||
|
"BA" => "europe/bosnia-herzegovina",
|
||||||
|
"BG" => "europe/bulgaria",
|
||||||
|
"HR" => "europe/croatia",
|
||||||
|
"CZ" => "europe/czech-republic",
|
||||||
|
"DK" => "europe/denmark",
|
||||||
|
"EE" => "europe/estonia",
|
||||||
|
"FI" => "europe/finland",
|
||||||
|
"FR" => "europe/france",
|
||||||
|
"DE" => "europe/germany",
|
||||||
|
"GR" => "europe/greece",
|
||||||
|
"HU" => "europe/hungary",
|
||||||
|
"IS" => "europe/iceland",
|
||||||
|
"IE" => "europe/ireland-and-northern-ireland",
|
||||||
|
"IT" => "europe/italy",
|
||||||
|
"XK" => "europe/kosovo",
|
||||||
|
"LV" => "europe/latvia",
|
||||||
|
"LT" => "europe/lithuania",
|
||||||
|
"LU" => "europe/luxembourg",
|
||||||
|
"MD" => "europe/moldova",
|
||||||
|
"ME" => "europe/montenegro",
|
||||||
|
"NL" => "europe/netherlands",
|
||||||
|
"MK" => "europe/macedonia",
|
||||||
|
"NO" => "europe/norway",
|
||||||
|
"PL" => "europe/poland",
|
||||||
|
"PT" => "europe/portugal",
|
||||||
|
"RO" => "europe/romania",
|
||||||
|
"RU" => "russia",
|
||||||
|
"RS" => "europe/serbia",
|
||||||
|
"SK" => "europe/slovakia",
|
||||||
|
"SI" => "europe/slovenia",
|
||||||
|
"ES" => "europe/spain",
|
||||||
|
"SE" => "europe/sweden",
|
||||||
|
"CH" => "europe/switzerland",
|
||||||
|
"UA" => "europe/ukraine",
|
||||||
|
"GB" => "europe/great-britain",
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# Mapterhorn (a Protomaps-affiliated project) publishes Terrarium-
|
||||||
|
# encoded terrain as pre-built PMTiles archives: one global
|
||||||
|
# planet.pmtiles (z0-12) plus ~450 regional chunks (z13 up to a
|
||||||
|
# per-region max, 13-18) — extract_offline below no longer touches
|
||||||
|
# Protomaps at all (see GEOFABRIK_SLUGS above), but terrain stays on
|
||||||
|
# this same "extract only the region's byte ranges from a remote
|
||||||
|
# archive" story via go-pmtiles, unrelated to the basemap swap.
|
||||||
|
MAPTERHORN_MANIFEST_URL = "https://download.mapterhorn.com/download_urls.json"
|
||||||
|
MAPTERHORN_PLANET_NAME = "planet.pmtiles"
|
||||||
|
# Deliberately *below* the online hillshade layer's own cap
|
||||||
|
# (public/js/overlays.js's ONLINE_TERRAIN_TILES source, maxzoom: 15) —
|
||||||
|
# raster DEM tiles don't compress away like vector ones do, so every
|
||||||
|
# extra zoom level roughly quadruples storage. z13 is each regional
|
||||||
|
# chunk's own native minzoom (see the comment above), so this is the
|
||||||
|
# cheapest useful cut: one raster level per chunk, not a 15-13=2-level
|
||||||
|
# pyramid on top of it. Bumped into a 181GB terrain.pmtiles for a
|
||||||
|
# 5-country selection before this was lowered — see docs/development.adoc.
|
||||||
|
TERRAIN_MAX_ZOOM = 13
|
||||||
|
|
||||||
|
OFFLINE_STATUS = { state: "idle" }
|
||||||
|
TERRAIN_STATUS = { state: "idle" }
|
||||||
|
OFFLINE_MUTEX = Mutex.new
|
||||||
|
TERRAIN_MUTEX = Mutex.new
|
||||||
|
|
||||||
|
def self.offline_status
|
||||||
|
OFFLINE_MUTEX.synchronize { OFFLINE_STATUS.dup }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.terrain_status
|
||||||
|
TERRAIN_MUTEX.synchronize { TERRAIN_STATUS.dup }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Test-only: the status hashes are process-wide state (same caveat as
|
||||||
|
# GPX_STORE/LIVETRACK_STORE in app.rb), so the suite needs a way to
|
||||||
|
# reset them between tests the way AppTestHelper#teardown already does
|
||||||
|
# for those.
|
||||||
|
def self.reset!
|
||||||
|
OFFLINE_MUTEX.synchronize { OFFLINE_STATUS.replace(state: "idle") }
|
||||||
|
TERRAIN_MUTEX.synchronize { TERRAIN_STATUS.replace(state: "idle") }
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- region selection ---
|
||||||
|
|
||||||
|
def self.available_regions
|
||||||
|
region_features.map { |f| { code: f["properties"]["iso_a2"], name: f["properties"]["name"] } }.sort_by { |r| r[:name] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.selected_regions(data_dir)
|
||||||
|
path = selected_regions_path(data_dir)
|
||||||
|
return [] unless File.exist?(path)
|
||||||
|
|
||||||
|
JSON.parse(File.read(path))
|
||||||
|
rescue JSON::ParserError
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.set_selected_regions(data_dir, codes)
|
||||||
|
unknown = codes - available_regions.map { |r| r[:code] }
|
||||||
|
raise BuildError, "unknown region code(s): #{unknown.join(", ")}" unless unknown.empty?
|
||||||
|
|
||||||
|
File.write(selected_regions_path(data_dir), JSON.generate(codes))
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.selected_regions_path(data_dir)
|
||||||
|
File.join(data_dir, "offline_regions.json")
|
||||||
|
end
|
||||||
|
private_class_method :selected_regions_path
|
||||||
|
|
||||||
|
def self.region_features
|
||||||
|
@region_features ||= JSON.parse(File.read(REGIONS_FILE))["features"]
|
||||||
|
end
|
||||||
|
private_class_method :region_features
|
||||||
|
|
||||||
|
# Still used by terrain (below) to compute the aggregate bbox Mapterhorn
|
||||||
|
# chunk intersection needs — offline.pmtiles no longer needs this at
|
||||||
|
# all, since each Geofabrik country file is already clipped to that
|
||||||
|
# country's real border (more accurate than this 110m polygon, not
|
||||||
|
# less), so there's no separate "--region=" precision step for it
|
||||||
|
# anymore.
|
||||||
|
def self.region_bbox(codes)
|
||||||
|
selected = region_features.select { |f| codes.include?(f["properties"]["iso_a2"]) }
|
||||||
|
raise BuildError, "no matching regions for #{codes.join(", ")}" if selected.empty?
|
||||||
|
|
||||||
|
coords = selected.flat_map { |f| ring_coordinates(f["geometry"]) }
|
||||||
|
lons = coords.map { |lng, _lat| lng }
|
||||||
|
lats = coords.map { |_lng, lat| lat }
|
||||||
|
[lats.min, lons.min, lats.max, lons.max]
|
||||||
|
end
|
||||||
|
private_class_method :region_bbox
|
||||||
|
|
||||||
|
def self.ring_coordinates(geometry)
|
||||||
|
case geometry["type"]
|
||||||
|
when "Polygon" then geometry["coordinates"].flatten(1)
|
||||||
|
when "MultiPolygon" then geometry["coordinates"].flatten(2)
|
||||||
|
else []
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :ring_coordinates
|
||||||
|
|
||||||
|
# --- builds ---
|
||||||
|
|
||||||
|
# Returns once the background Thread has started, not once the build
|
||||||
|
# finishes — raises BuildError (the caller turns this into an error
|
||||||
|
# response) if nothing's selected yet, or one's already running.
|
||||||
|
#
|
||||||
|
# download_extract/fetch_last_modified exist as injectable seams so
|
||||||
|
# tests can exercise the rest of this (scratch dir, osmium/planetiler
|
||||||
|
# invocation, atomic rename, the per-country cache below) without a
|
||||||
|
# real network call — same reasoning test_helper.rb already gives for
|
||||||
|
# keeping /api/plugins route tests off the real Overpass API.
|
||||||
|
def self.build_offline(data_dir:, download_extract: method(:download_extract), fetch_last_modified: method(:fetch_geofabrik_last_modified))
|
||||||
|
codes = selected_regions(data_dir)
|
||||||
|
raise BuildError, "select at least one region first" if codes.empty?
|
||||||
|
|
||||||
|
begin_run!(OFFLINE_MUTEX, OFFLINE_STATUS)
|
||||||
|
Thread.new do
|
||||||
|
run_job(OFFLINE_MUTEX, OFFLINE_STATUS) do |report_step|
|
||||||
|
extract_offline(codes, data_dir, download_extract, fetch_last_modified, report_step)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# fetch_manifest: same injectable-seam reasoning as download_extract
|
||||||
|
# above, for the one Mapterhorn manifest lookup this needs.
|
||||||
|
def self.build_terrain(data_dir:, fetch_manifest: method(:fetch_mapterhorn_manifest))
|
||||||
|
codes = selected_regions(data_dir)
|
||||||
|
raise BuildError, "select at least one region first" if codes.empty?
|
||||||
|
|
||||||
|
begin_run!(TERRAIN_MUTEX, TERRAIN_STATUS)
|
||||||
|
Thread.new do
|
||||||
|
run_job(TERRAIN_MUTEX, TERRAIN_STATUS) do |report_step|
|
||||||
|
build_terrain_pmtiles(codes, data_dir, fetch_manifest, report_step)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.begin_run!(mutex, status)
|
||||||
|
mutex.synchronize do
|
||||||
|
raise BuildError, "a build is already running" if status[:state] == "running"
|
||||||
|
status.replace(state: "running", started_at: Time.now.utc.iso8601)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :begin_run!
|
||||||
|
|
||||||
|
# Yields a report_step proc the build can call with a short
|
||||||
|
# human-readable description of what it's doing right now ("downloading
|
||||||
|
# SK (1/2)", "tiling with planetiler", ...) — surfaced as status[:step]
|
||||||
|
# so GET .../build-status can show progress beyond just "running since
|
||||||
|
# X" (see docs/development.adoc).
|
||||||
|
def self.run_job(mutex, status)
|
||||||
|
report_step = ->(text) { mutex.synchronize { status[:step] = text } }
|
||||||
|
yield report_step
|
||||||
|
mutex.synchronize { status.merge!(state: "done", finished_at: Time.now.utc.iso8601).delete(:message) }
|
||||||
|
rescue => e
|
||||||
|
mutex.synchronize { status.merge!(state: "error", finished_at: Time.now.utc.iso8601, message: e.message) }
|
||||||
|
ensure
|
||||||
|
mutex.synchronize { status.delete(:step) }
|
||||||
|
end
|
||||||
|
private_class_method :run_job
|
||||||
|
|
||||||
|
# --- offline.pmtiles ---
|
||||||
|
|
||||||
|
# One Geofabrik .osm.pbf per selected country (already clipped to that
|
||||||
|
# country's real border), osmium-merged into one file if more than one
|
||||||
|
# was selected (skipped entirely for a single country — nothing to
|
||||||
|
# merge), then tiled by Planetiler using its default OpenMapTiles
|
||||||
|
# profile straight to PMTiles — the same vector schema
|
||||||
|
# tiles.openfreemap.org's Liberty style already expects, so
|
||||||
|
# public/js/map.js's offlineStyle() can reuse ONLINE_STYLE_PATCH
|
||||||
|
# unchanged instead of a separate protomaps-themes-base palette.
|
||||||
|
def self.extract_offline(codes, data_dir, download_extract, fetch_last_modified, report_step)
|
||||||
|
scratch_dir = Dir.mktmpdir("maptool-offline")
|
||||||
|
sources = codes.each_with_index.map do |code, i|
|
||||||
|
slug = GEOFABRIK_SLUGS.fetch(code) { raise BuildError, "no Geofabrik source known for region #{code}" }
|
||||||
|
report_step.call(progress_step("fetching #{code}", codes, i))
|
||||||
|
cached_extract(code, slug, data_dir, download_extract, fetch_last_modified)
|
||||||
|
end
|
||||||
|
|
||||||
|
osm_path =
|
||||||
|
if sources.size == 1
|
||||||
|
sources.first["path"]
|
||||||
|
else
|
||||||
|
report_step.call("merging with osmium")
|
||||||
|
merged_path = File.join(scratch_dir, "merged.osm.pbf")
|
||||||
|
run_osmium!("merge", *sources.map { |s| s["path"] }, "-o", merged_path)
|
||||||
|
merged_path
|
||||||
|
end
|
||||||
|
|
||||||
|
report_step.call("tiling with planetiler")
|
||||||
|
# .tmp.pmtiles, not .pmtiles.tmp — Planetiler picks its output archive
|
||||||
|
# format (pmtiles vs mbtiles) from the file extension, so a ".tmp"
|
||||||
|
# suffix made it fail with "Unsupported format tmp" instead of
|
||||||
|
# actually tiling anything.
|
||||||
|
tmp_path = File.join(data_dir, "offline.tmp.pmtiles")
|
||||||
|
# --download: the OpenMapTiles profile also needs a few small auxiliary
|
||||||
|
# sources (water polygons, Natural Earth, lake centerlines) alongside
|
||||||
|
# the OSM extract itself — without this flag Planetiler expects them
|
||||||
|
# to already exist on disk and fails instead of fetching them. They
|
||||||
|
# land under Planetiler's own default "data/sources/", relative to
|
||||||
|
# this process's working directory (the app root, so in practice
|
||||||
|
# that's data_dir's own "sources" subdirectory) — fetched once,
|
||||||
|
# reused by every later build.
|
||||||
|
# --threads: Planetiler's own core-count autodetection can come up
|
||||||
|
# short in a container (cgroup/JVM detection quirks vary by runtime),
|
||||||
|
# leaving most of a many-core host idle — Etc.nprocessors asks the
|
||||||
|
# OS directly (sched_getaffinity on Linux), which correctly reflects
|
||||||
|
# whatever's actually usable by this process even under a container.
|
||||||
|
run_planetiler!("--osm-path=#{osm_path}", "--output=#{tmp_path}", "--download", "--threads=#{Etc.nprocessors}")
|
||||||
|
File.rename(tmp_path, File.join(data_dir, "offline.pmtiles"))
|
||||||
|
File.write(offline_meta_path(data_dir), JSON.generate(sources: sources.map { |s| s.slice("code", "slug", "last_modified") }))
|
||||||
|
|
||||||
|
report_step.call("extracting hiking trails")
|
||||||
|
extract_hiking_trails(sources, data_dir)
|
||||||
|
ensure
|
||||||
|
FileUtils.remove_entry(scratch_dir) if scratch_dir && Dir.exist?(scratch_dir)
|
||||||
|
File.delete(tmp_path) if tmp_path && File.exist?(tmp_path)
|
||||||
|
end
|
||||||
|
private_class_method :extract_offline
|
||||||
|
|
||||||
|
# One GeoJSON per country (data/hiking_trails/<CODE>.geojson), not one
|
||||||
|
# merged file — the frontend only ever needs to show a country at a
|
||||||
|
# time the viewer actually picks (see HikingTrailsControl's country
|
||||||
|
# checklist), and a combined multi-country file was needlessly making
|
||||||
|
# every viewer fetch every selected country's trails regardless.
|
||||||
|
#
|
||||||
|
# Runs over the same per-country .osm.pbf files `sources` above already
|
||||||
|
# downloaded/cached for offline.pmtiles itself — no separate download.
|
||||||
|
# Cached the same way geofabrik_cache is: skipped for a country whose
|
||||||
|
# extract is already newer than its source .osm.pbf, so growing the
|
||||||
|
# region selection by one country only extracts that one, not every
|
||||||
|
# already-covered country all over again.
|
||||||
|
#
|
||||||
|
# Best-effort per country: an extraction problem (a malformed PBF
|
||||||
|
# region, an osmium hiccup) shouldn't fail the whole offline.pmtiles
|
||||||
|
# build, or even the other countries in this same selection, over
|
||||||
|
# what's a bonus overlay, not the core map.
|
||||||
|
def self.extract_hiking_trails(sources, data_dir)
|
||||||
|
FileUtils.mkdir_p(hiking_trails_dir(data_dir))
|
||||||
|
sources.each do |source|
|
||||||
|
out_path = hiking_trails_country_path(data_dir, source["code"])
|
||||||
|
next if File.exist?(out_path) && File.mtime(out_path) >= File.mtime(source["path"])
|
||||||
|
|
||||||
|
tmp_path = "#{out_path}.tmp"
|
||||||
|
begin
|
||||||
|
File.write(tmp_path, JSON.generate(HikingTrails.extract(source["path"])))
|
||||||
|
File.rename(tmp_path, out_path)
|
||||||
|
rescue HikingTrails::ExtractError => e
|
||||||
|
File.delete(tmp_path) if File.exist?(tmp_path)
|
||||||
|
warn "hiking trails extraction failed for #{source["code"]}, skipping: #{e.message}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
private_class_method :extract_hiking_trails
|
||||||
|
|
||||||
|
def self.hiking_trails_dir(data_dir)
|
||||||
|
File.join(data_dir, "hiking_trails")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.hiking_trails_country_path(data_dir, code)
|
||||||
|
File.join(hiking_trails_dir(data_dir), "#{code}.geojson")
|
||||||
|
end
|
||||||
|
|
||||||
|
# Country codes with an already-extracted offline hiking-trails file,
|
||||||
|
# sorted — not necessarily the same as the *current* region selection
|
||||||
|
# (accumulates like geofabrik_cache does; a country deselected after
|
||||||
|
# being built once is left alone rather than swept up here).
|
||||||
|
def self.hiking_trails_countries(data_dir)
|
||||||
|
Dir.glob(File.join(hiking_trails_dir(data_dir), "*.geojson")).map { |path| File.basename(path, ".geojson") }.sort
|
||||||
|
end
|
||||||
|
|
||||||
|
# Renders a report_step string like "fetching PL (3/4) — done: SK, CZ —
|
||||||
|
# pending: HU" — so GET .../build-status shows not just "it's running"
|
||||||
|
# but which of the current build's sources is in flight and which are
|
||||||
|
# already behind/ahead of it.
|
||||||
|
def self.progress_step(action, labels, index)
|
||||||
|
done = labels[0...index]
|
||||||
|
pending = labels[(index + 1)..] || []
|
||||||
|
parts = ["#{action} (#{index + 1}/#{labels.size})"]
|
||||||
|
parts << "done: #{done.join(", ")}" unless done.empty?
|
||||||
|
parts << "pending: #{pending.join(", ")}" unless pending.empty?
|
||||||
|
parts.join(" — ")
|
||||||
|
end
|
||||||
|
private_class_method :progress_step
|
||||||
|
|
||||||
|
def self.geofabrik_url(slug)
|
||||||
|
"https://download.geofabrik.de/#{slug}-latest.osm.pbf"
|
||||||
|
end
|
||||||
|
private_class_method :geofabrik_url
|
||||||
|
|
||||||
|
# Per-country .osm.pbf cache, kept across builds under data_dir (not
|
||||||
|
# scratch_dir, which is wiped every run) — changing the region
|
||||||
|
# selection re-downloads only the countries that are new to it, and
|
||||||
|
# re-running the same selection re-downloads nothing unless Geofabrik's
|
||||||
|
# own Last-Modified for that country moved on. A stamp file next to
|
||||||
|
# each cached extract records the Last-Modified it was fetched with;
|
||||||
|
# a HEAD check (cheap) decides whether the multi-GB GET (not cheap) is
|
||||||
|
# skippable.
|
||||||
|
def self.cached_extract(code, slug, data_dir, download_extract, fetch_last_modified)
|
||||||
|
cache_dir = geofabrik_cache_dir(data_dir)
|
||||||
|
FileUtils.mkdir_p(cache_dir)
|
||||||
|
path = File.join(cache_dir, "#{code}.osm.pbf")
|
||||||
|
stamp_path = "#{path}.last_modified"
|
||||||
|
url = geofabrik_url(slug)
|
||||||
|
|
||||||
|
if File.exist?(path) && File.exist?(stamp_path)
|
||||||
|
current = begin
|
||||||
|
fetch_last_modified.call(url)
|
||||||
|
rescue BuildError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
return { "code" => code, "slug" => slug, "last_modified" => current, "path" => path } if current && current == File.read(stamp_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
last_modified = download_extract.call(url, path)
|
||||||
|
File.write(stamp_path, last_modified.to_s)
|
||||||
|
{ "code" => code, "slug" => slug, "last_modified" => last_modified, "path" => path }
|
||||||
|
end
|
||||||
|
private_class_method :cached_extract
|
||||||
|
|
||||||
|
def self.geofabrik_cache_dir(data_dir)
|
||||||
|
File.join(data_dir, "geofabrik_cache")
|
||||||
|
end
|
||||||
|
private_class_method :geofabrik_cache_dir
|
||||||
|
|
||||||
|
# Real network implementation of the download_extract seam above —
|
||||||
|
# streams the extract to dest (some of these are multi-GB — Net::HTTP's
|
||||||
|
# plain #get buffers the whole response body in memory first, which
|
||||||
|
# read_body's block form avoids) and returns its Last-Modified header
|
||||||
|
# (free off the same GET, no separate HEAD needed) for the sidecar
|
||||||
|
# meta / update-check below. Geofabrik 302s (e.g. .../europe/austria
|
||||||
|
# redirecting to wherever actually serves it) — followed manually
|
||||||
|
# since request_get's block form doesn't do this itself.
|
||||||
|
def self.download_extract(url, dest, limit: 5)
|
||||||
|
raise BuildError, "too many redirects downloading #{url}" if limit.zero?
|
||||||
|
|
||||||
|
uri = URI(url)
|
||||||
|
last_modified = nil
|
||||||
|
redirect_to = nil
|
||||||
|
Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |http|
|
||||||
|
http.request_get(uri) do |response|
|
||||||
|
case response
|
||||||
|
when Net::HTTPSuccess
|
||||||
|
last_modified = response["last-modified"]
|
||||||
|
File.open(dest, "wb") { |file| response.read_body { |chunk| file.write(chunk) } }
|
||||||
|
when Net::HTTPRedirection
|
||||||
|
redirect_to = URI.join(url, response["location"]).to_s
|
||||||
|
else
|
||||||
|
raise BuildError, "could not download #{url} (HTTP #{response.code})"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return download_extract(redirect_to, dest, limit: limit - 1) if redirect_to
|
||||||
|
|
||||||
|
last_modified
|
||||||
|
end
|
||||||
|
|
||||||
|
# Sidecar recording which Geofabrik sources (country code, slug, and
|
||||||
|
# that request's Last-Modified) the on-disk offline.pmtiles was
|
||||||
|
# actually built from — the file itself carries no such record. nil if
|
||||||
|
# never built (or built before this existed).
|
||||||
|
def self.offline_meta(data_dir)
|
||||||
|
read_json(offline_meta_path(data_dir))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Compares each currently-selected country's live Geofabrik
|
||||||
|
# Last-Modified against what's recorded, without downloading anything
|
||||||
|
# — nil (not true/false) if there's nothing to compare yet (no local
|
||||||
|
# build, nothing selected) or a check couldn't be completed, so the
|
||||||
|
# caller can tell "don't know" apart from "no". Also naturally reports
|
||||||
|
# "yes" if the selection changed since the last build (a country now
|
||||||
|
# relevant that wasn't recorded, or vice versa) — same reasoning
|
||||||
|
# terrain_update_available? below already uses.
|
||||||
|
def self.offline_update_available?(data_dir, fetch_last_modified: method(:fetch_geofabrik_last_modified))
|
||||||
|
meta = offline_meta(data_dir)
|
||||||
|
return nil unless meta
|
||||||
|
|
||||||
|
codes = selected_regions(data_dir)
|
||||||
|
return nil if codes.empty?
|
||||||
|
|
||||||
|
current = codes.map do |code|
|
||||||
|
slug = GEOFABRIK_SLUGS[code]
|
||||||
|
return nil unless slug
|
||||||
|
|
||||||
|
{ "code" => code, "last_modified" => fetch_last_modified.call(geofabrik_url(slug)) }
|
||||||
|
end.sort_by { |s| s["code"] }
|
||||||
|
recorded = (meta["sources"] || []).map { |s| s.slice("code", "last_modified") }.sort_by { |s| s["code"] }
|
||||||
|
current != recorded
|
||||||
|
rescue BuildError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.fetch_geofabrik_last_modified(url, limit: 5)
|
||||||
|
raise BuildError, "too many redirects reaching #{url}" if limit.zero?
|
||||||
|
|
||||||
|
uri = URI(url)
|
||||||
|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.head(uri) }
|
||||||
|
case response
|
||||||
|
when Net::HTTPSuccess then response["last-modified"]
|
||||||
|
when Net::HTTPRedirection then fetch_geofabrik_last_modified(URI.join(url, response["location"]).to_s, limit: limit - 1)
|
||||||
|
else raise BuildError, "could not reach #{url}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.offline_meta_path(data_dir)
|
||||||
|
File.join(data_dir, "offline.pmtiles.meta.json")
|
||||||
|
end
|
||||||
|
private_class_method :offline_meta_path
|
||||||
|
|
||||||
|
# Deletes offline.pmtiles, its sidecar meta, and the whole per-country
|
||||||
|
# download cache (data/geofabrik_cache/) — "start over" for the offline
|
||||||
|
# basemap. The current selection (data/offline_regions.json) is left
|
||||||
|
# alone, so a following build just re-downloads it fresh.
|
||||||
|
def self.delete_offline_data(data_dir)
|
||||||
|
OFFLINE_MUTEX.synchronize { raise BuildError, "a build is already running" if OFFLINE_STATUS[:state] == "running" }
|
||||||
|
|
||||||
|
File.delete(File.join(data_dir, "offline.pmtiles")) if File.exist?(File.join(data_dir, "offline.pmtiles"))
|
||||||
|
File.delete(offline_meta_path(data_dir)) if File.exist?(offline_meta_path(data_dir))
|
||||||
|
FileUtils.remove_entry(hiking_trails_dir(data_dir)) if Dir.exist?(hiking_trails_dir(data_dir))
|
||||||
|
cache_dir = geofabrik_cache_dir(data_dir)
|
||||||
|
FileUtils.remove_entry(cache_dir) if Dir.exist?(cache_dir)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Deletes just one country's cached .osm.pbf (and its Last-Modified
|
||||||
|
# stamp) — e.g. to force a fresh download without touching the others,
|
||||||
|
# or to reclaim space for a country no longer selected.
|
||||||
|
def self.delete_cached_extract(data_dir, code)
|
||||||
|
OFFLINE_MUTEX.synchronize { raise BuildError, "a build is already running" if OFFLINE_STATUS[:state] == "running" }
|
||||||
|
|
||||||
|
cache_dir = geofabrik_cache_dir(data_dir)
|
||||||
|
path = File.join(cache_dir, "#{code}.osm.pbf")
|
||||||
|
File.delete(path) if File.exist?(path)
|
||||||
|
File.delete("#{path}.last_modified") if File.exist?("#{path}.last_modified")
|
||||||
|
end
|
||||||
|
|
||||||
|
# What's actually sitting in the per-country download cache right now —
|
||||||
|
# not the same thing as offline_meta's "sources", which only reflects
|
||||||
|
# whatever the *last successful build* used. A country can be cached
|
||||||
|
# without ever having been built (selected, then deselected again
|
||||||
|
# before Apply) or built from without still being selected, and this is
|
||||||
|
# what lets the region picker show/clear either case.
|
||||||
|
def self.cached_extracts(data_dir)
|
||||||
|
cache_dir = geofabrik_cache_dir(data_dir)
|
||||||
|
return [] unless Dir.exist?(cache_dir)
|
||||||
|
|
||||||
|
Dir.glob(File.join(cache_dir, "*.osm.pbf")).map do |path|
|
||||||
|
stamp_path = "#{path}.last_modified"
|
||||||
|
{
|
||||||
|
"code" => File.basename(path, ".osm.pbf"),
|
||||||
|
"size" => File.size(path),
|
||||||
|
"last_modified" => File.exist?(stamp_path) ? File.read(stamp_path) : nil,
|
||||||
|
}
|
||||||
|
end.sort_by { |s| s["code"] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- terrain.pmtiles ---
|
||||||
|
|
||||||
|
# planet.pmtiles (z0-12, always) plus whichever regional chunks
|
||||||
|
# intersect the selection's aggregate bbox (z13+, zero or more) are
|
||||||
|
# each `extract`ed on their own using the *same* --region= file, then
|
||||||
|
# combined with `pmtiles merge` if there was more than one. Always
|
||||||
|
# safe to merge regardless of how many countries are selected or
|
||||||
|
# whether they're adjacent: each Mapterhorn chunk is its own
|
||||||
|
# pre-partitioned, non-overlapping source grid cell, so two different
|
||||||
|
# chunks' extracts can never share a tile id — the only thing `merge`
|
||||||
|
# refuses (see pmtiles/merge.go's own disjointness check). What would
|
||||||
|
# NOT be safe is extracting the *same* source once per country and
|
||||||
|
# merging those — adjacent countries' region-extracts would share
|
||||||
|
# border tiles (extraction copies whole tiles, it doesn't clip vector
|
||||||
|
# content to the polygon) — so this always does one extract per
|
||||||
|
# *source archive*, never one per country.
|
||||||
|
def self.build_terrain_pmtiles(codes, data_dir, fetch_manifest, report_step)
|
||||||
|
report_step.call("fetching mapterhorn manifest")
|
||||||
|
items = fetch_manifest.call
|
||||||
|
planet = items.find { |item| item["name"] == MAPTERHORN_PLANET_NAME }
|
||||||
|
raise BuildError, "mapterhorn manifest is missing #{MAPTERHORN_PLANET_NAME}" unless planet
|
||||||
|
|
||||||
|
scratch_dir = Dir.mktmpdir("maptool-terrain")
|
||||||
|
south, west, north, east = region_bbox(codes)
|
||||||
|
region_path = File.join(scratch_dir, "region.geojson")
|
||||||
|
selected = region_features.select { |f| codes.include?(f["properties"]["iso_a2"]) }
|
||||||
|
File.write(region_path, JSON.generate(type: "FeatureCollection", features: selected))
|
||||||
|
|
||||||
|
chunks = items.select { |item| item["name"] != MAPTERHORN_PLANET_NAME && bbox_intersects?(item, south, west, north, east) }
|
||||||
|
|
||||||
|
# .tmp.pmtiles, not .pmtiles.tmp — same reasoning as offline.pmtiles'
|
||||||
|
# own tmp_path above, in case go-pmtiles also derives format from
|
||||||
|
# the extension for its "merge" output.
|
||||||
|
tmp_path = File.join(data_dir, "terrain.tmp.pmtiles")
|
||||||
|
sources = [planet, *chunks]
|
||||||
|
labels = sources.map { |item| item["name"] }
|
||||||
|
extracts = sources.each_with_index.map do |item, i|
|
||||||
|
report_step.call(progress_step("extracting #{item["name"]}", labels, i))
|
||||||
|
extract_path = File.join(scratch_dir, "extract-#{i}.pmtiles")
|
||||||
|
# --overfetch=0: go-pmtiles' default (0.05) merges nearby byte
|
||||||
|
# ranges into fewer, bigger HTTP requests to save round-trips —
|
||||||
|
# for planet.pmtiles (global z0-12, tens of GB) covering a
|
||||||
|
# multi-country selection that collapsed the whole extract into
|
||||||
|
# a *single* ~60GB request, which then had no way to resume after
|
||||||
|
# an HTTP/2 stream reset 11 minutes in (see the "died again"
|
||||||
|
# 2026-09-12 build). Disabling merging trades that one fragile,
|
||||||
|
# unresumable mega-request for many small ones spread over
|
||||||
|
# --download-threads (default 4) — slower on a clean run, but no
|
||||||
|
# longer one dropped stream away from losing 20 minutes of transfer.
|
||||||
|
run_pmtiles!("extract", item["url"], extract_path, "--region=#{region_path}", "--maxzoom=#{TERRAIN_MAX_ZOOM}", "--overfetch=0")
|
||||||
|
extract_path
|
||||||
|
end
|
||||||
|
|
||||||
|
if extracts.size == 1
|
||||||
|
File.rename(extracts.first, tmp_path)
|
||||||
|
else
|
||||||
|
report_step.call("merging with pmtiles")
|
||||||
|
run_pmtiles!("merge", *extracts, tmp_path)
|
||||||
|
end
|
||||||
|
File.rename(tmp_path, File.join(data_dir, "terrain.pmtiles"))
|
||||||
|
File.write(terrain_meta_path(data_dir), JSON.generate(sources: [planet, *chunks].map { |i| { name: i["name"], md5sum: i["md5sum"] } }))
|
||||||
|
ensure
|
||||||
|
FileUtils.remove_entry(scratch_dir) if scratch_dir && Dir.exist?(scratch_dir)
|
||||||
|
File.delete(tmp_path) if tmp_path && File.exist?(tmp_path)
|
||||||
|
end
|
||||||
|
private_class_method :build_terrain_pmtiles
|
||||||
|
|
||||||
|
def self.bbox_intersects?(item, south, west, north, east)
|
||||||
|
item["min_lon"] < east && item["max_lon"] > west && item["min_lat"] < north && item["max_lat"] > south
|
||||||
|
end
|
||||||
|
private_class_method :bbox_intersects?
|
||||||
|
|
||||||
|
def self.fetch_mapterhorn_manifest
|
||||||
|
response = Net::HTTP.get_response(URI(MAPTERHORN_MANIFEST_URL))
|
||||||
|
raise BuildError, "could not reach #{MAPTERHORN_MANIFEST_URL}" unless response.is_a?(Net::HTTPSuccess)
|
||||||
|
|
||||||
|
JSON.parse(response.body).fetch("items")
|
||||||
|
rescue JSON::ParserError
|
||||||
|
raise BuildError, "mapterhorn manifest was not valid JSON"
|
||||||
|
end
|
||||||
|
private_class_method :fetch_mapterhorn_manifest
|
||||||
|
|
||||||
|
# Sidecar recording which Mapterhorn sources (name + md5sum) the on-disk
|
||||||
|
# terrain.pmtiles was actually extracted+merged from. nil if never built.
|
||||||
|
def self.terrain_meta(data_dir)
|
||||||
|
read_json(terrain_meta_path(data_dir))
|
||||||
|
end
|
||||||
|
|
||||||
|
# Same "don't know" vs. "no" distinction as offline_update_available?
|
||||||
|
# above. Recomputes which sources *would* be selected for the current
|
||||||
|
# selection (same manifest + bbox_intersects? this build itself uses)
|
||||||
|
# and compares their md5sums against what's recorded — so this also
|
||||||
|
# correctly says "yes" if the selection changed since the last build
|
||||||
|
# (a chunk now relevant that wasn't recorded), not just if Mapterhorn's
|
||||||
|
# own data moved on.
|
||||||
|
def self.terrain_update_available?(data_dir, fetch_manifest: method(:fetch_mapterhorn_manifest))
|
||||||
|
meta = terrain_meta(data_dir)
|
||||||
|
return nil unless meta
|
||||||
|
|
||||||
|
codes = selected_regions(data_dir)
|
||||||
|
return nil if codes.empty?
|
||||||
|
|
||||||
|
items = fetch_manifest.call
|
||||||
|
planet = items.find { |item| item["name"] == MAPTERHORN_PLANET_NAME }
|
||||||
|
return nil unless planet
|
||||||
|
|
||||||
|
south, west, north, east = region_bbox(codes)
|
||||||
|
chunks = items.select { |item| item["name"] != MAPTERHORN_PLANET_NAME && bbox_intersects?(item, south, west, north, east) }
|
||||||
|
current = [planet, *chunks].map { |i| { "name" => i["name"], "md5sum" => i["md5sum"] } }.sort_by { |s| s["name"] }
|
||||||
|
current != (meta["sources"] || []).sort_by { |s| s["name"] }
|
||||||
|
rescue BuildError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.terrain_meta_path(data_dir)
|
||||||
|
File.join(data_dir, "terrain.pmtiles.meta.json")
|
||||||
|
end
|
||||||
|
private_class_method :terrain_meta_path
|
||||||
|
|
||||||
|
# Deletes terrain.pmtiles and its sidecar meta — same "start over"
|
||||||
|
# reasoning as delete_offline_data above. No download cache to clean up
|
||||||
|
# here: terrain.pmtiles' pmtiles-extract step reads its sources over
|
||||||
|
# HTTP range requests rather than downloading a whole archive first.
|
||||||
|
def self.delete_terrain_data(data_dir)
|
||||||
|
TERRAIN_MUTEX.synchronize { raise BuildError, "a build is already running" if TERRAIN_STATUS[:state] == "running" }
|
||||||
|
|
||||||
|
File.delete(File.join(data_dir, "terrain.pmtiles")) if File.exist?(File.join(data_dir, "terrain.pmtiles"))
|
||||||
|
File.delete(terrain_meta_path(data_dir)) if File.exist?(terrain_meta_path(data_dir))
|
||||||
|
end
|
||||||
|
|
||||||
|
# --- shared ---
|
||||||
|
|
||||||
|
def self.read_json(path)
|
||||||
|
File.exist?(path) ? JSON.parse(File.read(path)) : nil
|
||||||
|
rescue JSON::ParserError
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
private_class_method :read_json
|
||||||
|
|
||||||
|
# ENV read lazily (not load-time constants) so tests can point these at
|
||||||
|
# fake binaries without a require-order dance.
|
||||||
|
def self.pmtiles_bin
|
||||||
|
ENV.fetch("PMTILES_BIN", "pmtiles")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.osmium_bin
|
||||||
|
ENV.fetch("OSMIUM_BIN", "osmium")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.java_bin
|
||||||
|
ENV.fetch("JAVA_BIN", "java")
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.planetiler_jar
|
||||||
|
ENV.fetch("PLANETILER_JAR", "/usr/local/lib/planetiler.jar")
|
||||||
|
end
|
||||||
|
|
||||||
|
# go-pmtiles' extract logs one progress line per chunk fetched
|
||||||
|
# ("fetching N dirs, M chunks, K requests") — fine on a terminal where
|
||||||
|
# it overwrites itself, but captured non-interactively that's one line
|
||||||
|
# per chunk, easily hundreds of thousands of characters for a big
|
||||||
|
# region. The actual failure is always at the end, so keep only the
|
||||||
|
# tail — same story for osmium/planetiler's own chatty stdout.
|
||||||
|
OUTPUT_TAIL_LINES = 40
|
||||||
|
|
||||||
|
# Some CLI tools (go-pmtiles included) write their actual error to
|
||||||
|
# stdout rather than stderr — combine both rather than risk raising a
|
||||||
|
# BuildError with nothing useful in it.
|
||||||
|
def self.command_output(stdout, stderr)
|
||||||
|
lines = [stdout, stderr].map(&:strip).reject(&:empty?).join("\n").lines
|
||||||
|
return lines.join if lines.size <= OUTPUT_TAIL_LINES
|
||||||
|
|
||||||
|
omitted = lines.size - OUTPUT_TAIL_LINES
|
||||||
|
"(#{omitted} earlier lines omitted)\n#{lines.last(OUTPUT_TAIL_LINES).join}"
|
||||||
|
end
|
||||||
|
private_class_method :command_output
|
||||||
|
|
||||||
|
# extract/merge stream tens of GB over HTTP range requests; retry a
|
||||||
|
# transient failure (e.g. a mid-transfer HTTP/2 stream reset from the
|
||||||
|
# remote) a couple times before giving up, rather than forcing a full
|
||||||
|
# re-download on every hiccup.
|
||||||
|
def self.run_pmtiles!(*args, attempts: 3)
|
||||||
|
attempts.times do |i|
|
||||||
|
stdout, stderr, status = Open3.capture3(pmtiles_bin, *args)
|
||||||
|
return if status.success?
|
||||||
|
raise BuildError, "pmtiles exited with status #{status.exitstatus}: #{command_output(stdout, stderr)}" if i == attempts - 1
|
||||||
|
end
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
raise BuildError, "could not run '#{pmtiles_bin}' (#{e.message}) — is go-pmtiles installed and on PATH? See docs/development.adoc."
|
||||||
|
end
|
||||||
|
private_class_method :run_pmtiles!
|
||||||
|
|
||||||
|
def self.run_osmium!(*args)
|
||||||
|
stdout, stderr, status = Open3.capture3(osmium_bin, *args)
|
||||||
|
raise BuildError, "osmium exited with status #{status.exitstatus}: #{command_output(stdout, stderr)}" unless status.success?
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
raise BuildError, "could not run '#{osmium_bin}' (#{e.message}) — is osmium-tool installed and on PATH? See docs/development.adoc."
|
||||||
|
end
|
||||||
|
private_class_method :run_osmium!
|
||||||
|
|
||||||
|
def self.run_planetiler!(*args)
|
||||||
|
stdout, stderr, status = Open3.capture3(java_bin, "-jar", planetiler_jar, *args)
|
||||||
|
raise BuildError, "planetiler exited with status #{status.exitstatus}: #{command_output(stdout, stderr)}" unless status.success?
|
||||||
|
rescue Errno::ENOENT, Errno::EACCES => e
|
||||||
|
raise BuildError, "could not run '#{java_bin} -jar #{planetiler_jar}' (#{e.message}) — is Java and planetiler.jar installed? See docs/development.adoc."
|
||||||
|
end
|
||||||
|
private_class_method :run_planetiler!
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"label": "Camper POIs (Overpass)",
|
||||||
|
"command": "./run.rb"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# The reference example of the plugin contract (see lib/maptool/plugins.rb):
|
||||||
|
# read --south/--west/--north/--east, print a GeoJSON FeatureCollection to
|
||||||
|
# stdout. Thin wrapper around lib/maptool/overpass_pois.rb, which already
|
||||||
|
# did all the real work for the "rake overpass:pois" dev-time task this
|
||||||
|
# plugin now sits alongside as a second way to run the same query.
|
||||||
|
|
||||||
|
require_relative "../../lib/maptool/overpass_pois"
|
||||||
|
|
||||||
|
args = ARGV.to_h { |arg| arg.sub(/\A--/, "").split("=", 2) }
|
||||||
|
bbox = %w[south west north east].map { |key| Float(args.fetch(key)) }
|
||||||
|
|
||||||
|
response = Maptool::OverpassPois.fetch(bbox)
|
||||||
|
puts JSON.generate(Maptool::OverpassPois.to_geojson(response))
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// No server-rendered flag for this exists, and adding one would be more
|
||||||
|
// plumbing than reusing what's already on the page: the topbar's own
|
||||||
|
// logged_in check (views/index.erb) already renders a "Log out" link only
|
||||||
|
// when logged in. Login state never changes without a full page reload
|
||||||
|
// (there's no client-side login/logout), so this only needs checking once
|
||||||
|
// per use, not reactively/on an event.
|
||||||
|
export function isLoggedIn() {
|
||||||
|
return !!document.querySelector('a[href="/logout"]');
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { addProtocol } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
import { ONLINE_TERRAIN_TILES } from "/js/overlays.js";
|
||||||
|
import { firstSymbolLayerId } from "/js/radar.js";
|
||||||
|
import { CONTOUR_ICON } from "/js/icons.js";
|
||||||
|
|
||||||
|
const SOURCE_ID = "contour-lines";
|
||||||
|
const LINE_LAYER_ID = "contour-lines-line";
|
||||||
|
|
||||||
|
// Topographic contour lines, generated client-side by the vendored
|
||||||
|
// maplibre-contour plugin (public/vendor/maplibre-contour/) from the same
|
||||||
|
// online Terrarium DEM tiles overlays.js's HillshadeControl already
|
||||||
|
// fetches. Online only, unlike Terrain shading/Terrain shadow: the
|
||||||
|
// plugin's DemSource has no public hook to route its own tile fetch
|
||||||
|
// through the pmtiles:// protocol HillshadeControl uses for the offline
|
||||||
|
// basemap, so — same story as the Wikipedia layer and Radar — this just
|
||||||
|
// quietly renders nothing without a network.
|
||||||
|
export class ContourControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._active = false;
|
||||||
|
this._demSource = new window.mlcontour.DemSource({
|
||||||
|
url: ONLINE_TERRAIN_TILES[0],
|
||||||
|
encoding: "terrarium",
|
||||||
|
maxzoom: 15,
|
||||||
|
worker: true,
|
||||||
|
cacheSize: 100,
|
||||||
|
});
|
||||||
|
this._demSource.setupMaplibre({ addProtocol });
|
||||||
|
|
||||||
|
// setStyle() (switching Online/Offline) tears down all custom sources/
|
||||||
|
// layers — put contour lines back if they were on, same as every other
|
||||||
|
// Overlays control.
|
||||||
|
map.on("style.load", () => {
|
||||||
|
if (this._active) this._addLayer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${CONTOUR_ICON}</span>
|
||||||
|
<span>Contour lines</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
||||||
|
this._checkbox.addEventListener("change", (event) => this._toggle(event.target.checked));
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle(active) {
|
||||||
|
this._active = active;
|
||||||
|
if (active) this._addLayer();
|
||||||
|
else this._removeLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
_addLayer() {
|
||||||
|
if (this._map.getSource(SOURCE_ID)) return;
|
||||||
|
|
||||||
|
// Starting-point thresholds (README's own generic example) — meters,
|
||||||
|
// more/tighter contours at higher zoom. Expect to eyeball-tune against
|
||||||
|
// real Tatras-scale terrain.
|
||||||
|
const tiles = [
|
||||||
|
this._demSource.contourProtocolUrl({
|
||||||
|
thresholds: {
|
||||||
|
11: [200, 1000],
|
||||||
|
12: [100, 500],
|
||||||
|
14: [50, 200],
|
||||||
|
15: [20, 100],
|
||||||
|
},
|
||||||
|
contourLayer: "contours",
|
||||||
|
elevationKey: "ele",
|
||||||
|
levelKey: "level",
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
this._map.addSource(SOURCE_ID, { type: "vector", tiles, maxzoom: 15 });
|
||||||
|
this._map.addLayer(
|
||||||
|
{
|
||||||
|
id: LINE_LAYER_ID,
|
||||||
|
type: "line",
|
||||||
|
source: SOURCE_ID,
|
||||||
|
"source-layer": "contours",
|
||||||
|
paint: {
|
||||||
|
// Roughly the same brown as the mid-slope band of overlays.js's
|
||||||
|
// own ELEVATION_COLOR_RAMP, so this reads as part of the same
|
||||||
|
// terrain treatment rather than a bolted-on layer.
|
||||||
|
"line-color": "#8a6d3b",
|
||||||
|
"line-width": ["match", ["get", "level"], 1, 1.2, 0.6],
|
||||||
|
"line-opacity": 0.6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
firstSymbolLayerId(this._map),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeLayer() {
|
||||||
|
if (this._map.getLayer(LINE_LAYER_ID)) this._map.removeLayer(LINE_LAYER_ID);
|
||||||
|
if (this._map.getSource(SOURCE_ID)) this._map.removeSource(SOURCE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
// Elevation lookups against the same Terrarium-encoded terrain tiles
|
||||||
|
// overlays.js's HillshadeControl already renders — pulled into its own file
|
||||||
|
// since TODO.adoc lists two more near-future features (slope readout, solar
|
||||||
|
// calculator) that will need the same "elevation at a point" primitive,
|
||||||
|
// shadow.js's raymarch being the first real consumer.
|
||||||
|
|
||||||
|
import { ONLINE_TERRAIN_TILES } from "/js/overlays.js";
|
||||||
|
|
||||||
|
const DEM_ZOOM = 10; // fixed lookup zoom, independent of the map's own display zoom
|
||||||
|
|
||||||
|
const tileCache = new Map(); // "z/x/y" -> Promise<{elevations,size}|null>, so concurrent lookups on the same not-yet-fetched tile share one fetch
|
||||||
|
let offlinePmtiles = null;
|
||||||
|
|
||||||
|
// fracX/fracY (not pixel coordinates): the tile hasn't been fetched yet at
|
||||||
|
// this point, so its actual pixel size isn't known — online tiles are
|
||||||
|
// 256px PNG, offline ones (Mapterhorn, see lib/maptool/pmtiles_builder.rb)
|
||||||
|
// are 512px WebP. elevationAt scales these once the real size is known.
|
||||||
|
function lngLatToTile(lng, lat, zoom) {
|
||||||
|
const n = 2 ** zoom;
|
||||||
|
const x = ((lng + 180) / 360) * n;
|
||||||
|
const latRad = (lat * Math.PI) / 180;
|
||||||
|
const y = ((1 - Math.log(Math.tan(latRad) + 1 / Math.cos(latRad)) / Math.PI) / 2) * n;
|
||||||
|
const tileX = Math.floor(x);
|
||||||
|
const tileY = Math.floor(y);
|
||||||
|
return { tileX, tileY, fracX: x - tileX, fracY: y - tileY };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function decodeTerrarium(bytes, mime) {
|
||||||
|
const bitmap = await createImageBitmap(new Blob([bytes], { type: mime }));
|
||||||
|
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.drawImage(bitmap, 0, 0);
|
||||||
|
const { data } = ctx.getImageData(0, 0, bitmap.width, bitmap.height);
|
||||||
|
const elevations = new Int16Array(bitmap.width * bitmap.height);
|
||||||
|
for (let i = 0; i < elevations.length; i++) {
|
||||||
|
elevations[i] = data[i * 4] * 256 + data[i * 4 + 1] + data[i * 4 + 2] / 256 - 32768;
|
||||||
|
}
|
||||||
|
return { elevations, size: bitmap.width };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTileBytes(z, x, y, offlineAvailable) {
|
||||||
|
if (offlineAvailable) {
|
||||||
|
if (!offlinePmtiles) offlinePmtiles = new window.pmtiles.PMTiles(`${window.location.origin}/tiles/terrain.pmtiles`);
|
||||||
|
const result = await offlinePmtiles.getZxy(z, x, y);
|
||||||
|
return result ? { bytes: new Uint8Array(result.data), mime: "image/webp" } : null;
|
||||||
|
}
|
||||||
|
const url = ONLINE_TERRAIN_TILES[0].replace("{z}", z).replace("{x}", x).replace("{y}", y);
|
||||||
|
const response = await fetch(url);
|
||||||
|
return response.ok ? { bytes: new Uint8Array(await response.arrayBuffer()), mime: "image/png" } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fetchTile(z, x, y, offlineAvailable) {
|
||||||
|
const key = `${z}/${x}/${y}`;
|
||||||
|
let cached = tileCache.get(key);
|
||||||
|
if (cached) return cached;
|
||||||
|
|
||||||
|
const promise = fetchTileBytes(z, x, y, offlineAvailable)
|
||||||
|
.then((result) => (result ? decodeTerrarium(result.bytes, result.mime) : null))
|
||||||
|
.catch(() => null);
|
||||||
|
tileCache.set(key, promise);
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> Promise<number|null> — null on a fetch/decode failure, or a point
|
||||||
|
// falling outside a partial offline extract's zoom range.
|
||||||
|
export async function elevationAt(lng, lat, offlineAvailable) {
|
||||||
|
const { tileX, tileY, fracX, fracY } = lngLatToTile(lng, lat, DEM_ZOOM);
|
||||||
|
const tile = await fetchTile(DEM_ZOOM, tileX, tileY, offlineAvailable);
|
||||||
|
if (!tile) return null;
|
||||||
|
|
||||||
|
const px = Math.min(tile.size - 1, Math.floor(fracX * tile.size));
|
||||||
|
const py = Math.min(tile.size - 1, Math.floor(fracY * tile.size));
|
||||||
|
return tile.elevations[py * tile.size + px];
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
import { GNSS_ICON } from "/js/icons.js";
|
import { GNSS_ICON } from "/js/icons.js";
|
||||||
|
import { isLoggedIn } from "/js/auth.js";
|
||||||
|
|
||||||
const POSITION_URL = "/api/gnss/position";
|
const POSITION_URL = "/api/gnss/position";
|
||||||
const POLL_INTERVAL_MS = 1500;
|
const POLL_INTERVAL_MS = 1500;
|
||||||
@@ -15,6 +16,16 @@ export class GnssControl {
|
|||||||
this._marker = null;
|
this._marker = null;
|
||||||
this._timer = null;
|
this._timer = null;
|
||||||
this._enabled = false;
|
this._enabled = false;
|
||||||
|
this._enabledListeners = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read by LocateControl: if it's mid-tracking on the browser-geolocation
|
||||||
|
// fallback when GNSS turns on, GNSS should take over there and then, not
|
||||||
|
// only the next time the button happens to get clicked — the browser's
|
||||||
|
// own (often less accurate) position has no business still being used
|
||||||
|
// once the app's own receiver has a fix.
|
||||||
|
onEnabledChange(callback) {
|
||||||
|
this._enabledListeners.push(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(sidebar) {
|
mount(sidebar) {
|
||||||
@@ -31,6 +42,12 @@ export class GnssControl {
|
|||||||
this._status = this._container.querySelector(".gnss-control__status");
|
this._status = this._container.querySelector(".gnss-control__status");
|
||||||
this._icon.addEventListener("click", () => this._setEnabled(!this._enabled));
|
this._icon.addEventListener("click", () => this._setEnabled(!this._enabled));
|
||||||
|
|
||||||
|
// GET /api/gnss/position itself requires login too (app.rb) — hidden
|
||||||
|
// here rather than shown and failing on click, same reasoning as
|
||||||
|
// gpx.js's write-action controls: it reveals where the receiver
|
||||||
|
// physically is, not something to leave open to anyone with the URL.
|
||||||
|
if (!isLoggedIn()) this._container.hidden = true;
|
||||||
|
|
||||||
sidebar.appendChild(this._container);
|
sidebar.appendChild(this._container);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -46,6 +63,7 @@ export class GnssControl {
|
|||||||
this._removeMarker();
|
this._removeMarker();
|
||||||
this._status.textContent = "";
|
this._status.textContent = "";
|
||||||
}
|
}
|
||||||
|
this._enabledListeners.forEach((callback) => callback(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopPolling() {
|
_stopPolling() {
|
||||||
@@ -74,6 +92,12 @@ export class GnssControl {
|
|||||||
this._status.textContent = fixLabel(data);
|
this._status.textContent = fixLabel(data);
|
||||||
|
|
||||||
const lngLat = [data.lon, data.lat];
|
const lngLat = [data.lon, data.lat];
|
||||||
|
// Recenters on every fix while enabled — this is the app's own
|
||||||
|
// receiver, so "GNSS on" means "follow it", the same way LocateControl
|
||||||
|
// (public/js/locate.js) follows it when GNSS is what it's using too.
|
||||||
|
// flyTo (with its zoom swoop) only for the first fix, since that's the
|
||||||
|
// one jumping from wherever the map already was; easeTo for later
|
||||||
|
// updates, a plain follow rather than repeating that swoop every poll.
|
||||||
if (!this._marker) {
|
if (!this._marker) {
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.className = "gnss-marker";
|
el.className = "gnss-marker";
|
||||||
@@ -82,8 +106,10 @@ export class GnssControl {
|
|||||||
// survives map.setStyle() (switching Online/Offline) with no extra
|
// survives map.setStyle() (switching Online/Offline) with no extra
|
||||||
// re-add handling needed.
|
// re-add handling needed.
|
||||||
this._marker = new Marker({ element: el }).setLngLat(lngLat).addTo(this._map);
|
this._marker = new Marker({ element: el }).setLngLat(lngLat).addTo(this._map);
|
||||||
|
this._map.flyTo({ center: lngLat, zoom: Math.max(this._map.getZoom(), 15) });
|
||||||
} else {
|
} else {
|
||||||
this._marker.setLngLat(lngLat);
|
this._marker.setLngLat(lngLat);
|
||||||
|
this._map.easeTo({ center: lngLat });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +119,21 @@ export class GnssControl {
|
|||||||
this._marker = null;
|
this._marker = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read by LocateControl: GNSS is only ever used for "find my location"
|
||||||
|
// when explicitly turned on here — clicking Locate while this is off
|
||||||
|
// falls back to the browser's own geolocation instead, rather than
|
||||||
|
// silently switching sources on whatever GET /api/gnss/position happens
|
||||||
|
// to report at that moment.
|
||||||
|
isEnabled() {
|
||||||
|
return this._enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The current fix, if any — null both when disabled and when enabled
|
||||||
|
// but not fixed yet (no marker placed either way).
|
||||||
|
getPosition() {
|
||||||
|
return this._marker ? this._marker.getLngLat() : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fixLabel({ mode, eph }) {
|
function fixLabel({ mode, eph }) {
|
||||||
|
|||||||
@@ -12,8 +12,9 @@ import {
|
|||||||
POI_ICONS,
|
POI_ICONS,
|
||||||
PARK4NIGHT_TAGS,
|
PARK4NIGHT_TAGS,
|
||||||
} from "/js/icons.js";
|
} from "/js/icons.js";
|
||||||
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
import { notifySidePanelOpen, onlyOneSidePanelOpen, onGpxStoreChanged, notifyGpxStoreChanged } from "/js/panels.js";
|
||||||
import { formatActivityType } from "/js/track_profile.js";
|
import { formatActivityType } from "/js/track_profile.js";
|
||||||
|
import { isLoggedIn } from "/js/auth.js";
|
||||||
|
|
||||||
const TREE_URL = "/api/gpx/tree";
|
const TREE_URL = "/api/gpx/tree";
|
||||||
const UPLOAD_URL = "/api/gpx";
|
const UPLOAD_URL = "/api/gpx";
|
||||||
@@ -31,17 +32,6 @@ const BADGE_IMAGE_PREFIX = "badge-"; // one registered image per distinct badge
|
|||||||
// one — both land on the same drop targets, told apart by this.
|
// one — both land on the same drop targets, told apart by this.
|
||||||
const MOVE_MIME = "application/x-gpx-path";
|
const MOVE_MIME = "application/x-gpx-path";
|
||||||
|
|
||||||
// No server-rendered flag for this exists yet, and adding one would be more
|
|
||||||
// plumbing than reusing what's already on the page: the topbar's own
|
|
||||||
// logged_in check (views/index.erb) already renders a "Log out" link only
|
|
||||||
// when logged in. Write-action controls (delete/download/upload/new
|
|
||||||
// folder) check this to hide themselves entirely rather than being shown
|
|
||||||
// and failing on click — login state never changes without a full page
|
|
||||||
// reload, so this only needs checking once per render, not reactively.
|
|
||||||
function isLoggedIn() {
|
|
||||||
return !!document.querySelector('a[href="/logout"]');
|
|
||||||
}
|
|
||||||
|
|
||||||
// A sidebar icon that opens a side panel for uploading GPX files (via
|
// A sidebar icon that opens a side panel for uploading GPX files (via
|
||||||
// drag & drop, stored server-side in a folder hierarchy the user can add to
|
// drag & drop, stored server-side in a folder hierarchy the user can add to
|
||||||
// and reorganize by dragging), and toggling their tracks/waypoints on and
|
// and reorganize by dragging), and toggling their tracks/waypoints on and
|
||||||
@@ -53,6 +43,9 @@ export class GpxPanelControl {
|
|||||||
this._visible = new Set(); // file paths currently shown on the map
|
this._visible = new Set(); // file paths currently shown on the map
|
||||||
this._interactiveLayers = new Set(); // point layer ids with click/hover wired up
|
this._interactiveLayers = new Set(); // point layer ids with click/hover wired up
|
||||||
this._pointsByPath = new Map(); // path -> its waypoints FeatureCollection, for the category legend
|
this._pointsByPath = new Map(); // path -> its waypoints FeatureCollection, for the category legend
|
||||||
|
this._linesByPath = new Map(); // path -> its lines/polygons FeatureCollection, same reason (a polygon/line note also carries a category)
|
||||||
|
this._hiddenCategories = new Set(); // category names toggled off in the legend
|
||||||
|
this._openDownloadMenu = null; // the one gpx-tree__download-menu currently open, if any
|
||||||
|
|
||||||
// setStyle() (switching the Online/Offline base map) tears down all
|
// setStyle() (switching the Online/Offline base map) tears down all
|
||||||
// custom sources/layers/images — re-register the category glyphs before
|
// custom sources/layers/images — re-register the category glyphs before
|
||||||
@@ -65,6 +58,20 @@ export class GpxPanelControl {
|
|||||||
this._iconsReady = this._registerCategoryGlyphs();
|
this._iconsReady = this._registerCategoryGlyphs();
|
||||||
this._visible.forEach((path) => this._addLayers(path));
|
this._visible.forEach((path) => this._addLayers(path));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// notes.js writes directly into the same GPX store (a hand-drawn note
|
||||||
|
// saved into a .geojson layer) — refresh so a newly created/grown file
|
||||||
|
// shows up in the tree without a manual reopen, and auto-show it (check
|
||||||
|
// + render) since a note the user just placed being invisible until
|
||||||
|
// they find and check its layer by hand defeats the point of dropping
|
||||||
|
// a pin right there on the map.
|
||||||
|
onGpxStoreChanged((path) => {
|
||||||
|
if (path) {
|
||||||
|
this._visible.add(path);
|
||||||
|
this._reloadFile(path);
|
||||||
|
}
|
||||||
|
this._loadTree();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Registered once, always white, as plain (non-SDF) images: these used to
|
// Registered once, always white, as plain (non-SDF) images: these used to
|
||||||
@@ -110,7 +117,7 @@ export class GpxPanelControl {
|
|||||||
mount(sidebar) {
|
mount(sidebar) {
|
||||||
this._container = document.createElement("div");
|
this._container = document.createElement("div");
|
||||||
this._container.className = "sidebar-item";
|
this._container.className = "sidebar-item";
|
||||||
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Tracks" aria-label="Tracks">${GPX_ICON}</button>`;
|
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Tracks & Points" aria-label="Tracks & Points">${GPX_ICON}</button>`;
|
||||||
this._icon = this._container.querySelector(".sidebar-icon");
|
this._icon = this._container.querySelector(".sidebar-icon");
|
||||||
this._icon.addEventListener("click", () => this._togglePanel());
|
this._icon.addEventListener("click", () => this._togglePanel());
|
||||||
|
|
||||||
@@ -130,6 +137,14 @@ export class GpxPanelControl {
|
|||||||
|
|
||||||
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
|
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
|
||||||
|
|
||||||
|
// Closes whichever per-file download menu is open on any click that
|
||||||
|
// isn't the toggle button that opened it (that one calls
|
||||||
|
// stopPropagation so this never fights it) — same "outside click
|
||||||
|
// closes it" behavior sidebar-flyout dropdowns already have, just not
|
||||||
|
// wired through onlyOneSidePanelOpen since these aren't full side
|
||||||
|
// panels.
|
||||||
|
document.addEventListener("click", () => this._closeDownloadMenu());
|
||||||
|
|
||||||
this._setupToolbarActions();
|
this._setupToolbarActions();
|
||||||
this._setupRootDropTarget();
|
this._setupRootDropTarget();
|
||||||
this._setupMapDropTarget();
|
this._setupMapDropTarget();
|
||||||
@@ -287,37 +302,16 @@ export class GpxPanelControl {
|
|||||||
checkbox.type = "checkbox";
|
checkbox.type = "checkbox";
|
||||||
checkbox.checked = this._visible.has(file.path);
|
checkbox.checked = this._visible.has(file.path);
|
||||||
checkbox.addEventListener("change", () => this._toggleFile(file.path, checkbox.checked, checkbox));
|
checkbox.addEventListener("change", () => this._toggleFile(file.path, checkbox.checked, checkbox));
|
||||||
// Once a .gpx/.fit is cached, its converted GeoJSON is the thing that's
|
// Just the real, as-uploaded name — it used to grow a ".geojson" suffix
|
||||||
// actually shown on the map from here on (see GpxStore#geojson) — the
|
// once cached (matching the real "<path>.geojson" cache file on disk),
|
||||||
// name reflects that (matching the real "<path>.geojson" cache file on
|
// back when the name doubled as a GeoJSON-only download link and the
|
||||||
// disk) rather than still reading as a plain, un-converted .gpx/.fit.
|
// suffix explained what clicking it would get you. Now that downloading
|
||||||
// A direct .geojson upload already ends in .geojson, so it's left
|
// is the download button's job (see below), that suffix was just
|
||||||
// alone.
|
// confusing: the file is still named "ride.gpx", not "ride.gpx.geojson".
|
||||||
const displayName = file.cached && !file.name.toLowerCase().endsWith(".geojson") ? `${file.name}.geojson` : file.name;
|
|
||||||
const loggedIn = isLoggedIn();
|
const loggedIn = isLoggedIn();
|
||||||
// Logged in: an <a>, not a <span> — clicking it downloads that GeoJSON,
|
const name = document.createElement("span");
|
||||||
// the only way to get it out of the app, since the original .gpx/.fit
|
|
||||||
// is never deleted but also never re-shown once converted. An <a>
|
|
||||||
// nested in a <label> doesn't also toggle the checkbox on click (unlike
|
|
||||||
// a plain element), so this doesn't fight the row's own
|
|
||||||
// toggle-on-map behavior. href kept pointing at the real endpoint (so
|
|
||||||
// hover/right-click/middle-click still do something sensible) but the
|
|
||||||
// actual click goes through _download, which redirects to /login on a
|
|
||||||
// 401 rather than navigating to a raw JSON error page (this endpoint
|
|
||||||
// requires login the same as upload/move/delete, see app.rb).
|
|
||||||
// Logged out: plain text — nothing here would actually work, so it
|
|
||||||
// isn't shown as if it would (same reasoning as the buttons below).
|
|
||||||
const name = document.createElement(loggedIn ? "a" : "span");
|
|
||||||
name.className = "gpx-tree__file-name";
|
name.className = "gpx-tree__file-name";
|
||||||
name.textContent = displayName;
|
name.textContent = file.name;
|
||||||
if (loggedIn) {
|
|
||||||
name.href = `${gpxFileUrl(file.path)}?download=geojson`;
|
|
||||||
name.title = `Download ${displayName}`;
|
|
||||||
name.addEventListener("click", (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
this._download(name.href, displayName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
label.appendChild(checkbox);
|
label.appendChild(checkbox);
|
||||||
(file.kinds || []).forEach((kind) => label.appendChild(this._kindIcon(kind)));
|
(file.kinds || []).forEach((kind) => label.appendChild(this._kindIcon(kind)));
|
||||||
if (!file.cached) label.appendChild(this._pendingIcon());
|
if (!file.cached) label.appendChild(this._pendingIcon());
|
||||||
@@ -357,24 +351,75 @@ export class GpxPanelControl {
|
|||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The file's name already downloads its GeoJSON (?download=geojson,
|
// One button, one obvious place for both download kinds — clicking it
|
||||||
// matching what's displayed) — this covers the other half: the
|
// opens a small menu (Original / GeoJSON) instead of splitting them
|
||||||
// untouched, as-uploaded file, which otherwise has no way out of the
|
// across two differently-shaped click targets (the file name used to be
|
||||||
// app once it stops being shown as itself. For a native .geojson
|
// a GeoJSON-only download link; easy to miss it was a link at all, and
|
||||||
// upload the two downloads are identical (nothing to convert), so this
|
// it only ever covered half the story). Costs a click for the common
|
||||||
// isn't hidden for those — just redundant, not wrong.
|
// case of downloading several files in a row, in exchange for both
|
||||||
|
// formats actually being reachable from one, discoverable place.
|
||||||
_downloadButton(file) {
|
_downloadButton(file) {
|
||||||
const link = document.createElement("a");
|
const wrapper = document.createElement("span");
|
||||||
link.className = "gpx-tree__download";
|
wrapper.className = "gpx-tree__download-wrapper";
|
||||||
link.href = `${gpxFileUrl(file.path)}?download=original`;
|
|
||||||
link.title = "Download original file";
|
const button = document.createElement("button");
|
||||||
link.setAttribute("aria-label", "Download original file");
|
button.type = "button";
|
||||||
link.innerHTML = DOWNLOAD_ICON;
|
button.className = "gpx-tree__download";
|
||||||
link.addEventListener("click", (event) => {
|
button.title = "Download";
|
||||||
event.preventDefault();
|
button.setAttribute("aria-label", "Download");
|
||||||
this._download(link.href, file.name);
|
button.innerHTML = DOWNLOAD_ICON;
|
||||||
|
|
||||||
|
const menu = document.createElement("div");
|
||||||
|
menu.className = "gpx-tree__download-menu";
|
||||||
|
menu.hidden = true;
|
||||||
|
const geojsonName = file.cached && !file.name.toLowerCase().endsWith(".geojson") ? `${file.name}.geojson` : file.name;
|
||||||
|
menu.appendChild(this._downloadMenuItem("Original file", `${gpxFileUrl(file.path)}?download=original`, file.name));
|
||||||
|
menu.appendChild(this._downloadMenuItem("GeoJSON", `${gpxFileUrl(file.path)}?download=geojson`, geojsonName));
|
||||||
|
|
||||||
|
// stopPropagation so this click doesn't immediately reach the
|
||||||
|
// document-level listener (mount()) that closes whichever menu is
|
||||||
|
// open on an outside click — otherwise it would close right back up
|
||||||
|
// the instant it opened.
|
||||||
|
button.addEventListener("click", (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
this._toggleDownloadMenu(menu);
|
||||||
});
|
});
|
||||||
return link;
|
|
||||||
|
wrapper.appendChild(button);
|
||||||
|
wrapper.appendChild(menu);
|
||||||
|
return wrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
_downloadMenuItem(label, url, filename) {
|
||||||
|
const item = document.createElement("a");
|
||||||
|
item.className = "gpx-tree__download-menu-item";
|
||||||
|
item.href = url;
|
||||||
|
item.textContent = label;
|
||||||
|
item.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this._closeDownloadMenu();
|
||||||
|
this._download(url, filename);
|
||||||
|
});
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only one menu open at a time — opening a second one closes whichever
|
||||||
|
// was already open, same as onlyOneSidePanelOpen does for the sidebar's
|
||||||
|
// own panels (panels.js), just scoped to this one control instead of
|
||||||
|
// shared across the page.
|
||||||
|
_toggleDownloadMenu(menu) {
|
||||||
|
if (this._openDownloadMenu === menu) {
|
||||||
|
this._closeDownloadMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._closeDownloadMenu();
|
||||||
|
menu.hidden = false;
|
||||||
|
this._openDownloadMenu = menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
_closeDownloadMenu() {
|
||||||
|
if (this._openDownloadMenu) this._openDownloadMenu.hidden = true;
|
||||||
|
this._openDownloadMenu = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch-based rather than a plain navigation so a 401 (not logged in —
|
// fetch-based rather than a plain navigation so a 401 (not logged in —
|
||||||
@@ -496,15 +541,53 @@ export class GpxPanelControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this._map.addSource(linesId, { type: "geojson", data: lines });
|
this._map.addSource(linesId, { type: "geojson", data: lines });
|
||||||
|
// Excludes category:"Note" — a saved Measure-tool distance line gets
|
||||||
|
// its own layer below (popup-on-click, not this hover-summary/profile-
|
||||||
|
// panel track behavior), not this one.
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: `${linesId}-line`,
|
id: `${linesId}-line`,
|
||||||
type: "line",
|
type: "line",
|
||||||
source: linesId,
|
source: linesId,
|
||||||
|
filter: ["any", ["!has", "category"], ["!=", ["get", "category"], "Note"]],
|
||||||
layout: { "line-cap": "round", "line-join": "round" },
|
layout: { "line-cap": "round", "line-join": "round" },
|
||||||
paint: { "line-color": TRACK_COLOR, "line-width": 3 },
|
paint: { "line-color": TRACK_COLOR, "line-width": 3 },
|
||||||
});
|
});
|
||||||
this._registerLineInteractivity(`${linesId}-line`);
|
this._registerLineInteractivity(`${linesId}-line`);
|
||||||
|
|
||||||
|
// Polygon/line notes (see notes.js and measure.js's "Save as note") —
|
||||||
|
// the same "lines" source already only renders LineString geometry
|
||||||
|
// through the line layer above, and Polygon geometry isn't drawn by a
|
||||||
|
// "line"-type layer at all (MapLibre only draws LineString for that
|
||||||
|
// type), so these are just more layers reading the same source, not a
|
||||||
|
// new bucket. Popup-on-click like a waypoint (_registerPointInteractivity),
|
||||||
|
// not the track line's hover-summary/profile-panel behavior — a note
|
||||||
|
// has no elevation profile to open.
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${linesId}-polygon-fill`,
|
||||||
|
type: "fill",
|
||||||
|
source: linesId,
|
||||||
|
filter: ["==", ["geometry-type"], "Polygon"],
|
||||||
|
paint: { "fill-color": TRACK_COLOR, "fill-opacity": 0.2 },
|
||||||
|
});
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${linesId}-polygon-outline`,
|
||||||
|
type: "line",
|
||||||
|
source: linesId,
|
||||||
|
filter: ["==", ["geometry-type"], "Polygon"],
|
||||||
|
paint: { "line-color": TRACK_COLOR, "line-width": 2 },
|
||||||
|
});
|
||||||
|
this._registerPointInteractivity(`${linesId}-polygon-fill`, path);
|
||||||
|
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${linesId}-note-line`,
|
||||||
|
type: "line",
|
||||||
|
source: linesId,
|
||||||
|
filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "category"], "Note"]],
|
||||||
|
layout: { "line-cap": "round", "line-join": "round" },
|
||||||
|
paint: { "line-color": TRACK_COLOR, "line-width": 3, "line-dasharray": [2, 1] },
|
||||||
|
});
|
||||||
|
this._registerPointInteractivity(`${linesId}-note-line`, path);
|
||||||
|
|
||||||
// clusterRadius/clusterMaxZoom are harmless when there's nothing dense
|
// clusterRadius/clusterMaxZoom are harmless when there's nothing dense
|
||||||
// enough to cluster (a handful of waypoints just render individually).
|
// enough to cluster (a handful of waypoints just render individually).
|
||||||
this._map.addSource(pointsId, { type: "geojson", data: points, cluster: true, clusterRadius: 50, clusterMaxZoom: 14 });
|
this._map.addSource(pointsId, { type: "geojson", data: points, cluster: true, clusterRadius: 50, clusterMaxZoom: 14 });
|
||||||
@@ -512,10 +595,12 @@ export class GpxPanelControl {
|
|||||||
await Promise.all([this._iconsReady, this._registerBadgeColors(badgeColors)]);
|
await Promise.all([this._iconsReady, this._registerBadgeColors(badgeColors)]);
|
||||||
if (!this._map.getSource(pointsId)) return true; // a style.load (basemap switch) tore it down while icons were loading
|
if (!this._map.getSource(pointsId)) return true; // a style.load (basemap switch) tore it down while icons were loading
|
||||||
|
|
||||||
this._addClusterLayers(pointsId);
|
this._addClusterLayers(pointsId, path);
|
||||||
|
|
||||||
if (points.features.length > 0) this._pointsByPath.set(path, points);
|
if (points.features.length > 0) this._pointsByPath.set(path, points);
|
||||||
|
if (lines.features.length > 0) this._linesByPath.set(path, lines);
|
||||||
this._updateLegend();
|
this._updateLegend();
|
||||||
|
this._applyCategoryFilter();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -525,7 +610,7 @@ export class GpxPanelControl {
|
|||||||
// each waypoint's own category color (see Maptool::Gpx#waypoint_color),
|
// each waypoint's own category color (see Maptool::Gpx#waypoint_color),
|
||||||
// falling back to the plain track color for waypoints with no category
|
// falling back to the plain track color for waypoints with no category
|
||||||
// (e.g. a hand-placed GNSS pin, not from a POI export).
|
// (e.g. a hand-placed GNSS pin, not from a POI export).
|
||||||
_addClusterLayers(pointsId) {
|
_addClusterLayers(pointsId, path) {
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: `${pointsId}-clusters`,
|
id: `${pointsId}-clusters`,
|
||||||
type: "circle",
|
type: "circle",
|
||||||
@@ -584,7 +669,7 @@ export class GpxPanelControl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._registerClusterInteractivity(pointsId);
|
this._registerClusterInteractivity(pointsId);
|
||||||
this._registerPointInteractivity(`${pointsId}-unclustered`);
|
this._registerPointInteractivity(`${pointsId}-unclustered`, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clicking a cluster zooms in just far enough to break it apart, the
|
// Clicking a cluster zooms in just far enough to break it apart, the
|
||||||
@@ -612,17 +697,25 @@ export class GpxPanelControl {
|
|||||||
// delegated listener that lives on the map itself and keeps matching
|
// delegated listener that lives on the map itself and keeps matching
|
||||||
// that layer id after setStyle() removes and (via _addLayers) re-adds
|
// that layer id after setStyle() removes and (via _addLayers) re-adds
|
||||||
// it, so re-registering on every re-add would stack duplicate popups.
|
// it, so re-registering on every re-add would stack duplicate popups.
|
||||||
_registerPointInteractivity(layerId) {
|
// `path` (which file this layer's features came from) is only used for
|
||||||
|
// a "Note" feature's Edit/Delete actions — plain GPX/FIT/park4night
|
||||||
|
// waypoints don't get those, so it's otherwise unused.
|
||||||
|
_registerPointInteractivity(layerId, path) {
|
||||||
if (this._interactiveLayers.has(layerId)) return;
|
if (this._interactiveLayers.has(layerId)) return;
|
||||||
this._interactiveLayers.add(layerId);
|
this._interactiveLayers.add(layerId);
|
||||||
|
|
||||||
this._map.on("click", layerId, (event) => {
|
this._map.on("click", layerId, (event) => {
|
||||||
const feature = event.features[0];
|
const feature = event.features[0];
|
||||||
if (!feature) return;
|
if (!feature) return;
|
||||||
new Popup({ closeButton: true, maxWidth: "260px" })
|
// properties.id is what update/delete target by — a note saved before
|
||||||
|
// that existed (see `rake gpx:backfill_note_ids`) doesn't have one
|
||||||
|
// yet, and offering Edit/Delete for it would just 404.
|
||||||
|
const editable = feature.properties.category === "Note" && !!feature.properties.id && isLoggedIn();
|
||||||
|
const popup = new Popup({ closeButton: true, maxWidth: "260px" })
|
||||||
.setLngLat(event.lngLat)
|
.setLngLat(event.lngLat)
|
||||||
.setHTML(popupHtml(feature.properties))
|
.setHTML(popupHtml(feature.properties, { editable }))
|
||||||
.addTo(this._map);
|
.addTo(this._map);
|
||||||
|
if (editable) this._wireNoteActions(popup, path, feature);
|
||||||
});
|
});
|
||||||
this._map.on("mouseenter", layerId, () => {
|
this._map.on("mouseenter", layerId, () => {
|
||||||
this._map.getCanvas().style.cursor = "pointer";
|
this._map.getCanvas().style.cursor = "pointer";
|
||||||
@@ -632,6 +725,76 @@ export class GpxPanelControl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edit swaps the popup's content for a plain textarea + Save/Cancel (the
|
||||||
|
// note's location doesn't change, just its text, so there's no need for
|
||||||
|
// NoteSaveForm's fuller target-file picker here). Delete just confirms
|
||||||
|
// and removes. Both end by reloading the file's layers from the server —
|
||||||
|
// simplest way to keep the map, the category legend, and this popup's
|
||||||
|
// own feature data all in sync with what the edit/delete actually wrote,
|
||||||
|
// rather than hand-patching three different client-side copies of it.
|
||||||
|
_wireNoteActions(popup, path, feature) {
|
||||||
|
const element = popup.getElement();
|
||||||
|
const id = feature.properties.id;
|
||||||
|
|
||||||
|
element.querySelector(".gpx-popup__delete")?.addEventListener("click", async () => {
|
||||||
|
if (!window.confirm("Delete this note?")) return;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/gpx/notes?${new URLSearchParams({ path, id })}`, { method: "DELETE" });
|
||||||
|
if (!response.ok) throw new Error(`delete failed: ${response.status}`);
|
||||||
|
popup.remove();
|
||||||
|
notifyGpxStoreChanged();
|
||||||
|
await this._reloadFile(path);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert("Could not delete the note — check your connection and try again.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
element.querySelector(".gpx-popup__edit")?.addEventListener("click", () => {
|
||||||
|
const content = popup.getElement().querySelector(".maplibregl-popup-content");
|
||||||
|
content.innerHTML = `
|
||||||
|
<div class="gpx-popup__edit-form">
|
||||||
|
<input type="text" class="gpx-popup__edit-title" value="${escapeHtml(feature.properties.name || "")}">
|
||||||
|
<textarea class="gpx-popup__edit-body" rows="3" placeholder="Details (optional) — **bold**, *italic*, links">${escapeHtml(feature.properties.description || "")}</textarea>
|
||||||
|
<div class="gpx-popup__edit-actions">
|
||||||
|
<button type="button" class="gpx-popup__edit-save">Save</button>
|
||||||
|
<button type="button" class="gpx-popup__edit-cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
const titleInput = content.querySelector(".gpx-popup__edit-title");
|
||||||
|
const bodyInput = content.querySelector(".gpx-popup__edit-body");
|
||||||
|
titleInput.focus();
|
||||||
|
content.querySelector(".gpx-popup__edit-cancel").addEventListener("click", () => {
|
||||||
|
content.innerHTML = popupHtml(feature.properties, { editable: true });
|
||||||
|
this._wireNoteActions(popup, path, feature);
|
||||||
|
});
|
||||||
|
content.querySelector(".gpx-popup__edit-save").addEventListener("click", async () => {
|
||||||
|
const title = titleInput.value.trim();
|
||||||
|
if (!title) return;
|
||||||
|
const details = bodyInput.value.trim();
|
||||||
|
const properties = { ...feature.properties, name: title };
|
||||||
|
if (details) properties.description = details;
|
||||||
|
else delete properties.description;
|
||||||
|
const updated = { ...feature, properties };
|
||||||
|
try {
|
||||||
|
const body = new URLSearchParams({ path, id, feature: JSON.stringify(updated) });
|
||||||
|
const response = await fetch("/api/gpx/notes", { method: "PUT", body });
|
||||||
|
if (!response.ok) throw new Error(`save failed: ${response.status}`);
|
||||||
|
popup.remove();
|
||||||
|
notifyGpxStoreChanged();
|
||||||
|
await this._reloadFile(path);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert("Could not save the note — check your connection and try again.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _reloadFile(path) {
|
||||||
|
this._removeLayers(path);
|
||||||
|
await this._addLayers(path);
|
||||||
|
}
|
||||||
|
|
||||||
// Hover shows a quick summary (unlike a waypoint popup, which is
|
// Hover shows a quick summary (unlike a waypoint popup, which is
|
||||||
// click-triggered — a track's summary describes the whole line, not
|
// click-triggered — a track's summary describes the whole line, not
|
||||||
// whatever point along it happens to get hovered, so there's no single
|
// whatever point along it happens to get hovered, so there's no single
|
||||||
@@ -668,6 +831,9 @@ export class GpxPanelControl {
|
|||||||
const pointsId = pointsSourceId(path);
|
const pointsId = pointsSourceId(path);
|
||||||
[
|
[
|
||||||
`${linesId}-line`,
|
`${linesId}-line`,
|
||||||
|
`${linesId}-polygon-fill`,
|
||||||
|
`${linesId}-polygon-outline`,
|
||||||
|
`${linesId}-note-line`,
|
||||||
`${pointsId}-clusters`,
|
`${pointsId}-clusters`,
|
||||||
`${pointsId}-cluster-count`,
|
`${pointsId}-cluster-count`,
|
||||||
`${pointsId}-unclustered`,
|
`${pointsId}-unclustered`,
|
||||||
@@ -679,37 +845,85 @@ export class GpxPanelControl {
|
|||||||
if (this._map.getSource(pointsId)) this._map.removeSource(pointsId);
|
if (this._map.getSource(pointsId)) this._map.removeSource(pointsId);
|
||||||
|
|
||||||
this._pointsByPath.delete(path);
|
this._pointsByPath.delete(path);
|
||||||
|
this._linesByPath.delete(path);
|
||||||
this._updateLegend();
|
this._updateLegend();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rebuilt from whichever visible files actually carry categorized
|
// Rebuilt from whichever visible files actually carry categorized
|
||||||
// waypoints (a park4night/OsmAnd POI export) — a plain GPS track has none
|
// waypoints (a park4night/OsmAnd POI export) — a plain GPS track has none
|
||||||
// and the legend just stays hidden.
|
// and the legend just stays hidden. Doubles as a filter: each item is a
|
||||||
|
// toggle button (Unigrid-style icon grid) that hides/shows that category
|
||||||
|
// on the map, not just a read-only color key.
|
||||||
_updateLegend() {
|
_updateLegend() {
|
||||||
const categories = new Map(); // category name -> color
|
const categories = new Map(); // category name -> { color, icon }
|
||||||
for (const points of this._pointsByPath.values()) {
|
for (const collection of [...this._pointsByPath.values(), ...this._linesByPath.values()]) {
|
||||||
for (const feature of points.features) {
|
for (const feature of collection.features) {
|
||||||
const category = feature.properties.category;
|
const category = feature.properties.category;
|
||||||
if (category && !categories.has(category)) categories.set(category, feature.properties.color || TRACK_COLOR);
|
if (category && !categories.has(category)) {
|
||||||
|
categories.set(category, { color: feature.properties.color || TRACK_COLOR, icon: feature.properties.icon });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._legendContainer.innerHTML = "";
|
this._legendContainer.innerHTML = "";
|
||||||
this._legendContainer.hidden = categories.size === 0;
|
this._legendContainer.hidden = categories.size === 0;
|
||||||
[...categories].sort(([a], [b]) => a.localeCompare(b)).forEach(([category, color]) => {
|
[...categories].sort(([a], [b]) => a.localeCompare(b)).forEach(([category, { color, icon }]) => {
|
||||||
const item = document.createElement("div");
|
const item = document.createElement("button");
|
||||||
|
item.type = "button";
|
||||||
item.className = "gpx-legend__item";
|
item.className = "gpx-legend__item";
|
||||||
const swatch = document.createElement("span");
|
item.classList.toggle("gpx-legend__item--hidden", this._hiddenCategories.has(category));
|
||||||
swatch.className = "gpx-legend__swatch";
|
item.title = category;
|
||||||
swatch.style.background = color; // a malformed/malicious value from an uploaded file is simply ignored by the CSSOM, never parsed as markup
|
item.setAttribute("aria-pressed", String(!this._hiddenCategories.has(category)));
|
||||||
|
|
||||||
|
const glyph = document.createElement("span");
|
||||||
|
if (icon && POI_ICONS[icon]) {
|
||||||
|
glyph.className = "gpx-legend__icon";
|
||||||
|
glyph.innerHTML = POI_ICONS[icon];
|
||||||
|
} else {
|
||||||
|
glyph.className = "gpx-legend__swatch";
|
||||||
|
glyph.style.background = color; // a malformed/malicious value from an uploaded file is simply ignored by the CSSOM, never parsed as markup
|
||||||
|
}
|
||||||
const label = document.createElement("span");
|
const label = document.createElement("span");
|
||||||
|
label.className = "gpx-legend__label";
|
||||||
label.textContent = category;
|
label.textContent = category;
|
||||||
item.appendChild(swatch);
|
item.appendChild(glyph);
|
||||||
item.appendChild(label);
|
item.appendChild(label);
|
||||||
|
|
||||||
|
item.addEventListener("click", () => {
|
||||||
|
if (!this._hiddenCategories.delete(category)) this._hiddenCategories.add(category);
|
||||||
|
this._updateLegend();
|
||||||
|
this._applyCategoryFilter();
|
||||||
|
});
|
||||||
|
|
||||||
this._legendContainer.appendChild(item);
|
this._legendContainer.appendChild(item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-clusters each visible file's points around only the categories still
|
||||||
|
// toggled on — a plain layer `filter` can't do this: MapLibre clusters a
|
||||||
|
// GeoJSON source's *entire* data set regardless of what a layer filter
|
||||||
|
// hides, so a cluster's count/expansion would stay wrong. Cheap to redo
|
||||||
|
// in full: _pointsByPath already holds each file's complete features in
|
||||||
|
// memory, this is just re-slicing it, no re-parsing or network involved.
|
||||||
|
_applyCategoryFilter() {
|
||||||
|
for (const [path, points] of this._pointsByPath) {
|
||||||
|
const source = this._map.getSource(pointsSourceId(path));
|
||||||
|
if (!source) continue;
|
||||||
|
const visible = points.features.filter((f) => !this._hiddenCategories.has(f.properties.category));
|
||||||
|
source.setData({ type: "FeatureCollection", features: visible });
|
||||||
|
}
|
||||||
|
// Same re-slicing for polygon/line notes sharing the "lines" source with
|
||||||
|
// plain tracks/routes — those have no `category` of their own, so
|
||||||
|
// `_hiddenCategories.has(undefined)` is always false and they're never
|
||||||
|
// affected by this, only a hidden Note (or other categorized) shape is.
|
||||||
|
for (const [path, lines] of this._linesByPath) {
|
||||||
|
const source = this._map.getSource(linesSourceId(path));
|
||||||
|
if (!source) continue;
|
||||||
|
const visible = lines.features.filter((f) => !this._hiddenCategories.has(f.properties.category));
|
||||||
|
source.setData({ type: "FeatureCollection", features: visible });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async _uploadFiles(fileList, folder) {
|
async _uploadFiles(fileList, folder) {
|
||||||
// .zip: Garmin's bulk/FIT export format — the server expands it into
|
// .zip: Garmin's bulk/FIT export format — the server expands it into
|
||||||
// its .gpx/.fit/.geojson entries, so it's accepted here even though the
|
// its .gpx/.fit/.geojson entries, so it's accepted here even though the
|
||||||
@@ -720,19 +934,29 @@ export class GpxPanelControl {
|
|||||||
const files = [...fileList].filter((file) => /\.(gpx|fit|geojson|zip)$/i.test(file.name));
|
const files = [...fileList].filter((file) => /\.(gpx|fit|geojson|zip)$/i.test(file.name));
|
||||||
if (files.length === 0) return;
|
if (files.length === 0) return;
|
||||||
|
|
||||||
this._setStatus(files.length === 1 ? "Uploading…" : `Uploading ${files.length} files…`);
|
|
||||||
let tree = null;
|
let tree = null;
|
||||||
let failed = 0;
|
let failed = 0;
|
||||||
|
|
||||||
for (const file of files) {
|
for (const [index, file] of files.entries()) {
|
||||||
|
this._setStatus(files.length === 1 ? "Uploading…" : `Uploading ${index + 1} of ${files.length}…`);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append("file", file);
|
formData.append("file", file);
|
||||||
formData.append("folder", folder);
|
formData.append("folder", folder);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(UPLOAD_URL, { method: "POST", body: formData });
|
// fetch() has no cross-browser upload progress event, so this one
|
||||||
if (redirectIfUnauthorized(response)) return;
|
// upload uses XMLHttpRequest instead — its upload.progress firing
|
||||||
if (!response.ok) throw new Error("upload failed");
|
// with loaded === total is the only client-observable signal that
|
||||||
({ tree } = await response.json());
|
// the server has (or is about to start) converting rather than
|
||||||
|
// still receiving bytes, worth surfacing now that a several-
|
||||||
|
// thousand-waypoint POI export's conversion can itself take
|
||||||
|
// several more seconds on top of the transfer (GpxStore#save).
|
||||||
|
const xhr = await uploadFile(formData, () => this._setStatus("Converting…"));
|
||||||
|
if (xhr.status === 401) {
|
||||||
|
window.location.href = "/login";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (xhr.status < 200 || xhr.status >= 300) throw new Error("upload failed");
|
||||||
|
({ tree } = JSON.parse(xhr.responseText));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
failed += 1;
|
failed += 1;
|
||||||
}
|
}
|
||||||
@@ -814,11 +1038,33 @@ function redirectIfUnauthorized(response) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XMLHttpRequest rather than fetch() — only so onUploadComplete (the
|
||||||
|
// upload.progress event) is available at all; every other write action in
|
||||||
|
// this file stays on fetch()/redirectIfUnauthorized. Resolves with the xhr
|
||||||
|
// itself (not just responseText) so the caller can still read .status the
|
||||||
|
// same way a fetch Response would let it.
|
||||||
|
function uploadFile(formData, onUploadComplete) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.open("POST", UPLOAD_URL);
|
||||||
|
xhr.upload.addEventListener("progress", (event) => {
|
||||||
|
if (event.lengthComputable && event.loaded === event.total) onUploadComplete();
|
||||||
|
});
|
||||||
|
xhr.addEventListener("load", () => resolve(xhr));
|
||||||
|
xhr.addEventListener("error", () => reject(new Error("network error")));
|
||||||
|
xhr.send(formData);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Rasterizes a hand-authored SVG icon to ImageData for map.addImage(), at a
|
// Rasterizes a hand-authored SVG icon to ImageData for map.addImage(), at a
|
||||||
// fixed square size (the source SVGs have no explicit width/height, just a
|
// fixed square size (the source SVGs have no explicit width/height, just a
|
||||||
// viewBox, so one is injected — otherwise an <img> falls back to the CSS
|
// viewBox, so one is injected — otherwise an <img> falls back to the CSS
|
||||||
// default replaced-element size instead of the viewBox's aspect).
|
// default replaced-element size instead of the viewBox's aspect).
|
||||||
async function svgToImageData(svgMarkup, size) {
|
// Exported: overlays.js's own live layers (Wikipedia, more to come per its
|
||||||
|
// own doc comment) need the same SVG-to-map-image/badge-id/escaping
|
||||||
|
// helpers this file already has, and reimplementing them there would just
|
||||||
|
// be the same three functions typed twice.
|
||||||
|
export async function svgToImageData(svgMarkup, size) {
|
||||||
// xmlns is required here even though the other hand-drawn icons in this
|
// xmlns is required here even though the other hand-drawn icons in this
|
||||||
// app get away without it — those are inlined via innerHTML, where the
|
// app get away without it — those are inlined via innerHTML, where the
|
||||||
// HTML parser assigns the SVG namespace from context, but a standalone
|
// HTML parser assigns the SVG namespace from context, but a standalone
|
||||||
@@ -841,7 +1087,7 @@ async function svgToImageData(svgMarkup, size) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function badgeImageId(color) {
|
export function badgeImageId(color) {
|
||||||
return `${BADGE_IMAGE_PREFIX}${color}`;
|
return `${BADGE_IMAGE_PREFIX}${color}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -859,18 +1105,36 @@ function gpxFileUrl(path) {
|
|||||||
|
|
||||||
// properties come straight from an uploaded GPX file — untrusted input —
|
// properties come straight from an uploaded GPX file — untrusted input —
|
||||||
// so every value is escaped before landing in the popup's HTML.
|
// so every value is escaped before landing in the popup's HTML.
|
||||||
function popupHtml({ name, description, time, category, photos }) {
|
// editable (only ever true for a "Note" feature the viewer is logged in
|
||||||
|
// for — see _registerPointInteractivity) adds Edit/Delete buttons; the
|
||||||
|
// click handling for them lives in _wireNoteActions, not here, since this
|
||||||
|
// only builds markup, it has no map/store access of its own.
|
||||||
|
function popupHtml({ name, description, time, category, photos, icon }, { editable = false } = {}) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
if (name) parts.push(`<strong>${escapeHtml(name)}</strong>`);
|
if (name) {
|
||||||
|
const glyph = icon && POI_ICONS[icon] ? `<span class="gpx-popup__header-icon">${POI_ICONS[icon]}</span>` : "";
|
||||||
|
parts.push(`<div class="gpx-popup__header"><strong>${escapeHtml(name)}</strong>${glyph}</div>`);
|
||||||
|
}
|
||||||
if (category) parts.push(`<p class="gpx-popup__category">${escapeHtml(category)}</p>`);
|
if (category) parts.push(`<p class="gpx-popup__category">${escapeHtml(category)}</p>`);
|
||||||
if (photos?.length > 0) parts.push(formatPhotos(photos));
|
if (photos?.length > 0) parts.push(formatPhotos(photos));
|
||||||
if (description) parts.push(formatDescription(description));
|
// A Note's description is self-authored by the (logged-in) viewer, not
|
||||||
|
// uploaded/untrusted like every other description here — formatDescription
|
||||||
|
// is deliberately left alone for that untrusted case (see
|
||||||
|
// docs/nps-resources.adoc's note on why markdown rendering wasn't adopted
|
||||||
|
// for it); formatNoteBody is the separate, narrower path that's actually
|
||||||
|
// safe to add light markdown to.
|
||||||
|
if (description) parts.push(category === "Note" ? formatNoteBody(description) : formatDescription(description));
|
||||||
if (time) {
|
if (time) {
|
||||||
const parsed = new Date(time);
|
const parsed = new Date(time);
|
||||||
if (!Number.isNaN(parsed.getTime())) {
|
if (!Number.isNaN(parsed.getTime())) {
|
||||||
parts.push(`<p class="gpx-popup__time">${escapeHtml(parsed.toLocaleString())}</p>`);
|
parts.push(`<p class="gpx-popup__time">${escapeHtml(parsed.toLocaleString())}</p>`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (editable) {
|
||||||
|
parts.push(
|
||||||
|
`<div class="gpx-popup__note-actions"><button type="button" class="gpx-popup__edit">Edit</button><button type="button" class="gpx-popup__delete">Delete</button></div>`,
|
||||||
|
);
|
||||||
|
}
|
||||||
return parts.length > 0 ? parts.join("") : "<em>No details</em>";
|
return parts.length > 0 ? parts.join("") : "<em>No details</em>";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -896,7 +1160,7 @@ function formatDuration(totalSeconds) {
|
|||||||
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeHtml(text) {
|
export function escapeHtml(text) {
|
||||||
const div = document.createElement("div");
|
const div = document.createElement("div");
|
||||||
div.textContent = text;
|
div.textContent = text;
|
||||||
return div.innerHTML;
|
return div.innerHTML;
|
||||||
@@ -1007,3 +1271,17 @@ function urlToLink(url) {
|
|||||||
const href = escapeHtml(core);
|
const href = escapeHtml(core);
|
||||||
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${href}</a>${escapeHtml(trailing)}`;
|
return `<a href="${href}" target="_blank" rel="noopener noreferrer">${href}</a>${escapeHtml(trailing)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A Note's body only — never an uploaded file's description (see the
|
||||||
|
// comment where this is called). linkify() runs first and does its own
|
||||||
|
// escaping, so what it returns is already safe HTML; **bold**/*italic* are
|
||||||
|
// then matched against that (already-escaped) text and wrapped in real
|
||||||
|
// tags — order matters: running it before linkify would have that same
|
||||||
|
// escaping re-encode the tags this just added.
|
||||||
|
function formatNoteBody(text) {
|
||||||
|
let html = linkify(text)
|
||||||
|
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
||||||
|
.replace(/\*(.+?)\*/g, "<em>$1</em>")
|
||||||
|
.replace(/\n/g, "<br>");
|
||||||
|
return `<p class="gpx-popup__description">${html}</p>`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,442 @@
|
|||||||
|
import { WALKING_ICON } from "/js/icons.js";
|
||||||
|
import { firstSymbolLayerId } from "/js/radar.js";
|
||||||
|
import { escapeHtml } from "/js/gpx.js";
|
||||||
|
import { lineLength, formatDistance } from "/js/measure.js";
|
||||||
|
import { buildLineGraph, orderLanesAsync, LaneLayer } from "/vendor/maplibre-gl-lanes/maplibre-gl-lanes.mjs";
|
||||||
|
|
||||||
|
// Overpass has permissive CORS (verified: Access-Control-Allow-Origin: *),
|
||||||
|
// so this queries it directly from the browser — no server-side proxy
|
||||||
|
// needed, same "public API, no key" story as the Nominatim search already
|
||||||
|
// hits from here. One query per click (not per pan/move like the
|
||||||
|
// Wikipedia layer's live refetch), so this stays well within Overpass's
|
||||||
|
// shared rate limits.
|
||||||
|
//
|
||||||
|
// Three independent public mirrors (also CORS-open, verified), raced with
|
||||||
|
// Promise.any rather than called one at a time — the shared free instances
|
||||||
|
// vary a lot in load from one moment to the next, and a multi-second wait
|
||||||
|
// on a single one was the main complaint. Whichever answers first wins;
|
||||||
|
// the others are simply left to finish and get GC'd, not worth the extra
|
||||||
|
// AbortController plumbing for a single one-off per-click request.
|
||||||
|
const OVERPASS_APIS = [
|
||||||
|
"https://overpass-api.de/api/interpreter",
|
||||||
|
"https://overpass.kumi.systems/api/interpreter",
|
||||||
|
"https://overpass.private.coffee/api/interpreter",
|
||||||
|
];
|
||||||
|
const CLICK_RADIUS_M = 40;
|
||||||
|
// OSM colour tag values are free text (untrusted) — only used as a CSS
|
||||||
|
// background if they look like an actual color (hex or a plain word),
|
||||||
|
// never interpolated unchecked into a style attribute.
|
||||||
|
const SAFE_COLOR = /^(#[0-9a-fA-F]{3,8}|[a-zA-Z]+)$/;
|
||||||
|
|
||||||
|
// Pre-rendered raster tiles, not vector — waymarkedtrails.org already
|
||||||
|
// colors each route by its OSM `colour` tag (the KST/KČT-style paint-blaze
|
||||||
|
// colors used in Slovakia/Czechia), falling back to plain red where a route
|
||||||
|
// has none, so there's no styling to do here ourselves. Used whenever no
|
||||||
|
// offline extract is available at all — same "quietly renders nothing
|
||||||
|
// without a network" story as the Wikipedia/Radar/Contour lines overlays.
|
||||||
|
const ONLINE_SOURCE_ID = "hiking-trails-online";
|
||||||
|
const ONLINE_LAYER_ID = "hiking-trails-online-line";
|
||||||
|
const TILE_URL = "https://tile.waymarkedtrails.org/hiking/{z}/{x}/{y}.png";
|
||||||
|
const ATTRIBUTION =
|
||||||
|
'Trails © <a href="https://waymarkedtrails.org" target="_blank">Waymarked Trails</a> (CC-BY-SA), map data © OpenStreetMap contributors';
|
||||||
|
const OPACITY = 0.85;
|
||||||
|
|
||||||
|
// Offline: real vector trails for each country the viewer actually picks,
|
||||||
|
// GeoJSON generated at build time by Maptool::HikingTrails straight
|
||||||
|
// from the same Geofabrik .osm.pbf offline.pmtiles itself is built from
|
||||||
|
// (see lib/maptool/hiking_trails.rb, PmtilesBuilder.extract_hiking_trails)
|
||||||
|
// — no network needed, and no waiting on Overpass for click details either
|
||||||
|
// (hit-tested locally via LaneLayer.queryLane instead, see _handleClick
|
||||||
|
// below). One file per country rather than one combined file: a viewer
|
||||||
|
// covering a dozen countries' worth of offline map data rarely wants all
|
||||||
|
// of their hiking trails loaded at once, so this is opt-in per country
|
||||||
|
// instead.
|
||||||
|
//
|
||||||
|
// Drawn with maplibre-gl-lanes rather than a plain line layer: a stretch
|
||||||
|
// of path several marked routes share (very common with KST blazes) is
|
||||||
|
// otherwise one line drawn over and over, only the topmost color visible.
|
||||||
|
// Lanes puts each route in its own parallel lane instead. It needs routes
|
||||||
|
// sharing a path to share exact vertices, which OSM relations over shared
|
||||||
|
// ways already do. One LaneLayer over every picked country, not one per
|
||||||
|
// country, so lanes at a border stay ordered together. Doesn't drape over
|
||||||
|
// 3D terrain (sinks under raised ground, warns on the console) — a known
|
||||||
|
// limitation of the library, not worth working around for an overlay.
|
||||||
|
const LANES_LAYER_ID = "hiking-trails-lanes";
|
||||||
|
const LANE_SIZES = () => ({ spacing: 3.5, width: 3, casingWidth: 0.5 });
|
||||||
|
function offlineUrl(code) {
|
||||||
|
return `/tiles/hiking_trails/${code}.geojson`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// waymarkedtrails.org's own fallback for an untagged route.
|
||||||
|
const DEFAULT_TRAIL_COLOR = "#c1272d";
|
||||||
|
// Real-world `colour` values seen in production data (see the 2026-09-13
|
||||||
|
// extraction against SK.osm.pbf) — anything outside this set (typos like
|
||||||
|
// "yelllow", multi-value combos like "blue, yellow", stray hex codes) falls
|
||||||
|
// back to DEFAULT_TRAIL_COLOR rather than being trusted as a CSS value
|
||||||
|
// straight from OSM tag text.
|
||||||
|
const KNOWN_TRAIL_COLORS = new Set(["red", "yellow", "blue", "green", "orange", "purple", "brown", "black", "white", "grey", "gray", "darkblue"]);
|
||||||
|
|
||||||
|
function normalizedTrailColor(raw) {
|
||||||
|
if (!raw) return DEFAULT_TRAIL_COLOR;
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (KNOWN_TRAIL_COLORS.has(trimmed.toLowerCase())) return trimmed.toLowerCase();
|
||||||
|
return /^#[0-9a-fA-F]{3,8}$/.test(trimmed) ? trimmed : DEFAULT_TRAIL_COLOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Online mode's highlight outline for whichever route(s) the last click
|
||||||
|
// matched — drawn as its own separate source/layer (real geometry, from the
|
||||||
|
// Overpass response), since the raster tiles have no paintable features to
|
||||||
|
// highlight at all. Offline lanes use LaneLayer.setHighlight instead. Same "yellow outline on a picked
|
||||||
|
// route" treatment the earlier SVG prototype visualization used.
|
||||||
|
const HIGHLIGHT_SOURCE_ID = "hiking-trails-highlight";
|
||||||
|
const HIGHLIGHT_LAYER_ID = "hiking-trails-highlight-line";
|
||||||
|
|
||||||
|
// An Overlays panel item — a plain on/off checkbox (raster overlay) until
|
||||||
|
// offline extracts exist for at least one country, at which point it
|
||||||
|
// becomes an expandable per-country checklist instead (same expand shape
|
||||||
|
// as RadarControl), since a viewer with a dozen countries' worth of
|
||||||
|
// offline map data rarely wants all of their hiking trails loaded, let
|
||||||
|
// alone parsed by the browser, at once.
|
||||||
|
export class HikingTrailsControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._active = false; // the online-mode checkbox's own state
|
||||||
|
this._offlineCountries = []; // codes with an extract available on the server
|
||||||
|
this._selectedCountries = new Set(); // codes currently shown, offline mode
|
||||||
|
this._trailData = new Map(); // code -> fetched GeoJSON, kept across toggles/style swaps
|
||||||
|
this._routeInfo = new Map(); // String(relation id) -> feature properties, for the panel
|
||||||
|
this._lanes = null; // the current LaneLayer, null when no country is shown
|
||||||
|
|
||||||
|
map.on("click", (event) => this._handleClick(event));
|
||||||
|
|
||||||
|
this._panel = document.getElementById("trail-panel");
|
||||||
|
this._panelBody = this._panel.querySelector(".trail-panel__body");
|
||||||
|
this._panel.querySelector(".trail-panel__close").addEventListener("click", () => this._hidePanel());
|
||||||
|
|
||||||
|
// setStyle() (switching Online/Offline) tears down all custom sources/
|
||||||
|
// layers — put whatever was showing back, same as every other
|
||||||
|
// Overlays control. The highlight is transient (tied to whichever
|
||||||
|
// trail is showing in the panel, if any) — simpler to just drop it
|
||||||
|
// here than try to carry it across a style swap.
|
||||||
|
map.on("style.load", () => {
|
||||||
|
if (this._active) this._addOnlineLayer();
|
||||||
|
this._clearHighlight();
|
||||||
|
if (this._selectedCountries.size > 0) this._rebuildLanes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="overlays-panel__row">
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${WALKING_ICON}</span>
|
||||||
|
<button type="button" class="overlays-panel__name">Hiking trails</button>
|
||||||
|
</div>
|
||||||
|
<div class="overlays-panel__details" hidden>
|
||||||
|
<ul class="hiking-trail-countries"></ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
||||||
|
this._nameButton = li.querySelector(".overlays-panel__name");
|
||||||
|
this._details = li.querySelector(".overlays-panel__details");
|
||||||
|
this._countryList = li.querySelector(".hiking-trail-countries");
|
||||||
|
|
||||||
|
this._checkbox.addEventListener("change", (event) => this._toggleOnline(event.target.checked));
|
||||||
|
this._nameButton.addEventListener("click", () => {
|
||||||
|
if (this._offlineCountries.length === 0) return; // nothing to expand into yet
|
||||||
|
const expanding = this._details.hidden;
|
||||||
|
this._details.hidden = !expanding;
|
||||||
|
this._nameButton.setAttribute("aria-expanded", String(expanding));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called once map.js's GET /api/hiking-trails check resolves. Switches
|
||||||
|
// this item from "online raster, plain on/off" to "offline vector,
|
||||||
|
// pick your countries" — the two are mutually exclusive rather than
|
||||||
|
// both offered at once, since once a real local extract exists there's
|
||||||
|
// no reason to still fall back to a third-party tile server for any of
|
||||||
|
// it.
|
||||||
|
setOfflineCountries(countries) {
|
||||||
|
this._offlineCountries = countries;
|
||||||
|
if (countries.length === 0) return;
|
||||||
|
|
||||||
|
if (this._active) this._toggleOnline(false);
|
||||||
|
this._checkbox.hidden = true;
|
||||||
|
|
||||||
|
this._countryList.innerHTML = countries
|
||||||
|
.map(
|
||||||
|
(code) => `
|
||||||
|
<li class="hiking-trail-countries__item">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="hiking-trail-countries__toggle" value="${code}">
|
||||||
|
${code}
|
||||||
|
</label>
|
||||||
|
</li>
|
||||||
|
`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
this._countryList.querySelectorAll(".hiking-trail-countries__toggle").forEach((input) => {
|
||||||
|
input.addEventListener("change", (event) => this._toggleCountry(event.target.value, event.target.checked));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggleOnline(active) {
|
||||||
|
this._active = active;
|
||||||
|
if (active) this._addOnlineLayer();
|
||||||
|
else this._removeOnlineLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _toggleCountry(code, active) {
|
||||||
|
if (!active) {
|
||||||
|
this._selectedCountries.delete(code);
|
||||||
|
this._rebuildLanes();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selectedCountries.add(code);
|
||||||
|
if (!this._trailData.has(code)) {
|
||||||
|
let geojson;
|
||||||
|
try {
|
||||||
|
geojson = await (await fetch(offlineUrl(code))).json();
|
||||||
|
} catch {
|
||||||
|
return; // degrade quietly, same story as every other optional layer here
|
||||||
|
}
|
||||||
|
// Normalized once here so an untrustworthy `colour` tag value never
|
||||||
|
// reaches the layer's color directly — same reasoning gpx.js's own
|
||||||
|
// feature.properties.color ||= TRACK_COLOR normalization gives.
|
||||||
|
for (const feature of geojson.features) {
|
||||||
|
feature.properties.color = normalizedTrailColor(feature.properties.colour);
|
||||||
|
}
|
||||||
|
this._trailData.set(code, geojson);
|
||||||
|
if (!this._selectedCountries.has(code)) return; // toggled back off while fetching
|
||||||
|
}
|
||||||
|
this._rebuildLanes();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rebuilt from scratch on every country toggle and style swap (setStyle
|
||||||
|
// drops custom layers too) — lane order depends on every route drawn, so
|
||||||
|
// there's no cheaper incremental path. Ordering runs in a worker; a newer
|
||||||
|
// rebuild started meanwhile wins, same token guard as _handleOnlineClick.
|
||||||
|
async _rebuildLanes() {
|
||||||
|
const build = (this._lanesBuild = {});
|
||||||
|
const features = [...this._selectedCountries].flatMap((code) => this._trailData.get(code)?.features || []);
|
||||||
|
let graph = null;
|
||||||
|
if (features.length > 0) {
|
||||||
|
graph = buildLineGraph(features, { routeProperty: "id" });
|
||||||
|
await orderLanesAsync(graph);
|
||||||
|
}
|
||||||
|
if (this._lanesBuild !== build) return;
|
||||||
|
|
||||||
|
this._hidePanel();
|
||||||
|
if (this._map.getLayer(LANES_LAYER_ID)) this._map.removeLayer(LANES_LAYER_ID);
|
||||||
|
this._lanes = null;
|
||||||
|
this._routeInfo = new Map(features.map((feature) => [String(feature.properties.id), feature.properties]));
|
||||||
|
if (!graph) return;
|
||||||
|
|
||||||
|
this._lanes = new LaneLayer({ id: LANES_LAYER_ID, graph, sizes: LANE_SIZES, casingColor: "rgba(255, 255, 255, 0.8)" });
|
||||||
|
// Below labels (same firstSymbolLayerId spot contour.js/radar.js use),
|
||||||
|
// so place names/POI icons stay readable on top of the trail lines.
|
||||||
|
this._map.addLayer(this._lanes, firstSymbolLayerId(this._map));
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleClick(event) {
|
||||||
|
if (this._selectedCountries.size > 0) this._handleOfflineClick(event);
|
||||||
|
else if (this._active) this._handleOnlineClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The lanes have real geometry to hit-test directly — no network
|
||||||
|
// round-trip, so this resolves instantly. Each route has its own lane, so
|
||||||
|
// a click picks exactly one, highlighted by the layer itself. A genuine
|
||||||
|
// miss just closes an already-open panel rather than showing a "not
|
||||||
|
// found" message, same as clicking empty space anywhere else on the map.
|
||||||
|
_handleOfflineClick(event) {
|
||||||
|
const hit = this._lanes?.queryLane(event.point, 8);
|
||||||
|
const info = hit && this._routeInfo.get(String(hit.route));
|
||||||
|
if (!info) {
|
||||||
|
this._hidePanel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._lanes.setHighlight(hit.route);
|
||||||
|
this._showPanel(routeHtml(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
_showPanel(html) {
|
||||||
|
this._panelBody.innerHTML = html;
|
||||||
|
this._panel.hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_hidePanel() {
|
||||||
|
if (this._panel.hidden) return;
|
||||||
|
this._panel.hidden = true;
|
||||||
|
this._clearHighlight();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The raster tiles have no queryable features (they're pixels), so
|
||||||
|
// "what trail is this" is answered with a one-off Overpass query instead
|
||||||
|
// of a MapLibre feature hit-test — an `around` search rather than an
|
||||||
|
// exact point match, since a click rarely lands exactly on the (already
|
||||||
|
// pixel-thick) rendered line.
|
||||||
|
async _handleOnlineClick(event) {
|
||||||
|
const { lng, lat } = event.lngLat;
|
||||||
|
this._showPanel(`<p class="gpx-popup__category">Looking for trails…</p>`);
|
||||||
|
// A newer click's request replaces this one while Overpass is still
|
||||||
|
// answering — same "in-flight response arrives after it's already
|
||||||
|
// stale" guard the old Popup-identity check gave, just keyed off a
|
||||||
|
// fresh token each click instead of a fresh Popup instance.
|
||||||
|
const request = (this._onlineRequest = {});
|
||||||
|
|
||||||
|
let relations;
|
||||||
|
try {
|
||||||
|
relations = await fetchNearbyRoutes(lng, lat);
|
||||||
|
} catch {
|
||||||
|
if (this._onlineRequest === request) this._panelBody.innerHTML = `<p class="gpx-popup__category">Could not reach Overpass.</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this._onlineRequest !== request) return;
|
||||||
|
|
||||||
|
if (relations.length === 0) {
|
||||||
|
this._panelBody.innerHTML = `<p class="gpx-popup__category">No hiking trail found here.</p>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._panelBody.innerHTML = relations.map((relation) => routeHtml(overpassRouteInfo(relation))).join("<hr>");
|
||||||
|
this._setHighlight(relations.map((relation) => ({ geometry: routeGeometry(relation), color: normalizedTrailColor(relation.tags?.colour) })));
|
||||||
|
}
|
||||||
|
|
||||||
|
_addOnlineLayer() {
|
||||||
|
if (this._map.getSource(ONLINE_SOURCE_ID)) return;
|
||||||
|
|
||||||
|
this._map.addSource(ONLINE_SOURCE_ID, {
|
||||||
|
type: "raster",
|
||||||
|
tiles: [TILE_URL],
|
||||||
|
tileSize: 256,
|
||||||
|
maxzoom: 17,
|
||||||
|
attribution: ATTRIBUTION,
|
||||||
|
});
|
||||||
|
// Below labels (same firstSymbolLayerId spot contour.js/radar.js use),
|
||||||
|
// so place names/POI icons stay readable on top of the trail lines.
|
||||||
|
this._map.addLayer(
|
||||||
|
{ id: ONLINE_LAYER_ID, type: "raster", source: ONLINE_SOURCE_ID, paint: { "raster-opacity": OPACITY } },
|
||||||
|
firstSymbolLayerId(this._map),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeOnlineLayer() {
|
||||||
|
if (this._map.getLayer(ONLINE_LAYER_ID)) this._map.removeLayer(ONLINE_LAYER_ID);
|
||||||
|
if (this._map.getSource(ONLINE_SOURCE_ID)) this._map.removeSource(ONLINE_SOURCE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
// features: an array of { geometry, color } — geometry a GeoJSON
|
||||||
|
// LineString or MultiLineString (mixed is fine), color the trail's own
|
||||||
|
// (already-normalized) colour — everything the last click matched, drawn
|
||||||
|
// as one highlight layer (a wider outline in the trail's own color,
|
||||||
|
// rather than a fixed color that would repaint it) on top of the base
|
||||||
|
// trail rendering.
|
||||||
|
_setHighlight(features) {
|
||||||
|
const data = {
|
||||||
|
type: "FeatureCollection",
|
||||||
|
features: features.map(({ geometry, color }) => ({ type: "Feature", properties: { color }, geometry })),
|
||||||
|
};
|
||||||
|
const source = this._map.getSource(HIGHLIGHT_SOURCE_ID);
|
||||||
|
if (source) {
|
||||||
|
source.setData(data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._map.addSource(HIGHLIGHT_SOURCE_ID, { type: "geojson", data });
|
||||||
|
this._map.addLayer({
|
||||||
|
id: HIGHLIGHT_LAYER_ID,
|
||||||
|
type: "line",
|
||||||
|
source: HIGHLIGHT_SOURCE_ID,
|
||||||
|
layout: { "line-cap": "round", "line-join": "round" },
|
||||||
|
paint: { "line-color": ["get", "color"], "line-width": 5, "line-opacity": 0.9 },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearHighlight() {
|
||||||
|
this._lanes?.setHighlight(null);
|
||||||
|
if (this._map.getLayer(HIGHLIGHT_LAYER_ID)) this._map.removeLayer(HIGHLIGHT_LAYER_ID);
|
||||||
|
if (this._map.getSource(HIGHLIGHT_SOURCE_ID)) this._map.removeSource(HIGHLIGHT_SOURCE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// way(around:...) finds candidate ways near the click, then rel(bw...)
|
||||||
|
// walks backward from those to their parent relations — the same
|
||||||
|
// find-the-way-then-its-relation shape as clicking a route on
|
||||||
|
// waymarkedtrails.org's own site. `out geom` inlines each member way's
|
||||||
|
// full coordinate list right on the relation, so no separate stitching
|
||||||
|
// step is needed to get real geometry back.
|
||||||
|
async function queryOverpass(url, query) {
|
||||||
|
const response = await fetch(url, { method: "POST", body: `data=${encodeURIComponent(query)}` });
|
||||||
|
if (!response.ok) throw new Error(`overpass ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchNearbyRoutes(lng, lat) {
|
||||||
|
const query = `[out:json][timeout:15];way(around:${CLICK_RADIUS_M},${lat},${lng})->.w;rel(bw.w)["route"="hiking"];out geom;`;
|
||||||
|
const data = await Promise.any(OVERPASS_APIS.map((url) => queryOverpass(url, query)));
|
||||||
|
return (data.elements || []).filter((element) => element.type === "relation");
|
||||||
|
}
|
||||||
|
|
||||||
|
// OSM doesn't tag a route's length directly — summed from each member
|
||||||
|
// way's own geometry instead, same haversine-sum measure.js's own Measure
|
||||||
|
// tool uses for a hand-drawn line. Shared endpoints between adjacent ways
|
||||||
|
// get walked twice, same as how a real hike over those ways would. Mirrors
|
||||||
|
// Maptool::HikingTrails.relation_feature's own distance calc server-side
|
||||||
|
// (lib/maptool/hiking_trails.rb) — this is the online, no-server-file path.
|
||||||
|
function routeDistance(relation) {
|
||||||
|
return (relation.members || [])
|
||||||
|
.filter((member) => member.type === "way" && member.geometry)
|
||||||
|
.reduce((total, member) => total + lineLength(member.geometry.map((point) => [point.lon, point.lat])), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same member-way walk as routeDistance above, collecting coordinates
|
||||||
|
// instead of summing them — the online click highlight's geometry source,
|
||||||
|
// since there's no vector feature to read it back from the way the offline
|
||||||
|
// layer's own queryRenderedFeatures result already carries one.
|
||||||
|
function routeGeometry(relation) {
|
||||||
|
const lines = (relation.members || [])
|
||||||
|
.filter((member) => member.type === "way" && member.geometry)
|
||||||
|
.map((member) => member.geometry.map((point) => [point.lon, point.lat]));
|
||||||
|
return { type: "MultiLineString", coordinates: lines };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapts a raw Overpass relation into the same shape offline GeoJSON
|
||||||
|
// features' properties already carry (lib/maptool/hiking_trails.rb's own
|
||||||
|
// ROUTE_TAGS + distance_meters + id) — one HTML builder (routeHtml) below
|
||||||
|
// serves both instead of two near-identical ones.
|
||||||
|
function overpassRouteInfo(relation) {
|
||||||
|
const tags = relation.tags || {};
|
||||||
|
return {
|
||||||
|
id: relation.id,
|
||||||
|
name: tags.name,
|
||||||
|
description: tags.description,
|
||||||
|
colour: tags.colour,
|
||||||
|
network: tags.network,
|
||||||
|
operator: tags.operator,
|
||||||
|
ref: tags.ref,
|
||||||
|
distance_meters: routeDistance(relation),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function routeHtml(info) {
|
||||||
|
const name = info.name || info.description || "Hiking trail";
|
||||||
|
const swatch = SAFE_COLOR.test(info.colour || "")
|
||||||
|
? `<span class="hiking-trail-popup__swatch" style="background:${info.colour}"></span>`
|
||||||
|
: "";
|
||||||
|
const meta = [formatDistance(info.distance_meters || 0)];
|
||||||
|
if (info.ref) meta.push(`#${info.ref}`);
|
||||||
|
if (info.network) meta.push(info.network);
|
||||||
|
if (info.operator) meta.push(escapeHtml(info.operator));
|
||||||
|
|
||||||
|
const parts = [
|
||||||
|
`<div class="gpx-popup__header"><strong>${escapeHtml(name)}</strong>${swatch}</div>`,
|
||||||
|
`<p class="gpx-popup__category">${meta.filter(Boolean).join(" · ")}</p>`,
|
||||||
|
];
|
||||||
|
// description already used as the title above when there's no name — only
|
||||||
|
// shown again here as its own line if there both is a name *and* a
|
||||||
|
// description to add, so it's never rendered twice for the common
|
||||||
|
// no-name case.
|
||||||
|
if (info.name && info.description) parts.push(`<p class="gpx-popup__description">${escapeHtml(info.description)}</p>`);
|
||||||
|
parts.push(
|
||||||
|
`<p class="gpx-popup__link"><a href="https://www.openstreetmap.org/relation/${info.id}" target="_blank" rel="noopener noreferrer">View on OpenStreetMap →</a></p>`,
|
||||||
|
);
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
@@ -7,6 +7,22 @@ export const LAYERS_ICON = `
|
|||||||
<path d="M2 14l10 6 10-6"/>
|
<path d="M2 14l10 6 10-6"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
|
// Two overlapping circles — distinct from LAYERS_ICON's stacked-planes
|
||||||
|
// look (that one picks a *base* map; this one is for the sidebar flyout
|
||||||
|
// toggling live overlay layers, e.g. Wikipedia, on top of whichever base
|
||||||
|
// map is showing) while still reading as "layers combining."
|
||||||
|
export const OVERLAYS_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
|
||||||
|
<circle cx="9" cy="12" r="6"/>
|
||||||
|
<circle cx="15" cy="12" r="6"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
export const NOTE_PIN_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z"/>
|
||||||
|
<path d="M12 6.5v5M9.5 9h5"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
export const GPX_ICON = `
|
export const GPX_ICON = `
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<circle cx="5" cy="6" r="2"/>
|
<circle cx="5" cy="6" r="2"/>
|
||||||
@@ -14,11 +30,6 @@ export const GPX_ICON = `
|
|||||||
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5"/>
|
<path d="M6.5 7.5C10 11 8 13 12 13s2 5 6.5 5"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
export const RADAR_ICON = `
|
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round">
|
|
||||||
<path d="M7 18a4 4 0 1 1 .7-7.94A5 5 0 0 1 17 9a4 4 0 0 1 0 8H7z"/>
|
|
||||||
</svg>`;
|
|
||||||
|
|
||||||
export const GNSS_ICON = `
|
export const GNSS_ICON = `
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round">
|
||||||
<circle cx="12" cy="12" r="5.5"/>
|
<circle cx="12" cy="12" r="5.5"/>
|
||||||
@@ -32,6 +43,31 @@ export const RULER_ICON = `
|
|||||||
<path d="M9 10l1.5 1.5M12 7l1.5 1.5M15 4l1.5 1.5M6 13l1.5 1.5"/>
|
<path d="M9 10l1.5 1.5M12 7l1.5 1.5M15 4l1.5 1.5M6 13l1.5 1.5"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
|
// Toolbar readout glyphs (public/js/sun.js's text, assembled with these by
|
||||||
|
// map.js) — same monoline style as the sidebar icons above, no NPS source
|
||||||
|
// for either (that set has no sunrise/sunset pictogram).
|
||||||
|
export const SUNRISE_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 18h18"/>
|
||||||
|
<path d="M6 18a6 6 0 0 1 12 0"/>
|
||||||
|
<path d="M12 13V5"/>
|
||||||
|
<path d="M9 8l3-3 3 3"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
export const SUNSET_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M3 18h18"/>
|
||||||
|
<path d="M6 18a6 6 0 0 1 12 0"/>
|
||||||
|
<path d="M12 5v8"/>
|
||||||
|
<path d="M9 10l3 3 3-3"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
export const SEARCH_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<circle cx="11" cy="11" r="7"/>
|
||||||
|
<path d="M20 20l-4.35-4.35"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
export const NEW_FOLDER_ICON = `
|
export const NEW_FOLDER_ICON = `
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>
|
<path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/>
|
||||||
@@ -44,6 +80,15 @@ export const UPLOAD_ICON = `
|
|||||||
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/>
|
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
|
// The inverse of UPLOAD_ICON (arrow into the tray, not out of it) — used
|
||||||
|
// for the import plugins panel (plugins.js), which pulls data in from
|
||||||
|
// somewhere else rather than sending a file out.
|
||||||
|
export const PLUGIN_ICON = `
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M12 4v11M8 11l4 4 4-4"/>
|
||||||
|
<path d="M4 15v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
// Shown next to each file in the GPX tree, indicating whether it has
|
// Shown next to each file in the GPX tree, indicating whether it has
|
||||||
// waypoints, a track, or both.
|
// waypoints, a track, or both.
|
||||||
export const WAYPOINT_ICON = `
|
export const WAYPOINT_ICON = `
|
||||||
@@ -120,6 +165,46 @@ export const MAP_MARKER_ICON = `
|
|||||||
// tinted at render time, see gpx.js's _registerCategoryGlyphs) so only the
|
// tinted at render time, see gpx.js's _registerCategoryGlyphs) so only the
|
||||||
// alpha shape matters, not the actual `currentColor` value.
|
// alpha shape matters, not the actual `currentColor` value.
|
||||||
|
|
||||||
|
// src/wilderness-22.svg — exported (unlike BICYCLE_TRAIL_ICON below, this
|
||||||
|
// one's also used outside this file) as the Overlays panel's Terrain
|
||||||
|
// shading glyph (overlays.js): the closest honest match in NPS's set for
|
||||||
|
// "mountains/terrain" — it has no weather or elevation-specific pictogram.
|
||||||
|
export const WILDERNESS_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor"><polygon points="18,18 13,15 17,15 13,11 16,11 12,7 15,7 12,4 14,4 11,0 8,4 10,4 7,7 10,7 6,11 9,11 5,15 9,15 4,18 10,18 10,22 12,22 12,18 "/></svg>`;
|
||||||
|
|
||||||
|
// Hand-drawn, not NPS-sourced — that set has no weather/precipitation
|
||||||
|
// pictogram to reuse for the Overlays panel's Radar item, unlike Wikipedia
|
||||||
|
// (WIKI_ICON) and Terrain shading (WILDERNESS_ICON) above.
|
||||||
|
export const RADAR_CLOUD_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor">
|
||||||
|
<path d="M17,10.1c-0.4-3-2.9-5.3-6-5.3c-2.5,0-4.7,1.5-5.6,3.8C3.4,8.9,1,11.1,1,13.9C1,16.7,3.3,19,6.1,19h10.3
|
||||||
|
c2.5,0,4.6-2,4.6-4.5C21,12.2,19.3,10.4,17,10.1z"/>
|
||||||
|
<rect x="6" y="20" width="1.6" height="2"/>
|
||||||
|
<rect x="10.2" y="20" width="1.6" height="2"/>
|
||||||
|
<rect x="14.4" y="20" width="1.6" height="2"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// Hand-drawn, not NPS-sourced — same story as RADAR_CLOUD_ICON above, no
|
||||||
|
// terrain-shadow/sun-behind-a-ridge pictogram in that set either. Used as
|
||||||
|
// the Overlays panel's Terrain shadow glyph (shadow.js via overlays.js).
|
||||||
|
export const TERRAIN_SHADOW_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor">
|
||||||
|
<circle cx="16" cy="6" r="3"/>
|
||||||
|
<polygon points="0,19 6,9 9,13 13,6 22,19"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// Hand-drawn, not NPS-sourced — no contour-line pictogram in that set
|
||||||
|
// either. Concentric rings via fill-rule evenodd, same solid-glyph
|
||||||
|
// convention as WILDERNESS_ICON/RADAR_CLOUD_ICON/TERRAIN_SHADOW_ICON above —
|
||||||
|
// used as the Overlays panel's Contour lines glyph (contour.js).
|
||||||
|
export const CONTOUR_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor" fill-rule="evenodd">
|
||||||
|
<path d="M21,11 A10,10 0 1,0 1,11 A10,10 0 1,0 21,11 Z
|
||||||
|
M18.5,11 A7.5,7.5 0 1,0 3.5,11 A7.5,7.5 0 1,0 18.5,11 Z
|
||||||
|
M16,11 A5,5 0 1,0 6,11 A5,5 0 1,0 16,11 Z
|
||||||
|
M13.5,11 A2.5,2.5 0 1,0 8.5,11 A2.5,2.5 0 1,0 13.5,11 Z"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
// src/bicycle-trail-22.svg — pulled out to its own constant since it's also
|
// src/bicycle-trail-22.svg — pulled out to its own constant since it's also
|
||||||
// used unrasterized, inline, as PARK4NIGHT_TAGS.vtt below.
|
// used unrasterized, inline, as PARK4NIGHT_TAGS.vtt below.
|
||||||
const BICYCLE_TRAIL_ICON = `
|
const BICYCLE_TRAIL_ICON = `
|
||||||
@@ -134,6 +219,20 @@ const BICYCLE_TRAIL_ICON = `
|
|||||||
c1,0,1,1.3,0,1.3H13.1L13.1,9.8z"/>
|
c1,0,1,1.3,0,1.3H13.1L13.1,9.8z"/>
|
||||||
</svg>`;
|
</svg>`;
|
||||||
|
|
||||||
|
// src/store-22.svg — NPS's generic shop pictogram, the closest match in
|
||||||
|
// their set for both a supermarket and a convenience store (no separate
|
||||||
|
// grocery-specific glyph exists there).
|
||||||
|
const STORE_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor">
|
||||||
|
<path d="M20,9c0,0-0.8,3-4,3C16,12,15.8,9.9,20,9z"/>
|
||||||
|
<path d="M17,14.3c0.5-0.5,1.3-0.9,2.1-0.9c1.6,0,2.9,1.3,2.9,3v0.2l0,0c-0.1,0.6-0.3,1.5-0.7,2.4c0,0-0.8,1.6-1.5,2.3l-0.1,0.1
|
||||||
|
c-0.3,0.3-0.8,0.5-1.3,0.5c-0.6,0-1.1-0.5-1.5-1c-0.3,0.5-0.9,1-1.5,1c-0.5,0-0.9-0.2-1.3-0.5L14,21.3c-0.7-0.7-1.5-2.3-1.5-2.3
|
||||||
|
c-0.5-1-0.7-1.8-0.7-2.4l0,0v-0.2c0-1.7,1.3-3,2.9-3C15.7,13.5,16.5,13.8,17,14.3"/>
|
||||||
|
<polygon points="4,0 8,0 8,2 4,2 "/>
|
||||||
|
<path d="M7,3H3c0,0-0.7,0.2-1.2,0.7L0.2,5.8C0.1,6,0,5.9,0,6.1V21c0,0.4,0.6,1,1,1h8c0.4,0,1-0.6,1-1V6C10,6,9.6,3.3,7,3z M4,4v5H3
|
||||||
|
v1H2l-1,1v-1V6h1V5h1L4,4z"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
export const POI_ICONS = {
|
export const POI_ICONS = {
|
||||||
// src/parking-22.svg
|
// src/parking-22.svg
|
||||||
amenity_parking: `
|
amenity_parking: `
|
||||||
@@ -168,8 +267,7 @@ export const POI_ICONS = {
|
|||||||
</svg>`,
|
</svg>`,
|
||||||
|
|
||||||
// src/wilderness-22.svg
|
// src/wilderness-22.svg
|
||||||
forest: `
|
forest: WILDERNESS_ICON,
|
||||||
<svg viewBox="0 0 22 22" fill="currentColor"><polygon points="18,18 13,15 17,15 13,11 16,11 12,7 15,7 12,4 14,4 11,0 8,4 10,4 7,7 10,7 6,11 9,11 5,15 9,15 4,18 10,18 10,22 12,22 12,18 "/></svg>`,
|
|
||||||
|
|
||||||
// src/bicycle-trail-22.svg
|
// src/bicycle-trail-22.svg
|
||||||
sport_cycling: BICYCLE_TRAIL_ICON,
|
sport_cycling: BICYCLE_TRAIL_ICON,
|
||||||
@@ -230,7 +328,11 @@ const FISHING_ICON = `
|
|||||||
const SCENIC_VIEWPOINT_ICON = `
|
const SCENIC_VIEWPOINT_ICON = `
|
||||||
<svg viewBox="0 0 22 22" fill="currentColor"><g transform="translate(4.000000, 0.000000)"><path d="M3.2,11c0,1.2-0.9,2.2-2.1,2.2c-1.2,0-2.1-1-2.1-2.2 c0-1.2,0.9-2.2,2.1-2.2C2.2,8.8,3.2,9.8,3.2,11"/><path d="M4.6,10.4v1.3l10.6,0.8v-3L4.6,10.4"/><path d="M2.3,7.5l1.1,0.7L7.7,2L5.3,0.5L2.3,7.5"/><path d="M4.4,9.8l7.9-3.8l-1.4-2.6L3.8,8.6L4.4,9.8z"/><path d="M2.3,14.5l1.1-0.7l4.3,6.2l-2.4,1.5L2.3,14.5"/><path d="M3.8,13.4l0.6-1.2l7.9,3.8l-1.4,2.6L3.8,13.4"/></g></svg>`;
|
<svg viewBox="0 0 22 22" fill="currentColor"><g transform="translate(4.000000, 0.000000)"><path d="M3.2,11c0,1.2-0.9,2.2-2.1,2.2c-1.2,0-2.1-1-2.1-2.2 c0-1.2,0.9-2.2,2.1-2.2C2.2,8.8,3.2,9.8,3.2,11"/><path d="M4.6,10.4v1.3l10.6,0.8v-3L4.6,10.4"/><path d="M2.3,7.5l1.1,0.7L7.7,2L5.3,0.5L2.3,7.5"/><path d="M4.4,9.8l7.9-3.8l-1.4-2.6L3.8,8.6L4.4,9.8z"/><path d="M2.3,14.5l1.1-0.7l4.3,6.2l-2.4,1.5L2.3,14.5"/><path d="M3.8,13.4l0.6-1.2l7.9,3.8l-1.4,2.6L3.8,13.4"/></g></svg>`;
|
||||||
|
|
||||||
const WALKING_ICON = `
|
// Exported (unlike the other POI glyphs around it) — also used as the
|
||||||
|
// Overlays panel's Hiking trails glyph (hiking_trails.js), same "reuse an
|
||||||
|
// existing NPS-sourced pictogram" story as WILDERNESS_ICON above rather
|
||||||
|
// than hand-drawing another one.
|
||||||
|
export const WALKING_ICON = `
|
||||||
<svg viewBox="0 0 22 22" fill="currentColor"><path d="M10.4,3.6c1,0,1.8-0.8,1.7-1.8c0-1-0.8-1.8-1.8-1.7c-1,0-1.8,0.8-1.7,1.8C8.6,2.8,9.5,3.6,10.4,3.6z"/><path d="M16.5,9.5L13.9,8l-2.2-3.4c-0.3-0.4-0.9-0.8-1.5-0.7C9.7,4,9.3,4.2,9,4.4L5.8,7.7C5.7,7.8,5.6,8,5.6,8.1l-0.4,3.6 c0,0,0,0.1,0,0.1c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.7-0.4,0.8-0.8l0.3-3.1l1.1-1.1l-1,8.6l-1.9,4.1c-0.1,0.1-0.1,0.3-0.1,0.5 c0,0.7,0.6,1.2,1.2,1.2c0.5,0,0.9-0.3,1.1-0.7l2-4.4c0-0.1,0.2-0.4,0.2-0.5l0.2-2.1l2.1,6.9c0.2,0.5,0.6,0.8,1.2,0.8 c0.7,0,1.2-0.6,1.2-1.2c0-0.1,0-0.1,0-0.2L11.5,11l0.4-3.1l0.8,1.3c0.1,0.1,0.1,0.2,0.2,0.2l2.8,1.6c0.1,0,0.2,0.1,0.4,0.1 c0.4,0,0.8-0.4,0.8-0.8C16.9,9.8,16.7,9.6,16.5,9.5z"/></svg>`;
|
<svg viewBox="0 0 22 22" fill="currentColor"><path d="M10.4,3.6c1,0,1.8-0.8,1.7-1.8c0-1-0.8-1.8-1.8-1.7c-1,0-1.8,0.8-1.7,1.8C8.6,2.8,9.5,3.6,10.4,3.6z"/><path d="M16.5,9.5L13.9,8l-2.2-3.4c-0.3-0.4-0.9-0.8-1.5-0.7C9.7,4,9.3,4.2,9,4.4L5.8,7.7C5.7,7.8,5.6,8,5.6,8.1l-0.4,3.6 c0,0,0,0.1,0,0.1c0,0.4,0.4,0.8,0.8,0.8c0.4,0,0.7-0.4,0.8-0.8l0.3-3.1l1.1-1.1l-1,8.6l-1.9,4.1c-0.1,0.1-0.1,0.3-0.1,0.5 c0,0.7,0.6,1.2,1.2,1.2c0.5,0,0.9-0.3,1.1-0.7l2-4.4c0-0.1,0.2-0.4,0.2-0.5l0.2-2.1l2.1,6.9c0.2,0.5,0.6,0.8,1.2,0.8 c0.7,0,1.2-0.6,1.2-1.2c0-0.1,0-0.1,0-0.2L11.5,11l0.4-3.1l0.8,1.3c0.1,0.1,0.1,0.2,0.2,0.2l2.8,1.6c0.1,0,0.2,0.1,0.4,0.1 c0.4,0,0.8-0.4,0.8-0.8C16.9,9.8,16.7,9.6,16.5,9.5z"/></svg>`;
|
||||||
|
|
||||||
const GUIDED_TOURS_ICON = `
|
const GUIDED_TOURS_ICON = `
|
||||||
@@ -293,6 +395,59 @@ export const PARK4NIGHT_TAGS = {
|
|||||||
windsurf: { icon: WIND_SURFING_ICON, label: "Windsurfing" },
|
windsurf: { icon: WIND_SURFING_ICON, label: "Windsurfing" },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Extends POI_ICONS (and so POI_ICON_KEYS, and what a marker badge can
|
||||||
|
// show — see gpx.js's _registerCategoryGlyphs) with a handful of the same
|
||||||
|
// symbols PARK4NIGHT_TAGS above already vendors, reused rather than
|
||||||
|
// duplicated — this app's own hand-drawn notes (notes.js's icon picker)
|
||||||
|
// need marker-badge glyphs for "water source"/"dump station"/"viewpoint"
|
||||||
|
// far more than the original OSM-POI-upload set (parking/hotel/fuel) ever
|
||||||
|
// covered. Object.assign, not more entries in the POI_ICONS literal
|
||||||
|
// itself, since these constants are declared below it in this file.
|
||||||
|
Object.assign(POI_ICONS, {
|
||||||
|
amenity_drinking_water: DRINKING_WATER_ICON,
|
||||||
|
amenity_sanitary_dump_station: SANITARY_DISPOSAL_ICON,
|
||||||
|
amenity_toilets: RESTROOMS_ICON,
|
||||||
|
shop_gas: PROPANE_ICON,
|
||||||
|
amenity_electricity: ELECTRICAL_HOOKUP_ICON,
|
||||||
|
shop_laundry: LAUNDRY_ICON,
|
||||||
|
leisure_playground: PLAYGROUND_ICON,
|
||||||
|
tourism_viewpoint: SCENIC_VIEWPOINT_ICON,
|
||||||
|
leisure_swimming: SWIMMING_ICON,
|
||||||
|
leisure_fishing: FISHING_ICON,
|
||||||
|
sport_climbing: CLIMBING_ICON,
|
||||||
|
route_hiking: WALKING_ICON,
|
||||||
|
shop_supermarket: STORE_ICON,
|
||||||
|
shop_convenience: STORE_ICON,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Hand-drawn, not NPS-sourced — no "download offline map data" pictogram
|
||||||
|
// in that set either. A tray with a downward arrow into it, same solid
|
||||||
|
// 22x22 currentColor convention as CONTOUR_ICON/TERRAIN_SHADOW_ICON above
|
||||||
|
// — used for the "Offline data" sidebar icon (offline_data.js).
|
||||||
|
export const OFFLINE_DATA_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor">
|
||||||
|
<path d="M10,0h2v10.5l3-3l1.4,1.4L11,14.3L5.6,8.9L7,7.5l3,3V0z"/>
|
||||||
|
<path d="M2,15h18v2H2z M2,18h18v2H2z"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
|
// src/bookstore-22.svg (NPS symbol library, same source/license as
|
||||||
|
// POI_ICONS above) — used as the fixed marker glyph for the "Wikipedia"
|
||||||
|
// map layer (gpx.js), not part of POI_ICONS itself: that set is keyed by
|
||||||
|
// OSM/p4n2gpx category and doubles as the note icon-picker's option list
|
||||||
|
// (notes.js), neither of which applies here — every point on this layer
|
||||||
|
// gets the same one glyph, there's no per-feature category to key it by.
|
||||||
|
export const WIKI_ICON = `
|
||||||
|
<svg viewBox="0 0 22 22" fill="currentColor">
|
||||||
|
<path d="M16.6,20.7c-0.6,0-0.9-0.4-0.9-0.9c0-0.5,0.3-0.9,0.9-0.9c0.3,0,0.5-0.2,0.5-0.5
|
||||||
|
S16.9,18,16.6,18c-1.1,0-1.9,0.8-1.9,1.9c0,1,0.7,1.9,1.9,1.9c0.3,0,0.5-0.2,0.5-0.5S16.9,20.7,16.6,20.7L16.6,20.7z"/>
|
||||||
|
<path d="M5,18c0,0,0,0,0,0c0,3.1,0.5,4,2.7,4l8.9,0c0.3,0,0.5-0.2,0.5-0.5
|
||||||
|
c0-0.3-0.2-0.5-0.5-0.5H8c-1.5,0-2-0.5-2-3c0,0,0,0,0-0.1c0-0.3-0.2-0.5-0.5-0.5S5,17.7,5,18L5,18z"/>
|
||||||
|
<path d="M5,8c0-0.6,5-5,5-5h5h1c0.6,0,1,0.4,1,1v14c0,0.6-0.4,1-1,1H6c-0.6,0-1-0.4-1-1
|
||||||
|
V8z M9,4L6,18c0,0,10,0,10,0c0,0,0-14,0-14C16,4,9,4,9,4z"/>
|
||||||
|
<path d="M5,6.8c0-0.5,0.4-1.3,0.8-1.6l6.4-4.7C12.7,0.2,13,0.4,13,1v10
|
||||||
|
c0,0.6-0.3,1.7-0.7,2.1l-6.5,5.3C5.4,18.8,5,18.6,5,18V6.8z"/>
|
||||||
|
</svg>`;
|
||||||
|
|
||||||
export const LIVETRACK_ICON = `
|
export const LIVETRACK_ICON = `
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z"/>
|
<path d="M12 21s7-7.58 7-12a7 7 0 0 0-14 0c0 4.42 7 12 7 12z"/>
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
|||||||
const SESSIONS_URL = "/api/livetrack/sessions";
|
const SESSIONS_URL = "/api/livetrack/sessions";
|
||||||
const TRACK_SOURCE = "livetrack-track";
|
const TRACK_SOURCE = "livetrack-track";
|
||||||
const TRACK_COLOR = "#7209b7";
|
const TRACK_COLOR = "#7209b7";
|
||||||
|
// The planned route (only present when the activity was started from a
|
||||||
|
// saved Garmin course, see LiveTrackClient#fetch) — dashed and muted so it
|
||||||
|
// reads as a reference line, not more recorded data next to the actual
|
||||||
|
// track's own color.
|
||||||
|
const PLANNED_COLOR = "#888";
|
||||||
// Garmin devices post a new point roughly every 15s (postTrackPointFrequency
|
// Garmin devices post a new point roughly every 15s (postTrackPointFrequency
|
||||||
// in the session data) — polling twice that often would just re-fetch the
|
// in the session data) — polling twice that often would just re-fetch the
|
||||||
// same 400+KB page for nothing most of the time.
|
// same 400+KB page for nothing most of the time.
|
||||||
@@ -76,15 +81,18 @@ export class LiveTrackPanelControl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sessions.forEach(({ id, received_at, summary }) => {
|
sessions.forEach(({ id, received_at, summary, map_available }) => {
|
||||||
const li = document.createElement("li");
|
const li = document.createElement("li");
|
||||||
li.className = "livetrack-panel__item";
|
li.className = "livetrack-panel__item";
|
||||||
|
|
||||||
|
const unavailable = map_available === false;
|
||||||
|
|
||||||
const button = document.createElement("button");
|
const button = document.createElement("button");
|
||||||
button.type = "button";
|
button.type = "button";
|
||||||
button.className = "livetrack-panel__row";
|
button.className = "livetrack-panel__row";
|
||||||
button.classList.toggle("active", id === this._activeSessionId);
|
button.classList.toggle("active", id === this._activeSessionId);
|
||||||
button.title = id;
|
button.disabled = unavailable;
|
||||||
|
button.title = unavailable ? "Garmin no longer serves this session's map" : id;
|
||||||
|
|
||||||
const labelSpan = document.createElement("span");
|
const labelSpan = document.createElement("span");
|
||||||
labelSpan.className = "livetrack-panel__label";
|
labelSpan.className = "livetrack-panel__label";
|
||||||
@@ -100,7 +108,7 @@ export class LiveTrackPanelControl {
|
|||||||
} else {
|
} else {
|
||||||
button.append(labelSpan, timeSpan);
|
button.append(labelSpan, timeSpan);
|
||||||
}
|
}
|
||||||
button.addEventListener("click", () => this._toggleTrack(id, button));
|
if (!unavailable) button.addEventListener("click", () => this._toggleTrack(id, button));
|
||||||
li.appendChild(button);
|
li.appendChild(button);
|
||||||
this._list.appendChild(li);
|
this._list.appendChild(li);
|
||||||
});
|
});
|
||||||
@@ -168,11 +176,22 @@ export class LiveTrackPanelControl {
|
|||||||
if (this._map.getSource(TRACK_SOURCE)) return;
|
if (this._map.getSource(TRACK_SOURCE)) return;
|
||||||
|
|
||||||
this._map.addSource(TRACK_SOURCE, { type: "geojson", data: geojson });
|
this._map.addSource(TRACK_SOURCE, { type: "geojson", data: geojson });
|
||||||
|
// Added before the actual track's own line layer so it paints
|
||||||
|
// underneath — the recorded track is the thing that actually matters
|
||||||
|
// when the two overlap.
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${TRACK_SOURCE}-planned`,
|
||||||
|
type: "line",
|
||||||
|
source: TRACK_SOURCE,
|
||||||
|
filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "kind"], "planned"]],
|
||||||
|
layout: { "line-cap": "round", "line-join": "round" },
|
||||||
|
paint: { "line-color": PLANNED_COLOR, "line-width": 2, "line-dasharray": [2, 2] },
|
||||||
|
});
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: `${TRACK_SOURCE}-line`,
|
id: `${TRACK_SOURCE}-line`,
|
||||||
type: "line",
|
type: "line",
|
||||||
source: TRACK_SOURCE,
|
source: TRACK_SOURCE,
|
||||||
filter: ["==", ["geometry-type"], "LineString"],
|
filter: ["all", ["==", ["geometry-type"], "LineString"], ["==", ["get", "kind"], "actual"]],
|
||||||
layout: { "line-cap": "round", "line-join": "round" },
|
layout: { "line-cap": "round", "line-join": "round" },
|
||||||
paint: { "line-color": TRACK_COLOR, "line-width": 3 },
|
paint: { "line-color": TRACK_COLOR, "line-width": 3 },
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
|
||||||
|
// Replaces MapLibre's own GeolocateControl (browser Geolocation API only)
|
||||||
|
// with one that prefers this app's own GNSS receiver when it's turned on
|
||||||
|
// (the sidebar "GNSS" icon, GnssControl) over a phone/laptop's separate
|
||||||
|
// (and often less accurate, e.g. wifi-based) location service. GNSS itself
|
||||||
|
// is only ever used here when explicitly enabled via that sidebar
|
||||||
|
// control — not just whenever a receiver happens to have a fix — so this
|
||||||
|
// doesn't do any polling of its own for that case; GnssControl already
|
||||||
|
// does, and already keeps the map following it once enabled, the same way
|
||||||
|
// this control's own browser-geolocation fallback follows below.
|
||||||
|
//
|
||||||
|
// Reuses MapLibre's own geolocate button styling (same classes/icon
|
||||||
|
// GeolocateControl itself uses, maplibre-gl.css) rather than a new icon —
|
||||||
|
// visually a drop-in replacement, just sourced differently.
|
||||||
|
export class LocateControl {
|
||||||
|
constructor(gnssControl) {
|
||||||
|
this._gnssControl = gnssControl;
|
||||||
|
this._active = false;
|
||||||
|
this._marker = null;
|
||||||
|
this._circleMarker = null;
|
||||||
|
this._accuracy = null;
|
||||||
|
this._watchId = null;
|
||||||
|
this._onMapChange = () => this._updateCircleRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._button = document.createElement("button");
|
||||||
|
this._button.type = "button";
|
||||||
|
this._button.className = "maplibregl-ctrl-geolocate";
|
||||||
|
this._button.title = "Find my location";
|
||||||
|
this._button.setAttribute("aria-label", "Find my location");
|
||||||
|
const icon = document.createElement("span");
|
||||||
|
icon.className = "maplibregl-ctrl-icon";
|
||||||
|
icon.setAttribute("aria-hidden", "true");
|
||||||
|
this._button.appendChild(icon);
|
||||||
|
this._button.addEventListener("click", () => this._click());
|
||||||
|
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "maplibregl-ctrl maplibregl-ctrl-group";
|
||||||
|
this._container.appendChild(this._button);
|
||||||
|
|
||||||
|
// GNSS turning on mid-track (this control's own browser-geolocation
|
||||||
|
// fallback, not GnssControl's — that one manages its own tracking)
|
||||||
|
// should switch over immediately, not just the next time this button
|
||||||
|
// gets clicked; the browser's own position has no business still
|
||||||
|
// being used once the app's own receiver has a fix. No alertIfNoFix
|
||||||
|
// here (unlike a deliberate click, below) — GnssControl only just
|
||||||
|
// turned on and almost certainly hasn't gotten its first fix back yet
|
||||||
|
// (its own poll is async, still in flight at the moment this fires),
|
||||||
|
// so this would otherwise pop a blocking "waiting for a fix" dialog on
|
||||||
|
// every single enable; GnssControl's own next poll will show/recenter
|
||||||
|
// once it actually has something, same as it would either way.
|
||||||
|
this._gnssControl.onEnabledChange((enabled) => {
|
||||||
|
if (enabled && this._active) {
|
||||||
|
this._stop();
|
||||||
|
this._jumpToGnss();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._stop();
|
||||||
|
this._container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPosition() {
|
||||||
|
return "top-left";
|
||||||
|
}
|
||||||
|
|
||||||
|
_click() {
|
||||||
|
if (this._gnssControl.isEnabled()) {
|
||||||
|
this._jumpToGnss({ alertIfNoFix: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._active) {
|
||||||
|
this._stop();
|
||||||
|
} else {
|
||||||
|
this._start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GNSS already follows the map on its own once enabled (GnssControl) and
|
||||||
|
// has no separate "off" state to toggle from here — just jump to its
|
||||||
|
// current fix, for the same instant feedback clicking this button has
|
||||||
|
// always given. alertIfNoFix is opt-in (a deliberate click wants to know
|
||||||
|
// why nothing happened; the automatic mid-track handoff above doesn't —
|
||||||
|
// see its own comment) since a JS alert() blocks the whole page until
|
||||||
|
// dismissed, not something to pop up on a path that isn't a direct user
|
||||||
|
// action.
|
||||||
|
_jumpToGnss({ alertIfNoFix = false } = {}) {
|
||||||
|
const position = this._gnssControl.getPosition();
|
||||||
|
if (position) {
|
||||||
|
this._map.flyTo({ center: position, zoom: Math.max(this._map.getZoom(), 15) });
|
||||||
|
} else if (alertIfNoFix) {
|
||||||
|
window.alert("Waiting for a GNSS fix…");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_start() {
|
||||||
|
this._active = true;
|
||||||
|
this._button.classList.add("maplibregl-ctrl-geolocate-active");
|
||||||
|
this._watchId = navigator.geolocation.watchPosition(
|
||||||
|
(position) => this._setPosition(position.coords.longitude, position.coords.latitude, position.coords.accuracy),
|
||||||
|
() => {
|
||||||
|
window.alert("Could not determine your location.");
|
||||||
|
this._stop();
|
||||||
|
},
|
||||||
|
{ enableHighAccuracy: true },
|
||||||
|
);
|
||||||
|
this._map.on("zoom", this._onMapChange);
|
||||||
|
this._map.on("move", this._onMapChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recenters on every fix, following the position the way GeolocateControl
|
||||||
|
// itself does while tracking — flyTo (with its zoom swoop) only for the
|
||||||
|
// first fix, since that's the one jumping from wherever the map already
|
||||||
|
// was; easeTo for later updates, a plain pan/zoom-preserving follow
|
||||||
|
// rather than repeating that same swoop on every update.
|
||||||
|
//
|
||||||
|
// MapLibre's own blue pulsing "user location" dot styling
|
||||||
|
// (.maplibregl-user-location-dot, maplibre-gl.css — the exact look
|
||||||
|
// GeolocateControl itself used here before), not GnssControl's green
|
||||||
|
// .gnss-marker — reusing that green for a browser-estimated position too
|
||||||
|
// looked like this control was somehow still using GNSS data even when
|
||||||
|
// it very much wasn't; distinct colors for distinct sources.
|
||||||
|
_setPosition(lon, lat, accuracy) {
|
||||||
|
this._accuracy = accuracy;
|
||||||
|
if (!this._marker) {
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.className = "maplibregl-user-location-dot";
|
||||||
|
this._marker = new Marker({ element: el }).setLngLat([lon, lat]).addTo(this._map);
|
||||||
|
const circleEl = document.createElement("div");
|
||||||
|
circleEl.className = "maplibregl-user-location-accuracy-circle";
|
||||||
|
this._circleMarker = new Marker({ element: circleEl, pitchAlignment: "map" }).setLngLat([lon, lat]).addTo(this._map);
|
||||||
|
this._map.flyTo({ center: [lon, lat], zoom: Math.max(this._map.getZoom(), 15) });
|
||||||
|
} else {
|
||||||
|
this._marker.setLngLat([lon, lat]);
|
||||||
|
this._circleMarker.setLngLat([lon, lat]);
|
||||||
|
this._map.easeTo({ center: [lon, lat] });
|
||||||
|
}
|
||||||
|
this._updateCircleRadius();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mirrors MapLibre's own GeolocateControl._updateCircleRadiusIfNeeded:
|
||||||
|
// measures real-world meters per pixel at the marker's position (rather
|
||||||
|
// than assuming a flat conversion, which breaks away from the equator)
|
||||||
|
// by projecting a 100px offset back to lng/lat and measuring that
|
||||||
|
// distance, then sizes the circle element to the accuracy radius in
|
||||||
|
// that many pixels.
|
||||||
|
_updateCircleRadius() {
|
||||||
|
if (!this._circleMarker || !this._accuracy) return;
|
||||||
|
|
||||||
|
const lngLat = this._marker.getLngLat();
|
||||||
|
const point = this._map.project(lngLat);
|
||||||
|
const offsetLngLat = this._map.unproject([point.x + 100, point.y]);
|
||||||
|
const metersPerPixel = lngLat.distanceTo(offsetLngLat) / 100;
|
||||||
|
const diameter = (2 * this._accuracy) / metersPerPixel;
|
||||||
|
this._circleMarker.getElement().style.width = `${diameter.toFixed(2)}px`;
|
||||||
|
this._circleMarker.getElement().style.height = `${diameter.toFixed(2)}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
_stop() {
|
||||||
|
this._active = false;
|
||||||
|
this._button.classList.remove("maplibregl-ctrl-geolocate-active");
|
||||||
|
if (this._watchId !== null) {
|
||||||
|
navigator.geolocation.clearWatch(this._watchId);
|
||||||
|
this._watchId = null;
|
||||||
|
}
|
||||||
|
this._map.off("zoom", this._onMapChange);
|
||||||
|
this._map.off("move", this._onMapChange);
|
||||||
|
if (this._marker) {
|
||||||
|
this._marker.remove();
|
||||||
|
this._marker = null;
|
||||||
|
}
|
||||||
|
if (this._circleMarker) {
|
||||||
|
this._circleMarker.remove();
|
||||||
|
this._circleMarker = null;
|
||||||
|
}
|
||||||
|
this._accuracy = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
Map as MaplibreMap,
|
Map as MaplibreMap,
|
||||||
NavigationControl,
|
NavigationControl,
|
||||||
GeolocateControl,
|
|
||||||
Popup,
|
Popup,
|
||||||
|
ScaleControl,
|
||||||
addProtocol,
|
addProtocol,
|
||||||
} from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
} from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
import { RadarControl } from "/js/radar.js";
|
|
||||||
import { GpxPanelControl } from "/js/gpx.js";
|
import { GpxPanelControl } from "/js/gpx.js";
|
||||||
|
import { OverlaysControl } from "/js/overlays.js";
|
||||||
import { GnssControl } from "/js/gnss.js";
|
import { GnssControl } from "/js/gnss.js";
|
||||||
import { LiveTrackPanelControl } from "/js/livetrack.js";
|
import { LiveTrackPanelControl } from "/js/livetrack.js";
|
||||||
import { TrackProfilePanel } from "/js/track_profile.js";
|
import { TrackProfilePanel } from "/js/track_profile.js";
|
||||||
import { MeasureControl } from "/js/measure.js";
|
import { MeasureControl } from "/js/measure.js";
|
||||||
import { LAYERS_ICON } from "/js/icons.js";
|
import { NotesControl } from "/js/notes.js";
|
||||||
|
import { PluginsControl } from "/js/plugins.js";
|
||||||
|
import { LocateControl } from "/js/locate.js";
|
||||||
|
import { SearchControl } from "/js/search.js";
|
||||||
|
import { OfflineDataControl } from "/js/offline_data.js";
|
||||||
|
import { LAYERS_ICON, WAYPOINT_ICON, SUNRISE_ICON, SUNSET_ICON, PENDING_ICON } from "/js/icons.js";
|
||||||
|
import { formatCoords, formatSunInfo } from "/js/sun.js";
|
||||||
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||||
|
import { isLoggedIn } from "/js/auth.js";
|
||||||
import HomeButton from "/vendor/npmap-home-button/home-button.esm.js";
|
import HomeButton from "/vendor/npmap-home-button/home-button.esm.js";
|
||||||
import OverviewMap from "/vendor/npmap-overview/overview.esm.js";
|
import OverviewMap from "/vendor/npmap-overview/overview.esm.js";
|
||||||
import * as maplibregl from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
import * as maplibregl from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
@@ -21,28 +28,46 @@ const CENTER = [17.1077, 48.1486];
|
|||||||
const ZOOM = 13;
|
const ZOOM = 13;
|
||||||
const ONLINE_STYLE_URL = "https://tiles.openfreemap.org/styles/liberty";
|
const ONLINE_STYLE_URL = "https://tiles.openfreemap.org/styles/liberty";
|
||||||
|
|
||||||
// Recolors OpenFreeMap Liberty to the same NPS-inspired palette as
|
// Recolors OpenFreeMap Liberty to a warm, NPS-Park-Tiles-inspired palette —
|
||||||
// OFFLINE_THEME below — warm cream ground, brown/khaki land cover, green
|
// cream ground, muted roads/boundaries — mapped onto Liberty's OpenMapTiles
|
||||||
// reserved for parks, muted roads/boundaries — mapped onto Liberty's
|
// layer ids. Applied identically regardless of which PMTiles data actually
|
||||||
// OpenMapTiles layer ids instead of protomaps' theme roles (the two
|
// backs the "openmaptiles" source (see offlineStyle() below): offline tiles
|
||||||
// schemas don't share layer names, so this is a separate hand-built
|
// are built by Planetiler's own OpenMapTiles profile (docs/development.adoc),
|
||||||
// table, not a shared object). Only the layers that dominate the map's
|
// the same schema Liberty already expects, so one patch serves both —
|
||||||
// overall color read are touched; labels, halos, rail, and minor
|
// no separate offline theme/palette to keep in sync. Only the layers that
|
||||||
// overlays (cemetery/hospital/school/pitch/track shading, building
|
// dominate the map's overall color read are touched; labels, halos, rail,
|
||||||
// outlines) are left at Liberty's own defaults — same "not a full
|
// and minor overlays (cemetery/hospital/school/pitch/track shading,
|
||||||
// recolor" scope as OFFLINE_THEME. Road casings have no protomaps-role
|
// building outlines) are left at Liberty's own defaults.
|
||||||
// equivalent to reuse (offline roads don't get a separate outline), so
|
//
|
||||||
// #c9a66b there is reasoned to fit the family rather than sourced from
|
// wood/grass are deliberately left unpatched — Liberty's own green (a
|
||||||
// anywhere real.
|
// translucent landcover_wood/landcover_grass wash) already reads well over
|
||||||
|
// the patched cream background.
|
||||||
const ONLINE_STYLE_PATCH = {
|
const ONLINE_STYLE_PATCH = {
|
||||||
background: { "background-color": "#f9f6ef" },
|
background: { "background-color": "#f9f6ef" },
|
||||||
landcover_wood: { "fill-color": "#c9bd9c" },
|
// Liberty washes every built-up area in a translucent gray-tan below
|
||||||
landcover_grass: { "fill-color": "#d9cfb0" },
|
// z12 (landuse_residential, maxzoom 12 — gone by this app's default
|
||||||
|
// z13) that the offline schema has no equivalent of; protomaps just
|
||||||
|
// leaves that ground as plain background/earth. Matched to background
|
||||||
|
// here rather than left alone, so a zoomed-out view doesn't read as
|
||||||
|
// visibly dingier online than offline.
|
||||||
|
landuse_residential: { "fill-color": "#f9f6ef" },
|
||||||
landcover_sand: { "fill-color": "#f0e4c4" },
|
landcover_sand: { "fill-color": "#f0e4c4" },
|
||||||
water: { "fill-color": "#a3cedf" },
|
water: { "fill-color": "#a3cedf" },
|
||||||
park: { "fill-color": "#b3e378" },
|
// fill-outline-color isn't touched by a plain fill-color patch — Liberty
|
||||||
park_outline: { "line-color": "#546619" },
|
// draws it separately, defaulting to a bright green that reads as a
|
||||||
building: { "fill-color": "#ded7cc" },
|
// distinct border around named conservation areas (e.g. the Malé
|
||||||
|
// Karpaty CHKO). Matched to the fill itself, and park_outline's own
|
||||||
|
// dashed line hidden below, so a reserve blends in as ordinary forest —
|
||||||
|
// offline has no equivalent stroke at all (see park_a/park_b below),
|
||||||
|
// and the point was never to call reserves out as visibly different,
|
||||||
|
// on either side.
|
||||||
|
park: { "fill-color": "#b3e378", "fill-outline-color": "#b3e378" },
|
||||||
|
park_outline: { "line-opacity": 0 },
|
||||||
|
// fill-opacity: 0.5 matches the offline theme's own "buildings" layer —
|
||||||
|
// without it, Liberty's dense individual building footprints (each with
|
||||||
|
// its own outline) paint fully opaque and read much heavier/grayer than
|
||||||
|
// offline's softer, half-transparent fill over the same cream ground.
|
||||||
|
building: { "fill-color": "#ded7cc", "fill-opacity": 0.5 },
|
||||||
boundary_2: { "line-color": "#546619" },
|
boundary_2: { "line-color": "#546619" },
|
||||||
boundary_3: { "line-color": "#546619" },
|
boundary_3: { "line-color": "#546619" },
|
||||||
|
|
||||||
@@ -50,19 +75,30 @@ const ONLINE_STYLE_PATCH = {
|
|||||||
// minor (residential/service/track), pedestrian — each repeated for its
|
// minor (residential/service/track), pedestrian — each repeated for its
|
||||||
// tunnel_/bridge_ counterpart so a road doesn't change color when it
|
// tunnel_/bridge_ counterpart so a road doesn't change color when it
|
||||||
// dips into a tunnel or crosses a bridge. Rail is left alone.
|
// dips into a tunnel or crosses a bridge. Rail is left alone.
|
||||||
road_motorway: { "line-color": "#fdefbe" },
|
//
|
||||||
road_trunk_primary: { "line-color": "#fdefbe" },
|
// line-width below is a flat number, not Liberty's own zoom-interpolated
|
||||||
road_secondary_tertiary: { "line-color": "#fef7dd" },
|
// expression — picked to match the offline theme's actual rendered width
|
||||||
road_link: { "line-color": "#fef7dd" },
|
// at this app's default zoom (13; verified live via queryRenderedFeatures
|
||||||
road_motorway_link: { "line-color": "#fef7dd" },
|
// on both sides), since without it Liberty's own curve draws surface
|
||||||
|
// roads roughly twice as thick as offline at street level, reading as a
|
||||||
|
// much bolder road network online.
|
||||||
|
// ponytail: tuned for z13 only, not a matching curve across all zooms —
|
||||||
|
// will drift back apart at very different zoom levels. Upgrade path:
|
||||||
|
// sample offline's width at a few more zooms and replace with a matching
|
||||||
|
// ["interpolate", ["linear"], ["zoom"], ...] expression.
|
||||||
|
road_motorway: { "line-color": "#fdefbe", "line-width": 2.26 },
|
||||||
|
road_trunk_primary: { "line-color": "#fdefbe", "line-width": 2.26 },
|
||||||
|
road_secondary_tertiary: { "line-color": "#fef7dd", "line-width": 1.87 },
|
||||||
|
road_link: { "line-color": "#fef7dd", "line-width": 1 },
|
||||||
|
road_motorway_link: { "line-color": "#fef7dd", "line-width": 1 },
|
||||||
road_minor: { "line-color": "#fffdf8" },
|
road_minor: { "line-color": "#fffdf8" },
|
||||||
road_service_track: { "line-color": "#fffdf8" },
|
road_service_track: { "line-color": "#fffdf8" },
|
||||||
road_path_pedestrian: { "line-color": "#e9e2d8" },
|
road_path_pedestrian: { "line-color": "#e9e2d8" },
|
||||||
road_motorway_casing: { "line-color": "#c9a66b" },
|
road_motorway_casing: { "line-color": "#c9a66b", "line-width": 1.48 },
|
||||||
road_trunk_primary_casing: { "line-color": "#c9a66b" },
|
road_trunk_primary_casing: { "line-color": "#c9a66b", "line-width": 1.48 },
|
||||||
road_secondary_tertiary_casing: { "line-color": "#c9a66b" },
|
road_secondary_tertiary_casing: { "line-color": "#c9a66b", "line-width": 1 },
|
||||||
road_link_casing: { "line-color": "#c9a66b" },
|
road_link_casing: { "line-color": "#c9a66b", "line-width": 1.5 },
|
||||||
road_motorway_link_casing: { "line-color": "#c9a66b" },
|
road_motorway_link_casing: { "line-color": "#c9a66b", "line-width": 1.5 },
|
||||||
road_minor_casing: { "line-color": "#c9a66b" },
|
road_minor_casing: { "line-color": "#c9a66b" },
|
||||||
road_service_track_casing: { "line-color": "#c9a66b" },
|
road_service_track_casing: { "line-color": "#c9a66b" },
|
||||||
|
|
||||||
@@ -103,6 +139,32 @@ function patchOnlineStyle(style) {
|
|||||||
const patch = ONLINE_STYLE_PATCH[layer.id];
|
const patch = ONLINE_STYLE_PATCH[layer.id];
|
||||||
if (patch) Object.assign((layer.paint ??= {}), patch);
|
if (patch) Object.assign((layer.paint ??= {}), patch);
|
||||||
}
|
}
|
||||||
|
return insertTrackOnlyLayer(style);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Liberty's own "road_service_track" layer paints service roads (paved
|
||||||
|
// driveways/parking aisles) and actual unpaved tracks the same color —
|
||||||
|
// fine for a general-purpose map, not for this app's "can the camper get
|
||||||
|
// there" use case. The underlying OpenMapTiles data does carry `class:
|
||||||
|
// "track"` separately from `"service"` (see the filter below), so rather
|
||||||
|
// than recoloring the merged layer (which would mislabel paved driveways
|
||||||
|
// as unpaved), this clones it into a second layer scoped to `class ==
|
||||||
|
// "track"` only, dashed and dirt-brown — matching how the offline theme
|
||||||
|
// already renders protomaps' own separate "other"/"path" road kind.
|
||||||
|
// Surface-level only (excludes tunnel_/bridge_ variants): an unpaved track
|
||||||
|
// in a tunnel or on a bridge is rare enough not to be worth a third layer.
|
||||||
|
function insertTrackOnlyLayer(style) {
|
||||||
|
const index = style.layers.findIndex((layer) => layer.id === "road_service_track");
|
||||||
|
if (index === -1) return style;
|
||||||
|
|
||||||
|
const base = style.layers[index];
|
||||||
|
const trackOnly = {
|
||||||
|
...base,
|
||||||
|
id: "road_track_only",
|
||||||
|
filter: ["all", base.filter, ["==", ["get", "class"], "track"]],
|
||||||
|
paint: { ...base.paint, "line-color": "#c9a66b", "line-dasharray": [2, 2] },
|
||||||
|
};
|
||||||
|
style.layers.splice(index + 1, 0, trackOnly);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,76 +189,45 @@ async function loadOnlineStyle() {
|
|||||||
|
|
||||||
const ONLINE_STYLE = await loadOnlineStyle();
|
const ONLINE_STYLE = await loadOnlineStyle();
|
||||||
|
|
||||||
|
// A vendored copy of the same Liberty style ONLINE_STYLE fetches live
|
||||||
|
// (public/vendor/liberty-style/liberty.json — a point-in-time snapshot, not
|
||||||
|
// auto-updated) that offlineStyle() below patches+repoints the exact same
|
||||||
|
// way. Offline mode can't depend on ONLINE_STYLE_URL actually being
|
||||||
|
// reachable — that fetch failing/timing out is the normal case in the
|
||||||
|
// field, not an edge case — so this is a local, same-origin fetch instead
|
||||||
|
// (fast and always available, since it's served by this app itself).
|
||||||
|
const OFFLINE_STYLE_TEMPLATE = await fetch("/vendor/liberty-style/liberty.json").then((response) => response.json());
|
||||||
|
|
||||||
const protocol = new pmtiles.Protocol();
|
const protocol = new pmtiles.Protocol();
|
||||||
addProtocol("pmtiles", protocol.tile);
|
addProtocol("pmtiles", protocol.tile);
|
||||||
|
|
||||||
// Nudged from protomaps-themes-base's stock "light" theme towards the
|
// Builds the offline style from OFFLINE_STYLE_TEMPLATE — the identical
|
||||||
// National Park Service's own "Park Tiles" cartography — a warm,
|
// Liberty JSON + ONLINE_STYLE_PATCH recolor as the online map, just
|
||||||
// Harpers-Ferry-Center-derived look. Not their actual map: a handful of
|
// repointed at a local PMTiles source instead of tiles.openfreemap.org.
|
||||||
// values below (marked) are the genuine paint colors pulled from a public
|
// This works because data/offline.pmtiles is now built by Planetiler's own
|
||||||
// Park Tiles style JSON
|
// OpenMapTiles profile (see docs/development.adoc), the same vector schema
|
||||||
// (nps.gov/lib/npmap.js/style/ck72fwp2642dv07o7tbqinvz4.json, the "Imagery"
|
// Liberty expects — before that, offline tiles used a different schema
|
||||||
// variant — the only one of their four styles served from a plain,
|
// (protomaps' own basemaps) needing a completely separate hand-tuned
|
||||||
// unauthenticated URL; "Standard"/"Slate"/"Light" resolve through Mapbox
|
// theme (protomaps-themes-base + OFFLINE_THEME) that could never be made
|
||||||
// Studio's private API with an access token, which isn't something to
|
// to match ONLINE_STYLE_PATCH's look exactly, no matter how much either
|
||||||
// piggyback on even though it's technically visible in their own JS bundle).
|
// side got tuned.
|
||||||
// The rest are reasoned to match that same family (warm cream ground, olive
|
|
||||||
// greens, muted blue) rather than sourced. Not a full recolor: labels,
|
|
||||||
// casings, and roles with no clean real-color anchor are left at theme
|
|
||||||
// defaults.
|
|
||||||
//
|
//
|
||||||
// Recreating the color choices is legitimate — that's not the kind of thing
|
// glyphs/sprite point at vendored local copies (public/vendor/fonts,
|
||||||
// a data license restricts (see docs/architecture.adoc's "Offline maps"
|
// public/vendor/liberty-sprite) rather than tiles.openfreemap.org's —
|
||||||
// section) — what stays off-limits is their actual tile *service*: its
|
// glyphs are Latin-only (see docs/architecture.adoc: labels in other
|
||||||
// OSM/terrain layers are licensed through Mapbox Atlas, a commercial,
|
// scripts won't render offline), sprite is a one-time snapshot like
|
||||||
// non-sublicensable data service scoped to NPS.
|
// OFFLINE_STYLE_TEMPLATE itself. ne2_shaded (Liberty's low-zoom shaded-
|
||||||
const OFFLINE_THEME = {
|
// relief backdrop, z0-6 only) is left pointing at its remote URL
|
||||||
...protomaps_themes_base.namedTheme("light"),
|
// unchanged — offline usage is regional, not whole-earth zoomed out, so
|
||||||
background: "#f9f6ef", // real: Park Tiles' own background, hsl(40, 37%, 97%)
|
// this only ever silently fails to load at a zoom level offline users
|
||||||
earth: "#f9f6ef",
|
// aren't in, the same graceful-degradation story as this app's other
|
||||||
water: "#a3cedf",
|
// online-only extras (Wikipedia, live radar).
|
||||||
// Brown/khaki, not green: in Park Tiles' own Imagery style, green is
|
|
||||||
// specifically the protected-area/park color (below), not a general land
|
|
||||||
// cover fill — actual forest there is just the aerial photo underneath,
|
|
||||||
// which this offline style has no equivalent of. Using that same vivid
|
|
||||||
// green for ordinary wood/scrub as well (an earlier version of this
|
|
||||||
// theme did) made everything read as "green," not "brown with green
|
|
||||||
// parks" — reversed here to match the real distinction.
|
|
||||||
wood_a: "#c9bd9c",
|
|
||||||
wood_b: "#bcae8a",
|
|
||||||
scrub_a: "#d9cfb0",
|
|
||||||
scrub_b: "#cfc29d",
|
|
||||||
park_a: "#c8e2a0",
|
|
||||||
park_b: "#b3e378", // real: Park Tiles' protected-area/park-point green
|
|
||||||
sand: "#f0e4c4",
|
|
||||||
beach: "#f0e4c4",
|
|
||||||
buildings: "#ded7cc",
|
|
||||||
pedestrian: "#e9e2d8",
|
|
||||||
highway: "#fdefbe", // real: Park Tiles' primary/highway road color
|
|
||||||
major: "#fef7dd", // real: Park Tiles' secondary road color
|
|
||||||
minor: "#fffdf8",
|
|
||||||
link: "#fef7dd",
|
|
||||||
boundaries: "#546619", // real: Park Tiles' park-boundary line color
|
|
||||||
};
|
|
||||||
|
|
||||||
function offlineStyle() {
|
function offlineStyle() {
|
||||||
const pmtilesUrl = `${window.location.origin}/tiles/offline.pmtiles`;
|
const style = patchOnlineStyle(structuredClone(OFFLINE_STYLE_TEMPLATE));
|
||||||
|
style.sources.openmaptiles.url = `pmtiles://${window.location.origin}/tiles/offline.pmtiles`;
|
||||||
return {
|
style.glyphs = `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`;
|
||||||
version: 8,
|
style.sprite = `${window.location.origin}/vendor/liberty-sprite/ofm`;
|
||||||
glyphs: `${window.location.origin}/vendor/fonts/{fontstack}/{range}.pbf`,
|
return style;
|
||||||
sources: {
|
|
||||||
offline: {
|
|
||||||
type: "vector",
|
|
||||||
url: `pmtiles://${pmtilesUrl}`,
|
|
||||||
attribution: "© OpenStreetMap contributors",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// lang: "en" pulls in labels_layers (see docs/architecture.adoc) — only
|
|
||||||
// the Latin-script glyph ranges are vendored, so labels using other
|
|
||||||
// scripts won't render offline.
|
|
||||||
layers: protomaps_themes_base.layers("offline", OFFLINE_THEME, { lang: "en" }),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A sidebar icon that opens a full-height side panel (like the GPX one)
|
// A sidebar icon that opens a full-height side panel (like the GPX one)
|
||||||
@@ -324,6 +355,65 @@ class ZoomLevelControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tilts the camera between a flat top-down view and a fixed 3D-perspective
|
||||||
|
// pitch — bearing stays untouched (this map never rotates, see the
|
||||||
|
// NavigationControl comment below), so this is purely "look at it from an
|
||||||
|
// angle", not a rotate/compass control. Independent of the "Terrain
|
||||||
|
// shading" overlay toggle (overlays.js) — that adds a raster-dem hillshade/
|
||||||
|
// color-relief layer, this just moves the camera; combining both gives a
|
||||||
|
// real sense of relief but neither depends on the other.
|
||||||
|
const PITCH_3D = 60;
|
||||||
|
|
||||||
|
class PitchToggleControl {
|
||||||
|
onAdd(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "maplibregl-ctrl maplibregl-ctrl-group pitch-toggle-ctrl";
|
||||||
|
this._button = document.createElement("button");
|
||||||
|
this._button.type = "button";
|
||||||
|
this._button.className = "pitch-toggle-ctrl__button";
|
||||||
|
this._button.addEventListener("click", () => this._toggle());
|
||||||
|
this._container.appendChild(this._button);
|
||||||
|
|
||||||
|
this._update = () => this._render();
|
||||||
|
map.on("pitchend", this._update);
|
||||||
|
this._render();
|
||||||
|
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._map.off("pitchend", this._update);
|
||||||
|
this._container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPosition() {
|
||||||
|
return "top-left";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set once overlaysControl exists (it's built later in this file) —
|
||||||
|
// lets raised terrain (OverlaysControl.syncTerrain, see overlays.js)
|
||||||
|
// follow the pitch toggle without this control needing to know anything
|
||||||
|
// about hillshade/DEM sources itself.
|
||||||
|
setOverlaysControl(overlaysControl) {
|
||||||
|
this._overlaysControl = overlaysControl;
|
||||||
|
this._overlaysControl.syncTerrain(this._map.getPitch() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle() {
|
||||||
|
this._map.easeTo({ pitch: this._map.getPitch() > 0 ? 0 : PITCH_3D, duration: 300 });
|
||||||
|
}
|
||||||
|
|
||||||
|
_render() {
|
||||||
|
const is3d = this._map.getPitch() > 0;
|
||||||
|
this._button.textContent = is3d ? "2D" : "3D";
|
||||||
|
this._button.title = is3d ? "Switch to 2D view" : "Switch to 3D view";
|
||||||
|
this._button.setAttribute("aria-label", this._button.title);
|
||||||
|
this._button.classList.toggle("active", is3d);
|
||||||
|
this._overlaysControl?.syncTerrain(is3d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const map = new MaplibreMap({
|
const map = new MaplibreMap({
|
||||||
container: "map",
|
container: "map",
|
||||||
style: ONLINE_STYLE,
|
style: ONLINE_STYLE,
|
||||||
@@ -349,25 +439,38 @@ const map = new MaplibreMap({
|
|||||||
// above), "current view" and "the default view" are no longer the same
|
// above), "current view" and "the default view" are no longer the same
|
||||||
// thing, and a home button that resets to wherever you happened to load
|
// thing, and a home button that resets to wherever you happened to load
|
||||||
// the page wouldn't be a home button. This is *not* "go to my location" —
|
// the page wouldn't be a home button. This is *not* "go to my location" —
|
||||||
// that's GeolocateControl, below.
|
// that's LocateControl, below.
|
||||||
const SLOVAKIA_BOUNDS = [
|
const SLOVAKIA_BOUNDS = [
|
||||||
[16.83, 47.73],
|
[16.83, 47.73],
|
||||||
[22.57, 49.61],
|
[22.57, 49.61],
|
||||||
];
|
];
|
||||||
|
map.addControl(new SearchControl(), "top-left");
|
||||||
map.addControl(new HomeButton({ bounds: SLOVAKIA_BOUNDS }), "top-left");
|
map.addControl(new HomeButton({ bounds: SLOVAKIA_BOUNDS }), "top-left");
|
||||||
// showCompass: false — this map never rotates, so a bearing-reset button
|
// showCompass: false — this map never rotates, so a bearing-reset button
|
||||||
// (NavigationControl's default third button) would just be dead weight.
|
// (NavigationControl's default third button) would just be dead weight.
|
||||||
map.addControl(new NavigationControl({ showCompass: false }), "top-left");
|
map.addControl(new NavigationControl({ showCompass: false }), "top-left");
|
||||||
map.addControl(new ZoomLevelControl(), "top-left");
|
map.addControl(new ZoomLevelControl(), "top-left");
|
||||||
|
const pitchToggle = new PitchToggleControl();
|
||||||
|
map.addControl(pitchToggle, "top-left");
|
||||||
map.addControl(new MeasureControl(), "top-left");
|
map.addControl(new MeasureControl(), "top-left");
|
||||||
map.addControl(
|
// Writing a note needs a login (POST /api/gpx/notes) — no point adding a
|
||||||
new GeolocateControl({
|
// button that can only ever end in a 401 for a logged-out viewer, same
|
||||||
positionOptions: { enableHighAccuracy: true },
|
// "hide the write-only affordance" story as GpxPanelControl's own
|
||||||
trackUserLocation: true,
|
// upload/new-folder buttons.
|
||||||
showAccuracyCircle: true,
|
if (isLoggedIn()) map.addControl(new NotesControl(), "top-left");
|
||||||
}),
|
// Constructed here (rather than down where it's mounted, alongside the
|
||||||
"top-left",
|
// other sidebar controls) so LocateControl can hold the same instance —
|
||||||
);
|
// its constructor only touches `map`, mount() (which needs the sidebar
|
||||||
|
// element) can still happen later.
|
||||||
|
const gnssControl = new GnssControl(map);
|
||||||
|
// Not MapLibre's own GeolocateControl (browser Geolocation API only) —
|
||||||
|
// this app has its own GNSS receiver (gpsd, possibly a simulated one, see
|
||||||
|
// bin/gnss_simulator.rb), and "find my location" should prefer that over a
|
||||||
|
// device's separate, often less accurate location service, but only once
|
||||||
|
// the "GNSS" sidebar icon actually turns it on — not just whenever a
|
||||||
|
// receiver happens to have a fix. See locate.js.
|
||||||
|
map.addControl(new LocateControl(gnssControl), "top-left");
|
||||||
|
map.addControl(new ScaleControl({ unit: "metric" }), "bottom-left");
|
||||||
|
|
||||||
// A small inset map showing where the current view sits in context — NPS's
|
// A small inset map showing where the current view sits in context — NPS's
|
||||||
// own overview plugin (docs/nps-resources.adoc's "overview" entry, still on
|
// own overview plugin (docs/nps-resources.adoc's "overview" entry, still on
|
||||||
@@ -384,25 +487,88 @@ map.once("style.load", () => {
|
|||||||
map.addControl(new OverviewMapControl({}), "top-right");
|
map.addControl(new OverviewMapControl({}), "top-right");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const coordsLabel = document.querySelector(".topbar__coords");
|
||||||
|
const updateCoords = () => {
|
||||||
|
const { lng, lat } = map.getCenter();
|
||||||
|
coordsLabel.innerHTML = `<span class="topbar__icon">${WAYPOINT_ICON}</span><span>${formatCoords(lat, lng)}</span>`;
|
||||||
|
};
|
||||||
|
updateCoords();
|
||||||
|
map.on("move", updateCoords);
|
||||||
|
|
||||||
|
// Plain decimal degrees, not the DMS text actually shown — that's what
|
||||||
|
// pastes cleanly into another map/GPS app, which is the whole point of
|
||||||
|
// copying a coordinate in the first place.
|
||||||
|
coordsLabel.title = "Click to copy coordinates";
|
||||||
|
coordsLabel.addEventListener("click", async () => {
|
||||||
|
const { lng, lat } = map.getCenter();
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(`${lat.toFixed(6)}, ${lng.toFixed(6)}`);
|
||||||
|
} catch {
|
||||||
|
return; // clipboard API unavailable (e.g. an insecure context) — nothing to fall back to
|
||||||
|
}
|
||||||
|
coordsLabel.textContent = "Copied!";
|
||||||
|
setTimeout(updateCoords, 1000);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sunrise/sunset for the same center point the coords above already show —
|
||||||
|
// on moveend (not every "move" frame, unlike updateCoords: the sun position
|
||||||
|
// barely moves between two nearby spots, not worth recomputing constantly)
|
||||||
|
// plus a periodic tick so the "Xh Ym to sunset" countdown keeps counting
|
||||||
|
// down even while the map sits still.
|
||||||
|
const sunLabel = document.querySelector(".topbar__sun");
|
||||||
|
const updateSun = () => {
|
||||||
|
const { lng, lat } = map.getCenter();
|
||||||
|
const info = formatSunInfo(lat, lng);
|
||||||
|
sunLabel.hidden = info === null;
|
||||||
|
if (!info) return;
|
||||||
|
|
||||||
|
const countdownGroup = info.countdown
|
||||||
|
? `<span class="topbar__group"><span class="topbar__icon">${PENDING_ICON}</span><span>${info.countdown}</span></span>`
|
||||||
|
: "";
|
||||||
|
sunLabel.innerHTML =
|
||||||
|
`<span class="topbar__group"><span class="topbar__icon">${SUNRISE_ICON}</span><span>${info.sunrise}</span></span>` +
|
||||||
|
`<span class="topbar__group"><span class="topbar__icon">${SUNSET_ICON}</span><span>${info.sunset}</span></span>` +
|
||||||
|
countdownGroup;
|
||||||
|
};
|
||||||
|
updateSun();
|
||||||
|
map.on("moveend", updateSun);
|
||||||
|
setInterval(updateSun, 60000);
|
||||||
|
|
||||||
const sidebar = document.getElementById("sidebar");
|
const sidebar = document.getElementById("sidebar");
|
||||||
|
|
||||||
|
// Grouped into the TODO's planned 4-module menu (Explorer / Offline hub /
|
||||||
|
// Navigation / Camper & HW) ahead of that split actually happening —
|
||||||
|
// Camper & HW has no sidebar item yet (leveling/solar/telemetry are all
|
||||||
|
// still unbuilt), so it's left out rather than shown empty.
|
||||||
const layerToggle = new LayerToggleControl(applyStyle);
|
const layerToggle = new LayerToggleControl(applyStyle);
|
||||||
|
const profilePanel = new TrackProfilePanel(map);
|
||||||
|
profilePanel.mount();
|
||||||
|
|
||||||
|
sidebar.insertAdjacentHTML("beforeend", `<div class="sidebar-group-label" title="Explorer"></div>`);
|
||||||
|
new GpxPanelControl(map, profilePanel).mount(sidebar);
|
||||||
|
const overlaysControl = new OverlaysControl(map);
|
||||||
|
overlaysControl.mount(sidebar);
|
||||||
|
pitchToggle.setOverlaysControl(overlaysControl);
|
||||||
|
// Running a plugin needs a login (POST /api/plugins/:name/run) — same
|
||||||
|
// "hide the write-only affordance" story as NotesControl above.
|
||||||
|
if (isLoggedIn()) new PluginsControl(map).mount(sidebar);
|
||||||
|
|
||||||
|
sidebar.insertAdjacentHTML("beforeend", `<div class="sidebar-group-label" title="Offline hub"></div>`);
|
||||||
layerToggle.mount(sidebar);
|
layerToggle.mount(sidebar);
|
||||||
layerToggle.setActive("online");
|
layerToggle.setActive("online");
|
||||||
|
new OfflineDataControl().mount(sidebar);
|
||||||
|
|
||||||
|
sidebar.insertAdjacentHTML("beforeend", `<div class="sidebar-group-label" title="Navigation"></div>`);
|
||||||
|
gnssControl.mount(sidebar);
|
||||||
|
new LiveTrackPanelControl(map).mount(sidebar);
|
||||||
|
|
||||||
// The sidebar already has a "Base map" icon (opens the same panel) —
|
// The sidebar already has a "Base map" icon (opens the same panel) —
|
||||||
// this is a second entry point for it, grouped with Home/zoom on the map
|
// this is a second entry point for it, grouped with Home/zoom on the map
|
||||||
// itself rather than off in the page's own side rail, matching NPS's
|
// itself rather than off in the page's own side rail, matching NPS's
|
||||||
// layout. Added after mount() since it reuses that panel/toggle state.
|
// layout. layerToggle only needs to exist (not be mounted yet) for this.
|
||||||
const mapLayersButton = new MapLayersButton(layerToggle);
|
const mapLayersButton = new MapLayersButton(layerToggle);
|
||||||
map.addControl(mapLayersButton, "top-left");
|
map.addControl(mapLayersButton, "top-left");
|
||||||
|
|
||||||
const profilePanel = new TrackProfilePanel(map);
|
|
||||||
profilePanel.mount();
|
|
||||||
new GpxPanelControl(map, profilePanel).mount(sidebar);
|
|
||||||
new RadarControl(map).mount(sidebar);
|
|
||||||
new GnssControl(map).mount(sidebar);
|
|
||||||
new LiveTrackPanelControl(map).mount(sidebar);
|
|
||||||
|
|
||||||
function applyStyle(name) {
|
function applyStyle(name) {
|
||||||
layerToggle.setActive(name);
|
layerToggle.setActive(name);
|
||||||
map.setStyle(name === "offline" ? offlineStyle() : ONLINE_STYLE);
|
map.setStyle(name === "offline" ? offlineStyle() : ONLINE_STYLE);
|
||||||
@@ -413,6 +579,7 @@ function applyStyle(name) {
|
|||||||
// the Online/Offline switch itself doesn't make sense (there's nothing to
|
// the Online/Offline switch itself doesn't make sense (there's nothing to
|
||||||
// switch to), so it's removed entirely rather than left sitting there
|
// switch to), so it's removed entirely rather than left sitting there
|
||||||
// offering a choice that can't work.
|
// offering a choice that can't work.
|
||||||
|
const offlineStatus = document.querySelector(".topbar__offline-status");
|
||||||
fetch("/api/offline-tiles")
|
fetch("/api/offline-tiles")
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then(({ available }) => {
|
.then(({ available }) => {
|
||||||
@@ -422,6 +589,9 @@ fetch("/api/offline-tiles")
|
|||||||
layerToggle.remove();
|
layerToggle.remove();
|
||||||
map.removeControl(mapLayersButton);
|
map.removeControl(mapLayersButton);
|
||||||
}
|
}
|
||||||
|
offlineStatus.hidden = false;
|
||||||
|
offlineStatus.classList.toggle("topbar__offline-status--ready", available);
|
||||||
|
offlineStatus.title = available ? "Offline map data ready" : "No offline map data on this server";
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
// Backend check failed; keep the online style and leave the switch as
|
// Backend check failed; keep the online style and leave the switch as
|
||||||
@@ -429,6 +599,24 @@ fetch("/api/offline-tiles")
|
|||||||
// failing, not evidence offline tiles are actually unavailable.
|
// failing, not evidence offline tiles are actually unavailable.
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fetch("/api/terrain-tiles")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ available }) => {
|
||||||
|
overlaysControl.setHillshadeOfflineAvailable(available);
|
||||||
|
overlaysControl.setShadowOfflineAvailable(available);
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
fetch("/api/hiking-trails")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ countries }) => overlaysControl.setHikingTrailsOfflineCountries(countries))
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
fetch("/api/kiwix")
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then(({ available }) => overlaysControl.setWikiAvailable(available))
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
// Alt+click anywhere shows every rendered feature at that point — base map
|
// Alt+click anywhere shows every rendered feature at that point — base map
|
||||||
// (OSM tags via OpenFreeMap/protomaps), GPX/FIT layers, radar, all of it —
|
// (OSM tags via OpenFreeMap/protomaps), GPX/FIT layers, radar, all of it —
|
||||||
// with its raw properties, inspired by NPMap4's infoboxControl (a generic
|
// with its raw properties, inspired by NPMap4's infoboxControl (a generic
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { RULER_ICON } from "/js/icons.js";
|
import { RULER_ICON } from "/js/icons.js";
|
||||||
|
import { NoteSaveForm } from "/js/notes.js";
|
||||||
|
import { isLoggedIn } from "/js/auth.js";
|
||||||
|
|
||||||
const SOURCE_ID = "measure";
|
const SOURCE_ID = "measure";
|
||||||
const LINE_LAYER_ID = "measure-line";
|
const LINE_LAYER_ID = "measure-line";
|
||||||
@@ -19,6 +21,9 @@ export class MeasureControl {
|
|||||||
this._mode = "distance";
|
this._mode = "distance";
|
||||||
this._active = false;
|
this._active = false;
|
||||||
this._points = [];
|
this._points = [];
|
||||||
|
this._selectedIndex = null; // which vertex Delete/Backspace acts on
|
||||||
|
this._draggingIndex = null;
|
||||||
|
this._suppressNextClick = false; // a mousedown on a vertex already handled this click; don't also add a new point
|
||||||
}
|
}
|
||||||
|
|
||||||
onAdd(map) {
|
onAdd(map) {
|
||||||
@@ -29,29 +34,108 @@ export class MeasureControl {
|
|||||||
this._container.innerHTML = `
|
this._container.innerHTML = `
|
||||||
<button type="button" class="measure-ctrl__button" title="Measure" aria-label="Measure">${RULER_ICON}</button>
|
<button type="button" class="measure-ctrl__button" title="Measure" aria-label="Measure">${RULER_ICON}</button>
|
||||||
<div class="measure-panel" hidden>
|
<div class="measure-panel" hidden>
|
||||||
|
<div class="measure-panel__controls">
|
||||||
<button type="button" class="measure-panel__mode active" data-mode="distance">Distance</button>
|
<button type="button" class="measure-panel__mode active" data-mode="distance">Distance</button>
|
||||||
<button type="button" class="measure-panel__mode" data-mode="area">Area</button>
|
<button type="button" class="measure-panel__mode" data-mode="area">Area</button>
|
||||||
<span class="measure-panel__result"></span>
|
<span class="measure-panel__result"></span>
|
||||||
|
<button type="button" class="measure-panel__save" title="Save as a note" aria-label="Save as a note" disabled>📍</button>
|
||||||
<button type="button" class="measure-panel__clear" title="Clear" aria-label="Clear">×</button>
|
<button type="button" class="measure-panel__clear" title="Clear" aria-label="Clear">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
this._button = this._container.querySelector(".measure-ctrl__button");
|
this._button = this._container.querySelector(".measure-ctrl__button");
|
||||||
this._panel = this._container.querySelector(".measure-panel");
|
this._panel = this._container.querySelector(".measure-panel");
|
||||||
|
this._controls = this._container.querySelector(".measure-panel__controls");
|
||||||
this._result = this._container.querySelector(".measure-panel__result");
|
this._result = this._container.querySelector(".measure-panel__result");
|
||||||
|
this._saveButton = this._container.querySelector(".measure-panel__save");
|
||||||
|
// Same "hide the write-only affordance for a logged-out viewer" story
|
||||||
|
// as notes.js's own button not being added at all — Measure itself
|
||||||
|
// stays available either way, only saving needs a login.
|
||||||
|
if (!isLoggedIn()) this._saveButton.hidden = true;
|
||||||
|
|
||||||
|
this._noteForm = new NoteSaveForm(this._panel, {
|
||||||
|
onSaved: () => {
|
||||||
|
this._controls.hidden = false;
|
||||||
|
this._clear();
|
||||||
|
},
|
||||||
|
onCancel: () => (this._controls.hidden = false),
|
||||||
|
});
|
||||||
|
|
||||||
this._button.addEventListener("click", () => this._setActive(!this._active));
|
this._button.addEventListener("click", () => this._setActive(!this._active));
|
||||||
this._panel.querySelectorAll(".measure-panel__mode").forEach((button) => {
|
this._panel.querySelectorAll(".measure-panel__mode").forEach((button) => {
|
||||||
button.addEventListener("click", () => this._setMode(button.dataset.mode));
|
button.addEventListener("click", () => this._setMode(button.dataset.mode));
|
||||||
});
|
});
|
||||||
|
this._saveButton.addEventListener("click", () => this._saveAsNote());
|
||||||
this._panel.querySelector(".measure-panel__clear").addEventListener("click", () => this._clear());
|
this._panel.querySelector(".measure-panel__clear").addEventListener("click", () => this._clear());
|
||||||
|
|
||||||
this._onClick = (event) => {
|
this._onClick = (event) => {
|
||||||
if (!this._active) return;
|
if (!this._active || this._noteForm.isOpen) return;
|
||||||
|
// A mousedown on an existing vertex (below) already selected/started
|
||||||
|
// dragging it — without this, that same click would also land here
|
||||||
|
// and add a brand new point right on top of it.
|
||||||
|
if (this._suppressNextClick) {
|
||||||
|
this._suppressNextClick = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._selectedIndex = null;
|
||||||
this._points.push([event.lngLat.lng, event.lngLat.lat]);
|
this._points.push([event.lngLat.lng, event.lngLat.lat]);
|
||||||
this._render();
|
this._render();
|
||||||
};
|
};
|
||||||
map.on("click", this._onClick);
|
map.on("click", this._onClick);
|
||||||
|
|
||||||
|
// Vertex drag: mousedown on a point starts it, a map-wide mousemove
|
||||||
|
// follows the cursor while dragging, mouseup anywhere ends it — not
|
||||||
|
// scoped to the layer since the cursor is very likely to leave the
|
||||||
|
// (few-pixel) vertex circle mid-drag. preventDefault on the mousedown
|
||||||
|
// is what stops MapLibre's own drag-to-pan from also engaging for the
|
||||||
|
// same gesture (its documented way to opt out per-interaction).
|
||||||
|
this._onVertexMouseDown = (event) => {
|
||||||
|
if (!this._active || this._noteForm.isOpen) return;
|
||||||
|
const index = event.features[0]?.properties?.index;
|
||||||
|
if (index === undefined) return;
|
||||||
|
event.preventDefault();
|
||||||
|
this._draggingIndex = index;
|
||||||
|
this._selectedIndex = index;
|
||||||
|
this._suppressNextClick = true;
|
||||||
|
this._map.getCanvas().style.cursor = "grabbing";
|
||||||
|
this._render();
|
||||||
|
};
|
||||||
|
map.on("mousedown", POINT_LAYER_ID, this._onVertexMouseDown);
|
||||||
|
|
||||||
|
this._onMouseMove = (event) => {
|
||||||
|
if (this._draggingIndex === null) return;
|
||||||
|
this._points[this._draggingIndex] = [event.lngLat.lng, event.lngLat.lat];
|
||||||
|
this._render();
|
||||||
|
};
|
||||||
|
map.on("mousemove", this._onMouseMove);
|
||||||
|
|
||||||
|
this._onMouseUp = () => {
|
||||||
|
if (this._draggingIndex === null) return;
|
||||||
|
this._draggingIndex = null;
|
||||||
|
this._map.getCanvas().style.cursor = this._active ? "crosshair" : "";
|
||||||
|
};
|
||||||
|
map.on("mouseup", this._onMouseUp);
|
||||||
|
|
||||||
|
map.on("mouseenter", POINT_LAYER_ID, () => {
|
||||||
|
if (this._draggingIndex === null) this._map.getCanvas().style.cursor = "grab";
|
||||||
|
});
|
||||||
|
map.on("mouseleave", POINT_LAYER_ID, () => {
|
||||||
|
if (this._draggingIndex === null) this._map.getCanvas().style.cursor = this._active ? "crosshair" : "";
|
||||||
|
});
|
||||||
|
|
||||||
|
this._onKeyDown = (event) => {
|
||||||
|
if (!this._active || this._selectedIndex === null) return;
|
||||||
|
if (event.key !== "Delete" && event.key !== "Backspace") return;
|
||||||
|
// Don't eat Backspace while the note-text form (or anything else on
|
||||||
|
// the page) actually has focus for typing.
|
||||||
|
if (["INPUT", "TEXTAREA"].includes(document.activeElement?.tagName)) return;
|
||||||
|
event.preventDefault();
|
||||||
|
this._points.splice(this._selectedIndex, 1);
|
||||||
|
this._selectedIndex = null;
|
||||||
|
this._render();
|
||||||
|
};
|
||||||
|
document.addEventListener("keydown", this._onKeyDown);
|
||||||
|
|
||||||
// setStyle() (switching Online/Offline) tears down all sources/layers —
|
// setStyle() (switching Online/Offline) tears down all sources/layers —
|
||||||
// same re-add-on-reload pattern as radar.js/gpx.js.
|
// same re-add-on-reload pattern as radar.js/gpx.js.
|
||||||
map.on("style.load", () => {
|
map.on("style.load", () => {
|
||||||
@@ -63,6 +147,10 @@ export class MeasureControl {
|
|||||||
|
|
||||||
onRemove() {
|
onRemove() {
|
||||||
this._map.off("click", this._onClick);
|
this._map.off("click", this._onClick);
|
||||||
|
this._map.off("mousedown", POINT_LAYER_ID, this._onVertexMouseDown);
|
||||||
|
this._map.off("mousemove", this._onMouseMove);
|
||||||
|
this._map.off("mouseup", this._onMouseUp);
|
||||||
|
document.removeEventListener("keydown", this._onKeyDown);
|
||||||
this._removeLayers();
|
this._removeLayers();
|
||||||
this._container.remove();
|
this._container.remove();
|
||||||
}
|
}
|
||||||
@@ -79,21 +167,44 @@ export class MeasureControl {
|
|||||||
if (active) {
|
if (active) {
|
||||||
this._addLayers();
|
this._addLayers();
|
||||||
} else {
|
} else {
|
||||||
|
this._noteForm.close();
|
||||||
|
this._controls.hidden = false;
|
||||||
this._clear();
|
this._clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The same shape _render() draws — a distance line (2+ points) or a
|
||||||
|
// closed area polygon (3+ points), or null while there isn't yet enough
|
||||||
|
// to measure — extracted so "Save as note" saves exactly what's on
|
||||||
|
// screen rather than recomputing it slightly differently.
|
||||||
|
_currentShapeGeometry() {
|
||||||
|
if (this._mode === "area") {
|
||||||
|
return this._points.length >= 3 ? { type: "Polygon", coordinates: [[...this._points, this._points[0]]] } : null;
|
||||||
|
}
|
||||||
|
return this._points.length >= 2 ? { type: "LineString", coordinates: this._points } : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveAsNote() {
|
||||||
|
const geometry = this._currentShapeGeometry();
|
||||||
|
if (!geometry) return;
|
||||||
|
this._controls.hidden = true;
|
||||||
|
this._noteForm.open(geometry);
|
||||||
|
}
|
||||||
|
|
||||||
_setMode(mode) {
|
_setMode(mode) {
|
||||||
this._mode = mode;
|
this._mode = mode;
|
||||||
this._panel.querySelectorAll(".measure-panel__mode").forEach((button) => {
|
this._panel.querySelectorAll(".measure-panel__mode").forEach((button) => {
|
||||||
button.classList.toggle("active", button.dataset.mode === mode);
|
button.classList.toggle("active", button.dataset.mode === mode);
|
||||||
});
|
});
|
||||||
this._points = [];
|
this._points = [];
|
||||||
|
this._selectedIndex = null;
|
||||||
this._render();
|
this._render();
|
||||||
}
|
}
|
||||||
|
|
||||||
_clear() {
|
_clear() {
|
||||||
this._points = [];
|
this._points = [];
|
||||||
|
this._selectedIndex = null;
|
||||||
|
this._draggingIndex = null;
|
||||||
this._render();
|
this._render();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,22 +216,34 @@ export class MeasureControl {
|
|||||||
id: FILL_LAYER_ID,
|
id: FILL_LAYER_ID,
|
||||||
type: "fill",
|
type: "fill",
|
||||||
source: SOURCE_ID,
|
source: SOURCE_ID,
|
||||||
filter: ["==", "$type", "Polygon"],
|
filter: ["==", ["geometry-type"], "Polygon"],
|
||||||
paint: { "fill-color": "#1e5fbf", "fill-opacity": 0.15 },
|
paint: { "fill-color": "#1e5fbf", "fill-opacity": 0.15 },
|
||||||
});
|
});
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: LINE_LAYER_ID,
|
id: LINE_LAYER_ID,
|
||||||
type: "line",
|
type: "line",
|
||||||
source: SOURCE_ID,
|
source: SOURCE_ID,
|
||||||
filter: ["!=", "$type", "Point"],
|
filter: ["!=", ["geometry-type"], "Point"],
|
||||||
paint: { "line-color": "#1e5fbf", "line-width": 2 },
|
paint: { "line-color": "#1e5fbf", "line-width": 2 },
|
||||||
});
|
});
|
||||||
|
// "role" alone (not also a geometry-type check) — it's only ever set on
|
||||||
|
// Point features to begin with (the line/polygon shape feature has no
|
||||||
|
// "role" property at all), so it already implies the geometry type.
|
||||||
|
// Mixing the legacy "$type" filter syntax with an expression like
|
||||||
|
// ["get", "role"] in the same filter logs as unsupported in newer
|
||||||
|
// MapLibre — this sidesteps that rather than fixing it by switching
|
||||||
|
// "$type" to ["geometry-type"] and keeping both checks.
|
||||||
this._map.addLayer({
|
this._map.addLayer({
|
||||||
id: POINT_LAYER_ID,
|
id: POINT_LAYER_ID,
|
||||||
type: "circle",
|
type: "circle",
|
||||||
source: SOURCE_ID,
|
source: SOURCE_ID,
|
||||||
filter: ["all", ["==", "$type", "Point"], ["==", ["get", "role"], "vertex"]],
|
filter: ["==", ["get", "role"], "vertex"],
|
||||||
paint: { "circle-radius": 4, "circle-color": "#fff", "circle-stroke-color": "#1e5fbf", "circle-stroke-width": 2 },
|
paint: {
|
||||||
|
"circle-radius": ["case", ["get", "selected"], 6, 4],
|
||||||
|
"circle-color": ["case", ["get", "selected"], "#ffd43b", "#fff"],
|
||||||
|
"circle-stroke-color": "#1e5fbf",
|
||||||
|
"circle-stroke-width": 2,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
// Distance mode only (see _render) — each segment's own length, not
|
// Distance mode only (see _render) — each segment's own length, not
|
||||||
// just the running total, matching how a typical measure tool
|
// just the running total, matching how a typical measure tool
|
||||||
@@ -153,15 +276,14 @@ export class MeasureControl {
|
|||||||
const source = this._map.getSource(SOURCE_ID);
|
const source = this._map.getSource(SOURCE_ID);
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
|
|
||||||
const pointFeatures = this._points.map((coord) => ({
|
const pointFeatures = this._points.map((coord, index) => ({
|
||||||
type: "Feature",
|
type: "Feature",
|
||||||
properties: { role: "vertex" },
|
properties: { role: "vertex", index, selected: index === this._selectedIndex },
|
||||||
geometry: { type: "Point", coordinates: coord },
|
geometry: { type: "Point", coordinates: coord },
|
||||||
}));
|
}));
|
||||||
const shapeFeature =
|
const geometry = this._currentShapeGeometry();
|
||||||
this._mode === "area"
|
const shapeFeature = geometry && { type: "Feature", geometry };
|
||||||
? this._points.length >= 3 && { type: "Feature", geometry: { type: "Polygon", coordinates: [[...this._points, this._points[0]]] } }
|
this._saveButton.disabled = !geometry;
|
||||||
: this._points.length >= 2 && { type: "Feature", geometry: { type: "LineString", coordinates: this._points } };
|
|
||||||
// Area mode has no equivalent yet — only asked for distance's own
|
// Area mode has no equivalent yet — only asked for distance's own
|
||||||
// per-leg labels, and a polygon's edges read differently (perimeter
|
// per-leg labels, and a polygon's edges read differently (perimeter
|
||||||
// segments, not a path) if this ever extends there.
|
// segments, not a path) if this ever extends there.
|
||||||
@@ -195,7 +317,9 @@ function haversine([lng1, lat1], [lng2, lat2]) {
|
|||||||
return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(a));
|
return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
function lineLength(points) {
|
// Exported — also used by hiking_trails.js to total up an OSM route
|
||||||
|
// relation's member ways into one distance, same haversine-sum approach.
|
||||||
|
export function lineLength(points) {
|
||||||
let total = 0;
|
let total = 0;
|
||||||
for (let i = 1; i < points.length; i++) total += haversine(points[i - 1], points[i]);
|
for (let i = 1; i < points.length; i++) total += haversine(points[i - 1], points[i]);
|
||||||
return total;
|
return total;
|
||||||
@@ -225,7 +349,8 @@ function polygonArea(points) {
|
|||||||
return Math.abs((area * EARTH_RADIUS_M * EARTH_RADIUS_M) / 2);
|
return Math.abs((area * EARTH_RADIUS_M * EARTH_RADIUS_M) / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatDistance(meters) {
|
// Exported — also used by hiking_trails.js's route-details popup.
|
||||||
|
export function formatDistance(meters) {
|
||||||
if (meters === 0) return "";
|
if (meters === 0) return "";
|
||||||
return meters >= 1000 ? `${(meters / 1000).toFixed(2)} km` : `${Math.round(meters)} m`;
|
return meters >= 1000 ? `${(meters / 1000).toFixed(2)} km` : `${Math.round(meters)} m`;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,291 @@
|
|||||||
|
import { Marker } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
import { NOTE_PIN_ICON, POI_ICONS } from "/js/icons.js";
|
||||||
|
import { notifyGpxStoreChanged } from "/js/panels.js";
|
||||||
|
|
||||||
|
// Human labels for the icon picker's title attribute — POI_ICONS' own keys
|
||||||
|
// are internal (OSM-tag-shaped) ids, not display text.
|
||||||
|
const ICON_LABELS = {
|
||||||
|
amenity_parking: "Parking",
|
||||||
|
tourism_camp_site: "Camp site",
|
||||||
|
leisure_picnic_table: "Picnic table",
|
||||||
|
amenity_fuel: "Fuel",
|
||||||
|
tourism_hotel: "Lodging",
|
||||||
|
forest: "Forest",
|
||||||
|
sport_cycling: "Cycling",
|
||||||
|
amenity_drinking_water: "Drinking water",
|
||||||
|
amenity_sanitary_dump_station: "Dump station",
|
||||||
|
amenity_toilets: "Toilets",
|
||||||
|
shop_gas: "Gas/LPG refill",
|
||||||
|
amenity_electricity: "Electricity",
|
||||||
|
shop_laundry: "Laundry",
|
||||||
|
leisure_playground: "Playground",
|
||||||
|
tourism_viewpoint: "Viewpoint",
|
||||||
|
leisure_swimming: "Swimming",
|
||||||
|
leisure_fishing: "Fishing",
|
||||||
|
sport_climbing: "Climbing",
|
||||||
|
route_hiking: "Hiking",
|
||||||
|
shop_supermarket: "Supermarket",
|
||||||
|
shop_convenience: "Convenience store",
|
||||||
|
};
|
||||||
|
|
||||||
|
const NEW_LAYER_VALUE = "__new__";
|
||||||
|
|
||||||
|
// Every hand-drawn note (from here or measure.js's "Save as note") gets one
|
||||||
|
// of these — the only stable way to find a specific feature again for
|
||||||
|
// editing/deleting later (see GpxStore#update_feature/#delete_feature):
|
||||||
|
// array position shifts as other notes are added/removed, and the note's
|
||||||
|
// own text is exactly the thing an edit changes.
|
||||||
|
export function generateNoteId() {
|
||||||
|
return typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The "note text + which .geojson layer" form, shared by NotesControl (a
|
||||||
|
// single click) and measure.js's "Save as note" button (its current
|
||||||
|
// distance/area shape) — extracted here rather than duplicated in both,
|
||||||
|
// since the two differ only in *what* the note attaches a location to,
|
||||||
|
// not in how it's captured or where it's saved. Renders into whatever
|
||||||
|
// container it's given (each caller owns where that sits in its own panel);
|
||||||
|
// hidden until open() is called.
|
||||||
|
export class NoteSaveForm {
|
||||||
|
constructor(container, { onSaved, onCancel, onIconChange } = {}) {
|
||||||
|
this._onSaved = onSaved;
|
||||||
|
this._onCancel = onCancel;
|
||||||
|
this._onIconChange = onIconChange;
|
||||||
|
|
||||||
|
this._el = document.createElement("div");
|
||||||
|
this._el.className = "notes-panel__form";
|
||||||
|
this._el.hidden = true;
|
||||||
|
this._el.innerHTML = `
|
||||||
|
<input type="text" class="notes-panel__title" placeholder="Title">
|
||||||
|
<div class="notes-panel__icons">
|
||||||
|
<button type="button" class="notes-panel__icon-option active" data-icon="" title="No icon">${NOTE_PIN_ICON}</button>
|
||||||
|
${Object.keys(POI_ICONS)
|
||||||
|
.map(
|
||||||
|
(key) =>
|
||||||
|
`<button type="button" class="notes-panel__icon-option" data-icon="${key}" title="${escapeAttr(ICON_LABELS[key] || key)}">${POI_ICONS[key]}</button>`,
|
||||||
|
)
|
||||||
|
.join("")}
|
||||||
|
</div>
|
||||||
|
<textarea class="notes-panel__body" rows="3" placeholder="Details (optional) — **bold**, *italic*, links"></textarea>
|
||||||
|
<select class="notes-panel__target"></select>
|
||||||
|
<input type="text" class="notes-panel__new-name" placeholder="Layer name" hidden>
|
||||||
|
<div class="notes-panel__actions">
|
||||||
|
<button type="button" class="notes-panel__save">Save</button>
|
||||||
|
<button type="button" class="notes-panel__cancel">Cancel</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
container.appendChild(this._el);
|
||||||
|
|
||||||
|
this._title = this._el.querySelector(".notes-panel__title");
|
||||||
|
this._iconPicker = this._el.querySelector(".notes-panel__icons");
|
||||||
|
this._body = this._el.querySelector(".notes-panel__body");
|
||||||
|
this._target = this._el.querySelector(".notes-panel__target");
|
||||||
|
this._newName = this._el.querySelector(".notes-panel__new-name");
|
||||||
|
this._saveButton = this._el.querySelector(".notes-panel__save");
|
||||||
|
this._selectedIcon = "";
|
||||||
|
|
||||||
|
this._iconPicker.querySelectorAll(".notes-panel__icon-option").forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
this._selectedIcon = button.dataset.icon;
|
||||||
|
this._iconPicker.querySelectorAll(".notes-panel__icon-option").forEach((b) => b.classList.toggle("active", b === button));
|
||||||
|
this._onIconChange?.(this._selectedIcon);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._target.addEventListener("change", () => {
|
||||||
|
this._newName.hidden = this._target.value !== NEW_LAYER_VALUE;
|
||||||
|
if (!this._newName.hidden) this._newName.focus();
|
||||||
|
});
|
||||||
|
this._saveButton.addEventListener("click", () => this._save());
|
||||||
|
this._el.querySelector(".notes-panel__cancel").addEventListener("click", () => this.close(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOpen() {
|
||||||
|
return !this._el.hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
async open(geometry) {
|
||||||
|
this._geometry = geometry;
|
||||||
|
this._el.hidden = false;
|
||||||
|
this._title.value = "";
|
||||||
|
this._body.value = "";
|
||||||
|
this._selectedIcon = "";
|
||||||
|
this._iconPicker.querySelectorAll(".notes-panel__icon-option").forEach((b, i) => b.classList.toggle("active", i === 0));
|
||||||
|
this._onIconChange?.(this._selectedIcon);
|
||||||
|
// An icon only makes sense on a single point — Measure's "Save as
|
||||||
|
// note" can hand this a LineString/Polygon, which has no one spot for
|
||||||
|
// a badge glyph to sit at.
|
||||||
|
this._iconPicker.hidden = geometry.type !== "Point";
|
||||||
|
this._title.focus();
|
||||||
|
await this._populateTargets();
|
||||||
|
}
|
||||||
|
|
||||||
|
close(viaCancel = false) {
|
||||||
|
this._geometry = null;
|
||||||
|
this._el.hidden = true;
|
||||||
|
this._newName.hidden = true;
|
||||||
|
this._newName.value = "";
|
||||||
|
if (viaCancel) this._onCancel?.();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _populateTargets() {
|
||||||
|
const previousValue = this._target.value;
|
||||||
|
let files = [];
|
||||||
|
try {
|
||||||
|
const tree = await (await fetch("/api/gpx/tree")).json();
|
||||||
|
files = geojsonFiles(tree);
|
||||||
|
} catch (err) {
|
||||||
|
// Tree fetch failed — target select just falls back to "new layer"
|
||||||
|
// only, still usable.
|
||||||
|
}
|
||||||
|
|
||||||
|
this._target.innerHTML =
|
||||||
|
files.map((file) => `<option value="${escapeAttr(file.path)}">${escapeText(file.path)}</option>`).join("") +
|
||||||
|
`<option value="${NEW_LAYER_VALUE}">+ New layer…</option>`;
|
||||||
|
this._target.value = files.some((file) => file.path === previousValue) ? previousValue : NEW_LAYER_VALUE;
|
||||||
|
this._newName.hidden = this._target.value !== NEW_LAYER_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _save() {
|
||||||
|
const title = this._title.value.trim();
|
||||||
|
const details = this._body.value.trim();
|
||||||
|
if (!title || !this._geometry) return;
|
||||||
|
|
||||||
|
const isNewLayer = this._target.value === NEW_LAYER_VALUE;
|
||||||
|
const name = isNewLayer ? this._newName.value.trim() : "";
|
||||||
|
if (isNewLayer && !name) {
|
||||||
|
this._newName.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const feature = {
|
||||||
|
type: "Feature",
|
||||||
|
properties: {
|
||||||
|
id: generateNoteId(),
|
||||||
|
name: title,
|
||||||
|
category: "Note",
|
||||||
|
time: new Date().toISOString(),
|
||||||
|
...(details ? { description: details } : {}),
|
||||||
|
...(this._selectedIcon ? { icon: this._selectedIcon } : {}),
|
||||||
|
},
|
||||||
|
geometry: this._geometry,
|
||||||
|
};
|
||||||
|
|
||||||
|
const requestBody = new URLSearchParams({ feature: JSON.stringify(feature) });
|
||||||
|
if (isNewLayer) requestBody.set("name", name);
|
||||||
|
else requestBody.set("path", this._target.value);
|
||||||
|
|
||||||
|
this._saveButton.disabled = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/gpx/notes", { method: "POST", body: requestBody });
|
||||||
|
if (!response.ok) throw new Error(`save failed: ${response.status}`);
|
||||||
|
const { path } = await response.json();
|
||||||
|
notifyGpxStoreChanged(path);
|
||||||
|
this.close();
|
||||||
|
this._onSaved?.();
|
||||||
|
} catch (err) {
|
||||||
|
window.alert("Could not save the note — check your connection and try again.");
|
||||||
|
} finally {
|
||||||
|
this._saveButton.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A quick "pin it with a note" tool — click the map, write a short note,
|
||||||
|
// pick which .geojson "layer" file it joins (or start a new one). Point
|
||||||
|
// only: for a distance/area shape, draw it with the Measure tool and use
|
||||||
|
// its own "Save as note" button instead of duplicating that click-to-place
|
||||||
|
// UX here — see measure.js.
|
||||||
|
export class NotesControl {
|
||||||
|
constructor() {
|
||||||
|
this._active = false;
|
||||||
|
this._marker = null; // where the pending note will land — visible until saved/cancelled
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(map) {
|
||||||
|
this._map = map;
|
||||||
|
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "maplibregl-ctrl maplibregl-ctrl-group notes-ctrl";
|
||||||
|
this._container.innerHTML = `
|
||||||
|
<button type="button" class="notes-ctrl__button" title="Add note" aria-label="Add note">${NOTE_PIN_ICON}</button>
|
||||||
|
<div class="notes-panel" hidden>
|
||||||
|
<p class="notes-panel__hint">Click the map to place a note.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this._button = this._container.querySelector(".notes-ctrl__button");
|
||||||
|
this._panel = this._container.querySelector(".notes-panel");
|
||||||
|
this._hint = this._container.querySelector(".notes-panel__hint");
|
||||||
|
|
||||||
|
this._form = new NoteSaveForm(this._panel, {
|
||||||
|
onSaved: () => {
|
||||||
|
this._hint.hidden = false;
|
||||||
|
this._removeMarker();
|
||||||
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
this._hint.hidden = false;
|
||||||
|
this._removeMarker();
|
||||||
|
},
|
||||||
|
// Keeps the map preview in sync with the icon picker — it used to
|
||||||
|
// always show a plain red pin no matter which icon got picked.
|
||||||
|
onIconChange: (icon) => {
|
||||||
|
if (this._marker) this._marker.getElement().innerHTML = icon && POI_ICONS[icon] ? POI_ICONS[icon] : NOTE_PIN_ICON;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this._button.addEventListener("click", () => this._setActive(!this._active));
|
||||||
|
|
||||||
|
this._onClick = (event) => {
|
||||||
|
if (!this._active || this._form.isOpen) return;
|
||||||
|
this._hint.hidden = true;
|
||||||
|
const el = document.createElement("div");
|
||||||
|
el.className = "notes-panel__preview-marker";
|
||||||
|
el.innerHTML = NOTE_PIN_ICON;
|
||||||
|
this._marker = new Marker({ element: el }).setLngLat(event.lngLat).addTo(this._map);
|
||||||
|
this._form.open({ type: "Point", coordinates: [event.lngLat.lng, event.lngLat.lat] });
|
||||||
|
};
|
||||||
|
map.on("click", this._onClick);
|
||||||
|
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeMarker() {
|
||||||
|
this._marker?.remove();
|
||||||
|
this._marker = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._map.off("click", this._onClick);
|
||||||
|
this._container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPosition() {
|
||||||
|
return "top-left";
|
||||||
|
}
|
||||||
|
|
||||||
|
_setActive(active) {
|
||||||
|
this._active = active;
|
||||||
|
this._button.classList.toggle("active", active);
|
||||||
|
this._panel.hidden = !active;
|
||||||
|
this._map.getCanvas().style.cursor = active ? "crosshair" : "";
|
||||||
|
if (!active) {
|
||||||
|
this._form.close();
|
||||||
|
this._removeMarker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function geojsonFiles(node, results = []) {
|
||||||
|
if (node.type === "file" && node.path.toLowerCase().endsWith(".geojson")) {
|
||||||
|
results.push({ path: node.path });
|
||||||
|
}
|
||||||
|
(node.children || []).forEach((child) => geojsonFiles(child, results));
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeAttr(value) {
|
||||||
|
return value.replace(/&/g, "&").replace(/"/g, """);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeText(value) {
|
||||||
|
return value.replace(/&/g, "&").replace(/</g, "<");
|
||||||
|
}
|
||||||
@@ -0,0 +1,400 @@
|
|||||||
|
import { OFFLINE_DATA_ICON } from "/js/icons.js";
|
||||||
|
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||||
|
import { isLoggedIn } from "/js/auth.js";
|
||||||
|
import { escapeHtml } from "/js/gpx.js";
|
||||||
|
|
||||||
|
const POLL_INTERVAL_MS = 3000;
|
||||||
|
|
||||||
|
function formatBytes(bytes) {
|
||||||
|
const mb = bytes / (1024 * 1024);
|
||||||
|
return mb >= 1024 ? `${(mb / 1024).toFixed(1)} GB` : `${mb.toFixed(1)} MB`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatElapsed(ms) {
|
||||||
|
const totalSeconds = Math.max(0, Math.floor(ms / 1000));
|
||||||
|
const minutes = Math.floor(totalSeconds / 60);
|
||||||
|
const seconds = totalSeconds % 60;
|
||||||
|
return minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Building…" alone gave no way to tell a slow build from a stuck one, or
|
||||||
|
// which of the current selection's sources it's actually working on right
|
||||||
|
// now — elapsed time answers "is it alive", status.step (see
|
||||||
|
// PmtilesBuilder.run_job) answers "doing what, and what's left".
|
||||||
|
function formatRunning(status) {
|
||||||
|
const elapsed = status.started_at ? ` (${formatElapsed(Date.now() - new Date(status.started_at).getTime())})` : "";
|
||||||
|
const step = status.step ? `: ${status.step}` : "";
|
||||||
|
return `Building…${elapsed}${step}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vendored square-cropped SVGs (public/vendor/flag-icons/, MIT, see
|
||||||
|
// docs/third-party-notices.adoc) rather than Unicode flag emoji — emoji
|
||||||
|
// are font glyphs, not shapes, so they can't be resized/bordered/muted to
|
||||||
|
// fit this app's own controlled, hand-drawn-icon look the way a real SVG
|
||||||
|
// can (same reasoning the topbar readout's icons already settled on
|
||||||
|
// earlier — see icons.js). Framed in a small rounded-square badge in the
|
||||||
|
// markup below, the same convention icons.js's MAP_MARKER_ICON already
|
||||||
|
// uses for framing a category glyph, rather than a new visual language.
|
||||||
|
function flagIconUrl(code) {
|
||||||
|
return `/vendor/flag-icons/1x1/${code.toLowerCase()}.svg`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// meta.sources is one entry per country the current build covers (see
|
||||||
|
// lib/maptool/pmtiles_builder.rb) — listing the codes is more useful here
|
||||||
|
// than a count, since they're exactly what RegionPicker's checklist above
|
||||||
|
// already shows.
|
||||||
|
function formatOfflineMeta(meta) {
|
||||||
|
if (!meta) return "";
|
||||||
|
return ` · ${meta.sources.map((s) => s.code).join(", ")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTerrainMeta(meta) {
|
||||||
|
if (!meta) return "";
|
||||||
|
const count = meta.sources.length;
|
||||||
|
return ` · ${count} source${count === 1 ? "" : "s"}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The checklist of selectable countries (GET /api/offline-regions) plus
|
||||||
|
// the "Apply changes" button that saves the ticked set (POST, same URL)
|
||||||
|
// and hands it off — doesn't trigger a build itself, OfflineDataControl
|
||||||
|
// does that across both files once the selection is saved.
|
||||||
|
class RegionPicker {
|
||||||
|
constructor(onApply) {
|
||||||
|
this._onApply = onApply;
|
||||||
|
}
|
||||||
|
|
||||||
|
async mount(container) {
|
||||||
|
const section = document.createElement("div");
|
||||||
|
section.className = "offline-data-panel__regions";
|
||||||
|
section.innerHTML = `
|
||||||
|
<p class="offline-data-panel__regions-label">Regions</p>
|
||||||
|
<ul class="offline-data-panel__region-list"><li class="offline-data-panel__region-status">Loading…</li></ul>
|
||||||
|
<button type="button" class="offline-data-panel__apply" hidden>Apply changes</button>
|
||||||
|
`;
|
||||||
|
this._list = section.querySelector(".offline-data-panel__region-list");
|
||||||
|
this._applyButton = section.querySelector(".offline-data-panel__apply");
|
||||||
|
// Viewing/ticking works for anyone; only saving needs a login — same
|
||||||
|
// finer-grained gating measure.js/gpx.js use for their own
|
||||||
|
// write-only affordances, rather than hiding this whole control.
|
||||||
|
this._applyButton.hidden = !isLoggedIn();
|
||||||
|
this._applyButton.addEventListener("click", () => this._apply());
|
||||||
|
container.appendChild(section);
|
||||||
|
|
||||||
|
let data;
|
||||||
|
let cache;
|
||||||
|
try {
|
||||||
|
[data, cache] = await Promise.all([
|
||||||
|
fetch("/api/offline-regions").then((r) => r.json()),
|
||||||
|
fetch("/api/offline-tiles/cache").then((r) => r.json()),
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
this._list.innerHTML = `<li class="offline-data-panel__region-status">Could not load the region list.</li>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selected = new Set(data.selected);
|
||||||
|
const cached = new Map(cache.extracts.map((e) => [e.code, e]));
|
||||||
|
this._list.innerHTML = data.available
|
||||||
|
.map((region) => {
|
||||||
|
const extract = cached.get(region.code);
|
||||||
|
// Downloaded-but-not-(yet)-built is a real state (ticked, not
|
||||||
|
// Applied yet) as is built-but-no-longer-selected (unticked since)
|
||||||
|
// — this badge reflects the cache on disk, independent of either.
|
||||||
|
const cacheInfo = extract
|
||||||
|
? `
|
||||||
|
<span class="offline-data-panel__cache-info">
|
||||||
|
${formatBytes(extract.size)}
|
||||||
|
${
|
||||||
|
isLoggedIn()
|
||||||
|
? `<button type="button" class="offline-data-panel__cache-delete" data-code="${escapeHtml(region.code)}" data-name="${escapeHtml(region.name)}" title="Delete downloaded data for ${escapeHtml(region.name)}">✕</button>`
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
</span>`
|
||||||
|
: "";
|
||||||
|
return `
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" value="${escapeHtml(region.code)}" ${selected.has(region.code) ? "checked" : ""}>
|
||||||
|
<img class="offline-data-panel__flag" src="${flagIconUrl(region.code)}" alt="" width="18" height="18">
|
||||||
|
${escapeHtml(region.name)}
|
||||||
|
</label>
|
||||||
|
${cacheInfo}
|
||||||
|
</li>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
this._list.querySelectorAll(".offline-data-panel__cache-delete").forEach((button) => {
|
||||||
|
button.addEventListener("click", () => this._deleteCache(button));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes just this row's cache badge on success, rather than re-fetching
|
||||||
|
// and re-rendering the whole list — that would also overwrite whatever
|
||||||
|
// unticked/ticked-but-not-yet-Applied state the user has pending.
|
||||||
|
async _deleteCache(button) {
|
||||||
|
const code = button.dataset.code;
|
||||||
|
if (!window.confirm(`Delete downloaded data for ${button.dataset.name}? This can't be undone.`)) return;
|
||||||
|
|
||||||
|
button.disabled = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/offline-tiles/cache/${code}`, { method: "DELETE" });
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) throw new Error(data.error || `could not delete: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err.message);
|
||||||
|
button.disabled = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.closest(".offline-data-panel__cache-info").remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _apply() {
|
||||||
|
const codes = [...this._list.querySelectorAll("input:checked")].map((el) => el.value);
|
||||||
|
const body = new URLSearchParams();
|
||||||
|
codes.forEach((code) => body.append("codes[]", code));
|
||||||
|
|
||||||
|
this._applyButton.disabled = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/offline-regions", { method: "POST", body });
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) throw new Error(data.error || `could not save the selection: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err.message);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
this._applyButton.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._onApply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// One row of the Offline data panel — just a status readout for either
|
||||||
|
// offline.pmtiles or terrain.pmtiles, both driven by the same selection
|
||||||
|
// (RegionPicker above). Both files behave identically from here (same
|
||||||
|
// status/build/build-status/check-update endpoint shape, see
|
||||||
|
// lib/maptool/pmtiles_builder.rb), just against different URLs/labels and
|
||||||
|
// a different way of describing what was actually built (formatMeta), so
|
||||||
|
// one class covers both rather than writing the same fetch/poll logic
|
||||||
|
// twice.
|
||||||
|
class DataFileRow {
|
||||||
|
constructor({ label, statusUrl, deleteUrl, buildUrl, buildStatusUrl, checkUpdateUrl, formatMeta }) {
|
||||||
|
this._label = label;
|
||||||
|
this._statusUrl = statusUrl;
|
||||||
|
this._deleteUrl = deleteUrl;
|
||||||
|
this._buildUrl = buildUrl;
|
||||||
|
this._buildStatusUrl = buildStatusUrl;
|
||||||
|
this._checkUpdateUrl = checkUpdateUrl;
|
||||||
|
this._formatMeta = formatMeta;
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(container) {
|
||||||
|
const row = document.createElement("div");
|
||||||
|
row.className = "offline-data-panel__row";
|
||||||
|
row.innerHTML = `
|
||||||
|
<div class="offline-data-panel__row-head">
|
||||||
|
<strong>${this._label}</strong>
|
||||||
|
<button type="button" class="offline-data-panel__delete" hidden>Delete</button>
|
||||||
|
</div>
|
||||||
|
<p class="offline-data-panel__status">Checking…</p>
|
||||||
|
`;
|
||||||
|
this._statusEl = row.querySelector(".offline-data-panel__status");
|
||||||
|
this._deleteButton = row.querySelector(".offline-data-panel__delete");
|
||||||
|
this._deleteButton.addEventListener("click", () => this._delete());
|
||||||
|
container.appendChild(row);
|
||||||
|
this._refreshStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Single place that writes to _statusEl, so the error styling (pre-wrap,
|
||||||
|
// scrollable, red — see style.css) never stays stuck on once a build's
|
||||||
|
// failure message has scrolled past into a normal status line.
|
||||||
|
_setStatus(text, isError = false) {
|
||||||
|
this._statusEl.textContent = text;
|
||||||
|
this._statusEl.classList.toggle("offline-data-panel__status--error", isError);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _refreshStatus() {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await (await fetch(this._statusUrl)).json();
|
||||||
|
} catch {
|
||||||
|
this._setStatus("Could not check status.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data.available) {
|
||||||
|
this._setStatus("Not generated yet.");
|
||||||
|
this._deleteButton.hidden = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = new Date(data.mtime).toLocaleString([], { dateStyle: "short", timeStyle: "short" });
|
||||||
|
this._setStatus(`${formatBytes(data.size)} · updated ${updated}${this._formatMeta(data.meta)}`);
|
||||||
|
this._deleteButton.hidden = !isLoggedIn();
|
||||||
|
this._checkUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deletes the built file (and, for offline.pmtiles, its whole per-country
|
||||||
|
// download cache — see PmtilesBuilder.delete_offline_data) so the row
|
||||||
|
// goes back to "Not generated yet." — a manual way to reclaim space or
|
||||||
|
// force a completely fresh rebuild, since Apply alone never removes
|
||||||
|
// anything already on disk.
|
||||||
|
async _delete() {
|
||||||
|
if (!window.confirm(`Delete ${this._label.toLowerCase()}? This can't be undone.`)) return;
|
||||||
|
|
||||||
|
this._deleteButton.disabled = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(this._deleteUrl, { method: "DELETE" });
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) throw new Error(data.error || `could not delete: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
window.alert(err.message);
|
||||||
|
return;
|
||||||
|
} finally {
|
||||||
|
this._deleteButton.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._refreshStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Separate from _refreshStatus's own fetch (and only called once, right
|
||||||
|
// after — not polled): this one reaches an external service (Geofabrik
|
||||||
|
// or Mapterhorn's manifest, see lib/maptool/pmtiles_builder.rb), so it's
|
||||||
|
// kept off the hot status path
|
||||||
|
// and just silently skipped if it fails, same as everywhere else this
|
||||||
|
// app degrades gracefully when an optional network check doesn't answer.
|
||||||
|
async _checkUpdate() {
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = await (await fetch(this._checkUpdateUrl)).json();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!data.update_available) return;
|
||||||
|
|
||||||
|
const badge = document.createElement("span");
|
||||||
|
badge.className = "offline-data-panel__update-badge";
|
||||||
|
badge.textContent = " · a newer version is available";
|
||||||
|
this._statusEl.appendChild(badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by OfflineDataControl once RegionPicker's Apply has saved a
|
||||||
|
// new selection — not tied to a button of this row's own.
|
||||||
|
async triggerBuild() {
|
||||||
|
this._setStatus("Checking…");
|
||||||
|
try {
|
||||||
|
// false (not null/true — see offline_update_available? in
|
||||||
|
// pmtiles_builder.rb) means the just-saved selection matches what's
|
||||||
|
// already on disk byte-for-byte: same countries, nothing upstream
|
||||||
|
// changed. Rebuilding would just burn bandwidth/CPU to reproduce
|
||||||
|
// the same file, so skip it. null (never built, or can't tell)
|
||||||
|
// still falls through to building below.
|
||||||
|
const check = await (await fetch(this._checkUpdateUrl)).json();
|
||||||
|
if (check.update_available === false) {
|
||||||
|
this._refreshStatus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// couldn't tell — fall through and build rather than silently skip
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setStatus("Starting…");
|
||||||
|
try {
|
||||||
|
const response = await fetch(this._buildUrl, { method: "POST" });
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) throw new Error(data.error || `build failed: ${response.status}`);
|
||||||
|
} catch (err) {
|
||||||
|
this._setStatus(err.message, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._setStatus("Building…");
|
||||||
|
this._poll();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-rescheduling setTimeout loop, same shape as livetrack.js's
|
||||||
|
// _scheduleRefresh — check status, only reschedule while still running.
|
||||||
|
_poll() {
|
||||||
|
setTimeout(async () => {
|
||||||
|
let status;
|
||||||
|
try {
|
||||||
|
status = await (await fetch(this._buildStatusUrl)).json();
|
||||||
|
} catch {
|
||||||
|
this._poll(); // transient failure — try again next tick
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.state === "running") {
|
||||||
|
this._setStatus(formatRunning(status));
|
||||||
|
this._poll();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.state === "error") {
|
||||||
|
this._setStatus(`Build failed: ${status.message}`, true);
|
||||||
|
} else {
|
||||||
|
this._refreshStatus();
|
||||||
|
}
|
||||||
|
}, POLL_INTERVAL_MS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sidebar panel wrapping the manual go-pmtiles workflow
|
||||||
|
// docs/development.adoc documents — a region picker (which countries
|
||||||
|
// should be available offline) plus, on "Apply changes", a rebuild of
|
||||||
|
// offline.pmtiles/terrain.pmtiles covering the whole current selection
|
||||||
|
// (see lib/maptool/pmtiles_builder.rb — there's no standalone per-country
|
||||||
|
// file to individually delete; "remove a country" and "refresh a
|
||||||
|
// country" are both just "change the selection, then Apply").
|
||||||
|
export class OfflineDataControl {
|
||||||
|
constructor() {
|
||||||
|
this._rows = [
|
||||||
|
new DataFileRow({
|
||||||
|
label: "Offline map data",
|
||||||
|
statusUrl: "/api/offline-tiles",
|
||||||
|
deleteUrl: "/api/offline-tiles",
|
||||||
|
buildUrl: "/api/offline-tiles/build",
|
||||||
|
buildStatusUrl: "/api/offline-tiles/build-status",
|
||||||
|
checkUpdateUrl: "/api/offline-tiles/check-update",
|
||||||
|
formatMeta: formatOfflineMeta,
|
||||||
|
}),
|
||||||
|
new DataFileRow({
|
||||||
|
label: "Offline terrain data",
|
||||||
|
statusUrl: "/api/terrain-tiles",
|
||||||
|
deleteUrl: "/api/terrain-tiles",
|
||||||
|
buildUrl: "/api/terrain-tiles/build",
|
||||||
|
buildStatusUrl: "/api/terrain-tiles/build-status",
|
||||||
|
checkUpdateUrl: "/api/terrain-tiles/check-update",
|
||||||
|
formatMeta: formatTerrainMeta,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
this._regionPicker = new RegionPicker(() => this._rows.forEach((row) => row.triggerBuild()));
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(sidebar) {
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "sidebar-item";
|
||||||
|
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Offline data" aria-label="Offline data">${OFFLINE_DATA_ICON}</button>`;
|
||||||
|
this._icon = this._container.querySelector(".sidebar-icon");
|
||||||
|
this._icon.addEventListener("click", () => this._togglePanel());
|
||||||
|
|
||||||
|
this._panel = document.getElementById("offline-data-panel");
|
||||||
|
this._panel.querySelector(".basemap-panel__close").addEventListener("click", () => this._togglePanel(false));
|
||||||
|
|
||||||
|
const body = this._panel.querySelector(".offline-data-panel__body");
|
||||||
|
this._regionPicker.mount(body);
|
||||||
|
this._rows.forEach((row) => row.mount(body));
|
||||||
|
|
||||||
|
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
|
||||||
|
|
||||||
|
sidebar.appendChild(this._container);
|
||||||
|
}
|
||||||
|
|
||||||
|
_togglePanel(force) {
|
||||||
|
const open = force === undefined ? this._panel.hidden : force;
|
||||||
|
this._panel.hidden = !open;
|
||||||
|
this._icon.classList.toggle("active", open);
|
||||||
|
if (open) notifySidePanelOpen(this._panel);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,408 @@
|
|||||||
|
import { Popup } from "/vendor/maplibre-gl/maplibre-gl.mjs";
|
||||||
|
import { OVERLAYS_ICON, WIKI_ICON, WILDERNESS_ICON, MAP_MARKER_ICON } from "/js/icons.js";
|
||||||
|
import { notifySidePanelOpen, onlyOneSidePanelOpen } from "/js/panels.js";
|
||||||
|
import { svgToImageData, badgeImageId, escapeHtml } from "/js/gpx.js";
|
||||||
|
import { RadarControl } from "/js/radar.js";
|
||||||
|
import { ShadowControl } from "/js/shadow.js";
|
||||||
|
import { ContourControl } from "/js/contour.js";
|
||||||
|
import { HikingTrailsControl } from "/js/hiking_trails.js";
|
||||||
|
|
||||||
|
const ICON_IMAGE_SIZE = 48; // rasterized once at this size, rendered much smaller by icon-size below
|
||||||
|
const ICON_SIZE = 0.3; // 48 * 0.3 = ~14px, sized to sit inside the marker badge below
|
||||||
|
const MARKER_SIZE = 0.55;
|
||||||
|
|
||||||
|
const WIKI_SOURCE_ID = "wiki-points";
|
||||||
|
const WIKI_ICON_ID = "wiki-icon";
|
||||||
|
const WIKI_BADGE_COLOR = "#795548";
|
||||||
|
|
||||||
|
const HILLSHADE_ID = "hillshade-dem";
|
||||||
|
const COLOR_RELIEF_ID = "elevation-tint";
|
||||||
|
// MapLibre's own default light ({anchor:"viewport", intensity:0.5,
|
||||||
|
// position:[1.15,210,30]}) sits the sun almost overhead (polar 30°) — fine
|
||||||
|
// for flat 2D, but it barely rakes across 3D terrain at all, which is
|
||||||
|
// most of why real relief still looked flat/washed out even once the mesh
|
||||||
|
// itself was raised. Lower polar angle = lower sun = longer, more visible
|
||||||
|
// slope shadows; azimuth 315 is the classic NW hillshade convention.
|
||||||
|
// anchor:"map" (not "viewport") keeps the sun fixed to geography rather
|
||||||
|
// than spinning with the camera as you look around a tilted view.
|
||||||
|
const TERRAIN_LIGHT = { anchor: "map", color: "#fff", intensity: 0.6, position: [1.5, 315, 65] };
|
||||||
|
const DEFAULT_LIGHT = { anchor: "viewport", color: "#fff", intensity: 0.5, position: [1.15, 210, 30] };
|
||||||
|
// Hypsometric tint reusing this app's own palette (wood_a's green at
|
||||||
|
// valley floor, the road casing brown through the mid-slopes) rather than
|
||||||
|
// a generic rainbow ramp, so it reads as part of the same map rather than
|
||||||
|
// a bolted-on overlay. Stops picked for Central European terrain — the
|
||||||
|
// Tatras top out around 2600m — not a general-purpose world ramp.
|
||||||
|
const ELEVATION_COLOR_RAMP = [
|
||||||
|
"interpolate",
|
||||||
|
["linear"],
|
||||||
|
["raster-value"],
|
||||||
|
0, "#c3e8ab",
|
||||||
|
400, "#dcd08f",
|
||||||
|
900, "#c9a66b",
|
||||||
|
1600, "#a89a86",
|
||||||
|
2600, "#f9f6ef",
|
||||||
|
];
|
||||||
|
// Public, unauthenticated, no API key — the same open (SRTM/ArcticDEM-derived)
|
||||||
|
// elevation tiles OpenStreetMap's own hillshade layer and many other free
|
||||||
|
// tools use. Only reachable online; while the offline basemap is active this
|
||||||
|
// is skipped in favor of a local terrain.pmtiles if one's been generated
|
||||||
|
// (see docs/development.adoc), same optional-local-file story as
|
||||||
|
// offline.pmtiles itself.
|
||||||
|
// Exported: dem.js's elevation lookups (shadow.js's raymarch) hit the same
|
||||||
|
// online tile source, rather than duplicating the URL template.
|
||||||
|
export const ONLINE_TERRAIN_TILES = ["https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"];
|
||||||
|
|
||||||
|
// A plain on/off Overlays item — there's nothing to choose, just whether
|
||||||
|
// the shading shows, unlike Radar's expandable scrub/forecast.
|
||||||
|
class HillshadeControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._active = false;
|
||||||
|
this._offlineAvailable = false;
|
||||||
|
// setStyle() (switching Online/Offline) tears down all custom
|
||||||
|
// sources/layers — put hillshade back if it was on.
|
||||||
|
map.on("style.load", () => {
|
||||||
|
if (this._active) this._addLayer();
|
||||||
|
this.syncTerrain(this._map.getPitch() > 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${WILDERNESS_ICON}</span>
|
||||||
|
<span>Terrain shading</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
||||||
|
this._checkbox.addEventListener("change", (event) => this._toggle(event.target.checked));
|
||||||
|
}
|
||||||
|
|
||||||
|
setOfflineAvailable(available) {
|
||||||
|
this._offlineAvailable = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle(active) {
|
||||||
|
this._active = active;
|
||||||
|
// Clear first: setTerrain may still be pointing at HILLSHADE_ID from
|
||||||
|
// before, and removing that source out from under it is worth avoiding
|
||||||
|
// even for the instant before syncTerrain below re-evaluates it.
|
||||||
|
if (this._map.getTerrain()) this._map.setTerrain(null);
|
||||||
|
if (active) this._addLayer();
|
||||||
|
else this._removeLayer();
|
||||||
|
this.syncTerrain(this._map.getPitch() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Real terrain displacement (map.setTerrain), not just the flat
|
||||||
|
// hillshade/color-relief paint above — called both when this checkbox
|
||||||
|
// changes and when the 3D pitch toggle changes (map.js's
|
||||||
|
// PitchToggleControl, via OverlaysControl.syncTerrain below), since
|
||||||
|
// showing raised terrain needs both "tilted enough to see it" and "the
|
||||||
|
// DEM source this checkbox controls is actually loaded".
|
||||||
|
syncTerrain(pitched) {
|
||||||
|
// setLight/setTerrain throw if the style isn't loaded yet — called
|
||||||
|
// synchronously from map.js's setOverlaysControl during page init,
|
||||||
|
// before the first style.load; that listener re-invokes this once it
|
||||||
|
// actually is loaded, so bailing here just skips the premature call.
|
||||||
|
if (!this._map.isStyleLoaded()) return;
|
||||||
|
const terrainOn = pitched && this._active && this._map.getSource(HILLSHADE_ID);
|
||||||
|
if (terrainOn) {
|
||||||
|
this._map.setTerrain({ source: HILLSHADE_ID, exaggeration: 3 });
|
||||||
|
} else if (this._map.getTerrain()) {
|
||||||
|
this._map.setTerrain(null);
|
||||||
|
}
|
||||||
|
this._map.setLight(terrainOn ? TERRAIN_LIGHT : DEFAULT_LIGHT);
|
||||||
|
// The raster hillshade layer fakes relief by shading a flat texture —
|
||||||
|
// once real 3D geometry is doing that job (with its own lit shading
|
||||||
|
// for free), the raster version just looks like a blurry, low-res
|
||||||
|
// texture pasted on top of it. It's the only way to suggest relief in
|
||||||
|
// flat 2D though, so only hide it once real terrain is actually up.
|
||||||
|
// The color-relief elevation tint stays regardless — that's
|
||||||
|
// colour-by-height information, not a shading trick.
|
||||||
|
if (this._map.getLayer(HILLSHADE_ID)) {
|
||||||
|
this._map.setLayoutProperty(HILLSHADE_ID, "visibility", terrainOn ? "none" : "visible");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_addLayer() {
|
||||||
|
if (this._map.getSource(HILLSHADE_ID)) return;
|
||||||
|
|
||||||
|
// terrain.pmtiles is built from Mapterhorn (mapterhorn.com — see
|
||||||
|
// lib/maptool/pmtiles_builder.rb) whether generated in-app or by hand
|
||||||
|
// (docs/development.adoc), so this credits them regardless of how it
|
||||||
|
// got there — their own site lists the full per-region source
|
||||||
|
// provenance (CC-BY-4.0 and similar), not practical to reproduce here.
|
||||||
|
const source = this._offlineAvailable
|
||||||
|
? {
|
||||||
|
type: "raster-dem",
|
||||||
|
url: `pmtiles://${window.location.origin}/tiles/terrain.pmtiles`,
|
||||||
|
encoding: "terrarium",
|
||||||
|
attribution: "Terrain: Mapterhorn",
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: "raster-dem",
|
||||||
|
tiles: ONLINE_TERRAIN_TILES,
|
||||||
|
tileSize: 256,
|
||||||
|
encoding: "terrarium",
|
||||||
|
maxzoom: 15,
|
||||||
|
attribution: "Terrain tiles: Mapzen, SRTM, ArcticDEM",
|
||||||
|
};
|
||||||
|
this._map.addSource(HILLSHADE_ID, source);
|
||||||
|
// Elevation tint under, hillshade on top: the tint sets the base color
|
||||||
|
// per elevation band, hillshade multiplies slope-shading over it — the
|
||||||
|
// combined "characteristic relief look" the TODO asked for, not two
|
||||||
|
// independent layers.
|
||||||
|
this._map.addLayer({
|
||||||
|
id: COLOR_RELIEF_ID,
|
||||||
|
type: "color-relief",
|
||||||
|
source: HILLSHADE_ID,
|
||||||
|
paint: { "color-relief-color": ELEVATION_COLOR_RAMP, "color-relief-opacity": 0.7 },
|
||||||
|
});
|
||||||
|
this._map.addLayer({ id: HILLSHADE_ID, type: "hillshade", source: HILLSHADE_ID, paint: { "hillshade-exaggeration": 0.4 } });
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeLayer() {
|
||||||
|
if (this._map.getLayer(HILLSHADE_ID)) this._map.removeLayer(HILLSHADE_ID);
|
||||||
|
if (this._map.getLayer(COLOR_RELIEF_ID)) this._map.removeLayer(COLOR_RELIEF_ID);
|
||||||
|
if (this._map.getSource(HILLSHADE_ID)) this._map.removeSource(HILLSHADE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A sidebar side panel (same shell as LiveTrackPanelControl/#livetrack-panel)
|
||||||
|
// listing live map layers that aren't a base map or a Tracks & Points file —
|
||||||
|
// each one refetched/toggled live rather than something to add/remove/
|
||||||
|
// reorder, so this is a plain checklist, its items built here (like
|
||||||
|
// PluginsControl's own list) rather than as static markup. Radar and
|
||||||
|
// Terrain shading used to be their own separate sidebar icons; both moved
|
||||||
|
// here since neither has anything to "manage," just on/off (Radar's own
|
||||||
|
// scrub/forecast controls live behind expanding its item, not a factor in
|
||||||
|
// where it's mounted).
|
||||||
|
export class OverlaysControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._hillshade = new HillshadeControl(map);
|
||||||
|
this._radar = new RadarControl(map);
|
||||||
|
this._shadow = new ShadowControl(map);
|
||||||
|
this._contour = new ContourControl(map);
|
||||||
|
this._hikingTrails = new HikingTrailsControl(map);
|
||||||
|
this._wikiEnabled = false; // the checkbox's own state
|
||||||
|
this._wikiMoveHandler = null; // the moveend listener driving its live refetch, while enabled
|
||||||
|
this._wikiRequestSeq = 0; // ignores an in-flight refetch a newer one has already superseded
|
||||||
|
this._interactivityRegistered = false; // guards against double-registering the click/hover handlers below across enable/disable cycles
|
||||||
|
|
||||||
|
// setStyle() (switching the Online/Offline base map) tears down all
|
||||||
|
// custom sources/layers/images — put the wiki layer back afterwards,
|
||||||
|
// same as GpxPanelControl does for visible GPX files. Hillshade/Radar
|
||||||
|
// handle their own the same way, from their own constructors above.
|
||||||
|
map.on("style.load", () => {
|
||||||
|
if (this._wikiEnabled) this._addWikiLayers().then(() => this._refreshWikiLayer());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(sidebar) {
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "sidebar-item";
|
||||||
|
this._container.innerHTML = `<button type="button" class="sidebar-icon" title="Overlays" aria-label="Overlays">${OVERLAYS_ICON}</button>`;
|
||||||
|
this._icon = this._container.querySelector(".sidebar-icon");
|
||||||
|
this._icon.addEventListener("click", () => this._togglePanel());
|
||||||
|
|
||||||
|
this._panel = document.getElementById("overlays-panel");
|
||||||
|
this._panel
|
||||||
|
.querySelector(".basemap-panel__close")
|
||||||
|
.addEventListener("click", () => this._togglePanel(false));
|
||||||
|
|
||||||
|
const list = this._panel.querySelector(".overlays-panel__list");
|
||||||
|
list.innerHTML = `
|
||||||
|
<li class="overlays-panel__item"></li>
|
||||||
|
<li class="overlays-panel__item"></li>
|
||||||
|
<li class="overlays-panel__item overlays-panel__item--expandable"></li>
|
||||||
|
<li class="overlays-panel__item overlays-panel__item--expandable"></li>
|
||||||
|
<li class="overlays-panel__item"></li>
|
||||||
|
<li class="overlays-panel__item overlays-panel__item--expandable"></li>
|
||||||
|
`;
|
||||||
|
const [hillshadeItem, wikiItem, radarItem, shadowItem, contourItem, hikingTrailsItem] = list.children;
|
||||||
|
this._hillshade.mount(hillshadeItem);
|
||||||
|
this._mountWiki(wikiItem);
|
||||||
|
this._radar.mount(radarItem);
|
||||||
|
this._shadow.mount(shadowItem);
|
||||||
|
this._contour.mount(contourItem);
|
||||||
|
this._hikingTrails.mount(hikingTrailsItem);
|
||||||
|
|
||||||
|
onlyOneSidePanelOpen(this._panel, () => this._icon.classList.remove("active"));
|
||||||
|
|
||||||
|
sidebar.appendChild(this._container);
|
||||||
|
}
|
||||||
|
|
||||||
|
_togglePanel(force) {
|
||||||
|
const open = force === undefined ? this._panel.hidden : force;
|
||||||
|
this._panel.hidden = !open;
|
||||||
|
this._icon.classList.toggle("active", open);
|
||||||
|
if (open) notifySidePanelOpen(this._panel);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHillshadeOfflineAvailable(available) {
|
||||||
|
this._hillshade.setOfflineAvailable(available);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHikingTrailsOfflineCountries(countries) {
|
||||||
|
this._hikingTrails.setOfflineCountries(countries);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called by map.js's PitchToggleControl whenever the 3D pitch toggle
|
||||||
|
// changes, so raised terrain follows the camera tilt rather than needing
|
||||||
|
// the "Terrain shading" checkbox re-clicked to notice.
|
||||||
|
syncTerrain(pitched) {
|
||||||
|
this._hillshade.syncTerrain(pitched);
|
||||||
|
}
|
||||||
|
|
||||||
|
setShadowOfflineAvailable(available) {
|
||||||
|
this._shadow.setOfflineAvailable(available);
|
||||||
|
}
|
||||||
|
|
||||||
|
_mountWiki(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${WIKI_ICON}</span>
|
||||||
|
<span>Wikipedia</span>
|
||||||
|
</label>
|
||||||
|
`;
|
||||||
|
this._wikiItem = li;
|
||||||
|
this._wikiToggle = li.querySelector(".overlays-panel__toggle");
|
||||||
|
this._wikiToggle.addEventListener("change", (event) => {
|
||||||
|
if (event.target.checked) this._enableWikiLayer();
|
||||||
|
else this._disableWikiLayer();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called once map.js's one-time GET /api/kiwix check resolves — disables
|
||||||
|
// (rather than hides) the checkbox rather than the whole panel/icon,
|
||||||
|
// since the other overlays are still usable with no kiwix-serve
|
||||||
|
// configured at all.
|
||||||
|
setWikiAvailable(available) {
|
||||||
|
this._wikiToggle.disabled = !available;
|
||||||
|
if (!available) this._wikiItem.title = "No kiwix-serve configured";
|
||||||
|
}
|
||||||
|
|
||||||
|
async _enableWikiLayer() {
|
||||||
|
this._wikiEnabled = true;
|
||||||
|
await this._addWikiLayers();
|
||||||
|
this._wikiMoveHandler = () => this._refreshWikiLayer();
|
||||||
|
this._map.on("moveend", this._wikiMoveHandler);
|
||||||
|
this._refreshWikiLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
_disableWikiLayer() {
|
||||||
|
this._wikiEnabled = false;
|
||||||
|
if (this._wikiMoveHandler) {
|
||||||
|
this._map.off("moveend", this._wikiMoveHandler);
|
||||||
|
this._wikiMoveHandler = null;
|
||||||
|
}
|
||||||
|
this._removeWikiLayers();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _registerWikiIcon() {
|
||||||
|
if (this._map.hasImage(WIKI_ICON_ID)) return;
|
||||||
|
const whiteSvg = WIKI_ICON.replace(/currentColor/g, "#fff");
|
||||||
|
const imageData = await svgToImageData(whiteSvg, ICON_IMAGE_SIZE);
|
||||||
|
if (!this._map.hasImage(WIKI_ICON_ID)) this._map.addImage(WIKI_ICON_ID, imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same one-image-per-color story as gpx.js's own _registerBadgeColors,
|
||||||
|
// just for this layer's single fixed color rather than a set of them
|
||||||
|
// discovered from a file.
|
||||||
|
async _registerWikiBadge() {
|
||||||
|
const id = badgeImageId(WIKI_BADGE_COLOR);
|
||||||
|
if (this._map.hasImage(id)) return;
|
||||||
|
const coloredSvg = MAP_MARKER_ICON.replace(/currentColor/g, WIKI_BADGE_COLOR);
|
||||||
|
const imageData = await svgToImageData(coloredSvg, ICON_IMAGE_SIZE);
|
||||||
|
if (!this._map.hasImage(id)) this._map.addImage(id, imageData);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _addWikiLayers() {
|
||||||
|
if (this._map.getSource(WIKI_SOURCE_ID)) return;
|
||||||
|
await Promise.all([this._registerWikiIcon(), this._registerWikiBadge()]);
|
||||||
|
if (this._map.getSource(WIKI_SOURCE_ID)) return; // style.load raced this while awaiting
|
||||||
|
|
||||||
|
this._map.addSource(WIKI_SOURCE_ID, { type: "geojson", data: { type: "FeatureCollection", features: [] } });
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${WIKI_SOURCE_ID}-badge`,
|
||||||
|
type: "symbol",
|
||||||
|
source: WIKI_SOURCE_ID,
|
||||||
|
layout: {
|
||||||
|
"icon-image": badgeImageId(WIKI_BADGE_COLOR),
|
||||||
|
"icon-size": MARKER_SIZE,
|
||||||
|
"icon-allow-overlap": true,
|
||||||
|
"icon-ignore-placement": true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this._map.addLayer({
|
||||||
|
id: `${WIKI_SOURCE_ID}-icon`,
|
||||||
|
type: "symbol",
|
||||||
|
source: WIKI_SOURCE_ID,
|
||||||
|
layout: { "icon-image": WIKI_ICON_ID, "icon-size": ICON_SIZE, "icon-allow-overlap": true, "icon-ignore-placement": true },
|
||||||
|
});
|
||||||
|
this._registerWikiInteractivity(`${WIKI_SOURCE_ID}-badge`);
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeWikiLayers() {
|
||||||
|
[`${WIKI_SOURCE_ID}-badge`, `${WIKI_SOURCE_ID}-icon`].forEach((layerId) => {
|
||||||
|
if (this._map.getLayer(layerId)) this._map.removeLayer(layerId);
|
||||||
|
});
|
||||||
|
if (this._map.getSource(WIKI_SOURCE_ID)) this._map.removeSource(WIKI_SOURCE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
_registerWikiInteractivity(layerId) {
|
||||||
|
if (this._interactivityRegistered) return;
|
||||||
|
this._interactivityRegistered = true;
|
||||||
|
|
||||||
|
this._map.on("click", layerId, (event) => {
|
||||||
|
const feature = event.features[0];
|
||||||
|
if (!feature) return;
|
||||||
|
new Popup({ closeButton: true, maxWidth: "260px" }).setLngLat(event.lngLat).setHTML(wikiPopupHtml(feature.properties)).addTo(this._map);
|
||||||
|
});
|
||||||
|
this._map.on("mouseenter", layerId, () => {
|
||||||
|
this._map.getCanvas().style.cursor = "pointer";
|
||||||
|
});
|
||||||
|
this._map.on("mouseleave", layerId, () => {
|
||||||
|
this._map.getCanvas().style.cursor = "";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// seq guards against an out-of-order response — a fast second pan's
|
||||||
|
// fetch resolving before a slower first one's — overwriting newer data
|
||||||
|
// with stale data; not a full abort-in-flight setup, just the ordering
|
||||||
|
// guarantee this actually needs.
|
||||||
|
async _refreshWikiLayer() {
|
||||||
|
const seq = ++this._wikiRequestSeq;
|
||||||
|
const bounds = this._map.getBounds();
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
south: bounds.getSouth(),
|
||||||
|
west: bounds.getWest(),
|
||||||
|
north: bounds.getNorth(),
|
||||||
|
east: bounds.getEast(),
|
||||||
|
});
|
||||||
|
|
||||||
|
let geojson;
|
||||||
|
try {
|
||||||
|
geojson = await (await fetch(`/api/kiwix/nearby?${params}`)).json();
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (seq !== this._wikiRequestSeq) return;
|
||||||
|
|
||||||
|
const source = this._map.getSource(WIKI_SOURCE_ID);
|
||||||
|
if (source) source.setData(geojson);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// properties come from GET /api/kiwix/nearby — kiwix-serve's own ZIM
|
||||||
|
// content, not an uploaded file, but still untrusted-by-convention like
|
||||||
|
// every other popup builder in this app (escaped all the same). link
|
||||||
|
// already carries the /kiwix prefix this app proxies (lib/maptool/kiwix.rb).
|
||||||
|
function wikiPopupHtml({ name, link }) {
|
||||||
|
const parts = [`<div class="gpx-popup__header"><strong>${escapeHtml(name)}</strong></div>`];
|
||||||
|
parts.push(`<p class="gpx-popup__wiki"><a href="${escapeHtml(link)}" target="_blank" rel="noopener noreferrer">Read on Wikipedia →</a></p>`);
|
||||||
|
return parts.join("");
|
||||||
|
}
|
||||||
@@ -15,3 +15,19 @@ export function onlyOneSidePanelOpen(panel, onClosedByOther) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lets a control that writes into the GPX store (e.g. notes.js saving a
|
||||||
|
// drawn point) tell GpxPanelControl its tree is stale, without either one
|
||||||
|
// holding a reference to the other. path (the .geojson file just written
|
||||||
|
// to) lets the listener also auto-show that file instead of just refreshing
|
||||||
|
// the tree — the point/shape just saved is otherwise invisible until the
|
||||||
|
// user finds and checks it by hand.
|
||||||
|
const STORE_CHANGED_EVENT = "gpx-store-changed";
|
||||||
|
|
||||||
|
export function notifyGpxStoreChanged(path) {
|
||||||
|
document.dispatchEvent(new CustomEvent(STORE_CHANGED_EVENT, { detail: path }));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function onGpxStoreChanged(callback) {
|
||||||
|
document.addEventListener(STORE_CHANGED_EVENT, (event) => callback(event.detail));
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
import { PLUGIN_ICON } from "/js/icons.js";
|
||||||
|
import { notifyGpxStoreChanged } from "/js/panels.js";
|
||||||
|
|
||||||
|
// A sidebar flyout listing whatever import plugins are on disk (see
|
||||||
|
// lib/maptool/plugins.rb for the contract) — each one just a "Run for
|
||||||
|
// this area" button using the current map viewport as the bbox, so there's
|
||||||
|
// no separate area-picker to build: pan/zoom to where you want, click Run.
|
||||||
|
// Only ever mounted when logged in (see map.js) — same "hide the
|
||||||
|
// write-only affordance" story as NotesControl, since the only thing this
|
||||||
|
// panel does is a login-gated write.
|
||||||
|
export class PluginsControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(sidebar) {
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "sidebar-item";
|
||||||
|
this._container.innerHTML = `
|
||||||
|
<button type="button" class="sidebar-icon" title="Import plugins" aria-label="Import plugins">${PLUGIN_ICON}</button>
|
||||||
|
<div class="sidebar-flyout" hidden>
|
||||||
|
<p class="plugins-panel__hint">Runs for whatever area is currently on screen.</p>
|
||||||
|
<ul class="plugins-panel__list"></ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this._icon = this._container.querySelector(".sidebar-icon");
|
||||||
|
this._flyout = this._container.querySelector(".sidebar-flyout");
|
||||||
|
this._list = this._container.querySelector(".plugins-panel__list");
|
||||||
|
|
||||||
|
this._icon.addEventListener("click", () => {
|
||||||
|
const opening = this._flyout.hidden;
|
||||||
|
this._flyout.hidden = !opening;
|
||||||
|
this._icon.classList.toggle("active", opening);
|
||||||
|
if (opening) this._loadPlugins();
|
||||||
|
});
|
||||||
|
|
||||||
|
sidebar.appendChild(this._container);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _loadPlugins() {
|
||||||
|
this._list.innerHTML = `<li class="plugins-panel__empty">Loading…</li>`;
|
||||||
|
let plugins;
|
||||||
|
try {
|
||||||
|
plugins = await (await fetch("/api/plugins")).json();
|
||||||
|
} catch (err) {
|
||||||
|
this._list.innerHTML = `<li class="plugins-panel__empty">Could not load plugins</li>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plugins.length === 0) {
|
||||||
|
this._list.innerHTML = `<li class="plugins-panel__empty">No plugins installed — see docs/development.adoc</li>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._list.innerHTML = "";
|
||||||
|
plugins.forEach((plugin) => this._list.appendChild(this._renderItem(plugin)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderItem(plugin) {
|
||||||
|
const li = document.createElement("li");
|
||||||
|
li.className = "plugins-panel__item";
|
||||||
|
li.innerHTML = `
|
||||||
|
<span class="plugins-panel__label"></span>
|
||||||
|
<button type="button" class="plugins-panel__run">Run for this area</button>
|
||||||
|
<span class="plugins-panel__status"></span>
|
||||||
|
`;
|
||||||
|
li.querySelector(".plugins-panel__label").textContent = plugin.label;
|
||||||
|
|
||||||
|
const button = li.querySelector(".plugins-panel__run");
|
||||||
|
const status = li.querySelector(".plugins-panel__status");
|
||||||
|
button.addEventListener("click", () => this._run(plugin, button, status));
|
||||||
|
return li;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _run(plugin, button, status) {
|
||||||
|
button.disabled = true;
|
||||||
|
status.textContent = "Running…";
|
||||||
|
try {
|
||||||
|
const bounds = this._map.getBounds();
|
||||||
|
const body = new URLSearchParams({
|
||||||
|
south: bounds.getSouth(),
|
||||||
|
west: bounds.getWest(),
|
||||||
|
north: bounds.getNorth(),
|
||||||
|
east: bounds.getEast(),
|
||||||
|
});
|
||||||
|
const response = await fetch(`/api/plugins/${encodeURIComponent(plugin.name)}/run`, { method: "POST", body });
|
||||||
|
// Every app.rb response on this route is JSON, success or error —
|
||||||
|
// the catch below is only for an infra-level failure in front of it
|
||||||
|
// (a proxy's own HTML error page, a dropped connection) that never
|
||||||
|
// reached app.rb at all.
|
||||||
|
const data = await response.json().catch(() => ({}));
|
||||||
|
if (!response.ok) throw new Error(data.error || `run failed: ${response.status}`);
|
||||||
|
notifyGpxStoreChanged(data.path);
|
||||||
|
status.textContent = "Done";
|
||||||
|
status.classList.remove("plugins-panel__status--error");
|
||||||
|
} catch (err) {
|
||||||
|
// Plugins::RunError's message (e.g. "plugin exited with status 1")
|
||||||
|
// reaches here as `data.error` above — nothing about a plugin run
|
||||||
|
// failure gets logged server-side (app.rb turns it into a plain 502
|
||||||
|
// JSON response, not an exception), so showing that message directly
|
||||||
|
// is the only way to see why, short of SSHing in to reproduce it.
|
||||||
|
status.textContent = err.message;
|
||||||
|
status.classList.add("plugins-panel__status--error");
|
||||||
|
} finally {
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,51 @@
|
|||||||
import { RADAR_ICON } from "/js/icons.js";
|
import { RADAR_CLOUD_ICON } from "/js/icons.js";
|
||||||
|
|
||||||
const RAINVIEWER_API = "https://api.rainviewer.com/public/weather-maps.json";
|
const RAINVIEWER_API = "https://api.rainviewer.com/public/weather-maps.json";
|
||||||
|
|
||||||
|
// Open-Meteo: free, no API key. Only the forecast (small JSON) is cached
|
||||||
|
// here, in localStorage — the radar tile images above aren't: actually
|
||||||
|
// serving those without a live network needs a Service Worker (intercepting
|
||||||
|
// the tile requests and answering from a cache), which is its own separate
|
||||||
|
// piece of infrastructure (registration, cache strategy, invalidation) and
|
||||||
|
// deliberately left for later rather than folded into this. A cached
|
||||||
|
// forecast still gives *something* useful once genuinely offline, which the
|
||||||
|
// radar animation above can't.
|
||||||
|
const FORECAST_API = "https://api.open-meteo.com/v1/forecast";
|
||||||
|
const FORECAST_DAYS = 3;
|
||||||
|
const HOURLY_FORECAST_COUNT = 8;
|
||||||
|
const FORECAST_CACHE_KEY = "maptool:forecast";
|
||||||
|
|
||||||
|
// A small subset of WMO weather codes (open-meteo's `weather_code`) mapped
|
||||||
|
// to one glyph each — not the full table, just enough to tell clear/cloudy/
|
||||||
|
// rain/snow/storm apart at a glance.
|
||||||
|
const WEATHER_EMOJI = [
|
||||||
|
[[0], "☀️"],
|
||||||
|
[[1, 2, 3], "⛅"],
|
||||||
|
[[45, 48], "🌫️"],
|
||||||
|
[[51, 53, 55, 56, 57, 61, 63, 65, 66, 67, 80, 81, 82], "🌧️"],
|
||||||
|
[[71, 73, 75, 77, 85, 86], "❄️"],
|
||||||
|
[[95, 96, 99], "⛈️"],
|
||||||
|
];
|
||||||
|
|
||||||
|
function weatherEmoji(code) {
|
||||||
|
return WEATHER_EMOJI.find(([codes]) => codes.includes(code))?.[1] ?? "🌡️";
|
||||||
|
}
|
||||||
|
|
||||||
|
// A rolling window from "now" rather than "rest of today" — the latter
|
||||||
|
// shrinks to nothing right before midnight and balloons to ~24 entries just
|
||||||
|
// after, for no good reason.
|
||||||
|
function nextHours(hourly, count) {
|
||||||
|
const now = new Date();
|
||||||
|
return hourly.time
|
||||||
|
.map((time, i) => ({
|
||||||
|
time,
|
||||||
|
code: hourly.weather_code[i],
|
||||||
|
temp: Math.round(hourly.temperature_2m[i]),
|
||||||
|
}))
|
||||||
|
.filter((hour) => new Date(hour.time) >= now)
|
||||||
|
.slice(0, count);
|
||||||
|
}
|
||||||
|
|
||||||
// Matches the default window used by github.com/jpettitt/weather-radar-card
|
// Matches the default window used by github.com/jpettitt/weather-radar-card
|
||||||
// (60 minutes of history at RainViewer's native 10-minute cadence = 7
|
// (60 minutes of history at RainViewer's native 10-minute cadence = 7
|
||||||
// frames, the last one being "now").
|
// frames, the last one being "now").
|
||||||
@@ -11,10 +55,10 @@ const OPACITY = 0.7;
|
|||||||
const ATTRIBUTION =
|
const ATTRIBUTION =
|
||||||
'Weather data © <a href="https://www.rainviewer.com" target="_blank">RainViewer</a>';
|
'Weather data © <a href="https://www.rainviewer.com" target="_blank">RainViewer</a>';
|
||||||
|
|
||||||
// A sidebar icon that fetches RainViewer's recent precipitation frames on
|
// An Overlays panel item (see overlays.js) that fetches RainViewer's recent
|
||||||
// demand and animates them as a raster overlay, with play/pause and a scrub
|
// precipitation frames on demand and animates them as a raster overlay,
|
||||||
// slider in a flyout. No API key needed — see docs/architecture.adoc for
|
// with play/pause and a scrub slider revealed by expanding the item. No API
|
||||||
// RainViewer's usage terms.
|
// key needed — see docs/architecture.adoc for RainViewer's usage terms.
|
||||||
export class RadarControl {
|
export class RadarControl {
|
||||||
constructor(map) {
|
constructor(map) {
|
||||||
this._map = map;
|
this._map = map;
|
||||||
@@ -31,70 +75,154 @@ export class RadarControl {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
mount(sidebar) {
|
// li: the <li class="overlays-panel__item"> this item owns (see
|
||||||
this._container = document.createElement("div");
|
// overlays.js) — built here rather than in the static markup since this
|
||||||
this._container.className = "sidebar-item";
|
// control is the only thing that knows its own internal structure
|
||||||
this._container.innerHTML = `
|
// (playback/forecast), same as before when it built its own sidebar
|
||||||
<button type="button" class="sidebar-icon" title="Radar" aria-label="Radar">${RADAR_ICON}</button>
|
// flyout.
|
||||||
<div class="sidebar-flyout" hidden>
|
mount(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="overlays-panel__row">
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${RADAR_CLOUD_ICON}</span>
|
||||||
|
<button type="button" class="overlays-panel__name">Radar</button>
|
||||||
|
</div>
|
||||||
|
<div class="overlays-panel__details" hidden>
|
||||||
<div class="radar-control__playback">
|
<div class="radar-control__playback">
|
||||||
<button type="button" class="radar-control__play" aria-label="Play">▶</button>
|
<button type="button" class="radar-control__play" aria-label="Play">▶</button>
|
||||||
<input type="range" class="radar-control__scrub" min="0" max="0" step="1" value="0">
|
<input type="range" class="radar-control__scrub" min="0" max="0" step="1" value="0">
|
||||||
<span class="radar-control__time"></span>
|
<span class="radar-control__time"></span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="radar-control__forecast"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
this._icon = this._container.querySelector(".sidebar-icon");
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
||||||
this._flyout = this._container.querySelector(".sidebar-flyout");
|
this._nameButton = li.querySelector(".overlays-panel__name");
|
||||||
this._playButton = this._container.querySelector(".radar-control__play");
|
this._details = li.querySelector(".overlays-panel__details");
|
||||||
this._scrub = this._container.querySelector(".radar-control__scrub");
|
this._playback = li.querySelector(".radar-control__playback");
|
||||||
this._time = this._container.querySelector(".radar-control__time");
|
this._playButton = li.querySelector(".radar-control__play");
|
||||||
|
this._scrub = li.querySelector(".radar-control__scrub");
|
||||||
|
this._time = li.querySelector(".radar-control__time");
|
||||||
|
this._forecastEl = li.querySelector(".radar-control__forecast");
|
||||||
|
|
||||||
this._icon.addEventListener("click", () => this._setEnabled(!this._enabled));
|
this._checkbox.addEventListener("change", (event) => this._setEnabled(event.target.checked));
|
||||||
|
// Separate from the checkbox on purpose — expanding to see the scrub/
|
||||||
|
// forecast shouldn't also flip the layer on, and turning the layer on
|
||||||
|
// shouldn't force the details open (a viewer who never cares to scrub
|
||||||
|
// history/see the forecast keeps a one-line item either way).
|
||||||
|
this._nameButton.addEventListener("click", () => {
|
||||||
|
const expanding = this._details.hidden;
|
||||||
|
this._details.hidden = !expanding;
|
||||||
|
this._nameButton.setAttribute("aria-expanded", String(expanding));
|
||||||
|
});
|
||||||
this._playButton.addEventListener("click", () => this._togglePlayback());
|
this._playButton.addEventListener("click", () => this._togglePlayback());
|
||||||
this._scrub.addEventListener("input", () => {
|
this._scrub.addEventListener("input", () => {
|
||||||
this._stopAnimation();
|
this._stopAnimation();
|
||||||
this._showFrame(Number(this._scrub.value));
|
this._showFrame(Number(this._scrub.value));
|
||||||
});
|
});
|
||||||
|
|
||||||
sidebar.appendChild(this._container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _setEnabled(enabled) {
|
async _setEnabled(enabled) {
|
||||||
this._enabled = enabled;
|
this._enabled = enabled;
|
||||||
this._icon.classList.toggle("active", enabled);
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
this._stopAnimation();
|
this._stopAnimation();
|
||||||
this._removeLayers();
|
this._removeLayers();
|
||||||
this._frames = [];
|
this._frames = [];
|
||||||
this._updateFrameUI();
|
this._updateFrameUI();
|
||||||
|
this._renderForecast(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._icon.disabled = true;
|
this._checkbox.disabled = true;
|
||||||
let frames;
|
// Independent fetches: RainViewer being unreachable shouldn't hide a
|
||||||
try {
|
// forecast that Open-Meteo still answered, or vice versa — especially
|
||||||
frames = await fetchFrames();
|
// since only the forecast has a cached fallback to fall back to.
|
||||||
} catch (err) {
|
const [framesResult, forecastResult] = await Promise.allSettled([fetchFrames(), this._loadForecast()]);
|
||||||
|
this._checkbox.disabled = false;
|
||||||
|
|
||||||
|
// The checkbox may have been unchecked again (disabling) while the
|
||||||
|
// fetches were in flight.
|
||||||
|
if (!this._enabled) return;
|
||||||
|
|
||||||
|
const frames = framesResult.status === "fulfilled" ? framesResult.value : [];
|
||||||
|
const forecast = forecastResult.status === "fulfilled" ? forecastResult.value : null;
|
||||||
|
|
||||||
|
// Nothing at all came back (both offline, no cached forecast either) —
|
||||||
|
// same "turn back off" story the radar-only fetch used to have on its
|
||||||
|
// own failure.
|
||||||
|
if (frames.length === 0 && !forecast) {
|
||||||
this._enabled = false;
|
this._enabled = false;
|
||||||
this._icon.classList.remove("active");
|
this._checkbox.checked = false;
|
||||||
this._icon.disabled = false;
|
} else if (frames.length > 0) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._icon.disabled = false;
|
|
||||||
|
|
||||||
// The icon may have been clicked again (disabling) while the fetch was
|
|
||||||
// in flight.
|
|
||||||
if (!this._enabled || !frames.length) return;
|
|
||||||
|
|
||||||
this._frames = frames;
|
this._frames = frames;
|
||||||
this._frameIndex = frames.length - 1;
|
this._frameIndex = frames.length - 1;
|
||||||
this._addLayers();
|
this._addLayers();
|
||||||
this._updateFrameUI();
|
|
||||||
this._startAnimation();
|
this._startAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._updateFrameUI();
|
||||||
|
this._renderForecast(forecast);
|
||||||
|
}
|
||||||
|
|
||||||
|
// cachedAt is null for a fresh fetch, or the cache's own timestamp when
|
||||||
|
// falling back to it — _renderForecast uses that to show a "cached" note
|
||||||
|
// instead of claiming this is live data.
|
||||||
|
async _loadForecast() {
|
||||||
|
const { lat, lng } = this._map.getCenter();
|
||||||
|
try {
|
||||||
|
const data = await fetchForecast(lat, lng);
|
||||||
|
writeCachedForecast(data);
|
||||||
|
return { data, cachedAt: null };
|
||||||
|
} catch (err) {
|
||||||
|
const cached = readCachedForecast();
|
||||||
|
if (!cached) throw err;
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderForecast(result) {
|
||||||
|
if (!result) {
|
||||||
|
this._forecastEl.innerHTML = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data, cachedAt } = result;
|
||||||
|
const days = data.daily.time.map((date, i) => ({
|
||||||
|
date,
|
||||||
|
code: data.daily.weather_code[i],
|
||||||
|
max: Math.round(data.daily.temperature_2m_max[i]),
|
||||||
|
min: Math.round(data.daily.temperature_2m_min[i]),
|
||||||
|
precip: data.daily.precipitation_probability_max[i],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const hourRows = nextHours(data.hourly, HOURLY_FORECAST_COUNT)
|
||||||
|
.map(
|
||||||
|
(hour) => `
|
||||||
|
<div class="radar-control__forecast-hour">
|
||||||
|
<span>${new Date(hour.time).toLocaleTimeString([], { hour: "2-digit" })}</span>
|
||||||
|
<span>${weatherEmoji(hour.code)}</span>
|
||||||
|
<span>${hour.temp}°</span>
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
const dayRows = days
|
||||||
|
.map(
|
||||||
|
(day) => `
|
||||||
|
<div class="radar-control__forecast-day">
|
||||||
|
<span>${new Date(day.date).toLocaleDateString([], { weekday: "short" })}</span>
|
||||||
|
<span>${weatherEmoji(day.code)}</span>
|
||||||
|
<span>${day.max}° / ${day.min}°</span>
|
||||||
|
<span>${day.precip}%</span>
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
const staleNote = cachedAt
|
||||||
|
? `<p class="radar-control__forecast-stale">Cached from ${new Date(cachedAt).toLocaleString([], { dateStyle: "short", timeStyle: "short" })}</p>`
|
||||||
|
: "";
|
||||||
|
this._forecastEl.innerHTML = `<div class="radar-control__forecast-hours">${hourRows}</div>` + dayRows + staleNote;
|
||||||
|
}
|
||||||
|
|
||||||
_addLayers() {
|
_addLayers() {
|
||||||
const beforeId = firstSymbolLayerId(this._map);
|
const beforeId = firstSymbolLayerId(this._map);
|
||||||
this._frames.forEach((frame, i) => {
|
this._frames.forEach((frame, i) => {
|
||||||
@@ -164,7 +292,7 @@ export class RadarControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_updateFrameUI() {
|
_updateFrameUI() {
|
||||||
this._flyout.hidden = this._frames.length === 0;
|
this._playback.hidden = this._frames.length === 0;
|
||||||
this._scrub.max = String(Math.max(0, this._frames.length - 1));
|
this._scrub.max = String(Math.max(0, this._frames.length - 1));
|
||||||
this._scrub.value = String(this._frameIndex);
|
this._scrub.value = String(this._frameIndex);
|
||||||
const frame = this._frames[this._frameIndex];
|
const frame = this._frames[this._frameIndex];
|
||||||
@@ -192,7 +320,46 @@ async function fetchFrames() {
|
|||||||
return past.slice(-FRAME_COUNT).map((frame) => ({ ...frame, host }));
|
return past.slice(-FRAME_COUNT).map((frame) => ({ ...frame, host }));
|
||||||
}
|
}
|
||||||
|
|
||||||
function firstSymbolLayerId(map) {
|
async function fetchForecast(lat, lon) {
|
||||||
|
const url = new URL(FORECAST_API);
|
||||||
|
url.searchParams.set("latitude", lat);
|
||||||
|
url.searchParams.set("longitude", lon);
|
||||||
|
url.searchParams.set("daily", "weather_code,temperature_2m_max,temperature_2m_min,precipitation_probability_max");
|
||||||
|
// Same call, same cached JSON (writeCachedForecast below) — the next-few-
|
||||||
|
// hours strip alongside the multi-day outlook rather than a second
|
||||||
|
// request/cache entry.
|
||||||
|
url.searchParams.set("hourly", "weather_code,temperature_2m");
|
||||||
|
url.searchParams.set("timezone", "auto");
|
||||||
|
url.searchParams.set("forecast_days", String(FORECAST_DAYS));
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) throw new Error(`Open-Meteo request failed: ${response.status}`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
// localStorage, not IndexedDB: this is one small JSON object (a few days of
|
||||||
|
// daily figures), not worth a heavier async storage API for. Wrapped since
|
||||||
|
// private browsing / storage-blocked contexts can throw on access, not just
|
||||||
|
// on being full — a forecast is a nice-to-have, never worth crashing over.
|
||||||
|
function writeCachedForecast(data) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(FORECAST_CACHE_KEY, JSON.stringify({ data, cachedAt: Date.now() }));
|
||||||
|
} catch (err) {
|
||||||
|
// Ignore — see above.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCachedForecast() {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(FORECAST_CACHE_KEY);
|
||||||
|
return raw ? JSON.parse(raw) : null;
|
||||||
|
} catch (err) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exported: contour.js's own contour-line layers want the same
|
||||||
|
// "sit above base fill/roads, below place labels" placement.
|
||||||
|
export function firstSymbolLayerId(map) {
|
||||||
const layers = map.getStyle()?.layers || [];
|
const layers = map.getStyle()?.layers || [];
|
||||||
const symbolLayer = layers.find((layer) => layer.type === "symbol");
|
const symbolLayer = layers.find((layer) => layer.type === "symbol");
|
||||||
return symbolLayer ? symbolLayer.id : undefined;
|
return symbolLayer ? symbolLayer.id : undefined;
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
import { SEARCH_ICON } from "/js/icons.js";
|
||||||
|
import { escapeHtml } from "/js/gpx.js";
|
||||||
|
|
||||||
|
const NOMINATIM_API = "https://nominatim.openstreetmap.org/search";
|
||||||
|
// "lat, lng" / "lat lng" decimal degrees — the same plain-text shape the
|
||||||
|
// topbar's own coordinate readout copies to the clipboard (map.js). Tried
|
||||||
|
// before hitting the geocoder, so pasting a coordinate pair jumps straight
|
||||||
|
// there with no network round-trip.
|
||||||
|
const COORD_PATTERN = /^\s*(-?\d+(?:\.\d+)?)\s*[, ]\s*(-?\d+(?:\.\d+)?)\s*$/;
|
||||||
|
|
||||||
|
// A place-name/coordinate search box, matching NPS's own Park Tiles viewer.
|
||||||
|
// Online only, like this app's other non-basemap online features (Radar,
|
||||||
|
// the Wikipedia layer) — no offline geocoding index to fall back to.
|
||||||
|
// Nominatim: OpenStreetMap's own geocoder, free, no API key, matching the
|
||||||
|
// online basemap's own OSM-derived style already used here.
|
||||||
|
export class SearchControl {
|
||||||
|
constructor() {
|
||||||
|
this._results = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._container = document.createElement("div");
|
||||||
|
this._container.className = "maplibregl-ctrl maplibregl-ctrl-group search-ctrl";
|
||||||
|
this._container.innerHTML = `
|
||||||
|
<button type="button" class="search-ctrl__button" title="Search" aria-label="Search">${SEARCH_ICON}</button>
|
||||||
|
<div class="search-panel" hidden>
|
||||||
|
<form class="search-panel__form">
|
||||||
|
<input type="text" class="search-panel__input" placeholder="Search, or lat, lng">
|
||||||
|
</form>
|
||||||
|
<ul class="search-panel__results"></ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
this._button = this._container.querySelector(".search-ctrl__button");
|
||||||
|
this._panel = this._container.querySelector(".search-panel");
|
||||||
|
this._input = this._container.querySelector(".search-panel__input");
|
||||||
|
this._resultsEl = this._container.querySelector(".search-panel__results");
|
||||||
|
|
||||||
|
this._button.addEventListener("click", () => this._toggle());
|
||||||
|
this._container.querySelector(".search-panel__form").addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
this._search();
|
||||||
|
});
|
||||||
|
|
||||||
|
return this._container;
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove() {
|
||||||
|
this._container.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDefaultPosition() {
|
||||||
|
return "top-left";
|
||||||
|
}
|
||||||
|
|
||||||
|
_toggle() {
|
||||||
|
const opening = this._panel.hidden;
|
||||||
|
this._panel.hidden = !opening;
|
||||||
|
this._button.classList.toggle("active", opening);
|
||||||
|
if (opening) this._input.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _search() {
|
||||||
|
const query = this._input.value.trim();
|
||||||
|
if (!query) return;
|
||||||
|
|
||||||
|
const coords = query.match(COORD_PATTERN);
|
||||||
|
if (coords) {
|
||||||
|
this._resultsEl.innerHTML = "";
|
||||||
|
this._jumpTo(Number(coords[1]), Number(coords[2]));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._resultsEl.innerHTML = `<li class="search-panel__status">Searching…</li>`;
|
||||||
|
let places;
|
||||||
|
try {
|
||||||
|
const url = new URL(NOMINATIM_API);
|
||||||
|
url.searchParams.set("q", query);
|
||||||
|
url.searchParams.set("format", "jsonv2");
|
||||||
|
url.searchParams.set("limit", "5");
|
||||||
|
places = await (await fetch(url)).json();
|
||||||
|
} catch {
|
||||||
|
this._resultsEl.innerHTML = `<li class="search-panel__status">Search unavailable — check your connection.</li>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._renderResults(places);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderResults(places) {
|
||||||
|
this._results = places;
|
||||||
|
if (places.length === 0) {
|
||||||
|
this._resultsEl.innerHTML = `<li class="search-panel__status">No results</li>`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._resultsEl.innerHTML = places
|
||||||
|
.map((place, i) => `<li class="search-panel__result" data-index="${i}">${escapeHtml(place.display_name)}</li>`)
|
||||||
|
.join("");
|
||||||
|
this._resultsEl.querySelectorAll(".search-panel__result").forEach((el) => {
|
||||||
|
el.addEventListener("click", () => {
|
||||||
|
const place = this._results[Number(el.dataset.index)];
|
||||||
|
this._jumpTo(Number(place.lat), Number(place.lon));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_jumpTo(lat, lng) {
|
||||||
|
this._map.flyTo({ center: [lng, lat], zoom: Math.max(this._map.getZoom(), 14) });
|
||||||
|
this._panel.hidden = true;
|
||||||
|
this._button.classList.remove("active");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
import { getPosition } from "/vendor/suncalc/suncalc.js";
|
||||||
|
import { elevationAt } from "/js/dem.js";
|
||||||
|
import { TERRAIN_SHADOW_ICON } from "/js/icons.js";
|
||||||
|
|
||||||
|
const MIN_ZOOM = 11; // below this, a screen is roughly the same scale as the 40km raymarch — "one screen, one shadow-casting neighborhood" stops holding
|
||||||
|
const GRID_COLS = 96;
|
||||||
|
const GRID_ROWS = 64;
|
||||||
|
const RAYMARCH_STEPS = 16;
|
||||||
|
const RAYMARCH_START_M = 150;
|
||||||
|
const RAYMARCH_MAX_M = 40000;
|
||||||
|
const RAYMARCH_RATIO = (RAYMARCH_MAX_M / RAYMARCH_START_M) ** (1 / (RAYMARCH_STEPS - 1)); // geometric spacing: dense near the point, coarse far out
|
||||||
|
const EARTH_RADIUS_M = 6371000;
|
||||||
|
const SHADOW_RGBA = [20, 20, 50, Math.round(0.45 * 255)];
|
||||||
|
const SLIDER_DEBOUNCE_MS = 300;
|
||||||
|
|
||||||
|
const IMAGE_SOURCE_ID = "terrain-shadow";
|
||||||
|
|
||||||
|
// An Overlays panel item (see overlays.js) shading the parts of the visible
|
||||||
|
// map currently blocked from the sun by surrounding terrain — offline
|
||||||
|
// equivalent of shademap.app, built on the same Terrarium DEM tiles
|
||||||
|
// HillshadeControl already renders (via dem.js) and the same sun-position
|
||||||
|
// math the toolbar readout uses (public/js/sun.js). No Web Worker: a
|
||||||
|
// recompute is dozens of small awaited fetches (mostly cache hits after the
|
||||||
|
// first pan), not sustained sync CPU work, so it already yields to the
|
||||||
|
// event loop on its own between grid rows without blocking a pan/zoom.
|
||||||
|
export class ShadowControl {
|
||||||
|
constructor(map) {
|
||||||
|
this._map = map;
|
||||||
|
this._enabled = false;
|
||||||
|
this._offlineAvailable = false;
|
||||||
|
this._seq = 0; // bumped on every recompute/disable so a superseded in-flight one (a fast second pan, or turning the layer off mid-compute) discards its result instead of overwriting newer data
|
||||||
|
this._moveHandler = null;
|
||||||
|
this._sliderMinutes = currentMinutesOfDay();
|
||||||
|
this._sliderTimer = null;
|
||||||
|
|
||||||
|
// setStyle() (switching Online/Offline) tears down all custom sources/
|
||||||
|
// layers — recompute puts the shadow layer back if it was on, same as
|
||||||
|
// every other Overlays control.
|
||||||
|
map.on("style.load", () => {
|
||||||
|
if (this._enabled) this._recompute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
mount(li) {
|
||||||
|
li.innerHTML = `
|
||||||
|
<div class="overlays-panel__row">
|
||||||
|
<input type="checkbox" class="overlays-panel__toggle">
|
||||||
|
<span class="overlays-panel__icon">${TERRAIN_SHADOW_ICON}</span>
|
||||||
|
<button type="button" class="overlays-panel__name">Terrain shadow</button>
|
||||||
|
</div>
|
||||||
|
<div class="overlays-panel__details" hidden>
|
||||||
|
<div class="shadow-control__time">
|
||||||
|
<input type="range" class="shadow-control__slider" min="0" max="1439" step="5" value="${this._sliderMinutes}">
|
||||||
|
<span class="shadow-control__clock">${formatMinutes(this._sliderMinutes)}</span>
|
||||||
|
</div>
|
||||||
|
<p class="shadow-control__hint" hidden>Zoom in further to compute terrain shadow.</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
this._checkbox = li.querySelector(".overlays-panel__toggle");
|
||||||
|
this._nameButton = li.querySelector(".overlays-panel__name");
|
||||||
|
this._details = li.querySelector(".overlays-panel__details");
|
||||||
|
this._slider = li.querySelector(".shadow-control__slider");
|
||||||
|
this._clock = li.querySelector(".shadow-control__clock");
|
||||||
|
this._hint = li.querySelector(".shadow-control__hint");
|
||||||
|
|
||||||
|
this._checkbox.addEventListener("change", (event) => this._setEnabled(event.target.checked));
|
||||||
|
this._nameButton.addEventListener("click", () => {
|
||||||
|
const expanding = this._details.hidden;
|
||||||
|
this._details.hidden = !expanding;
|
||||||
|
this._nameButton.setAttribute("aria-expanded", String(expanding));
|
||||||
|
});
|
||||||
|
this._slider.addEventListener("input", () => {
|
||||||
|
this._sliderMinutes = Number(this._slider.value);
|
||||||
|
this._clock.textContent = formatMinutes(this._sliderMinutes);
|
||||||
|
clearTimeout(this._sliderTimer);
|
||||||
|
this._sliderTimer = setTimeout(() => this._recompute(), SLIDER_DEBOUNCE_MS);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setOfflineAvailable(available) {
|
||||||
|
this._offlineAvailable = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setEnabled(enabled) {
|
||||||
|
this._enabled = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
this._moveHandler = () => this._recompute();
|
||||||
|
this._map.on("moveend", this._moveHandler);
|
||||||
|
this._recompute();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._seq++;
|
||||||
|
if (this._moveHandler) {
|
||||||
|
this._map.off("moveend", this._moveHandler);
|
||||||
|
this._moveHandler = null;
|
||||||
|
}
|
||||||
|
this._hint.hidden = true;
|
||||||
|
this._removeLayer();
|
||||||
|
}
|
||||||
|
|
||||||
|
async _recompute() {
|
||||||
|
const seq = ++this._seq;
|
||||||
|
|
||||||
|
if (this._map.getZoom() < MIN_ZOOM) {
|
||||||
|
this._hint.hidden = false;
|
||||||
|
this._removeLayer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._hint.hidden = true;
|
||||||
|
|
||||||
|
const bounds = this._map.getBounds();
|
||||||
|
const center = this._map.getCenter();
|
||||||
|
const date = this._selectedDate();
|
||||||
|
// One sun position for the whole viewport — azimuth/altitude vary
|
||||||
|
// negligibly across it next to the raymarch's own 150m-step coarseness.
|
||||||
|
const sun = getPosition(date, center.lat, center.lng);
|
||||||
|
|
||||||
|
const west = bounds.getWest();
|
||||||
|
const east = bounds.getEast();
|
||||||
|
const south = bounds.getSouth();
|
||||||
|
const north = bounds.getNorth();
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = GRID_COLS;
|
||||||
|
canvas.height = GRID_ROWS;
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
const imageData = ctx.createImageData(GRID_COLS, GRID_ROWS);
|
||||||
|
|
||||||
|
if (sun.altitude <= 0) {
|
||||||
|
// Sun's below the horizon everywhere in view — no point raymarching.
|
||||||
|
fillShadow(imageData);
|
||||||
|
} else {
|
||||||
|
for (let row = 0; row < GRID_ROWS; row++) {
|
||||||
|
const lat = north - ((row + 0.5) / GRID_ROWS) * (north - south);
|
||||||
|
const rowWork = [];
|
||||||
|
for (let col = 0; col < GRID_COLS; col++) {
|
||||||
|
const lng = west + ((col + 0.5) / GRID_COLS) * (east - west);
|
||||||
|
rowWork.push(
|
||||||
|
this._isShadowed(lng, lat, sun.azimuth, sun.altitude).then((shadowed) => setPixel(imageData, col, row, shadowed)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Promise.all(rowWork); // yields once per row, so a fast pan/zoom's newer recompute can cut in
|
||||||
|
if (seq !== this._seq) return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.putImageData(imageData, 0, 0);
|
||||||
|
if (seq !== this._seq) return;
|
||||||
|
this._publish(canvas.toDataURL(), bounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
async _isShadowed(lng, lat, sunAzimuth, sunAltitude) {
|
||||||
|
const pointElev = await elevationAt(lng, lat, this._offlineAvailable);
|
||||||
|
if (pointElev === null) return false; // no DEM data here — render sunlit rather than guess
|
||||||
|
|
||||||
|
let dist = RAYMARCH_START_M;
|
||||||
|
const samples = [];
|
||||||
|
for (let i = 0; i < RAYMARCH_STEPS; i++) {
|
||||||
|
samples.push(this._horizonSample(lng, lat, pointElev, sunAzimuth, dist));
|
||||||
|
dist *= RAYMARCH_RATIO;
|
||||||
|
}
|
||||||
|
const angles = (await Promise.all(samples)).filter((a) => a !== null);
|
||||||
|
const horizonMax = angles.length ? Math.max(...angles) : -90;
|
||||||
|
return sunAltitude <= horizonMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
async _horizonSample(lng, lat, pointElev, sunAzimuth, dist) {
|
||||||
|
const sample = destinationPoint(lng, lat, sunAzimuth, dist);
|
||||||
|
const sampleElev = await elevationAt(sample.lng, sample.lat, this._offlineAvailable);
|
||||||
|
if (sampleElev === null) return null;
|
||||||
|
const curvatureDrop = (dist * dist) / (2 * EARTH_RADIUS_M);
|
||||||
|
return (Math.atan2(sampleElev - pointElev - curvatureDrop, dist) * 180) / Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
_selectedDate() {
|
||||||
|
const date = new Date();
|
||||||
|
date.setHours(0, this._sliderMinutes, 0, 0);
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// First image-source usage in this app — addSource/addLayer once, then
|
||||||
|
// updateImage on every later recompute rather than remove+re-add.
|
||||||
|
_publish(dataUrl, bounds) {
|
||||||
|
const coordinates = [
|
||||||
|
[bounds.getWest(), bounds.getNorth()],
|
||||||
|
[bounds.getEast(), bounds.getNorth()],
|
||||||
|
[bounds.getEast(), bounds.getSouth()],
|
||||||
|
[bounds.getWest(), bounds.getSouth()],
|
||||||
|
];
|
||||||
|
|
||||||
|
const source = this._map.getSource(IMAGE_SOURCE_ID);
|
||||||
|
if (source) {
|
||||||
|
source.updateImage({ url: dataUrl, coordinates });
|
||||||
|
} else {
|
||||||
|
this._map.addSource(IMAGE_SOURCE_ID, { type: "image", url: dataUrl, coordinates });
|
||||||
|
this._map.addLayer({ id: IMAGE_SOURCE_ID, type: "raster", source: IMAGE_SOURCE_ID });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeLayer() {
|
||||||
|
if (this._map.getLayer(IMAGE_SOURCE_ID)) this._map.removeLayer(IMAGE_SOURCE_ID);
|
||||||
|
if (this._map.getSource(IMAGE_SOURCE_ID)) this._map.removeSource(IMAGE_SOURCE_ID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flat-earth destination point (bearing/distance -> lng/lat) — fine under
|
||||||
|
// 40km, and nothing in this codebase already does lng/lat offset math.
|
||||||
|
function destinationPoint(lng, lat, bearingDeg, distanceM) {
|
||||||
|
const bearing = (bearingDeg * Math.PI) / 180;
|
||||||
|
const latRad = (lat * Math.PI) / 180;
|
||||||
|
const dLat = (distanceM * Math.cos(bearing)) / EARTH_RADIUS_M;
|
||||||
|
const dLng = (distanceM * Math.sin(bearing)) / (EARTH_RADIUS_M * Math.cos(latRad));
|
||||||
|
return { lng: lng + (dLng * 180) / Math.PI, lat: lat + (dLat * 180) / Math.PI };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPixel(imageData, col, row, shadowed) {
|
||||||
|
if (!shadowed) return; // ImageData starts fully transparent — nothing to do for a sunlit cell
|
||||||
|
const i = (row * imageData.width + col) * 4;
|
||||||
|
imageData.data.set(SHADOW_RGBA, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function fillShadow(imageData) {
|
||||||
|
for (let i = 0; i < imageData.data.length; i += 4) imageData.data.set(SHADOW_RGBA, i);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatMinutes(minutes) {
|
||||||
|
const h = Math.floor(minutes / 60);
|
||||||
|
const m = minutes % 60;
|
||||||
|
return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function currentMinutesOfDay() {
|
||||||
|
const now = new Date();
|
||||||
|
return now.getHours() * 60 + now.getMinutes();
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { getTimes } from "/vendor/suncalc/suncalc.js";
|
||||||
|
|
||||||
|
// A Locus Pro-style toolbar readout: DMS coordinates (plain decimal degrees
|
||||||
|
// read less like a "GPS unit" and are harder to scan at a glance) plus
|
||||||
|
// sunrise/sunset and a countdown to whichever comes next — both computed
|
||||||
|
// for the map's current center, the same point the coordinates already
|
||||||
|
// describe, not a GNSS fix (this app's coordinate readout was never tied
|
||||||
|
// to having one).
|
||||||
|
|
||||||
|
function toDMS(value, positiveLabel, negativeLabel) {
|
||||||
|
const label = value >= 0 ? positiveLabel : negativeLabel;
|
||||||
|
const abs = Math.abs(value);
|
||||||
|
const degrees = Math.floor(abs);
|
||||||
|
const minutesFull = (abs - degrees) * 60;
|
||||||
|
let minutes = Math.floor(minutesFull);
|
||||||
|
let seconds = Math.round((minutesFull - minutes) * 60);
|
||||||
|
// A seconds value that rounds up to 60 has to carry into minutes (and
|
||||||
|
// from there, in the rare case, into degrees) rather than print "60".
|
||||||
|
if (seconds === 60) {
|
||||||
|
seconds = 0;
|
||||||
|
minutes += 1;
|
||||||
|
}
|
||||||
|
const carriedDegrees = minutes === 60 ? degrees + 1 : degrees;
|
||||||
|
const carriedMinutes = minutes === 60 ? 0 : minutes;
|
||||||
|
return `${carriedDegrees}°${String(carriedMinutes).padStart(2, "0")}'${String(seconds).padStart(2, "0")}"${label}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatCoords(lat, lng) {
|
||||||
|
return `${toDMS(lat, "N", "S")} ${toDMS(lng, "E", "W")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTime(date) {
|
||||||
|
return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCountdown(ms) {
|
||||||
|
const totalMinutes = Math.max(0, Math.round(ms / 60000));
|
||||||
|
const hours = Math.floor(totalMinutes / 60);
|
||||||
|
const minutes = totalMinutes % 60;
|
||||||
|
return hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
// null when there's no sunrise/sunset to show at all — getTimes returns
|
||||||
|
// null (not Invalid Date) for both on a polar day/night, per its own
|
||||||
|
// comment in suncalc.js — the caller just hides the readout in that case
|
||||||
|
// rather than showing something misleading. Returns the three pieces
|
||||||
|
// separately (not one joined string) so the caller can put its own icon in
|
||||||
|
// front of each rather than this module owning any markup.
|
||||||
|
export function formatSunInfo(lat, lng, now = new Date()) {
|
||||||
|
const today = getTimes(now, lat, lng);
|
||||||
|
if (today.sunrise === null || today.sunset === null) return null;
|
||||||
|
|
||||||
|
let next;
|
||||||
|
if (now < today.sunrise) {
|
||||||
|
next = { label: "sunrise", at: today.sunrise };
|
||||||
|
} else if (now < today.sunset) {
|
||||||
|
next = { label: "sunset", at: today.sunset };
|
||||||
|
} else {
|
||||||
|
// Already past today's sunset — the next event is tomorrow's sunrise.
|
||||||
|
const tomorrow = getTimes(new Date(now.getTime() + DAY_MS), lat, lng);
|
||||||
|
next = tomorrow.sunrise === null ? null : { label: "sunrise", at: tomorrow.sunrise };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
sunrise: formatTime(today.sunrise),
|
||||||
|
sunset: formatTime(today.sunset),
|
||||||
|
countdown: next ? `${formatCountdown(next.at - now)} to ${next.label}` : null,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
// Caches RainViewer radar tile images so the last-seen frames stay visible
|
||||||
|
// once the network is actually gone — nothing else here. No app-shell
|
||||||
|
// precaching: the app itself is served by the same local server that's
|
||||||
|
// already the offline resource in the van, so there's no "app unreachable
|
||||||
|
// but should still work" scenario to solve, unlike the radar tiles (a
|
||||||
|
// third-party CDN).
|
||||||
|
const CACHE_VERSION = "v1";
|
||||||
|
const RADAR_CACHE = `radar-tiles-${CACHE_VERSION}`;
|
||||||
|
|
||||||
|
// ponytail: a rough cap, not real LRU/expiry — trims oldest-by-insertion via
|
||||||
|
// Cache API iteration order (stable in practice, not spec-guaranteed).
|
||||||
|
// Revisit with timestamp-based expiry if storage actually becomes a
|
||||||
|
// problem; 400 tiles is at most a few MB either way.
|
||||||
|
const MAX_RADAR_TILES = 400;
|
||||||
|
|
||||||
|
function isRadarTile(url) {
|
||||||
|
return url.hostname.endsWith("rainviewer.com") && url.pathname.includes("/v2/radar/");
|
||||||
|
}
|
||||||
|
|
||||||
|
self.addEventListener("install", (event) => {
|
||||||
|
self.skipWaiting();
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("activate", (event) => {
|
||||||
|
event.waitUntil(
|
||||||
|
caches
|
||||||
|
.keys()
|
||||||
|
.then((keys) => Promise.all(keys.filter((key) => key !== RADAR_CACHE).map((key) => caches.delete(key))))
|
||||||
|
.then(() => self.clients.claim()),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
self.addEventListener("fetch", (event) => {
|
||||||
|
const url = new URL(event.request.url);
|
||||||
|
if (!isRadarTile(url)) return;
|
||||||
|
|
||||||
|
event.respondWith(networkFallingBackToCache(event.request));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Network first (frame ids rotate as new radar sweeps come in, so a cached
|
||||||
|
// tile can go stale) — cache is only the answer once the network actually
|
||||||
|
// fails, not a shortcut when it's merely slow.
|
||||||
|
async function networkFallingBackToCache(request) {
|
||||||
|
const cache = await caches.open(RADAR_CACHE);
|
||||||
|
try {
|
||||||
|
const response = await fetch(request);
|
||||||
|
if (response.ok) {
|
||||||
|
cache.put(request, response.clone());
|
||||||
|
trimRadarCache(cache);
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
} catch (err) {
|
||||||
|
const cached = await cache.match(request);
|
||||||
|
if (cached) return cached;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function trimRadarCache(cache) {
|
||||||
|
const keys = await cache.keys();
|
||||||
|
if (keys.length <= MAX_RADAR_TILES) return;
|
||||||
|
await Promise.all(keys.slice(0, keys.length - MAX_RADAR_TILES).map((key) => cache.delete(key)));
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-al" viewBox="0 0 512 512">
|
||||||
|
<path fill="red" d="M0 0h512v512H0z"/>
|
||||||
|
<path id="al-a" fill="#000001" d="M204.9 99.5c-5 0-13.2 1.6-13 5.4-14-2.3-15.4 3.4-14.6 8.5q2.3-3 4.2-3.3 2.8-.4 5.8 1.5a23 23 0 0 1 5 4.4c-4.8 1.1-8.6.4-12.4-.3a18 18 0 0 1-6.1-2.5c-1.6-1.1-2.1-2.1-4.6-4.7-2.9-3-6-2.1-5 2.5 2.2 4.3 6 6.3 10.7 7 2.2.4 5.6 1.2 9.4 1.2s8.1-.5 10.5 0c-1.4.8-3 2.4-6.2 3s-8-2-11-2.6c.4 2.5 3.5 4.8 9.7 6 10.2 2.2 18.7 4 24.3 7s9.1 6.8 11.6 9.8c5 6 5.3 10.5 5.6 11.5 1 9.5-2.2 14.8-8.4 16.4-3 .8-8.5-.7-10.5-3-2-2.4-4-6.4-3.4-12.7.5-2.5 3.4-9 1-10.3a292 292 0 0 0-34.4-16c-2.7-1.1-5 2.5-5.8 4A54 54 0 0 1 129 107c-4.6-8.1-12.1 0-10.9 7.7 2.1 8.6 8.6 14.8 16.5 19.2 8 4.5 18.1 8.8 28.3 8.6 5.5 1 5.5 8.2-1.1 9.5-13 0-23.2-.2-32.9-9.6-7.4-6.7-11.5 1.3-9.4 5.8 3.6 14 23.6 18 43.8 13.4 7.8-1.3 3.1 7 .9 7.2-8.4 6-23.5 12-36.8-.1-6.1-4.7-10.2-.7-8 6 6 17.5 28.5 13.8 44 5.2 4-2.2 7.6 3 2.7 6.9-19.2 13.4-28.9 13.6-37.6 8.4-10.8-4.3-11.8 7.8-5.3 11.8 7.2 4.4 25.4 1 38.9-7.4 5.7-4.2 6 2.4 2.3 5-15.9 13.8-22.2 17.5-38.8 15.2-8.2-.6-8 9.5-1.6 13.5 8.8 5.4 26.1-3.6 39.5-14.7 5.6-3 6.6 2 3.8 7.8a57 57 0 0 1-23.3 19.2 29 29 0 0 1-19.5.7c-6.2-2.2-7 4.2-3.6 10 2 3.5 10.6 4.7 19.7 1.4 9.2-3.2 19-10.8 25.7-19.8 6-5.1 5.2 1.8 2.5 6.7-13.5 21.3-25.9 29.2-42.1 27.9-7.3-1.2-8.9 4.4-4.3 9.6 8 6.7 18.2 6.4 27-.2a751 751 0 0 0 30.8-32.6c5.5-4.4 7.3 0 5.7 9-1.5 5.1-5.2 10.5-15.3 14.5-7 4-1.8 9.4 3.4 9.5 2.9 0 8.7-3.3 13-8.3 5.9-6.5 6.2-11 9.5-21.1 3-5 8.4-2.7 8.4 2.5-2.6 10.2-4.8 12-10 16.2-5.1 4.7 3.4 6.3 6.3 4.4 8.3-5.6 11.3-12.8 14.1-19.4 2-4.8 7.8-2.5 5.1 5.3-6.4 18.5-17 25.8-35.5 29.6-1.9.3-3 1.4-2.4 3.6l7.5 7.5c-11.5 3.3-20.8 5.2-32.2 8.5L142 300.6c-1.5-3.4-2.2-8.7-10.4-5-5.7-2.6-8.2-1.6-11.4 1 4.5.1 6.5 1.3 8.3 3.4 2.3 6 7.6 6.6 13 5 3.5 2.9 5.4 5.2 9 8.2l-17.8-.6c-6.3-6.7-11.3-6.3-15.8-1-3.5.5-5 .5-7.3 4.7 3.7-1.5 6-2 7.7-.3 6.6 3.9 11 3 14.3 0l18.7 1.1c-2.3 2-5.6 3.1-8 5.2-9.7-2.8-14.7 1-16.4 8.8a18 18 0 0 0-1.4 10c1-3.2 2.5-5.9 5.3-7.6 8.6 2.2 11.8-1.3 12.3-6.5 4.2-3.4 10.5-4.1 14.6-7.6 4.9 1.6 7.2 2.6 12.1 4.1q2.5 8 12 6c7.7.3 6.3 3.4 7 5.9 2-3.6 2-7-2.8-10.3-1.7-4.6-5.5-6.7-10.4-4-4.7-1.3-5.9-3.2-10.5-4.6 11.7-3.7 20-4.5 31.8-8.3 3 2.8 5.2 4.8 8.2 7.2q2.5 1.6 4 0c7.3-10.6 10.6-20 17.4-27 2.6-2.9 6-6.8 9.6-7.8 1.8-.4 4-.2 5.5 1.4 1.4 1.6 2.6 4.4 2 8.7-.6 6.2-2 8.2-3.8 11.8s-3.9 6-6 8.8c-4.4 5.7-10.1 9-13.5 11.2-6.8 4.4-9.7 2.5-15 2.2-6.7.8-8.5 4.1-3 8.7a21 21 0 0 0 13.7 2.3c3.3-.6 7-4.8 9.8-7 3-3.6 8.1.6 4.7 4.7-6.3 7.5-12.6 12.4-20.3 12.3-8.2 1-6.7 5.7-1.3 7.9 9.8 4 18.6-3.5 23-8.5 3.5-3.7 6-3.9 5.3 2-3.4 10.5-8.1 14.6-15.7 15.1-6.2-.5-6.3 4.2-1.7 7.5 10.3 7 17.7-5 21.2-12.4 2.5-6.6 6.3-3.5 6.7 2 0 7.3-3.2 13.2-12 20.7 6.7 10.7 14.5 21.7 21.3 32.5l20.5-228.2-20.5-36c-2.1-2-9.3-10.5-11.2-11.7-.7-.7-1.1-1.2-.1-1.6s3.2-.8 4.8-1c-4.4-4.4-8-5.8-16.3-8.2 2-.8 4-.3 9.9-.6a32 32 0 0 0-14.4-11c4.5-3 5.3-3.3 9.8-7-7.7-.6-14.3-2-20.8-4a41 41 0 0 0-12.8-3.7m.7 9c4 0 6.6 1.4 6.6 3s-2.5 3.1-6.6 3.1-6.6-1.5-6.6-3.2 2.6-3 6.6-3z"/>
|
||||||
|
<use xlink:href="#al-a" width="100%" height="100%" transform="matrix(-1 0 0 1 512 0)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-at" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#c8102e" d="M0 0h512v170.7H0zm0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 203 B |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ba" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="ba-a">
|
||||||
|
<path fill-opacity=".7" d="M0 0h512v512H0z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g fill-rule="evenodd" clip-path="url(#ba-a)">
|
||||||
|
<path fill="#009" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#fc0" d="m77 0 437 437V0z"/>
|
||||||
|
<path fill="#FFF" d="m461.4 470.4-26.1-19.1-26.9 19 10.2-31.2-26.4-19.2h32.7l10.2-31 10 31.1 32.8.1-26.2 19.4zm76.7 10.4h-32.7l-10-31.2-10.2 31.1h-32.8l26.4 19.2-10.1 31.2 26.8-19 26.2 19-9.8-30.9zM391.8 379.6l26.2-19.4h-32.7L375.2 329 365 360h-32.7l26.4 19.3-10.1 31.1 26.8-19 26.1 19.1zm-60.3-60.4 26.2-19.4-32.8-.1-10-31.2-10.2 31.2-32.7-.1 26.4 19.2-10.2 31.2 26.9-19 26.1 19.1zm-59.7-59.7 26.2-19.4h-32.7l-10.1-31.2L245 240h-32.7l26.4 19.2-10.1 31.2 26.8-19 26.1 19zm-60.4-60.3 26.2-19.3-32.8-.1-10-31.2-10.2 31.2-32.7-.1 26.4 19.2-10.2 31.2 26.9-19 26.1 19-9.7-30.8zm-59.7-59.9L178 120l-32.7-.1-10-31.2-10.3 31.1H92.2l26.4 19.2-10.1 31.2 26.8-19 26.1 19zm-60-60L118 60l-32.7-.1-10-31.2L65 59.8H32.2L58.6 79l-10.1 31.2 26.8-19 26.2 19zm-60-60L58 0 25.2-.1l-10-31.2L4.8-.2h-32.7L-1.4 19l-10.1 31.2 26.8-19 26.1 19z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-be" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="#000001" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#ffd90c" d="M170.7 0h170.6v512H170.7z"/>
|
||||||
|
<path fill="#f31830" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 302 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-bg" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#00966e" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#d62612" d="M0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" id="flag-icons-by" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="by-a">
|
||||||
|
<path d="M0 0h200v608h8v284l-8 8H0Z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<path fill="#ce1720" d="M0 0h512v512H0Z"/>
|
||||||
|
<g fill="#fff" clip-path="url(#by-a)" transform="matrix(.5625 0 0 .56889 5 0)">
|
||||||
|
<g id="by-c">
|
||||||
|
<path id="by-b" d="M36 0v14h-9v14H16v16H8v13H-8V24H8V6H-8V0Zm26 77v15h-8v12h-8V92h-8V77h-8V57h8V42h8V30h8v12h8v15h8v20Zm-17-1h10V58H45ZM19 183h8v-18h-8zm54 0h8v-18h-8ZM-8 305H6v13h6v16h9v15h12v-15h9v-16h8v-13H38v-15h21v10h13v17h11v19h-8v14h-7v13h-6v14h-9v12h-7v11h-9v14H24v-15h-9v-14H8v-9H-8v-23H8v-20H-8Z"/>
|
||||||
|
<use xlink:href="#by-b" transform="matrix(-1 0 0 1 200 0)"/>
|
||||||
|
<path d="M96 0v32h8V0h32v14h-8v14h-12v16h-8v13H92V44h-8V28H72V14h-8V0Zm-2 274v-11h-6v-13h-7v-14h-8v-14h-8v-10h-9v-14H44v14h-9v10h-7v14h-8v14h-6v13H8v17H-8v-44H8v-20H-8v-33H8v14h10v14h10v-14h10v-14h8v-18h-8v-14H28v-14H18v14H8v14H-8v-41H8v-19H-8V77H8v13h8v16h11v13h9v15h7v12h14v-12h7v-15h9v-13h11V90h8V77h16v13h8v16h11v13h9v15h7v12h14v-12h7v-15h9v-13h11V90h8V77h16v28h-16v19h16v41h-16v-14h-10v-14h-10v14h-10v14h-8v18h8v14h10v14h10v-14h10v-14h16v33h-16v20h16v44h-16v-17h-6v-13h-6v-14h-8v-14h-7v-10h-9v-14h-12v14h-9v10h-8v14h-8v14h-7v13h-6v11zm2-167v27h8v-27zm-4 58v-14H82v-14H72v14H62v14h-8v18h8v14h10v14h10v-14h10v-14h16v14h10v14h10v-14h10v-14h8v-18h-8v-14h-10v-14h-10v14h-10v14zm4 46v27h8v-27z"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#by-c" transform="matrix(1 0 0 -1 0 900)"/>
|
||||||
|
<path d="M-8 408H8v14h7v8h8v14h7v12h-7v14h-8v8H8v14H-8Zm216 0v84h-16v-14h-7v-8h-8v-14h-7v-12h7v-14h8v-8h7v-14ZM62 459h8v-18h-8zm76 0v-18h-8v18zm-42-59h8v-18h-8zm0 100v18h8v-18Zm-50-75h14v-11h10v-10h5v-10h6v-14h8v-14h4v-13h14v13h4v14h8v14h6v10h5v10h10v11h14v50h-14v11h-10v10h-5v10h-6v14h-8v14h-4v13H93v-13h-4v-14h-8v-14h-6v-10h-5v-10H60v-11H46Zm50 9v-15h-8v-10h-8v25h8v9h5v14h-5v9h-8v25h8v-10h8v-15h8v15h8v10h8v-25h-8v-9h-5v-14h5v-9h8v-25h-8v10h-8v15z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#007c30" d="M117.3 341.3h395V512h-395z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ch" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="red" d="M0 0h512v512H0z"/>
|
||||||
|
<g fill="#fff">
|
||||||
|
<path d="M96 208h320v96H96z"/>
|
||||||
|
<path d="M208 96h96v320h-96z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 288 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-cz" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 0h512v256H0z"/>
|
||||||
|
<path fill="#d7141a" d="M0 256h512v256H0z"/>
|
||||||
|
<path fill="#11457e" d="M300 256 0 56v400z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 226 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-de" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fc0" d="M0 341.3h512V512H0z"/>
|
||||||
|
<path fill="#000001" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="red" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 229 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-dk" viewBox="0 0 512 512">
|
||||||
|
<path fill="#c8102e" d="M0 0h512.1v512H0z"/>
|
||||||
|
<path fill="#fff" d="M144 0h73.1v512H144z"/>
|
||||||
|
<path fill="#fff" d="M0 219.4h512.1v73.2H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 232 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ee" viewBox="0 0 512 512">
|
||||||
|
<path fill="#1791ff" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#000001" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#fff" d="M0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,547 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-es" viewBox="0 0 512 512">
|
||||||
|
<path fill="#AA151B" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#F1BF00" d="M0 128h512v256H0z"/>
|
||||||
|
<path fill="#ad1519" d="M171.7 227.6s-.5 0-.8-.2l-1.1-1-.7-.5-.7-.9s-.7-1.2-.4-2q.7-1.3 1.5-1.6c.5-.3 1.6-.6 1.6-.6l1.2-.5 1.3-.3.6-.3.9-.1 1.1-.3 1.7.1h5.1l3.6 1.2c.6.1 1.9.3 2.4.6q.9.6 1.3 1.1l.4 1.1v1.1l-.5.9-.6 1-.8.7s-.6.5-1.1.5-5.1-.9-8.2-.9-7.8.9-7.8.9"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M171.7 227.6s-.5 0-.8-.2l-1.1-1-.7-.5-.7-.9s-.7-1.2-.4-2q.7-1.3 1.5-1.6c.5-.3 1.6-.6 1.6-.6l1.2-.5 1.3-.3.6-.3.9-.1 1.1-.3 1.7.1h5.1l3.6 1.2c.6.1 1.9.3 2.4.6q.9.6 1.3 1.1l.4 1.1v1.1l-.5.9-.6 1-.8.7s-.6.5-1.1.5-5.1-.9-8.2-.9-7.8.9-7.8.9z"/>
|
||||||
|
<path fill="#c8b100" d="M178.2 220.9q.1-2.4 1.4-2.6 1.3.2 1.4 2.6-.1 2.3-1.4 2.5-1.3-.2-1.4-2.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M178.2 220.9q.1-2.4 1.4-2.6 1.3.2 1.4 2.6-.1 2.3-1.4 2.5-1.3-.2-1.4-2.5z"/>
|
||||||
|
<path fill="#c8b100" d="M179 220.9c0-1.3.3-2.4.6-2.4q.7.2.7 2.4t-.7 2.3q-.5-.2-.6-2.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179 220.9c0-1.3.3-2.4.6-2.4q.7.2.7 2.4t-.7 2.3q-.5-.2-.6-2.3z"/>
|
||||||
|
<path fill="#c8b100" d="M178.7 218.2q0-.9.9-1 1.2 0 1 1c-.2 1-.5.9-1 .9a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="#c8b100" d="M180.3 217.8v.6h-1.5v-.6h.5v-1.3h-.7v-.6h.7v-.6h.6v.6h.6v.6h-.6v1.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M180.3 217.8v.6h-1.5v-.6h.5v-1.3h-.7v-.6h.7v-.6h.6v.6h.6v.6h-.6v1.3h.4"/>
|
||||||
|
<path fill="#c8b100" d="M181 217.8v.6h-2.7v-.6h1v-1.3h-.7v-.6h.7v-.6h.6v.6h.6v.6h-.6v1.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M181 217.8v.6h-2.7v-.6h1v-1.3h-.7v-.6h.7v-.6h.6v.6h.6v.6h-.6v1.3h1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179.9 217.3a1 1 0 0 1 .6.9q0 .8-.9.9-1.2 0-1-1 .1-.6.8-.8"/>
|
||||||
|
<path fill="#c8b100" d="M179.6 227.4h-5v-1.2l-.3-1.2-.2-1.6c-1.4-1.8-2.6-3-3-2.7q0-.6.4-.8c1.2-.7 3.7 1 5.6 3.9l.5.7h4l.5-.7c1.9-2.9 4.4-4.6 5.6-3.9l.5.8c-.5-.3-1.7.9-3 2.7l-.3 1.6-.2 1.2-.1 1.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179.6 227.4h-5v-1.2l-.3-1.2-.2-1.6c-1.4-1.8-2.6-3-3-2.7q0-.6.4-.8c1.2-.7 3.7 1 5.6 3.9l.5.7h4l.5-.7c1.9-2.9 4.4-4.6 5.6-3.9l.5.8c-.5-.3-1.7.9-3 2.7l-.3 1.6-.2 1.2-.1 1.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M171.3 220.6c1-.5 3 1.2 4.8 3.8m11.9-3.8c-1-.5-3.1 1.2-4.9 3.8"/>
|
||||||
|
<path fill="#c8b100" d="m172.3 229.6-.6-1q3.1-1 7.9-1c3 0 5.9.3 7.9 1l-.6.9-.3.8q-2.7-.9-7-.8c-2.9 0-5.6.3-7 .8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m172.3 229.6-.6-1q3.1-1 7.9-1c3 0 5.9.3 7.9 1l-.6.9-.3.8q-2.7-.9-7-.8c-2.9 0-5.6.3-7 .8l-.3-.7"/>
|
||||||
|
<path fill="#c8b100" d="M179.6 232.2a27 27 0 0 0 6.2-.7q1.1-.3 1-.8 0-.3-.3-.4a26 26 0 0 0-7-.9c-2.6 0-5.3.4-6.8.9q-.3 0-.4.4 0 .5 1 .8c1 .3 3.8.7 6.3.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179.6 232.2a27 27 0 0 0 6.2-.7q1.1-.3 1-.8 0-.3-.3-.4a26 26 0 0 0-7-.9c-2.6 0-5.3.4-6.8.9q-.3 0-.4.4 0 .5 1 .8c1 .3 3.8.7 6.3.7z"/>
|
||||||
|
<path fill="#c8b100" d="m187.6 227.4-.6-.5s-.6.3-1.3.2-1-1-1-1-.8.7-1.5.6-1-.6-1-.6-.8.5-1.4.5c-.7 0-1.3-.9-1.3-.9s-.6.9-1.2 1-1.2-.6-1.2-.6-.3.6-1 .7c-.9.1-1.6-.6-1.6-.6s-.5.7-1 1c-.6.1-1.3-.4-1.3-.4l-.2.5-.3.2.2.4a33 33 0 0 1 15.5.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m187.6 227.4-.6-.5s-.6.3-1.3.2-1-1-1-1-.8.7-1.5.6-1-.6-1-.6-.8.5-1.4.5c-.7 0-1.3-.9-1.3-.9s-.6.9-1.2 1-1.2-.6-1.2-.6-.3.6-1 .7c-.9.1-1.6-.6-1.6-.6s-.5.7-1 1c-.6.1-1.3-.4-1.3-.4l-.2.5-.3.2.2.4a33 33 0 0 1 15.5.1z"/>
|
||||||
|
<path fill="#c8b100" d="M179.6 224.8h.3a1 1 0 0 0 1 1.5q.9 0 1.2-.8l.1-.4v.5q.2.8 1.2.9a1 1 0 0 0 1.1-1.1v-.1l.4-.4.2.4-.1.5q.1 1 1 1 .7 0 1-.4l.2-.3v.4q0 .5.5.7l1-.4.8-.8v.5s-.5.8-1 1.1l-1 .3q-.4-.2-.7-.7l-.8.3q-1 0-1.4-1a2 2 0 0 1-1.2.6 2 2 0 0 1-1.3-.6l-1.1.4q-1 0-1.4-.7-.5.7-1.4.7l-1-.4q-.6.5-1.4.6a2 2 0 0 1-1.2-.5q-.4.8-1.4.8-.5 0-.8-.2-.2.5-.7.7l-1-.3-1-1.1v-.5l.9.8q.8.5.9.4.5-.1.5-.7v-.4l.2.3q.3.5 1 .5.9-.1 1-1v-.6l.1-.4.4.4q.1 1.1 1 1.2 1.1 0 1.2-1v-.3l.2.3q.3.8 1.1.8 1.1-.1 1.2-1.1l-.1-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179.6 224.8h.3a1 1 0 0 0 1 1.5q.9 0 1.2-.8l.1-.4v.5q.2.8 1.2.9a1 1 0 0 0 1.1-1.1v-.1l.4-.4.2.4-.1.5q.1 1 1 1 .7 0 1-.4l.2-.3v.4q0 .5.5.7l1-.4.8-.8v.5s-.5.8-1 1.1l-1 .3q-.4-.2-.7-.7l-.8.3q-1 0-1.4-1a2 2 0 0 1-1.2.6 2 2 0 0 1-1.3-.6l-1.1.4q-1 0-1.4-.7-.5.7-1.4.7l-1-.4q-.6.5-1.4.6a2 2 0 0 1-1.2-.5q-.4.8-1.4.8-.5 0-.8-.2-.2.5-.7.7l-1-.3-1-1.1v-.5l.9.8q.8.5.9.4.5-.1.5-.7v-.4l.2.3q.3.5 1 .5.9-.1 1-1v-.6l.1-.4.4.4q.1 1.1 1 1.2 1.1 0 1.2-1v-.3l.2.3q.3.8 1.1.8 1.1-.1 1.2-1.1l-.1-.4z"/>
|
||||||
|
<path fill="#c8b100" d="M179.6 227.6q-4.8 0-7.9 1l-.3-.2q0-.3.2-.4 3.1-1 8-1c4.9 0 6 .4 8 1l.2.3q0 .3-.3.2-3.1-1-8-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M179.6 227.6q-4.8 0-7.9 1l-.3-.2q0-.3.2-.4 3.1-1 8-1c4.9 0 6 .4 8 1l.2.3q0 .3-.3.2-3.1-1-8-1z"/>
|
||||||
|
<path fill="#fff" d="M176.6 228.7q0-.5.5-.5.3 0 .4.5 0 .3-.5.4-.6 0-.4-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M176.6 228.7q0-.5.5-.5.3 0 .4.5 0 .3-.5.4-.6 0-.4-.4z"/>
|
||||||
|
<path fill="#ad1519" d="M179.6 228.8h-1l-.3-.3q0-.2.3-.3h2a.3.3 0 0 1 .4.3.3.3 0 0 1-.4.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179.6 228.8h-1l-.3-.3q0-.2.3-.3h2a.3.3 0 0 1 .4.3.3.3 0 0 1-.4.3h-1"/>
|
||||||
|
<path fill="#058e6e" d="M174.7 229.2h-.7q-.3.1-.4-.2l.2-.3.7-.1.8-.2q.3 0 .4.3 0 .3-.3.4h-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M174.7 229.2h-.7q-.3.1-.4-.2l.2-.3.7-.1.8-.2q.3 0 .4.3 0 .3-.3.4h-.8"/>
|
||||||
|
<path fill="#ad1519" d="m171.8 229.7.3-.5.7.1-.4.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m171.8 229.7.3-.5.7.1-.4.6-.6-.2"/>
|
||||||
|
<path fill="#fff" d="M181.7 228.7q0-.5.4-.5t.5.5q0 .3-.5.4-.6 0-.4-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M181.7 228.7q0-.5.4-.5t.5.5q0 .3-.5.4-.6 0-.4-.4z"/>
|
||||||
|
<path fill="#058e6e" d="M184.5 229.2h.8q.2.1.3-.2l-.2-.3-.8-.1-.7-.2q-.3 0-.4.3 0 .3.3.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M184.5 229.2h.8q.2.1.3-.2l-.2-.3-.8-.1-.7-.2q-.3 0-.4.3 0 .3.3.4h.7"/>
|
||||||
|
<path fill="#ad1519" d="m187.3 229.7-.2-.5h-.7l.3.6h.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m187.3 229.7-.2-.5h-.7l.3.6h.7"/>
|
||||||
|
<path fill="#ad1519" d="M179.6 231.6q-3.9 0-6.5-.7a27 27 0 0 1 6.5-.7q3.8 0 6.5.7-2.7.7-6.5.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M179.6 231.6q-3.9 0-6.5-.7a27 27 0 0 1 6.5-.7q3.8 0 6.5.7-2.7.7-6.5.7z"/>
|
||||||
|
<path fill="#c8b100" d="M187.4 226.2v-.4q-.3 0-.5.2 0 .3.2.5.2 0 .3-.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M187.4 226.2v-.4q-.3 0-.5.2 0 .3.2.5.2 0 .3-.3z"/>
|
||||||
|
<path fill="#c8b100" d="M182.5 225.2q0-.3-.2-.4-.3 0-.3.4t.2.4.3-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M182.5 225.2q0-.3-.2-.4-.3 0-.3.4t.2.4.3-.4z"/>
|
||||||
|
<path fill="#c8b100" d="M176.7 225.2q0-.3.2-.4.3 0 .3.4t-.2.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M176.7 225.2q0-.3.2-.4.3 0 .3.4t-.2.4z"/>
|
||||||
|
<path fill="#c8b100" d="M171.8 226.2q-.1-.3.1-.4t.4.2q0 .3-.2.5-.2 0-.3-.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M171.8 226.2q-.1-.3.1-.4t.4.2q0 .3-.2.5-.2 0-.3-.3z"/>
|
||||||
|
<path fill="#c8b100" d="m179.6 222.4-.9.5.7 1.4.2.2.2-.2.7-1.4-1-.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m179.6 222.4-.9.5.7 1.4.2.2.2-.2.7-1.4-1-.5"/>
|
||||||
|
<path fill="#c8b100" d="m177.7 224.5.4.6 1.3-.4.2-.2-.2-.2-1.3-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m177.7 224.5.4.6 1.3-.4.2-.2-.2-.2-1.3-.4-.4.6"/>
|
||||||
|
<path fill="#c8b100" d="m181.5 224.5-.4.6-1.3-.4-.2-.2.1-.2 1.4-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m181.5 224.5-.4.6-1.3-.4-.2-.2.1-.2 1.4-.4.4.6"/>
|
||||||
|
<path fill="#c8b100" d="m173.9 223-.7.6.9 1.2.2.1.2-.2.3-1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m173.9 223-.7.6.9 1.2.2.1.2-.2.3-1.4-1-.3"/>
|
||||||
|
<path fill="#c8b100" d="m172.4 225.3.5.5 1.3-.7v-.4h-1.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m172.4 225.3.5.5 1.3-.7v-.4h-1.5l-.3.6"/>
|
||||||
|
<path fill="#c8b100" d="m176.2 224.6-.3.6-1.4-.1-.2-.2.1-.2 1.3-.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m176.2 224.6-.3.6-1.4-.1-.2-.2.1-.2 1.3-.7.5.6"/>
|
||||||
|
<path fill="#c8b100" d="M171 225.5v.7l-1.5.1h-.2v-.3l1.1-1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M171 225.5v.7l-1.5.1h-.2v-.3l1.1-1 .6.5"/>
|
||||||
|
<path fill="#c8b100" d="M173.8 225q0-.6.5-.6t.5.5l-.5.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M173.8 225q0-.6.5-.6t.5.5l-.5.5z"/>
|
||||||
|
<path fill="#c8b100" d="m185.3 223 .7.6-.9 1.2-.2.1-.2-.2-.3-1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m185.3 223 .7.6-.9 1.2-.2.1-.2-.2-.3-1.4 1-.3"/>
|
||||||
|
<path fill="#c8b100" d="m186.8 225.3-.6.5-1.2-.7-.1-.2.2-.2h1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m186.8 225.3-.6.5-1.2-.7-.1-.2.2-.2h1.4l.3.6"/>
|
||||||
|
<path fill="#c8b100" d="m183 224.6.3.6 1.4-.1.2-.2-.1-.2-1.3-.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m183 224.6.3.6 1.4-.1.2-.2-.1-.2-1.3-.7-.5.6"/>
|
||||||
|
<path fill="#c8b100" d="M188 225.5v.7l1.5.1h.2v-.3l-1.1-1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M188 225.5v.7l1.5.1h.2v-.3l-1.1-1-.6.5"/>
|
||||||
|
<path fill="#c8b100" d="M179 224.5a.5.5 0 0 1 .6-.5q.5 0 .5.5l-.5.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M179 224.5a.5.5 0 0 1 .6-.5q.5 0 .5.5l-.5.4z"/>
|
||||||
|
<path fill="#c8b100" d="M184.4 225a.5.5 0 0 1 .5-.6l.5.5-.5.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M184.4 225a.5.5 0 0 1 .5-.6l.5.5-.5.5z"/>
|
||||||
|
<path fill="#c8b100" d="m169.1 226.3-.7-.8-.7-.3s.3-.3.6-.3l.5.2v-.2s.3 0 .4.4v1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m169.1 226.3-.7-.8-.7-.3s.3-.3.6-.3l.5.2v-.2s.3 0 .4.4v1z"/>
|
||||||
|
<path fill="#c8b100" d="m169.1 226 .6.1q.3.3 0 .5-.4.2-.4 0-.3-.3-.2-.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m169.1 226 .6.1q.3.3 0 .5-.4.2-.4 0-.3-.3-.2-.5z"/>
|
||||||
|
<path fill="#c8b100" d="m189.9 226.3.7-.8.7-.3s-.3-.3-.6-.3l-.5.2v-.2s-.3 0-.4.4v.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m189.9 226.3.7-.8.7-.3s-.3-.3-.6-.3l-.5.2v-.2s-.3 0-.4.4v.7z"/>
|
||||||
|
<path fill="#c8b100" d="m189.9 226-.5.1q-.3.3-.2.5h.6q.3-.3.1-.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m189.9 226-.5.1q-.3.3-.2.5h.6q.3-.3.1-.5z"/>
|
||||||
|
<path fill="#c8b100" d="M168.2 238h22.9v-6h-22.9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M168.2 238h22.9v-6h-22.9z"/>
|
||||||
|
<path fill="#c8b100" d="m170.6 242 .5-.1h17.5q-1-.3-1-1.3.1-1 1-1.4l-.5.1h-17.5q1 .3 1 1.3t-1 1.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m170.6 242 .5-.1h17.5q-1-.3-1-1.3.1-1 1-1.4l-.5.1h-17.5q1 .3 1 1.3t-1 1.3z"/>
|
||||||
|
<path fill="#c8b100" d="M171 241.9h17.2q.9 0 1 .8-.1.7-1 .8H171q-1-.1-1.1-.8.1-.8 1-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M171 241.9h17.2q.9 0 1 .8-.1.7-1 .8H171q-1-.1-1.1-.8.1-.8 1-.8z"/>
|
||||||
|
<path fill="#c8b100" d="M171 238h17.2q.9 0 1 .6-.1.7-1 .7H171q-1 0-1-.7t1-.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M171 238h17.2q.9 0 1 .6-.1.7-1 .7H171q-1 0-1-.7t1-.7z"/>
|
||||||
|
<path fill="#005bbf" d="M195.6 338.6a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8q-2.5 0-4 .8a9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8q-1.6 1-4 1v2.4a9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.9 9 9 0 0 0 4 .9 9 9 0 0 0 4-.9 9 9 0 0 1 4-.9q2.4 0 4 .9a9 9 0 0 0 4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M195.6 338.6a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8q-2.5 0-4 .8a9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8q-1.6 1-4 1v2.4a9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.9 9 9 0 0 0 4 .9 9 9 0 0 0 4-.9 9 9 0 0 1 4-.9q2.4 0 4 .9a9 9 0 0 0 4 .9z"/>
|
||||||
|
<path fill="#ccc" d="M195.6 341a9 9 0 0 1-4-.8 9 9 0 0 0-4-.8q-2.5 0-4 .8a9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.3 0-3.9.9a9 9 0 0 1-4 .9v2.4a9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8 9 9 0 0 0 4 1 9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 4 .9 9 9 0 0 0 4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M195.6 341a9 9 0 0 1-4-.8 9 9 0 0 0-4-.8q-2.5 0-4 .8a9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.3 0-3.9.9a9 9 0 0 1-4 .9v2.4a9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8 9 9 0 0 0 4 1 9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 4 .9 9 9 0 0 0 4 .9V341"/>
|
||||||
|
<path fill="#005bbf" d="M195.6 343.6a9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1v2.4q2.4 0 4-.9a9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8 9 9 0 0 0 8 0 9 9 0 0 1 4-.8q2.4 0 4 .8 1.5 1 4 1v-2.6"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M195.6 343.6a9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1v2.4q2.4 0 4-.9a9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8 9 9 0 0 0 8 0 9 9 0 0 1 4-.8q2.4 0 4 .8 1.5 1 4 1v-2.6"/>
|
||||||
|
<path fill="#ccc" d="M195.6 348.5a9 9 0 0 1-4-1 9 9 0 0 0-4-.7 9 9 0 0 0-4 .8 9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.7 9 9 0 0 0-3.9.8 9 9 0 0 1-4 .9V346a9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8q1.5 1 4 1a9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 4 .8q1.5 1 4 1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M195.6 348.5a9 9 0 0 1-4-1 9 9 0 0 0-4-.7 9 9 0 0 0-4 .8 9 9 0 0 1-4 .9 9 9 0 0 1-4-1 9 9 0 0 0-4-.7 9 9 0 0 0-3.9.8 9 9 0 0 1-4 .9V346a9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 3.9.8q1.5 1 4 1a9 9 0 0 0 4-1 9 9 0 0 1 4-.8 9 9 0 0 1 4 .8q1.5 1 4 1v2.4"/>
|
||||||
|
<path fill="#005bbf" d="M195.6 351a9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .8 9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8q-1.6 1-4 1v-2.5q2.4 0 4-1a9 9 0 0 1 4-.7 9 9 0 0 1 3.9.8 9 9 0 0 0 4 .9 9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 4 .8 9 9 0 0 0 4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M195.6 351a9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .8 9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8q-1.6 1-4 1v-2.5q2.4 0 4-1a9 9 0 0 1 4-.7 9 9 0 0 1 3.9.8 9 9 0 0 0 4 .9 9 9 0 0 0 4-.9 9 9 0 0 1 4-.8 9 9 0 0 1 4 .8 9 9 0 0 0 4 .9z"/>
|
||||||
|
<path fill="#c8b100" d="m170.6 328.5.2.6c0 1.5-1.3 2.7-3 2.7h23.6c-1.6 0-2.9-1.2-2.9-2.7l.1-.6h-.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m170.6 328.5.2.6c0 1.5-1.3 2.7-3 2.7h23.6c-1.6 0-2.9-1.2-2.9-2.7l.1-.6h-.5z"/>
|
||||||
|
<path fill="#c8b100" d="M171 327h17.2q.9 0 1 .7-.1.8-1 .8H171q-1 0-1.1-.8.1-.7 1-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M171 327h17.2q.9 0 1 .7-.1.8-1 .8H171q-1 0-1.1-.8.1-.7 1-.8z"/>
|
||||||
|
<path fill="#c8b100" d="M168 337.8h23.3v-6H168z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M168 337.8h23.3v-6H168z"/>
|
||||||
|
<path fill="#ad1519" d="M166 305.8c-2.2 1.3-3.8 2.7-3.5 3.4q.1.8 1.9 1.8c1.6 1.1 2.6 3.2 1.8 4.1a6 6 0 0 0-.1-9.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M166 305.8c-2.2 1.3-3.8 2.7-3.5 3.4q.1.8 1.9 1.8c1.6 1.1 2.6 3.2 1.8 4.1a6 6 0 0 0-.1-9.3z"/>
|
||||||
|
<path fill="#ccc" d="M171.3 326h16.6v-81.6h-16.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M183.1 244.5V326m2-81.4V326m-13.8 0h16.6v-81.5h-16.6z"/>
|
||||||
|
<path fill="#ad1519" d="M205 275a53 53 0 0 0-17-3 52 52 0 0 0-8 .8c-9.9 1.7-17.5 5.6-16.9 8.9v.2l-3.7-8.7c-.7-3.6 7.7-8 18.8-9.8a57 57 0 0 1 9.8-.8c7 0 13.2.9 16.9 2.3v10"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M205 275a53 53 0 0 0-17-3 52 52 0 0 0-8 .8c-9.9 1.7-17.5 5.6-16.9 8.9v.2l-3.7-8.7c-.7-3.6 7.7-8 18.8-9.8a57 57 0 0 1 9.8-.8c7 0 13.2.9 16.9 2.3v10"/>
|
||||||
|
<path fill="#ad1519" d="M171.3 285.1c-4.7-.3-7.8-1.5-8.2-3.5-.3-1.5 1.3-3.2 4-4.7l4.2.3v8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M171.3 285.1c-4.7-.3-7.8-1.5-8.2-3.5-.3-1.5 1.3-3.2 4-4.7l4.2.3v8"/>
|
||||||
|
<path fill="#ad1519" d="M188 279q4.4.5 6 2l.2.1c.5 1-2 3.3-6.3 5.8v-8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M188 279q4.4.5 6 2l.2.1c.5 1-2 3.3-6.3 5.8v-8"/>
|
||||||
|
<path fill="#ad1519" d="M160.9 300.9c-.4-1.3 4-4 10.4-6.3 3-1 5.3-2.1 8.3-3.4 8.9-4 15.4-8.4 14.6-10l-.1-.2c.5.4 1.2 8.4 1.2 8.4.8 1.5-5.2 6-13.3 9.8-2.6 1.3-8.1 3.3-10.7 4.2-4.7 1.6-9.3 4.7-8.9 5.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M160.9 300.9c-.4-1.3 4-4 10.4-6.3 3-1 5.3-2.1 8.3-3.4 8.9-4 15.4-8.4 14.6-10l-.1-.2c.5.4 1.2 8.4 1.2 8.4.8 1.5-5.2 6-13.3 9.8-2.6 1.3-8.1 3.3-10.7 4.2-4.7 1.6-9.3 4.7-8.9 5.8z"/>
|
||||||
|
<path fill="#c8b100" d="M170.1 271c2-.7 3.4-1.6 2.7-3.2q-.6-1.5-3-.7l-2.8 1 2.5 6.2.9-.3.8-.3zm-1.2-3 .7-.2q1-.4 1.5.8.5.8-.5 1.6l-.7.3zm7.7-2.6-.8.3h-1l1.5 6.5 4.5-.9-.2-.4v-.4l-2.7.7zm9 5.6 2.9-6.8h-1.1l-2 5-2.4-4.7-1 .1h-1.1l3.7 6.5.5-.1zm9.4-5 .5-.9a4 4 0 0 0-1.9-.6q-2.7-.1-3 1.7c-.2 2.3 3.4 2.1 3.3 3.7q-.3 1-1.6.8-1.3 0-1.5-1.2h-.2l-.5 1.2q.8.4 1.9.6c1.8.2 3.3-.6 3.4-1.8.2-2.2-3.4-2.3-3.3-3.6q0-1 1.4-.8 1 0 1.3 1z"/>
|
||||||
|
<path fill="#ad1519" d="M332.4 225.7s-.8.8-1.3 1c-.6 0-1.3-.6-1.3-.6s-.5.5-1.1.7-1.4-.7-1.4-.7-.6.8-1.2 1-1.1-.2-1.1-.2-.2.4-.7.6h-.5l-.6-.5-.7-.7-.6-.2-.3-1.1-.1-.6q0-1.1 2.4-1.8 1.2-.2 2 0a6 6 0 0 1 3.3-.8 6 6 0 0 1 3.2.7 6 6 0 0 1 3-.7q2.3 0 3.3.8a4 4 0 0 1 2.1 0q2.4.7 2.4 1.8v.6l-.4 1-.6.3-.7.8-.6.3s-.3.2-.5.1q-.7-.4-.7-.6c0-.2-.6.4-1.2.2s-1-1-1-1-.9.8-1.5.7q-1-.5-1.1-.7c-.1-.2-.7.6-1.2.5s-1.4-.9-1.4-.9"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.4 225.7s-.8.8-1.3 1c-.6 0-1.3-.6-1.3-.6s-.5.5-1.1.7-1.4-.7-1.4-.7-.6.8-1.2 1-1.1-.2-1.1-.2-.2.4-.7.6h-.5l-.6-.5-.7-.7-.6-.2-.3-1.1-.1-.6q0-1.1 2.4-1.8 1.2-.2 2 0a6 6 0 0 1 3.3-.8 6 6 0 0 1 3.2.7 6 6 0 0 1 3-.7q2.3 0 3.3.8a4 4 0 0 1 2.1 0q2.4.7 2.4 1.8v.6l-.4 1-.6.3-.7.8-.6.3s-.3.2-.5.1q-.7-.4-.7-.6c0-.2-.6.4-1.2.2s-1-1-1-1-.9.8-1.5.7q-1-.5-1.1-.7c-.1-.2-.7.6-1.2.5s-1.4-.9-1.4-.9z"/>
|
||||||
|
<path fill="#c8b100" d="M331 221.4q.1-1.8 1.3-2 1.3.2 1.4 2c.1 1.8-.6 2-1.4 2q-1.3-.2-1.4-2"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M331 221.4q.1-1.8 1.3-2 1.3.2 1.4 2c.1 1.8-.6 2-1.4 2q-1.3-.2-1.4-2z"/>
|
||||||
|
<path fill="#c8b100" d="M331.7 221.4c0-1 .3-1.9.7-1.9s.6.9.6 1.9q0 1.7-.7 1.8-.5-.2-.6-1.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M331.7 221.4c0-1 .3-1.9.7-1.9s.6.9.6 1.9q0 1.7-.7 1.8-.5-.2-.6-1.8z"/>
|
||||||
|
<path fill="#c8b100" d="m325 229.6-.5-1q3-1 7.8-1t8 1l-.6.9-.4.8q-2.7-.9-7-.8c-2.8 0-5.6.3-7 .8l-.2-.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m325 229.6-.5-1q3-1 7.8-1t8 1l-.6.9-.4.8q-2.7-.9-7-.8c-2.8 0-5.6.3-7 .8l-.2-.7"/>
|
||||||
|
<path fill="#c8b100" d="M332.3 232.2c2.5 0 5.3-.4 6.3-.7q1-.3 1-.8 0-.3-.4-.4a26 26 0 0 0-6.9-.9 26 26 0 0 0-6.8.9q-.4 0-.4.4t1 .8c1 .3 3.7.7 6.2.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.3 232.2c2.5 0 5.3-.4 6.3-.7q1-.3 1-.8 0-.3-.4-.4a26 26 0 0 0-6.9-.9 26 26 0 0 0-6.8.9q-.4 0-.4.4t1 .8c1 .3 3.7.7 6.2.7z"/>
|
||||||
|
<path fill="#fff" d="m338.4 222.3.4-.5q.5 0 .5.5 0 .3-.5.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="m338.4 222.3.4-.5q.5 0 .5.5 0 .3-.5.4zm-.3-1.6.5-.4q.3 0 .4.4 0 .5-.4.5l-.5-.4zm-1.1-1 .4-.3q.5 0 .5.4t-.5.4l-.4-.4zm-1.5-.4q0-.3.5-.4l.4.4-.4.5q-.5 0-.5-.5zm-1.5 0 .5-.4q.5 0 .5.5t-.5.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linecap="round" stroke-width=".3" d="m343 225.3.2-1.1a3 3 0 0 0-3-3l-1.3.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m337.8 223.2.3-.9c0-1.2-1.3-2.1-2.7-2.1q-1 0-1.7.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="M343.4 224q0-.5.4-.5.6 0 .5.4-.1.6-.5.5t-.4-.5zm-.2-1.7q0-.3.5-.4.3 0 .4.4 0 .5-.5.4t-.4-.4zm-1-1.2.4-.5q.5 0 .5.5t-.5.4l-.5-.4zm-1.5-.7q.1-.3.5-.4.5 0 .5.4l-.5.5-.4-.5zm-1.4 0q0-.3.4-.4.5 0 .5.5t-.5.4q-.6-.2-.4-.4z"/>
|
||||||
|
<path fill="#c8b100" d="m340.3 227.4-.6-.5s-.6.3-1.3.2-1-1-1-1-.7.7-1.4.6-1.1-.6-1.1-.6-.7.5-1.4.5-1.2-.9-1.2-.9-.7.9-1.3 1-1.1-.6-1.1-.6-.3.6-1.1.7-1.5-.6-1.5-.6-.5.7-1 1c-.6.1-1.3-.4-1.3-.4l-.2.5-.4.2.2.4a33 33 0 0 1 15.5.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m340.3 227.4-.6-.5s-.6.3-1.3.2-1-1-1-1-.7.7-1.4.6-1.1-.6-1.1-.6-.7.5-1.4.5-1.2-.9-1.2-.9-.7.9-1.3 1-1.1-.6-1.1-.6-.3.6-1.1.7-1.5-.6-1.5-.6-.5.7-1 1c-.6.1-1.3-.4-1.3-.4l-.2.5-.4.2.2.4a33 33 0 0 1 15.5.1z"/>
|
||||||
|
<path fill="#fff" d="m325.3 222.3.5-.5.4.5-.4.4q-.5 0-.5-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="m325.3 222.3.5-.5.4.5-.4.4q-.5 0-.5-.4zm.3-1.6.4-.4.5.4-.5.5a.4.4 0 0 1-.4-.4zm1.1-1q0-.2.5-.3t.5.4q-.1.6-.5.4l-.5-.4zm1.5-.4q0-.3.4-.4.5 0 .5.4t-.5.5q-.4 0-.4-.5zm1.5 0 .4-.4.5.5q0 .3-.5.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linecap="round" stroke-width=".3" d="m321.7 225.3-.3-1.1a3 3 0 0 1 3-3q.7 0 1.3.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m326.9 223.2-.3-.9c0-1.2 1.2-2.1 2.7-2.1q1 0 1.6.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="M320.3 224q0-.5.5-.5t.5.4l-.5.5q-.5 0-.5-.5zm.2-1.7q0-.3.5-.4.6 0 .4.4t-.4.4zm1-1.2q.1-.5.5-.5l.5.5-.5.4-.4-.4zm1.5-.7.4-.4q.5 0 .5.4l-.5.5-.5-.5zm1.4 0q0-.3.5-.4l.4.5q0 .3-.4.4-.5 0-.5-.4z"/>
|
||||||
|
<path fill="#c8b100" d="M332.4 224.8h.2v.4q.1 1 1.1 1 .8 0 1.1-.7l.2-.4v.5q.2.8 1.1.9a1 1 0 0 0 1.2-1.1v-.1l.3-.4.2.4v.5q0 1 1 1a1 1 0 0 0 1-.4l.1-.3v.4q0 .5.5.7l1-.4.8-.8v.5s-.5.8-1 1.1l-1 .3q-.5-.2-.6-.7l-.8.3q-1.1 0-1.5-1a2 2 0 0 1-1.2.6 2 2 0 0 1-1.3-.6q-.5.4-1 .4a2 2 0 0 1-1.5-.7 2 2 0 0 1-2.4.3 2 2 0 0 1-1.3.6q-.8 0-1.2-.5-.4.8-1.5.8l-.8-.2q-.1.5-.7.7l-1-.3-1-1.1v-.5l1 .8q.7.5.8.4.5-.1.5-.7v-.4l.3.3q.3.5.9.5 1-.1 1-1v-.6l.2-.4.3.4q.1 1.1 1.1 1.2t1.2-1v-.3l.2.3q.3.8 1 .8a1.1 1.1 0 0 0 1.2-1.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.4 224.8h.2v.4q.1 1 1.1 1 .8 0 1.1-.7l.2-.4v.5q.2.8 1.1.9a1 1 0 0 0 1.2-1.1v-.1l.3-.4.2.4v.5q0 1 1 1a1 1 0 0 0 1-.4l.1-.3v.4q0 .5.5.7l1-.4.8-.8v.5s-.5.8-1 1.1l-1 .3q-.5-.2-.6-.7l-.8.3q-1.1 0-1.5-1a2 2 0 0 1-1.2.6 2 2 0 0 1-1.3-.6q-.5.4-1 .4a2 2 0 0 1-1.5-.7 2 2 0 0 1-2.4.3 2 2 0 0 1-1.3.6q-.8 0-1.2-.5-.4.8-1.5.8l-.8-.2q-.1.5-.7.7l-1-.3-1-1.1v-.5l1 .8q.7.5.8.4.5-.1.5-.7v-.4l.3.3q.3.5.9.5 1-.1 1-1v-.6l.2-.4.3.4q.1 1.1 1.1 1.2t1.2-1v-.3l.2.3q.3.8 1 .8a1.1 1.1 0 0 0 1.2-1.5z"/>
|
||||||
|
<path fill="#c8b100" d="M332.3 227.6q-4.6 0-7.8 1l-.4-.2.2-.4q3.2-1 8-1c4.8 0 6 .4 8 1q.3 0 .2.3t-.3.2q-3.1-1-7.9-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.3 227.6q-4.6 0-7.8 1l-.4-.2.2-.4q3.2-1 8-1c4.8 0 6 .4 8 1q.3 0 .2.3t-.3.2q-3.1-1-7.9-1z"/>
|
||||||
|
<path fill="#fff" d="M329.4 228.7q0-.5.4-.5t.5.5q-.1.3-.5.4-.5 0-.5-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M329.4 228.7q0-.5.4-.5t.5.5q-.1.3-.5.4-.5 0-.5-.4z"/>
|
||||||
|
<path fill="#ad1519" d="M332.4 228.8h-1q-.4 0-.4-.3t.4-.3h2l.3.3q0 .3-.3.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.4 228.8h-1q-.4 0-.4-.3t.4-.3h2l.3.3q0 .3-.3.3h-1"/>
|
||||||
|
<path fill="#058e6e" d="M327.4 229.2h-.7a.3.3 0 0 1-.4-.2l.3-.3.7-.1.7-.2q.3 0 .4.3 0 .3-.3.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M327.4 229.2h-.7a.3.3 0 0 1-.4-.2l.3-.3.7-.1.7-.2q.3 0 .4.3 0 .3-.3.4h-.7"/>
|
||||||
|
<path fill="#ad1519" d="m324.5 229.7.4-.5.6.1-.4.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m324.5 229.7.4-.5.6.1-.4.6-.6-.2"/>
|
||||||
|
<path fill="#fff" d="M334.4 228.7q0-.5.5-.5t.4.5q0 .3-.4.4-.5 0-.5-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M334.4 228.7q0-.5.5-.5t.4.5q0 .3-.4.4-.5 0-.5-.4z"/>
|
||||||
|
<path fill="#058e6e" d="M337.3 229.2h.7q.3.1.4-.2l-.3-.3-.7-.1-.7-.2q-.3 0-.4.3 0 .3.3.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M337.3 229.2h.7q.3.1.4-.2l-.3-.3-.7-.1-.7-.2q-.3 0-.4.3 0 .3.3.4h.7"/>
|
||||||
|
<path fill="#ad1519" d="m340.1 229.7-.3-.5h-.7l.4.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m340.1 229.7-.3-.5h-.7l.4.6h.6"/>
|
||||||
|
<path fill="#ad1519" d="M332.3 231.6a27 27 0 0 1-6.4-.7 28 28 0 0 1 13 0 27 27 0 0 1-6.6.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M332.3 231.6a27 27 0 0 1-6.4-.7 28 28 0 0 1 13 0 27 27 0 0 1-6.6.7z"/>
|
||||||
|
<path fill="#c8b100" d="m340.2 226.2-.1-.4q-.3 0-.4.2l.1.5q.3 0 .4-.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m340.2 226.2-.1-.4q-.3 0-.4.2l.1.5q.3 0 .4-.3z"/>
|
||||||
|
<path fill="#c8b100" d="M335.2 225.2q0-.3-.2-.4-.2 0-.3.4t.2.4.3-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M335.2 225.2q0-.3-.2-.4-.2 0-.3.4t.2.4.3-.4z"/>
|
||||||
|
<path fill="#c8b100" d="M329.5 225.2q0-.3.2-.4.2 0 .3.4t-.2.4-.3-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M329.5 225.2q0-.3.2-.4.2 0 .3.4t-.2.4-.3-.4z"/>
|
||||||
|
<path fill="#c8b100" d="m324.5 226.2.1-.4q.3 0 .4.2l-.1.5q-.3 0-.4-.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m324.5 226.2.1-.4q.3 0 .4.2l-.1.5q-.3 0-.4-.3z"/>
|
||||||
|
<path fill="#c8b100" d="m332.3 222.4-.8.5.6 1.4.2.2.3-.2.6-1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m332.3 222.4-.8.5.6 1.4.2.2.3-.2.6-1.4-.9-.5"/>
|
||||||
|
<path fill="#c8b100" d="m330.4 224.5.4.6 1.4-.4.1-.2-.1-.2-1.4-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m330.4 224.5.4.6 1.4-.4.1-.2-.1-.2-1.4-.4-.4.6"/>
|
||||||
|
<path fill="#c8b100" d="m334.3 224.5-.4.6-1.4-.4-.1-.2.1-.2 1.4-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m334.3 224.5-.4.6-1.4-.4-.1-.2.1-.2 1.4-.4.4.6"/>
|
||||||
|
<path fill="#c8b100" d="m326.6 223-.7.6 1 1.2.2.1.1-.2.3-1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m326.6 223-.7.6 1 1.2.2.1.1-.2.3-1.4-.9-.3"/>
|
||||||
|
<path fill="#c8b100" d="m325.2 225.3.5.5 1.2-.7.1-.2-.1-.2h-1.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m325.2 225.3.5.5 1.2-.7.1-.2-.1-.2h-1.5l-.2.6"/>
|
||||||
|
<path fill="#c8b100" d="m329 224.6-.3.6-1.4-.1-.2-.2v-.2l1.3-.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m329 224.6-.3.6-1.4-.1-.2-.2v-.2l1.3-.7.6.6"/>
|
||||||
|
<path fill="#c8b100" d="m323.8 225.5-.1.7-1.5.1h-.2v-.3l1.2-1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m323.8 225.5-.1.7-1.5.1h-.2v-.3l1.2-1 .6.5"/>
|
||||||
|
<path fill="#c8b100" d="M326.6 225a.5.5 0 0 1 .5-.6l.5.5-.5.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M326.6 225a.5.5 0 0 1 .5-.6l.5.5-.5.5z"/>
|
||||||
|
<path fill="#c8b100" d="m338 223 .8.6-1 1.2-.2.1-.1-.2-.3-1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m338 223 .8.6-1 1.2-.2.1-.1-.2-.3-1.4.9-.3"/>
|
||||||
|
<path fill="#c8b100" d="m339.5 225.3-.5.5-1.3-.7v-.2l.1-.2h1.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m339.5 225.3-.5.5-1.3-.7v-.2l.1-.2h1.5l.2.6"/>
|
||||||
|
<path fill="#c8b100" d="m335.7 224.6.3.6 1.4-.1.2-.2v-.2l-1.3-.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m335.7 224.6.3.6 1.4-.1.2-.2v-.2l-1.3-.7-.6.6"/>
|
||||||
|
<path fill="#c8b100" d="m340.7 225.5.1.7 1.4.1h.3v-.3l-1.2-1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m340.7 225.5.1.7 1.4.1h.3v-.3l-1.2-1-.6.5"/>
|
||||||
|
<path fill="#c8b100" d="M331.8 224.5q.1-.5.5-.5.6 0 .5.5 0 .3-.5.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M331.8 224.5q.1-.5.5-.5.6 0 .5.5 0 .3-.5.4z"/>
|
||||||
|
<path fill="#c8b100" d="M337.1 225a.5.5 0 0 1 .5-.6q.5 0 .5.5t-.5.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M337.1 225a.5.5 0 0 1 .5-.6q.5 0 .5.5t-.5.5z"/>
|
||||||
|
<path fill="#c8b100" d="M331.4 219q0-.7 1-.8c1-.1.9.4.9.9s-.4.9-1 .9a1 1 0 0 1-.9-1"/>
|
||||||
|
<path fill="#c8b100" d="M333 218.8v.6h-1.5v-.6h.5v-1.4h-.6v-.6h.6v-.5h.7v.5h.6v.6h-.6v1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M333 218.8v.6h-1.5v-.6h.5v-1.4h-.6v-.6h.6v-.5h.7v.5h.6v.6h-.6v1.4z"/>
|
||||||
|
<path fill="#c8b100" d="M333.7 218.8v.6H331v-.6h1v-1.4h-.7v-.6h.6v-.5h.7v.5h.6v.6h-.6v1.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M332.6 218.2a1 1 0 0 1 .7.9q0 .8-1 .9a1 1 0 0 1-.9-1q0-.6.7-.8"/>
|
||||||
|
<path fill="#c8b100" d="m321.9 226.3-.7-.8-.7-.3s.3-.3.6-.3l.5.2v-.2s.2 0 .3.4v.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m321.9 226.3-.7-.8-.7-.3s.3-.3.6-.3l.5.2v-.2s.2 0 .3.4v.7z"/>
|
||||||
|
<path fill="#c8b100" d="m321.9 226 .5.1q.4.3.1.5h-.5q-.3-.3-.1-.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m321.9 226 .5.1q.4.3.1.5h-.5q-.3-.3-.1-.5z"/>
|
||||||
|
<path fill="#c8b100" d="m342.6 226.3.7-.8.7-.3s-.3-.3-.6-.3l-.5.2v-.2s-.3 0-.4.4v.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m342.6 226.3.7-.8.7-.3s-.3-.3-.6-.3l-.5.2v-.2s-.3 0-.4.4v.7z"/>
|
||||||
|
<path fill="#c8b100" d="m342.6 226-.5.1q-.3.3-.1.5t.5 0q.4-.3.1-.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m342.6 226-.5.1q-.3.3-.1.5t.5 0q.4-.3.1-.5z"/>
|
||||||
|
<path fill="#c8b100" d="M321 238h22.8v-6h-22.9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M321 238h22.8v-6h-22.9z"/>
|
||||||
|
<path fill="#c8b100" d="m341.4 242-.4-.1h-17.6q1-.3 1-1.3-.1-1-1-1.4l.4.1h17.6q-1 .3-1 1.3t1 1.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m341.4 242-.4-.1h-17.6q1-.3 1-1.3-.1-1-1-1.4l.4.1h17.6q-1 .3-1 1.3t1 1.3z"/>
|
||||||
|
<path fill="#c8b100" d="M323.9 241.9h17q1 0 1.1.8-.1.7-1 .8h-17.1q-1-.1-1-.8t1-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M323.9 241.9h17q1 0 1.1.8-.1.7-1 .8h-17.1q-1-.1-1-.8t1-.8z"/>
|
||||||
|
<path fill="#c8b100" d="M323.9 238h17q1 0 1.1.6-.1.7-1 .7h-17.1q-1 0-1.1-.7.1-.7 1-.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M323.9 238h17q1 0 1.1.6-.1.7-1 .7h-17.1q-1 0-1.1-.7.1-.7 1-.7z"/>
|
||||||
|
<path fill="#005bbf" d="M316.4 338.6q2.5 0 4-.9a9 9 0 0 1 4-.8q2.4 0 4 .8 1.6 1 4 1a9 9 0 0 0 4-1 9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v2.4a9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.2 0-3.9.9a9 9 0 0 1-4 .9 9 9 0 0 1-4-.9 9 9 0 0 0-4-.9q-2.4 0-4 .9a9 9 0 0 1-4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M316.4 338.6q2.5 0 4-.9a9 9 0 0 1 4-.8q2.4 0 4 .8 1.6 1 4 1a9 9 0 0 0 4-1 9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v2.4a9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.2 0-3.9.9a9 9 0 0 1-4 .9 9 9 0 0 1-4-.9 9 9 0 0 0-4-.9q-2.4 0-4 .9a9 9 0 0 1-4 .9z"/>
|
||||||
|
<path fill="#ccc" d="M316.4 341a9 9 0 0 0 4-.8 9 9 0 0 1 4-.8q2.4 0 4 .8a9 9 0 0 0 4 .9q2.5 0 4-1a9 9 0 0 1 4-.8 8 8 0 0 1 3.9.9 9 9 0 0 0 4 .9v2.4a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8q-2.2 0-3.9.8a9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M316.4 341a9 9 0 0 0 4-.8 9 9 0 0 1 4-.8q2.4 0 4 .8a9 9 0 0 0 4 .9q2.5 0 4-1a9 9 0 0 1 4-.8 8 8 0 0 1 3.9.9 9 9 0 0 0 4 .9v2.4a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8q-2.2 0-3.9.8a9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .9 9 9 0 0 1-4 .9V341"/>
|
||||||
|
<path fill="#005bbf" d="M316.4 343.6q2.5 0 4-1a9 9 0 0 1 4-.8 9 9 0 0 1 4 .9q1.6.8 4 .9 2.5 0 4-1a9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v2.4a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.4 0-4 .8a9 9 0 0 1-4 1v-2.6"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M316.4 343.6q2.5 0 4-1a9 9 0 0 1 4-.8 9 9 0 0 1 4 .9q1.6.8 4 .9 2.5 0 4-1a9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v2.4a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8q-2.4 0-4 .8a9 9 0 0 1-4 1v-2.6"/>
|
||||||
|
<path fill="#ccc" d="M316.4 348.5a9 9 0 0 0 4-1 9 9 0 0 1 4-.7 9 9 0 0 1 4 .8q1.6.8 4 .9 2.5 0 4-1a9 9 0 0 1 4-.7q2.3 0 3.9.8t4 .9V346a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .8 9 9 0 0 1-4 1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M316.4 348.5a9 9 0 0 0 4-1 9 9 0 0 1 4-.7 9 9 0 0 1 4 .8q1.6.8 4 .9 2.5 0 4-1a9 9 0 0 1 4-.7q2.3 0 3.9.8t4 .9V346a9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-3.9.8 9 9 0 0 1-4 1 9 9 0 0 1-4-1 9 9 0 0 0-4-.8 9 9 0 0 0-4 .8 9 9 0 0 1-4 1v2.4"/>
|
||||||
|
<path fill="#005bbf" d="M316.4 351q2.5 0 4-1a9 9 0 0 1 4-.8 9 9 0 0 1 4 .9q1.6.8 4 .8 2.5 0 4-.9a9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v-2.5a9 9 0 0 1-4-1 9 9 0 0 0-4-.7q-2.2 0-3.9.8a9 9 0 0 1-4 .9 9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-4 .8 9 9 0 0 1-4 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M316.4 351q2.5 0 4-1a9 9 0 0 1 4-.8 9 9 0 0 1 4 .9q1.6.8 4 .8 2.5 0 4-.9a9 9 0 0 1 4-.8q2.3 0 3.9.8 1.6 1 4 1v-2.5a9 9 0 0 1-4-1 9 9 0 0 0-4-.7q-2.2 0-3.9.8a9 9 0 0 1-4 .9 9 9 0 0 1-4-.9 9 9 0 0 0-4-.8 9 9 0 0 0-4 .8 9 9 0 0 1-4 .9z"/>
|
||||||
|
<path fill="#c8b100" d="m341.4 328.5-.2.6c0 1.5 1.3 2.7 3 2.7h-23.6c1.6 0 2.9-1.2 2.9-2.7v-.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="m341.4 328.5-.2.6c0 1.5 1.3 2.7 3 2.7h-23.6c1.6 0 2.9-1.2 2.9-2.7v-.6z"/>
|
||||||
|
<path fill="#c8b100" d="M323.9 327h17q1 0 1.1.7-.1.8-1 .8h-17.1q-1 0-1-.8 0-.7 1-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M323.9 327h17q1 0 1.1.7-.1.8-1 .8h-17.1q-1 0-1-.8 0-.7 1-.8z"/>
|
||||||
|
<path fill="#c8b100" d="M320.7 337.8H344v-6h-23.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M320.7 337.8H344v-6h-23.4z"/>
|
||||||
|
<path fill="#ad1519" d="M346 305.8c2.2 1.3 3.8 2.7 3.6 3.4q-.4.8-2 1.8c-1.6 1.1-2.6 3.2-1.8 4.1a6 6 0 0 1 .1-9.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M346 305.8c2.2 1.3 3.8 2.7 3.6 3.4q-.4.8-2 1.8c-1.6 1.1-2.6 3.2-1.8 4.1a6 6 0 0 1 .1-9.3z"/>
|
||||||
|
<path fill="#ccc" d="M324 326h16.7v-81.6h-16.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M336.2 244.4v81.4m1.8-81.4v81.4m-14 .2h16.7v-81.6h-16.6z"/>
|
||||||
|
<path fill="#ad1519" d="M307 275a53 53 0 0 1 25-2.2c9.9 1.7 17.5 5.6 16.9 8.9v.2s3.7-8.4 3.7-8.7c.7-3.6-7.7-8-18.8-9.8a57 57 0 0 0-9.8-.8c-7 0-13.2.9-16.9 2.3v10"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M307 275a53 53 0 0 1 25-2.2c9.9 1.7 17.5 5.6 16.9 8.9v.2s3.7-8.4 3.7-8.7c.7-3.6-7.7-8-18.8-9.8a57 57 0 0 0-9.8-.8c-7 0-13.2.9-16.9 2.3v10"/>
|
||||||
|
<path fill="#ad1519" d="M340.8 285.1c4.6-.3 7.7-1.5 8-3.5q.4-2.4-4-4.7l-4 .3v8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M340.8 285.1c4.6-.3 7.7-1.5 8-3.5q.4-2.4-4-4.7l-4 .3v8"/>
|
||||||
|
<path fill="#ad1519" d="M324 279q-4.4.5-6 2l-.2.1c-.5 1 2 3.3 6.3 5.8v-8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M324 279q-4.4.5-6 2l-.2.1c-.5 1 2 3.3 6.3 5.8v-8"/>
|
||||||
|
<path fill="#ad1519" d="M351.1 300.9c.4-1.3-4-4-10.4-6.3-3-1-5.3-2.1-8.3-3.4-8.8-4-15.4-8.4-14.6-10l.1-.2c-.4.4-1.2 8.4-1.2 8.4-.8 1.5 5.2 6 13.3 9.8 2.6 1.3 8.1 3.3 10.7 4.2 4.7 1.6 9.3 4.7 8.9 5.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M351.1 300.9c.4-1.3-4-4-10.4-6.3-3-1-5.3-2.1-8.3-3.4-8.8-4-15.4-8.4-14.6-10l.1-.2c-.4.4-1.2 8.4-1.2 8.4-.8 1.5 5.2 6 13.3 9.8 2.6 1.3 8.1 3.3 10.7 4.2 4.7 1.6 9.3 4.7 8.9 5.8z"/>
|
||||||
|
<path fill="#c8b100" d="M317.5 271.3q1-3.6 2.2-7l-.5.1h-.6q-.6 2.6-1.5 5.1l-2.9-4.4-1 .2-1 .1 4.2 6h1.1m6.3-7H322l-.2 6.5h4.6v-.8l-2.7.1zm7.3 1.1 2.1.3v-.4l.1-.4-6.2-.5v.8h2.2l-.6 6h1l.8.1zm2.5 6.5 1 .2.8.2.7-3h.1l.5 1.2 1 2.3 1 .2 1.1.2-.3-.6-1.5-3.1q1.8 0 2.3-1.3.2-1-.7-1.6l-1.9-.6-2.5-.5zm3.2-5.6c.8.2 1.7.3 1.7 1.1v.5q-.4 1.5-2.3 1zm8.7 7.6-.3 2.1.9.5.9.5.6-7.4-.8-.4-6.6 4.1.6.3.4.3 1.8-1.4zm-1.9-1.7 2.2-1.4-.3 2.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M230.3 205.3c0-1.2 1-2.1 2.2-2.1q2.1.2 2.3 2c0 1.2-1 2.2-2.3 2.2a2 2 0 0 1-2.2-2.1z"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".3" d="M255.3 187.1c6.8 0 13 1 16.8 2.6a32 32 0 0 0 8.6 2.2q4 .5 7 .2c2.9 0 6.9.8 11 2.6a29 29 0 0 1 8 5l-1.7 1.5-.4 4-4.4 5-2.2 2-5.3 4-2.6.3-.8 2.3-33.7-4-33.8 4-.9-2.3-2.6-.2-5.2-4.2-2.2-1.9-4.4-5-.5-4-1.6-1.5a30 30 0 0 1 8-5c4-1.8 8-2.6 10.9-2.6q3 .3 7-.2a32 32 0 0 0 8.6-2.2q6.1-2.5 16.4-2.6z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M255.9 231.6c-12.6 0-23.8-1.5-31.9-4q-1-.2-.8-1.2a1 1 0 0 1 .8-1.2 120 120 0 0 1 31.9-4c12.5.1 23.8 1.6 31.8 4q.9.4.9 1.2 0 1-1 1.3a119 119 0 0 1-31.7 4"/>
|
||||||
|
<path fill="#ad1519" d="M255.8 230a121 121 0 0 1-29.3-3.4c7.8-2 18-3.2 29.3-3.2a123 123 0 0 1 29.5 3.2c-7.8 2-18.1 3.4-29.5 3.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M256.7 230v-6.7m-1.9 6.7v-6.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="M253.1 230v-6.7m-1.7 6.7v-6.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M249.9 230v-6.7m-2.9 6.4v-6.2m1.4 6.2v-6.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M244.3 229.4v-5.7m1.4 5.9v-6m-5 5.4v-4.9m1.2 5V224m1.2 5.4V224"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M239.4 229v-4.8m-1.1 4.6v-4.5"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".6" d="M237 228.7v-4.2m-2.6 3.8V225m1.4 3.5v-3.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".7" d="M233.1 228v-2.9m-1.2 2.7v-2.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".8" d="M230.6 227.5v-2m-1.4 1.8v-1.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".9" d="M227.8 227v-.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M264 229.7v-6.2m-3.2 6.4v-6.6m-2.2 6.6v-6.6"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M255.8 221.3a115 115 0 0 0-32.2 4c.7-.3.6-1-.2-3.2-1-2.6-2.6-2.5-2.6-2.5a130 130 0 0 1 35-4.3c13.8 0 26.2 1.7 35.1 4.3 0 0-1.5-.1-2.5 2.5q-1.3 3-.3 3.3c-8-2.5-19.6-4.1-32.3-4.1"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M255.8 215.3a131 131 0 0 0-35 4.3q-1 .3-1.4-.6a1 1 0 0 1 .8-1.4c8.9-2.7 21.6-4.4 35.6-4.4s26.9 1.7 35.8 4.4q1 .5.7 1.4t-1.4.6c-8.9-2.6-21.3-4.2-35-4.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".4" d="M255.8 230a121 121 0 0 1-29.3-3.4c7.8-2 18-3.2 29.3-3.2a123 123 0 0 1 29.5 3.2c-7.8 2-18.1 3.4-29.5 3.4z"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M246 218.5q.1-1 1.1-1.1 1.1.1 1.2 1t-1.2 1.1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="M255.9 219.3h-3.4q-1-.1-1.1-1 .1-1 1-1.1h6.9a1 1 0 0 1 1.1 1q-.1 1-1.1 1.1h-3.4"/>
|
||||||
|
<path fill="#058e6e" stroke="#000" stroke-width=".4" d="m239 220.2-2.5.3q-1 0-1.2-1a1 1 0 0 1 1-1.1l2.4-.3 2.5-.3a1 1 0 0 1 1.2 1q0 .9-1 1.1l-2.5.3"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M229.1 220.4q.1-.9 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1.1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="m221.6 222.4 1.3-1.7 3.4.5-2.7 2-2-.8"/>
|
||||||
|
<path fill="#058e6e" stroke="#000" stroke-width=".4" d="m272.8 220.2 2.4.3q1 0 1.3-1a1 1 0 0 0-1-1.1l-2.5-.3-2.4-.3q-1.1 0-1.3 1 0 .9 1 1.1l2.5.3"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M263.4 218.5q.2-1 1.2-1.1c1-.1 1.1.5 1.1 1q-.1 1-1.1 1.1a1 1 0 0 1-1.2-1m17 2q.1-1 1-1.1a1 1 0 0 1 1.2 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="m290 222.4-1.2-1.7-3.4.5 2.8 2 1.9-.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M225.1 227c8-2.3 18.7-3.6 30.7-3.7 12 0 22.9 1.4 30.8 3.7"/>
|
||||||
|
<path fill="#c8b100" d="m230.4 196 1.5 1.1 2.1-3.4a8 8 0 0 1-3.8-7.6c.2-4.5 5.6-8.1 12.5-8.1q5.4.1 9 2.5l.2-1.9a19 19 0 0 0-9.2-2.3c-8 0-14.1 4.4-14.4 9.8a10 10 0 0 0 3.3 8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="m230.4 196 1.5 1.1 2.1-3.4a8 8 0 0 1-3.8-7.6c.2-4.5 5.6-8.1 12.5-8.1q5.4.1 9 2.5l.2-1.9a19 19 0 0 0-9.2-2.3c-8 0-14.1 4.4-14.4 9.8a10 10 0 0 0 3.3 8l-1.2 2"/>
|
||||||
|
<path fill="#c8b100" d="M230.5 196a10 10 0 0 1-4.3-7.7c0-3.5 2.2-6.6 5.7-8.6a9 9 0 0 0-3.6 6.4 10 10 0 0 0 3.3 8l-1 2"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M230.5 196a10 10 0 0 1-4.3-7.7c0-3.5 2.2-6.6 5.7-8.6a9 9 0 0 0-3.6 6.4 10 10 0 0 0 3.3 8l-1 2"/>
|
||||||
|
<path fill="#c8b100" d="M206.8 199.6a9 9 0 0 1-2.5-6.3q0-2.2 1-4c2.1-4.5 9-7.8 17.1-7.8q3.4 0 6.3.7-.7.8-1 1.5a27 27 0 0 0-5.3-.5c-7.4 0-13.6 3-15.4 6.8a8 8 0 0 0-.8 3.3 8 8 0 0 0 2.9 6l-2.7 4.4-1.5-1.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M206.8 199.6a9 9 0 0 1-2.5-6.3q0-2.2 1-4c2.1-4.5 9-7.8 17.1-7.8q3.4 0 6.3.7-.7.8-1 1.5a27 27 0 0 0-5.3-.5c-7.4 0-13.6 3-15.4 6.8a8 8 0 0 0-.8 3.3 8 8 0 0 0 2.9 6l-2.7 4.4-1.5-1.2z"/>
|
||||||
|
<path fill="#c8b100" d="M209.5 184.8a11 11 0 0 0-4.2 4.4 9 9 0 0 0-1 4.1q.1 3.7 2.5 6.3l-1.7 2.6a11 11 0 0 1-2.4-6.8c0-4.3 2.7-8.1 6.8-10.6"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M209.5 184.8a11 11 0 0 0-4.2 4.4 9 9 0 0 0-1 4.1q.1 3.7 2.5 6.3l-1.7 2.6a11 11 0 0 1-2.4-6.8c0-4.3 2.7-8.1 6.8-10.6z"/>
|
||||||
|
<path fill="#c8b100" d="M255.8 175.4c1.7 0 3.3 1.2 3.7 2.8q.3 2.1.4 4.7v1.2q.1 5.5 1.3 8.7l-5.5 5.2-5.5-5.2q1.1-3.2 1.3-8.7v-1.2q0-2.6.5-4.7c.3-1.6 2-2.8 3.7-2.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M255.8 175.4c1.7 0 3.3 1.2 3.7 2.8q.3 2.1.4 4.7v1.2q.1 5.5 1.3 8.7l-5.5 5.2-5.5-5.2q1.1-3.2 1.3-8.7v-1.2q0-2.6.5-4.7c.3-1.6 2-2.8 3.7-2.8z"/>
|
||||||
|
<path fill="#c8b100" d="M255.8 177a2 2 0 0 1 1.9 1.5l.4 4.5v1.1c0 3.4.5 6.4 1.2 8.2l-3.6 3.5-3.6-3.5c.7-1.8 1.2-4.8 1.2-8.2V183q0-2.4.5-4.5.4-1.3 2-1.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M255.8 177a2 2 0 0 1 1.9 1.5l.4 4.5v1.1c0 3.4.5 6.4 1.2 8.2l-3.6 3.5-3.6-3.5c.7-1.8 1.2-4.8 1.2-8.2V183q0-2.4.5-4.5.4-1.3 2-1.4z"/>
|
||||||
|
<path fill="#c8b100" d="m281 196-1.4 1.1-2.2-3.4a8 8 0 0 0 3.9-7.6c-.2-4.5-5.7-8.1-12.5-8.1a16 16 0 0 0-9 2.5l-.3-1.9a19 19 0 0 1 9.3-2.3c7.9 0 14 4.4 14.4 9.8a10 10 0 0 1-3.3 8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="m281 196-1.4 1.1-2.2-3.4a8 8 0 0 0 3.9-7.6c-.2-4.5-5.7-8.1-12.5-8.1a16 16 0 0 0-9 2.5l-.3-1.9a19 19 0 0 1 9.3-2.3c7.9 0 14 4.4 14.4 9.8a10 10 0 0 1-3.3 8l1.1 1.9"/>
|
||||||
|
<path fill="#c8b100" d="M280.9 196q4.2-3.1 4.4-7.7c0-3.5-2.3-6.6-5.7-8.6a9 9 0 0 1 3.6 7.3q-.2 4.3-3.3 7.2l1 1.9"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M280.9 196q4.2-3.1 4.4-7.7c0-3.5-2.3-6.6-5.7-8.6a9 9 0 0 1 3.6 7.3q-.2 4.3-3.3 7.2l1 1.9"/>
|
||||||
|
<path fill="#c8b100" d="M304.7 199.6a9 9 0 0 0 1.5-10.3c-2.2-4.5-9-7.8-17.2-7.8a28 28 0 0 0-6.3.7l1.1 1.5a27 27 0 0 1 5.2-.5c7.5 0 13.7 3 15.5 6.8q.8 1.5.8 3.3a8 8 0 0 1-3 6l2.8 4.4 1.4-1.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M304.7 199.6a9 9 0 0 0 1.5-10.3c-2.2-4.5-9-7.8-17.2-7.8a28 28 0 0 0-6.3.7l1.1 1.5a27 27 0 0 1 5.2-.5c7.5 0 13.7 3 15.5 6.8q.8 1.5.8 3.3a8 8 0 0 1-3 6l2.8 4.4 1.4-1.2z"/>
|
||||||
|
<path fill="#c8b100" d="M302 184.8a11 11 0 0 1 4.2 4.4 9 9 0 0 1 1 4.1q-.1 3.7-2.5 6.3l1.6 2.6a11 11 0 0 0 2.5-6.8c0-4.3-2.7-8.1-6.9-10.6"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M302 184.8a11 11 0 0 1 4.2 4.4 9 9 0 0 1 1 4.1q-.1 3.7-2.5 6.3l1.6 2.6a11 11 0 0 0 2.5-6.8c0-4.3-2.7-8.1-6.9-10.6z"/>
|
||||||
|
<path fill="#fff" d="M253.8 193.5c0-1 .9-1.9 2-1.9 1 0 1.9.9 1.9 1.9a2 2 0 0 1-2 1.8 2 2 0 0 1-2-1.8"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M253.8 193.5c0-1 .9-1.9 2-1.9 1 0 1.9.9 1.9 1.9a2 2 0 0 1-2 1.8 2 2 0 0 1-2-1.8z"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M253.8 189.8a2 2 0 0 1 2-1.8 2 2 0 0 1 1.9 1.9 2 2 0 0 1-2 1.8 2 2 0 0 1-2-1.8m.5-4q.1-1.3 1.5-1.4 1.5 0 1.6 1.5-.1 1.3-1.6 1.4-1.4-.1-1.5-1.4m.4-3.6q.1-.9 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1.1a1 1 0 0 1-1-1m.1-3.1q0-.8 1-.9c1-.1.8.4.8.9s-.4.8-.9.8-.9-.3-.9-.8"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="m255.9 204.6 1.3.2a5 5 0 0 0 4.8 6.4 5 5 0 0 0 4.8-3.3s.4-1.7.6-1.7.2 1.9.3 1.9c.3 2.4 2.5 4 5 4a5 5 0 0 0 5-5.4l1.5-1.5.9 2a4 4 0 0 0-.5 2 4.7 4.7 0 0 0 4.8 4.6 5 5 0 0 0 4-2l1-1.3v1.6c0 1.5.7 3 2.2 3.2 0 0 1.8.1 4.1-1.7a28 28 0 0 0 3.7-3.4l.2 1.9s-2 3-4 4.2c-1.2.7-3 1.4-4.4 1.1s-2.6-1.4-3.1-2.7a7 7 0 0 1-3.6 1 7 7 0 0 1-6.5-4 7.5 7.5 0 0 1-11-.3 7 7 0 0 1-5 1.9 7 7 0 0 1-6.1-3.1 7 7 0 0 1-6.1 3 7 7 0 0 1-5-1.8 7.5 7.5 0 0 1-11 .3 7 7 0 0 1-6.5 4 7 7 0 0 1-3.6-1q-.9 2.2-3.1 2.8c-1.4.2-3.2-.5-4.3-1.2-2.2-1.2-4.1-4.3-4.1-4.3l.2-1.8s1.3 1.5 3.6 3.4c2.4 1.8 4.2 1.7 4.2 1.7 1.5-.2 2.2-1.7 2.2-3.2v-1.6l1 1.3a5 5 0 0 0 4 2c2.6 0 4.8-2 4.8-4.6q0-1-.5-2l.8-2 1.6 1.5v.6a5 5 0 0 0 5 4.8c2.5 0 4.7-1.7 5-4 0 0 0-1.9.2-1.9s.7 1.8.7 1.7a5 5 0 0 0 4.8 3.3 5 5 0 0 0 4.8-6.4l1.3-.2"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M290.5 210.8c.3-.8 0-1.7-.6-1.8q-1.1-.2-1.6 1.2c-.3.8 0 1.7.5 1.8q1 .2 1.7-1.2m-22-4.2q.2-1.4-.9-1.7-1 0-1.3 1.4-.1 1.6 1 1.8 1 0 1.3-1.5m-25.4 0q-.1-1.4 1-1.7 1 0 1.3 1.4.1 1.6-1 1.8-1 0-1.3-1.5m-22 4.3q-.3-1.4.7-2 1-.1 1.6 1.3.4 1.4-.6 1.9-1 0-1.6-1.2"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M230.8 196.3a6 6 0 0 1 2.4 3s0-.2.7-.6q1-.4 1.1-.3l-.2 1.4c-.1.3-.1 1.4-.4 2.3l-.6 1.8a2 2 0 0 0-1.6-.5 2 2 0 0 0-1.4 1s-.7-.6-1.2-1.4l-1.2-2.2-.7-1.2h1.1l1 .3a5 5 0 0 1 1-3.7"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M231.3 206.7a2 2 0 0 1-.7-1.1 2 2 0 0 1 .2-1.3l-1.9-.7c-.7-.2-2-.2-2.4-.2h-1.2l.3.6.5.7a5 5 0 0 0-3 2.2 6 6 0 0 0 3.6 1l-.2.8v.7l1-.4c.4-.2 1.6-.6 2.2-1l1.6-1.3m2.9-.5.2-1.2a2 2 0 0 0-.7-1.1s.7-.8 1.5-1.3l2.3-1 1-.4v.6l-.2.9a6 6 0 0 1 3.7 1 5 5 0 0 1-3.1 2.1l.8 1.4h-1.2c-.4 0-1.7 0-2.4-.3-1-.2-2-.7-2-.7"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="M230.3 205.3c0-1.2 1-2.1 2.2-2.1q2.1.2 2.3 2c0 1.2-1 2.2-2.3 2.2a2 2 0 0 1-2.2-2.1"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M255.8 192.9a6 6 0 0 1 2 3.9s.2-.3 1-.6 1.2-.1 1.2-.1l-.5 1.4c-.2.4-.4 1.6-.9 2.6l-1 1.8a2 2 0 0 0-1.8-.7q-1 0-1.7.7-.2 0-1-1.8c-.5-1-.7-2.2-.9-2.6l-.6-1.4s.6-.1 1.3.2q1.1.4 1 .5a6 6 0 0 1 2-4"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M254.2 204.6a2 2 0 0 1-.6-1.3q0-.8.6-1.4s-1-.7-2-1.1c-.8-.4-2.2-.6-2.6-.7l-1.3-.3.1.7.4 1q-2.3.2-3.9 1.8a6 6 0 0 0 4 1.8l-.5 1-.2.6 1.4-.2 2.6-.7 2-1.2m3.3 0q.5-.5.5-1.3a2 2 0 0 0-.5-1.4s1-.7 2-1.1c.8-.4 2.2-.6 2.6-.7l1.3-.3-.2.7-.4 1a6 6 0 0 1 4 1.8 6 6 0 0 1-4 1.8l.4 1 .2.6-1.3-.2-2.6-.7-2-1.2m23.5-8.4a6 6 0 0 0-2.4 3.1l-.7-.6q-1-.4-1.1-.3l.2 1.4.3 2.3.7 1.8a2 2 0 0 1 1.6-.5 2 2 0 0 1 1.4 1l1.2-1.4 1.2-2.2.7-1.2h-1.2q-1 .2-.9.3a5 5 0 0 0-1-3.7"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M280.5 206.7q.5-.5.7-1.1a2 2 0 0 0-.2-1.3l1.8-.7c.8-.2 2-.2 2.5-.2h1.1l-.2.6-.6.7a5 5 0 0 1 3.1 2.2 6 6 0 0 1-3.6 1l.2.8v.7l-1-.4-2.3-1-1.5-1.3m-2.9-.5-.3-1.2q.2-.8.7-1.1s-.7-.8-1.5-1.3c-.6-.4-1.8-.9-2.2-1l-1-.4v.6l.2.9a6 6 0 0 0-3.7 1q1.1 1.6 3 2.1l-.4.8-.3.6h1.2c.4 0 1.7 0 2.4-.3 1-.2 1.9-.7 1.9-.7"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="M277 205.3q.2-2 2.2-2.1 2.1.2 2.3 2c0 1.2-1 2.2-2.3 2.2a2 2 0 0 1-2.2-2.1m24.8 4.6c-.5-.5-1.6-.4-2.4.3s-1 1.7-.5 2.2 1.6.5 2.4-.2q1.2-1.2.5-2.3"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".4" d="M298.7 211.3q.1-.6.7-1.1c.8-.7 2-.8 2.4-.3l.2.3s1.2-2.1 2.5-2.9 3.6-.5 3.6-.5a3 3 0 0 0-3.1-3q-1.6 0-2.5 1.2l-.3-1.1s-1.4.3-2 1.8c-.6 1.7 0 4 0 4s-.3-1-.8-1.7a9 9 0 0 0-2.6-1.7l-1.4-.8v1.5a8 8 0 0 0-4 .5 5 5 0 0 0 2.7 2.3l-.8.8-.5.5 1.4.2c.4 0 1.8.3 2.7.2l1.8-.2m-85.6 0a2 2 0 0 0-.8-1.1c-.8-.7-1.9-.8-2.4-.3l-.2.3s-1-2.1-2.4-2.9-3.6-.5-3.6-.5a3 3 0 0 1 3-3q1.6 0 2.6 1.2l.2-1.1s1.4.3 2 1.9 0 3.9 0 3.9.4-1 .9-1.7 1.8-1.4 2.5-1.7l1.4-.8v1.5a8 8 0 0 1 4 .5 5 5 0 0 1-2.7 2.3l.9.8.4.5-1.3.2c-.4 0-1.9.3-2.7.2l-1.8-.2"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".4" d="M210 210q1-.9 2.3.2c.9.7 1 1.7.6 2.2q-1 .9-2.4-.2t-.6-2.3m43.7-6.6q.2-2 2.3-2.1c2.1-.1 2.2.9 2.2 2s-1 2.2-2.3 2.2a2 2 0 0 1-2.2-2.1"/>
|
||||||
|
<path fill="#005bbf" stroke="#000" stroke-width=".3" d="M251.2 171.3c0-2.4 2-4.3 4.6-4.3s4.5 2 4.5 4.3a4.4 4.4 0 0 1-4.5 4.3 4.4 4.4 0 0 1-4.6-4.3"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".3" d="M254.6 159.3v2.3H252v2.3h2.5v6.8h-3.2l-.2.6q0 1 .4 1.7h8.3l.4-1.7-.2-.6h-3V164h2.4v-2.3H257v-2.3z"/>
|
||||||
|
<path fill="#ccc" d="M256.2 352.6a87 87 0 0 1-37.8-8.8 24 24 0 0 1-13.7-21.6V288h102.8v34c0 9.5-5.4 17.7-13.7 21.8a86 86 0 0 1-37.6 8.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M256.2 352.6a87 87 0 0 1-37.8-8.8 24 24 0 0 1-13.7-21.6V288h102.8v34c0 9.5-5.4 17.7-13.7 21.8a86 86 0 0 1-37.6 8.7z"/>
|
||||||
|
<path fill="#ccc" d="M256 288h51.5v-57H256z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M256 288h51.5v-57H256z"/>
|
||||||
|
<path fill="#ad1519" d="M256 322.1a25 25 0 0 1-25.6 24.4c-14.2 0-25.8-11-25.8-24.4V288H256v34"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="M215.8 342.3c1.7.8 3.9 2.2 6.2 2.8l-.1-58.3h-6v55.4z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-linejoin="round" stroke-width=".5" d="M204.5 321.7a26 26 0 0 0 6 16v-50.6h-5.9z"/>
|
||||||
|
<path fill="#c7b500" stroke="#000" stroke-width=".5" d="M227.3 346.4c2.4.2 4.1.2 6 0v-59.6h-6z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="M238.6 345a20 20 0 0 0 6.2-2.6v-55.6h-6z"/>
|
||||||
|
<path fill="#ad1519" d="M204.6 288H256v-57h-51.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M204.6 288H256v-57h-51.4z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="M250.4 337.1c2.5-2.2 4.9-7.3 5.7-13l.2-37.3h-6V337z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M256 322.1a25 25 0 0 1-25.6 24.4c-14.2 0-25.8-11-25.8-24.4V288H256v34"/>
|
||||||
|
<path fill="#ad1519" d="M307.6 288v34a25 25 0 0 1-25.8 24.4c-14.2 0-25.8-11-25.8-24.4v-34z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".5" d="M307.6 288v34a25 25 0 0 1-25.8 24.4c-14.2 0-25.8-11-25.8-24.4v-34h51.6"/>
|
||||||
|
<path fill="#c8b100" d="m265.4 313.7.1.6q0 1-1.2 1.1-1-.1-1.2-1.1 0-.3.2-.5h-1.7a3 3 0 0 0 1.9 3v4.1h1.7v-4.1a3 3 0 0 0 1.8-1.7h4.7v-1.4zm23.1 0v1.4h-4.2l-.3.6 5 5.6-1.4 1-4.9-5.6-.2.1v9.3h-1.8v-9.3l-.2-.1-5 5.6-1.4-1 5.1-5.7-.2-.5h-4.4v-1.4h14zm2.8 0v1.4h4.8q.4 1.2 1.7 1.7v4.1h1.8v-4.1a3 3 0 0 0 2-2.5l-.2-.6h-1.6a1.2 1.2 0 0 1-1 1.7q-1.2-.1-1.3-1.1l.1-.5zm-7 23.5a17 17 0 0 0 3.8-1l.9 1.4a19 19 0 0 1-4.6 1.3 3 3 0 0 1-2.8 2 3 3 0 0 1-2.7-2 19 19 0 0 1-4.8-1.3l.8-1.5q2 .9 4.2 1.2a3 3 0 0 1 1.6-1.5v-7.1h1.8v7.1a3 3 0 0 1 1.7 1.4zm-11.8-2.4-.9 1.5a18 18 0 0 1-3.9-3.3 3 3 0 0 1-2.6-.5 2.5 2.5 0 0 1-.4-3.7l.2-.2a16 16 0 0 1-1.4-5.1h1.8a14 14 0 0 0 1.1 4.4l1.5.1 4.4-4.8 1.3 1-4.4 4.9a3 3 0 0 1 0 3 16 16 0 0 0 3.2 2.7zm-6.5-5a1.2 1.2 0 0 1 1.7-.2 1 1 0 0 1 0 1.6 1.3 1.3 0 0 1-1.6.2 1 1 0 0 1-.1-1.6m-2.2-4.8-1.8-.4-.3-4.6 1.8-.6v2.6l.2 3zm1.4-5.7 1.8.4v2.3c0-.8.3 2.3.3 2.3l-1.8.6-.3-2.9zm6 14.6a17 17 0 0 0 5.1 2.7l.4-1.7a15 15 0 0 1-4.3-2.1zm-1 1.6a19 19 0 0 0 5.2 2.7l-1.3 1.3a20 20 0 0 1-4.2-2.2zm2.3-10 1.7.7 3.2-3.5-1-1.5zm-1.3-1-1-1.5 3.1-3.5 1.7.7zm19.3 10.5.9 1.5a18 18 0 0 0 3.8-3.3q1.6.4 2.7-.5a2.5 2.5 0 0 0 .3-3.7l-.1-.2q1.1-2.4 1.3-5.1h-1.7a14 14 0 0 1-1.2 4.4l-1.5.1-4.3-4.8-1.3 1 4.3 4.9a3 3 0 0 0 .1 3 16 16 0 0 1-3.3 2.7m6.5-5a1.2 1.2 0 0 0-1.7-.2 1 1 0 0 0-.1 1.6q.8.8 1.7.1a1 1 0 0 0 0-1.6zm2.2-4.8 1.8-.5.2-4.5-1.7-.6v2.6zm-1.5-5.7-1.8.4v2.3c0-.8-.3 2.3-.3 2.3l1.9.6.2-2.9zm-6 14.6a17 17 0 0 1-5 2.7l-.5-1.7a15 15 0 0 0 4.3-2.1zm.9 1.6a19 19 0 0 1-5.2 2.7l1.3 1.3a20 20 0 0 0 4.3-2.2zm-2.4-10-1.7.7-3.1-3.5 1-1.5zm1.4-1 1-1.5-3.2-3.5-1.7.7 3.8 4.3m-21.4-9.3.5 1.7h4.8l.6-1.7zm22.5 0-.5 1.7h-4.8l-.5-1.7zm-12.4 23.3q0-1 1.2-1.1 1 .1 1.1 1.1 0 1.1-1.2 1.2c-1.2.1-1.2-.6-1.2-1.2zm2-8.2 1.8-.5v-5l-1.8-.5zm-1.8 0-1.8-.5v-5l1.8-.5z"/>
|
||||||
|
<path fill="#c8b100" d="M261.6 313.8q.4-1.5 1.9-2V306h1.7v5.7q1.4.5 1.8 1.7h4.7v.3h-6.3a1 1 0 0 0-1-.6 1 1 0 0 0-1.1.6h-1.7m13 0v-.3h4.4l.2-.4-5.4-6 1.3-1 5.3 5.8h.3v-8h1.7v7.9h.3l5.2-5.8 1.3 1-5.2 5.9.3.6h4.2v.3zm23 0a1 1 0 0 1 1.1-.6q.8 0 1 .6h1.7a3 3 0 0 0-1.8-2V306h-1.8v5.7a3 3 0 0 0-1.8 1.7h-4.6v.3zm-32.2-16 6.5 7.3 1.3-1-6.5-7.3.3-.6h4.7v-1.6H267a3 3 0 0 0-2.7-1.9 2.7 2.7 0 0 0-2.8 2.7q.1 1.8 1.9 2.5v5.6h1.7v-5.6zm34 .1v5.6h-1.7v-5.6l-.4-.2-6.3 7.4-1.3-1 6.5-7.5-.1-.3h-4.8v-1.6h4.8a3 3 0 0 1 2.6-1.9c1.6 0 2.8 1.2 2.8 2.7q-.1 2-2 2.5zm-17 0v3.4h-1.8V298a3 3 0 0 1-1.8-1.7h-4.3v-1.6h4.3a3 3 0 0 1 2.6-1.9q2 .1 2.7 1.9h4.3v1.6h-4.3a3 3 0 0 1-1.7 1.7zm-19 4.1-1.8.5v4.6l1.8.5V302m1.8 0 1.8.5v4.6l-1.8.5zm32.5 0-1.8.5v4.6l1.8.5zm1.8 0 1.8.5v4.6l-1.8.5zm-27.3 1 1.7-.8 3.2 3.5-1 1.5zm-1.3 1-1 1.5 3 3.5 1.8-.7zm19.7-1.2-1.8-.7-3 3.5 1 1.5zm1.3 1 1 1.6-3 3.5-1.8-.7 3.8-4.3m-21.7 9.6.5-1.7h4.8l.6 1.7zm-7-18.1q.1-1 1.1-1.2 1.1 0 1.2 1.2c.1 1.2-.5 1.1-1.2 1.1q-1-.1-1.2-1.1zm12.8.8-.5 1.7h-4.8l-.5-1.7zm0-1.7-.5-1.7h-4.8l-.5 1.7zm16.7 19-.5-1.7h-4.8l-.5 1.7zm4.6-18.1q.2-1 1.2-1.2 1.1 0 1.2 1.2c.1 1.2-.5 1.1-1.2 1.1q-1-.1-1.2-1.1m-17.2 0q.2-1 1.2-1.2c1-.2 1.2.5 1.2 1.2s-.5 1.1-1.2 1.1q-1-.1-1.2-1.1m6.7.8.6 1.7h4.8l.5-1.7zm0-1.6.5-1.8h4.9l.5 1.7H287m-6.3 5.4-1.8.5v4.6l1.8.5zm1.7 0 1.8.5v4.6l-1.8.5z"/>
|
||||||
|
<path fill="none" stroke="#c8b100" stroke-width=".3" d="M284.2 337.3a17 17 0 0 0 4-1l.8 1.4a19 19 0 0 1-4.6 1.3 3 3 0 0 1-2.8 2 3 3 0 0 1-2.7-2 19 19 0 0 1-4.8-1.3l.8-1.5q2 1 4.2 1.1a3 3 0 0 1 1.6-1.4v-7.2h1.8v7.2a3 3 0 0 1 1.7 1.4zm-5-21.7-.2-.5h-4.4v-1.6h4.4q0-.3.2-.4l-5.4-6 1.3-1 5.3 5.8.3-.1V304h1.7v7.7l.3.1 5.2-5.9 1.3 1-5.2 6 .3.6h4.2v1.6h-4.2l-.3.6 5 5.6-1.4 1-4.9-5.6-.2.1v9.3h-1.8v-9.3l-.2-.1-5 5.6-1.4-1 5.1-5.7m-13.7-17.7 6.5 7.2 1.3-1-6.5-7.3.3-.6h4.7v-1.7h-4.7a3 3 0 0 0-2.7-1.8 2.7 2.7 0 0 0-2.8 2.7q.1 1.8 1.9 2.5v5.5h1.7V298h.3zm7 37-.9 1.5a18 18 0 0 1-3.9-3.3q-1.4.4-2.6-.5a2.5 2.5 0 0 1-.3-3.7v-.2a16 16 0 0 1-1.3-5.1h1.8a14 14 0 0 0 1.1 4.4l1.5.1 4.4-4.8 1.3 1-4.3 4.9a3 3 0 0 1-.1 3 16 16 0 0 0 3.3 2.7zm-9-14v-4a3 3 0 0 1-2-2.6q.2-1.9 2-2.6v-5.6h1.7v5.7q1.4.4 1.8 1.7h4.7v1.6H267a3 3 0 0 1-1.8 1.7v4.1h-1.7m2.5 9a1.2 1.2 0 0 1 1.7-.2q.7.7 0 1.6a1.3 1.3 0 0 1-1.6.1 1 1 0 0 1-.1-1.6zm-2.2-4.9-1.8-.4-.3-4.5 1.8-.6v2.6q0 1.5.3 3zm1.5-5.6 1.7.4.3 4.6-1.8.6-.3-2.9v-2.7zm5.9 14.6a17 17 0 0 0 5.1 2.7l.4-1.7a15 15 0 0 1-4.3-2.2l-1.2 1.2m-1 1.5a19 19 0 0 0 5.3 2.7l-1.4 1.2a20 20 0 0 1-4.2-2.1l.4-1.8"/>
|
||||||
|
<path fill="none" stroke="#c8b100" stroke-width=".3" d="m272.6 325.4 1.7.8 3.2-3.5-1-1.5-3.9 4.2m-1.3-1-1-1.5 3.1-3.5 1.7.7-3.8 4.3m-8.2-10.1q.2-1.1 1.2-1.2 1.1 0 1.2 1.2 0 1-1.2 1.1-1-.1-1.2-1.1zm27.5 20.6.8 1.5a18 18 0 0 0 4-3.3q1.3.4 2.6-.5a2.5 2.5 0 0 0 .3-3.7l-.2-.2q1.1-2.4 1.4-5.1h-1.7a14 14 0 0 1-1.2 4.4l-1.5.1-4.3-4.8-1.4 1 4.4 4.9a3 3 0 0 0 .1 3 16 16 0 0 1-3.3 2.7zm9-14v-4.1a3 3 0 0 0 2-2.5 3 3 0 0 0-2-2.6v-5.6h-1.8v5.7q-1.3.4-1.8 1.7h-4.7v1.6h4.8a3 3 0 0 0 1.7 1.7v4.1zm-2.5 9a1.2 1.2 0 0 0-1.7-.2 1 1 0 0 0-.1 1.6q.7.8 1.7.1a1 1 0 0 0 0-1.6zm2.2-4.8 1.8-.4.2-4.6-1.7-.6v2.6zm-1.5-5.7-1.8.4v2.3c0-.8-.3 2.3-.3 2.3l1.9.6.2-2.9v-2.7m1.8-21.5v5.5h-1.8V298l-.4-.2-6.4 7.2-1.3-1.1 6.5-7.4-.1-.3h-4.8v-1.7h4.8a3 3 0 0 1 2.6-1.8c1.6 0 2.8 1.2 2.8 2.7q-.1 2-2 2.5zm-17.2 0v3.4h-1.7v-3.4a3 3 0 0 1-1.8-1.7h-4.3v-1.7h4.3a3 3 0 0 1 2.6-1.8q2 .2 2.7 1.8h4.3v1.7h-4.3a3 3 0 0 1-1.7 1.7zm9.5 36a17 17 0 0 1-5.2 2.8l-.4-1.7a15 15 0 0 0 4.3-2.1l1.3 1m.9 1.6a19 19 0 0 1-5.2 2.7l1.3 1.3a20 20 0 0 0 4.3-2.2l-.4-1.8M263.5 302l-1.8.5v4.6l1.8.5V302m1.8 0 1.8.5v4.6l-1.8.5V302m32.5 0-1.8.5v4.6l1.8.5V302"/>
|
||||||
|
<path fill="none" stroke="#c8b100" stroke-width=".3" d="m299.6 302 1.8.5v4.6l-1.8.5V302m-9.2 23.4-1.7.8-3.1-3.5 1-1.5 3.8 4.2m1.3-1 1-1.5-3-3.5-1.8.7 3.8 4.3m-19.4-21.5 1.7-.7 3.1 3.5-1 1.5-3.8-4.3M271 304l-1 1.5 3 3.5 1.8-.8-3.8-4.2m19.7-1.2-1.8-.7-3 3.5 1 1.5 3.8-4.3m1.3 1 1 1.5-3 3.5-1.8-.7 3.8-4.2m-21.7 9.6.5-1.7h4.8l.6 1.7h-5.9m0 1.6.5 1.7h4.8l.6-1.7h-5.9m-7-19.7q.1-1.1 1.1-1.2 1.1 0 1.2 1.2c.1 1.2-.5 1.1-1.2 1.1q-1-.1-1.2-1.1zm12.8.8-.5 1.7h-4.8l-.5-1.7h5.8m0-1.7-.5-1.7h-4.8l-.5 1.7h5.8m21.4 19.8q0-1 1.2-1.2c1.2-.2 1.2.5 1.2 1.2q0 1-1.2 1.1c-1.2.1-1.2-.5-1.2-1.1zm-4.7-.8-.5-1.7h-4.8l-.5 1.7h5.9m0 1.6-.6 1.7h-4.8l-.5-1.7h5.9m-12.4 23.3q0-1 1.2-1.1 1 .1 1.1 1.1 0 1.1-1.2 1.2c-1.2.1-1.2-.5-1.2-1.2zm2-8.2 1.8-.5V325l-1.8-.5v5.6m-1.8 0-1.8-.5V325l1.8-.5v5.6m16.8-34.8q.2-1.1 1.2-1.2 1.1 0 1.2 1.2c.1 1.2-.5 1.1-1.2 1.1q-1-.1-1.2-1.1zm-17.2 0q.2-1.1 1.2-1.2 1.1 0 1.2 1.2c.1 1.2-.5 1.1-1.2 1.1q-1-.1-1.2-1.1zm6.7.8.6 1.7h4.8l.5-1.7H287m0-1.7.6-1.7h4.8l.5 1.7H287m-6.3 5.4-1.8.5v4.5l1.8.5V300m1.7 0 1.8.5v4.5l-1.8.5V300"/>
|
||||||
|
<path fill="#058e6e" d="M278.8 314.3a2.7 2.7 0 0 1 2.8-2.6q2.6.2 2.8 2.6a2.7 2.7 0 0 1-2.8 2.7 2.7 2.7 0 0 1-2.8-2.7"/>
|
||||||
|
<path fill="#db4446" d="M282.3 245v-.6l.1-.4s-1.6.2-2.5 0a6 6 0 0 1-2.5-1.4q-1-1-1.7-1.2c-1.4-.2-2.4.4-2.4.4s1 .4 1.8 1.4a5 5 0 0 0 2 1.5c.6.2 2.7 0 3.3.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M282.3 245v-.6l.1-.4s-1.6.2-2.5 0a6 6 0 0 1-2.5-1.4q-1-1-1.7-1.2c-1.4-.2-2.4.4-2.4.4s1 .4 1.8 1.4a5 5 0 0 0 2 1.5c.6.2 2.7 0 3.3.1z"/>
|
||||||
|
<path fill="#ed72aa" stroke="#000" stroke-width=".4" d="M290 242.7v1.4c.1.7-.2 1.3 0 1.7l.2 1c.2.2.3.9.3.9l-.8-.6-.6-.4v1l.6 1.3q.6.6 1 1.4c.3.4.2 1.4.2 1.4s-.4-.7-.9-.9l-1.3-.6s.9.8.9 1.6c0 .7-.4 1.6-.4 1.6s-.3-.7-.8-1.1l-1.2-1s.5 1.2.5 2v2.6s-.5-.7-1-1l-1-.8c0-.2.5.7.6 1.2s.3 2.4 2 4.8c1 1.4 2.4 3.8 5.6 3s2-5 1.3-7-1-4.3-1-5c.1-.8.7-3 .6-3.5a9 9 0 0 1 .1-3.4q.6-1.8 1-2.4l.5-1.4v-1.4l.8 1.5.1 1.5s.1-1.1 1-1.7 2-1.2 2.2-1.5l.4-.5s-.1 2-.7 2.7l-1.8 2.2s.8-.3 1.3-.3h.9s-.7.5-1.5 1.6-.5 1.3-1 2.3c-.7 1-1.1 1-1.9 1.6-1 1-.5 4.5-.3 5 .1.5 2 4.9 2 6 .1 1 .3 3.3-1.6 4.9-1.2 1-3.1 1-3.6 1.2-.4.3-1.3 1.2-1.3 3s.7 2.1 1.2 2.6 1.2.2 1.3.5l.5.8q.4.3.3.8c0 .4-1 1.3-1.2 2-.3.6-1 2.2-1 2.5s0 1 .3 1.4c0 0 .9 1 .2 1.3-.4.1-.8-.3-1-.2l-1 .4q-.4 0-.4-.8l-.1-.7q-.4 0-.4.5c0 .2 0 .9-.3.9s-.6-.5-.9-.6c-.2 0-.8-.2-.9-.5q.1-.6.8-1 .8-.1.5-.5-.4-.3-.7 0-.6.3-.8-.3c-.2-.6.1-.7 0-.8 0-.2-.4-.6.2-.9s.8.3 1.4.2q1-.1 1.1-.7.4-.6-.2-1.5c-.6-.9-.8-.5-1-.8l-.3-1v2.4l-.8-.9c-.3-.3-.6-1.4-.6-1.4v1.4c0 .4.4.7.2.9-.1.1-.8-.8-1-.9q-.5-.3-1-1l-.6-1.5c0-.2-.2-1.4 0-1.6l.4-1.2h-1.4c-.8 0-1.3-.2-1.6.3q-.4.8.2 3 .6 2 .5 2.2l-.8 1-1-.1q-.2-.2-1.2-.3h-1.4c-.3 0-1-.4-1.2-.3q-.7 0-.7.7.1.9-.5.7l-1-.2c-.3-.1-.9 0-.8-.4q0-.5.4-.8.4-.5 0-.5h-.7q-.4.5-.7.5-.5-.2-.5-1.1c0-.7-.7-1.3 0-1.3s1.4.5 1.5.2q.2-.5-.3-.8c-.3-.3-.7-.5-.3-.8l.8-.6c.2-.1.4-.8.7-.6.7.3 0 .7.7 1.4s1 1 2.1.9q1.4-.1 1.4-.6l-.2-1 .2-1s-.5.3-.6.6l-.5.8v-2.1l-.2-.9-.3 1-.1 1s-.7-.5-.5-1.5v-2.1c.3-.4.9-1.7 2.2-1.8h2.8l2.1-.3s-3-1.5-3.7-2-1.8-1.7-2.1-2.2l-.7-1.6q-.1 0-1 .3l-1.3 1-.7 1v-2s-.4 1.3-1 1.8l-1.5 1.2v-1l.2-1s-.5.8-1.2 1-1.9 0-2 .5v1.4q-.3.6-.5.6l-.8-.4c-.6-.2-.7.2-.7.2s-.4-.4-.2-.7c0-.3.7-.7.5-.9l-.9.2c-.3.2-.9.3-.8-.2q.2-.6 0-1-.1-.5.2-.6c.2-.1 1.2 0 1.3-.2q0-.4-.9-.7t-.6-.8l.6-.6c.1-.3.2-.8.7-.6.6.3.5.9 1 1 .6.3 2 0 2.3-.1l1.5-1 1.7-1.2-1.1-.8-1-1.1-2-.7c-.5 0-1.8-.5-1.8-.5l.8-.3q.4-.4.9-.6h.3-1.5c-.3-.2-1-.6-1.3-.6h-.9s.8-.4 1.4-.5l1.2-.1s-1-.3-1.3-.6l-.6-1q-.2-.5-.6-.6c-.4-.1-.9.4-1.2.3q-.4 0-.6-.7v-.5c-.2-.3-.7-.8-.2-1h1.4c.1-.2-.5-.7-.8-1-.4-.1-1-.4-.7-.7l.8-.6c.2-.3.4-1 .8-.7.3.2.9 1.3 1.1 1.2s.4-.9.3-1.2c0-.3 0-1 .3-.9s.5.5 1 .5 1.1-.1 1 .2q0 .6-.6 1.1a2 2 0 0 0-.2 1.6 4 4 0 0 0 1.2 1.5c.4.3 1.3.5 1.8.8s1.9 1.4 2.3 1.5l.9.3s.5-.2 1.1-.2 2.2.1 2.8-.1 1.3-.7 1.1-1.2-1.5-1-1.4-1.4q.2-.5 1.4-.5c.8 0 1.8.2 2-1 .2-1 .3-1.6-.8-1.9q-1.8-.1-2.1-1t-.2-1.3 1.5-.3 2-.3l.6-1 1.5-.3s1.5.7 3 1.7c1.2 1 2.3 2.3 2.3 2.3"/>
|
||||||
|
<path d="m279.3 242-.2-.7-.1-.3s.9 0 .9.2q-.1.4-.4.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m279.3 242-.2-.7-.1-.3s.9 0 .9.2q-.1.4-.4.4z"/>
|
||||||
|
<path d="M283.4 240.5v-.5s.7 0 1.1.3c.6.4 1 1 1 1-.2.2-.6-.2-1-.3h-.4q-.3 0-.4-.2v-.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M283.4 240.5v-.5s.7 0 1.1.3c.6.4 1 1 1 1-.2.2-.6-.2-1-.3h-.4q-.3 0-.4-.2v-.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m289 246.7-.4-.7-.2-.5"/>
|
||||||
|
<path fill="#db4446" d="M267.9 241.8s.5.3.8.3l.9.1s.2-.5.1-1c-.2-1.2-1.3-1.4-1.3-1.4s.3.7.1 1q-.4 1-.6 1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M267.9 241.8s.5.3.8.3l.9.1s.2-.5.1-1c-.2-1.2-1.3-1.4-1.3-1.4s.3.7.1 1q-.4 1-.6 1z"/>
|
||||||
|
<path fill="#db4446" d="M265.5 242.8s-.4-.8-1.3-.7-1.6.9-1.6.9h1.3c.4.3.5 1 .5 1l.7-.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M265.5 242.8s-.4-.8-1.3-.7-1.6.9-1.6.9h1.3c.4.3.5 1 .5 1l.7-.6z"/>
|
||||||
|
<path fill="#db4446" d="M264.4 246s-.8.1-1.2.6c-.5.5-.4 1.5-.4 1.5s.5-.6 1-.6l1.2.2-.3-.9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M264.4 246s-.8.1-1.2.6c-.5.5-.4 1.5-.4 1.5s.5-.6 1-.6l1.2.2-.3-.9z"/>
|
||||||
|
<path d="m279.3 245.9.4-.5.3.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m279.3 245.9.4-.5.3.5h-.7"/>
|
||||||
|
<path d="m280.2 245.9.4-.6.4.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m280.2 245.9.4-.6.4.5h-.8"/>
|
||||||
|
<path d="m279.8 242.5.8.3-.7.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m279.8 242.5.8.3-.7.4-.1-.7"/>
|
||||||
|
<path d="m280.8 242.8.8.2-.6.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m280.8 242.8.8.2-.6.4-.2-.6"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M275.2 249.2s-.9.3-1.2.7q-.4 1-.3 1.1c.1.1.7-.6 1.6-.3l1.3.3c.4 0 1.3-.4 1.3-.4s-.7.9-.6 1.5l.1 1.2c0 .7-.5 1.6-.5 1.6l1-.3a5 5 0 0 0 1.8-.9l1-1s-.2 1 0 1.5l.2 1.7s.4-.5.8-.7c.2 0 .8-.4 1-.7l.3-1s0 .8.4 1.3c.3.4.7 1.8.7 1.8s.3-.9.6-1.3q.6-.6.7-1v-1l.4 1m-11.7.6s.5-.9 1-1.2l1.2-.8 1-.4m1 5.3 1.4-.8 1.2-1.2"/>
|
||||||
|
<path fill="#db4446" d="M267 256.4s-.4-.5-1.2-.3c-.7 0-1.2 1-1.2 1l1-.2.6.5.5-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M267 256.4s-.4-.5-1.2-.3c-.7 0-1.2 1-1.2 1l1-.2.6.5.5-.4z"/>
|
||||||
|
<path fill="#db4446" d="M266.2 259.4s-.7 0-1.2.4-.6 1.2-.6 1.2.5-.4 1-.3l.8.2.1-.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M266.2 259.4s-.7 0-1.2.4-.6 1.2-.6 1.2.5-.4 1-.3l.8.2.1-.6z"/>
|
||||||
|
<path fill="#db4446" d="M267.6 262.2s0 .8.4 1.3 1.2.6 1.2.6l-.3-.8c-.1-.4.3-.7.3-.7s-.4-.4-.7-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M267.6 262.2s0 .8.4 1.3 1.2.6 1.2.6l-.3-.8c-.1-.4.3-.7.3-.7s-.4-.4-.7-.4zm17.1 1.4s2.1 1.3 2 2.4c0 1-1.1 2.4-1.1 2.4"/>
|
||||||
|
<path fill="#db4446" d="M275.2 269.4s-.5-.6-1.3-.6c-.7 0-1.5.7-1.5.7s1 0 1.2.2l.4.7.6-.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M275.2 269.4s-.5-.6-1.3-.6c-.7 0-1.5.7-1.5.7s1 0 1.2.2l.4.7.6-.3z"/>
|
||||||
|
<path fill="#db4446" d="M273 272.3s-1-.1-1.5.4-.4 1.4-.4 1.4.6-.7 1-.6q1 .1 1.2.3l-.2-.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M273 272.3s-1-.1-1.5.4-.4 1.4-.4 1.4.6-.7 1-.6q1 .1 1.2.3l-.2-.8z"/>
|
||||||
|
<path fill="#db4446" d="M275 275.4s-.5.6-.1 1.1c.3.6 1 .8 1 .8s-.2-.4-.1-.8.7-.8.7-.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M275 275.4s-.5.6-.1 1.1c.3.6 1 .8 1 .8s-.2-.4-.1-.8.7-.8.7-.8z"/>
|
||||||
|
<path fill="#db4446" d="M287.7 276.6s-.8-.2-1.3 0-.8 1.5-.8 1.5.7-.6 1.3-.6l1 .3v-.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M287.7 276.6s-.8-.2-1.3 0-.8 1.5-.8 1.5.7-.6 1.3-.6l1 .3v-.8z"/>
|
||||||
|
<path fill="#db4446" d="M288.1 279.7s-.6.6-.4 1.2l.6 1s0-.7.3-1 1-.3 1-.3l-.7-.6z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M288.1 279.7s-.6.6-.4 1.2l.6 1s0-.7.3-1 1-.3 1-.3l-.7-.6z"/>
|
||||||
|
<path fill="#db4446" d="M291.3 280.6s-.3.8.3 1.3q1 .8 1.1.6c.1-.2-.5-.8-.3-1.3q.3-.6.5-.7l-.8-.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M291.3 280.6s-.3.8.3 1.3q1 .8 1.1.6c.1-.2-.5-.8-.3-1.3q.3-.6.5-.7l-.8-.2z"/>
|
||||||
|
<path fill="#ffd691" stroke="#000" stroke-width=".5" d="M258.7 337.5q3.2 1.1 3.2 4c0 2.5-2.3 4.3-5.4 4.3-3 0-5.6-1.8-5.6-4.2 0-1.9 1-4 3.2-4l-.3-.6-.6-.7h1.3l.7.5.6-.7.7-.6.6.7.3.6s.4-.4.8-.5l.9-.3-.3.7-.1.8"/>
|
||||||
|
<path fill="#058e6e" stroke="#000" stroke-width=".5" d="M256 348.5s-4-2.8-5.8-3.1c-2.3-.5-4.8-.1-5.9-.2l1.9 1.5a11 11 0 0 0 3.5 2c3.3.8 6.3-.2 6.3-.2m1.2.2s2.6-2.7 5.3-3c3.3-.5 5.4.2 6.6.5 0 0-1 .5-1.6 1-.5.3-2 1.5-4.2 1.6-2.2 0-4.7-.3-5.1-.2l-1 .1"/>
|
||||||
|
<path fill="#ad1519" stroke="#000" stroke-width=".5" d="M256.4 345.4a5.2 5.2 0 0 1 0-7.6 5 5 0 0 1 1.7 3.8 5 5 0 0 1-1.7 3.8"/>
|
||||||
|
<path fill="#058e6e" stroke="#000" stroke-width=".5" d="M255.4 351s.6-1.6.7-3l-.2-2.2h.8s.4 1.2.4 2.2l-.2 2.5-.7.1-.8.3"/>
|
||||||
|
<path fill="#fff" d="M307 203.4q0-1 1-1 1.1 0 1.2 1c.1 1-.5 1.1-1.2 1.1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M307 203.4q0-1 1-1 1.1 0 1.2 1c.1 1-.5 1.1-1.2 1.1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M308.4 200.7q.2-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M308.4 200.7q.2-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M309.5 197.6q.1-1 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M309.5 197.6q.1-1 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M309.6 194.2q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1-1.1 0-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M309.6 194.2q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1-1.1 0-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M308.8 190.9q.1-1 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M308.8 190.9q.1-1 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M307 187.8q0-.9 1.2-1a1 1 0 0 1 1 1q0 1-1 1.1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M307 187.8q0-.9 1.2-1a1 1 0 0 1 1 1q0 1-1 1.1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M304.7 185.4q0-1 1.2-1a1 1 0 0 1 1 1q0 .9-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M304.7 185.4q0-1 1.2-1a1 1 0 0 1 1 1q0 .9-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M302 183.3q.2-1 1.2-1 1.3 0 1.1 1c-.2 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M302 183.3q.2-1 1.2-1 1.3 0 1.1 1c-.2 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M298.9 181.6q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M298.9 181.6q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M295.5 180.4q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M295.5 180.4q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M291.8 179.8q.1-1 1.1-1.1 1.1.1 1.2 1t-1.2 1.1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M291.8 179.8q.1-1 1.1-1.1 1.1.1 1.2 1t-1.2 1.1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M288.3 179.6q.1-1 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M288.3 179.6q.1-1 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M284.9 179.7q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M284.9 179.7q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M281.4 179.7q.1-1 1.1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M281.4 179.7q.1-1 1.1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M283.1 182.7q.1-1 1.1-1.1 1.1.1 1.2 1-.1 1-1.1 1.1a1 1 0 0 1-1.2-1m.7 3.2q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1m.2 3.3q.1-1 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1a1 1 0 0 1-1.1-1m-1 3q0-1 1-1.1a1 1 0 0 1 1.2 1q-.1 1-1.1 1.1a1 1 0 0 1-1.2-1m-1.9 2.7q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="#fff" d="M278.8 177.6q.1-.9 1.1-1t1.1 1c.1 1-.5 1.1-1 1.1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M278.8 177.6q.1-.9 1.1-1t1.1 1c.1 1-.5 1.1-1 1.1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M275.7 176q.1-1 1.1-1.1 1.1 0 1.2 1c.1 1-.5 1.1-1.1 1.1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M275.7 176q.1-1 1.1-1.1 1.1 0 1.2 1c.1 1-.5 1.1-1.1 1.1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M272.3 175q0-1 1.2-1.1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M272.3 175q0-1 1.2-1.1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M268.8 174.3q.1-1 1.1-1 1.1 0 1.2 1c.1 1-.5 1-1.2 1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M268.8 174.3q.1-1 1.1-1 1.1 0 1.2 1c.1 1-.5 1-1.2 1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M265.4 174.4q.1-1 1.1-1t1.1 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M265.4 174.4q.1-1 1.1-1t1.1 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M261.8 175q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M261.8 175q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M258.5 176.1q.1-1 1.1-1t1.1 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M258.5 176.1q.1-1 1.1-1t1.1 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M202.3 203.4q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1.1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M202.3 203.4q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1.1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M200.8 200.7q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M200.8 200.7q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M199.7 197.6q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M199.7 197.6q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M199.6 194.2q.1-1 1.1-1 1.1 0 1.2 1c.1 1-.5 1-1.2 1q-1 0-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M199.6 194.2q.1-1 1.1-1 1.1 0 1.2 1c.1 1-.5 1-1.2 1q-1 0-1-1z"/>
|
||||||
|
<path fill="#fff" d="M200.4 190.9q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M200.4 190.9q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M202.2 187.8q.1-.9 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M202.2 187.8q.1-.9 1-1a1 1 0 0 1 1.2 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M204.5 185.4q.1-1 1.1-1t1.1 1q-.1.9-1.1 1t-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M204.5 185.4q.1-1 1.1-1t1.1 1q-.1.9-1.1 1t-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M207.2 183.3q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M207.2 183.3q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M210.3 181.6q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M210.3 181.6q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M213.7 180.4q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M213.7 180.4q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M217.4 179.8q.1-1 1.1-1.1a1 1 0 0 1 1.1 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M217.4 179.8q.1-1 1.1-1.1a1 1 0 0 1 1.1 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M220.9 179.6q.1-1 1.1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M220.9 179.6q.1-1 1.1-1t1.1 1c.1 1-.5 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M224.3 179.7q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M224.3 179.7q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M227.8 179.7q.1-1 1.1-1t1.2 1c.2 1-.5 1-1.2 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M227.8 179.7q.1-1 1.1-1t1.2 1c.2 1-.5 1-1.2 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".4" d="M226 182.7q.1-1 1.2-1.1c1.1-.1 1.1.5 1.1 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1m-.7 3.2q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1q-1 0-1.1-1m-.2 3.3q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1m1 3q.2-1 1.2-1.1a1 1 0 0 1 1.1 1q-.1 1-1.1 1.1a1 1 0 0 1-1.1-1m1.9 2.7q.1-1 1-1 1.1 0 1.2 1c.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="#fff" d="M230.4 177.6q.1-.9 1.1-1 1.1 0 1.2 1c.1 1-.5 1.1-1.2 1.1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M230.4 177.6q.1-.9 1.1-1 1.1 0 1.2 1c.1 1-.5 1.1-1.2 1.1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M233.5 176q.1-1 1.1-1.1 1 0 1.1 1c.1 1-.5 1.1-1 1.1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M233.5 176q.1-1 1.1-1.1 1 0 1.1 1c.1 1-.5 1.1-1 1.1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M236.9 175q.1-1 1-1.1 1.3 0 1.2 1c-.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M236.9 175q.1-1 1-1.1 1.3 0 1.2 1c-.1 1-.5 1-1.1 1a1 1 0 0 1-1.1-1z"/>
|
||||||
|
<path fill="#fff" d="M240.4 174.3q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M240.4 174.3q.1-1 1.1-1a1 1 0 0 1 1.1 1q-.1 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M243.8 174.4q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M243.8 174.4q0-1 1.2-1a1 1 0 0 1 1 1q0 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#fff" d="M247.4 175q.1-1 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1a1 1 0 0 1-1-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M247.4 175q.1-1 1.1-1a1 1 0 0 1 1.2 1q0 1-1.2 1a1 1 0 0 1-1-1z"/>
|
||||||
|
<path fill="#fff" d="M250.7 176.1q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".4" d="M250.7 176.1q0-1 1.2-1 1 0 1 1c0 1-.4 1-1 1a1 1 0 0 1-1.2-1z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="M222.3 244.1h-1v-1h-1.6v3.9h1.6v2.6h-3.5v7.5h1.9v15.2h-3.8v7.8h29v-7.8h-3.7v-15.2h1.9v-7.5h-3.6V247h1.7v-3.8h-1.7v1h-1v-1H237v1h-1.2v-1h-1.6v3.8h1.6v2.6h-3.5v-8.3h1.9v-3.8h-1.9v1h-1v-1h-1.6v1h-1v-1h-1.9v3.8h2v8.3H225V247h1.7v-3.8H225v1h-1v-1h-1.8v1zm-6.4 36h29m-29-1.9h29m-29-1.9h29m-29-1.9h29m-29-2.1h29m-25.2-1.7h21.5m-21.5-1.9h21.5m-21.5-2h21.5m-21.5-2h21.5m-21.5-1.9h21.5m-21.5-1.9h21.5m-21.5-1.9h21.5m-23.4-1.9H243m-25.3-1.8H243m-25.3-2H243m-25.3-1.8H243m-21.8-2h18.2m-10.9-1.8h3.6m-3.5-2h3.5m-3.5-1.8h3.5m-3.5-1.9h3.5m-5.4-2.4h7.3m-12.8 8h3.8m-5.4-2.3h7m-7 34.8v-1.9m0-1.9v-1.9m-2 2v1.8m3.6 0v-1.9m2 3.8v-1.9m0-1.9v-1.9m0-2.1v-1.7m0-1.9v-2m-2 7.7v-2.1m-3.5 2.1v-2.1m7.3 0v2.1m1.7-2.1v-1.7m-5.5-1.9v2m3.8-2v2m3.5-2v2m-1.8-2v-2m1.8-2v2m0-5.8v2m-1.8-3.9v2m1.8-3.8v1.8m-3.5-1.9v2m-3.8-2v2m-1.6-3.8v1.9m3.5-2v2m3.6-2v2m1.9-3.8v1.9m-3.6-2v2m-3.8-2v2m-1.6-3.8v1.9m7-2v2m-3.5-5.7v1.9m16.3-2h-3.7m5.4-2.3H234m7 34.8v-1.9m0-1.9v-1.9m2 2v1.8m-3.6 0v-1.9m-1.9 3.8v-1.9m0-1.9v-1.9m0-2.1v-1.7m0-1.9v-2m2 7.7v-2.1m3.5 2.1v-2.1m-7.3 0v2.1m-1.7-2.1v-1.7m5.4-1.9v2m-3.7-2v2m-3.6-2v2m1.9-2v-2m-1.9-2v2m0-5.8v2m1.9-3.9v2m-1.9-3.8v1.8m3.6-1.9v2m3.8-2v2m1.6-3.8v1.9m-3.5-2v2m-3.6-2v2m-1.9-3.8v1.9m3.6-2v2m3.7-2v2m1.7-3.8v1.9m-7.1-2v2m3.6-5.7v1.9m-7.4 19.1v-2m0-5.8V259m0 5.7v-1.9m0-5.7v-1.8m0-2v-1.8m0-3.8v-2m0-1.8V242m-9 5h3.8m3.6-5.7h3.5m3.6 5.7h3.7"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="M235.3 280.1v-5c0-1-.5-3.8-5-3.8-4.3 0-4.7 2.9-4.7 3.8v5z"/>
|
||||||
|
<path fill="#c8b100" stroke="#000" stroke-width=".5" d="m227.2 275.4-2.3-.3c0-1 .2-2.3 1-2.8l2 1.7c-.2.2-.7.9-.7 1.4zm6.4 0 2.4-.3c0-1-.3-2.3-1-2.8l-2 1.7c.1.2.6.9.6 1.4zm-2.4-2.4 1.2-2.1-2-.5q-1 0-2 .5l1.2 2.1zm-4.4-6v-5c0-1.4-1-2.6-2.6-2.6s-2.6 1.2-2.6 2.6v5.2h5.2zm7.3 0v-5c0-1.4 1-2.6 2.6-2.6s2.6 1.2 2.6 2.6v5.2h-5.2zm-1.9-12.7.5-4.7h-4.5l.2 4.7zm3.5 0-.4-4.7h4.7l-.5 4.7zm-10.6 0 .2-4.7h-4.5l.5 4.7z"/>
|
||||||
|
<path fill="#0039f0" d="M233.6 280.1v-4.3c0-.7-.4-2.8-3.3-2.8a3 3 0 0 0-3 2.8v4.3zm-7.3-13.5v-4.5c0-1.2-.7-2.4-2.1-2.4s-2.2 1.2-2.2 2.4v4.5zm8.3 0v-4.5c0-1.2.7-2.4 2.1-2.4s2.1 1.2 2.1 2.4v4.5z"/>
|
||||||
|
<path fill="#ad1519" d="M239.5 287.8c0-10.4 7.5-18.8 16.6-18.8s16.7 8.4 16.7 18.7c0 10.4-7.5 18.8-16.7 18.8s-16.6-8.4-16.6-18.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".6" d="M239.5 287.8c0-10.4 7.5-18.8 16.6-18.8s16.7 8.4 16.7 18.7c0 10.4-7.5 18.8-16.7 18.8s-16.6-8.4-16.6-18.7z"/>
|
||||||
|
<path fill="#005bbf" d="M244.4 287.7c0-7.6 5.3-13.7 11.7-13.7 6.5 0 11.8 6.1 11.8 13.7s-5.3 13.7-11.7 13.7c-6.5 0-11.8-6.1-11.8-13.7"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".6" d="M244.4 287.7c0-7.6 5.3-13.7 11.7-13.7 6.5 0 11.8 6.1 11.8 13.7s-5.3 13.7-11.7 13.7c-6.5 0-11.8-6.1-11.8-13.7z"/>
|
||||||
|
<path fill="#c8b100" d="M250.6 278.3s-1.4 1.5-1.4 2.9.6 2.6.6 2.6a2 2 0 0 0-1.4-1 1.5 1.5 0 0 0-1.6 1.5l.3.8.5 1q.3-.6 1-.6 1 .1 1 1v.3h-1.2v1h1.1l-.8 1.7 1-.4 1 1 .8-1 1 .4-.7-1.6h1v-1.1h-1.2v-.3q.1-.9 1-1 1.2 0 1 .6l.6-1 .2-.8q0-1.4-1.5-1.5-1 0-1.4 1s.5-1.2.5-2.6-1.4-3-1.4-3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M250.6 278.3s-1.4 1.5-1.4 2.9.6 2.6.6 2.6a2 2 0 0 0-1.4-1 1.5 1.5 0 0 0-1.6 1.5l.3.8.5 1q.3-.6 1-.6 1 .1 1 1v.3h-1.2v1h1.1l-.8 1.7 1-.4 1 1 .8-1 1 .4-.7-1.6h1v-1.1h-1.2v-.3q.1-.9 1-1 1.2 0 1 .6l.6-1 .2-.8q0-1.4-1.5-1.5-1 0-1.4 1s.5-1.2.5-2.6-1.4-3-1.4-3z"/>
|
||||||
|
<path fill="#c8b100" d="M248.4 287.9h4.5v-1.1h-4.5v1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M248.4 287.9h4.5v-1.1h-4.5v1z"/>
|
||||||
|
<path fill="#c8b100" d="M261.5 278.3s-1.4 1.5-1.4 2.9.6 2.6.6 2.6q-.4-1-1.4-1-1.5.2-1.6 1.5l.3.8.5 1q.2-.6 1-.6a1 1 0 0 1 1 1v.3h-1.2v1h1.1l-.8 1.7 1-.4.9 1 .9-1 1 .4-.8-1.6h1.1v-1.1h-1.2v-.3q.1-.9 1-1c.9-.1.9.2 1 .6l.6-1 .2-.8q-.1-1.4-1.5-1.5-1.1 0-1.5 1s.6-1.2.6-2.6-1.4-3-1.4-3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M261.5 278.3s-1.4 1.5-1.4 2.9.6 2.6.6 2.6q-.4-1-1.4-1-1.5.2-1.6 1.5l.3.8.5 1q.2-.6 1-.6a1 1 0 0 1 1 1v.3h-1.2v1h1.1l-.8 1.7 1-.4.9 1 .9-1 1 .4-.8-1.6h1.1v-1.1h-1.2v-.3q.1-.9 1-1c.9-.1.9.2 1 .6l.6-1 .2-.8q-.1-1.4-1.5-1.5-1.1 0-1.5 1s.6-1.2.6-2.6-1.4-3-1.4-3z"/>
|
||||||
|
<path fill="#c8b100" d="M259.3 287.9h4.5v-1.1h-4.5v1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M259.3 287.9h4.5v-1.1h-4.5v1z"/>
|
||||||
|
<path fill="#c8b100" d="M256 287.6s-1.3 1.5-1.3 3c0 1.4.6 2.6.6 2.6a2 2 0 0 0-1.5-1 1.5 1.5 0 0 0-1.5 1.4l.2.8.5 1q.3-.5 1-.5 1 0 1.1 1v.3h-1.2v1h1l-.8 1.7 1.1-.5.9 1 .8-1 1.1.5-.8-1.7h1.1v-1h-1.2v-.3q0-1 1-1 .8 0 1 .5l.5-1 .3-.8q-.1-1.3-1.6-1.4a2 2 0 0 0-1.4 1s.6-1.2.6-2.7c0-1.4-1.4-2.9-1.4-2.9"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-linejoin="round" stroke-width=".3" d="M256 287.6s-1.3 1.5-1.3 3c0 1.4.6 2.6.6 2.6a2 2 0 0 0-1.5-1 1.5 1.5 0 0 0-1.5 1.4l.2.8.5 1q.3-.5 1-.5 1 0 1.1 1v.3h-1.2v1h1l-.8 1.7 1.1-.5.9 1 .8-1 1.1.5-.8-1.7h1.1v-1h-1.2v-.3q0-1 1-1 .8 0 1 .5l.5-1 .3-.8q-.1-1.3-1.6-1.4a2 2 0 0 0-1.4 1s.6-1.2.6-2.7c0-1.4-1.4-2.9-1.4-2.9z"/>
|
||||||
|
<path fill="#c8b100" d="M253.8 297.2h4.5v-1h-4.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M253.8 297.2h4.5v-1h-4.5z"/>
|
||||||
|
<path fill="#c8b100" d="M289.4 238.3h-.3l-.3.4q-.5.4-.9 0l-.1-.4h-.5q-.5-.2-.2-.8l.1-.2v-.3h-.3l-.1.3q-.5.4-.8.1l-.1-.2h-.2c-.5.2-.7-1-.8-1.3l-.1.3v1.3l-.3 1.2q1.4.3 3 1.7a10 10 0 0 1 2.5 2.5l1.2-.6c.6-.2 1.4-.2 1.4-.2l.2-.2c-.3 0-1.6.1-1.6-.4l.1-.2h-.3q-.3-.3 0-.8h.2v-.4h-.3l-.2.1q-.5.5-.9 0v-.4h-.6a1 1 0 0 1 0-.9l.2-.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M289.4 238.3h-.3l-.3.4q-.5.4-.9 0l-.1-.4h-.5q-.5-.2-.2-.8l.1-.2v-.3h-.3l-.1.3q-.5.4-.8.1l-.1-.2h-.2c-.5.2-.7-1-.8-1.3l-.1.3v1.3l-.3 1.2q1.4.3 3 1.7a10 10 0 0 1 2.5 2.5l1.2-.6c.6-.2 1.4-.2 1.4-.2l.2-.2c-.3 0-1.6.1-1.6-.4l.1-.2h-.3q-.3-.3 0-.8h.2v-.4h-.3l-.2.1q-.5.5-.9 0v-.4h-.6a1 1 0 0 1 0-.9l.2-.3z"/>
|
||||||
|
<path d="M287 239h.3s.1.2 0 .2h-.2v-.2"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M287 239h.3s.1.2 0 .2h-.2v-.2z"/>
|
||||||
|
<path d="m288 239.8-.3-.2v-.2h.2l.3.3.3.2s.1.1 0 .2h-.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="m288 239.8-.3-.2v-.2h.2l.3.3.3.2s.1.1 0 .2h-.1l-.4-.3"/>
|
||||||
|
<path d="m286.3 238.6-.3-.2s-.1 0 0-.1h.1l.3.1.2.2.1.1h-.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="m286.3 238.6-.3-.2s-.1 0 0-.1h.1l.3.1.2.2.1.1h-.2l-.2-.1"/>
|
||||||
|
<path d="M285.2 237.9h.2l.1.2h-.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M285.2 237.9h.2l.1.2h-.2z"/>
|
||||||
|
<path d="M289.1 240.6v-.3h-.3v.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M289.1 240.6v-.3h-.3v.3z"/>
|
||||||
|
<path d="m289.7 241.2.2.2q.1.1.2 0l-.2-.3-.2-.2h-.2v.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="m289.7 241.2.2.2q.1.1.2 0l-.2-.3-.2-.2h-.2v.1l.2.2"/>
|
||||||
|
<path d="M290.7 242v-.2h-.3v.3h.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M290.7 242v-.2h-.3v.3h.3z"/>
|
||||||
|
<path fill="#c8b100" d="M287.9 235.9h-.6l-.2.9.1.1h.2l.7-.5z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M287.9 235.9h-.6l-.2.9.1.1h.2l.7-.5-.2-.5"/>
|
||||||
|
<path fill="#c8b100" d="M286.2 236.4v.5l1 .2v-.3l-.5-.7z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M286.2 236.4v.5l1 .2v-.3l-.5-.7-.5.3"/>
|
||||||
|
<path fill="#c8b100" d="m288.2 237.5-.5.3-.6-.8v-.1h1.1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m288.2 237.5-.5.3-.6-.8v-.1h1.1v.6"/>
|
||||||
|
<path fill="#c8b100" d="m287 236.8.3-.1.1.4h-.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m287 236.8.3-.1.1.4h-.3z"/>
|
||||||
|
<path fill="#c8b100" d="m284.8 235.9-.3-.8-.4-.4s.4-.2.9.1 0 .9 0 .9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m284.8 235.9-.3-.8-.4-.4s.4-.2.9.1 0 .9 0 .9z"/>
|
||||||
|
<path fill="#c8b100" d="m285.8 236.2-.4.4-.7-.6v-.3h1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m285.8 236.2-.4.4-.7-.6v-.3h1l.1.5"/>
|
||||||
|
<path fill="#c8b100" d="m284.6 235.8.3-.2q.2 0 .1.3 0 .3-.3.3v-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m284.6 235.8.3-.2q.2 0 .1.3 0 .3-.3.3v-.4z"/>
|
||||||
|
<path fill="#c8b100" d="M290.2 237.3h-.6l-.3.8v.2h.2l.9-.4z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M290.2 237.3h-.6l-.3.8v.2h.2l.9-.4-.2-.6"/>
|
||||||
|
<path fill="#c8b100" d="m288.5 237.6-.1.6.9.2h.1v-.2l-.4-.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m288.5 237.6-.1.6.9.2h.1v-.2l-.4-.8-.5.2"/>
|
||||||
|
<path fill="#c8b100" d="m290.3 239-.6.2-.4-.8v-.2h.1l1 .2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m290.3 239-.6.2-.4-.8v-.2h.1l1 .2-.1.6"/>
|
||||||
|
<path fill="#c8b100" d="M289.1 238.1q.2-.2.4 0l.1.4h-.4v-.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M289.1 238.1q.2-.2.4 0l.1.4h-.4v-.4z"/>
|
||||||
|
<path fill="#c8b100" d="m292.2 239.2.1.6-.9.3h-.1v-.2l.3-.8z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m292.2 239.2.1.6-.9.3h-.1v-.2l.3-.8.6.1"/>
|
||||||
|
<path fill="#c8b100" d="m292 240.8-.5.2-.3-.9v-.1h.2l.8.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m292 240.8-.5.2-.3-.9v-.1h.2l.8.3-.1.5"/>
|
||||||
|
<path fill="#c8b100" d="m290.5 239.3-.2.5.9.3h.1v-.1l-.2-.9z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m290.5 239.3-.2.5.9.3h.1v-.1l-.2-.9-.6.2"/>
|
||||||
|
<path fill="#c8b100" d="M291.5 240.3v-.4a.3.3 0 0 0-.5 0v.3h.4"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M291.5 240.3v-.4a.3.3 0 0 0-.5 0v.3h.4z"/>
|
||||||
|
<path fill="#c8b100" d="m292.9 242.1.8.1.5.3s.1-.5-.3-.8-.9.2-.9.2z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m292.9 242.1.8.1.5.3s.1-.5-.3-.8-.9.2-.9.2z"/>
|
||||||
|
<path fill="#c8b100" d="m292.3 241.2-.3.5.8.5v-.1h.2l-.1-1z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="m292.3 241.2-.3.5.8.5v-.1h.2l-.1-1h-.6"/>
|
||||||
|
<path fill="#c8b100" d="M293 242.2s.2-.2.1-.3h-.4q-.2 0-.1.3z"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".3" d="M293 242.2s.2-.2.1-.3h-.4q-.2 0-.1.3zm40.7-23.4v.6H331v-.6h1v-1.4h-.7v-.6h.6v-.5h.7v.5h.6v.6h-.6v1.4h1"/>
|
||||||
|
<path fill="none" d="M179.3 231.6v-1.3m-.3 1.3v-1.3m-.3 1.3v-1.3m-.4 1.3v-1.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M178 231.6v-1.3m-.5 1.2v-1.2m.2 1.2v-1.2m-.7 1.2v-1.1m.2 1v-1m-.9 1v-1m.2 1v-1m.2 1v-1m-.7 1v-1m-.2.9v-.8m-.2.8v-.8m-.6.8v-.7m.3.7v-.7m-.5.6v-.6m-.2.5v-.4m-.3.3v-.3m-.3.3v-.2"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="M173.8 231v-.1"/>
|
||||||
|
<path fill="none" d="M180.8 231.5v-1.2m-.7 1.2v-1.2m-.4 1.3v-1.3m152.6 1.3v-1.3m-.3 1.3v-1.3m-.4 1.3v-1.3m-.4 1.3v-1.3"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".1" d="M331 231.6v-1.3m-.6 1.2v-1.1m.3 1.1v-1.2m-.8 1.2v-1.1m.2 1.1v-1.2m-.9 1.1v-1m.2 1v-1m.3 1v-1m-.7 1v-1m-.3 1v-.9m-.2.8v-.8m-.5.8v-.7m.2.7v-.7m-.5.6v-.5m-.2.5v-.5m-.3.4v-.4m-.2.3v-.2"/>
|
||||||
|
<path fill="none" stroke="#000" stroke-width=".2" d="M326.7 231v-.1"/>
|
||||||
|
<path fill="none" d="M333.7 231.5v-1.2m-.6 1.3v-1.3m-.5 1.3v-1.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 80 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-fi" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#002f6c" d="M0 186.2h512v139.6H0z"/>
|
||||||
|
<path fill="#002f6c" d="M123.2 0h139.6v512H123.1z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 237 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-fr" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#000091" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#e1000f" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 231 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gb" viewBox="0 0 512 512">
|
||||||
|
<path fill="#012169" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#FFF" d="M512 0v64L322 256l190 187v69h-67L254 324 68 512H0v-68l186-187L0 74V0h62l192 188L440 0z"/>
|
||||||
|
<path fill="#C8102E" d="m184 324 11 34L42 512H0v-3zm124-12 54 8 150 147v45zM512 0 320 196l-4-44L466 0zM0 1l193 189-59-8L0 49z"/>
|
||||||
|
<path fill="#FFF" d="M176 0v512h160V0zM0 176v160h512V176z"/>
|
||||||
|
<path fill="#C8102E" d="M0 208v96h512v-96zM208 0v512h96V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-gr" viewBox="0 0 512 512">
|
||||||
|
<path fill="#0d5eaf" fill-rule="evenodd" d="M0 0h512v57H0z"/>
|
||||||
|
<path fill="#fff" fill-rule="evenodd" d="M0 57h512v57H0z"/>
|
||||||
|
<path fill="#0d5eaf" fill-rule="evenodd" d="M0 114h512v57H0z"/>
|
||||||
|
<path fill="#fff" fill-rule="evenodd" d="M0 171h512v57H0z"/>
|
||||||
|
<path fill="#0d5eaf" fill-rule="evenodd" d="M0 228h512v56.9H0z"/>
|
||||||
|
<path fill="#fff" fill-rule="evenodd" d="M0 284.9h512v57H0z"/>
|
||||||
|
<path fill="#0d5eaf" fill-rule="evenodd" d="M0 341.9h512v57H0z"/>
|
||||||
|
<path fill="#fff" fill-rule="evenodd" d="M0 398.9h512v57H0z"/>
|
||||||
|
<path fill="#0d5eaf" d="M0 0h284.9v284.9H0z"/>
|
||||||
|
<g fill="#fff" fill-rule="evenodd" stroke-width="1.3">
|
||||||
|
<path d="M114 0h57v284.9h-57z"/>
|
||||||
|
<path d="M0 114h284.9v57H0z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#0d5eaf" fill-rule="evenodd" d="M0 455h512v57H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 861 B |
@@ -0,0 +1,56 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-hr" viewBox="0 0 512 512">
|
||||||
|
<path fill="#171796" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#fff" d="M0 0h512v341.3H0z"/>
|
||||||
|
<path fill="red" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="red" d="M256 388.4a100 100 0 0 0 99.6-99.5V170.6H156.5V289a100 100 0 0 0 99.5 99.5z"/>
|
||||||
|
<path fill="#fff" d="M256 386.8a98 98 0 0 0 97.7-97.6V172.5H158.4v116.7a98 98 0 0 0 97.6 97.6"/>
|
||||||
|
<path fill="red" d="M199.6 176.2H162v41.3h37.6zm0 82.6h37.6v-41.3h-37.6zM162 289q0 5.6.7 11h36.9v-41.2H162zm112.8-30.2h-37.6V300h37.6zm0 82.5h37.5V300h-37.6zm37.5 22.6a95 95 0 0 0 21.5-22.6h-21.5V364zm-112.7-22.6h37.6V300h-37.6zm-21.5 0a95 95 0 0 0 21.5 22.6v-22.6zm84.4 41.3a93 93 0 0 0 12.2-1.7v-39.6h-37.5V381a93 93 0 0 0 12.2 1.7q6.6.4 13.1 0zm86.7-82.6q.6-5.4.7-11v-30.2h-37.6V300zm-74.4-41.2h37.5v-41.3h-37.6zm0-82.6h-37.6v41.3h37.6zm75 41.3v-41.3h-37.5v41.3z"/>
|
||||||
|
<path fill="#fff" d="m352 169.4 23.3-52.8L357.5 88l-29.4 11-20.6-23.7-27.2 15.6L256 71l-24.3 19.8-27.2-15.6L184 98.8 154.5 88l-17.8 28.7 23.3 52.8a232 232 0 0 1 96-20.6c34.2 0 66.7 7.4 96 20.6z"/>
|
||||||
|
<path fill="#0093dd" d="m184.5 101.2-29.2-10.8-16.3 26.4 6.3 14.2 15.8 36a234 234 0 0 1 36.9-12.8z"/>
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".3" d="M182.9 127.2a14 14 0 0 1 1.6 6.6c0 7.9-6.4 14.3-14.3 14.3a14 14 0 0 1-14-11.5 14 14 0 0 0 12.5 7.3 14.3 14.3 0 0 0 14.2-16.7z"/>
|
||||||
|
<path d="M169.8 107.5v.2l-1 5.2 3.9 3.4.2.1-.3.1-4.9 1.7-1 5.1v.3l-.2-.2-4-3.4-4.9 1.7h-.2v-.2l1-5.1-3.9-3.4-.2-.2h.2l5-1.8 1-5v-.3l.2.1 4 3.5 4.9-1.7z"/>
|
||||||
|
<path fill="#f7db17" d="m163.9 114.8 3.7 3.2 4.6-1.6zm0-.2 8.3 1.6-3.7-3.2zm-8.9-1.7 8.3 1.6-3.7-3.2zm8.3 1.7L155 113l3.6 3.2zm.4-.3 5.6-6.3-4.6 1.5zm-6 6.9 5.6-6.4-4.6 1.6zm5.8-6.3-5.6 6.4 4.6-1.6zm5.9-6.8-5.5 6.3 4.6-1.5zm-6 6.3-2.7-8-1 4.7zm3 8.5-2.8-8-1 4.8zm-2.6-8 2.7 8 1-4.8zm-3-8.6 2.8 8 .9-4.8z"/>
|
||||||
|
<path fill="#171796" d="m232 93.2-27-15.4-20.5 23.4 13.5 53a234 234 0 0 1 38.5-6.5z"/>
|
||||||
|
<path fill="red" d="M194.7 141a248 248 0 0 1 40.7-6.8l-1.1-13.8a262 262 0 0 0-43 7.2zm-6.8-26.6a275 275 0 0 1 45.2-7.6L232 93.3a289 289 0 0 0-47.5 8l3.4 13z"/>
|
||||||
|
<g transform="translate(-256)scale(.00253)">
|
||||||
|
<path fill="#0093dd" d="m212105 36890-23 13-9517-7794-9497 7778 1788 21560c2543-210 5113-322 7709-322 2608 0 5190 113 7744 325l1795-21560z"/>
|
||||||
|
<g id="hr-a">
|
||||||
|
<path d="M202545 46585c-18-2-44 10-69 45-186 250-359 469-545 720-195 61-242 180-167 348-261-26-291 193-302 432-250-379-522-482-814-307-11-230-187-338-439-392-180-10-319-65-436-145-60-42-110-64-170-106-126-88-226-5-172 74 267 434 535 868 802 1302-14 80 6 151 88 204 47 133 93 265 140 397-11 38-21 75-32 113-221-105-443-118-664-133-170-8-287-50-361-137-54-63-91-26-92 82-3 534 162 1014 599 1492-231 4-462 11-694 21-79 6-95 39-73 104 126 304 339 579 822 766-208 112-327 285-357 520-9 224-75 382-212 455-60 32-81 65-24 106 253 185 565 193 895 112-157 270-226 553-198 850 208 56 412 15 614-52-29 61-44 175-52 309-7 115-41 229-104 343-32 33-65 84 4 102 336 91 648 52 915-47 0 243 2 487 76 727 18 58 70 102 125 26 155-214 322-396 527-517 31 90 75 168 156 215 96 55 147 170 153 343 0 30-2 60 35 90 149 7 514-380 589-597 206 121 284 246 439 461 55 76 99 29 128-25 62-243 67-481 66-724 267 99 579 138 915 47 69-19 36-70 4-102-62-114-105-250-113-365-9-133-14-226-43-287 202 68 405 108 614 52 29-297-53-579-211-850 330 80 655 73 908-112 57-41 35-74-24-106-136-73-203-231-212-455-30-235-149-409-357-520 483-187 696-463 822-766 22-66 6-99-73-104-231-10-480-24-711-27 437-478 606-961 604-1495-1-108-38-146-92-82-74 87-179 137-348 146-222 15-435 24-656 128-11-38-21-75-32-113 46-132 106-260 153-393 82-53 102-123 88-204 267-434 513-868 781-1302 54-79-46-162-171-74-60 42-110 64-170 106-117 80-257 134-437 145-251 54-417 167-428 397-293-175-564-73-814 307-11-239-41-457-302-432 75-168 17-291-178-352-186-250-458-470-644-720-31-35-51-47-69-45z"/>
|
||||||
|
<g fill="#f7db17">
|
||||||
|
<path d="M205075 47978c-51-26-124 17-162 95s-33 170 19 196c40 20 84-6 119-56l22-36c2-3 4-6 5-9 38-78 49-163-2-188zm-5008 0c52-26 124 17 162 95s39 165-13 191-103-24-141-102-60-158-9-184zm4539 905c-32 0-59 27-59 59s26 59 59 59 59-26 59-59c0-32-26-59-59-59m-4032 0a59.1 59.1 0 1 0 .2 118.2 59.1 59.1 0 0 0-.2-118.2m4294-304c-754-91-1506-133-2260-133s-1509 41-2269 115c-26 8-21 90 14 86 756-73 1507-113 2256-113 743 0 1485 40 2228 129 39 4 54-80 32-84z"/>
|
||||||
|
<path d="M200319 48495c768-75 1530-117 2289-116 754 0 1507 42 2261 133l111-184c-32 10-62 9-90-5-76-38-92-161-36-274 56-114 164-175 240-138 39 19 62 62 68 114l446-739c-204 130-328 214-581 252-281 41-409 139-368 307 38 156-57 133-201 54-314-171-541 71-652 353-73 186-159 181-168-13-4-70 0-131-7-200-21-223-89-286-216-224-161 78-175 25-137-58 28-60 86-128 66-221-9-67-66-92-151-98-182-244-467-483-649-727-182 244-374 483-556 727-86 5-142 30-152 98-20 93 52 157 80 217 38 82 23 135-137 57-127-61-186-3-207 220-7 69-10 139-13 209-9 194-95 199-168 13-111-282-352-524-667-353-145 79-203 102-182-54 23-172-107-266-388-307-253-37-377-122-581-252l419 682c12-25 29-45 53-57 76-38 184 24 240 138 56 113 40 237-36 274-10 5-21 8-32 10l100 163zm4389 911c-7 3-7 4-24 11-46 19-80 66-134 124-57 60-128 125-211 188-12 10-25 19-44-6s-7-35 6-44c80-62 149-124 204-182 30-32 56-63 77-92-95-11-190-21-284-30-79 24-157 55-222 95-59 35-107 77-137 125-8 14-16 27-44 11-27-16-19-30-11-44 35-58 91-107 158-147 33-20 69-38 106-54-107-9-214-18-321-25-22 13-42 29-61 47-20 19-39 42-56 67-9 13-18 26-44 8s-18-31-8-44c19-29 41-54 64-77l9-9c-80-5-161-10-241-14-2 2-5 5-8 7a340 340 0 0 0-93 126c-6 15-12 29-41 18-29-12-23-26-17-41 12-29 27-55 45-81 8-11 18-22 27-33-115-5-230-9-344-12-4 5-9 8-14 11q-37.5 22.5-66 51c-28.5 28.5-35 40-48 63-8 14-16 28-43 12-28-16-20-29-12-43 16-28 35-54 59-77 7-7 14-13 21-19-122-2-244-4-365-4q-180 0-360 3c8 7 15 13 22 20 23 23 42 49 59 77 8 14 16 27-12 43s-35 2-44-12q-19.5-34.5-48-63c-28.5-28.5-41-36-66-51-6-3-12-7-15-12-115 2-230 6-345 11 11 11 20 23 29 35 19 25 33 52 45 81 6 15 12 29-17 41s-35-3-41-18c-9-24-22-46-38-67-15-21-34-41-55-59-4-3-7-6-10-10-81 4-162 8-243 13 4 4 9 8 13 12 24 23 45 48 64 77 9 13 18 26-8 44s-35 5-44-8c-18-26-36-48-56-67s-41-35-64-49c-1-1-3-2-5-3-110 7-220 14-330 23 43 18 85 38 122 61 67 40 124 89 158 147 8 14 16 27-11 44-27 16-35 3-44-11-29-48-78-90-137-125-72-44-159-77-246-102h-2c-90 7-179 15-268 24 22 33 51 68 86 106 55 58 124 120 204 182 13 9 25 19 6 44s-32 15-44 6c-83-64-155-128-211-188-37-38-99-111-135-140-196-90-354-127-575-147-153-14-318-9-458-79 36 85 75 164 126 229 53 68 120 121 209 147 8 2 21 16 22 25 28 157 84 286 169 386 52 60 114 110 188 149-75-81-132-166-172-251-67-142-90-286-77-420 1-16 3-32 34-29 32 3 30 19 29 35-11 123 9 256 72 387 56 118 159 237 291 346 24 19 0 63-29 55-154-44-290-123-383-231-89-104-149-237-180-397-94-32-165-90-222-164-47-60-85-131-118-205 28 428 182 801 456 1137 61 75 165 182 255 216 92 35 95 100-20 101-34 1-69 1-105 1 84 31 164 66 233 105 127 73 217 162 224 273 1 16 2 32-29 34-32 2-33-14-34-29-6-86-82-160-192-223-113-65-259-117-402-160-154 0-312-1-459 3 39 28 80 57 131 84 82 44 188 86 343 122 89 21 166 52 233 91 71 42 130 93 177 150 10 12 20 25-5 45s-34 8-45-5c-42-52-95-98-159-135q-91.5-54-216-84c-161-38-272-81-358-128-75-40-131-82-184-123 180 393 450 573 835 689 23 7 43 13 61 19 3 1 6 1 9 2 86 21 175 40 266 55 92 15 166 28 261 37 16 1 32 3 29 34-3 32-19 30-34 29-99-9-174-22-266-38q-87-15-171-33c-26 6-64 9-107 12-232 14-420 225-435 494 0 5 0 11-1 16 88-80 179-157 273-212 117-68 239-103 364-69 15 4 31 8 22 39-8 31-23 27-39 22-106-28-212 3-316 63-108 63-213 158-315 253-24 147-82 285-205 377 61 34 104 65 163 45 86-39 172-78 261-108 91-31 184-52 282-57 16-1 32-1 33 31s-14 32-31 33c-91 4-179 24-264 53-75 26-149 58-222 91 221 47 460-1 667-79 60-22 105-42 133-41 51-30 112-53 172-79 66-28 132-51 182-57 16-2 32-4 35 28 4 32-12 33-28 35-112 13-127 21-222 79 0 21-66 57-126 96-36 24-70 52-87 67-95 86-144 181-188 287-29 70-52 145-68 224 55-108 121-211 201-303 94-108 208-201 345-265 14-7 29-13 42 15 13 29-1 35-15 42-129 60-236 147-324 250-90 103-161 222-219 345-31 64-8 1-42 86 110-122 212-224 323-307 132-100 283-157 418-133 15 3 31 6 26 37s-21 28-37 26c-116-21-250 32-369 121-121 92-244 223-366 361 184 26 366-26 542-85 91-30 183-135 239-152 19-24 38-46 57-67 33-37 67-71 102-100 12-10 24-20 45 4s8 34-4 45c-33 28-65 60-96 94-32 35-62 73-92 113-6 8-13 17-24 16-60 70-151 162-172 240-57 210-25 370-122 576 71-38 128-81 175-134 53-60 94-135 128-230 37-104 95-195 167-270 75-77 165-136 261-172 15-5 30-11 41 19s-4 35-19 41c-87 32-169 86-238 157-66 68-119 151-153 247-37 102-81 183-141 250-44 50-95 91-156 127 52-3 78-10 121-7 79-6 211-66 279-119 66-51 116-120 154-206 6-15 13-29 42-16s23 27 16 42c-42 96-99 174-173 231-56 43-121 75-196 93 161-5 311-42 467-100 65-24 87-168 127-208 32-58 66-112 105-158 47-56 101-101 164-127 15-6 29-12 41 18 12 29-3 35-17 41-52 21-98 60-139 108-36 42-68 93-98 147 10 73-51 228-53 305-7 205-2 409 53 612 53-71 107-134 162-192 0-5 0-10 1-15 18-106 33-219 40-332 7-112 7-223-6-329-2-16-4-32 27-35 32-4 34 12 35 28 14 111 14 226 7 340-6 90-16 180-30 269 54-51 53-51 77-103 37-80 59-159 67-237 9-80 5-157-13-230-4-15-7-31 24-38s35 8 38 24c19 80 25 165 14 252-8 65-24 132-49 199 56-42 114-82 178-122-4-75-5-153-3-227 2-68 7-134 18-190 4-20 7-40 47-33s37 27 33 48c-9 50-14 111-16 177-2 78 0 162 4 243 5 82 49 185 125 230 103 62 158 163 186 274 16-145 17-280 3-400-17-143-55-26Line truncated
|
||||||
|
<path d="M204649 49231c-680-88-1359-113-2041-114-684 0-1369 40-2058 112-20 6-15 33-14 46 2 28 37 35 121 27 643-60 1285-93 1932-93 674 0 1351 21 2038 102 33 9 77-85 22-81z"/>
|
||||||
|
<path fill-rule="evenodd" d="M200570 49160c683-71 1362-110 2038-110 675 0 1349 40 2025 127l31-127c-17 9-37 15-58 15a123 123 0 1 1 113-171l60-170c-724-84-1446-122-2171-122-729 0-1459 38-2193 107l58 164c22-32 59-54 101-54a123 123 0 1 1 0 246c-12 0-25-2-36-6l33 94-2 7zm3067-416a123 123 0 1 0 0 246 123 123 0 1 0 0-246m0 64a59.1 59.1 0 0 0 0 118c32 0 59-27 59-59s-26-59-59-59m-1082-91a123 123 0 1 0 123 123c0-68-55-123-123-123m0 64a59.1 59.1 0 1 0 .2 118.2 59.1 59.1 0 0 0-.2-118.2m-1064-40a123 123 0 1 0-.2 245.8 123 123 0 0 0 .2-245.8m0 64c-33 0-59 26-59 59s26 59 59 59a59.1 59.1 0 0 0 0-118"/>
|
||||||
|
</g>
|
||||||
|
<path d="M202601 47974c-14-68-49-129-100-175-51-47-116-78-187-88-33-4-39-58-7-68 60-20 114-67 157-133 45-69 79-157 95-256 5-34 64-35 69-1 15 84 51 153 97 208 55 66 125 112 193 138 31 12 25 63-8 68-59 9-105 42-141 87-50 62-81 145-100 221-8 33-62 31-69-2zm33-118c20-52 47-103 81-146 28-34 60-64 99-84-51-30-100-70-143-120-28-34-53-73-73-116-19 59-45 112-75 158-31 47-67 86-108 116 50 19 95 47 134 82 34 31 63 68 85 110m799 5115-515 206c-17 7-35 14-48-21-14-34 4-41 21-48l515-206c17-7 35-14 48 21 14 34-4 41-21 48m59-326-604 328c-16 9-33 18-51-15s-1-42 15-51l604-328c16-9 33-18 51 15s1 42-15 51m-1826-65 604 328c16 9 33 18 15 51s-34 24-51 15l-604-328c-16-9-33-18-15-51s34-24 51-15m51 322 515 206c18 7 35 14 21 48-14 35-31 28-49 21l-515-206c-17-7-34-14-21-48 14-35 31-28 48-21zm224 434c137 33 261 48 358 31 88-16 155-60 191-146v-493c-107-1-212-15-303-41-109-31-170-98-201-178-41-107-27-235-4-329 5-18 9-36 45-27s32 27 27 45c-20 82-33 194 1 284 23 60 69 110 152 133 91 25 198 38 307 38 107 0 214-13 304-40 82-24 148-69 192-123s65-117 57-176c-5-36-24-62-49-80-34-24-82-35-128-37-47-2-94 7-142 16-25 5-50 9-77 13-19 2-37 5-42-32s14-40 32-42c23-3 48-8 73-12 52-10 105-20 159-18 60 2 121 18 168 51 42 29 72 72 80 131 11 80-16 163-73 233-53 65-131 119-229 147-83 24-178 38-274 42v483c3 5 3 11 2 16 37 82 102 125 188 141 97 18 221 2 358-31 18-5 36-9 45 27 8 37-9 41-28 45-146 35-279 51-388 32-92-17-165-58-215-132-49 74-124 115-215 132-109 20-242 4-388-32-18-4-37-8-28-45 8-36 27-32 45-27zm356 210 402-9c19 0 38-1 38 37 1 38-18 38-37 38l-402 9c-19 0-37 1-38-37s18-38 37-38m593-3082c151-125 293-227 423-297 133-72 254-111 359-106 19 1 37 1 36 39-1 37-20 37-39 36-92-4-200 32-322 97-125 67-263 166-410 289-14 12-29 24-53-5s-9-41 5-53zm-605 56c-141-130-298-240-445-314-139-71-268-108-363-100-19 2-37 4-40-34-4-37 15-39 34-40 110-10 252 31 404 107 152 77 315 191 461 325 14 13 28 25 2 53-25 27-39 15-53 2zm-213 1004c37-83 83-155 136-219 53-63 112-119 174-170 14-12 29-24 52 5 24 29 9 41-5 53-59 48-114 101-164 160-49 59-91 125-125 201-8 17-15 34-49 19s-27-32-19-49m371-1734c49 66 88 139 114 223 26 82 40 175 39 279 5 80 6 165-7 249-13 86-42 170-97 246-43 60-101 97-165 113-53 13-109 10-164-7 29 100 51 208 6 308-8 18-33 27-51 18-43-22-86-43-128-62s-84-36-127-51l-1-1c-95-37-173-73-236-112-65-39-115-80-150-124l1 2c-44-49-72-106-88-170-14-55-20-114-22-174-72-39-138-78-194-116-64-43-118-87-161-131-13-14-26-27 1-53s40-12 53 1c39 40 89 80 150 121 60 40 128 81 204 121 124 13 247 51 370 109 106 50 211 115 317 192 13 7 19 16 27 20 8 6 16 13 25 19 51 22 104 28 152 16 47-11 90-39 122-84 48-66 72-139 84-214 12-77 11-157 6-234v-2c1-97-12-183-35-258-24-76-58-142-102-201-11-15-22-30 7-52s41-7 52 7zm-375 1047c-104-77-207-141-311-190-105-49-210-83-314-98q3 72 18 135c13 52 35 99 71 138l1 1c30 37 73 72 130 107 60 36 134 71 225 106l-1-1c45 16 89 34 133 54 31 14 61 28 93 44 19-83-10-179-37-267-2-8-5-15-9-29zm776-1003c-44 59-79 125-102 201-24 76-36 161-35 258v2c-5 77-6 158 6 234 12 75 37 148 84 214 32 45 75 72 122 84 48 12 101 6 152-16l25-19c6-4 13-12 27-20 105-77 211-143 317-192 123-58 246-95 370-109 75-40 144-80 204-121s111-81 149-121c13-13 26-27 53-1s14 39 0 53c-43 44-97 88-161 131-57 38-122 77-194 116-2 61-8 119-22 174-16 63-44 121-88 170l1-2c-35 44-85 85-150 124-63 38-141 75-237 112l-1 1c-43 15-85 32-127 51-43 19-85 40-128 62-18 9-43 0-51-18-45-100-23-208 6-308-55 18-111 20-164 7-64-15-122-53-165-113-55-76-84-160-97-246-13-85-12-169-7-249-1-104 13-196 39-279 26-84 65-158 114-223 11-15 22-30 52-7 30 22 19 37 7 52zm940 715c-105 15-209 49-314 98-104 49-207 113-311 190-4 13-6 21-8 29-27 88-56 184-37 267 31-15 62-30 93-44 44-20 87-38 133-54l-1 1c91-35 165-70 225-106 58-34 100-70 131-107l1-1c35-39 57-86 71-138 11-42 16-87 19-135z"/>
|
||||||
|
<path fill-rule="evenodd" d="M203459 50602a216.4 216.4 0 0 0 0 433 216.6 216.6 0 0 0 0-433m0 69c-81 0-147 66-147 147s66 147 147 147 147-66 147-147-66-147-147-147m0 60a87 87 0 1 0 .1 174.1 87 87 0 0 0-.1-174.1m-1697-124c119 0 217 97 217 216s-97 217-217 217a216.6 216.6 0 0 1 0-433m0 69c81 0 147 66 147 147s-66 147-147 147-147-66-147-147 66-147 147-147m0 60a87 87 0 1 1-.1 174.1 87 87 0 0 1 .1-174.1"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#hr-a" width="100%" height="100%" transform="rotate(-2.2 -55532.8 156275.8)"/>
|
||||||
|
<use xlink:href="#hr-a" width="100%" height="100%" transform="rotate(2.2 459865.8 156275.8)"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#171796" d="M328.1 101.3 307.6 78l-27 15.4-4.5 54.6a235 235 0 0 1 38.5 6.4l13.5-53z"/>
|
||||||
|
<path d="M286 104.7q1.4-.5 2.7.2.6.3.9.7l1.2-1 2-1a8 8 0 0 1 2.9-.9l3-.1q1.3 0 2.5.7l2.1 1.1 2.3 1.1a10 10 0 0 0 4.8.4q.9 0 .2.5c-3.5 2.2-6.3.2-8.6-.6l2.2 1.8q1.3 1.3 3.7 2.5a9 9 0 0 0 3.8 1l1.7-.2q.4 0 .4.2l-.2.4a5 5 0 0 1-2.7.8q-2.9 0-5.6-1.4-2-1.2-3.5-2.6a8 8 0 0 0-3.2-1.5 6 6 0 0 0-3.1.1q.4 0 .6.4.8.4 2 .5.8.1-.3.8-.7.8-1.8.8c-1 .7-1.4.2-1.8-.2l-.2.8v.5l.2.8.2.4.4.7q0 .3.2.5l.7.6q.9.4 1 1.3.7.2.8.9.4.1.6.5h2.8q1.3.3 2.1 1.4h1.3q.6-.3 1.4-.4h2.5l1.3.8q.8.7 1.9.3a3 3 0 0 1 2.5 0q.6-.3 1.2-.2.4-.5 1-.5 1-.2 1 .9-.1.4-.4.4-.5.9-1.4.7l-.5 1q.4 1.3-.7 1.2-.3.5-.7.4l-1 .6.3.6c.5.7-.2 1-.8 1q.2.8.1 1.4 1 .6.1 1.2.7 1.2-.5 1.8.1.7-.4 1t-.3.6q.6 1.1-.2 1.3l-.2.1q0 .2-.2.2l-1.6 1.1h-.3q-.5 1.3-1.8 2.5a2 2 0 0 1-1 1.3q0 .3-.4.3.6.6.2 1a3 3 0 0 1-1.4 1q-1.6.4-2.2-.2-.4-.5 0-.7-1 0-.8-.8 0-.2.4-.2l1-.3.5-.6q.2-1 1.2-1.4.8-.3 1.5-1.3l1-1.5-.3-.9-.6-.5q-.7 0-.8-.5h-.3l-.7.2-1.2 1q-.2.5-.8.5c-1 .2-1.5 1.3-2 1.8l-.5.8q0 1-.5 1.1h-.7q.4.4 0 1-.3.3-.9.4l-1.7-.3q-.7-.3-.5-.7-.6-.3-.4-.5 0-.4.5-.3l.6-.2a6 6 0 0 1 2-1.5l.7-.7q0-.8 1-1.5l.2-.6v-.3q-.3-.6-.2-1.2v-.6q-1.6.9-1.7 0-.6.5-1 0h-1.3l-.8.3-.6.7a3 3 0 0 1-.5 1.5l-.7 1.7-.1.5q0 1-.5 1.1l-.5.8v.2q.3.6-.2 1.2l-1 .5q-.5.2-1 0c-.6-.2-.5-.4-.5-.6h-.5l-.4-.3q-.2-.3.2-.5l.9-.6q0-.3.4-.5.3-.6.7-.7l.7-1.6q.2-.2.1-.5l.1-.3q.2 0 0-.2a1 1 0 0 1 .3-1.2c.3-.5.6-1.6.3-2.1l-.1-1h-.3q-.4-.2-.6.3l-.4.6q-.4 1.4-1 1.7l-.2 1.2q0 .8-.2.8t-.5.6l-.1.3q.4.5 0 1-1.2.9-2.5.3-.9-.3-.7-.7c-.9-.1-.7-.8 0-1a4 4 0 0 0 2-1.5q0-1.4.7-1.5.1-.8.5-1.5t.3-1.4q-.7-.5 0-1l-.1-.3q-.4 0-.3-.6 0-.2-.2-.2c-.7 0-.5-.4-.2-.7q.2-.2 0-.4l-.1-.5q-.6-.4 0-1-.4-.2-.4-.7-1.2-.1-.7-1l.7-.7q.4-.5.3-.9c-.2-.5.5-1 1-1.4v-.4q-.5-.5 0-1v-.4q-1.4.2-.5-1.1-.8-.4.5-1.6l.1-.4-1 .4q-.6.2-1 0l-.5-.4-.2-.7c-1 0-1.2-.7-.6-1.1l.6-.9q.6-.9 1.4-1.6l.1-.6-.8-.3q0-.4-.6-.6-.3-.2-.1-.5-.6-.7-.3-1z"/>
|
||||||
|
<g fill="#f7db17">
|
||||||
|
<path d="M304 130.2h-.3l-.8.8c-.2-.3-.6 0-.2.1l-.2.2q-.2.4-.6.5h-.2v-.4l.1-.4v-.1l-.2 1c-.8.2-1.3 1.1-2 1.7l-.4.9c0 .2 0 1-.3 1h-.2q-.1-.3-.4 0h-1c-.3.2.3.1.6.3q.2.2.2.5c-.3.7-2 .5-2.5.2q-.4-.2-.3-.5h.4l.6-.2v-.2q0-.2-.2 0l-.5.1h-.5q-.2 0-.1-.1h.2l.7-.3a6 6 0 0 1 2-1.5l.8-.7q.2-.9 1-1.5 0-.4.3-.8v-.4q-.3-.6-.2-1v-1l.9-.6v.8l-.2.3v.3-.2l.3-.3q.4 0 .8-.4l.7-.7v1.2l-.6.7v.1h.1l.5-.7h.1l.1-.2.9-.4v1z"/>
|
||||||
|
<path d="m287.4 120.6.2-.2.4-.6.5-.7h.1v.1l-.5.6-.4.6v.2q0 .5.3.8l.3-.5.5-.4.3-.3.2-.3h.1l-.2.4-.4.3-.4.4-.3.3q.2.2 0 .4c-.1.3-.4.5 0 .8l.2.5q.2.3-.1.7c-.3.3 0 .2.1.2h.1l.3-.4.3-.4.4-.5.2-.6h.1l-.2.6-.4.6-.3.4-.3.4q.3.3.2.8h.2l.2-.3.2-.4h.1l-.2.5-.2.2q.3.3 0 .7-.6.4 0 .6v1.7-.4l-.1.1q-.5 1-.7 1.9c-.7.1-.6.8-.6 1.4a4 4 0 0 1-2 1.6q-.4 0-.6.3.1.3.4.2.8-.6.2.2v.3c.6.4 1.8.6 2.4.2q.7-.3.2-.9 0-.9.8-1.3.2-.1 0-.4 0-.8.2-1.4l.2-.2q.4-.1.7-1.3l.1-.4q0-.3.3-.1l.1-.4q.4-.6 1-.4l-.2-.9.1-1 .1-.2.3-.8.4-.8v.1l-.3.8-.3.8v.1a3 3 0 0 0 .2 2v.1q.3 0 .2.4v.6c.4.6 0 1.8-.3 2.3q-.2.4-.2 1 .2.2 0 .5v.1l-.3.6-.7 1.7q-.6.2-.7.8-.4 0-.4.3l-1 .7q-.2.1.3.4.3 0 .6-.4t.2 0l-.2.7c0 .2.6.3.7.3q.8-.2 1.3-.6t.1-.8h-.5l-.6-.3q0-.2 0 0l.7.1h.5v-.2l.5-.9q.5-.2.4-1l.1-.5q.5-.7.8-1.7.5-.8.5-1.5 0-.4.3-.4 0-.2.3-.5l.2-.4.1-.2v-1l-.2-.2v-.2h.1v.2l.1.1.2.6v.5l-.2.2-.1.4.7-.2.4-.5.2-.6h.1l-.2.7-.3.4 1.1-.2v-1.2l.4-.8-.2.8v1.2q.1.5.5.3l.4-.3v-.1l2-1.6-1.7 1.5v.3q-.1.7.6.4l.6-.3.3-.2 1-.7.2-.6.8-1.3.7-.6.5-.7-.4.8-.7.6a4 4 0 0 0-.9 1.8v1l.7-.4.8-.7q0-.5.3-.6l.4-.4v.2l-.4.3-.2.5v1.2l1-.6h.1v1.1q0 .5.4.8l.5-.6.4-.3.2-.2.2-.3h.1v.1l-.2.2-.3.3-.3.2v.5h.7q-.1.7.5.6v-.5l.1-.5h.1v1q.2.5.6.7l.2-.5.1-.5h.1v.5l-.3.6v.5l.2-.3.3-.4h.1l-.3.4-.2.4.2.2c-.7 1.1-1.4 2.7-2.6 3.2q-1 .3-1.1 1.3l-.6.6q-.6.3-1.3.4 0 .4.4.4l.8-.3s-.1.5-.6.8v.2c.4.6 1.4.4 2 .2.4 0 1.1-.6 1.3-1q.1-.3-.2-.6c-.8.1-.7-.6 0-.3q.3.1.3-.4 1-.2 1-1.1l1.8-2.2.1-.6.2-.4.1-.5h.1v.5l-.2.4q0 .2.2.2.7-.7 1.6-1.1 0-.4.5-.4l.1-.1q.2-.5-.2-.9 0-.5.4-.7.4-.5.3-1.1v-.2l-.1-.3.2.3v.1q.5-.2.7-.6l-.3-.9-.6.3v-.1l.7-.4.3-.2c.2-.3-.3-.6-.5-.7l-.8-.7v-.1h.1l.7.7v-1.3l-.2-.4-.5-.4-.2-.5h.1l.2.5.4.3q.5.2 1 0c.4-.2-.1-.7-.2-1l-.2-.5h.1l-.1-.2h-.2v-.1h.2l.2.3h.1q.5-.1.8-.6l-.4-.4-.4-.4.5.3.5.5q.4 0 .5-.3l-.2-.5-.3-.4q.3 0 .4.4l.2.4h.5c.3-.2 0-.8 0-1l-.2-.2h-.2v-.2h.1l.2.1.2.1.4-.6c0-.4.1-.2.5-.3q.6.1 1-.7.2 0 .3-.3.1-.5-.4-.5-.7-.1-1.1.4v.2q.2 0 0 .1l-.1-.2-1.3.2h-.1l.2.3.1.4v.1h-.1l-.1-.5-.3-.4-1-.2.1.1.2.2v.1l-.3-.2-.2-.2-.9.2-.6.2.4.5.2.6v.1h-.1l-.2-.6-.4-.5h-.6l.2.3-.3-.4-.5-.2.2 1.6h-.1v-1l-.2-.7-.3-.2-.6-.5-.2.8.1-.8-1.1-.3.6 1 .3.4v.5l-.1-.5-.3-.4-.3-.5-.4-.5h-.4.1l.1.3h-.1v-.2l-.2-.1h-.8a6 6 0 0 1 1 2.4v.1l-.5-1.3-.7-1.2-.7.2.5.4-.3-.2-.3-.2-.3.1h-.2l1.1 1.2-.6-.6-.6-.5h-.7l.5 1.9-.3-1-.4-.8h-.4l.4 1.7h-.1l-.2-1-.2-.8a4 4 0 0 0-1.4-1.1 2 2 0 0 1 .4 1.2h-.1l-.1-.7-.4-.6h-.4l-1-.1q.4.3.4.7h-.1q0-.3-.5-.7h-.7l.3.3.3.5q0 .4.2.6 0 .3.4.5l-.5-.4-.3-.7-.2-.4-.3-.5h-.7l.2 1v1l-.1-1-.3-1-.5-.4-.2.3v-.4q-.2-.7-.7-.9v.5l-.2.5q-.2 0 0 0v-.6l.1-.4q-.1-1-1-1.3v.7h-.1v-.9l-.5-.5-.1.8-.4.9h-.1l.4-1v-.8l-.1-.3-.1.3h-.1v-.3l.1-.1v-.1l-.3-.5-.3.5.2-.6-.1-.3-.1.2-.3.2.2-.3v-.1q.2 0 0-.2.1-.4-.1-.8h-.4l-.5.4v-.1q.3-.3.5-.3l.4-.1v-.3l-1.2.3h-.8v-.2l.8.1 1.2-.3.2-.6h-.6l.6-.1v-.2q.1-.4.5 0 .3 0 .5.3.4.2.9-.2h-.3v-.1h.4q.8.1 1.5-.5h-1l-.7-.2v-.1l.7.2h1l.2-.1.4-.3-2-.6-.8-.4a2 2 0 0 0-1.4.3l-.3-.3-.5-.1h-.7v-.1h.7l.6.1 1.9-.4 1.8-.2v-.2q.4-.4.3-1-.2-.4-.8-.7h-1.4l-.9.1q.4.2.6.5.4.4.5 1v.1h-.2q0-.7-.4-1t-.8-.5l-1.4.3q-.5 0-.8.3l1 .1q.3.2.6.8v.1h-.2q-.2-.6-.5-.7c-.5-.2-1.4 0-1.8.2l-.6.7h-.2v-.2l.7-.7.7-.3 1-.6 1.5-.2-.6-.6h-.7l-1.7.8h.6v.2h-.9s0 .1 0 0l-1.6 1.3q-.9 0-1 .2-.2.2-.2.6l-.2.8q-.9.7-1.4 1.6l-.3.6h.3v.1-.3l.3-.4-.1.4v.6h-.2l-.1-.3h-.3l-.4.3-.2.3q0 .4.6.3c.6 0 .1.4.5.7l.4.5h.6q0-.7.2-.7l.2-.4v.3l-.1.2v.4l1-.3q1.2-1 .5 0l-.1.2-.2.7c-.2.1-1 .8-.8 1.1q.4 0 .3.2c0 .2-.4.7-.3 1h.3l.8-.7.6-.8v-.1l.3-.8.3-.8h.1l.3.3.2.1h.3l.3-.3s0-.1 0 0l-.2.3-.4.1h-.3l-.2-.3-.3.7-.2.7v.2l-.7.9-.6.5.2.6 1-1 .9-1.1-.3.6-.1.4v.8l-.1-.8v-.2l-1.3 1.4q-.7.3-.3.7l.4-.3.4-.3h.1v.1l-.4.3-.4.3.1.4 1-.6-.8.6-.1.2c-.3.2-1.3.8-1.2 1.2q.4 0 .7-.3l.7-.5v.1l-.6.5v.3h-.2v-.2l-.5.2q0 .6-.4 1c0 .2-1 1-.7 1.2h.4z"/>
|
||||||
|
<path d="m287.9 106.9-.8-.3-.5-.6q-.3 0-.1-.3l.3-.1h.3l.2.2-.2-.3h-.8l-.2-.6.4.1.2.2-.2-.3h-.2a3 3 0 0 1 2.3.3q.5.2.8.6l-.2.2q-.8 0-1.1.2-.2.2-.3.7z"/>
|
||||||
|
</g>
|
||||||
|
<path d="m304.4 130-.4.2.4-.6zm2.1 4.5-.4.4-.5.4.5-.5zm1.4-1.7-.2.3q-.2 0 0 0zm-.7 0a1 1 0 0 1-.7.7l.4-.4.2-.4zm1.3-1.7-.1.2-.1.2h-.1l.2-.5zm1.1 0-.1.5-.2.5h-.1l.2-.5.1-.5zm-.5-6.4q.5.7.5 1.5l.1-.5a2 2 0 0 0 .3-1.3h.1v.7l-.3.6-.2 1v.1h-.2v-.1q.1-1-.4-2zm-.8 1v.3h-.1v-.2s0-.1 0 0zm-.2 1.5.2 1.5.4-.4h.1v.1l-.3.3-.2.3-.2-.9-.2-.8h.2zm1.1.6v1.2zs-.1 0 0 0m1-.2v1.8l.1.6-.3-.6v-.7l.2-.5zm.5-1.7v.3l-.1.3s0 .1-.1 0v-.3zm-.5-2.8.1.3h-.1zs-.1 0 0-.1m.3-2v.8l-.3.4v.6h-.1v-.7l.3-.4zm-2.1-.8.8.7q.3.4.3 1h-.1l-.6-.2q.2 1.2 0 2a2 2 0 0 1-1 1l-.1-.1a4 4 0 0 0 .1-1.8h.1v1.7q.5-.3.8-1 .2-.6 0-1.9l.7.2-.3-.8-.8-.7zm5.5-1zh-.1v-.2zm-1.3-.1.2.2.2.2-.2-.2-.3-.1v-.1zm-2 .5.3.6v.6s0 .1 0 0v-.6zm-6.4.7v.4h-.1zm1.2-.7v.3l.1.1v.1l-.1-.1zm2.5 1.7v.3h-.1zm.8-1.9.1.2v.2h-.1v-.3q-.2 0 0-.1m-5.9.3.1.4.2.3.2.5v.4-.4l-.3-.4-.2-.4zm2.4 1.6.2.7v.8q0 .5.2.8t.7.7l-.2-1 .4-1 .3-.4v-.5h.1v.5l-.3.5-.3 1 .1 1v.1l-1-.8a1 1 0 0 1-.1-1v-.7zm-1 1.5-.1.8-.5.6.3-.1.7-.6.4-.3-.3.4-.3.3.1.3v.1h-.1v-.4l-.5.3-.4.1s-.1 0 0 0l.5-.8zm-1.1-.1v.2h-.1v-.2z"/>
|
||||||
|
<path d="M302.4 121.3q.2.6.1 1 0 .6-.4 1l-.6.7-.1.5.2-.2q0-.2.5-.3l1.2-1h.1l-.6.6-.7.5-.5.3-.3.4h-.1l.2-.9.6-.7.4-.8-.1-1zm2.3 3.8.2.4.1.2q.3.3.3.8l-.1 1h-.1l-.4-1-.9-.7 1 .7.3.7v-.7l-.2-.8v-.1q-.4-.2-.3-.5zm1 0 .2.1v.1q.2 0 0 .1l-.1-.1zm1.3.4-.3.7-.2.7s0 .1 0 0h-.1l.2-.7.3-.8zm.7 4.1v.2h-.1v-.2zm-.8-1.2v.4l-.2.4v-.5zm-1 0v.2l-.1-.1v-.1zm-1.3-.6-.1.4h-.1l.1-.4s0-.1.1 0m-1.8 1.9-.1.2v.3h-.1v-.3zm-5 5-.1.1-.2.2h-.1v-.1l.3-.3zm.8-.5-.1.2h-.2s0 .1 0 0v-.1l.2-.1zm1-.6-.2.2v-.1l.1-.2zm1.6-4.1v.8l-.1.1-.1-.5.1-.5zm1-11v.3l-.3.4v-.1l.2-.3v-.4zm-1-1 .2.5v.5h-.1v-.5l-.2-.5zm-1.5 1.8-.4-.5-.1-.4q.2 0 0 0l.2.4zq0 .1 0 0m1.6 2.7v.3zm-2-2q.4.3.6 1v.8h.1q.2 0 .3-.4l.2-.9.2-.6.1-.6h.1l.5.9.3 1h-.1l-.3-1-.4-.7v.5l-.3.5q0 .7-.2 1 0 .3-.3.4h-.1v.5h-.1l-.1-1.3-.6-1v-.2zm1.2 5.1v.5l-.2.4v-.9zm-2.4 1 .1.4h-.1zm2-2.6-.1.6-.4.6-.3.7v.7l-.1-.8q0-.3.3-.7a2 2 0 0 0 .6-1.2zm-.5-.8v.6l-.5.6-.4.5-.3.6.2-.6.5-.6.4-.5v-.6zm-2.5 1.2q0 .6-.2 1-.2.6-.7 1-.2-.1 0-.2.4-.3.6-.8.3-.4.2-1zm-3.3 1.8v.2l.2.2h-.1l-.1-.1-.1-.2v-.1zm-1.3 0v.8q0 .3.4.7l.2.9.4-.6.1-.6h.1l-.1.7-.5.6h-.1v-.5l-.2-.5-.3-.7-.1-.8zm.2 4.5.1-.8h.1l-.1.8zm-.2 1.2v-.5h.1v.5zm-.9 2.1.3-.3.2-.3h.1l-.2.4-.3.3v-.1zm-1.2-7v.2zm-2 4.8v-.3zm5.2-8.5-.2 1q0 .6.3.8.4.3.5.7l.1-.4v-.4h.1l-.1 1h-.1l-.1-.2q-.1-.4-.5-.6-.3-.3-.3-.8t.2-1.1zm-.4-1.4v.8l-.3.8-.2 1q0 .6.3 1.1v.1a3 3 0 0 1-.2-2.2l.3-.8zm-2.4 3.5v.5l.1.5-.3-.5v-.6zm-1-.4-.2.6v.3l.1.3-.2-.3v-.4l.1-.2v-.3zm6.1-9.2a3 3 0 0 0 .3 1.6l-.4-.8zm-.8.7a4 4 0 0 1 .6 1.9h-.1a4 4 0 0 0-.6-1.8h.1zm-3.8.2q-.6.8-.6 1.3t.2 1a3 3 0 0 1 0 1.7h-.1l.2-.9-.2-.8-.2-1q0-.6.6-1.4.2 0 0 0zm3.6-1.6v.5l-.4.5-.4.5v.7q-.2-.4 0-.8l.3-.5.3-.4v-.4zm-3.8.4v.7l-.4.6q-.4.3-.4.7l-.2.8v-.9l.5-.6.4-.7zm2.1 0v.3l.1.3-.2-.2v-.3h.1zm0-.5-.5.4-.2.8.2.6.1.7h-.1v-.7l-.3-.5q-.6.6-.6 1.3a3 3 0 0 0 .5 1.6 3 3 0 0 1-.6-1.6q0-.8.7-1.4 0-.5.2-.8zm-.7-.8v.1q0 .5-.2.8l-.3.3v.4h-.1v-.4l.3-.4.2-.7zm6 6.5.3.8h-.1v-.4zm0 2v.6l-.2.7-.2.6h-.1l.2-.6.3-.7zm-1.2-2a3 3 0 0 1 .7 2.8h-.1a3 3 0 0 0-.7-2.6v-.1h.1zm0 2.3-1 1.4-.6 1-.2-.5-.2-.5q-.3-.4-.2-.9t.3-.9l.3-.6v-.6h.1l-.3 1.3-.3.8.2.9.3.7.5-.7zm-3.4-1v.7l.4.7h-.1l-.3-.6zm-1.9 1v.3zm.7-1.7-.4 1q-.2 0 0 0l.2-.5v-.5zM291 124a4 4 0 0 1-.3-1.9l.6-1.1.5-.7v-1h.1v1l-.6.8-.5 1q-.2.7.3 1.9zm-1-1.3v.8s.1 0 0 0l-.2-.4v-.4zm4.8-3.4.3.9-.2 1q-.2 0 0-.1l.1-.9-.3-.8zm1.5-1.1a3 3 0 0 1 0 1.7l-.2-.7-.5-.6q0-.1.1 0l.6 1v-.6zm.9-1.3.4.8.1 1.1v.5l.1.5-.2-.4v-.6l-.1-1q0-.5-.4-.8zm-2.4-.2-.2.6v.6h-.1v-.7l.2-.6h.1zm-4.6 3.1-.4 1.2-.8.7h-.1l.8-.8.4-1v-.1zm-.1-1.6v.5l-.3.5.1-.5v-.4l.1-.1zm3.1-.2v1.1h-.1V118zm.8-1.4q-.4.4-.4.8l.2.8q.4.6.3 1.2 0 .7-.3 1.3h-.1l.3-1.3-.3-1.2-.2-.8q0-.5.4-.9v.1zm-5-8.7 1.2-.5q.6 0 1.2.3h.8-.8v.1l-1.2.7a1 1 0 0 1-.9-.2v-.1l.9.2 1-.6-1-.2q-.5 0-1 .4z"/>
|
||||||
|
<path d="M290.2 107.8a.3.3 0 0 1 0 .6l-.3-.3q0-.3.3-.3m1.9 1.7.5-.3.3-.4q.2 0 0 0l-.2.5zm4-1.6h.2l.4.1v.1h-.1l-.3-.1zm-2.3 0 .4-.4.7-.1v.1l-.7.1zm.2.4.2-.2.3-.2v.1l-.3.1v.2zm-3.7 1.3-.2.3v.3h-.1l.2-.7q.2 0 0 0zm1 .5v.1zm-.6-1.2h.7l-.4.1h-.4v-.1zm.7-2h-.3l-.1.2h-.1l.2-.3zm-1.2 0-.2.2-.1.1v-.2zm-.7 0-.1.2-.1.2h-.1v-.2zm-.8.8-.4.4h-.1l.4-.5zm0 1-.1.1-.2.2.1-.2zm-.9.2v.4zm1.2 1.5v.6s0 .1-.1 0v-.6q.2 0 0 0zm-.7-.5v.4h-.1v-.4zm-.6-4.8.5.5v.1h-.1l-.2-.3-.2-.2s-.1 0 0 0zm5.5 7.2a2 2 0 0 1 0 1.5v-.8l-.1-.6v-.1zm-1.8 2 .3-.6.5-.6a2 2 0 0 0 .3-1.5v-.1h.1v.9l-.3.7-.5.6-.3.7q-.2 0 0 0zm-2.3-.5.2-.3.3-.2v.1l-.3.2zm-.5-.2.3-.3.4-.2-.3.3-.3.3zm2.5-2.3h.5-.6zm-1.3-.2v.2h-.1zm25.8 7.4.2.2v.2l-.1-.1-.2-.2h.1zM287 132l.4.2.5.1v.1h-.5l-.5-.4z"/>
|
||||||
|
<g fill="red">
|
||||||
|
<path d="M308.6 106.3h1.8q-1 .5-2 .7l.2-.3zm-1.5-.1 1.3.1v.3l-.3.3v.2h-1.3l.3-.5zm-11.2-3.3h2.2q.3 0 .5.4.2.3.1.8l.1.1h.1l-.1-1-.3-.3h.2q1.2 0 2.3.6l.2.6-.4.6q.2.2.2 0 .3-.2.4-.6v-.4l1.8 1v.5l-.5.3s-.1 0 0 .1q0 .2 0 0 .4 0 .7-.3v-.5l1.3.6v.4q0 .3-.4.4v.1q0 .1.1 0 .4 0 .5-.4v-.3l.5.1 1.4.4.1.4-.2.4v.2c-1.5-.2-3-1-4.4-1.5l-.5-.2-1.7-.7h-.2v-.1l-1.5-.4h-1.8q0-.5-.2-.8z"/>
|
||||||
|
<path d="m293.3 103.6 2.2-.6q.6.2.7.5.2.3.1.7a5 5 0 0 0-2 .2q-.2-.4-.6-.7h-.4zm8 4-.6-.3a7 7 0 0 0-3.3-.9q.6-.6.4-1.2l-.7-.8 1.1.1 2 .5q.2.6.1 1-.2.5-.6.7 0 .3.1.1.6-.3.7-.8v-.9l1 .7 1.2.8q0 .7-.4.9h-1z"/>
|
||||||
|
<path d="m303.4 109.3-2-1.5q.6 0 1-.2t.5-.8l1.7 1.5v.7l-.3.2z"/>
|
||||||
|
<path d="m305.7 110.8-.7-.4-1.4-1h.7l.4-.2.2-.4v-.3l2.2 1.6q0 .4-.3.4l-1 .2h-.1z"/>
|
||||||
|
<path d="m308.1 111.8-2.2-.9q.6 0 1-.2.5 0 .5-.5l1.3.6v.4q-.1.3-.5.4v.1z"/>
|
||||||
|
<path d="M313.2 111.2h.2q-2.2 1.4-5.1.6.5-.2.6-.5t0-.4l1.8.4-.1.2-.5.4s-.1 0 0 0v.2l.7-.4.1-.4zM302.9 137l-.3.4-1.3.3q0 .4.4.4l.8-.3s-.1.5-.6.8v.2c.4.6 1.4.4 2 .2.4 0 1.1-.5 1.3-1q.1-.2-.2-.6-.7 0-.6-.3-.7-.2-1.5-.1m-16.2-5.1-1.1.6q-.4 0-.6.3.1.3.4.2 1-.6.2.2v.3c.6.5 1.8.6 2.4.3q.7-.4.2-1v-.2l-.6-.1-.4-.3zm10.7 3.7h-.2c-.2.2.4.1.7.3q.3.2.2.5c-.3.7-2 .5-2.5.2q-.4-.2-.3-.5l1-.2v-.2h-.2l-.5.1h-.5q-.2 0-.1-.1h.2l.8-.2v-.2q.9 0 1.4.3m-7.7-.5-.2.3-1 .7q-.2.1.3.4.3 0 .6-.4t.2 0l-.2.7c0 .2.6.3.8.3q.7-.2 1.2-.6t.1-.8h-.5l-.6-.3v-.1zm3-28.1h.3l.7.1 1.8-.4 1.8-.2v-.2q.4-.4.3-1-.1-.3-.8-.7h-1.4q-.5 0-.9.2.4 0 .7.4.3.3.4 1v.1h-.2l-.4-1q-.5-.4-.8-.4-.8 0-1.4.2-.4 0-.8.3h1q.4.3.6.9v.1h-.2q-.2-.6-.5-.7c-.5-.2-1.4 0-1.7.2l-.7.7h-.1q1.2 0 2.3.4m-2.4-.5.7-.7.7-.3 1-.6 1.5-.2-.6-.6h-.7l-1.7.8h.6v.2h-.9s0 .1 0 0l-1.4 1.1z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#0093dd" d="m351.4 167 22.2-50.1-16.4-26.4-29 10.8-13.6 53a234 234 0 0 1 36.8 12.8z"/>
|
||||||
|
<path fill="#fff" d="M323.2 120.6a269 269 0 0 1 42.3 14.7l-8.6 19.3a248 248 0 0 0-38.9-13.4z"/>
|
||||||
|
<path fill="red" d="m358.3 151.5 5.7-12.9a265 265 0 0 0-41.7-14.4l-3.4 13.2a252 252 0 0 1 39.6 13.7z"/>
|
||||||
|
<path d="M326.4 134.2q.6-.2 1.2 0 .5-.2 1.1-.2l.7-.2v-.3l-.2-.4a3 3 0 0 1-1.1-1.6q-.6 0-.6-.3h-.6q-1-.1-1.3-.5l-.7-.5-.2-.3q-.4-.6.2-.7h.7q.4-.2 1-.2.4 0 .9-.5v-.5q0-.2.2-.1.6 0 .9.5.8.1 1.3.8 1.1.1 1.2.5t-.5.7v.1l.4.5 1.1.7q1.6-.1 5.1 2.1 2.3.4 4.4 1.3h1q3.8-.7 7.2 1.7 1.2.2 2.4.7l1.7.4q1.3 0 2.7.7 1.6.3 2.6 1.3.7.8-.2 1.2-.5 1-2 .4-1 0-2-1a10 10 0 0 1-2.7-1.4l-1.3-.6h-.9v.2q.4.3.5.8v1q0 .7.7 1l1 .5q.4 0 .4.3l.4 1.8.4.4q.8.8-.3 1-.7.8-1.4.9t-.7-.2l-.5-.3c-.5-.4 0-1 1.1-.7q0-.2.3-.2v-1l-.4-.2-1.1-.6q-1-.8-2.5-1.6-1 0-2-.6h-.7q-.4-.1-.6.1-.3.4-.8.2h-1.2l-.5.1q-.8 0-1.1.2t-.7.2l-.4-.3-.6-.2q-.7-.3-.6-.4c-.6-.2-.3-.7.1-.7l1.5.1q.7.1 1.2-.2l.6-.6a4 4 0 0 1-2-.5q-1.8-1.2-3-.3-.3.3-.6.1h-.6l-1.3-.1a4 4 0 0 1-2 0q-.9.5-1.2.1l-.7-.4q-.5 0-.5-.2-.6-.2-.5-.4-.2-.4 0-.6.5-.2 1.1 0l.5.4h.7q.4-.2 1-.2l.8-.2v-.2q-.7-.2-.9-.8a3 3 0 0 1-1.7-.6q-.6 0-1-.2H328l-.9-.1q-.4.5-1 .2l-.9-.5q-.4-.1-.4-.4-.5-.3 0-.6.4-.1 1.1.1z"/>
|
||||||
|
<path fill="#fff" d="M343.1 139.3s-.1-.2-.3 0q-1.3.1-2.4-.5t-2.2-.7l.9-.5h1.5q1.2.1 2 .5 1 .3 1.1.7h.1l.9.8h-.8z"/>
|
||||||
|
<path d="M345 101.1v.2l1 5.2-4 3.5-.2.1.3.1 5 1.7 1 5.2v.2l.2-.2 4-3.4 5 1.7h.1v-.1l-1-5.2 4-3.5.2-.1-.3-.1-5-1.7-1-5.2v-.2l-.2.1-4 3.5-5-1.7z"/>
|
||||||
|
<path fill="#f7db17" d="m342.5 109.9 3.7-3.2 4.7 1.6zm9-1.7 3.7-3.3 4.6 1.6zm8.4-1.5-3.7 3.2-4.7-1.6zm-9 1.7-3.7 3.3-4.7-1.6zm-5.4-6.8 4.6 1.6 1 4.8zm6 6.9 4.6 1.6 1 4.8zm5.4 6.5-4.6-1.6-1-4.8zm-6-6.9-4.6-1.6-1-4.8zm3.2-8.1 1 4.8-3.7 3.2zm-3 8.6 1 4.9-3.7 3.2zm-2.9 8-1-4.8 3.8-3.2zm3-8.6-1-4.9 3.8-3.2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 31 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-hu" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path fill="#fff" d="M512 512H0V0h512z"/>
|
||||||
|
<path fill="#388d00" d="M512 512H0V341.3h512z"/>
|
||||||
|
<path fill="#d43516" d="M512 170.8H0V.1h512z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 273 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ie" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="#fff" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#009A49" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#FF7900" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 289 B |
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-is" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="is-a">
|
||||||
|
<path fill-opacity=".7" d="M85.4 0h486v486h-486z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g fill-rule="evenodd" stroke-width="0" clip-path="url(#is-a)" transform="translate(-90)scale(1.0535)">
|
||||||
|
<path fill="#003897" d="M0 0h675v486H0z"/>
|
||||||
|
<path fill="#fff" d="M0 189h189V0h108v189h378v108H297v189H189V297H0z"/>
|
||||||
|
<path fill="#d72828" d="M0 216h216V0h54v216h405v54H270v216h-54V270H0z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 520 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-it" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="#fff" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#009246" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#ce2b37" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 289 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-lt" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt" transform="scale(.51314 1.0322)">
|
||||||
|
<rect width="1063" height="708.7" fill="#006a44" rx="0" ry="0" transform="scale(.93865 .69686)"/>
|
||||||
|
<rect width="1063" height="236.2" y="475.6" fill="#c1272d" rx="0" ry="0" transform="scale(.93865 .69686)"/>
|
||||||
|
<path fill="#fdb913" d="M0 0h997.8v164.6H0z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 439 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-lu" viewBox="0 0 512 512">
|
||||||
|
<path fill="#ed2939" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#fff" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#00a1de" d="M0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-lv" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path fill="#fff" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#981e32" d="M0 0h512v204.8H0zm0 307.2h512V512H0z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 234 B |
@@ -0,0 +1,71 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-md" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="#00319c" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#ffde00" d="M170.7 0h170.6v512H170.7z"/>
|
||||||
|
<path fill="#de2110" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#ff1900" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M173.6 691v63.7c0 3.6 0 7.1-3.5 7.1-3.6 0-7.1 7-7.1 10.6 0 7.1 3.5 10.7 7 10.7h32c3.5 0 7-3.6 7-10.7-.2-3.7-3.5-10.6-7-10.6-3.6 0-3.6-3.5-3.6-7v-63.9" transform="matrix(.46695 -.29552 .30926 .4462 -5.1 64.5)"/>
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" stroke="#000" stroke-width="3.1" d="M177.2 696.3c0 24.4-9.6 44.3-21.3 44.3s-21.3-19.9-21.3-44.3S144.3 652 156 652s21.3 19.8 21.3 44.3z" transform="matrix(.42858 -.34452 .36055 .40954 -29.7 77.3)"/>
|
||||||
|
<path fill="#ff1900" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M173.6 691v63.7c0 3.6 0 7.1-3.5 7.1-3.6 0-7.1 7-7.1 10.6 0 7.1 3.5 10.7 7 10.7h32c3.5 0 7-3.6 7-10.7-.2-3.7-3.5-10.6-7-10.6-3.6 0-3.6-3.5-3.6-7v-63.9" transform="matrix(.48902 .26089 -.27302 .46728 313.4 -58.3)"/>
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" stroke="#000" stroke-width="3.1" d="M421.6 701.6h-28.3c-1 1-9 54-17.7 74.4-10.6 24.8-42.5 46.7-42.5 46.7s19 6 24.8 10c1.4-.4 31.8-7.1 35.4-46 3.5 38.9-21.3 56.6-21.3 56.6s35.5 10.6 35.5 35.5c0-24.8 35.4-35.5 35.4-35.5s-17.8-17.7-21.3-56.7c7 39 34 45.7 35.5 46 5.7-3.9 24.7-10 24.7-10S450 800.9 439.4 776c-8.7-20.3-16.8-73.5-17.7-74.4z" transform="matrix(.56007 0 0 .53518 29.7 -86.3)"/>
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" stroke="#000" stroke-width="3.1" d="m336.6 467.7 53.2 17.7a38 38 0 0 0 35.4-35.4c.6-39.4-14.2-53.2-17.7-53.2l7-7L404 379s3.6-18.4 39-17.7 35.4 17.7 35.4 35.4c0 17.8-17.7 17.8-17.7 53.2a38 38 0 0 0 35.5 35.4l53.1-17.7V645H336.6z" transform="matrix(.56007 0 0 .53518 9.8 -56)"/>
|
||||||
|
<g fill="none" stroke="#000">
|
||||||
|
<path stroke-width=".6" d="m302.2 292.2 14.6 15 14.7-15 14.6 15 29.3-15 14.3 15.8 15-15.8 21.6 15.8 22.3-15.8 14.6 15 14.7-15" transform="matrix(.1355 0 0 .38018 203 87)"/>
|
||||||
|
<path stroke-width=".3" d="m432.3 435.8-7.1 39m10.6-39v39m7.1-39v39m7.1-39 3.5 39m3.6-39 3.5 39" transform="matrix(.56007 0 0 .53518 9.8 -56)"/>
|
||||||
|
</g>
|
||||||
|
<g stroke="#000" stroke-width="3.1">
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" d="M318.9 361.4c35.4 17.7 35.4 53.2 35.4 70.9-1.2 16.1 0 354.3 0 354.3s-2.3-20-17.7-35.4L319 733.5l-17.7-17.7c-13-11.8-17.7-35.5-17.7-53.2v-248s0-17.8 35.4-53.2z" transform="matrix(-.56007 0 0 .53518 357.1 -56)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(-.56007 0 0 .53518 357.1 -131.8)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(-.56007 0 0 -.53518 357.1 492)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(-.56007 0 0 .53518 357.1 -94)"/>
|
||||||
|
<path fill="none" d="m301.4 557-.2 158.8" transform="matrix(-.56007 0 0 1.01529 357.1 -399.6)"/>
|
||||||
|
<path fill="none" d="m301.4 539.3-.2 176.5" transform="matrix(-.56007 0 0 .91125 347.3 -316)"/>
|
||||||
|
<path fill="none" d="m301.4 539.3-.2 176.5" transform="matrix(-.56007 0 0 1.0208 337.4 -384.6)"/>
|
||||||
|
</g>
|
||||||
|
<g stroke="#000" stroke-width="3.1">
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" d="M318.9 361.4c35.4 17.7 35.4 53.2 35.4 70.9-1.2 16.1 0 354.3 0 354.3s-2.3-20-17.7-35.4L319 733.5l-17.7-17.7c-13-11.8-17.7-35.5-17.7-53.2v-248s0-17.8 35.4-53.2z" transform="matrix(.56007 0 0 .53518 158.7 -56)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(.56007 0 0 .53518 158.7 -131.8)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(.56007 0 0 -.53518 158.7 492)"/>
|
||||||
|
<path fill="none" d="M283.5 574c.4 0 17.7-17.7 17.7-17.7l17.7 17.7 17.7-17.7 17.7 17.7" transform="matrix(.56007 0 0 .53518 158.7 -94)"/>
|
||||||
|
<path fill="none" d="m301.4 557-.2 158.8" transform="matrix(.56007 0 0 1.01529 158.7 -399.6)"/>
|
||||||
|
<path fill="none" d="m301.4 539.3-.2 176.5" transform="matrix(.56007 0 0 .91125 168.5 -316)"/>
|
||||||
|
<path fill="none" d="m301.4 539.3-.2 176.5" transform="matrix(.56007 0 0 1.0208 178.4 -384.6)"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#a77b3b" fill-rule="evenodd" stroke="#000" stroke-width="3.1" d="M177.2 696.3c0 24.4-9.6 44.3-21.3 44.3s-21.3-19.9-21.3-44.3S144.3 652 156 652s21.3 19.8 21.3 44.3z" transform="matrix(.51699 .20584 -.21541 .49401 290.2 -73.2)"/>
|
||||||
|
<path fill="red" fill-rule="evenodd" d="M198.4 203.8h119v56.9h-119z"/>
|
||||||
|
<path fill="#564dff" fill-rule="evenodd" d="M198.4 260.7h119V289c0 19-29.7 19-59.5 38-29.8-19-59.5-19-59.5-38z"/>
|
||||||
|
<path fill="none" stroke="#ff0" stroke-width="2.5" d="M336.6 485.4h212.6V645c0 35.4-53.1 35.4-106.3 70.9-53.1-35.5-106.3-35.5-106.3-71V485.5z" transform="matrix(.56007 0 0 .53518 9.8 -56)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="3" d="M385.6 129.9S335 185.4 335 238.5s53.1 70.9 53.1 70.9-17.7 0-17.7 70.8c0 35.5 53.1 17.8 53.1 35.5s-.7 60.5 0 88.6c0 17.7-35.4 0-35.4 17.7 0 8.8 26.6 53.1 53.2 53.1s53.1-44.3 53.1-53.1c0-17.7-35.4 0-35.4-17.7v-88.6c0-17.7 53.1 0 53.1-35.5 0-70.8-17.7-70.8-17.7-70.8s53.2-17.7 53.2-70.9-50.7-108.6-50.7-108.6 28.5 73.2 28.5 108.6c0 17.7-13.3 53.2-48.7 53.2 0 0-9-17.8-17.7 0 0 0-10.2-17.8-17.7 0-2.6 6-7.2-17.8-17.8 0-4.4 4.7-8-17.8-17.7 0-17.7 0-53.1-17.8-53.1-53.2s32.9-108.6 32.9-108.6z" transform="matrix(.39205 0 0 .2513 84.9 175)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="3" d="M382.7 248c-3.6 3.6 4.6 61.3 7 63.8 3.6 3.5 24.9 3.5 28.4 0 2.5-2.5 0-56.7-3.5-60.2-3.6-3.6-29.4-6-32-3.6z" transform="matrix(.39205 0 0 .2513 86.4 225)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="3" d="M382.7 248c-3.6 3.6 4.6 61.3 7 63.8 3.6 3.5 24.9 3.5 28.4 0 2.5-2.5 0-56.7-3.5-60.2-3.6-3.6-29.4-6-32-3.6z" transform="matrix(-.39205 0 0 .2513 429.4 225)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="3" d="M414.6 228.5a16 16 0 1 1-32 0 16 16 0 0 1 32 0z" transform="matrix(.45063 0 0 .31327 63.9 210.4)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="3" d="M414.6 228.5a16 16 0 1 1-32 0 16 16 0 0 1 32 0z" transform="matrix(.45063 0 0 .31328 92.6 210.4)"/>
|
||||||
|
<path fill-rule="evenodd" d="M270.4 311c0 1.2-1.6 2.2-3.5 2.2s-3.4-1-3.4-2.2 1.5-2.2 3.4-2.2 3.5 1 3.5 2.2m-18 0c0 1.2-1.6 2.2-3.5 2.2s-3.5-1-3.5-2.2 1.6-2.2 3.5-2.2 3.4 1 3.4 2.2z"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="m439.4 550-7.8-10.3-12.8 1.7 1.8-12.8-10.3-7.7 10.3-7.8-1.8-12.8 12.8 1.8 7.8-10.3 7.8 10.3 12.7-1.8-1.7 12.8 10.2 7.8-10.2 7.8 1.7 12.7-12.8-1.7z" transform="matrix(.54641 0 0 .52213 17.8 -47.3)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="m496 591.7 21.3 10.7 21.3-10.7-21.3-10.6z" transform="matrix(.56007 0 0 .53518 9.8 -56)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="m496 591.7 21.3 10.7 21.3-10.7-21.3-10.6z" transform="matrix(.56007 0 0 .53518 -75.5 -56)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M535 659c-3.5-7-14.1-10.6-21.2-10.6s-14.2 0-21.3 10.7c0-14.2 10.5-21.3 21.3-21.3a23 23 0 0 1 21.2 21.3z" transform="matrix(.48568 -.26652 .27892 .4641 -137.5 131.4)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 7.9 -65.5)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 3.9 -52.2)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 1.9 -59.8)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 11.8 -52.2)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 13.8 -59.8)"/>
|
||||||
|
<path fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M386.2 652a7 7 0 1 1-14.1 0 7 7 0 0 1 14.1 0z" transform="matrix(.56007 0 0 .53518 7.9 -57.9)"/>
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path fill="#da4500" d="M238.7 159c-10 0-11.3 1.5-15.8 3 0-4.3 11.1-9.4 21.1-9.4 0 3.2-2.6 6.3-5.3 6.3z"/>
|
||||||
|
<path fill="#cac9c7" d="M254 148.8c0 2.1-1.8 3.8-4 3.8s-4-1.7-4-3.8 1.8-3.8 4-3.8 4 1.7 4 3.8"/>
|
||||||
|
<path fill="#ff0" stroke="#000" stroke-width="1pt" d="M361.4 357.9v10.6h17.7v53.1h10.7v-53.1h17.7v-10.6h-17.7v-14.2H379v14.2z" transform="matrix(.56007 0 0 .53518 9.8 -56)"/>
|
||||||
|
<path fill="#da4500" d="M244 152.6a64 64 0 0 0-23.8 3.2c0-5.9 6.8-12.7 18.5-12.7 2.7 0 5.3 6.3 5.3 9.5"/>
|
||||||
|
</g>
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path fill="#008500" stroke="#000" stroke-width=".9" d="M67.3 613c-14.2-14.2-16.6-21.4 3.6-10.1l283.7 247.5-3.4 7L67.4 613z" transform="matrix(.31554 .24102 -.11439 .19257 189.7 119)"/>
|
||||||
|
<path fill="#008f00" d="M182.3 329.6c.7-8.6-15-17.9-15.7-17.3s1 5.6 4 8.2c-5.8-2.9-11.8-4.7-12-3.4-.4 1.7 9 5.8 9.8 7.4 0 1.1-7 1-6.8 2.1.1 1.2 19.7 11 20.7 3m-15.7-25c.6-8.6-15.1-18-15.8-17.3-.6.6 1 5.6 4 8.2-5.7-3-11.7-4.8-11.9-3.5-.4 1.7 9 5.8 9.7 7.4.1 1.2-6.9 1-6.7 2.2.1 1.1 19.7 11 20.7 3m-12-22.8c.6-8.6-15.2-17.9-15.8-17.3s1 5.6 4 8.2c-5.8-3-11.8-4.7-11.9-3.4-.5 1.7 9 5.8 9.7 7.4.1 1.1-6.9 1-6.8 2.1s19.7 11 20.7 3zm-12-24.6c.7-8.6-15-18-15.7-17.3s1 5.6 4 8.2c-5.8-3-11.8-4.8-11.9-3.5-.5 1.7 8.9 5.8 9.7 7.4.1 1.2-6.9 1-6.8 2.2.1 1 19.7 11 20.7 3"/>
|
||||||
|
<path fill="#008f00" d="M146.7 256c7.7-4.6 6.2-22.2 5.3-22.3s-4.2 4-4.6 7.9c-1-6.2-3-11.9-4-11.2-1.8.7.3 10.3-.6 11.9-.9.7-5-4.7-5.8-4-.9.8 2.4 21.7 9.7 17.7m11 21.7c8.9-2 13.6-19 12.9-19.4-.8-.4-5.4 2.5-7.1 6 1.3-6.1 1.3-12.1 0-11.9-1.8.1-3.3 9.8-4.6 11-1.1.5-3-6-4.1-5.5s-5.3 21.2 3 19.8zm13.3 22.1c8-4 8.2-21.6 7.3-21.8s-4.5 3.7-5.3 7.5c-.4-6.2-1.8-12-3-11.5-1.8.5-.7 10.3-1.7 11.8-1 .6-4.4-5.2-5.4-4.5s.4 21.9 8 18.5zm14.2 29c8.4-3.2 10.5-20.8 9.6-21-.8-.4-4.9 3.1-6 6.8.3-6.2-.6-12.1-1.8-11.7-1.9.4-1.7 10.2-2.9 11.6-1 .6-3.9-5.5-4.9-5-1 .7-2 21.8 6 19.2z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#ff1900" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M209 776c0 7.8-10.2 14.2-23 14.2s-23-6.4-23-14.2 10.3-14.2 23-14.2 23 6.4 23 14.2z" transform="matrix(.48902 .26089 -.27302 .46728 313.4 -58.3)"/>
|
||||||
|
<g fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-width=".6">
|
||||||
|
<path d="M156 414.6v-7.1h7v-7.1h7v7h7.2v7.2H170v17.7h-7v-17.7zm7 60.2h7v205.5h-7z" transform="matrix(.40549 .1277 -.14842 .38308 363.6 58)"/>
|
||||||
|
<path d="M159.4 676.8h14.2v56.7h-14.2zM156 439.4h21.3c14.1 0-3.6 39-3.6 39h-14.1s-17.8-39-3.6-39zm17.6-5.4c0 3-3.1 5.4-7 5.4s-7.1-2.4-7.1-5.3 3.1-5.4 7-5.4 7.1 2.4 7.1 5.4z" transform="matrix(.40549 .1277 -.14842 .38308 363.6 58)"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#ff1900" fill-rule="evenodd" stroke="#000" stroke-width="1pt" d="M209 776c0 7.8-10.2 14.2-23 14.2s-23-6.4-23-14.2 10.3-14.2 23-14.2 23 6.4 23 14.2z" transform="matrix(.46695 -.29552 .30926 .4462 -5.1 64.5)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 11 KiB |
@@ -0,0 +1,118 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-me" viewBox="0 0 512 512">
|
||||||
|
<path fill="#d3ae3b" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#c40308" d="M25.6 25.6h460.8v460.8H25.6z"/>
|
||||||
|
<path fill="#b96b29" d="M367.4 251.8c3-.8 13.5-3.9 20.8-9.8s8.8-10.7 9.4-13.3.8-4.6-1-3.2c-2 1.5-7.7 6-11.6 8.2a43 43 0 0 1-9.6 3.7c-2 .6-3.8 1-1.7.2s8.6-3.1 13.7-8.2c5.1-5.2 9.2-12.8 9.3-17.6 0-4.7-.3-4.3-1.8-3.1s-6.9 7.8-10 10.3a96 96 0 0 1-10.3 7.3c-2.4 1.4-5.8 3-1-.2s13.8-11 17.5-21 3-16 3-17c-.1-1-1-3-1.8-1.1s-6.9 14-11.8 19.2c-4.8 5.2-3.7 4.2-4.8 5s-3 2.3-.5-.6c2.3-3 7.2-9.7 9.7-16.5a55 55 0 0 0 3.3-17.3c0-3.9-1.6-9.2-2.4-10.8s-1.5-2.8-1.7-1-4.2 13.8-6.3 19.6a75 75 0 0 1-7.1 13.7c-.8 1.2-3 3.8-.7-.6a83 83 0 0 0 7.5-29.4 47 47 0 0 0-2.8-22.3c-1.6-3.3-3.1-4.5-3.1-4.5s.3 8.3-2.2 20.3-6.1 18.2-6.1 18.2-1.5 1.8-.5-1.1c.7-2 2.2-6.2 2.8-13.4.7-7.5.8-16.1-1.4-23.4-2.4-8-6.5-12-6.5-12-1.2 11.4-1 26-5.2 38-.7-.7-1.8-2-2.2.7a67 67 0 0 1-7.4 20.6c-5.2 9.4-7.3 10.6-7.3 10.6l-1.6-3.5c-.7-1.3-1-2-2-.2-.8 1.7-3.7 8.3-10 15.6s-9.8 10.8-9.6 16.2c.2 5.5 3 8.3 6.9 10.7 3.8 2.5 4.5 3.9 3.9 6.8-.6 3-4 4-7 4.1s-6.7-.1-8 .6c-1.3.8-1.4 2.7-2.8 4-1.3 1.4-1.9 2.1-3.1 2.3l-1 .2c-.4-14.4 5.2-20.8 5.2-20.8L291 217.7l-2.4 1.1-.4-.7a12 12 0 0 1-2.8-8.3c.8.1 2.3.7 3.8.4 1.4-.3.3-1.7-.1-3a28 28 0 0 1-.2-10.3s2.3 2.4 3.7 2.4 1.3-1.4 1.5-3a12 12 0 0 1 3.2-6.9s2.5 1.7 5.3 1.7c2.9 0 6.7-.7 9.7-3.6s3-3.6 3-3.6 5.6 2 10 2.2c4.2.2 8.8-.5 11-5s-.6-8.8-1.1-9.5-2-1.3-2 0 .1 4.7-2.5 6.4-4.6 1.6-7.1.8a22 22 0 0 0-14-.3c.2-.2 3.4-3.5 6.6-4.5s5.1.4 6.2 1.6c1 1.3 2 3.4 2.8.4.9-3 .4-6.4-1-8.6a8 8 0 0 0-7.7-3.2q-4.5.6-4.3.8c.2.2-1.6-4.6-5.7-5.4-5-1-10.5.8-14.8 3.2 0 0-3.4.8-6.8-.2a22 22 0 0 1-5.9-2.9l1.2-3.7 2-.7c1-.4.8-.8 1-1.6s-2-2.4-2-2.4-.3-2.6 3.1-4.3l3-1.5s2 .3 2.9-.2q1-1 .8-1c-.2 0 1.5-.5 2-1.4q.9-1.6.3-3.3c-.4-1-1-1.3-.4-1.8a5 5 0 0 0 1.2-2.5v-1s1.2-.5 1.1-2.3-1-2.4-1.6-2.6l-.9-.1s0-1.1-1-2.5l-1.5-2c-.1 0 .7-.5.3-1.7a3 3 0 0 0-2-2.1c-.7-.3-1.6 0-1.6 0l-4-2.3-1.5-.8s0-1.1-.5-1.7-1.5-.9-2-.9l-1.2.3c-.2.1-4.5-1.1-6.4-1.4s-2-.5-2-.5-4-1.9-5.5-2.2l-2-.5s1.6-2 1.7-4.2a6 6 0 0 0-3-5.4l-1.7-.8V99l3 .3.2-4.8-3 .4.5-3h-6.2l.5 2.9-3-.2.3 4.8 3-.4-.2 1.8s-1.1.2-2.1.9a6 6 0 0 0-2.9 5.1 9 9 0 0 0 1.5 4.3l-2 .4c-1.5.3-5.6 2.2-5.6 2.2s0 .2-2 .5l-6.3 1.4s-.6-.3-1.2-.3-1.6.3-2 .9c-.5.6-.5 1.7-.5 1.7l-1.6.8-4 2.3s-.8-.3-1.6 0a3 3 0 0 0-2 2.1c-.3 1.2.3 1.7.3 1.7s-.6.6-1.5 2-1 2.5-1 2.5l-.9.1c-.5.2-1.5.9-1.5 2.6-.1 1.8 1 2.2 1 2.2l.1 1q.2 1.7 1.1 2.6c.6.5 0 .7-.3 1.8a4 4 0 0 0 .2 3.3c.6.9 2 1.4 2 1.4s0 .4.9 1 2.8.2 2.8.2-.4-.3 3 1.4c3.4 1.8 3.1 4.4 3.1 4.4s-2 1.5-2 2.4c.2.8 0 1.2 1 1.6l2 .7 1.2 3.7c-1.3.9-3.5 2.3-5.8 3-3.4 1-6.8.1-6.8.1a23 23 0 0 0-14.8-3.2c-4.1.8-5.8 5.4-5.8 5.4s-1.1-.5-4.3-.8a8 8 0 0 0-7.6 3.2 11 11 0 0 0-1 8.6c.8 3 1.7 1 2.8-.4 1-1.2 3-2.6 6.2-1.6a19 19 0 0 1 6.5 4.5s-.5-.4-4-.9c-4-.5-7.4.4-10 1.2-2.4.8-4.4.9-7-.8-2.7-1.7-2.6-5.1-2.6-6.4s-1.4-.7-1.9 0-3.3 4.9-1.1 9.4 6.8 5.3 11 5c4.4-.2 10-2.2 10-2.2s0 .8 3 3.7 6.8 3.6 9.6 3.6 5.3-1.7 5.3-1.7a12 12 0 0 1 3.3 7c.2 1.5 0 3 1.5 3s3.6-2.5 3.6-2.5l.4 2.4c.2 1.7-.1 6.5-.5 7.8-.4 1.4-1.5 2.8 0 3 1.3.4 2.9-.2 3.7-.3a12 12 0 0 1-2.8 8.3l-.4.7-2.3-1.1-15.7 18.3s5.6 6.4 5.3 20.9v-.1l-1.2-.2c-1.2-.2-1.8-1-3.1-2.3s-1.4-3.2-2.8-4-5-.5-8-.6-6.4-1.1-7-4c-.5-3 .1-4.4 4-6.9 3.8-2.4 6.6-5.2 6.8-10.7s-3.3-9-9.6-16.2c-6.3-7.3-9.2-13.9-10-15.6-1-1.7-1.3-1-2 .2l-1.6 3.5s-2-1.2-7.2-10.6a67 67 0 0 1-7.5-20.7c-.4-2.6-1.4-1.3-2.2-.6-4.2-12-4-26.7-5.1-38 0 0-4.2 4-6.6 12a64 64 0 0 0-1.4 23.4c.6 7.2 2.1 11.5 2.8 13.4 1 2.9-.5 1.1-.5 1.1s-3.6-6.3-6-18.2c-2.6-12-2.3-20.3-2.3-20.3s-1.4 1.2-3.1 4.5a47 47 0 0 0-2.8 22.3c.6 12.2 5.1 25 7.5 29.4s.2 1.8-.7.5c-.8-1.2-5-7.8-7.1-13.6A242 242 0 0 1 128 165c-.3-1.8-1-.6-1.8 1s-2.4 7-2.4 10.8a55 55 0 0 0 3.3 17.3 68 68 0 0 0 9.8 16.5c2.3 3 .5 1.4-.6.6s0 .1-4.8-5a98 98 0 0 1-11.8-19.2c-.8-1.9-1.7.1-1.7 1.2-.1 1-.8 6.9 2.9 16.9a49 49 0 0 0 17.6 21c4.8 3.2 1.3 1.6-1.1.2A96 96 0 0 1 127 219c-3-2.5-8.4-9.2-9.9-10.3s-1.8-1.6-1.7 3.2c0 4.7 4 12.3 9.2 17.4 5 5.2 11.7 7.5 13.7 8.3s.4.4-1.7-.2a43 43 0 0 1-9.6-3.7c-3.9-2.2-9.6-6.7-11.5-8.2s-1.6.6-1 3.2c.5 2.5 2 7.3 9.3 13.3s17.8 9 20.8 9.8.9.9.9.9l-7.3-1.5c-6-1.4-15.3-6.2-17.8-7.8s-2.8-2.1-2.5 1.2 3.9 10.3 9.5 13.6a53 53 0 0 0 18.3 5.1c2.6.2 2 .5.8.6a55 55 0 0 1-20.6-3.5l-.5-2.1c-.2-1.3-.9-1-1.3-.8-.4.3-.6.5-2 1.2s-1.5-.1-1.5-.6l-.4-3.1q0-1.3-1.2-.7l-5 2c-1 .6 0 1.5.6 2.6l1.5 2.8-3.8 1c-1 .2-.7.9-.4 1.5l2 4.9c.4 1 1.2.4 1.2.4l3-1.8 1 4c-.1 0-1.4.3-2.9 1.3s-1 2.4.7 2.9c1.8.5 3.8 1.4 6.1 5.6s2.9 11.6 4.3 23.4 9 17.8 10 18.5c1 .8.4 1.4.4 1.4s-1 1.5-.6 3.7a6 6 0 0 0 3.5 4c1 .4.8 1.5.8 1.5s-.6 2.5 0 7c.5 4.7 4.2 9.9 5.3 10.8 1 .8 1 1.3 1 1.7-.2.3-.9 0-2.1 1q-2.1 1.5-3.6 4c-.9 1.6-.6 2.5-.3 4.7s3.6 4.3 3.6 4.3-.6.1-.8 1.8q0 2.6 2.6 4.6c1.4 1.2 4.2 1.8 4.2 1.8l.4 2.4c.2 1 2 2 3.2 2.6s3.5.3 4.4-.2q1.5-.5 2.2 1c.6 1 .4 4.7.4 6.2s-.9 3-.4 3.8 2 .2 3.4-.6 1.7-.2 3 .6 3.3 1.6 3.8 1.5c.5 0 .3-1.4.3-2.5s.2-3.7.7-4c.2-.2 2.4-.4 4-1q1.5-.4 2-1.5c0-.7-1.1-.7-3.6-2-2.4-1.3-2.8-2-4.4-3-1.5-1-.8-1.4-.8-1.9s.7-1 1-1.5.2-2 .2-2l1.8-1.9 2.2-2.4c.8-.6 2-3.8 2.9-4.9.8-1 2.6-2.1 3.7-3.5s4-3.7 5.8-4.8c1.7-1.2 6.3-3.3 6.3-3.3s-.1 2.7 1 4c1.3 1.4 2.8-1 8-4.7s9.3-5 9.3-5l2.5 3.5c1.4 1.7 2.7-.1 9.1-9 5-7 11.8-13.4 14.4-15.8l1.4.6-.4 1.2c-.6 1.8-1.5 3-2.8 5.6s-3.2 3.1-5.4 6.6Line truncated
|
||||||
|
<path fill="#d4af3a" d="M300 253.6c0-10.9 5-17.7 5-17.7l-13.7-15.6c-12.9 5-24.8 5.3-35.3 0-10.6 5.3-22.4 5-35.4 0L207 236s5 6.8 5 17.7-2 19-3.3 24.2-2.2 15 .6 22c2.7 7 5.7 13.2 17.8 18.2 12 5.1 18.4 7.7 22.6 11.2 4.2 3.6 6.3 6.4 6.3 6.4s2.1-2.9 6.3-6.4c4.2-3.4 10.5-6 22.6-11.2 12-5 15-11.2 17.9-18.2s1.7-16.8.5-22-3.3-13.3-3.3-24.2z"/>
|
||||||
|
<path fill="#b96b29" d="M295.7 258a30 30 0 0 1 4.3-19.4L288.3 225c-9 5-21.4 4.3-32.3-.7-10.8 5-23.3 5.7-32.2.7L212 238.6s6 9.1 4.3 19.5-5.4 26.8-4.5 33.4c1 6.7 5.7 14.5 10.5 18.5a61 61 0 0 0 17.2 9.4 49 49 0 0 1 12.5 7.2c2.4 1.8 4 3.4 4 3.4s1.5-1.6 3.9-3.4c2.3-1.8 7.3-5.4 12.4-7.2a62 62 0 0 0 17.3-9.3 33 33 0 0 0 10.4-18.6c1-6.7-2.7-23-4.4-33.4z"/>
|
||||||
|
<path fill="#1d5e91" d="M294.6 259.3a35 35 0 0 1 3.3-20.2l-10.5-11.7c-8.8 4.6-19.6 3.4-31.4-.7-11.7 4-22.5 5.3-31.4.7l-10.5 11.7s5 9 3.4 20.2-4.3 23.9-3.8 31.3l.2 1.6h9.5s.6-.7 1.6-1.1l1.4-.8c-.1 0 .2-2 1.4-2.3q1.5-.5 2.1-.4c.6.1.8-.1 1.4-.7s2-2 2.7-3.9 1-2.7 1-4 .5-3.2.5-3.2-.2-.6-1.2-.3c-1 .4-1.1-.6-1.2-1.6s0-2.9.5-3.8l.8-1.2-.4-1.6s-6.1 1.3-7.2-3.5l-1.4-7.5q0-.7-1.1-.7c-1.1 0-2.5-.5-3.8-3-1.2-2.4-1.3-3.4-1-4.4s1-.6 1.3-.3c.2.3 1.8 2.7 2.5 2 .6-.6.3-.6-.6-2.5-1-1.8-.6-2.4-.5-3.3 0-1-.3-2.2-.3-2.7s.5-.9 1-.6l1.4 1c.2.2.3.7.9.4.5-.3.2-2 .2-2.5q.1-1 1.4-.6a2 2 0 0 1 1.2 2q-.1 1 .4 1.1c.3 0 .7-.9.7-1.5s.4-1.5 1-.7.7 1.8.5 3 .2.6-.2 2.6-.6 3-.5 4.5 0 2.4.8 4.2c.8 2 1.4 4.1 2.8 4.2h1.8s1.6-3 2.8-3.8 3-.8 4.2-1q1.7-.1 1.7-.7.2-.4-1.9-.6c-1.4-.2-2.7-.6-2.7-1.7s.2-2.1-.7-2.2c-1 0-3.4-.8-4.2-2.4s-.7-1.9-.6-2.7 1.2-2.6 2-1 1.1 1.8 1.7 2.2c.6.3 1.4.4 1-.7a5 5 0 0 1-.5-2.6q.1-1.2 1.3-1.3l3.6-1 1.1-.3s.1-.7 1.4-1.1a10 10 0 0 1 3.2-.5c1 0 1.6.2 2.9-.4a6 6 0 0 1 3-.7 4 4 0 0 1 2.1.8c.6.4.4 1 .4 1.5s.1.8 1 .8q1.2-.1 2.4.4c1.2.5 2 .7 1.2 1.6-.7.8-.7 1.8-1.2 1.8q-.4.1-.6.7-.4.5.2.9c.5.3.5.6 1.2 1.3s1.8 1.3.7 2.4c-1 1-3 2-3 2s-1 .4.2 1.2c1.3.6 1.8 2.1 2.3 2.4l1.2.8c.3.1.2 1.2.2 1.2l-1.5.8q-1.5.6-2.8.7-1.3-.2-1 .8l1.2 2.2.4 1.3s1.4.3 2.6.3 1.9 0 4.6-.3 3.6-.4 6.3 0c2.7.2 3.1.1 3.8.4l1.4.6s3.8 0 6-.5c2-.5 2.5-1.8 2.2-2.5s-.3-1.8-3.9-2.3-5.1-.3-8-.9c-2.9-.5-7.4-2-8.2-5.6s2.1-5.2 3.6-5.9 4.6-1.8 6.1-2.4a14 14 0 0 1 7-.8c2.8.4 4.5 1.2 5.6.5s2-.2 1.8.9c-.2 1-1.3 3-4.5 4s-7 1.1-9.1.5c-2.1-.5-3.7-1.3-5-.9s-2.6 2-1.3 4c1.4 1.8 8.4 2.4 10.3 2.6s8.8-.3 10.8 3.1c2 3.5.4 7.3-2 8.2a30 30 0 0 1-6 1.7l-1.8.2s.8 1.9.8 2.6-.1 2.2.8 2.8 2 1 3.5.8 3.3.8 3.4 2.2c0 1.3.5 3.1.6 5.8.2 2.6 0 4.8 0 4.8s0 1-.4 2l-2.2 4.7h10.2l.1-1.6c.5-7.5-2.1-20.2-3.8-31.4z"/>
|
||||||
|
<path fill="#6d8c3e" d="M215 297.1c2 5.8 6.2 11.5 12.5 15.3 8.7 5.3 12.4 4.6 20.7 10 8.2 5.3 7.7 6.2 7.8 6.2s-.4-1 7.8-6.3c8.3-5.3 12-4.6 20.7-9.9a29 29 0 0 0 12.4-15.3z"/>
|
||||||
|
<path fill="#d4af3a" d="M222.2 253c1 1 1.6 1.3 3 1.1 1.5-.1 1.8 1 2 2 .3 1.2.3 3.9 1.4 6.3 1 2.5 4.4 2.6 4.4 2.6q.3-2 1-4c.2-.3.6-1.4-.6-1.4s-1.2-.5-2-1.7a23 23 0 0 1-2.4-6.3c-.4-2 .3-5 .6-6.1q.5-1.8 0-2c-.4-.3-1.2 0-1.2 0s.3.9 0 1c-.4.2-.7-.5-1-1.2-.2-.7-.5-.6-1-.5q-.7.1-.1 1c.3.5.1 1.4.1 1.4l-1.5-1c-.4-.2-1.5.2-1.6.8 0 .6.4 2 1.4 2.9s1 2.4.6 3.1c-.3.8-1.7.7-2 .3a1 1 0 0 0-1.2 0c-.4.5-.8.7.1 1.7m-.5-2.6-.8-.7-.6-.8v.9c0 .6.6 1.2.6 1.2zm2-6.8q.8-.2.5-.8l-1-1.2h-.6l.4 1.4q.1.9.7.6m2.6-2.1q-.1.7.4.4.7-.5.4-1.1c-.2-.4-.7-.7-.7-.7l-.3-.2zm3.7.6-.2-1s0 .5-.2.9q-.4.3 0 .5.3.3.4-.4m5.8 25c.7 1.7 2.5-1 4.2-3 1.7-2.2 1.6-.6 1.4 1.2a8 8 0 0 0 1.1 4.7c.7 1.2 1.3.5 1.5-.3.3-.8.5-2.6 1-3.4.7-.9 1.2-.1 1.3.6a8 8 0 0 0 1.7 3.3c.9 1 1.3-.3 1.5-1.5l.9-3.7c.2-.7 1-.1 1-.1l1.7 1.6c1 1 1.2-.6 1.3-1.4.1-.9-.2-2.7.2-3 .3-.3 1.2.4 2.7 1.1s1.4-.7 1.2-1.4a8 8 0 0 0-2.1-3.6c-1.2-1-1.4-2.2-1-2.7s1.8 0 2.7-.2q1.1 0 2-.3c.7-.1.7-.8.7-.8s-.5-.8-1.5-1.7-1.5-1.5-2.7-1.8c-1.1-.2-2.7 0-2.7 0l-.2-.3q-.4-.4-.7 0l-1.3 1.2q-.6.6-1 .4c-.3-.1.3-.5.3-.5s.6-.5 1.1-1.6q.9-1.8.3-2.7c-.4-.5-1.7 0-2.3 0q-1 .1-1.2-.2c0-.4.7-.2 1.5-.3s1.4-1.1 1.7-2q.3-1 1-.2c.7.8 1 .4 1 0l1.1-2c.6-.8 1-2 .3-2.7s-2.1-.7-2.7-.2q-.9.6-1.2 2.2c-.1 1.3-.5.6-.5.2s-.5-1-.3-1.5 0-1 0-1 0-1.1-1.2-1.4a9 9 0 0 0-3.7.4c-1.2.3-1 1.3-1 1.3l-1.8.1-3 .8q-1.3.2-1.2.7l.2 1.2s.7 0 1 .2q.6.5-.2.8-.6.1-.6.8 0 .9.7.7.7-.1 2.5.5c1 .5 1.4 1.5 1.3 2-.2.6-1.7 1.3-2.5 1.3s-1 .2-1 .7 1 .5 1 .5l2.7.2c1.9.3 2.2.6 3 1.4s.2.9-.2 1q-.7.6-1.6 1-.8.6-1.6.8c-.5.1-2 0-4.5 1s-3.4 4.5-3.4 4.5l1.5-.8 4-2.1c1.3-.8 1.3.4.6 1.6s.6 1 1 .6.9-.7 2.2-1.4.8.4.8.4-.4.8-.5 2.7c-.1 1.8 1.9 0 2.7-1.1 1-1.2 1.3 0 .7 1.3s-2 2.4-2.7 2.4-1.3-.7-1.4-1.2.3-1.8-.2-2.1-1.7 0-2.7.6-1.1.1-1.4-.4.4-.7.2-1q-.5-.3-1.2.2l-3 1.5c-1.7 1-1.6 1.4-1.6 2.7s.3 1.6 1 3.3z"/>
|
||||||
|
<path fill="#d4af3a" d="m252.5 239.1 1-.5 1.6-.3c.8 0 .6-.5.5-.9q-.2-.5-1.5-.5c-1.3 0-1.6.5-2.1.7-.6.3-1.3.4-1.3.4l.5.7q.7.9 1.3.4m4 4 .9-.2 1.3-.6c.7-.3 1-.4 1.4-1.1s-.5-.8-1.6-1.1q-1.7-.4-1.9.5.1.6-.5 1.8c-.6 1.2 0 .7.4.7m-.3 1c-1.1 0-2.4 1.7-2.9 2s-.2.6-.2.6a7 7 0 0 0 .7 3.1h.8q.8 0 1.7-.7a4 4 0 0 1 2-.8 6 6 0 0 0 2.3-.9c1-.5-.5-1.4-1.6-2.3a4 4 0 0 0-2.8-1m-15 3.9q1.5-.3 1.4-1 0-1-1.6-1c-2.8.3-5.5-.7-6-3.5 0 0-1 1.7.8 3.7 1.7 1.9 4.6 1.9 5.5 1.8zm-9.4 43.7.5.5h4.3q.9-.5.7-1.3l1-5c.7-2.8 1.7-4.8 2.1-6 .4-1.1-.4-1.5-.7-2.2s-.8-2-.9-2.6-1.1-.6-1.7-.5-.6 1-.7 1.7c0 .9-.5 3.4-.9 5.5-.4 2.2-2.8 5-3.5 5.7q-1 .9-1.7.9-1.1 0-2 .3-1.2.5-1 1.7c0 .6 1 1 1 1l1-.9q.6-.4 1.7-.6 1 0 .5.5c-.3.3-.2.4.3 1.3m-6.5-.2q-.5.1-.7.7h2.8l-.3-.7c-.3-.4-1.2-.2-1.8 0"/>
|
||||||
|
<path fill="#d4af3a" d="m229 291.4-.9.8h3q.3-.5-.2-1c-.7-.5-1.4 0-1.8.2zm26-1.2q-.4 0-1 .6t-.7 1.2v.2h1.3v-.2q0-.5.7-1.2c.6-.4 0-.6-.2-.6zm2.5 1.3q0-.6-.4-.7c-.3 0-.8.6-1.2.8l-.5.6h2z"/>
|
||||||
|
<path fill="#d4af3a" d="M269 277.7c1-.6.8-.8.9-1.5q0-1-.3-1.1c-.4-.3-1.8-.9-3-.9s-3.6.3-4.6 1c-.9.5-1.3 2.8-1.3 4q0 1.3.5 1.2c.5-.1.9-.8.9-.8l1.9 2.7c.7 1 .4 1.5 0 2.3-.3.8-1.4 3-2.4 3.5s-1.6 0-2.8-.1q-1.4-.3-2.3.8c-.7.6 0 1.1.3 1.1q.7-.1 1.4.5c.3.4 0 1.3 0 1.8h5.5c.9-.4 1.6-1.7 1.8-2.4.3-.8 1.5-1.9 2-2.4.7-.6.7-1 .7-2.1a5 5 0 0 0-.7-2.7 5 5 0 0 1-.7-2.8c.1-.7 1.3-1.5 2.2-2.1"/>
|
||||||
|
<path fill="#d4af3a" d="M260 264q.7 1-.5 1.2-1.1.3-3 .2c-1.9-.1-1.1 0-1.2.7a5 5 0 0 1-.8 2.9c-.8 1-1.7.5-2.2.2q-.9-.6-1 .4l-1.2 2.2c-.6 1-1 1-1.4 1-.4-.2-.8-.8-1.2-.8q-.7-.2-1.7-1.9c-.6-1-1 .1-1.4 1q-.5 1.4-1.6 1.4c-.8-.1-1.7-1.6-2.1-2.5q-.6-1.7-1.4-1.7c-.7 0-1.9 1.4-2.6 1.8q-1 .6-1.3-.2-.6-.6-1.3 1.3-.5 1.8.7 2.4c.6.3 1.8-.6 2.7-1.1.8-.6 1.2-.4 2-.4s.8 1.4 1.2 3.4c.4 2.1 1.6 2.4 1.6 2.4s.4.3 1.3-1.2c1-1.6 1-3.2 2-3.2s1 .2 1.8 1.5c1 1.2 1.7 2.1 2.2 1.2s1-2.3 1.6-2.8c.5-.5 1-.6.5.3s-1 1.8.4 1.5c1.2-.4 6-2.4 8-2.8l5-.5 4.4-.3c1 0 1 0 1.1-.8 0-.7-.5-.5-2.4-.5q-2.8 0-4.6.4-2.1.3-1.8-.2c.3-.3.7-.6 2.2-.8l6.4-.4c1.7 0 1.1.4 1 1.6-.1 1 .1 2.7.2 3.7s.2 1.4 1 2.8c1 1.5 4 2.4 5 2.8.9.5.7 0 .7-.8q.1-1.2 1.5-.4 1.4.6 3 1c1 .3 1 .5 1 1l.3 2.5c.1 1 0 1.3-.4 2l-2.1 2.4s-.8 0-2.5.4-2.2 1.2-2.4 1.9.3.9.4 1.1v.9h.4l.6-1c.6-.7 2-.9 3-.4q.9.6.6 1.4h3.9l2-4.2c.5-.9.7-1.5.7-2.8s.2-6.2 0-6.4 0-1.1 0-2.5c-.1-1.4 0-2.4-2.6-2.7-2.5-.2-3.3-1.3-4.2-2.2-.9-.8-.6-2.8-1.3-5.2s-2.4-2.8-5.4-3.4a25 25 0 0 0-7.5-.5c-2.2.2-5 .6-6.6.4s-1.2.3-.7 1.2z"/>
|
||||||
|
<path fill="#d4af3a" d="m274.5 291.6-.6.6h2.6l-.2-.8q-.7-.6-1.8.2m4.7-.1-1 .7h2.7v-.3c0-.4-.9-.9-1.7-.4M214 293l.6 2.5h82.8l.6-2.5zm55.8-39.6c2.7 1.8 11 1.9 14.9 2.6s3.6 3.8 2.4 5.4c-1.2 1.5-6.7 1.7-7.7 1.8.6.4 1.4 1.6 1.4 1.6l2.2-.2a12 12 0 0 0 6.5-2.2c2-1.7 1.9-4.3.1-6.1s-4.6-2-7.6-2.3l-8.5-1.1q-5-1-4.9-3.8c0-2 1.8-3 3-3.3 1.4-.4 3.4-1 5.3-.3s4 .8 7.6.3 4.4-3 4.4-3-.4.3-2.3.3-3.1-.7-6.5-.8c-3.3 0-4.5 1.2-6 1.7-1.6.6-5.8 1-7 3.7s.1 4 2.8 5.7z"/>
|
||||||
|
<ellipse cx="248.1" cy="241" fill="#b96b29" rx="1.6" ry="1.5"/>
|
||||||
|
<path fill="#b96b29" d="M281.6 243.7c-2-.2-4.8.3-4.8.3s.6.4 1.3.4q.9-.1 3.9.3c2 .3 3.5-.6 3.5-.6z"/>
|
||||||
|
<path fill="#1d5e91" d="M262 282.2q-.6-1-1.2-.2c-.6.5-1.2-1.2-1.3-2.3l.4-4.4s-2.8.5-4.3 1-3 .7-4.2 1q-1.7.6-2.5.4l-3.1-.2c-1.1 0-1 .2-2.1 1.3-1.1 1-1.4 1.7-2.3 3.7q-1 2.8-1.4 6.4-.3 2.4-.7 3.3h13l.5-1.4a3 3 0 0 1 1.5-1.4q.9-.2 1.2-.8.6-.9 1.6-1.4 1.2-.4 3 .2c1 .3 1.4-.5 1.7-.9l1-1.7c.6-.8.2-1.4.2-1.4s-.5-.4-1-1.2m20.8.4q-.1-1-.5-1-.8 0-1.9-.5c-.6-.3-.6 1.5-.6 1.5s-1.5.1-3.2 0c-1.7-.3-3.8-1.8-4.5-2.3-.7-.6-1.5-1.7-1.5-1.7l-1 .8q-1 .6-1 2.5c-.1 1 1.2 2.4 1.3 3.5.1 1-1.2 3.3-1.8 4.1-.4.6-1 2-1.3 2.7h6.2l.7-1c.6-.7 1.7-.9 1.7-.9l.3-.8q.2-1 2.4-1.9c1.6-.6 3.4-.7 3.4-.7s.8-.6 1.3-1.5.2-2 0-2.8"/>
|
||||||
|
<path fill="#d4af3a" d="M230.6 351.6c1.2.4 3.4-.4 4.5-1.8 1-1.3 1.4-3.3 2.2-4.5l1.4-2s-.2 2-1.2 4.5a17 17 0 0 0-1 7.3c.5 2.8 2.4 3.3 3.5 3q1.3-.7 3.2-3.6l1-2c.7-1.5 1.4-3.8 1.7-3.7.7.1.3 1.7 0 2.8-.6 2.2-1.6 5.4-1.7 7.3-.1 2 .1 5.5 2 5.7 2 .1 3-1.6 4.1-5.6s1.3-5.2 1.8-5.2.5 2.7.5 3.9a20 20 0 0 0 1.7 7.9c1 .8 1.7.7 1.7.7s.8.1 1.7-.7c1-.9 1.7-6.7 1.7-7.9s0-3.9.5-3.9.7 1.3 1.8 5.2c1 4 2 5.7 4 5.6s2.2-3.7 2.1-5.6c0-2-1.1-5.2-1.7-7.4q-.6-3.3 1.7.8l1.4 2.7q1.5 2.4 2.8 3c1.1.3 3-.2 3.5-3 .4-3-1.7-11.2-1.7-11.2l.9 1.4c.8 1.2 1.2 3.2 2.2 4.5 1.1 1.4 3.3 2.2 4.5 1.8s1.3-2.5.4-6.4-4.3-6.1-6.8-9.2c-2.3-2.9-4-8-4.3-8.7a29 29 0 0 0-8.4 6l-6.3 6.6s-1-1.3-6.2-6.7a29 29 0 0 0-8.5-6c-.2.7-2.4 5.6-4.7 8.4-1.1 1.5-2.7 2.6-3.9 4a13 13 0 0 0-2.5 5.6q-1.3 5.8.5 6.4z"/>
|
||||||
|
<path fill="#d4af3a" d="m230.5 364.8 2.3-2s1-1 .8.8c-.3 1.7-.9 5.3-.5 6.8.4 1.6.6 3 2.5 1.8s2.3-1.9 3.2-2.8c1-1 2-1.4 1.7.1-.2 1.5-1 3.8-.6 6.9q.6 4.6 3.8.9a34 34 0 0 0 3.7-6.3c.6-1.3 1.8-2.4 1.8 0 0 5.1 1.2 12.6 6.8 14.5 5.7-2 6.8-9.4 6.8-14.5 0-2.4 1.2-1.3 1.8 0a35 35 0 0 0 3.7 6.3q3.2 3.8 3.8-1c.4-3-.3-5.3-.6-6.8s.8-1 1.7-.1 1.4 1.6 3.3 2.8 2-.2 2.4-1.8c.4-1.5-.2-5-.5-6.8s.9-.8.9-.8l2.2 2c1.5 1.3 3.6 1.4 3.6 1.4s1-5.3-1.3-8.5l-3-4.3s-1.3 0-2.2-.4l-2-1.1v3l3.4 3.3c.7.8 2 2.1 0 .9l-3.3-2.6s-.4 2.7-1.2 3.4-1.9.8-2.3.6l-.5-.2-.1 1s1.7 3.2 2 4.5q.2 1.8-1-.5c-.8-1.7-2.3-3.8-3-5-.7-1-1.7-1.8-1.6-.4.1 1.5-.5 4.1-1 5l-.8 1.4 1.2 4.4c.1.6 0 1.4-.6.5s-2.2-4.5-2.2-4.5-2.3 0-3-.7c-.6-.6-1.6-3.1-1.6-3.1s-.9 3.1-1.6 4.1-2.6 2-2.6 2l-.2 6.5q0 1.4-.6 1.4t-.6-1.4l-.2-6.4s-1.8-1-2.5-2c-.8-1-1.7-4.2-1.7-4.2s-1 2.5-1.6 3.1-3 .7-3 .7-1.6 3.7-2.2 4.5c-.6 1-.7.1-.5-.6 0-.6 1.1-4.3 1.1-4.3l-.8-1.3a13 13 0 0 1-1-5c.1-1.5-1-.8-1.6.4-.7 1.1-2.2 3.2-3 4.9q-1.1 2.4-.9.5c.3-1.3 2-4.5 2-4.5l-.2-1-.4.2c-.5.2-1.6 0-2.4-.6s-1.1-3.4-1.1-3.4l-3.4 2.6c-2 1.2-.6 0 0-.9l3.4-3.3v-3s-1 .7-2 1l-2.2.5-3 4.3c-2.2 3.2-1.3 8.5-1.3 8.5s2.1 0 3.6-1.4m27.5 35.7-2-2.5q.3 0-1.9 2.5c-1.4 1.7-4.8 4.3-4.8 4.3A17 17 0 0 0 256 417c2.2-1.4 3.5-3.3 4.9-5.3 1.6-2.4 1.9-6.9 1.9-6.9s-3.4-2.6-4.8-4.3"/>
|
||||||
|
<path fill="#b96b29" d="M255.3 406c.1 1 0 3.6.7 3.6s.5-2.6.7-3.6l.1-3.5h-1.7s0 2.4.2 3.5"/>
|
||||||
|
<path fill="#d4af3a" d="M256 161.4a54 54 0 0 1-17-2.2l-4.2 1a55 55 0 0 0 21.2 3.4c9.3-.1 14.7-1 21.1-3.5l-4.2-.9a54 54 0 0 1-17 2.2zm21.9-5-4-2.7c-.7-.4-.1-1.4 0-1.8a18 18 0 0 1 5-1.7c1.6-.4 1.4-.8 1.4-.8s-1-1.1-7.2-1.8-17.1-.5-17.1-.5-11-.2-17.1.5-7.2 1.8-7.2 1.8-.3.4 1.4.8 4.8 1.3 5 1.7c0 .4.6 1.4 0 1.8l-4 2.7c-.5.4-.5 1 .2 1 .8 0 .8 0 3-.4a101 101 0 0 1 37.5 0c2 .5 2.1.5 2.9.4s.7-.6.2-1"/>
|
||||||
|
<path fill="#d4af3a" d="M270.8 159c-4.2-.5-7-1-14.8-1s-10.6.5-14.8 1c2.6 1 7 1.6 14.8 1.6s12.2-.5 14.8-1.6m-19.4-61 3.5-.4-.2 3 3.2-.2-.3-2.9 3.5.5.2-2.8-3.7.3.6-3h-4l.7 3-3.5-.3zm10.4 7.2h-4.4v-3.7l-1.3-.1H255v3.8h-4.5a5 5 0 0 0-.2 2.1H262v-.6l-.3-1.5z"/>
|
||||||
|
<path fill="#1d5e91" d="M256.1 112c2.6 0 4.8-1.5 5.6-3.6h-11.2c.8 2.1 3 3.6 5.6 3.6m2.4-10.2v2.6h3a6 6 0 0 0-3-2.6m-4.8 0a6 6 0 0 0-2.9 2.6h3z"/>
|
||||||
|
<path fill="#b96b29" d="m259.5 149.3-3.5-1.6-3.5 1.8c-1.3.6-1.1.4-1.5 1.4q-.5 1.1.6 1.4l4.4 2.3 4.9-2.3c.9-.4.7-.9.5-1.6q-.3-.8-2-1.4z"/>
|
||||||
|
<path fill="#d4af3a" d="m259.9 150.4-4-1.8-3.7 1.8 3.9 2zm-4-15s-.3 1.4-.7 2q-.7.7-.6 1.8c.1.7.9 1.1 1.4 1.1s1.2-.4 1.4-1.1q0-1-.7-1.8c-.4-.6-.7-2-.7-2z"/>
|
||||||
|
<circle cx="256" cy="141.7" r=".7" fill="#d4af3a"/>
|
||||||
|
<path fill="#d4af3a" d="M253.4 138s1.3-1.4 1.7-2.3l.9-1.6.8 1.6c.5.9 1.7 2.3 1.7 2.3s1.8-1 2.8-4.3c1-3.4-.3-7-.7-10.8l-.8-10.4a8 8 0 0 1-3.8 1.1 8 8 0 0 1-3.8-1.1s-.4 6.6-.9 10.4-1.7 7.4-.7 10.8 2.8 4.3 2.8 4.3"/>
|
||||||
|
<ellipse cx="256" cy="130.4" fill="#b96b29" rx="3" ry="2.9"/>
|
||||||
|
<ellipse cx="256" cy="123.5" fill="#b96b29" rx="2.9" ry="2.8"/>
|
||||||
|
<ellipse cx="256" cy="117" fill="#b96b29" rx="2.8" ry="2.7"/>
|
||||||
|
<circle cx="256" cy="129.9" r="1.6" fill="#d4af3a"/>
|
||||||
|
<ellipse cx="256" cy="123" fill="#d4af3a" rx="1.6" ry="1.5"/>
|
||||||
|
<ellipse cx="256" cy="116.5" fill="#d4af3a" rx="1.4" ry="1.3"/>
|
||||||
|
<path fill="#b96b29" d="M255.9 358.5q.3 0 .2-1.4l.4-4.3c.2-1.2-.3-1.2-.7-1-.3 0-.5 1-.5 1.5l.2 4.4q0 .8.4.8"/>
|
||||||
|
<path fill="#d4af3a" d="M224.4 383c1.4-.8 2.6-1.7 5.6-3.8a13 13 0 0 0 4.2-4.2h-1q-1.1-.2-1.2-1.7l.2-4.4c.2-2-.3-3.5-.3-3.5s-1.3 1.2-3.3 2.3-2.8.5-2.8.5l-.5-2-2 4c-1.2 2-4.5 4.4-6.8 6.2a31 31 0 0 1-6.9 4.2c-1.9.7-.8 1.6-.8 1.6s2 2 7 2.8c5 1 7.3-1 8.6-2"/>
|
||||||
|
<path fill="#d4af3a" d="M241.9 381.1c-1.8-.2-2.9-.8-3.2-2.8-.2-1.2 0-6 0-6l-1.2 1.2c-.7.9-2.2 1-2.2 1s-.2.7-.9 1.8a10 10 0 0 1-3.2 3.5l-5 3.4a30 30 0 0 1-4.5 3.3 8 8 0 0 0-3.8 5q-1 3.6 1.8 3.2c2-.2 2.2 0 5.4-.8s7.7-4.4 11-6.5c3.4-2 5.8-6.3 5.8-6.3"/>
|
||||||
|
<path fill="#d4af3a" d="M250.7 384s-1.3-1.7-1.8-3.6l-1.5-5.8-1.5 3.5c-1 2.3-3 2.9-3 2.9s0 1-1.5 2.9a23 23 0 0 1-6 5 23 23 0 0 0-7 5.6c-1.6 2.3-1.7 4.7-1 5.7.8 1 1.9-.2 3.4-1 1.5-.7 5.9-1.4 12-4.6s7.9-10.6 7.9-10.6"/>
|
||||||
|
<path fill="#d4af3a" d="M253.3 397.5c1.7-2.5 2-8.5 2-8.5l-1.5-1-2.3-3.1-2 4a16 16 0 0 1-4 4.6c-2.2 2.1-5.2 4.1-6.5 6.3s-.6 6.7.2 8.1c.9 1.5 2 1 3.3-.2l3.9-3.4c2.2-1.9 5.3-4.3 7-6.8z"/>
|
||||||
|
<path fill="#b96b29" d="M233.6 347a23 23 0 0 1 2.7-5l1.3-2.5s-2.1 2-3 3.4a15 15 0 0 0-1.7 4.2q-.6 2.4.7 0zm8.5 1.6.5-2s.4-1.2.2-1.3q-.5 0-1.7 2.7c-.6 2-1.5 4.8-1.3 5 .1 0 2-3.2 2.3-4.4m6.7 5.4.4-1s.7-2.1.7-2.6-.2-1-.8.2q-.7 1.8-.7 2.7c-.2.5-.8 2.2-.6 2.4s.6-.9 1-1.7M228 374.4l-.9-.5-1.3 1.5-2.2 2a9 9 0 0 1-2.8 2.4q-2.1 1-1.7 1.1c.3.3 1.2-.3 1.2-.3s.4 0 2.1-1 2-1.6 3.1-2.5c1.1-1 2.5-2.7 2.5-2.7m7.3 8.7c.7-.7 1.3-1.2 1-1.5q-.6-.6-1.4.1l-3 2.8-2.2 1.7c-.7.6-2.4 1.5-3.2 1.9-.8.5 0 .5 0 .5l1.3-.5 2.6-1.1 1.8-1.1zm8.4 5.8c.6-1.1 0-.8-1-.4-.8.3-2.8 2.5-4 3.3l-3.1 1.7c-1 .5-1 .7-1 1q.2.3.9 0l1.8-1 1-.2q.8-.1 2.6-1.7 2-1.3 2.8-2.7m4 7.7c-1.6.9-4.1 2.9-4.8 4.2q-1.1 2.1-.9 2.6c.2.2 1-.3 1.3-.8l1-1.4s2.5-2.5 3.4-3a9 9 0 0 0 2.9-3.3c.3-1 0-1.5 0-1.5s-1.5 2.3-3 3.2z"/>
|
||||||
|
<path fill="#c52126" d="M208 181.3a24 24 0 0 0-19-1.6q-3.4 1-6 .2c-2.8-.7-5-3.2-5.7-6.3 0 0-2.6 3.3 0 6.8a8 8 0 0 0 4.6 3.2c5.5 1 10.8-2.3 15.3-2.6 4.4-.2 7.8 2.5 7.8 2.5s1 .9 2.5 0q2.3-1.2.5-2.3z"/>
|
||||||
|
<path fill="#d4af3a" d="m192.3 338.7-1.3-.2-2.8-.9q-.9-.4-1.7.3c-.8.7-4.4 2.9-4.4 2.9h6.5c2.2 0 3.7-2.2 3.7-2.2z"/>
|
||||||
|
<path fill="#d4af3a" d="M210.5 337.6v-.7l-.8-1.8s-1 .6-2 1.6c-1.3 1.3-2.6 3.2-3.2 4-1.1 1.3-2.6 2.6-1.3.4s1.7-3.2 2.7-4.6l1.7-2.2-3.7-.6c-2.7-.3-4.4-1.4-5.6-2.1l-3.5-2s-4.6 2-6.3 2.4c-1.8.5-2.8.7-1.1 1.5s3.1 1.1 4 1.6c.7.6 3.3 1.4 4.8 1.4 1.6 0 4.2 0 3.2 1q-1.6 1.8-3.3 6.4a22 22 0 0 0-1.2 7.4l.2 2.7s1.9-2.1 7-5.3 7-4.3 7.9-4.7 1.4-.4 1.2-1.6z"/>
|
||||||
|
<path fill="#d4af3a" d="m230 322.1-1-.4h-.2c-.6.4-3.7 2.4-5 4.5s-2.5 2.2-1.1 0a19 19 0 0 1 3.5-4.4l1-1a56 56 0 0 1-7.8-4.5c-.3.7-1 2-1.7 2.7-1 1.1-3.5 2.1-3.5 2.1s2-2 2.6-3.5l.8-2.6a34 34 0 0 1-5.1-4.9 19 19 0 0 1-4.4 7.9c-4 4.5-12 6.7-12 6.7s-2.8.3-.5 1c2.2.5 3.8 1.5 8.3.8 4.4-.8 4.7-1 5.3-1.2s2.2 0 0 .8-7.4 1.6-9.3 1.4c-2-.1-4.7-.6-1.9 1s4 2 5.7 2 2.5.6 4.2.6 2.1-.3 3.3-.6q1.9-.2 2 .7c.1.9-1.6 1.8-1.6 5q0 5 .6 6.4c.3 1 1 2.5 1.5 3.4.5 1 .7 1.8 1.1 0 .5-2 1.6-4.7 2-6.6s.7-1.9.8-2.9 1.2-1.6.8.1l-1.4 5.5c-.4 1.5-1.2 4.4.2 2.1 1.5-2.2 6.8-8.3 7.7-9.3q1.4-1.6 2.3-3c.6-.9 2.5-2 3.4-3.4.8-1.2 3.2-3.5 4.2-4.3L230 322zm-74.2-137.7c.1 3.3 3.8 9.6 5.8 13 2.1 3.3 2.7 4.6 2.3 4.8-.3.3-1.2-1.5-2.3-3l-5.2-7.6c-1.9-2.6-4.6-9-5-9.5-.3-.6-.5-1-1.4-.2-1 .7-2.7 7.6-.6 12.3 2.2 4.8 5.4 8.2 6.7 9.8 1.4 1.7 3.2 4.2 2.9 4.5s-.8-.5-2.4-2.2l-6.2-6.8a49 49 0 0 1-4-6.5q-.5-.7-1-.1c-.5.6-1.4 2-1 5.7.5 5.2 3.3 8.2 5.7 10.9 2.4 2.6 4.8 4.8 4.5 5.2-.4.3-2.9-1.9-4.6-3.5s-6-5.7-6.8-6.9c-1-1.1-2.3-.6-2.2 4 0 4.4 5 9 7 11.5 2.2 2.5 4.7 4 4.5 4.5s-2.3-.8-3.6-1.7c-1.4-1-6.4-4.2-7.4-5-1-.6-1.5.7-.3 5 1.3 4.3 5 7.7 7.2 9.3s5.8 3.5 5.6 4q-.5.6-3.8-1.1c-2-1-4.5-2-6.3-2.5s-1.8.8-1.8.8-.4 3.7 2.3 6.6a14 14 0 0 0 8.5 4.6c2.8.5 3.3.6 3.2 1q0 .6-1 .5c-.7 0-3.3.3-5.7.3s-3.7.6-3.7 1.7c0 1-.1 2.5 3.5 4.9 3.7 2.4 7.7 2.8 9.5 3.1s1.7.5 1.4 1-1.2.5-3.9.8c-2.7.2-4.3 1.3-4.7 2-.4.8.2 2.9 2.4 4.4q3.1 2.2 9.4 1.7c4.3-.3 5.3-.3 5.5.4.1.7-1.3.9-3.4 1.3-2.2.3-3.8.3-5 1.3s-.3 3 1.7 4.6c2.1 1.5 7.2 1.5 9.4 1.3 2.1-.1 5.4-.9 5.6-.3s-1.5 1.4-3.8 2.2-2.8 1.3-3.2 2.2-.1 1.8 2.5 2.8c2.7 1 6.5 1 8.5 0q3-1.3 3.5-.8.3.4-.5 1.2a10 10 0 0 1-4.3 2.4c-2.2.6-2.5.6-3.2 2.4s1.2 3.2 5.2 3.3c4 .2 9.3-1.4 11-2.4q2.4-1.4 2.7-.9.6.5-.7 1.2c-.8.7-3.5 2.6-4.9 3s-1.5 1.3-1.5 2.3-.4 2.8 3.2 3.6 7.8.2 11.6-2.5a9 9 0 0 0 2.4-2.2c-.3-3.7-.1-6.2.3-8.5a14 14 0 0 1-3.6-.9c-2.2-.8-3-2.8-3.5-4q-.8-1.3-2.1-.7-1.2.6-3 .6a8 8 0 0 1-5.6-3c-1.8-2.2-1.5-4.3-1.6-5.4 0-1-1.7-.3-2.6-.3s-3.3-.3-5.6-1.9-2.3-3.7-2.5-4.6-1.2-1.4-1.9-1.7-1.8-.6-3.4-1.8-2.1-3.8-2.2-4.7c0-.8-1-1-4.2-2.6s-3-5-2.8-6.4c.1-1.4 1.6-2.5 1.6-3s-1-.6-2.1-1.3c-1-.6-3.8-1.7-3.9-5.7 0-4 2.6-4.4 2.6-4.4l-1.6-.9c-1-.6-4-2.6-4-5s1.1-3.4 1.9-4 3-1.1 3-1.1-1.4-1-2-2.8-.8-4.1.6-5.8c1.3-1.6 3.6-1.6 4.8-1.6s2.3.7 2.7.3.5-.6.1-1.6-.5-3.2-.2-4c.4-.8.5-2.3 3.5-3.3s6.3 1 7.3 1.6.6-.8.6-1.8-2-3.5-4.4-7a78 78 0 0 1-10.8-25.1c-.4-1.8-1-1-1-1s-.8.3-2.4 4.5a27 27 0 0 0-1.8 11.2"/>
|
||||||
|
<path fill="#d4af3a" d="M209.6 257.5a8 8 0 0 1-6-3.8c-1.9-2.7-4.6-3.3-9.4-3.2s-7-2.2-8.2-5.5c-1.2-3.4.2-7.1 5-9.5 5-2.4 7.1-5.1 7.3-9.2.2-4.2-4.9-9.3-9.9-15.1s-9-14-9-14-1.5 1.9-1.5 3.8c0 2 .8 4 0 4.5-.7.6-1-.8-4-2s-5.3-1.5-7.1.6 0 5.8 1.7 7.5 3 3 2.5 3.7-3-1-3.7-1.6c-.9-.5-2.8-2.6-5.7-2.4-2.8.3-3.8 1.1-4 3.5-.4 2.4 2.5 4.6 4.3 5.5q2.9 1.4 2.1 2c-.4.5-.9 0-1.7-.2s-3.1-.5-4.6 0-3.9 2.6-1.7 5c2.2 2.6 6.8 3.5 7.8 3.7s1.3-.1 1.4.6c.1.8-1.9.8-4.4.9s-3.2 2.4-2.8 4.3c.3 1.8 4.5 3.8 6.8 4.2s3 0 3.3.7q.3.8-1.6 1.2c-1.3.4-3.8.7-4.2 2.7s-.6 3 1.4 4.7c1.9 1.8 5 1.5 6 1.3s2-1.2 2.9-.3c.9 1-.8 1.7-2.4 2.8s-1.5 3.2 1.2 4.7a8 8 0 0 0 6.4.5c1.1-.3 2.6-2 3.3-1 .8 1.1-2.3 1.5-3.6 3-1.2 1.6-.8 4.2 1.8 5.8s6.8.4 7.5-.2q1.3-1.2 2.2-.4c.6.6-.2 2-.8 3.3-.5 1.3-1.5 5 2.3 6.6 3.9 1.4 7.4-1.9 8-2.5q1-1 1.8-.5c.6.3.3.8-.1 1.5a4 4 0 0 0 1.3 5.3 7 7 0 0 0 5 1q1-3 2-7.6h-.5c-.4 0-.4.5-.4.7q0 .4-.3.4c-.3 0-.3.2-.8.6q-.8.6-1.2.5c-.2-.2 1-.8 1.3-1.2q.8-.7 1-1.5 0-.6 1-1.3l.5-.5q.7-3.2 1.3-7.7l.4-5.9zM131.1 200c4 8.3 9.9 13 9.9 13s-.8-2.1-1.1-3.5-.1-3-.1-3-1.5-2-4.1-6c-2.7-4.2-4.2-10.3-4.2-10.3s2.5 6.1 4.5 9.3a50 50 0 0 0 4.2 6s.8-3 1.3-3q.8-.2 1.5.9.8.7.9.5l-1.7-3q-.9-1-1.6-2.3c-1.6-2.4-4.9-7-6.6-11.6s-5.4-16.1-6-19.5-1.3-1.6-2 1a29 29 0 0 0-.9 11.3 57 57 0 0 0 6 20.2m5.3 22.4 3.8 2.7-.6-1.9-.3-3-3.1-2.7a44 44 0 0 1-6-6.4c-3-3.8-.6-1.5 2 1.3s7.4 6.8 7.4 6.8v-1q0-1.2.7-2c.5-.6.3-.6-2.5-2.7a80 80 0 0 1-18.7-26.1s-1.6 9.5 4.2 20 11.5 13.9 13 15zm30.4 60c3.2-1.2 2.3-4.3 2.3-4.4l-3.7 1.3c-1.8.7-6.2 2-8 2s-1.7-.2-1.8-.4c0-.3 1.4 0 3.9-.5s6.9-2.8 7.7-3.3q1.1-.7-.5-.7a5 5 0 0 1-2.5-1c-.4-.3-1.6.2-4.5.9a53 53 0 0 1-23 .8c-.6-.2-1.4.2.8 3.9s10 5.7 15.4 5.6c5.3 0 10.7-3 13.9-4.1zm-14.3-7.6c5.5 0 7.6-1.6 7.6-1.6s0-.3-.7-1.4c-.8-1.1-.6-3-.6-3l-3.7.5q-3.5.8-8 .2c-2.9-.6-1.6-.9.7-.6 2.4.3 7-.6 8.4-1 1.4-.6.8-1-.2-1.4s-3.6-2-3.6-2-6.8.8-13 .1-10.2-2.3-11.5-2.5-1.2.3-1.2.3-.4.6 1.8 4a18 18 0 0 0 10 6.6 54 54 0 0 0 14 1.8m-1.4-13-.6-1.4c-.3-1.2.6-2 .6-2s-1.3-.4-5.4-1c-4.1-.4-4.4-.8-7-1.3-2.4-.6-4.1-1.3-4.1-1.6q0-.3 2.4.7c1.4.5 7.8 1.2 10.5 1.6q4 .6 4.6.4c.5-.3 2.2-.7 2.2-.7l-2.6-.6a9 9 0 0 1-3.4-1.4c-.8-.5-1-.6-4.4-1s-6.1-1.5-12.2-3.7-10.6-4.8-11.8-5.8-1.2-.3-.8 1c.3 1.2.3 2.8 3.3 6.4s4.6 5.1 12.3 8c7.7 2.8 16.4 2.4 16.4 2.4M128.3 243a43 43 0 0 0 16.5 6.8c.3-.4-.4-2 0-3 .4-1.2 1.1-1.2 2.7-1.3 1.7 0 .6-.4.2-.4l-3.2-.8c-2.7-.7-5.5-1-9.2-2.5-3.8-1.4-5-2.5-4.8-2.7 0-.3 1.7.9 5.2 2.2s7.5 1.8 9.7 2.3.3-.5-.7-1.1-2.5-2.9-2.5-2.9-2.5-.4-6.5-1.6q-5.8-1.7-10.6-4.5l-3.5-2.3-5.5-4c-1.3-1-1-.2-.8.4s1.1 3.5 3.3 6.6 2.8 4 9.7 8.8m-1.2-13.5c6.8 5.3 12.5 7 13.5 7.4s1-.1.7-.7c-.2-.6-.3-2.4-.3-2.4l-5-3c-2.8-1.5-5.7-3.6-7.7-5s-.8-1.2-.2-.7 3.4 2 6 3.8l7 4.1s0-.9.6-1.4c.6-.6 2-.4 2-.4l-1.1-1.4c-.5-.7-2.7-1.4-5.2-2.6a62 62 0 0 1-10.2-7.2c-3.3-2.8-9.7-9.8-10.1-10.3q-.7-.9-.8.2c0 .7-.3 1.2.4 4.4s3.7 9.9 10.4 15.2m23.8-50.3 2.2 3.8 1.5 2.5V184a12 12 0 0 1 0-3.7q.3-1.3.2-2.4-.1-1.4-.4-2c-.2-1-1.2-4.9-1.7-7.7a62 62 0 0 1-.9-10.2c0-3.5.5-1.7.5 0s.2 4.3.8 8.3c.5 4 2.2 10 2.2 10s0-.9.6-2.2l2-4.5c1.1-2.4.4-2 .4-2s-2.4-11.3-3-16.6-1.6-19.4-1.6-19.4c-3.5 3.5-6 12.6-6.5 21.3-.3 6.5.7 12.5 1 15a38 38 0 0 0 2.7 11.3m-7.6 17.8q0-4 2.3-5s-1.4-2.6-2.7-6.3a49 49 0 0 1-2-8.2c-.9-6-1.6-12.3.2-2 1.8 10.2 4 13.8 4 13.8l2.6 4.3a18 18 0 0 1-.3-5.3c0-2 .3-4.8 1-7 0 0-3.2-6.3-5.2-13.9a87 87 0 0 1-3.4-24.1s-3.3 2.4-4.4 14.8a74 74 0 0 0 3.8 28.8l4 10zm35.4 94.5-2-1.3c-1.5-1-1-4-1-4l-1.6 1.1q-1.7 1.3-4 2.3L167 291q-1.4.4 0-.5c.9-.5 1.1-.8 3.2-1.6 2-.8 6.1-4.4 6.1-4.4l-2.6-.1c-1.7-.1-3.9-1-3.9-1s-1.7 1.6-8.5 4.5c-6.8 3-12.6 3.7-12.6 3.7s.7 3.5 4 4.7c2.7 1.1 6.5 1.9 13.8.1a47 47 0 0 0 12.3-4.8zm9 7c.6-2.9-1-4.9-1-4.9l-1.4 1.1c-1 1-2.6 2.7-5.4 4.1-2.9 1.5-4 1.4-1.2-.1s6.9-5.7 6.9-5.7l-1.1-.4c-1-.4-3 .3-10.6 4.9-7.7 4.6-12.2 4.8-12.2 4.8s3.4 4.1 12.7 3.8c9.4-.3 12.7-4.7 13.3-7.6m17.6-3.4s-1 1.5-2.4 2.4c-1.4 1-5 1.5-5 1.5l-1 1.3a15 15 0 0 1-3.9 4.3c-1.6 1.2-4.2 2-4.3 1.7-.2-.3 1.5-1 3-2 1.7-.9 3-2.7 4.2-4 1-1.2-.5-1-.5-1h-3.7c-1.8-.2-1.5 0-2 .8q-.6 1.1-2.6 3.2l-4.7 5.4c-1 1.3-.2 1.6-.2 1.6s.4.8 4.6 1.3c2.5.2 5.3 0 8.2-1.8a25 25 0 0 0 5.6-4.8c5-5.4 4.7-9.9 4.7-9.9m2.2 11.3-1.5 1.9s.7-2.2 1.3-3.3l1-1.4a28 28 0 0 1-1.7-5c-.5 1.5-1.9 5.8-3.7 8.6-2.1 3.5-4 4.7-2.8 5.6q2.2 1.7 6.4-.2a7 7 0 0 0 3.7-5.3l-1.3-2.3q-.5.3-1.3 1.4z"/>
|
||||||
|
<ellipse cx="206.9" cy="169.2" fill="#d4af3a" rx="1.9" ry="1.8"/>
|
||||||
|
<path fill="#d4af3a" d="M203 165.9c1.7 0 2.6-.9 4.5-.7a28 28 0 0 1 9.1 3.6 16 16 0 0 0 10.5.9q1.5-.6 3-1.5c4.6-2.5 3.5-7.6 3.5-7.6a18 18 0 0 1-11.4 3.5l1 .8c.6.7 1.8 1.5 1.4 1.8s-2-1.3-3.4-2.3a21 21 0 0 0-7-3.4c-3.4-1-7.4-1.1-10 .3-2.7 1.5-2.9 3.3-2.5 4q.4.7 1.2.6zm49.2 37c-.8-.2-2.2-.5-4-2.2a76 76 0 0 1-6.7-7.3q-.9-1.5 1-2.3c1.2-.5 2-2 2-2s-2.7-1.1-4.5-2.4-6-5.2-6.6-6.1c-.6-1-.3-1.1.5-1.4 1-.3 1.1-1 1.1-1s-2-1.1-3.8-2.6l-4.3-4c-2.7 1.2-7 .7-9.3.3-4.1-.7-7.1-2.4-7.1-2.4l-.7 1.9c-.5 1.3-2.4 2-4.3.7-2-1.2-1.5-4-1.5-4s-1.5-.9-4.7-2q-4.6-1.9-8.2-.2c-2.6 1.2-3.8 4.7-3.8 7.3 0 2.7 1.4 1.6 1.6 1.2s1-2.3 4.1-2.8c3.3-.5 6.2 1 9.8 4s4.5 3.3 6.3 5.2c1.9 1.9.4 3-2.7 4-3.2 1-7.2-1-7.4-1-.6.3 1.4 3.1 4.1 4.2 2.7 1 6.2.3 8.9-.7 1.8-.8 2.2 0 2.7.5 3 3.2 3.7 6 4.2 9.7a4 4 0 0 0 2.8-3.3c.1-.6.3-1.9 1.3-1 1 1 1.9 6.3 1.9 8.7 0 2.5-.8 5.7-.8 5.7l.8-.1q.7-.2 2.5-1.2c1.1-.7 1.2 0 1.2 1.4s.4 3.8 2.2 8.3c1.7 4.4 4.3 5.9 4.3 5.9s.5-.9 1-2.7c.5-1.7 1-5.2 1-8.2s-.9-9.4-.9-9.4 3.7 4.4 7.5 5.5 7.6.5 9.2-1.4.1-2.6-.7-2.8"/>
|
||||||
|
<path fill="#d4af3a" d="M227.2 214.5s-1.2 2.5-1.8 3.4c-.6 1-.9 1-.9 1s1.2 1 2.8 1.6l3.2.8s-1.9-2.6-2.5-4.4zm22.9-1.5-1-2.3a16 16 0 0 1-8.7-1.8s1.8 3.6 3.5 4.8c1.8 1.3 2 1.3 2 1.3s-1.7.3-4-2c-2.4-2.3-3.5-5-3.5-5s.6 6.2-.4 10l-1.1 4.2s5 1 10.5-.6 6-2 6-2 .6-.2-.3-1.4a30 30 0 0 1-3-5.2"/>
|
||||||
|
<path fill="#b96b29" d="m240 199.4-1.9-2.4s-.6.6 0 1.4 2.9 3 4.5 4a12 12 0 0 0 3.3 1.2v-.3s-4.2-2-5.9-3.9m-6.8 12.9-.4-9c0-1-.7-.8-.7-.8l-.8.4 1.6 11.3c.2 1.2.8 1.1 1.1 1-.6-.2-.7-2.6-.8-3zm-10.4-33.6c-.6-.6-2-1-.7.5s3.2 3.4 3.7 5 .8 3.5 0 3c-1-.3-1.6-.3-2.5-1.1-1-.8-2.8-2.1-3.5-2.3-.7-.3-2 0-1 1.1a26 26 0 0 0 4.2 3.7l4.2 2.9 2.1 1.4s-1-6.4-2.7-9.1a28 28 0 0 0-3.8-5zm-34.1 105.9c-1.3 1-3 1.4-3 1.4s-.8 0-.8.2 1 0 1.9-.1c1-.2 2.3-1.2 2.8-1.5s1.2-.8 1-1.5c-.3-.6-.7.5-2 1.5zm11 5.9-2.4 2-1.4.6q-1 .6-.9.7 0 .2 1 0c1-.2 2.9-1.8 3.6-2.5q1.1-.8.7-1-.3-.4-.6.2M155 211.1q.7 1 1 1 .4-.3-.5-1.1l-.8-1-1.2-1.6c-.4-.4-1.1-.7-.3.4zm-3.4 7.4 1.5 1.4q.8 1.1 1.2 1c.3-.4-.8-1-1.3-1.7l-1.5-1.5-1.4-.8zm1.3 12c.8 1 2 1.2 2.2.8q.3-.4-.8-1a24 24 0 0 1-4-2.7q-.6-.7-1-.6c0 .1 1.2 1.6 1.6 1.8zm2.4 9.7q.2-.6-.6-.6l-1.7-.2s-1.4-.5-1.5-.3c0 .3 1.2.8 1.7 1a3 3 0 0 1 1.5.4q.5.2.6-.3m1 10.4c-1.4 0-2.7-.3-2.8-.1s.7.6 1.4.8c.8.3 2.2.4 3.2.4s1.1-.1 1-.6c0-.5-1.2-.5-2.7-.5zm11.8 11.5c-.2-.3-.7 0-1.6 0-.8.3-2.2.5-3 .5l-2.5-.1-1 .2q.2.3 3.2.4c1.8.2 3-.2 4-.3q1.3-.3 1-.7zm4.9 8.1q-1.4.5-2.3.4l-2.4.3s-.8.2-.7.5q.2.3 1.1-.1h2.5q1.7-.1 2.7-.6c.8-.1 1.6-.5 1.2-1s-1.2.1-2.1.5m9.5 6.2-2.6 1.9-1.2.7c-.8.4-.7.6-.6.8s.6-.3 1-.5 2-1.2 2.7-1.8 1.7-1 1.6-1.5q-.2-.6-1 .4zm-15.8-79.8 1.8 2.5c1.6 2 .8 0 .4-.5s-2.8-3.9-3.7-4.8c-.8-1-.9-.5-.5.1zm-3.4 10.5q.3-.1-1.3-1.6l-2.8-3.6s-.5-1.1-.7-1 .7 1.9 1.4 2.6c.8.8 1.8 2.3 2.3 2.9q.7 1 1 .7zm4.5 19.7c0-.6-1.9-.5-2.7-.7l-2.3-.5s-1.8-.3-1.8 0q-.1.3 1.3.6l3.5.8c1.1.1 2 .4 2-.2m-4.5-10.7 2.6 1.9c.7.3 1.2.8 1.6.4q.4-.5-1.4-1.1l-1.5-1-1.2-.5c-.5-.3-1.4-.6-.1.3m10.5-7-1.4-1s-1-.8-1-.5c-.3.3 1 1.1 1.5 1.7s1.7 2 2.2 1.7-.5-1.2-1.3-1.9m-6.9 27.5-1.7.4c-.6.2-.7.5.5.5s1.7-.3 3-.5q1.6-.3 1.3-.8t-1.3-.1zm1.9 10c-.1.1.8 0 1.4-.3l2.7-1.1c.8-.2.8-.4.8-.8s-.8-.1-1.8.2-2.2 1.3-2.2 1.3zm12.8 6q1.2-.9.7-1.3-.3-.4-1.3.4l-2 1.5-1.3.6q-1.1.5-1 1c.2.2 1.3 0 1.9-.6.5-.4 2-1.1 3-1.7zm6 8c.3-.4 1.4-.8 1.9-1.1s.8-1.4.4-1.6c-.4-.1-.6.5-1.5 1.1l-3.7 2.5-.7.3q-1 .6.2.5c.7-.1 1-.1 1.9-.7q1.1-.9 1.5-1m9 7.5c.7-.3 1.8-1.3 2.3-1.8s1.2-1.1.8-1.4q-.6-.4-1.4.7c-.7.5-2.3 2.2-2.3 2.2l-1.1.8c-.9.7 0 .6.3.3q.4-.4 1.3-.8z"/>
|
||||||
|
<path fill="#d4af3a" d="m232.3 150.9-2.7 2.2 3.3 1.4 3.6-2.5z"/>
|
||||||
|
<ellipse cx="244.4" cy="151.6" fill="#b96b29" rx="3.4" ry="3.3"/>
|
||||||
|
<ellipse cx="244.4" cy="150.8" fill="#d4af3a" rx="2.1" ry="2"/>
|
||||||
|
<path fill="#d4af3a" d="M237.3 140.2c.9.3 2 .2 2 .2s.4-1.6-.5-2.3l-2-1.8-.7-.6v2.1c-.1.8.3 2 1.2 2.4"/>
|
||||||
|
<circle cx="239.1" cy="141.8" r=".6" fill="#d4af3a"/>
|
||||||
|
<path fill="#d4af3a" d="m234.4 141.2-.8 1s.7 0 1.2.4q1 .7 2 .7a1 1 0 0 0 .9-1.2q-.1-.8-1.6-1.3c-1.1-.4-1.4 0-1.7.4"/>
|
||||||
|
<path fill="#d4af3a" d="M236.8 145.2c-2.1.2-5-2.2-5-2.2q-.4 3-3.3 3.3h.2s1.7 1 3.7 1.2q1.3.2 2.7-.2a10 10 0 0 0 3.2-1c1.3-.8.9-2.9.9-2.9s-.2 1.6-2.4 1.8m-8.4-2.1c-.9-.4-2.2-1-2.8-1s-1.3-.2-1.2.8 1.2 1.5 2.2 1.6q1.4-.1 2.2-.7 1-.4-.4-.7m-6.7-3.7c-1-.7-1.9-.9-1.9-.9s-.6 1.7-.2 2.7 1 1.3 2 1.3 2.3-.5 1.9-1.2a5 5 0 0 0-1.8-2z"/>
|
||||||
|
<ellipse cx="222.5" cy="144.1" fill="#d4af3a" rx=".8" ry=".5"/>
|
||||||
|
<path fill="#d4af3a" d="M224.6 135.8c-.6-2.9-.5-6 2.2-9.9s7.4-5.5 7.4-5.5l1.6-1.9c1.5-1.6 4.5-3.6 4.5-3.6l-5.5 1.5v.1a2.4 2.4 0 0 1-2.4 2.5 2 2 0 0 1-1.4-.4 24 24 0 0 0-5.6 3.1q.4.6.4 1.3a2.4 2.4 0 0 1-2.5 2.5q-.6 0-1-.3l-.4.5-1.7 4a3 3 0 0 1 1 2.1c0 1.4-.9 2.5-2 2.5v1.6q-.2 1.2 1.7 1.7c1.4.4 3.3 2.8 3.3 2.8.1 0 1.1-1.8.4-4.6"/>
|
||||||
|
<path fill="#d4af3a" d="M233.2 117.6q.8-.7.2-1.4-.8-.5-1.7.1c-.9.6-.6 1-.2 1.4q.8.5 1.7-.1m-9 6.1q.8-.9.2-1.8-.9-.7-1.9.2-1 1-.2 1.7c.4.5 1.3.4 1.9 0z"/>
|
||||||
|
<circle cx="218.8" cy="131.4" r="1.4" fill="#d4af3a"/>
|
||||||
|
<path fill="#d4af3a" d="M248.9 112q-1.2.2-4.1 1.7c1.4 0 2.5.9 2.5 2s-1.3 2.4-2.6 2.4a2.4 2.4 0 0 1-2.4-2.3q0-.5.2-.8l-1.9 1.2c-4.4 3-5.5 4.5-7.7 8.5s-3 8.6-2 11.2c1 2.7 4.4 3.2 4.5 3.2q-.2 0-.5-.9l.3-1.7v-2.2s.8-.1 1.3.4l1.5 1.8 1.7 1.6s.7-1 .7-2.2-.8-2.5-1-4.6 1-5.8 3-8.6a24 24 0 0 1 7-5.7 2 2 0 0 0 1.4-2l.3-2c0-1.3-1-1.2-2.2-1"/>
|
||||||
|
<path fill="#d4af3a" d="M244.5 116.8q1.4-.2 1.5-1.4-.1-1-1.4-1.1-1.3.1-1.4 1.1 0 1.2 1.3 1.4m6.6 26c1 .4 2.2.2 2.8-.2q1-.8 0-1.9a2 2 0 0 0-2.2-.2l-2 1.4s.3.5 1.4.9"/>
|
||||||
|
<path fill="#d4af3a" d="M254.5 144c-.4.6-.7 1-1.9 1s-2.5 0-3.2-.8l-1.3-1.9h-1.4s-.4 1.9-1 2.4-2.7.6-3.6 0c-.9-.8-1.7-1.6-1.9-1.5q-.3.3 1 1.6c1.3 1.3 1.5 1.3 3 1.3h8.5s1.8.1 2.3-1q.7-1.5.2-1.9-.2-.2-.7.8"/>
|
||||||
|
<path fill="#d4af3a" d="M241.5 142.6c1.3.7 2 .2 2.5-.3l.5-.9s-1.2-.4-1.8-.8q-.8-.5-1.5-.6l-.7.2s-.2 1.6 1 2.4"/>
|
||||||
|
<circle cx="235.4" cy="130" r="2.8" fill="#b96b29"/>
|
||||||
|
<path fill="#b96b29" d="M240.3 120.2c-1.1-.7-2.8-.4-3.7.7s-.8 2.7.4 3.5c1.1.7 2.8.4 3.7-.7s.7-2.7-.4-3.5"/>
|
||||||
|
<circle cx="235.3" cy="129.6" r="1.6" fill="#d4af3a"/>
|
||||||
|
<path fill="#d4af3a" d="M239.1 120.5q-1.2-.2-2 .8-.6 1.2 1 1.6c1.8.3 2.6-2 1-2.4m-43.4 219.8s-4.3 2-5.7 2.4l-1.8.4s-3 2.7-5.1 4a19 19 0 0 1-7.6 2.7c-3.5.3-12.9.2-17.1 0-4.2 0-6.8 1-8.7 3s-2.2 5 0 7.2c2.4 2.3 5 1.1 5.8-.2.9-1.4.4-3.2 0-4-.3-.8.8-1 2-1.1a26 26 0 0 1 11.4 2.5c1.4.7.6.8-.4.6l-3.7-1.2a15 15 0 0 0-4.3-1c-1 0-.7.6-.4 1l1 1.7-.4 2 .5 1 .9-1.5c.2-.6 0-1.6.2-2q.4-1 1.8.2a7 7 0 0 1 1.8 3.8c.4 2-.3 3.5-.3 3.5s.9.7 1 1.2c.2.5-.2 2.4-.2 2.4s.6.4 1.2-.7l1.3-3.8c.2-.7.6-2 1.5-1a8 8 0 0 1 1.6 2.7l.2 1s1.9-1.8 2.5-2.9a18 18 0 0 1 3.8-4.2c1.8-1.5 5-4.5 6.5-5.5l6.8-4c1.2-.7 1.4-.7 1.6-2.8s1.3-4 1.8-5.4zm-70.1-78.7c0-.3-.8-1.6-1-2.2s-.8-.3-.8-.3-1.4.8-2.4 1.1q-1.3.3-1.3-.6l-.5-3.4c-.2-1.2-.4-1.1-.9-1l-2.6 1c-.9.5-.6.9-.3 1.4l1.8 3.4c.5.7 0 1-.5 1.3l-2.4.7c-1 .4-1 .6-.9 1l1.2 2.5c.4.6.8.2.8.2s1.1-1 2-1.5c1-.5 1.1-.3 1.4.4l1.5 5.2q.3.9 1.1.4l1.8-.5c.5-.2.7-1.3.7-1.3l-2.6-4.8c-.6-1 0-1.2.4-1.2l2.7-.5c1-.3.8-1 .8-1.3m23.8 103c-.2-.9 0-1.8-.7-2.5a9 9 0 0 1-2-3.2c-.2-1.1.4-1.7.3-2.3q-.3-1-1.9 0c-.8.7-2 1.4-2 3.4s.8 4.5 3.6 5.3c2.9.9 2.8.3 2.7-.7m2.8-1s-.5-.3-.8.2-.2.8.5 2 1.2 1.5 2.1 2 .3-.5.2-.9q0-.5-.3-1-.3-.2-.3-1.4c0-1.2-1.4-.8-1.4-.8z"/>
|
||||||
|
<path fill="#d4af3a" d="M155.7 373.3c.6-.8 1-1 2.3-1.6s3.7-.9 4.5-1 2.6-1.6 2.3-3.2q-.7-2.2-2.5-2.4-1.6-.5-2.2.2a4 4 0 0 1-2 1.5c-1.2.4-1.3.8-2 1s-.2.6-.8 1.1-1.4 0-2.3-.2-1-.5-2-.5l-3.6-.2h-.7q-.7-.2-1 .7c-.2.6.1 2.2.8 3 .6.8 2.7 1.2 4 1.6l3.3.8c1.3.2 1.4 0 2-.8zm13 1.3c1.1-.7.5-2 .2-2.8s-1.8-1-2.6-1-1.2 0-1.7.4q-.5.6-2.1 1.3c-1.2.3-2.1.8-3.7 1.4s-2.1 1.7-2.8 2.2c-.7.4-2 0-2 0s-1-.3-1.3 0q-.4.4.6 1.5c1 1.1 1.9.6 3.3 1s1.6.1 2.2-.2l1.9-1.8c1-.6 3-.7 4-.8 1.1-.1 3-.4 4-1.2m0-4.9c.5.5 1 1 1.5.3s1-2.1.6-3l-1-1.6-1.2 3.1c-.4.7-.3.8.1 1.2m-4.3-7.4c0-1.4-1-3-1-3s-.3 1.2-.9 2.3l-1.2 1.7 2 .4c.5 0 1.1 0 1.1-1.4m-8 3q2.1-.6 3-1.5c.5-.5 1-2 .5-2.7s-1.5-1.6-2-1.6h-.7s-.5 1.4-1 2.1-1.3 1.2-1.3 2.1 0 1.9 1.5 1.6m3-6.8q0-1.1-.7-2l-.8-.8-.2 1.7-.2.7.8.6c.4.1 1 .5 1-.2zM141 320.4l1-6.3c-.7-6.3-4-12.3-6.6-16.5a79 79 0 0 1-6.5-14 40 40 0 0 1-2-10.7c0-2.5 1-3.8 1-3.8s.5-.6 0-1c-.3-.5-1 0-1.7.6a8 8 0 0 0-1.6 2.8l-.3 1.2s-1.4-.3-3.5-.2q-3.2.2-3.3.8c0 .5.3.9 1.9 1.5s2.5 1 3.3 2c.8.8 2.8 5.5 3.4 7.7a109 109 0 0 1 2.4 13 52 52 0 0 0 4.4 15.3c.7 1.6 4.7 9.1 8.1 7.6m4 11.1c-1.5.8-1.5 6.8-.8 10 .6 3 3.7 6.9 4.6 8 1 1 1.1 1 1.6.7l.7-.3s1.3-.4 2.3-1.2.6-2.6.4-4.3-1.4-5-3.8-9-3.6-4.7-5-3.9m1.5-4.8c0-2-1.7-3.4-3.6-3.4q-1.5 0-2.2.6c-2 1.8-.8 6.3 2 6.3 1 0 2.3-.2 3-1 .7-.5.9-1.6.9-2.5zm28.3 56.2a13 13 0 0 1-5.7-4.4c-.3-.8-.3-.8-1.4-.5s-2.7.4-3.8.4c-1.2 0 .1 1.4 1 2.6.7 1.3.5 5.4.5 5.4l-.2 1q-.2.9.3 1.2.3.2 1.3-1.1c.8-1 2.3-3.5 2.3-3.5l1.6.3c1.3.3 3.3.2 4.4-.1 1-.4.5-1.1-.2-1.3z"/>
|
||||||
|
<path fill="#d4af3a" d="M169.8 386.1q-.7-.1-1 .3l-.7 1.4q-.1.6.3 1l.7.6s.5.5 1 .7.5-.5.5-1l.3-2q-.1-.9-1-1z"/>
|
||||||
|
<path fill="#1d5e91" d="M378.6 345.8c-1.4-.3-2-.9-2-.9s-.5 0-1 .8-2.1 2-2.1 2-2.4.6-2.8 2.2.9 2.3 2 2c1.2-.1 4-.7 5.2-2.2 1.2-1.4 1.4-2.3 1.7-2.8.2-.6.3-.8-1-1.1m3.8-13.2c-1-3.7-3.3-8.3-7.9-10.6l-2.2 8c-.5 2-.5 1.9 1 2.2l7 1.7c1 .3 2.6 1 2-1.4zm-22-3.5c1.2.2 1-.2 1.4-1.7l2.3-8.7s-4.8-.2-9 1.8-6.1 5.8-6.1 5.8 1 .5 4 1.1zm-11.2 10-2.3-.5s1 4.2 2.8 6.5 3.8 3.4 7.5 3.5c3.7 0 6.5-2 7-3.6l1.2-2.8s-6.7-.9-9.5-1.5q-4.1-1.1-6.7-1.6"/>
|
||||||
|
<path fill="#d4af3a" d="m348 336.5 16.5 3.3q1.9.5 2.5.2c.3-.4.5-.6 1.7-1s1.1-1.7 1.1-1.7l.5-2.3c0-1.2.7-1.5 2 0 1.4 1.3 1.7 3.4 2 5.3.2 2 3.3 2.5 4.5 2.7s1.8-.2 2.5-1.3a16 16 0 0 0 1.3-5c.2-1.4 0-1.3-1.5-1.7l-9.9-2.3c-1.2-.3-1-1.1-.8-2l3-9.9a21 21 0 0 0-8.1-2.4s-2 9.2-2.5 10.6-1.3 1.4-2.7 1.2l-12-2.8-1 2a17 17 0 0 0-1.1 5.6c0 1.2.4 1.2 2 1.5"/>
|
||||||
|
<path fill="#d4af3a" d="M384.3 344.8c-1.4-.9-2.4-1.2-3.2.6-.7 1.7-.8 4.2-2.6 6s-4.4 3.6-7 3.4-4-.8-3.4-3.7 4.5-3.9 5.5-5.4a3.4 3.4 0 0 0-.7-4.5c-1.4-1.2-3.3-1.4-3.9-1a8 8 0 0 0-2.9 3.9c-.8 2.2-1.3 4-3 5.4a13 13 0 0 1-9.5 2c-3.4-.8-4.7-1.8-6-5.2s-2.7-9-3.4-9.6-1.6-1-3 0c-1.5 1-1.6 2.6-1.8 3.8s0 2.6 1.5 4.4c1.4 1.8 3 3.5 3.5 5.4.6 1.8 2.2 4.6-.1 4.7s-3.4.2-5.1-1.5c-1.8-1.7-8.7-6.4-10.6-8l-2.7-2q-4.8-.1-9.6-3.5s1.9 4.7 2.2 6.5q.5 2.6.3 2.7l9.7 5a177 177 0 0 1 11 6c2.7 1.8 5 4.3 9.7 3.3 4.8-1 14.5-3.9 17.6-4.8 3.1-1 11.7-3 14.9-5.3s3.5-2 4-4.2q.8-3.1-1.4-4.3zm-18.4-33 2.4-.3c1.6-.2 1 1.4.7 2q-.3 1-1.4 2.9c-.7 1.1.2 1.3.7 1.5l2.4.6c1.3.3 1-.6 1.1-1.1 0-.5.1-3 .3-4 0-1 1-.7 1.3-.5l1.8 1q1 .3 1-.4l.8-2.2c.3-1-.3-.8-1-.8h-2.6c-1 0-.4-1-.4-1l1.4-2.3c.6-1 .4-1.1-.2-1.3l-3-.7q-.8-.2-.8.3v2.9q.2 1-1 1.1c-1 0-1.5-.5-2.3-1.3s-1-.5-1.1-.2l-.8 3.3q-.2.9.7.5m5.2 23.3s-.1 1.8-.3 2.4c-.2.7-.6 1.6.1 1.9q1.2.6 2 .4c.4 0 .2-1 0-1.7a7 7 0 0 0-1.3-2.4zm11.2 7.3q.5.8 1.1.9.8.3 1-.6a3 3 0 0 0-.5-2.4c-.6-.7-.9-1.3-1.2-.2q-.7 1.7-.4 2.3m-39.1-7.4 1.5.4s0-1.7.3-3c.2-1.2.7-2 .3-2q-.6 0-1 .9c-.4.5-1.3.6-1.6 2.1-.3 1.4.2 1.5.5 1.6"/>
|
||||||
|
<circle cx="350.8" cy="331.9" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="357.8" cy="333.6" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="378.3" cy="338" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="364.9" cy="335.3" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="366.7" cy="329.2" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="368.5" cy="323.1" r="2.5" fill="#b96b29"/>
|
||||||
|
<circle cx="351" cy="331.7" r="1.2" fill="#d4af3a"/>
|
||||||
|
<circle cx="358" cy="333.4" r="1.3" fill="#d4af3a"/>
|
||||||
|
<circle cx="378.5" cy="337.7" r="1.3" fill="#d4af3a"/>
|
||||||
|
<circle cx="365.1" cy="335.1" r="1.3" fill="#d4af3a"/>
|
||||||
|
<circle cx="366.9" cy="328.9" r="1.3" fill="#d4af3a"/>
|
||||||
|
<circle cx="368.7" cy="322.8" r="1.3" fill="#d4af3a"/>
|
||||||
|
<path fill="#d4af3a" d="M287.5 383c-1.4-.8-2.5-1.7-5.6-3.8s-4.1-4.2-4.1-4.2h1q1-.2 1.2-1.7l-.3-4.4c-.2-2 .3-3.5.3-3.5s1.3 1.2 3.3 2.3 2.8.5 2.8.5l.5-2 2 4c1.3 2 4.5 4.4 6.9 6.2 2.3 1.7 4.9 3.6 6.8 4.2 2 .7.8 1.6.8 1.6s-2 2-6.9 2.8c-5 1-7.4-1-8.7-2"/>
|
||||||
|
<path fill="#d4af3a" d="M270 381.1c1.8-.2 3-.8 3.3-2.8.2-1.2-.1-6-.1-6l1.2 1.2c.7.9 2.2 1 2.2 1s.2.7 1 1.8a10 10 0 0 0 3.2 3.5l5 3.4a30 30 0 0 0 4.5 3.3 8 8 0 0 1 3.8 5q1 3.6-1.9 3.2c-2-.2-2.1 0-5.3-.8s-7.8-4.4-11-6.5c-3.4-2-5.8-6.3-5.8-6.3z"/>
|
||||||
|
<path fill="#d4af3a" d="M261.2 384s1.4-1.7 1.9-3.6l1.4-5.8 1.5 3.5c1.1 2.3 3 2.9 3 2.9s0 1 1.6 2.9a23 23 0 0 0 6 5c2.3 1.4 5.3 3.3 6.9 5.6s1.8 4.7 1 5.7-1.8-.2-3.4-1c-1.5-.7-5.9-1.4-11.9-4.6s-8-10.6-8-10.6"/>
|
||||||
|
<path fill="#d4af3a" d="M258.6 397.5c-1.6-2.5-1.9-8.5-1.9-8.5l1.4-1c.8-.8 2.4-3.1 2.4-3.1l2 4c1.1 1.9 1.6 2.6 3.9 4.6 2.2 2.1 5.3 4.1 6.6 6.3s.6 6.7-.3 8.1c-.8 1.5-2 1-3.3-.2l-3.8-3.4c-2.3-1.9-5.3-4.3-7-6.8"/>
|
||||||
|
<path fill="#b96b29" d="M278.4 347c-1-1.8-1.9-4-2.7-5l-1.4-2.5s2.2 2 3 3.4a15 15 0 0 1 1.8 4.2q.5 2.4-.7 0zm-8.5 1.6-.6-2s-.3-1.2-.1-1.3q.5 0 1.7 2.7c.6 2 1.5 4.8 1.3 5-.2 0-2-3.2-2.3-4.4M263 354l-.4-1s-.6-2.1-.6-2.6.2-1 .7.2q.8 1.8.8 2.7c.1.5.8 2.2.6 2.4s-.7-.9-1-1.7zm20.9 20.4.9-.5 1.3 1.5q1.3 1 2.2 2a9 9 0 0 0 2.9 2.4q2 1 1.6 1.1c-.2.3-1.2-.3-1.2-.3s-.4 0-2-1-2.1-1.6-3.2-2.5zm-7.3 8.7c-.6-.7-1.3-1.2-1-1.5q.6-.6 1.5.1l3 2.8 2.1 1.7c.8.6 2.4 1.5 3.2 1.9.9.5.1.5.1.5l-1.3-.5-2.6-1.1-1.8-1.1zm-8.4 5.8c-.5-1.1 0-.8 1-.4.9.3 2.9 2.5 4 3.3l3.2 1.7c1.4.7 1 .7 1 1q-.2.3-1 0l-1.7-1-1.1-.2q-.8-.1-2.6-1.7-1.8-1.3-2.8-2.7m-3.9 7.7c1.5.9 4 2.9 4.7 4.2q1.2 2.1 1 2.6c-.3.2-1-.3-1.3-.8l-1-1.4s-2.6-2.5-3.5-3a9 9 0 0 1-2.8-3.3c-.4-1-.1-1.5-.1-1.5s1.6 2.3 3 3.2"/>
|
||||||
|
<path fill="#c52126" d="M304 181.3a24 24 0 0 1 19-1.6q3.3 1 6 .2a8 8 0 0 0 5.6-6.3s2.6 3.3 0 6.8a8 8 0 0 1-4.6 3.2c-5.4 1-10.8-2.3-15.3-2.6-4.3-.2-7.7 2.5-7.7 2.5s-1 .9-2.5 0q-2.4-1.2-.5-2.3z"/>
|
||||||
|
<path fill="#d4af3a" d="m319.6 338.7 1.3-.2c.8-.1 2.4-.6 2.8-.9q1-.4 1.7.3c.5.5 4.4 2.9 4.4 2.9h-6.4c-2.3 0-3.8-2.2-3.8-2.2z"/>
|
||||||
|
<path fill="#d4af3a" d="M301.4 337.6v-.7l.9-1.8s1 .6 2 1.6c1.2 1.3 2.6 3.2 3.2 4 1 1.3 2.6 2.6 1.3.4s-1.7-3.2-2.8-4.6l-1.7-2.2 3.7-.6c2.7-.3 4.4-1.4 5.6-2.1l3.5-2s4.6 2 6.4 2.4c1.7.5 2.7.7 1 1.5-1.6.9-3 1.1-3.9 1.6a11 11 0 0 1-4.9 1.4c-1.5 0-4.2 0-3.1 1q1.6 1.8 3.3 6.4a22 22 0 0 1 1.2 7.4l-.2 2.7s-2-2.1-7-5.3-7-4.3-8-4.7-1.4-.4-1.2-1.6z"/>
|
||||||
|
<path fill="#d4af3a" d="m282 322.1 1-.4h.2c.6.4 3.7 2.4 5 4.5 1.2 2 2.4 2.2 1 0a19 19 0 0 0-3.4-4.4l-1.1-1q3.8-1.7 7.8-4.5c.4.7 1 2 1.8 2.7 1 1.1 3.4 2.1 3.4 2.1s-1.9-2-2.5-3.5q-.7-1.6-.9-2.6a34 34 0 0 0 5.2-4.9 19 19 0 0 0 4.4 7.9c4 4.5 12 6.7 12 6.7s2.8.3.5 1c-2.3.5-3.9 1.5-8.3.8-4.4-.8-4.7-1-5.3-1.2s-2.2 0 0 .8 7.3 1.6 9.3 1.4c2-.1 4.7-.6 1.8 1-2.8 1.4-4 2-5.7 2s-2.4.6-4.1.6-2.1-.3-3.3-.6q-1.9-.2-2 .7c-.1.9 1.6 1.8 1.5 5a28 28 0 0 1-.6 6.4c-.3 1-1 2.5-1.5 3.4-.4 1-.6 1.8-1 0-.6-2-1.6-4.7-2-6.6-.5-1.8-.7-1.9-.9-2.9s-1.2-1.6-.7.1l1.4 5.5c.3 1.5 1.2 4.4-.3 2.1-1.4-2.2-6.8-8.3-7.7-9.3q-1.3-1.6-2.2-3c-.7-.9-2.5-2-3.4-3.4a30 30 0 0 0-4.2-4.3zm74.2-137.7c-.2 3.3-3.8 9.6-5.9 13-2 3.3-2.6 4.6-2.3 4.8s1.2-1.5 2.4-3 3.3-5 5.2-7.6c1.8-2.6 4.5-9 5-9.5.3-.6.4-1 1.3-.2 1 .7 2.8 7.6.6 12.3s-5.3 8.2-6.7 9.8-3.1 4.2-2.9 4.5.8-.5 2.5-2.2l6.1-6.8c1.4-1.7 3.4-5.2 4.1-6.5q.5-.7.8-.1c.7.6 1.6 2 1.2 5.7-.6 5.2-3.3 8.2-5.7 10.9-2.5 2.6-4.9 4.8-4.5 5.2.3.3 2.8-1.9 4.5-3.5s6-5.7 6.9-6.9c1-1.1 2.3-.6 2.2 4 0 4.4-5 9-7.1 11.5s-4.7 4-4.5 4.5 2.3-.8 3.7-1.7c1.3-1 6.4-4.2 7.4-5 1-.6 1.5.7.2 5s-5 7.7-7.2 9.3-5.8 3.5-5.6 4q.5.6 3.8-1.1c2-1 4.6-2 6.4-2.5s1.8.8 1.8.8.3 3.7-2.3 6.6a14 14 0 0 1-8.5 4.6c-2.9.5-3.3.6-3.2 1q0 .6 1 .5c.6 0 3.3.3 5.7.3s3.7.6 3.7 1.7c0 1 0 2.5-3.6 4.9s-7.7 2.8-9.4 3.1-1.8.5-1.4 1c.3.5 1.1.5 3.8.8 2.7.2 4.4 1.3 4.7 2 .4.8-.2 2.9-2.4 4.4s-5 2-9.3 1.7-5.4-.3-5.5.4c-.2.7 1.3.9 3.4 1.3 2.1.3 3.8.3 5 1.3s.3 3-1.8 4.6c-2 1.5-7.2 1.5-9.3 1.3s-5.5-.9-5.7-.3 1.6 1.4 3.8 2.2 2.8 1.3 3.3 2.2 0 1.8-2.6 2.8-6.5 1-8.5 0q-2.9-1.3-3.4-.8t.4 1.2q1.1 1.4 4.4 2.4c2.2.6 2.4.6 3.2 2.4s-1.2 3.2-5.2 3.3a25 25 0 0 1-11-2.4c-1.8-1-2.4-1.2-2.7-.9q-.5.5.6 1.2c.9.7 3.6 2.6 5 3s1.4 1.3 1.4 2.3.4 2.8-3.2 3.6-7.8.2-11.6-2.5a9 9 0 0 1-2.4-2.2c.3-3.7.2-6.2-.3-8.5q1.3 0 3.7-.9c2.1-.8 3-2.8 3.5-4q.8-1.3 2-.7 1.4.6 3 .6a8 8 0 0 0 5.6-3c1.9-2.2 1.5-4.3 1.6-5.4 0-1 1.8-.3 2.6-.3s3.4-.3 5.7-1.9 2.3-3.7 2.5-4.6 1.1-1.4 1.8-1.7 1.9-.6 3.4-1.8c1.6-1.3 2.2-3.8 2.3-4.7 0-.8 1-1 4.2-2.6s2.9-5 2.8-6.4-1.7-2.5-1.7-3 1-.6 2.2-1.3c1-.6 3.7-1.7 3.8-5.7s-2.6-4.4-2.6-4.4l1.6-.9c1.1-.6 4-2.6 4-5s-1-3.4-1.8-4-3-1.1-3-1.1 1.4-1 2-2.8c.5-1.8.7-4.1-.6-5.8-1.4-1.6-3.6-1.6-4.9-1.6s-2.2.7-2.7.3-.5-.6 0-1.6.5-3.2 0-4c-.3-.8-.3-2.3-3.4-3.3-3-1-6.3 1-7.3 1.6s-.6-.8-.6-1.8 2.1-3.5 4.5-7a79 79 0 0 0 10.7-25.1c.4-1.8 1.1-1 1.1-1s.7.3 2.4 4.5a27 27 0 0 1 1.8 11.2"/>
|
||||||
|
<path fill="#d4af3a" d="M302.3 257.5a8 8 0 0 0 6-3.8c2-2.7 4.7-3.3 9.5-3.2s7-2.2 8.1-5.5c1.2-3.4-.1-7.1-5-9.5s-7.1-5.1-7.3-9.2c-.2-4.2 4.9-9.3 10-15.1s9-14 9-14 1.4 1.9 1.4 3.8c0 2-.7 4 0 4.5.8.6 1-.8 4-2 3-1.4 5.3-1.5 7.2.6s0 5.8-1.7 7.5-3 3-2.6 3.7c.5.7 3-1 3.8-1.6.8-.5 2.8-2.6 5.6-2.4 2.9.3 3.9 1.1 4.1 3.5.3 2.4-2.5 4.6-4.4 5.5q-2.7 1.4-2 2c.4.5.8 0 1.7-.2s3-.5 4.6 0c1.4.6 3.8 2.6 1.6 5-2.2 2.6-6.8 3.5-7.7 3.7s-1.4-.1-1.5.6c0 .8 2 .8 4.4.9s3.2 2.4 2.9 4.3-4.5 3.8-6.8 4.2-3.1 0-3.3.7q-.4.8 1.5 1.2c1.9.4 3.8.7 4.3 2.7s.5 3-1.4 4.7c-2 1.8-5 1.5-6 1.3s-2-1.2-3-.3c-.9 1 .9 1.7 2.5 2.8 1.5 1 1.4 3.2-1.2 4.7a8 8 0 0 1-6.5.5c-1-.3-2.6-2-3.3-1-.7 1.1 2.3 1.5 3.6 3 1.3 1.6.8 4.2-1.8 5.8-2.5 1.5-6.8.4-7.5-.2q-1.2-1.2-2.2-.4c-.5.6.3 2 .8 3.3s1.6 5-2.3 6.6c-3.8 1.4-7.3-1.9-8-2.5q-1-1-1.8-.5-.7.5.2 1.5a4 4 0 0 1-1.3 5.3 7 7 0 0 1-5 1q-1-3-2.1-7.6h.5c.4 0 .5.5.5.7q-.1.4.2.4t.8.6q.8.6 1.2.5c.3-.2-1-.8-1.3-1.2s-1-1-1-1.5-.3-.7-1-1.3l-.4-.5-1.3-7.7-.5-5.9zM381 200c-4.1 8.3-10 13-10 13l1.2-3.5c.3-1.4 0-3 0-3l4.2-6c2.7-4.2 4.1-10.3 4.1-10.3s-2.5 6.1-4.4 9.3-4.2 6-4.2 6-.8-3-1.4-3q-.8-.2-1.4.9-.9.7-1 .5l1.8-3 1.6-2.3c1.5-2.4 4.8-7 6.5-11.6s5.4-16.1 6-19.5c.7-3.4 1.3-1.6 2 1a31 31 0 0 1 1 11.3 57 57 0 0 1-6 20.2m-5.3 22.4-3.8 2.7.6-1.9.3-3 3-2.7c1.3-1.1 2.9-2.5 6-6.4 3.1-3.8.6-1.5-2 1.3a97 97 0 0 1-7.3 6.8v-1a3 3 0 0 0-.8-2c-.5-.6-.2-.6 2.5-2.7a73 73 0 0 0 18.7-26.1s1.6 9.5-4.2 20-11.5 13.9-13 15m-30.5 60c-3.1-1.2-2.3-4.3-2.3-4.4l3.8 1.3c1.8.7 6.2 2 8 2s1.7-.2 1.7-.4-1.3 0-3.8-.5-7-2.8-7.7-3.3q-1.2-.7.4-.7 1.9-.3 2.5-1c.5-.3 1.6.2 4.5.9a53 53 0 0 0 23 .8c.6-.2 1.4.2-.8 3.9s-10 5.7-15.3 5.6c-5.4 0-10.8-3-14-4.1zm14.3-7.6c-5.5 0-7.6-1.6-7.6-1.6s0-.3.8-1.4.6-3 .6-3l3.6.5q3.5.8 8 .2c4.5-.6 1.7-.9-.7-.6s-6.9-.6-8.3-1c-1.5-.6-.9-1 .1-1.4s3.7-2 3.7-2 6.7.8 13 .1 10.2-2.3 11.5-2.5c1.2-.2 1.2.3 1.2.3s.3.6-1.8 4a18 18 0 0 1-10 6.6 54 54 0 0 1-14 1.8zm1.5-13 .5-1.4c.4-1.2-.6-2-.6-2s1.3-.4 5.4-1c4.2-.4 4.4-.8 7-1.3s4.2-1.3 4.1-1.6q0-.3-2.3.7c-1.5.5-7.9 1.2-10.6 1.6q-3.9.6-4.6.4c-.5-.3-2.1-.7-2.1-.7l2.5-.6a9 9 0 0 0 3.4-1.4c.8-.5 1-.6 4.4-1s6.1-1.5 12.2-3.7 10.6-4.8 11.9-5.8c1.2-1 1.1-.3.8 1-.4 1.2-.3 2.8-3.4 6.4-3 3.6-4.5 5.1-12.3 8s-16.3 2.4-16.3 2.4m22.8-18.8a43 43 0 0 1-16.5 6.8c-.4-.4.4-2 0-3-.4-1.2-1.1-1.2-2.8-1.3-1.6 0-.6-.4-.2-.4s.5 0 3.2-.8c2.7-.7 5.5-1 9.3-2.5 3.7-1.4 4.9-2.5 4.8-2.7s-1.8.9-5.2 2.2-7.6 1.8-9.8 2.3-.3-.5.8-1.1c1-.6 2.5-2.9 2.5-2.9s2.4-.4 6.4-1.6q5.8-1.7 10.7-4.5 1.7-1 3.4-2.3l5.5-4c1.4-1 1-.2.8.4s-1 3.5-3.2 6.6-2.9 4-9.7 8.8m1.1-13.5c-6.7 5.3-12.5 7-13.5 7.4s-.9-.1-.7-.7.3-2.4.3-2.4l5.1-3c2.7-1.5 5.7-3.6 7.6-5 2-1.3.8-1.2.2-.7s-3.3 2-6 3.8l-7 4.1s0-.9-.6-1.4c-.5-.6-2-.4-2-.4l1.2-1.4c.4-.7 2.6-1.4 5.1-2.6a62 62 0 0 0 10.3-7.2c3.3-2.8 9.7-9.8 10-10.3q.8-.9.8.2c0 1.1.3 1.2-.4 4.4s-3.7 9.9-10.4 15.2M361 179.3l-2.2 3.8-1.4 2.5q-.2 0 0-1.5v-3.7l-.3-2.4q.1-1.4.5-2l1.6-7.7c.5-2.7 1-6.7 1-10.2s-.6-1.7-.6 0-.2 4.3-.7 8.3-2.2 10-2.2 10-.1-.9-.7-2.2q-.8-2.1-2-4.5c-1.2-2.4-.4-2-.4-2s2.4-11.3 3-16.6 1.6-19.4 1.6-19.4c3.5 3.5 6 12.6 6.5 21.3.4 6.5-.7 12.5-1 15q-.8 7.2-2.7 11.3m7.7 17.8q0-4-2.4-5s1.5-2.6 2.7-6.3a35 35 0 0 0 2-8.2c1-6 1.6-12.3-.2-2a44 44 0 0 1-4 13.8l-2.6 4.3c.4-2 .5-3.2.4-5.3a34 34 0 0 0-1-7s3.2-6.3 5.2-13.9a88 88 0 0 0 3.4-24.1s3.2 2.4 4.3 14.8a74 74 0 0 1-3.8 28.8c-1.6 4.5-4 10-4 10zm-35.5 94.5 2-1.3c1.6-1 1.1-4 1.1-4l5.5 3.4 3.3 1.3c1 .3.7 0-.1-.5s-1-.8-3.1-1.6c-2-.8-6.2-4.4-6.2-4.4l2.7-.1c1.7-.1 3.8-1 3.8-1s1.7 1.6 8.5 4.5c6.9 3 12.7 3.7 12.7 3.7s-.8 3.5-4 4.7c-2.8 1.1-6.5 1.9-13.8.1a47 47 0 0 1-12.4-4.8m-9 7c-.5-2.9 1.1-4.9 1.1-4.9l1.4 1.1c1 1 2.5 2.7 5.4 4.1 2.8 1.5 3.9 1.4 1.1-.1s-6.8-5.7-6.8-5.7l1-.4c1.1-.4 3 .3 10.7 4.9a29 29 0 0 0 12.2 4.8s-3.5 4.1-12.8 3.8-12.7-4.7-13.3-7.6m-17.6-3.4s1 1.5 2.4 2.4c1.4 1 5 1.5 5 1.5l1 1.3c.7 1 2.3 3.2 3.9 4.3 1.6 1.2 4.2 2 4.4 1.7.1-.3-1.5-1-3.1-2s-3-2.7-4.1-4 .4-1 .4-1h3.8c1.8-.2 1.4 0 2 .8q.6 1.1 2.5 3.2l4.7 5.4c1 1.3.3 1.6.3 1.6s-.5.8-4.7 1.3c-2.5.2-5.3 0-8.2-1.8a25 25 0 0 1-5.6-4.8c-5-5.4-4.7-9.9-4.7-9.9m-2.2 11.3 1.6 1.9s-.7-2.2-1.4-3.3l-.9-1.4a28 28 0 0 0 1.7-5c.4 1.5 1.8 5.8 3.6 8.6 2.2 3.5 4 4.7 2.8 5.6-1.2 1-3.6 1.2-6.3-.2s-3.4-3.5-3.8-5.3l1.3-2.3q.5.3 1.4 1.4"/>
|
||||||
|
<ellipse cx="-305" cy="169.2" fill="#d4af3a" rx="1.9" ry="1.8" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="M309 165.9c-1.7 0-2.7-.9-4.6-.7a28 28 0 0 0-9 3.6 16 16 0 0 1-10.5.9q-1.5-.6-3-1.5c-4.6-2.5-3.5-7.6-3.5-7.6a18 18 0 0 0 11.3 3.5l-1 .8c-.6.7-1.8 1.5-1.4 1.8s2-1.3 3.4-2.3a21 21 0 0 1 7-3.4c3.5-1 7.4-1.1 10 .3 2.7 1.5 3 3.3 2.6 4q-.3.7-1.3.6m-49.3 37c.8-.2 2.2-.5 4-2.2a78 78 0 0 0 6.8-7.3q.9-1.5-1-2.3c-1.2-.5-2.1-2-2.1-2s2.7-1.1 4.5-2.4c1.9-1.4 6.1-5.2 6.7-6.1.6-1 .3-1.1-.6-1.4s-1-1-1-1 1.9-1.1 3.7-2.6l4.3-4c2.8 1.2 7 .7 9.4.3 4.1-.7 7.1-2.4 7.1-2.4l.7 1.9c.5 1.3 2.4 2 4.3.7 1.9-1.2 1.4-4 1.4-4s1.6-.9 4.7-2q4.6-1.9 8.3-.2c2.5 1.2 3.8 4.7 3.8 7.3 0 2.7-1.4 1.6-1.7 1.2s-.9-2.3-4.1-2.8-6.1 1-9.8 4-4.5 3.3-6.3 5.2-.4 3 2.8 4 7.2-1 7.3-1c.6.3-1.4 3.1-4 4.2-2.8 1-6.3.3-9-.7-1.8-.8-2.1 0-2.6.5-3 3.2-3.8 6-4.3 9.7a4 4 0 0 1-2.8-3.3c0-.6-.3-1.9-1.3-1s-1.8 6.3-1.8 8.7c0 2.5.7 5.7.7 5.7l-.7-.1q-.7-.2-2.5-1.2c-1.2-.7-1.3 0-1.3 1.4s-.4 3.8-2.2 8.3c-1.7 4.4-4.3 5.9-4.3 5.9s-.5-.9-1-2.7c-.5-1.7-.9-5.2-.9-8.2s.8-9.4.8-9.4-3.6 4.4-7.4 5.5-7.6.5-9.2-1.4-.2-2.6.6-2.8"/>
|
||||||
|
<path fill="#d4af3a" d="M284.8 214.5s1.1 2.5 1.7 3.4c.7 1 1 1 1 1s-1.2 1-2.9 1.6l-3.2.8s2-2.6 2.6-4.4zm-22.9-1.5.9-2.3a16 16 0 0 0 8.7-1.8s-1.8 3.6-3.5 4.8c-1.8 1.3-2 1.3-2 1.3s1.7.3 4.1-2 3.4-5 3.4-5-.5 6.2.4 10l1.2 4.2s-5 1-10.5-.6-6-2-6-2-.7-.2.2-1.4a30 30 0 0 0 3.1-5.2"/>
|
||||||
|
<path fill="#b96b29" d="m272 199.4 1.8-2.4s.6.6.1 1.4c-.5.9-3 3-4.6 4a13 13 0 0 1-3.3 1.2v-.3s4.2-2 6-3.9m6.7 12.9.4-9c0-1 .7-.8.7-.8l.9.4-1.7 11.3c-.1 1.2-.7 1.1-1 1 .6-.2.7-2.6.7-3zm10.4-33.6c.7-.6 2.1-1 .7.5-1.3 1.5-3.1 3.4-3.7 5-.5 1.6-.8 3.5 0 3 1-.3 1.6-.3 2.6-1.1s2.7-2.1 3.5-2.3c.7-.3 2 0 1 1.1a26 26 0 0 1-4.3 3.7l-4.2 2.9-2 1.4s.9-6.4 2.6-9.1a28 28 0 0 1 3.8-5zm34.2 105.9c1.3 1 3 1.4 3 1.4s.7 0 .7.2-.9 0-1.9-.2c-1-.1-2.3-1.1-2.7-1.5s-1.2-.8-1-1.4.6.5 1.9 1.5m-11 5.9 2.4 1.9 1.3.7 1 .7h-1c-.8-.1-3-1.8-3.6-2.5q-1.1-.8-.8-1 .4-.4.7.2M357 211q-.8 1-1.1 1-.4-.3.5-1.2l.9-.9s.7-1.2 1.1-1.6 1.1-.7.3.3l-1.8 2.4zm3.3 7.4L359 220q-.8 1.1-1.2 1c-.3-.4.8-1 1.2-1.7l1.5-1.5 1.4-.9-1.5 1.7zm-1.2 12c-.9 1-2.1 1.2-2.3.8q-.3-.4.8-1a24 24 0 0 0 4-2.7q.6-.7 1-.6c.1.1-1.2 1.5-1.6 1.8l-2 1.7zm-2.5 9.7q-.2-.6.7-.6l1.6-.2s1.4-.5 1.5-.3-1.1.8-1.7 1q-.9 0-1.5.4-.5.2-.6-.3m-1 10.4c1.5 0 2.8-.4 2.9-.1q-.1.3-1.5.8c-.8.3-2.2.4-3.1.4q-1.4 0-1.1-.7c0-.4 1.3-.4 2.8-.4M343.8 262q.4-.2 1.6.2c.9.2 2.2.4 3 .4l2.5-.1 1 .2q-.2.2-3.1.4c-1.9.2-3.1-.2-4-.3q-1.3-.3-1-.8m-4.9 8.2 2.4.4 2.3.3s.8.1.7.4-.4 0-1 0h-2.6l-2.7-.6c-.7-.1-1.6-.5-1.2-1s1.2.1 2.1.5m-9.4 6.1 3.7 2.7q1 .5.7.8c-.2.2-.7-.3-1.1-.5l-2.7-1.8c-.7-.6-1.7-1.1-1.5-1.6q.1-.4.9.5zm15.7-79.7-1.8 2.4c-1.6 2-.7.2-.3-.4l3.6-4.8c.8-1 1-.5.5.1zm3.5 10.5c-.3-.3.3-.5 1.2-1.7l2.8-3.5s.5-1.1.7-1-.6 1.9-1.4 2.6-1.7 2.2-2.3 2.9q-.7 1-1 .7m-4.6 19.7c0-.6 1.9-.5 2.7-.7l2.4-.5s1.7-.3 1.8 0q0 .3-1.4.5-1.7.5-3.5.8c-1 .2-2 .4-2-.1m4.6-10.7q-1.8 1.4-2.7 1.8c-.7.4-1.2 1-1.5.5q-.4-.5 1.3-1.2l1.6-.8 1.1-.6c.5-.3 1.4-.7.2.3m-10.6-7 1.4-1s1-.8 1.1-.5-1 1.1-1.6 1.6-1.6 2-2.2 1.8c-.5-.3.6-1.2 1.3-1.9m7 27.5 1.6.4q1.2.4-.5.5c-1.7.1-1.7-.3-2.9-.5q-1.7-.3-1.4-.8t1.3-.1zm-2 9.9c.1.2-.8 0-1.4-.2s-1.9-1-2.7-1.1q-.9-.3-.7-.8c0-.5.8-.1 1.8.2s2.2 1.3 2.2 1.3.8.4.9.6zm-12.7 6q-1.3-.8-.8-1.2.4-.5 1.3.4l2 1.5 1.3.6c.5.3 1.2.7 1 1-.1.2-1.2 0-1.8-.6zm-6 8-2-1c-.4-.4-.7-1.4-.3-1.6.3-.1.6.5 1.4 1 .8.7 3.7 2.6 3.7 2.6l.8.3c.8.3.4.6-.2.5q-1 0-2-.7zm-9 7.6c-.7-.3-1.8-1.3-2.4-1.8s-1.2-1.1-.8-1.4q.6-.4 1.5.6l2.2 2.3s.3 0 1.1.8c1 .7 0 .6-.3.3s-.5-.5-1.3-.8"/>
|
||||||
|
<path fill="#d4af3a" d="m279 154.5-3.6-2.5 4.3-1.1 2.7 2.2z"/>
|
||||||
|
<ellipse cx="-267.5" cy="151.6" fill="#b96b29" rx="3.4" ry="3.3" transform="scale(-1 1)"/>
|
||||||
|
<ellipse cx="-267.5" cy="150.8" fill="#d4af3a" rx="2.1" ry="2" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="M274.7 140.2c-1 .3-2 .2-2 .2s-.4-1.6.4-2.3l2.1-1.8.6-.6.1 2.1c0 .8-.3 2-1.2 2.4"/>
|
||||||
|
<circle cx="-272.8" cy="141.8" r=".6" fill="#d4af3a" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="m277.5 141.2.9 1s-.7 0-1.3.4q-1 .7-2 .7a1 1 0 0 1-.8-1.2q0-.8 1.6-1.3c1-.4 1.4 0 1.6.4"/>
|
||||||
|
<path fill="#d4af3a" d="M275.1 145.2c2.2.2 5-2.2 5-2.2q.5 3 3.3 3.3h-.2s-1.7 1-3.7 1.2q-1.3.2-2.6-.2a11 11 0 0 1-3.2-1c-1.3-.8-1-2.9-1-2.9s.3 1.6 2.4 1.8m8.4-2.1q1.7-.8 2.8-1c.5 0 1.3-.2 1.2.8s-1.2 1.5-2.2 1.6q-1.4-.1-2.2-.7-1-.4.4-.7m6.8-3.7q1.7-1 1.8-.9c.1.1.6 1.7.3 2.7s-1.1 1.3-2 1.3c-1 0-2.3-.5-2-1.2q.6-1 1.9-2z"/>
|
||||||
|
<ellipse cx="-289.4" cy="144.1" fill="#d4af3a" rx=".8" ry=".5" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="M287.3 135.8c.6-2.9.5-6-2.2-9.9s-7.3-5.5-7.3-5.5l-1.7-1.9a33 33 0 0 0-4.5-3.6l5.5 1.5v.1a2.4 2.4 0 0 0 2.4 2.5 2 2 0 0 0 1.4-.4c1.1.2 4.7 2.3 5.7 3.1l-.4 1.3a2.4 2.4 0 0 0 2.4 2.5l1.1-.3.3.5 1.8 4a3 3 0 0 0-1 2.1c0 1.4.8 2.5 2 2.5v1.6q.2 1.2-1.8 1.7c-1.4.4-3.3 2.8-3.3 2.8-.1 0-1-1.8-.4-4.6"/>
|
||||||
|
<path fill="#d4af3a" d="M278.8 117.6q-.8-.7-.2-1.4.7-.5 1.6.1c.9.6.6 1 .2 1.4q-.7.5-1.6-.1m9 6.1q-1-.9-.2-1.8.8-.7 1.8.2 1 1 .2 1.7-.7.7-1.8 0z"/>
|
||||||
|
<circle cx="-293.2" cy="131.4" r="1.4" fill="#d4af3a" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="M263 112q1.2.2 4.1 1.7c-1.4 0-2.5.9-2.5 2s1.3 2.4 2.7 2.4 2.3-1.2 2.3-2.3q0-.5-.2-.8l2 1.2c4.4 3 5.4 4.5 7.6 8.5s3 8.6 2 11.2c-.9 2.7-4.4 3.2-4.4 3.2s.4-.4.4-.9l-.2-1.7c-.2-1 0-2.2 0-2.2s-.9-.1-1.3.4l-1.6 1.8-1.6 1.6s-.7-1-.7-2.2.8-2.5 1-4.6-1-5.8-3-8.6a24 24 0 0 0-7-5.7 2 2 0 0 1-1.5-2l-.2-2c-.1-1.3 1-1.2 2.2-1z"/>
|
||||||
|
<path fill="#d4af3a" d="M267.5 116.8q-1.4-.2-1.5-1.4.1-1 1.4-1.1 1.3.1 1.4 1.1 0 1.2-1.3 1.4m-6.6 26a3 3 0 0 1-2.8-.2q-1-.8-.1-1.9a2 2 0 0 1 2.3-.2l2 1.4s-.3.5-1.4.9"/>
|
||||||
|
<path fill="#d4af3a" d="M257.5 144c.4.6.6 1 1.8 1s2.5 0 3.3-.8l1.3-1.9h1.3s.5 1.9 1 2.4c.7.5 2.7.6 3.6 0 1-.8 1.7-1.6 1.9-1.5q.3.3-1 1.6c-1.3 1.3-1.5 1.3-3 1.3h-8.5s-1.7.1-2.2-1q-.7-1.5-.3-1.9.2-.2.8.8"/>
|
||||||
|
<path fill="#d4af3a" d="M270.4 142.6c-1.3.7-2 .2-2.4-.3l-.6-.9s1.2-.4 1.8-.8q.8-.5 1.6-.6l.6.2s.3 1.6-1 2.4"/>
|
||||||
|
<circle cx="-276.6" cy="130" r="2.8" fill="#b96b29" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#b96b29" d="M271.6 120.2a3 3 0 0 1 3.8.7c.9 1.2.7 2.7-.4 3.5a3 3 0 0 1-3.7-.7c-1-1.2-.8-2.7.3-3.5"/>
|
||||||
|
<circle cx="-276.7" cy="129.6" r="1.6" fill="#d4af3a" transform="scale(-1 1)"/>
|
||||||
|
<path fill="#d4af3a" d="M272.8 120.5q1.4-.2 2 .8.6 1.2-1 1.6c-1.8.3-2.6-2-1-2.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 56 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-mk" viewBox="0 0 512 512">
|
||||||
|
<path fill="#d20000" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#ffe600" d="M0 0h86.8L256 246.9 425.2 0H512L0 512h86.8L256 265.1 425.2 512H512zm512 204.8v102.4L0 204.8v102.4zM204.8 0 256 219.4 307.2 0zm0 512L256 292.6 307.2 512z"/>
|
||||||
|
<circle cx="256" cy="256" r="82.3" fill="#ffe600" stroke="#d20000" stroke-width="18.3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 407 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-nl" viewBox="0 0 512 512">
|
||||||
|
<path fill="#ae1c28" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#fff" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#21468b" d="M0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-no" viewBox="0 0 512 512">
|
||||||
|
<path fill="#ed2939" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#fff" d="M128 0h128v512H128z"/>
|
||||||
|
<path fill="#fff" d="M0 192h512v128H0z"/>
|
||||||
|
<path fill="#002664" d="M160 0h64v512h-64z"/>
|
||||||
|
<path fill="#002664" d="M0 224h512v64H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 318 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-pl" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path fill="#fff" d="M512 512H0V0h512z"/>
|
||||||
|
<path fill="#dc143c" d="M512 512H0V256h512z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 219 B |
@@ -0,0 +1,57 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-pt" viewBox="0 0 512 512">
|
||||||
|
<path fill="red" d="M204.8 0H512v512H204.7z"/>
|
||||||
|
<path fill="#060" d="M0 0h204.8v512H-.1z"/>
|
||||||
|
<g fill="#ff0" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".6">
|
||||||
|
<path stroke-width=".7" d="M293.8 326.6c-34.4-1-192-99.4-193-115.1l8.6-14.5C125 219.7 286.2 315.4 302 312z"/>
|
||||||
|
<path stroke-width=".7" d="M107.6 195c-3.1 8.3 41.1 35.6 94.3 68 53.2 32.3 99 52.3 102.5 49.5l1.5-2.9q-1 1.6-4.6.6c-14.4-4.2-51.9-21.4-98.3-49.5s-86.8-54.1-93-65.1a7 7 0 0 1-.7-3.3h-.2l-1.3 2.3zm187 132c-.6 1.1-1.7 1.1-3.7 1-12.9-1.5-52-20.5-98-48.1-53.8-32.2-98.2-61.5-93.3-69.1l1.3-2.4.2.1c-4.3 13 87.6 65.5 93 68.9 53.1 33 98 52.2 102 47.2z"/>
|
||||||
|
<path stroke-width=".7" d="M205 221c34.3-.3 76.8-4.7 101.2-14.4L301 198c-14.5 8-57.1 13.3-96.3 14-46.4-.4-79.1-4.7-95.5-15.7l-5 9.1c30.2 12.7 61 15.5 100.8 15.6"/>
|
||||||
|
<path stroke-width=".7" d="M307.7 206.8c-.9 1.3-16.8 6.8-40.3 10.8a416 416 0 0 1-62.6 4.6 444 444 0 0 1-60-3.8c-24.5-3.9-37.3-9.3-42-11.2l1.2-2.3a186 186 0 0 0 41.3 11 439 439 0 0 0 59.5 3.7 418 418 0 0 0 62.1-4.6c24-3.9 37.2-8.9 39-11.2zM303 198c-2.6 2-15.6 6.7-38.4 10.3a414 414 0 0 1-59.5 4.2 400 400 0 0 1-57.4-3.7c-23.3-3-35.6-8.5-40-10.1l1.3-2.3c3.4 1.8 15.4 6.6 39 10a423 423 0 0 0 57.1 3.5 409 409 0 0 0 59-4.2c23-3.2 35.4-9 37.3-10.5zM92 262.4c21.2 11.4 68.1 17.2 112.6 17.6 40.5 0 93.3-6.3 113-16.8l-.6-11.4c-6.1 9.7-62.6 19-112.8 18.6s-96.8-8.2-112.3-18.2v10.2"/>
|
||||||
|
<path stroke-width=".7" d="M318.7 260.8v2.7c-3 3.6-21.6 9-44.9 12.7a463 463 0 0 1-69.7 4.8c-27.4 0-49.2-2-66.2-4.6a165 165 0 0 1-47.3-12.7v-3.2c10.4 6.9 38.3 11.9 47.7 13.4 16.8 2.6 38.5 4.5 65.8 4.5 28.7 0 51.7-2 69.3-4.7 16.8-2.4 40.6-8.7 45.3-12.9m0-9.7v2.8c-3 3.5-21.6 8.9-44.9 12.6a463 463 0 0 1-69.7 4.8c-27.4 0-49.2-2-66.1-4.5A165 165 0 0 1 90.6 254v-3.2c10.4 6.9 38.3 12 47.7 13.4 16.9 2.6 38.6 4.6 65.8 4.6 28.7 0 51.7-2 69.3-4.8 16.8-2.4 40.6-8.7 45.3-12.9m-114.2 73.5c-48.6-.3-90.3-13.2-99.1-15.4l6.4 10c15.5 6.6 56.2 16.4 93.2 15.3s69.3-4 92-15l6.6-10.5c-15.5 7.3-68.3 15.6-99 15.6"/>
|
||||||
|
<path d="m299.5 317.1-3 4.4a259 259 0 0 1-34.7 8.9 315 315 0 0 1-57.2 5.2c-43.1-.6-78.4-9-95-16.2l-1.4-2.3.3-.4 2.2.9a306 306 0 0 0 94.1 15.5c20 .1 40-2.3 56.1-5.1 24.8-5 34.8-8.7 37.9-10.4zm5.7-9.4h.1l-2.2 3.8c-5.7 2-21.3 6.6-44 9.8-15 2-24.3 4-54 4.6a371 371 0 0 1-100.5-15l-1.2-2.5a424 424 0 0 0 101.7 15c27.2-.6 38.8-2.6 53.6-4.7 26.5-4 39.8-8.4 43.8-9.7l-.1-.2z"/>
|
||||||
|
<path stroke-width=".7" d="M305.8 253.4c.2 32-16.2 60.8-29.4 73.5a106 106 0 0 1-72.3 30c-32.3.6-62.7-20.5-70.9-29.7-16-18.1-29-41-29.4-72 2-35 15.7-59.3 35.6-76a106 106 0 0 1 68.4-24.2c25.4.7 55.2 13.2 75.7 38 13.4 16.2 19.3 33.8 22.3 60.5zM204.4 143.8c62 0 113 50.5 113 112.3a113 113 0 0 1-113 112.3c-62 0-112.6-50.4-112.6-112.3s50.6-112.3 112.6-112.3"/>
|
||||||
|
<path stroke-width=".7" d="M204.7 143.4c62 0 112.6 50.7 112.6 112.7s-50.6 112.7-112.6 112.7A113 113 0 0 1 92 256a113 113 0 0 1 112.7-112.7zM94.5 256.1c0 60.6 49.8 110.2 110.2 110.2s110.1-49.6 110.1-110.2S265.1 146 204.7 146A110.6 110.6 0 0 0 94.5 256.1"/>
|
||||||
|
<path stroke-width=".7" d="M204.8 152.8c56.5 0 103.1 46.5 103.1 103.2s-46.6 103.2-103.1 103.2A103.6 103.6 0 0 1 101.5 256c0-56.7 46.6-103.2 103.2-103.2zM104 256c0 55.4 45.5 100.7 100.8 100.7S305.5 311.4 305.5 256 260 155.3 204.7 155.3 104 200.6 104 256"/>
|
||||||
|
<path stroke-width=".7" d="M209.3 143h-9.7v226.4h9.7z"/>
|
||||||
|
<path stroke-width=".7" d="M208.3 141.7h2.5v229h-2.5zm-9.6 0h2.5v229h-2.5z"/>
|
||||||
|
<path stroke-width=".7" d="M317.4 260.5V252l-6.8-6.3-38.7-10.3-55.8-5.7-67 3.4-47.9 11.4-9.6 7.2v8.3l24.4-11 58-9h55.8l41 4.5 28.4 6.9z"/>
|
||||||
|
<path stroke-width=".7" d="M204.7 238.7a394 394 0 0 1 72.9 6.5c21.1 4.2 36 9.5 41 15.4v3c-6-7.4-26-12.8-41.5-16-20.3-3.9-46-6.4-72.4-6.4-28 0-54 2.7-74 6.6-16 3.2-37.4 9.5-40.1 15.8v-3c1.5-4.4 17.4-10.9 39.8-15.4a405 405 0 0 1 74.3-6.5m0-9.7a396 396 0 0 1 73 6.5c21 4.2 36 9.5 41 15.5v2.9c-6.1-7.4-26.2-12.8-41.6-15.9-20.3-4-46-6.5-72.4-6.5a402 402 0 0 0-73.8 6.6c-15.5 3-37.8 9.6-40.3 15.8v-3c1.5-4.3 17.8-11 39.8-15.3a405 405 0 0 1 74.3-6.6m-.5-49.3c41.9-.2 78.5 5.9 95.2 14.5l6.1 10.5c-14.5-7.8-54-16-101.3-14.7-38.5.2-79.6 4.2-100.3 15.2l7.3-12.2c17-8.8 57-13.2 93-13.3"/>
|
||||||
|
<path stroke-width=".7" d="M204.7 188.4c24 0 47 1.3 65.4 4.6 17.1 3.2 33.5 8 35.8 10.6l1.8 3.2c-5.7-3.7-19.8-7.9-38-11.3a367 367 0 0 0-65-4.5c-27 0-48 1.3-66 4.5-19 3.5-32.2 8.6-35.5 11l1.8-3.3c6.3-3.3 16.4-7.2 33.3-10.2 18.6-3.4 39.7-4.4 66.4-4.6m0-9.6a371 371 0 0 1 63.1 4.4c13.9 2.7 27.6 6.9 32.7 10.6l2.6 4.2c-4.5-5-21.4-9.7-36.4-12.3-17.4-3-39.1-4.2-62-4.4-24 0-46.2 1.5-63.4 4.6-16.4 3.2-27 6.9-31.5 9.8l2.3-3.6c6.2-3.2 16.2-6.2 28.8-8.7 17.3-3.1 39.7-4.6 63.8-4.6m56 124.1a293 293 0 0 0-56-4.2c-69.9.8-92.4 14.3-95.2 18.4l-5.2-8.5C122 295.7 160 288.5 205 289.2q35 .5 60.4 5.2l-4.8 8.5"/>
|
||||||
|
<path d="M204.3 297.5c19.4.3 38.5 1 57 4.5l-1.4 2.4a298 298 0 0 0-55.5-4.3c-25.8-.2-52 2.2-74.6 8.7-7.2 2-19 6.6-20.3 10.4l-1.3-2.2c.4-2.2 7.6-6.9 21-10.6 26-7.5 50.3-8.7 75-9zm.9-9.8a351 351 0 0 1 61.1 5.3l-1.4 2.5a319 319 0 0 0-59.6-5.2c-25.9 0-53.3 1.9-78.3 9.1-8 2.4-22 7.4-22.4 11.4l-1.3-2.3c.3-3.6 12.3-8.4 23.2-11.6a285 285 0 0 1 78.7-9.2"/>
|
||||||
|
<path stroke-width=".7" d="m304.4 309.9-8.4 13-24.1-21.4-62.6-42-70.5-38.8-36.7-12.5 7.8-14.5 2.7-1.4 22.8 5.7 75 38.7 43.3 27.3 36.3 26 14.8 17.1z"/>
|
||||||
|
<path stroke-width=".7" d="M100.8 208.5c6.5-4.3 53.7 16.7 103 46.5s96.4 63.6 92.2 70l-1.4 2.1-.7.5c.2 0 .9-1 0-3.3-2.2-6.9-35.5-33.5-91-67-54-32.2-99-51.6-103.5-46zm205.3 101.2c4-8-39.7-41-94-73.1-55.5-31.5-95.6-50-102.9-44.5l-1.6 3 .4-.5c1.3-1.1 3.5-1 4.5-1 12.6.1 48.6 16.6 99 45.6 22.1 12.8 93.4 58.6 93.1 71.4 0 1.1.1 1.3-.3 1.9z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(-68.3 28.4)scale(1.13778)">
|
||||||
|
<path fill="#fff" stroke="#000" stroke-width=".7" d="M180.6 211a59 59 0 0 0 17.5 41.7 59 59 0 0 0 41.8 17.6 59 59 0 0 0 42-17.4 59 59 0 0 0 17.4-41.8v-79.2l-118.7-.2z"/>
|
||||||
|
<path fill="red" stroke="#000" stroke-width=".5" d="M183 211.1a56 56 0 0 0 16.8 40 57 57 0 0 0 40.2 16.8 57 57 0 0 0 40.2-16.6 56 56 0 0 0 16.7-40v-77H183v76.8m91-53.7v48.9l-.1 5.1a33 33 0 0 1-10 24 34 34 0 0 1-24 10c-9.4 0-17.7-4-23.9-10.2a34 34 0 0 1-10-24v-54z"/>
|
||||||
|
<g id="pt-e">
|
||||||
|
<g id="pt-d" fill="#ff0" stroke="#000" stroke-width=".5">
|
||||||
|
<path stroke="none" d="M190.2 154.4c.1-5.5 4-6.8 4-6.8.1 0 4.3 1.4 4.3 6.9z"/>
|
||||||
|
<path d="m186.8 147.7-.7 6.3h4.2c0-5.2 4-6 4-6 .1 0 4 1.1 4.1 6h4.2l-.8-6.4zm-1 6.4h17q.5 0 .6.7 0 .8-.6.8h-17q-.5 0-.6-.8 0-.7.7-.7z"/>
|
||||||
|
<path d="M192 154c0-3.3 2.3-4.2 2.3-4.2s2.3 1 2.3 4.2H192m-5.8-9h16.3q.5.1.6.8 0 .5-.6.6h-16.3q-.5 0-.6-.7 0-.5.6-.6zm.4 1.5H202q.5 0 .6.7t-.6.7h-15.5q-.6 0-.6-.7t.6-.7zm5-10.6h1.2v.8h.9v-.8h1.3v.9h.9v-1h1.2v2q0 .6-.5.6h-4.4q-.5 0-.6-.5zm4.6 2.7.3 6.4h-4.3l.3-6.5h3.7"/>
|
||||||
|
<path id="pt-a" d="M191 141.6v3.4h-4v-3.4z"/>
|
||||||
|
<use xlink:href="#pt-a" width="100%" height="100%" x="10.6"/>
|
||||||
|
<path id="pt-b" d="M186.3 139h1.2v1h.9v-1h1.2v1h.9v-1h1.2v2q0 .6-.5.6h-4.3l-.6-.6z"/>
|
||||||
|
<use xlink:href="#pt-b" width="100%" height="100%" x="10.6"/>
|
||||||
|
<path fill="#000001" stroke="none" d="M193.9 140.6c0-.6.9-.6.9 0v1.6h-.9z"/>
|
||||||
|
<path id="pt-c" fill="#000001" stroke="none" d="M188.6 142.8c0-.6.8-.6.8 0v1.2h-.8z"/>
|
||||||
|
<use xlink:href="#pt-c" width="100%" height="100%" x="10.6"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#pt-d" width="100%" height="100%" y="46.3"/>
|
||||||
|
<use xlink:href="#pt-d" width="100%" height="100%" transform="rotate(-45.2 312.8 180)"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#pt-d" width="100%" height="100%" x="45.7"/>
|
||||||
|
<use xlink:href="#pt-e" width="100%" height="100%" transform="matrix(-1 0 0 1 479.8 0)"/>
|
||||||
|
<g id="pt-f" fill="#fff">
|
||||||
|
<path fill="#039" d="M232.6 202.4a8 8 0 0 0 2.2 5.7 7 7 0 0 0 5.3 2.4q3.2-.2 5.3-2.4a8 8 0 0 0 2.2-5.7v-10.8h-15z"/>
|
||||||
|
<circle cx="236.1" cy="195.7" r="1.5"/>
|
||||||
|
<circle cx="244.4" cy="195.7" r="1.5"/>
|
||||||
|
<circle cx="240.2" cy="199.7" r="1.5"/>
|
||||||
|
<circle cx="236.1" cy="203.9" r="1.5"/>
|
||||||
|
<circle cx="244.4" cy="203.9" r="1.5"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#pt-f" width="100%" height="100%" y="-26"/>
|
||||||
|
<use xlink:href="#pt-f" width="100%" height="100%" x="-20.8"/>
|
||||||
|
<use xlink:href="#pt-f" width="100%" height="100%" x="20.8"/>
|
||||||
|
<use xlink:href="#pt-f" width="100%" height="100%" y="25.8"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 8.2 KiB |
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ro" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="#00319c" d="M0 0h170.7v512H0z"/>
|
||||||
|
<path fill="#ffde00" d="M170.7 0h170.6v512H170.7z"/>
|
||||||
|
<path fill="#de2110" d="M341.3 0H512v512H341.3z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 302 B |
@@ -0,0 +1,296 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="flag-icons-rs" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="rs-a">
|
||||||
|
<path d="M54.3 32h480v480h-480z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g clip-path="url(#rs-a)" transform="translate(-57.9 -34.1)scale(1.06667)">
|
||||||
|
<path fill="#fff" d="M-32 32h720v480H-32z"/>
|
||||||
|
<path fill="#0c4076" d="M-32 32h720v320H-32z"/>
|
||||||
|
<path fill="#c6363c" d="M-32 32h720v160H-32z"/>
|
||||||
|
<g transform="matrix(.53333 0 0 .53566 -32 31.8)">
|
||||||
|
<path fill="#21231e" d="M473.4 259.4v-.2h1v.2l-.5.5-.6-.6zm1 0v.5h-.5l.4-.6zm-93 5.2-1 .2.3-.6 4.2-.2 4-.3 4-.2 3.8-.3 4-.2 3.6-.3 3.7-.2 3.7-.2 3.5-.2h3.4l3.3-.3 3.2-.2 3.2-.2 3-.2h3l3-.2 3-.2 2.7-.2h2.8l2.7-.2 2.5-.2h2.5l2.4-.3h2.2l2.2-.2h2.2l2-.2h2l2-.2h5.3v1h-5.5l-1.8.2h-2l-2 .2h-2.2l-2.2.2-2.4.2h-2.4l-2.5.2-2.5.2H446l-2.8.3h-2.8l-3 .3-2.8.2h-3l-3 .3-3.3.2-3.3.2-3.3.2h-3.5l-3.5.3-3.6.2-3.5.3-3.8.2-3.8.2-4 .3-4 .2-4 .3-4.2.2.5-.6zm-1 .2-.3-.5h.7zm6.4 31-6.5-31 1-.2 6.5 31-.5.7zm.5.5h-.4l-.2-.4.5.3zm95-4-.2 1h.2-5.8l-3 .2h-3l-2.8.2h-3l-3 .2h-6l-3 .2h-3l-3 .2h-3l-3 .2h-3.2l-3 .2h-3l-3 .2h-3l-3 .2h-3l-3 .2h-3l-3 .2h-3l-3 .2h-3l-2.8.2h-5.8l-2.7.2h-2.7v-1h5.8l2.7-.2h5.8l3-.2h3l2.8-.2h3l3-.2h3l3-.2h3l3-.2h3l3-.2h3l3-.2h3.2l3-.2h3l3-.2h3l3-.2h3l3-.2h3l3-.2h3l2.8-.2h3l2.8-.2h.2zm-.2 0h.2zm94.5 3.4 1 .2-.5.3h-2.8l-2.8-.2h-8.8l-2.8-.2h-3l-3-.2H551l-3-.2h-3l-3-.2h-3l-3-.2h-3l-3-.2h-3l-3.2-.2h-3l-3-.2h-3l-3-.2h-3l-3-.2h-3l-3-.2h-3l-3-.2h-3l-3-.2H485l-3-.2.2-1h2.8l3 .2h3l2.8.2h3l3 .2h3l3 .2h3l3 .2h3l3 .2h3l3 .2h3l3 .2h3.2l3 .2h3l3 .2h3l3 .2h3l3 .2h3l3 .2h5.8l2.7.2h3l2.7.2h2.8l-.5.5zm1 .2v.3h-.5l.5-.4zm6.5-31.2-6.5 31h-1l6.5-31.2.6-.4zm-.4-.6h.5v.6zm-93.7-4.8h1l-.6-.5h5.4l2 .2h2l2 .2h2l2.4.2h2.4l2.4.3h5l2.6.3h2.7l3 .3 2.8.2h3l3 .3 3 .3 3.3.2 3.3.2 3.4.2h3.4l3.6.3 3.6.2 3.7.2 3.6.3 4 .2 3.8.3 4 .2 4 .3 4.2.2v1l-4.3-.2-4-.3-4-.2-4-.3-3.7-.2-3.8-.2-3.6-.3-3.6-.2-3.5-.2h-3.4l-3.3-.3-3.3-.2-3.2-.2-3-.2h-3l-3-.2-3-.2h-2.7l-2.6-.3h-2.6l-2.6-.3h-2.5l-2.4-.2-2.3-.2h-2.2l-2.2-.2h-2l-2-.2h-2l-1.8-.2h-3.3l-.5-.6zm.4.5h-.5v-.6l.4.5zm.5-.8v.2h-1v-.2l.3-.5.6.5zm-1 0v-.4h.3l-.4.4zm-8 .3.2-1h8.8l.3.2v.8h-.5v.2h-.3l-.2-1h.2v.7h.2-8.2zm-8.1.2v-1l-.2 1h-.4l-.2-.2-.3-.3v-.5l.2-.2h9.4v1h-8.8l.2-.2v-.8h-.2.4v1zm0-1v1l-.6-.5.5-.5zm-.6.5v-.5h.5l-.6.5z"/>
|
||||||
|
<path fill="#edb92e" d="M583.5 264.2q4.6-10 11.2-18.7-17.6 13.2-18.7-4-1.2-.2-2.6-.2-13.5 21-26.2-5.8-17.4 27.6-29.2-1-20 25-33.6.2v-2q.6.4-1 0l-.3-.3-1 .2-.8-.2-.2.4q-1.9.2-1 0v2q-13.8 24.6-33.7-.4-11.8 28.6-29.2 1-12.5 27-26 6-1.6 0-2.7.2-1.1 17-18.8 4c4.6 5.5 8 12 11.3 18.6q50.9-2.2 101.3-5 50.8 2.8 101.5 5z"/>
|
||||||
|
<path fill="#21231e" d="m595 246-.6-1 .7 1-.3.4-.4.5-.4.4-.5.6-.4.5-.3.6-.4.6-.4.6-.5.5-.3.6-.4.6-.4.4-.3.6-.3.6-.3.6-.4.6-.3.5-.3.6-.4.5-.3.6-.3.7-.4.6-.3.6-.3.5-.3.6-.2.8-.3.6-.4.6-.2.6-.3.6-.3.6-.3.6-1-.4.3-.6.3-.7.3-.6.2-.5.4-.6.3-.7.2-.6.3-.6.3-.6.4-.6.3-.6.3-.6.3-.6.5-.6.3-.6.4-.6.3-.6.4-.5.2-.5.4-.6.4-.7.3-.6.4-.5.4-.6.5-.6.4-.5.3-.6.4-.6.4-.6.5-.5.4-.6.4-.5.7.7zm-19.2-4 .3-1 .5.5v1l.2 1v1l.3.7.2.8.3.7.3.7.2.6.4.5.3.5.4.4.5.3.4.3.4.3.5.2h.4l.5.2h1.9l.7-.2.7-.2.8-.3.8-.3.8-.3 1-.5.8-.6 1-.5 1-.7 1-.7 1-.7 1-1 .7 1-1 .7-1 .8-1 .7-1 .6-1 .5-1 .5-1 .5-.8.4-1 .3-.7.3-.8.2h-.8l-.8.2h-2l-.7-.3-.5-.2-.6-.3-.5-.5-.6-.4-.5-.4-.5-.5-.3-.5-.4-.7-.3-.7-.2-.7-.3-.8-.2-1v-.8l-.2-1v-1l.2.3zm.3-1 .4.2v.3zm-2.2.6-.8-.6.4-.2h2v.2h.4l-.2 1h-1.2v-.2h-1.2l.4-.2zm-.8-.6v-.2h.4zm-25.3-5.2-1-.6h1l.8 1.7.8 1.4.8 1.5.8 1.2.8 1.2.8 1.2.8 1 .8 1 .8.8.8.8.8.7.7.5 1 .4.7.3.8.3.7.3h2.3l.8-.2.8-.2.8-.3.8-.4.8-.6.8-.6.8-.7.8-.6 1-1 .7-1 .8-1 .8-1 1-1.4.8.6-.8 1.3-1 1-.7 1.2-1 1-.7 1-1 .7-.8.6-.8.6-1 .6-.7.4-1 .4-.8.2-1 .2h-3l-1-.4-.7-.3-1-.5-.7-.5-1-.6-.7-.7-1-.8-.8-1-.8-1-.8-1-.8-1.2-.8-1.3-.8-1.3-.8-1.5-.8-1.6-.8-1.7h1zm-1-.6.6-.7.4.8h-1zm-28.3-.5-.8-.6 1 .2.6 1.7.7 1.5.7 1.5.8 1.4.7 1.3.8 1.2.8 1 .8 1 .8.7.8.7.8.6 1 .5.7.4.8.2 1 .2h2.5l1-.3.8-.2 1-.4.8-.6 1-.7 1-.6 1-1 1-1 1-1 1-1.2 1-1.3 1-1.6 1-1.5 1.2-1.8 1 .6-1.2 1.7-1 1.5-1.2 1.5-1 1.3-1 1.3-1 1.2-1.2 1-1 1-1 .8-1 .7-1 .6-1 .4-1 .4-1 .4h-3l-1-.2-1-.3-.8-.6-1-.5-.8-.8-1-.8-.7-.8-1-1-.8-1-.8-1.3-.8-1.4-.8-1.5-.8-1.5-.7-1.7-.8-1.7h1zm-.8-.6.5-.6.5.8h-1zm-33.8.7h1v-.2l.8 1.5 1 1.4.8 1.2 1 1.2.8 1 1 1 .8 1 1 .8.8.7 1 .6 1 .4 1 .4.8.3 1 .3 1 .2h2l1-.2 1-.3 1-.3 1.2-.4 1-.6 1.2-.6 1-.6 1.3-.8 1-1 1.3-1 1.2-1 1-1.3 1.4-1.3 1.2-1.4 1.2-1.5.8.7-1.3 1.5-1 1.5-1.3 1.3-1.2 1.3-1.2 1-1.2 1-1 1-1.3 1-1.2.7-1 .6-1.3.6-1 .5-1.3.3-1.2.3h-1l-1.2.2h-1l-1.2-.2-1-.2-1-.3-1-.4-1-.5-1-.7-1-.6-1-1-1-.8-1-1-1-1.2-.8-1.2-1-1.3-.8-1.5-1-1.5v-.3zm0 .3v-.3zm1-2.3v2h-1v-2l.8-.5.3.5zm-1 0v-.8l.8.2-.8.5zm-1 .3 1-.4-.4-.3h1l-.2.2v.4l.4-1h.2v.2l.2.2v.6h-1.8l-.5-.3zm.5.3h-.3v-.3zm.3-1 .2.3-1 .4v-.4l.2-.7.6.2zm-.6-.4.4-.2.2.4zm-1 1 .2-1h.8l.2 1h-1zm-.4-.4-1-.4.7-.3h.8v1h-1zm-1-.4.3-.4h.3zm0 .4v-.4l1 .4-.2.4-.4.3zm.8.4v.3h-.4zm-1-.3h-1l.7.4v.2h.2v-.8h-.2.2l.2-.2h.4l.2 1h-1.9v-.2h-.2v-.5h.2v-.2h.2l.8.4zm-.7-.5.7-.3v.7zm-.3 2.5v-2h1v2.3zm1 0v.2zm-33.6 0-1-.5h1l1 1.4 1.3 1.4 1.3 1.3 1.2 1.2 1.2 1 1.2 1.2 1 1 1.2.7 1 .7 1.2.7 1 .6 1.2.4 1 .3 1 .3h1l1 .2h1l1-.2 1-.2 1-.2 1-.4 1-.5 1-.7.8-.7 1-.8.8-1 1-1 .8-1 1-1.2.8-1.2.8-1.4 1-1.5.8.5-1 1.5-.8 1.4-1 1.2-.8 1.2-1 1.2-1 1-.8 1-1 .8-1 .7-1 .8-1 .5-1 .4-1 .4-1 .3-1.2.2h-2.2l-1-.2-1.3-.3-1-.3-1.2-.5-1-.6-1.2-.6-1.2-.8-1.2-.8-1.2-1-1.2-1-1.2-1-1.2-1.4-1.2-1.3-1.3-1.5-1-1.5h.8zm-1-.5.3-.8.7.7zm-28.3 1.5-1-.4h1l1 1.6 1 1.4 1.2 1.5 1 1.2 1 1.2 1 1 1 1 1 1 1 .7 1 .8 1 .6.8.4 1 .3.8.4h2.8l.8-.2.8-.3.8-.5 1-.5.7-.6 1-.7.7-.8.8-1 .8-1 .8-1.3.8-1.3.8-1.4.8-1.5.7-1.6.8-1.8 1 .4-.8 1.7-.7 1.7-.8 1.5-.8 1.4-.8 1.3-1 1.2-.7 1-1 1-.7 1-1 .7-.8.7-1 .4-.8.5-1 .2-1 .2h-3l-1-.3-1-.3-1-.4-1-.6-1-.7-1-.8-1-1-1-1-1-1-1-1.4-1-1.3-1.2-1.5-1-1.5-1.2-1.7h1zm-1-.4.5-.8.5.7h-1zm-25.6 6.5v-1l.3.2 1 1.3.7 1.2.8 1 .8 1 .8.8 1 .8.7.8 1 .6.7.5.7.3.8.3.8.2.8.2h2.3l.8-.4.8-.3.8-.4.7-.5 1-.6.7-.8.8-.8.8-.8.8-1 1-1 .7-1.2.7-1.2.8-1.3.8-1.6.8-1.5.8-1.7 1 .4-1 1.7-.7 1.5-1 1.4-.7 1.3-.8 1.3-.8 1.2-.8 1-1 1-.8 1-.8.8-.8.7-1 .6-.7.5-1 .5-1 .3-.7.3h-2.6l-1-.2-.8-.2-1-.4-.8-.4-1-.6-.8-.6-.8-.7-1-1-.7-.8-1-1-.8-1-1-1.3-.7-1.4.4.2zm0-1h.2v.2l-.3-.2zm-2.2.8h-1l.4-.5h1.2v-.2h1.5v1H389v.2h-.4zm-1 0v-.4h.4zm-18 3.6-.6.7.6-1 1 1 1.2.6 1 .7 1 .6 1 .4.8.5 1 .4.7.3.8.3.8.3.8.2h.7l.7.2h1.7l.4-.2.5-.2.4-.3.4-.3.4-.3.4-.4.3-.5.2-.5.3-.6.3-.7.3-.7.3-.8v-.8l.3-1v-.8l.2-1h1v1l-.2 1-.2 1-.2 1-.2.7-.2.8-.3.8-.4.7-.2.6-.5.6-.4.5-.6.5-.5.4-.7.2-.6.2-.6.2h-3.3l-.8-.3-.8-.3-1-.3-.8-.4-1-.5-.8-.5-1-.5-1-.6-1-.7-1-.8-1.2-.8.7-.8zm11 18.5v1l-.4-.3-.3-.6-.2-.6-.3-.6-.3-.6-.4-.6-.3-.6-.3-.7-.3-.5-.4-.6-.4-.7-.3-.6-.4-.6-.3-.5-.4-.6-.4-.7-.4-.5-.3-.6-.4-.6-.4-.6-.4-.6-.3-.5-.4-.7-.3-.6-.3-.5-.4-.6-.4-.5-.4-.5-.4-.5-.4-.6-.4-.5-.4-.6-.4-.5.7-.8.4.5.4.6.4.5.4.6.4.5.4.5.4.5.4.6.4.6.4.5.3.6.4.6.4.5.4.6.4.6.3.6.5.6.3.6.3.6.5.6.3.6.3.6.5.6.3.7.3.7.4.6.3.6.4.5.4.6.3.7.3.7.3.6-.5-.3zm0 1h-.3v-.3zm101.4-6v1l-3.2.2h-3.2l-3 .3h-3.3l-3 .3-3.3.2H460l-3 .3-3.3.3h-3.2l-3 .3H444l-3 .3h-3.3l-3.3.2-3 .2h-3.3l-3.2.3h-3l-3.3.3-3.3.2h-3l-3.3.3h-3.2l-3 .2-3.3.2h-3.3l-3.2.2h-3l-3.3.3H384l-3 .2-.2-1h3.2l3.2-.3h3l3.3-.2 3.2-.2h3l3.3-.3h3.2l3-.3h3.3l3.2-.3h3l3.3-.2h3.2l3-.3h3.3l3.2-.3 3-.2h3.3l3.2-.3h3l3.3-.3 3.2-.2h3l3.3-.3 3-.3h3.3l3.2-.3 3-.2h3.3l3.2-.3 3-.3h.2zm101.2 5v1h-3l-3.3-.2-3-.2h-3.3l-3-.3h-3.3l-3.2-.3h-3l-3.3-.2-3.3-.2h-3l-3.3-.3-3.2-.2h-3l-3.3-.3h-3.3l-3-.3h-3.3l-3.2-.3-3-.3h-3.3l-3.3-.3h-3l-3.3-.3h-3l-3.3-.2-3-.2-3.2-.2h-3.2l-3.2-.3h-3l-3.3-.2.2-1h3l3.3.3 3.2.2 3 .2 3.3.2h3l3.3.3h3.2l3 .2 3.3.2 3.2.2h3l3.3.3h3l3.3.2 3.2.2h3l3.3.3h3.3l3 .3h3.3l3.2.3h3l3.3.3h3.3l3 .3h3.3l3.2.2h3.2l3 .2 3.3.2h3.2zm0 1v-1l.5.7zm.5-.3-.2.3h-.4z"/>
|
||||||
|
<path fill="#edb92e" d="m505 276.2-23 11.4-22.6-11.4 22.7-11.5z"/>
|
||||||
|
<path fill="#edb92e" d="M474 259.2v.2q-26.4 1-93.2 5.3l6.5 31c29.6-.8 64.3-2 94.8-3 30.7 1 65.3 2.2 95 3l6.5-31q-66.9-4.2-93-5.3v-.2q2.6-.5-8.4-.2c-7-.2-10-.2-8 .2z"/>
|
||||||
|
<path fill="#edb92e" d="m597.3 236.7-3.7-.6c-7 17.5-13.4 18-14.6 1.4 1.6-.6 1-3.3 2.8-2.2 1 .5 2-1 1.3-2q-2.9-2.2-5.4.4c-.8-2.7-3-4-5.4-2.8-1 .5-.7 2.5.4 2.4 1.8-.2 1 1.6 1.6 3-12.5 16.4-19 14.4-17.4-7.3-3-.7-6.5-1.3-9.6-2.4-11.5 23.3-19.7 24.3-26.4 3.4 1.4-1 1-3.8 3-3 1.3.6 2.3-1.5 1.2-2.4-2.4-2-5-1.3-6.8 1.3-1.4-3-4-4-6.6-2.2-1.2.7-.5 3 .8 2.6 2.3-.7 1.3 2.4 3 3.5-5 19.6-30.8 21-26.6-8.2q-3.5 0-6.6.2l-6.7-.2c4.2 29-21.5 27.8-26.6 8 1.6-1 .6-4 3-3.4 1.2.3 2-2 .7-2.7-2.6-1.7-5.2-.7-6.6 2.2-1.7-2.7-4.3-3.4-6.7-1.4-1 1 0 3 1 2.5 2.2-.8 1.8 2 3.2 3-6.7 21-15 20-26.4-3.4-3 1-6.5 1.7-9.5 2.3 1.6 21.6-5 23.6-17.4 7 .6-1.2 0-3 1.7-2.8 1 0 1.5-2 .4-2.4-2.4-1-4.5 0-5.3 2.8q-2.5-2.7-5.5-.4c-.8 1 .4 2.5 1.3 2 1.8-1 1.2 1.6 2.8 2.2-1.2 16.5-7.6 16-14.6-1.3l-3.7.7c0 4 1.3 8.5 3.8 11.7 10.6 13.5 18.5 10.4 18.8-4.7 14 16.4 24.4 14.3 26.2-3 11.7 21.8 30.5 11.4 31-2 5 19.4 33.5 19.2 35.4-3 1.8 22.2 30.3 22.4 35.4 3 .4 13.4 19.2 23.8 31 2 1.7 17.3 12.3 19.4 26 3 .4 15 8.3 18.2 19 4.7 2.4-3.2 3.8-7.6 3.7-11.7z"/>
|
||||||
|
<path fill="#21231e" d="m593.7 235.6 3.7.6-.2 1-3.6-.6-.4-.7zm-.5.3v-.4h.5zm-14.3 1 .2 1 .3-.6.2 1.4v1.4l.3 1.3.2 1.2.2 1 .3 1 .3 1 .3.7.3.7.3.6.3.5.3.5.4.2.3.2h1.3l.4-.3.4-.2.5-.4.4-.3.6-.6.5-.6.6-.7.7-1 .6-.8.7-1.2.6-1.2.6-1.3.7-1.3.5-1.5.7-1.6 1 .3-.8 1.6-.6 1.4-.7 1.4-.5 1.3-.7 1.3-.6 1-.6 1-.7 1-.7.8-.6.8-.6.6-.5.5-.6.3-.7.4-.5.2h-1.2l-.6-.2-.5-.2-.5-.4-.5-.4-.4-.6-.3-.6-.3-.8-.4-.8-.3-1-.2-1-.3-1-.2-1.3-.2-1.3-.2-1.5v-1.6l.3-.5zm-.5.4v-.4h.4zm3.5-2.7-.5 1v-.2h-.4v.2h-.2v.2l-.2.2v.3l-.2.3-.2.3v.2h-.2v.3h-.3v.2h-.3l-.2.2-.3-1v-.2h.2v-.2h.2v-.2l.2-.2v-.5h.2v-.4l.2-.2v-.3l.2-.2.2-.2h.2l.2-.2h1l.2.2zm.8-1 .6-1v.2h.2l.2.3v1.6h-.2v.2h-.2v.2h-.2v.2h-.2v.2h-.5v.2h-.8l-.3-.2.5-1v.2h.4v-.2h.2v-.2h.2v-.8zm.6-1v.2zm-6.3 1 1-.3-.8-.2h.2l.2-.3.2-.2.2-.2.3-.2h.2l.2-.2h.2v-.2h.4l.2-.2h2.3l.2.2h.2l.2.2h.2l.2.2h.2l.2.2.2.2-.6.8-.3-.2h-.2l-.2-.2-.2-.2h-.3l-.2-.2h-2.1v.2h-.3v.2h-.2l-.3.2v.2h-.3v.2l-.3.2zm1 .3-.7.5-.2-.8 1 .3zm-5.6-2.7-.4-1h.5l.3-.2h2.1l.4.2h.4l.2.3h.2v.2l.3.2h.2l.2.2v.2l.2.2v.2l.2.2.2.2v.2l.2.3v.2l.2.2v.2l-1 .3v-.5h-.2v-.3l-.2-.2v-.2h-.3v-.3h-.2v-.4h-.3v-.2h-.3v-.2h-.6v-.2h-2l-.2.2zm0 1.4.2 1h-.6v-.2h-.2l-.2-.2-.2-.2-.2-.2V231l.2-.2.2-.2.3-.2.4 1h-.2v1.2h.2zm2.2 3.7-.8-.6v.5-.2h-.2v-1h-.2v-1l-.2-.2h-.7v-1.2h.6l.3.2.2.2h.2v.2l.2.3v.4h.2v1.5h.2v.8zm0-.5h.2l-.2.4zm-18-6.6.3-1 .3.6v2l-.2 1.8v4.9l.3 1.4v1.3l.3 1 .2 1 .3 1 .4.8.3.8.4.5.5.5.4.3.4.3.5.2h1l.6-.2.7-.3.7-.4.8-.5 1-.4.7-.8 1-.8 1-1 1-1 1-1 1-1.4 1-1.4 1.3-1.4.8.6-1.2 1.5-1 1.3-1.2 1.3-1 1-1 1.2-1 1-1 .8-1 .8-.8.6-1 .5-.7.4-.8.3h-.8l-.8.2h-.7l-.7-.2-.6-.3-.6-.5-.5-.5-.4-.7-.5-.7-.4-1-.2-1-.2-1-.2-1.2-.2-1.3V229l.5.6zm.3-1h.3v.6zm-9.3-1.6-1-.4.7-.3.3.2h.3l.3.2h.3l.2.2h.3l.4.2h.6l.3.2h.3l.4.2h.6l.3.2h.5l.3.2h.7l.3.2h.6l.3.2h.5l.3.2h.8l.3.2h.3l-.3 1h-.8l-.2-.2h-.6l-.3-.2h-.8l-.3-.2h-.6l-.4-.2h-.7l-.4-.2h-.3l-.3-.2h-.6l-.3-.2h-.2l-.3-.2h-.6l-.4-.2h-.3l-.4-.2h-.4l-.3-.2h-.2l.7-.4zm-1-.4.3-.4h.4l-.6.4zm-26.2 3.2.6.8.3-.6.6 2 .5 1.7.7 1.6.6 1.6.7 1.4.7 1.2.6 1 .7 1 .7 1 .7.6.6.6.7.4.7.4.7.2h1.4l1-.2.6-.2.8-.4 1-.6.7-.6 1-.8 1-1 .8-1 1-1.2 1-1.3.8-1.4 1-1.6 1-1.7 1-1.8 1-2 1.2-2 1 .3-1.2 2.2-1 2-1 1.8-1 1.8-1 1.6-1 1.5-1 1.3-1 1.2-1 1-1 1-1 1-.8.7-1 .5-.8.4-1 .2-.8.2h-1.8l-.8-.3-.8-.3-.8-.5-.8-.6-.7-.8-.8-1-.7-1-.7-1-.7-1.4-.7-1.4-.7-1.5-.7-1.7-.6-1.7-.7-1.8.2-.6zm-.2.6v-.4l.2-.2zm3.8-3.6-.3 1h-.3l-.2-.2h-.2v.2h-.2v.2h-.2v.5h-.2v.4l-.2.3-.2.2v.2l-.2.3-.3.3-.2.3h-.2l-.5-.7.2-.2.2-.2v-.5h.2v-.4h.2v-.3l.2-.3v-.2l.2-.2.2-.3.2-.2h.2v-.2h.5v-.2h.7l.2.2zm.7-1.6.6-.8.2.2.2.2v.2l.2.3v1.3l-.2.3v.2l-.2.2h-.2v.2h-.2v.2h-.2l-.2.2h-1.1l.4-1h.7v-.2h.2v-.6h.2v-.7h-.2v-.2zm-7 1 1-.3h-1l.2-.3v-.3h.3l.2-.3.2-.3.2-.2.3-.3.3-.3h.2l.2-.3h.3l.2-.2h.5l.2-.2h2.2l.3.2h.2l.3.2h.3l.3.3h.2l.3.3.3.2-.6.8-.2-.2h-.2l-.2-.3-.4-.2h-.2l-.2-.2H521v.2h-.5l-.2.3h-.2v.2h-.2l-.2.3-.2.2-.3.3v.2l-.2.3-.2.2h-1zm1 .2-.6.7-.3-1 1 .2zm-7-2-.4-1h.3l.2-.2h.3l.4-.2h.2l.3-.2h.4l.3-.2h1.6l.2.2h.2l.3.2h.2l.2.3.3.2.2.2v.2l.3.2.2.2.2.2v.2l.3.2.2.3.3.4v.3l-.8.4-.2-.4-.2-.2v-.2l-.3-.2v-.2l-.3-.2-.3-.3h-.2v-.2h-.3l-.3-.2h-.2l-.3-.2h-.4l-.2-.2h-1.2l-.2.2h-.5l-.2.2h-.2l-.2.2-.2.2zm.5 1.7.3 1h-.5l-.2-.2h-.2l-.2-.2-.2-.2-.2-.2v-.2l-.2-.2V224l.2-.2.2-.3.3-.2.5 1h-.2v1.2h.2v.2h.4zm3.7 4-1-.2.2.5h-.2l-.2-.2h-.2v-.3h-.2l-.2-.3v-.4l-.2-.2v-.3l-.2-.2v-.4l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2h-.6l-.4-1h1.1l.3.2h.2v.4h.2v.3l.2.2v.2h.2v.5l.2.3v.5h.2v.4h.2l.2.2.2.2.3.6zm-.3-.5.3.2v.3zm-27-7.2v-1l.6.5-.3 2.7-.2 2.4v2.2l.2 2 .3 2 .4 1.7.5 1.5.7 1.4.6 1 .8 1.2 1 1 1 .7 1 .6 1 .5 1 .4 1.3.3h2.4l1.3-.2 1.2-.3 1.2-.5 1.2-.5 1-.7 1.2-1 1-.8 1-1 1-1.3 1-1.3.8-1.3.8-1.5.6-1.7.5-1.7 1 .2-.6 2-.7 1.6-.8 1.6-1 1.5-1 1.4-1 1.3-1 1-1.2 1-1.2 1-1.2.8-1.3.7-1.2.5-1.3.3-1.4.2h-2.5l-1.2-.3-1.3-.4-1.2-.5-1-.7-1-1-1-1-1-1-.8-1.5-.7-1.5-.6-1.6-.4-1.8-.3-2-.2-2.2V227l.2-2.6.3-2.7.5.6zm0-1h.7v.5l-.6-.5zm-6.6 1.2.2-1h4l.2-.2h2.1v1h-4.1l-.2.2h-2zm-6-.8h-1l.5-.4h5.1l.2.2h1.2v1h-4l-.3-.2h-2l.5-.6zm-1 0v-.5h.5zm-26.4 7.8.5.8.3-.5.6 1.7.5 1.7.7 1.5 1 1.4.8 1.4 1 1.2 1 1 1 1 1.2 1 1.2.6 1.2.5 1.2.5 1.2.3 1.2.2h2.4l1.2-.3 1-.3 1.2-.4 1-.7 1-1 .8-.8 1-1 .6-1.3.6-1.5.6-1.6.4-1.8.3-2v-2l.2-2.3-.2-2.5-.3-2.7h1l.3 2.6.2 2.5v2.2l-.2 2.2-.3 2-.5 1.8-.5 1.6-.7 1.5-.8 1.4-1 1.2-1 1-1 .8-1 .7-1.2.5-1.3.4-1.3.3H463l-1.2-.2-1.3-.3-1.3-.5-1.3-.6-1.3-.7-1.2-1-1.2-1-1-1-1-1.2-1-1.3-1-1.5-.7-1.6-.7-1.7-.6-2 .2-.5zm-.2.5v-.3l.2-.2zm3.7-4-.2 1-.2-.2h-.7v.2l-.2.2v.4l-.2.2v.5l-.2.2v.2l-.2.3v.2l-.2.2v.2h-.2v.3h-.3l-.2.2h-.2l-.4-.7.2-.2.2-.2h.2v-.3l.2-.2v-.5l.2-.2v-.5l.2-.3v-.3l.2-.2.3-.3.2-.2h.2l.2-.2h1.1zm.4-1.8.6-1v.2h.3v.3h.2v.2l.2.2v1.5l-.2.2v.2l-.2.2-.3.2-.2.2h-.9l.3-1h.4v-.4l.2-.2v-.8h-.2v-.2h-.2zm-6.7 2 .8-.6h-.8v-.2l.2-.3.3-.4v-.2l.3-.2.2-.2.2-.2.2-.2.2-.2.2-.2h.2l.3-.3.3-.2h.2l.3-.2h.5l.2-.2h1.6l.4.2h.2l.3.2h.2l.3.2.2.2h.3l-.6 1-.2-.2h-.2l-.2-.2h-.3l-.2-.2h-.5l-.2-.2h-1.3l-.2.2h-.3v.2h-.3l-.2.3-.2.2-.3.3-.3.3-.2.2v.2l-.2.2-.2.2v.3h-.2zm1 0-.5.7-.5-.8h1zm-7-1.2-.8-.8.2-.2.2-.2h.3l.2-.3h.2l.2-.2h.3l.3-.2h2.5l.3.2h.2l.3.2.2.2h.2l.2.3h.2l.2.2.2.2.2.3.2.2h.2v.4l.3.2-.8.6-.2-.2v-.3l-.3-.2-.2-.2-.3-.3-.2-.2h-.2l-.2-.2v-.2h-.2l-.2-.2h-.4l-.2-.2H441l-.2.2h-.2l-.2.2h-.2l-.2.3h-.2l-Line truncated
|
||||||
|
<path fill="#edb92e" d="M482 263h-22.5l-1.3.2H451l-1.3.2h-4.4l-1.2.2h-2.3l-6.3.4h-.3l-9 .4h-.8l-18.2 1.3h-.2l-26.4 2.4c-5 .6-6-5.3 0-6l26.3-2.2a984 984 0 0 1 37-2.2h2.4l15.4-.5h40.6l15.2.5h3.3a984 984 0 0 1 37 2.3l26.2 2c5.8.7 5 6.6 0 6l-26.4-2.3h-.3l-18.3-1.2h-.6l-9.2-.5h-.3q-3.3 0-6.4-.3h-2.3l-1.2-.2h-4.5l-1.3-.2H506l-1-.2z"/>
|
||||||
|
<path fill="#0c4076" d="M482 105a17.3 17.3 0 0 1 .1 34.7c-9.3 0-17-7.8-17-17.3s7.7-17.3 17-17.3z"/>
|
||||||
|
<path fill="#edb92e" d="M485.3 85c1.6-.2 3-1.5 3-3.2s-1.4-3-3-3.2c0-1.7-1.4-3-3.2-3a3 3 0 0 0-3 3c-1.6 0-3 1.5-3 3.2a3 3 0 0 0 3 3v6.5h-5.5a3 3 0 0 0-3-3c-1.8 0-3 1.3-3.2 3a3 3 0 0 0-3 3c0 1.8 1.3 3.2 3 3.2a3 3 0 0 0 3 3c1.8 0 3.2-1.2 3.2-3h5.5V119h6.3V97.6h5.5a3 3 0 0 0 3 3c1.8 0 3-1.4 3.2-3 1.7 0 3-1.5 3-3.2a3 3 0 0 0-3-3c0-1.8-1.4-3.2-3-3.2-2 0-3.2 1.4-3.2 3h-5.5V85z"/>
|
||||||
|
<path fill="#21231e" d="M487.8 81.8h1v1h-.2v.5h-.2v.3l-.2.3-.2.2v.2h-.2l-.2.2-.2.2h-.2v.2h-.2l-.2.2h-.2v.2h-.8l-.2.2h-.3v-1h.7V84h.2l.3-.2h.2v-.2h.2v-.2l.2-.2V83h.2l.2-.2v-.4l.2-.2zm-3-3.2h1l-.5-.5h.2l.3.2h.4l.3.2h.3l.2.2.2.2h.2v.2h.2l.2.2.2.3.2.2v.2h.2v.4h.2v.5l.2.2v.7h-1V81h-.2v-.4l-.2-.2V80h-.4v-.2h-.2v-.2h-.2l-.3-.2h-.2V79h-.8l-.4-.6zm.4.5h-.4v-.4zm-3-3v-1h.7l.2.2h.2l.3.2h.2l.2.2.2.2h.2v.3l.2.2.2.3.2.2v.2h.2v.4h.2v.8h.2v.3h-1V78h-.2v-.5h-.2v-.2l-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2l-.2-.2h-.4zh-.4zm-3.2 3v-1l-.5.6v-.8l.2-.2V77h.2v-.6h.2v-.2l.2-.2.2-.2.2-.2h.2v-.2h.3v-.2h.3l.3-.2h.4l.2-.2h.4v1h-.5v.2h-.3l-.2.2h-.2v.2h-.2v.2h-.2v.2l-.2.2v.2h-.2v.5h-.2v1l-.6.5zm.5-.4v.5h-.5zm-3 3.2h-1V81l.2-.3v-.5l.2-.2v-.2l.2-.2.2-.3.2-.2h.2l.2-.2h.2v-.2h.3v-.2h.5l.3-.2h.7v1h-.7l-.2.2h-.2l-.2.2-.2.2-.2.2-.2.2v.2h-.2v.4l-.2.2v.8zm3 3h-1l.5.6h-1V85h-.7v-.2h-.3v-.2h-.2l-.2-.2-.2-.2V84h-.2v-.2l-.2-.2-.2-.3v-.5h-.2v-1h1v1h.2v.4h.2v.2h.2v.2l.2.2h.2v.2h.2l.3.2h.7v.2h.2zm-.5-.4h.5v.5l-.5-.6zm-.5 7V85h1v6.3l-.5.5-.5-.5zm1 0v.4h-.5l.5-.5zm-6-.6h5.5v1h-5.5l-.5-.5zm0 1h-.5v-.5zm-3-3v-1h1.2v.2h.3l.3.2.3.2.3.2v.2l.2.2.2.3.2.3.2.3v.4l.2.2v.8h-1v-.7l-.2-.2V90h-.2v-.2h-.2v-.2l-.2-.2-.2-.2h-.2V89h-.4l-.2-.2h-.8zm-3.2 3v-1l-.5.4v-.8l.2-.3V90l.2-.3v-.2h.2v-.2l.2-.2h.2v-.3h.2l.2-.2.2-.2h.2V88h.7v-.2h1.1v1h-1l-.2.2h-.2v.2h-.2v.2h-.2l-.2.2v.2h-.2v.4h-.2v.8l-.2.2v.3l-.5.5zm.5-.5v.5h-.5zm-3 3h-1V93h.2v-.2h.2v-.3l.2-.3.3-.3v-.2h.2v-.2h.2v-.2h.2l.2-.2h.2V91h.7l.2-.2h.8v1h-.7l-.2.2h-.2v.2h-.2v.2h-.2l-.2.2-.2.2v.2h-.2v.5h-.2v.7zm3 3.2h-1l.5.5h-1v-.2h-.5l-.2-.2h-.2v-.2h-.2l-.2-.2-.2-.2v-.2h-.3v-.2l-.2-.2V96h-.2v-.4l-.2-.3v-.8h1v.9h.2v.2l.2.3h.2v.2h.2v.2h.2v.2h.2l.2.2h.5v.2h.4l.6.5zm-.5-.5h.5v.5zm3 3v1h-1l-.2-.2h-.3v-.2h-.3v-.2h-.2l-.2-.2-.2-.3v-.2h-.2v-.3h-.2V99h-.2v-.5l-.2-.2V98h-.2v-.5h1v.7h.2v.4h.2v.2l.2.2v.2h.2v.2h.2v.2h.2l.2.2h.2l.3.2h.6zm3.2-3v1l.5-.4v.8l-.2.2v.3h-.2v.3l-.2.3-.2.2-.2.2-.2.2-.3.2-.2.2h-.2l-.2.2h-.2l-.3.2h-.8v-1h.9v-.2h.2l.2-.2h.2v-.2h.2l.2-.2V99h.2v-.2l.2-.3v-.2l.2-.2v-.4zm-.5.6V97h.5l-.5.6zm6 .5h-5.5v-1h5.5l.5.6zm0-1h.5v.6zm-.5 22V97.6h1V119l-.5.5-.5-.5zm.5.5h-.5v-.5zm6.3 0H479v-1h6.3l.5.5zm.5-.5v.5h-.5zm0-21.4V119h-1V97.6l.5-.5.5.6zm-1 0V97h.5l-.5.6zm6 .5h-5.5v-1h5.5l.5.6zm0-1h.4v.6zm3 3v1h-1l-.3-.2h-.2l-.3-.2-.2-.2h-.2v-.2h-.2l-.2-.2v-.2h-.2v-.2l-.2-.2V99h-.2v-.2h-.2v-1l-.2-.2h1v.4l.2.2v.2l.2.3v.2h.2v.2h.2l.2.2v.2h.2l.3.2h.2v.2h1.1zm3.2-3v1l.5-.5v.7h-.2v.5l-.2.3v.4h-.2v.3h-.2v.2h-.2v.2h-.3v.2l-.3.2h-.3l-.2.2h-.3l-.2.2h-.8v-1h.9v-.2h.2v-.2h.2l.2-.2.2-.2V99h.2v-.2l.2-.2v-.2l.2-.2v-.6l.6-.6zm-.5.5V97h.5l-.5.5zm3-3h1v.9l-.2.3v.2h-.2v.6h-.2l-.2.2v.2h-.2v.2h-.3v.2h-.2v.2h-.3l-.3.2h-.2l-.3.2h-.7v-1h.8V97h.2v-.2h.2l.2-.2.2-.2h.2V96l.2-.3v-.2h.2v-.7l.2-.3zm-3-3.2h1l-.5-.6h.7l.2.2h.4l.3.2.3.2.3.2.2.2.2.3.2.2v.5h.2v.3h.2v.7l.2.3v.4h-1v-.6h-.2v-.4l-.2-.2-.2-.2v-.2h-.2l-.2-.2-.2-.2h-.2V92h-.5l-.2-.2h-.5l-.5-.5zm.5.5h-.5v-.5zm-3-3v-1h1.1l.2.2h.4v.2h.3v.2l.3.2.2.3h.2v.3h.2v.4l.2.2v.4h.2v.7h-1v-.8h-.2V90l-.2-.2-.2-.2-.2-.2-.2-.2h-.2V89h-.5v-.2h-.6zm-3.2 3v-1l-.6.5v-.9l.2-.2V90l.2-.3.2-.3.2-.3h.2v-.2l.2-.2h.2v-.2h.2l.2-.2h.3l.2-.2h.3l.2-.2h1v1h-1l-.3.2h-.2v.2h-.2l-.2.2v.2l-.2.2-.2.2v.2l-.2.2v.7l-.7.6zm.5-.5v.5h-.6zm-6-.5h5.5v1h-5.5l-.5-.5zm0 1h-.5v-.5zm.5-7v6.5h-1V85l.4-.6.6.5zm-1 0v-.4h.4zm.5 0v-.4zm-.5 0v-.4h.4z"/>
|
||||||
|
<path fill="#21231e" d="M499.8 122.4h-1v-1.7l-.2-1v-.7l-.3-.8-.2-.8-.2-.8-.3-.8-.3-.7-.4-.6-.4-.7-.4-.7-.5-.7-.5-.6-.5-.6-.6-.5-.7-.6-.6-.6-.6-.5-.5-.6-.7-.4-.7-.4-.6-.4-.8-.3-.7-.4-1-.3-.7-.2h-.8l-.8-.2h-.8l-1-.2h-1v-1h2l.8.2h1l.8.3.8.4.8.2.8.4.8.3.8.4.7.4.7.6.7.5.7.6.6.6.6.6.6.6.6.6.5.7.5.7.5.7.4 1 .4.6.4.8.3.8.3 1 .3.7.2.8v1l.2.7v2zM482 140.2v-1h1.8l1-.2.7-.2.8-.2.8-.2 1-.3.6-.2.8-.3.7-.4.8-.3.7-.4.6-.5.7-.5.6-.5.6-.6.5-.5.5-.6.5-.6.5-.6.4-.7.4-.6.4-.8.3-.7.3-1 .3-.6.3-.8.2-.8v-1l.2-.7v-1.6h1v2.8l-.2.8-.2.8-.3 1-.3.7-.3.8-.3.8-.3 1-.4.6-.5.7-.5.7-.5.7-.6.6-.6.7-.6.6-.6.5-.7.6-.7.5-.7.6-.7.4-.8.4-.8.4-.8.3-.8.4-.8.2-1 .2-.8.2h-.8l-1 .2h-1zm-17.5-17.8h1v1.8l.2.7v.8l.3.8.2.8.3.7.3 1 .3.6.5.8.4.7.3.8.5.6.5.6.5.7.6.5.5.6.5.5.7.5.6.5.7.4.7.4.7.5.7.3.7.3.8.4.8.2.8.2.8.2h1.8l.7.2v1h-1.8l-.7-.2-1-.2-.8-.2-.8-.2-1-.3-.7-.2-.8-.4-.8-.4-.7-.4-.7-.5-.7-.4-.7-.5-.7-.4-.7-.6-.5-.7-.5-.6-.5-.7-.5-.7-.5-.7-.4-.7-.4-1-.3-.7-.5-.8-.2-.8-.3-1-.2-.7v-1l-.2-.8v-1.8zm17.6-17.8v1h-1.7l-.7.2h-.8l-.8.3-.8.3-.8.3-.7.3-.8.2-.8.4-.7.4-.7.4-.6.5-.7.4-.6.5-.6.5-.6.6-.5.7-.5.6-.5.7-.4.7-.5.7-.4.7-.2.8-.3.8-.3.8-.2.8-.2.8v.8l-.2 1v1.7h-1v-2.8l.2-1 .2-.8.3-1 .2-.8.4-.8.2-.8.4-.7.4-1 .5-.6.5-.7.5-.7.6-.6.6-.6.7-.6.6-.6.6-.5.7-.4.7-.5.7-.3.8-.4.8-.3.8-.4 1-.2.7-.3h1l.8-.2h.8l1-.2h.8z"/>
|
||||||
|
<path fill="#edb92e" d="M499 118.8a18 18 0 0 1 0 6q-8.3-.3-17-.3-8.3 0-17 .3a18 18 0 0 1 .3-6l16.8-.3q8.6 0 17 .3z"/>
|
||||||
|
<path fill="#21231e" d="M499.8 122.4h-1V120h-.2v-1l1-.2v1.7l.2.2v1.6zm-.7 3 .2-1-.6.3v-.8l.2-.2v-1.1h1v2.2h-.2zm.6-.5v.3h-.5l.6-.4zm-17.5 0v-1H494l.4.2h4.8v1h-10.6l-.5-.2h-6zm-17.4 0 1-.3-.5-.4h4.7l.4-.2H482v1h-11l-.5.2H465l-.5-.3zm.6.3h-.5v-.4l.5.3zm-.7-3h1v2.4l-1 .3v-2.6zm.8-4v1l.5-.3v.8l-.2.2v2.5h-1v-3h.2v-.4l.5-.5zm-.5.5v-.4h.5zm17.3-.8v1h-9.1l-.5.2H466l-.7.2v-1h2.5l.5-.2h9l.5-.2h4.2zm17.5.8-1 .2.4.4h-3.6l-.5-.2h-10.3l-.5-.2h-2.1v-1h10l.5.2h6.1l.5.6zm-.5-.5h.4v.5z"/>
|
||||||
|
<path fill="#edb92e" d="M468.8 133.2c-3.6-2.3-4.3 1.2-4.2 4.7.4 13.2 3 25.8-.6 42-1.4 6.4-3.7 10-1.5 11.3q8.6 5 7.4 7.7c1.5 3.2 5.7 2.6 12 1.6 6.6 1 10.7 1.6 12.4-1.6-1-1.6 1.7-4.5 7.4-7.7 2.2-1.2 0-5-1.5-11.3-3.5-16.2-1-28.8-.7-42 0-3.6-.5-7-4-4.8a29 29 0 0 1-26.8 0z"/>
|
||||||
|
<path fill="#21231e" d="M465 138h-1v-1.5l.2-.3v-1.6l.2-.3v-.3h.2v-.4l.2-.2v-.3h.2l.2-.3.2-.2.2-.2h.2l.2-.2h1.8l.4.2.4.2h.3l.4.3-.4 1-.4-.3h-.2l-.3-.2h-.3l-.2-.2h-1.2v.2h-.2v.2l-.2.2v.2l-.2.2v.4l-.2.3v.4l-.2.2v2.5zm-.5 42-1-.2.3-1.5.3-1.4.3-1.6.2-1.4.2-1.5.2-1.4v-2.5l.2-1.4V154l-.2-1.2v-2.5l-.2-1.2V148l-.2-1.2v-1.2l-.2-1.3V142l-.2-1.3V138h1v3.5l.2 1.2v2.6l.2 1.2v1.3l.2 1.2v2.5l.2 1.2v1.3l.2 1.3v13.3l-.2 1.4v1.4l-.3 1.5-.2 1.4-.2 1.6-.2 1.4-.2 1.5-.3 1.6zm-1.8 11-.4.8-.3-.2-.2-.2-.2-.2-.2-.2v-.2l-.2-.3V188l.2-.3v-.4l.2-.3v-.4l.2-.4v-.5l.3-.4.2-.4v-.5l.3-.3v-.5l.3-.5v-.6l.3-.4.2-.6v-.6l.3-.6 1 .3-.2.7v.6l-.3.5v.6l-.2.5-.2.4v.6l-.3.4v.5l-.3.4v.4l-.2.4v.4l-.2.4v.4l-.2.3v.4l-.2.4v1.9zl.2.2zm7.6 7.8-1 .4v-.4h.2v-1l-.2-.2v-.2l-.2-.2v-.4l-.3-.2-.2-.2-.2-.3h-.3l-.2-.4-.3-.2-.3-.3h-.3l-.4-.4-.4-.3-.3-.2-.5-.2-.4-.3-.3-.3-.5-.3-.5-.3-.4-.3-.5-.3-.6-.2.5-1 .6.4.5.3.5.3.4.3.5.4.4.3.5.3.5.2.3.3.4.3.4.3.3.4.2.2.3.3.3.2.2.2.2.3.3.2.2.2v.2l.3.3v.2l.3.2v.5l.2.2v1.1l-.2.3v-.5zm-1 .4v-.4zm13 1-.2 1h.2-1.2l-.5.2h-1.2l-.6.2h-1l-.5.2h-4.7l-.4-.2h-.3l-.3-.2-.3-.2-.3-.2-.2-.2-.3-.2-.2-.2-.3-.3h-.2l-.2-.4-.2-.4 1-.4v.2l.2.3.2.2.2.2.2.2h.2l.2.2h.2l.3.2h.3l.3.2h.3l.2.2h4.3l.5-.2h1l.6-.2h1.1l.5-.2h.8zm-.2 0h.2zm12-1 1-.4v.4l-.3.4-.2.3h-.2l-.3.4-.2.2-.3.2-.3.2-.3.2-.4.2h-.3l-.3.2h-.4l-.4.2H486l-.6-.2h-.5l-.6-.2h-1.2l-.5-.2h-.6l.2-1h1.8l.5.2h.6l.6.2h1.1l.5.2h4.2l.3-.2h.3l.2-.2h.3l.2-.2.2-.2.3-.3.2-.2.2-.2v-.3l.3-.2v.5zm1-.4v.4zm6.5-8 .5 1-.5.3-.5.4-.5.3-.4.2-.4.2-.4.3-.4.3-.4.3-.4.3-.3.3-.3.3h-.3l-.2.4-.3.2-.3.3h-.2l-.2.4-.2.2-.2.2v.2l-.3.2v.7h-.2v1.2l-.8.5v-.2l-.2-.2v-1.3l.2-.2v-.3l.3-.3v-.3l.3-.2.2-.3.2-.3.3-.3.2-.2.3-.3.3-.4.4-.2.3-.3.4-.2.4-.3.4-.3.4-.2.4-.3.5-.3.5-.3.5-.2.5-.3.5-.3.5-.3zm-1.7-10.7 1-.2v.6l.3.6.2.6.2.5v.7l.3.5v.5l.2.4v.6l.2.5.2.5v.4l.2.3v.4l.2.4v.5l.2.3v.7l.2.3v1.8l-.2.3v.2l-.3.3h-.2l-.2.3-.3.2-.5-1h.2l.2-.2v-.4h.2v-2.5l-.2-.3v-.3l-.2-.3v-.4l-.2-.4v-.4l-.2-.4v-.4l-.3-.5v-.6l-.2-.4v-.6l-.3-.5v-.6l-.3-.6v-.5l-.2-.6-.2-.6zm-.7-42h1v5.9l-.2 1.4v1.2l-.2 1.2v1.3l-.2 1.3v2.5l-.2 1.2v2.6l-.2 1.2v9.3l.2 1.4v1.4l.2 1.3.2 1.5.2 1.5.2 1.4.2 1.5.3 1.3.3 1.5-1 .3-.3-1.5-.3-1.5-.2-1.4-.2-1.5-.2-1.3-.2-1.5v-1.4l-.2-1.4V167l-.2-1.3v-10.3l.2-1.3v-2.5l.2-1.3V149l.2-1.2v-1.3l.2-1.2V143l.2-1.2zm-3.3-4.4-.5-1h.4l.3-.2.2-.2h.3l.4-.2h1.2l.3.2h.2l.2.3v.2l.2.2h.2v.4l.2.2v.3h.2v.7l.2.3v1.5l.2.3v1.1h-1v-2.4l-.2-.3v-.8l-.2-.3v-.4l-.2-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-1.1l-.2.2h-.3l-.3.2-.3.2zm-27 0 .4-1 .8.5.7.5.7.4.7.2.7.3.8.2.8.3 1 .2.7.2 1 .2h.7l1 .2h.8l1 .2h4.6l.7-.2h1l.8-.3h.8l1-.3.7-.2.8-.3.8-.2.8-.3.7-.4.8-.3.7-.4.7-.3.5 1-.7.3-.7.4-.8.4-.7.3-1 .4-.7.3-.7.3-1 .2-.7.2-1 .2h-.8l-1 .2h-1l-.8.2h-4.6l-1-.2h-.8l-1-.2-.8-.2-1-.2-.7-.2-.8-.3-.8-.3-.8-.3-.8-.2-.8-.4-.7-.4-.7-.4z"/>
|
||||||
|
<g id="rs-b">
|
||||||
|
<path fill="#edb92e" d="M363 212c-2-4-.3-9.8.3-11 .3-.8-.6-2.2-1-2.8-13-18.3-13.2-20-4.3-26.8 8-6 26.2-12 39.2-16.2 2.7-2 4.4-5 6.8-7.2-25 1.8-52 12-65.5 23.8-7.2 6.2 1.5 19 12.2 38 6.7 5 9.8 7 12.3 2.3z"/>
|
||||||
|
<path fill="#21231e" d="m362.8 200.7 1 .4v.2l-.2.2v.3l-.2.3v.5l-.2.2v.6l-.2.4v.7l-.2.3v1.6l-.2.4v2.5l.2.4v.4l.2.4v.4l.2.4v.3l.3.4-1 .3v-.4l-.3-.5v-.4l-.2-.4v-.4l-.2-.5v-.8l-.2-.6v-2.9l.2-.4v-1.2l.2-.3v-.4l.2-.2v-.6l.2-.3v-.6l.2-.2v-.2l.2-.2zm-1-2.2 1-.6v.2h.2v.4h.2v.4h.2v.2l.2.2v.4h.2v1.2l-1-.3v-.6l-.2-.2v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2zm-4-27.5.5 1-.8.5-.7.6-.7.6-.5.6-.6.5-.6.5-.5.6-.5.5-.3.4-.3.5-.3.5v.5l-.3.5v1.7l.2.6.2.8.2.7.4.8.4.8.4 1 .5 1 .6 1 .7 1 .7 1.3 1 1.2.7 1.3 1 1.4 1 1.5 1 1.5 1.3 1.7-.8.5-1.3-1.7-1-1.5-1-1.5-1-1.4-1-1.3-.8-1.2-.8-1.2-.7-1-.6-1-.7-1-.4-1-.6-1-.3-.7-.2-1-.2-.7v-.7l-.2-.7v-1.4l.2-.6.2-.6.2-.6.3-.6.4-.6.5-.5.5-.5.5-.5.6-.6.7-.5.7-.7.7-.6.7-.7zm39-16.2.7.8h-.2l-1.2.5-1.2.5-1.2.4-1.4.3-1.3.4-1.4.5-1.4.4-1.3.5-1.5.5-1.4.5-1.3.5-1.3.5-1.4.5-1.4.5-1.3.5-1.4.5-1.4.5-1.3.6-1.3.6-1.3.5-1.2.7-1.2.5-1.2.6-1 .5-1.2.6-1 .4-1 .6-1 .6-1 .5-1 .7-.7.6-.7.6-.6-1 .8-.5 1-.6.8-.7 1-.6 1-.6 1-.4 1-.6 1.2-.6 1.2-.5 1-.7 1.4-.6 1.3-.5 1.3-.6 1.3-.5 1.2-.4 1.4-.5 1.3-.5 1.3-.5 1.4-.5 1.3-.5 1.4-.5 1.4-.5 1.4-.4 1.4-.5 1.3-.4 1.3-.4 1.3-.4 1.3-.5 1.2-.4 1.3-.5 1.3-.4 1.2-.3zm.7.8h-.2zm6.6-7v-1l.4.8-.2.2-.3.2-.3.3-.2.3-.2.2-.2.2v.3l-.3.2-.2.2-.2.2-.2.3-.3.3-.2.3-.2.2-.2.3v.3l-.3.2-.2.3-.2.2-.3.2-.2.3-.2.2-.2.2h-.2l-.3.4-.3.2-.2.2-.3.2-.2.2-.3.2-.2.2-.6-.8v-.2l.3-.2.3-.2.2-.2.2-.2.2-.2.3-.2.2-.2.2-.2v-.2l.3-.3.2-.2.2-.2.2-.2v-.2l.3-.2.2-.2.2-.3.2-.3v-.3l.3-.2.2-.2.2-.3.2-.2v-.2l.4-.3.2-.2.2-.2.2-.3.2-.2h.3l.2-.3.4.8zm0-1 1.4-.2-1 1-.4-1zM338.8 172l-.7-.8 1.5-1 1.4-1.2 1.4-1 1.5-1.2 1.5-1 1.7-1 1.7-1 1.7-1 2-1 1.8-1 2-1 2-1 2-.8 2-1 2.2-.7 2-.8 2.3-.8 2.3-1 2.2-.6 2.3-.7 2.3-.6 2.4-.5 2.3-.6 2.4-.7 2.4-.5 2.4-.4 2.4-.4 2.4-.4 2.3-.3 2.3-.3 2.4-.3 2.3-.2v1l-2.3.2-2.3.3-2.3.3-2.3.3-2.4.4-2.3.4-2.4.4-2.3.5-2.3.6-2.3.5-2.3.6-2.3.7-2.2.7-2.2.7-2.2.8-2.2.8-2.2.8-2 1-2 .7-2.2 1-2 1-2 1-1.8.8-1.8 1-1.8 1-1.7 1-1.6 1-1.5 1-1.6 1-1.5 1.2-1.4 1-1.2 1.2zm12.2 37.3-.5.8-.2-.2-1-1.6-1-1.8-1-1.7-1-1.7-.8-1.7-1-1.6-.8-1.5-.8-1.5-1-1.5-.7-1.5-.8-1.4-.7-1.3-.7-1.4-.5-1.3-.6-1.3-.6-1.2-.4-1.2-.5-1.2-.3-1-.3-1-.4-1.2v-1l-.2-1v-2.8l.3-.8.3-.8.4-.7.5-.8.5-.7.6-.6.8.8-.6.5-.5.6-.4.7-.3.6-.3.7-.2.8v4.5l.4 1v1l.5 1 .4 1 .4 1.3.5 1.2.4 1.2.6 1.3.7 1.3.7 1.4.7 1.4 1 1.4.7 1.5 1 1.5.8 1.5 1 1.6.8 1.7 1 1.7 1 1.7 1 2 1 1.6h-.3zm-.5.8-.2-.2.2.3zm12.5 1.3v1l.5-.2-.3.4-.2.4-.3.4-.3.3-.3.2-.2.3-.3.2-.3.2h-.3l-.5.2h-2.1l-.4-.2-.5-.2-.4-.2-.3-.2-.5-.2-.4-.3-.5-.3-.5-.3-.4-.4-.6-.3-.5-.3-.5-.4-.6-.4-.5-.4-.6-.4-.5-.5.5-.8.7.4.5.3.6.5.6.3.5.5.4.4.5.2.4.3.4.3.5.3.4.3.4.2.5.2.4.2h.5l.3.2h1.5l.2-.2h.3l.2-.3.2-.2.3-.2.2-.3v-.3l.4-.4.2-.4.4-.4zm-.4.3v-.4h.4zm.4.6v-1l.5.3zm.5-.7.3.6h-.8z"/>
|
||||||
|
<path fill="#edb92e" d="m359 213.6-1-4.7q-1-4.3.5-8.3c-3-4.2-6.4-8.8-8.8-13.3-1.7-3.3-3-7-2-10.6 1-4 4.5-6.6 7.6-9 3.7-2.8 8.6-5.3 13.6-7.5q16.6-7 26.5-7l-4.2 3.8c-12 4-26.4 9.2-33.3 14.5-9 6.8-8.6 8.4 4.3 26.7.4.6 1.3 2 1 2.7-.6 1.2-1.8 7.2 0 11.4-1 2-2.6 1-4.2 1.2z"/>
|
||||||
|
<path fill="#21231e" d="m357.4 209 1-.2v.7l.2.2v.7h.2v.8l.2.3v.3l.2.3v.7h.2v.6h.2l-1 .4v-.3l-.2-.3v-.2l-.2-.3v-.5l-.2-.3v-.6h-.2v-.5l-.2-.3v-.7l-.2-.3v-.2zm.6-8 1-.6v.5l-.2.4v.2l-.2.2v1l-.2.2v.8l-.2.2v4.7l.2.3-1 .2v-.6l-.2-.2V203h.2v-1l.2-.2v-.5l.2-.2v-.2l.2-.3v.5zm1-.6v.5zm-9.7-12.8 1-.5v.6l.3.4.3.4v.4l.4.5.2.4.3.4v.5l.4.4.3.4.2.4.2.4.3.4.2.4.3.5.3.4.3.3.3.4.2.4.2.5.3.5.3.4.3.4.3.4.3.4.2.4.3.4.2.4.3.4.3.4.3.4.3.4-1 .6-.2-.4-.3-.4-.3-.4-.3-.4-.4-.4-.3-.4-.2-.4-.2-.4-.3-.4-.3-.4-.3-.5-.4-.4-.3-.3-.3-.4-.2-.4-.3-.5-.3-.5-.3-.4-.4-.4-.3-.4-.2-.5-.2-.4-.3-.5-.2-.4-.3-.4-.3-.4-.2-.4-.3-.5-.2-.4-.2-.5-.3-.5-.2-.4zm-2-11 1 .4-.2.3v4.1l.2.4v.3l.2.3v.7l.3.3v.4l.2.4v.6l.3.3v.3l.3.3v.2l.3.3v.3l.2.3-1 .6v-.3l-.2-.3-.2-.3v-.3l-.3-.4v-.3l-.3-.3v-.3l-.3-.3-.2-.3v-.3l-.2-.2v-.4l-.2-.3v-.3l-.2-.3v-.7l-.2-.4V180l-.2-.4V178l.2-.2v-.7l.2-.3zm7.7-9.2.6.8-.3.2-.3.3-.3.2h-.3l-.3.3-.2.3-.2.2-.3.3-.3.3-.3.2-.3.3-.2.2-.3.3-.4.3-.2.2-.3.3v.3l-.4.3-.2.3-.2.2-.3.3-.3.3-.2.3-.2.4v.3l-.3.3-.2.3v.3l-.3.3v.3l-.2.3v.4l-1-.3v-.4l.2-.4v-.4l.2-.4.2-.4v-.3l.3-.4.2-.2.2-.3.2-.3v-.3l.4-.5.2-.3.2-.2.3-.3.4-.3.2-.3.3-.2.2-.2.3-.3.2-.3.3-.2.3-.3.3-.2.3-.3.2-.2.3-.2.3-.3.3-.2h.4l.3-.4.3-.2zm13.7-7.5.4.8-.4.2-.4.2-.5.2-.5.2-.4.2-.5.2-.4.3-.6.2-.5.2-.5.2h-.5l-.4.3-.5.3-.3.2-.4.2-.5.3-.4.2-.4.2-.4.3-.5.2-.3.2-.4.3-.4.2-.5.2-.4.2-.2.3-.4.2-.4.2-.4.2-.3.3-.4.3-.4.2-.6-.8.3-.3h.4l.4-.4.4-.3.4-.2.4-.2.4-.2.4-.3.4-.2.4-.2.4-.2.4-.3.5-.2.5-.2.4-.2.4-.2.4-.3h.4l.5-.3.4-.3.6-.2.4-.2.5-.2.5-.3.4-.2.5-.2h.4l.4-.3.5-.2.5-.2.4-.2h.5zm27-6.4-.6-.7.5.7H394l-.6.2h-1.8l-.7.2h-.8l-.7.2h-.7l-.8.3-.8.2h-.7l-.8.2-.8.2-1 .2-.8.2-.8.3-1 .3-.8.3-1 .3-.8.2-1 .3-.8.3-1 .3-1 .5-1 .3-.8.4-1 .4-1 .4-1 .4-1 .4-.5-1 1-.4 1-.4 1-.4 1-.4 1-.3 1-.5 1-.3 1-.4 1-.3.8-.3 1-.3.8-.4 1-.2.8-.3h1l.8-.4.8-.2.8-.2.8-.2h.8l.8-.3.8-.2h.7l.7-.2h.8l.7-.2h1.4l.5-.2h1.9l.4.8zm-.3-1h1.4l-1 1zm-4.4 4 4-3.7.8.6-4 3.8-.3.2zm.7.8-.2.2zM358.3 172l-.6-1 .7-.5.7-.5 1-.5.7-.5.8-.5 1-.5.8-.4 1-.5 1-.4.8-.5 1.2-.4 1-.5 1-.3 1-.5 1.2-.5 1-.4 1.3-.5 1-.4 1.2-.5 1-.5 1.3-.5 1-.4 1.3-.4 1.2-.5 1.2-.4 1.2-.4 1.2-.5 1-.4 1.3-.3 1.2-.4 1-.3 1.2-.4.3 1-1.2.3-1 .3-1.3.4-1 .4-1.3.5-1.2.4-1.2.3-1.2.4-1 .4-1.2.6-1.2.4-1 .4-1.3.5-1 .3-1.3.5-1 .4-1.2.5-1 .4-1 .6-1.2.5-1 .5-1 .5-1 .5-1 .5-.8.5-1 .4-.8.4-.8.5-.8.4-.7.5-.7.4zm4.4 26-.8.5-1.3-1.7-1-1.5-1-1.5-1-1.4-1-1.3-.8-1.2-.8-1.2-.7-1-.6-1-.7-1-.4-1-.5-1-.4-.7-.2-1-.2-.7v-.7l-.2-.7v-1.4l.2-.6.2-.6.2-.6.3-.6.4-.6.5-.5.5-.5.5-.5.6-.6.7-.5.7-.7.7-.6.7-.7.6 1-.8.5-.7.6-.7.6-.5.6-.6.5-.6.5-.5.6-.5.5-.3.4-.3.5-.3.5v.5l-.2.6v2.2l.3.8.3.7.4.8.4.8.4 1 .6 1 .5 1 .7 1 .7 1.3 1 1.2.7 1.3 1 1.4 1 1.5 1 1.5 1.3 1.7zm1 3-1-.3v-1h-.2v-.2l-.2-.2v-.2h-.2v-.4l.7-.7.2.2v.4h.2v.2l.2.2v.2l.2.2v.4h.2v1.4zm.2 11.6-1-.5v.6l-.2-.4-.2-.5v-.4l-.2-.4v-.6l-.2-.4v-.5l-.2-.4v-5.4l.2-.4v-.7l.2-.4v-.5l.2-.3v-.4l.2-.2 1 .3v.2l-.2.2v.6l-.2.2v.6l-.2.3v1.1l-.2.4v5.1l.2.4v.8l.3.4v.4l.2.4zv.4zm0-.4v.4zm-5 1v1zh.2l.2-.2h1.4v.2h1.5l.2-.2h.2v-.2h.2v-.2l.2-.2.2-.3 1 .6-.2.2-.2.2v.2l-.2.2h-.2v.2l-.3.2h-.4l-.2.2h-.3v.2h-3v-1zm0 1v-1l.6.2-.5.7zm.6-.8.2.7h-.7l.6-.6z"/>
|
||||||
|
<path fill="#edb92e" d="M468 132.2q.3-3.6-.3 1.6-.5-1-1.4-1-1.6-.1-1.8 5.2l.2 2.8c-33-.7-52.7 4.3-57.7 14-4.2 7.8 3 21 13.6 36 1.3 1.8 1.2 1.6 0 3-1.8 2.3-3.8 6-2.3 9.2a24 24 0 0 1-21 1.5c-1-3-3.4-4.4-5.4-5.8-.5-.2-2-1-2-1.4l-10.2-31.6c-9.5-30.7 75.3-33.5 88.3-33.5"/>
|
||||||
|
<path fill="#21231e" d="m467.3 134 1-.4-1 .2V132h.2v-1.2l.2-.2.6-.3.3.3v1.6h-1V131l.4.2h.6v-.2 1l-.2.2v1.8h-1zm1 0-.2 1.6-.7-1.5h1zm-2-.7v-1h.7v.2h.4v.2h.2v.2h.2v.2h.2v.2h.2v.2l-1 .5v-.2h-.2v-.2h-.2v-.2zM465 138h-1v-1.8l.2-.3v-.9l.2-.3v-.7h.2v-.3l.2-.3.2-.3.2-.2h.2v-.2h.2v-.2h.5v1h-.2v.2h-.2v.2l-.2.2v.3l-.2.3v.6l-.2.2v2l-.2.3v.4zm-.3 3.3v-1l-.5.5v-2.3h-.2v-.4l1-.2v1.8l.2.2v1zm.5-.5v.5h-.5zM407.4 155l-1-.5.7-1 .7-.8.7-1 .8-.7 1-.8 1-.8 1-.8 1-.7 1.3-.7 1.2-.7 1.4-.6 1.6-.6 1.5-.5 1.7-.7 1.7-.5 1.8-.4 2-.5 1.8-.5 2-.3 2.2-.4 2.2-.3 2.2-.2 2.4-.3 2.4-.2 2.5-.2h2.5l2.7-.2h2.7l2.8-.2h9v1H453l-2.8.2h-2.6l-2.6.2-2.5.2-2.4.2-2.3.3-2.2.2-2.2.3-2 .4-2 .3-2 .4-1.8.4-1.8.4-1.7.5-1.6.5-1.5.6-1.5.5-1.3.5-1.3.7-1.2.6-1 .7-1 .7-1 .7-1 .7-.6.8-.7.8-.6.8-.6 1zm13.6 35.5-.8.6-1-1.3-1-1.4-1-1.4-1-1.5-.8-1.3-.8-1.4-1-1.3-.7-1.3-.8-1.3-.8-1.4-.7-1.2-.7-1.3-.6-1-.6-1.3-.5-1.2-.5-1.2-.5-1.2-.4-1-.5-1-.3-1.2-.3-1-.3-1-.2-1.2v-1l-.2-1V158l.3-.8.3-1 .2-.7.3-.8 1 .5-.4.7-.2.7-.2.8-.2.8v4.5l.2 1 .3 1v1l.5 1 .3 1.2.4 1 .5 1.2.5 1 .5 1.2.6 1.2.6 1.2.6 1.3.7 1 .7 1.4.8 1.3.8 1.3.8 1.3 1 1.3.8 1.4 1 1.3.8 1.4 1 1.4 1 1.5zm0 3.8-.8-.7.2-.3.2-.2h.2v-.2l.2-.2v-.4l-.2-.2v-.2h-.2v-.3h-.2v-.3h-.2l.8-.7.2.3.2.3v.2h.2v.4h.2v.2l.2.2v1l-.2.2v.2h-.2v.2l-.2.2-.2.2-.2.3zm-2.5 9-.4-.8-.2.7v-.3l-.2-.5v-.3l-.2-.3v-3.5l.2-.3v-.3l.2-.4.2-.3v-.3l.2-.3.2-.3v-.3l.3-.2v-.3l.3-.2.2-.3v-.2l.3-.3.2-.2v-.3l.3-.2.2-.2.8.7-.2.2-.2.2v.2h-.3v.3l-.3.3-.2.2v.3l-.3.3v.3l-.2.2-.2.3v.7l-.2.2v.2l-.2.3v.6l-.2.4v2.7l.2.3.2.3-.3.6zm.3-.5v.4l-.3.2zm-22 1.8 1-.3-.2-.3.6.2.7.3.5.2.6.2h.8l.6.2.6.2h.7l.6.2h5.7l.7-.2h.5l.7-.3h.6l.7-.2.6-.2.7-.2.7-.2.6-.2.6-.3.6-.3.6-.2.7-.4.6-.3.5 1-.6.2-.7.4-.6.4-.7.2-.6.3-.7.2-.6.2-.6.2-.7.2-.6.2h-.6l-.7.3h-.7l-.6.2h-.7l-.7.2h-5l-.7-.2h-.7l-.6-.3h-.7l-.7-.3-.6-.3-.7-.3-.6-.2-.8-.3-.3-.4zm.4.3-.2-.2v-.2zm-5.6-5.8.6-1 .2.2.2.2h.2l.2.2.2.2h.2l.2.2v.2h.3v.3h.3l.2.3h.2l.2.3.3.3.2.2.2.2.2.2v.2l.3.2v.2l.3.2.2.2v.2l.3.3.2.2.2.2v.2l.3.3v.3l.2.3-1 .3-.2-.2v-.3h-.2v-.3l-.2-.3-.2-.2v-.2l-.2-.2-.2-.2v-.2l-.3-.2-.2-.2-.3-.3-.2-.2-.3-.3h-.2l-.2-.3v-.2l-.3-.2h-.2v-.3h-.3l-.2-.2h-.2l-.2-.2h-.2l-.2-.3h-.2zh-.2zm-2-1.7.8-.3h.2v.2h.2l.2.2h.2v.2h.4v.2h.2l.2.2h.2l-.6 1-.2-.2h-.2v-.2h-.4v-.2h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2zm-10.2-31.7 1-.3.2 1 .3 1 .2.8.3 1 .3 1 .3.8.4 1 .3 1 .3 1 .3 1 .3 1 .3.8.2 1 .4 1 .3 1 .2 1 .3 1 .3 1 .4 1 .3 1 .3 1 .3 1 .5 1 .3 1 .3 1 .5 1 .3 1 .3 1 .4 1.2.4 1 .3 1 .4 1.2-1 .3-.3-1-.2-1.2-.3-1-.4-1-.2-1-.3-1-.3-1.2-.4-1-.3-1-.3-1-.4-1-.3-1-.3-1-.3-1-.4-1-.4-1-.3-1-.4-1-.3-1-.3-.8-.3-1-.4-1-.3-1-.3-1-.3-.8-.3-1-.2-1-.3-1-.3-.8-.3-1-.4-.8-.2-1zm88.2-33.6h1l-.5.5h-5.4l-2.5.2h-2.8l-3 .2-3.3.2-3.5.2-3.7.3-3.8.3-4 .4-4 .5-4 .6-4.2.6-4 .7-4.2 1-4 .8-4 1-3.7 1-3.7 1.3-3.4 1.4-3.3 1.4-3 1.5-2.6 1.6-2.3 1.7-2 2-1.6 1.8-1.2 2.2-.8 2.2-.4 2.3v2.5l.7 2.7-1 .3-.6-2.8v-2.8l.3-2.6 1-2.4 1.2-2.2 1.8-2 2-2 2.5-2 2.7-1.6 3-1.5 3.3-1.6 3.5-1.3 3.6-1 4-1.2 3.8-1 4-1 4-.7 4.2-.6 4.2-.6 4-.6 4-.5 4-.4 4-.4 3.7-.4 3.5-.2 3.3-.2 3-.2h3l2.4-.2h5.4l-.6.4zm1 0v.5h-.5l.7-.5zm-.5 0h-.4zm.7 0v.5h-.6z"/>
|
||||||
|
<path fill="#fff" d="M405 190.2c3.5 0 6.4 2.8 6.4 6.4 0 3.5-3 6.4-6.4 6.4s-6.4-3-6.4-6.4c0-3.6 3-6.4 6.4-6.4"/>
|
||||||
|
<path fill="#21231e" d="M412 196.6h-1a6 6 0 0 0-3.4-5.3 6 6 0 0 0-2.6-.6v-1c2.3 0 4.6 1.2 5.8 3.2a7 7 0 0 1 1 3.6zm-7 7v-1c2.2 0 4.3-1.4 5.2-3.4a6 6 0 0 0 .7-2.6h1c0 2.3-1.4 4.6-3.4 6a7 7 0 0 1-3.6 1zm-7-7h1c0 2.2 1.4 4.3 3.4 5.3a6 6 0 0 0 2.6.5v1a7 7 0 0 1-5.8-3.3 7 7 0 0 1-1-3.6zm7-7v1a6 6 0 0 0-5.2 3.3 6 6 0 0 0-.7 2.6h-1c0-2.4 1.4-4.7 3.4-6a7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M350.8 203.6c3.2 0 6 2.7 6 6s-2.8 6-6 6-6-2.7-6-6 2.7-6 6-6"/>
|
||||||
|
<path fill="#21231e" d="M357.2 209.6h-1c0-2-1-4-2.8-4.8a5 5 0 0 0-2.6-.7v-1a7 7 0 0 1 5.4 3 7 7 0 0 1 1 3.6zm-6.4 6.4v-1c2 0 4-1.2 4.8-3a6 6 0 0 0 .6-2.4h1c0 2-1.2 4.3-3 5.4a6 6 0 0 1-3.4 1m-6.5-6.4h1c0 2 1.2 4 3 4.8q1.2.6 2.5.6v1a7 7 0 0 1-5.5-3 7 7 0 0 1-1-3.4m6.5-6.5v1c-2 0-4 1.3-5 3a6 6 0 0 0-.5 2.6h-1c0-2.2 1.2-4.4 3-5.5a6 6 0 0 1 3.5-1z"/>
|
||||||
|
<path fill="#fff" d="M345 192.7c3.4 0 6 2.7 6 6s-2.6 6-6 6c-3.2 0-5.8-2.7-5.8-6s2.6-6 6-6z"/>
|
||||||
|
<path fill="#21231e" d="M351.5 198.7h-1c0-2-1.2-4-3-5a5 5 0 0 0-2.4-.5v-1q3.6.1 5.5 3a7 7 0 0 1 1 3.5zM345 205v-1c2 0 4-1 4.8-2.7a6 6 0 0 0 .7-2.6h1c0 2.2-1.2 4.3-3 5.5a6 6 0 0 1-3.4 1zm-6.3-6.3h1c0 2 1.2 4 3 4.8a5 5 0 0 0 2.4.6v1c-2 .2-4.2-1-5.3-3a7 7 0 0 1-1-3.3zm6.4-6.5v1c-1.8 0-3.7 1-4.6 2.8a6 6 0 0 0-.7 2.7h-1c0-2.2 1.2-4.4 3-5.5a6 6 0 0 1 3.4-1z"/>
|
||||||
|
<path fill="#fff" d="M339.8 181.7c3.3 0 6 2.7 6 6s-2.7 6-6 6-6-2.7-6-6 2.7-6 6-6"/>
|
||||||
|
<path fill="#21231e" d="M346.2 187.7h-1c0-2-1.2-4-3-5a5 5 0 0 0-2.4-.5v-1c2.2 0 4.3 1.2 5.4 3a7 7 0 0 1 1 3.5m-6.4 6.4v-1q3.2 0 4.7-2.7.7-1.2.7-2.6h1c0 2-1 4.3-3 5.4a6 6 0 0 1-3.4 1zm-6.4-6.3h1c0 2 1 4 3 4.8q1 .6 2.4.6v1a6 6 0 0 1-5.4-3 7 7 0 0 1-1-3.3zm6.4-6.5v1c-2 0-4 1.2-4.8 3a6 6 0 0 0-.6 2.5h-1c0-2.2 1-4.4 3-5.5a6 6 0 0 1 3.4-1"/>
|
||||||
|
<path fill="#edb92e" d="M444.3 202.2c3.4-.3 2.5 5.8 2 7.2q-1.3 2.6-3 .2c-.8-1.3-2.4-7.2 1-7.5z"/>
|
||||||
|
<path fill="#21231e" d="m446.6 209.6-1-.4h.2v-.7l.2-.3v-.5l.2-.3V204h-.2v-.3l-.2-.2v-.2l-.2-.2h-.2l-.3-.2h-.2l-.2-.2h-.3v-1h.7l.3.2h.3l.3.2v.2l.3.3.2.2.2.3v.2l.2.3v.7l.2.3v3.7l-.2.3v.5l-.2.3v.5h-.2zm-3.7.3.8-.7.2.3v.2h.2v.2h.2v.2h.7l.2-.2v-.2h.2v-.2h.2v-.3l1 .3v.2l-.2.2v.3h-.2v.2l-.2.2-.2.2h-.4v.2H444v-.2h-.2v-.2h-.2v-.2h-.2v-.3h-.2v-.3h-.3zm1.2-8.4v1h-.4v.2h-.3v.2h-.3v.3l-.2.2v.4h-.2v3.2l.2.2v.6l.2.2v.3l.2.2v.2l.2.2v.2h.2l-1 .7v-.5l-.2-.2v-.2l-.2-.4v-.2l-.2-.3v-.2l-.2-.3v-.7l-.2-.3v-3l.2-.3v-.3l.3-.3.2-.2.2-.3h.2l.2-.3.3-.2h.4l.2-.2z"/>
|
||||||
|
<path fill="#fff" d="M444.5 205.5c8-.6 6 13.8 4.4 17q-2.8 6.3-6.7.5c-2-3-5.8-17 2.2-17.5z"/>
|
||||||
|
<path fill="#21231e" d="m449.4 222.7-1-.4.2-.3.2-.4v-.4l.3-.5.2-.5v-.6l.2-.6.2-.7V217l.2-.7v-5.1l-.3-.7v-.6l-.3-.7-.3-.6-.3-.5-.3-.5-.3-.4-.3-.4-.4-.4-.5-.2-.5-.2-.5-.2h-1.4v-1h1.5l.6.2.7.2.5.4.6.4.4.4.4.5.4.7.3.6v.7l.3.6.2.7v.7l.3.8v6.3l-.3.7v.7l-.2.6v.6l-.3.6v.5l-.3.5v.4l-.3.3zm-7.5.5.8-.5.2.3.2.3.3.3.2.3h.2v.4h.3l.2.3.2.2h.4l.2.2h1v-.2h.3l.2-.3v-.2l.2-.2.2-.2h.2l.2-.4v-.3l.3-.3.3-.3.2-.4 1 .4-.3.4-.2.5-.2.3-.2.4-.2.3-.2.3-.2.2-.2.2-.2.3-.3.2h-.2v.3h-.4l-.2.2H445l-.4-.2h-.2l-.3-.3v-.2l-.4-.2-.2-.2-.2-.2-.3-.2-.3-.3-.3-.4-.2-.4zm2.5-17.2v-1zh-.6l-.7.2-.5.2-.5.3-.4.3-.5.4-.3.4-.3.4-.3.6-.2.5v.6l-.2.5v.7l-.2.7v.6l.2.7v1.4l.2.7v.8l.3.7v.6l.3.7.2.6.2.5.2.6.2.7.2.5.2.4.2.4.2.4.2.3-1 .5v-.3l-.3-.5-.2-.4-.2-.4v-.6l-.4-.5-.2-.7-.2-.6-.2-.7-.2-.7v-.7l-.3-.6v-.8l-.2-.7V213l-.2-.7v-.8l.2-.7v-.6l.2-.7v-.7l.4-.6.2-.6.4-.6.4-.5.4-.5.5-.4.6-.3.7-.3.7-.2h.8v1zm0-1v1z"/>
|
||||||
|
<path fill="#edb92e" d="M379.7 210.8c3.3-1 4 5 3.6 6.6q-.5 2.9-2.7 1c-1-1.2-4-6.5-1-7.6z"/>
|
||||||
|
<path fill="#21231e" d="m383.8 217.5-1-.2v-2.8l-.2-.3v-.7l-.2-.3v-.3l-.2-.2v-.3l-.3-.2-.2-.2-.3-.3-.3-.3h-.3l-.3-.2h-.6l-.4-1h1.5l.2.2.3.2.2.2.3.2.2.3.2.2v.3l.3.3v.4l.2.3v.3l.2.3v.3l.2.4v.6l.2.3v2.5zm-3.5 1 .7-.6h.2v.2h.2l.2.2h.2v.2h.4l.2-.2v-.2h.2v-.6h.2v-.3l1 .2v.4h-.2v.4l-.2.3v.2h-.2l-.2.2-.2.2h-.2l-.2.2h-1v-.2h-.3l-.2-.2h-.2v-.2h-.2zv-.2zm-.7-8.2.3 1h-.3l-.2.2h-.2v.2l-.2.2v.2l-.2.2v1.8l.2.4v.3l.2.3v.2l.2.3.2.3v.3l.2.3.2.3v.2l.3.2v.2l.2.2.3.3.2.2-.7.6h-.2v-.4l-.3-.2v-.2l-.3-.2v-.3l-.3-.2v-.2l-.2-.3v-.3l-.3-.3v-.4l-.2-.3v-.3l-.3-.3v-.7l-.2-.3V212l.3-.2.2-.3.2-.3.3-.2.2-.2h.2l.4-.3z"/>
|
||||||
|
<path fill="#fff" d="M380.7 214c7.7-2.5 9.2 12 8.4 15.5q-1.3 6.6-6.2 2c-2.6-2.4-9.7-15-2-17.5z"/>
|
||||||
|
<path fill="#21231e" d="m389.6 229.6-1-.2v-.8l.2-.4v-2.9l-.2-.7V223l-.3-.7v-.7l-.3-.8-.2-.7-.2-.6-.2-.7-.3-.7-.2-.6-.3-.6-.3-.4-.4-.5-.3-.4-.4-.3-.4-.4-.4-.4-.5-.2h-.5l-.5-.2h-1.1l-.6.3-.4-1 .7-.2h2.2l.7.2.7.2.5.4.5.5.5.4.4.5.4.5.4.6.3.7.3.6.3.6.3.7.2.8.2.8.2.7v.8l.3.7v1.5l.2.7v4.4zm-7 2.3.5-1 .5.4.2.2.3.3h.3l.3.3h.2l.3.3h.3l.2.2h1.4l.2-.2.2-.2.2-.3.3-.3v-.6l.2-.2v-.3l.2-.4v-.2l.2-.4 1 .2v.4l-.2.4v.4l-.2.4-.2.3v.3l-.2.3-.2.4v.3l-.3.2h-.2v.3l-.3.2h-.2l-.2.3h-.8l-.3.2h-.5l-.3-.2h-.2l-.3-.2h-.4l-.3-.3-.3-.2h-.3l-.3-.3-.3-.3-.2-.2-.3-.3zm-2-18.5.3 1-.7.2-.6.3-.4.3-.4.4-.4.4-.3.5-.2.5v.5l-.2.6v2.5l.2.6.2.7.2.6.2.7.3.7v.7l.4.6.3.7.4.6.4.6.3.6.4.6.4.5.3.4.3.4.4.4.2.4.3.3-.5 1-.3-.4-.3-.3-.4-.4-.4-.5-.3-.5-.4-.5-.4-.6-.3-.7-.5-.6-.3-.6-.4-.6-.3-.6-.3-.7-.3-.7-.3-.7-.3-.7-.2-.8-.2-.8v-.7l-.2-.7V218l.2-.7.2-.6.2-.6.3-.5.4-.5.6-.5.6-.4.7-.2.6-.3zm0 .5v-.5z"/>
|
||||||
|
<path fill="#edb92e" d="M464.2 137.8c-17.2-1.6-35 .2-52.3 7-14.8 6-13.4 23.2 4.6 47q-2.6 4.6-2.7 11h4.8c-1.3-2.8 0-6.4 1.8-9 1-1.5 1.3-1.2 0-3-10.7-15-17.8-28.2-13.6-36 5-9.7 24.6-14.7 57.7-14q0-1.4-.2-2.7l-.3-.2z"/>
|
||||||
|
<path fill="#21231e" d="m412 145.4-.3-1 1.7-.6 1.6-.6 1.7-.6 1.6-.5 1.6-.4 1.6-.5 1.6-.4 1.7-.4 1.5-.4 1.7-.4 1.6-.4 1.7-.3 1.6-.3 1.8-.3h1.6l1.7-.3 1.7-.2 1.7-.2 1.6-.2h3.3l1.6-.2h11.5l1.6.2h1.6l1.6.3v1l-1.7-.2h-4.9l-1.6-.2h-9.7l-1.6.2H443l-1.5.3-1.7.2-1.6.2-1.6.2H435l-1.7.4-1.6.3-1.7.3-1.6.4-1.6.3-1.7.5-1.5.4-1.6.4-1.8.5-1.6.6-1.6.6-1.7.6-1.6.6-1.6.6zm5 46.7-1-.4.2.5-1.7-2-1.5-2.3-1.5-2-1.3-2.2-1.3-2-1.2-2-1-2-1-2-1-2-.7-1.7-.6-1.8-.5-1.8-.6-1.7-.4-1.6-.3-1.7-.2-1.6v-4.4l.2-1.3.3-1.3.4-1.2.5-1.2.5-1.2.7-1 .8-1 .8-1 1-.7 1-.8 1.3-.8 1.4-.6 1.3-.5.4 1-1.2.5-1.2.6-1 .7-1 .8-1 1-1 .8-.6 1-.7 1-.5 1-.5 1-.3 1.2-.3 1.2-.2 1.2v3.8l.2 1.5.3 1.7.3 1.6.5 1.6.6 1.6.7 1.8.8 1.8 1 2 .8 1.8 1 2 1.2 2 1.2 2 1.4 2 1.4 2.2 1.5 2 1.7 2.3zm0-.5.2.3-.2.3v-.5zm-3 11v1l-.6-.6v-2.5l.2-.4v-1l.2-.4v-.7l.2-.5v-.4l.2-.2v-.4l.2-.3v-.3l.2-.3v-.4l.2-.3v-.3l.2-.3v-.3l.3-.3v-.3l.2-.2.2-.3v-.3l.3-.3v-.4l1 .5v.4l-.3.2v.3l-.2.2-.2.3v.3l-.2.3v.4l-.2.3v.3l-.2.4v.3l-.2.3v.3l-.2.4v.7l-.2.3v.8l-.2.4v.8l-.2.4v1.4l-.2.4v.7l-.5-.6zm0 1h-.6v-.6l.5.5zm4.3-.4 1-.4-.5.8h-4.6v-1h4.6l-.5.7zm1-.4.3.7h-.8l.4-.7zm1-9 .7.6-.2.2v.3l-.2.2-.2.2v.2l-.3.3v.4l-.2.3-.2.2v.3l-.2.3v.2l-.2.3v.7l-.2.2v.5l-.2.3v2.2l.2.3v.4l.2.2-1 .5v-.5l-.2-.3v-.7l-.2-.3v-2.7l.2-.3v-.3l.2-.2v-.3l.2-.3v-.3l.2-.4v-.3l.2-.3.2-.3v-.2l.2-.3.2-.3v-.3l.3-.3v-.3h.3l.2-.3zm0-2.5.7-.5.2.3.2.3v.2h.2v.4h.2v.4l.2.2v.5l-.2.2v.2h-.2v.2l-.2.2v.2h-.2v.3l-.2.2-.8-.6v-.2l.2-.2.2-.2v-.2h.2v-.4h.2v-.2h-.2v-.2l-.2-.2v-.3h-.2v-.3h-.2zm-13.8-36.5 1 .5-.4.7-.2.7-.2.8-.2.8v4.5l.2 1 .3 1v1l.5 1 .3 1.2.4 1 .5 1.2.5 1 .5 1.2.6 1.2.6 1.2.6 1.3.7 1 .7 1.4.8 1.3.8 1.3.8 1.3 1 1.3.8 1.4 1 1.3.8 1.4 1 1.4 1 1.5-.8.6-1-1.3-1-1.4-1-1.4-1-1.5-.8-1.3-.8-1.4-1-1.3-.7-1.3-.8-1.3-.8-1.4-.7-1.2-.7-1.3-.6-1-.6-1.3-.5-1.2-.5-1.2-.5-1.2-.4-1-.5-1-.3-1.2-.3-1-.3-1-.2-1.2v-1l-.2-1V158l.3-.8.3-1 .2-.7.3-.8zm57.7-13.7h1l-.5.5h-14.5l-2.6.2H445l-2.5.3-2.4.2-2.3.3-2.2.2-2.2.3-2 .4-2 .3-2 .4-1.8.4-1.8.4-1.7.5-1.6.5-1.5.6-1.5.5-1.3.5-1.3.7-1.3.6-1 .7-1 .7-1 .7-.8.7-.8.8-.7.8-.6.8-.6 1-1-.5.7-1 .7-.8.7-1 .8-.7 1-.8 1-.8 1-.8 1-.7 1.2-.7 1.3-.7 1.4-.6 1.6-.6 1.5-.5 1.7-.7 1.7-.5 1.8-.4 2-.5 1.8-.5 2-.3 2.2-.4 2.2-.3 2.2-.2 2.4-.3 2.4-.2 2.5-.2h2.5l2.7-.2h2.7l2.8-.2h9l-.6.6zm1 0v.5h-.5zm-1-2.3.7-.7v1.9h.2v1h-1v-2.3l-.2-.2v-.4.3zm.7-.7v.3-.2zm-1.2-.4.8.7v-.6.2h.2v.2l-.7.7v-.4h-.2v-.6zm0 .7-.3-.2.3-.4zm.8 0-.8-.6h.4zm-.4-.7h1l-.6.8-.4-.7z"/>
|
||||||
|
<path fill="#fff" d="M380.6 142.7c3.4 0 6 2.7 6 6s-2.6 6.3-6 6.3-6.2-2.7-6.2-6.2a6 6 0 0 1 6.2-6z"/>
|
||||||
|
<path fill="#21231e" d="M387.2 148.8h-1c0-2-1.2-4-3-5a6 6 0 0 0-2.6-.6v-1c2.2 0 4.5 1.2 5.6 3a7 7 0 0 1 1 3.6m-6.6 6.7v-1c2 0 4-1.2 5-3a7 7 0 0 0 .6-2.7h1a7 7 0 0 1-3 5.7 7 7 0 0 1-3.6 1m-6.7-6.7h1c0 2 1.2 4.2 3 5a6 6 0 0 0 2.6.7v1a7 7 0 0 1-5.6-3 7 7 0 0 1-1-3.7m6.6-6.7v1.2c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.6h-1c0-2.2 1-4.4 3-5.6a7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M392.6 138.7c3.4 0 6.2 2.8 6.2 6.2 0 3.3-2.8 6-6.2 6a6 6 0 0 1-6-6c0-3.5 2.6-6.3 6-6.3z"/>
|
||||||
|
<path fill="#21231e" d="M399.3 145h-1c0-2.2-1.3-4.3-3.2-5.2a6 6 0 0 0-2.4-.6v-1a7 7 0 0 1 5.7 3 7 7 0 0 1 1 3.7zm-6.7 6.6v-1c2 0 4-1.3 5-3a6 6 0 0 0 .7-2.7h1c0 2.2-1.2 4.4-3.2 5.6a7 7 0 0 1-3.4 1zM386 145h1c0 2 1.2 4 3 5a6 6 0 0 0 2.6.6v1a7 7 0 0 1-5.6-3.2 7 7 0 0 1-1-3.5zm6.6-6.8v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.7h-1c0-2.4 1.2-4.7 3.2-5.8a7 7 0 0 1 3.4-1z"/>
|
||||||
|
<path fill="#edb92e" d="m394.2 144 2 2.4q-14.6 8.1-13 16.7.1 1.4 9.8 31.7 6.7 4.7 10 8.4c-.2.4.8 1.2.5 1.5.7 3-2.8 2.4-5 1.7-1.2-3-4.6-6-6.6-7.5-.5-.2-2-1-2-1.4l-10.2-31.6c-3-9.8 3.6-16.8 14.4-21.7z"/>
|
||||||
|
<path fill="#21231e" d="m396.3 147-.5-1v.7h-.2v-.2h-.2v-.2l-.2-.2h-.2v-.2l-.2-.2v-.2h-.2v-.2h-.2v-.2l-.2-.2-.2-.2v-.2h-.2l.8-.7.2.2h.2v.4h.2v.2h.2v.2l.2.2.2.2v.2h.2v.2h.2v.2l.2.2v1zm.2-1 .3.6-.5.3zm-12.7 17-1 .2v-3.9l.2-.6.2-.5.2-.6.2-.5.3-.5.2-.5.3-.6.4-.5.3-.6.4-.6.5-.5.4-.6.6-.5.5-.5.6-.5.6-.6.5-.5.7-.4.7-.5.6-.5.8-.5.8-.5 1-.5.7-.5.8-.5.5 1-1 .4-.7.5-.8.4-.8.5-.8.4-.7.5-.7.4-.6.5-.7.4-.6.5-.6.4-.5.5-.6.4-.5.6-.5.5-.4.5-.3.5-.3.5-.3.5-.2.5-.2.5-.2.5-.2.5-.2.5v.5l-.2.5v3.1zm9.5 31.3-.6.8-.2-.2-.6-1.8-.7-1.8-.5-1.8-.6-1.7-.5-1.6-.5-1.5-.5-1.5-.5-1.5-.4-1.4-.4-1.2-.4-1.3-.4-1.3-.4-1-.3-1.3-.4-1-.3-1-.3-1-.3-1-.3-.8-.2-.8-.2-.8-.2-.6-.2-.7v-.5l-.3-.5v-.5l-.2-.4v-.8h-.2v-.3h1v.3l.2.3v.4l.2.3v.5l.2.5.2.6.2.7.2.7.2.7.2 1 .3.7.2 1 .2 1 .3 1 .3 1 .4 1 .4 1.2.3 1.2.3 1.3.5 1.3.4 1.4.5 1.5.4 1.5.7 1.6.5 1.6.5 1.7.6 2 .6 1.7.5 1.8-.2-.2zm-.6.8-.2-.2.2.3zm10.7 8.4-.7-.6v.6l-.2-.2-.2-.2-.3-.3-.2-.2-.2-.2-.3-.3-.2-.2-.2-.2-.2-.3-.3-.2-.3-.2-.2-.2-.3-.3-.3-.3-.3-.2-.4-.3-.3-.2-.4-.3-.4-.3-.3-.2-.4-.3-.4-.3-.4-.3-.3-.3-.5-.3-.4-.2-.3-.3-.4-.2-.4-.3-.5-.3-.4-.3-.3-.3.6-.7.4.3.4.3h.5l.4.4.3.3.4.3.4.3.5.3.3.2.4.3.3.4.4.3.2.2.4.2.3.3.2.2.3.3.3.3.3.2.4.3.3.2.3.3.3.3.2.2.3.3.3.3.3.3.2.2.3.2.2.3.2.2.2.3v.6zm0-.6.3.3-.3.4v-.6zm.6 1.6-1 .2.2-.5v.2h-.2v.2-.2l-.2-.2v-.2h-.2v-1.2l.8.6v-.2h.2v.2l.2.2v.2l.2.2v.7l-.2.2.2-.6zm-1 .2v-.3l.2-.2-.2.6zm-5 1.8 1-.4-.4-.3h.4l.2.2h.7l.4.2h2.3v-.2h.2v-1.4l1-.2v1.6l-.2.3v.2l-.2.2h-.2l-.2.2h-.4l-.2.2h-3l-.3-.2h-.5l-.3-.2h-.5l-.3-.4zm.2.3h-.2v-.3zm-6.6-7.5.6-1 .2.3h.2l.2.3h.2l.3.3.2.3.2.2.2.2.2.2h.2l.2.3.3.2v.3l.4.2.2.3.2.2.3.3.3.2.2.3.3.3.2.2.2.3.2.4.2.3.2.3.2.2.2.3v.3l.3.3v.4l.3.3v.3l-1 .4v-.3l-.2-.4-.2-.3v-.2l-.3-.2v-.3l-.2-.2-.2-.3-.2-.3-.2-.2-.2-.3-.2-.2-.2-.3-.3-.2-.2-.3v-.2l-.3-.3-.3-.2-.2-.2-.2-.2-.3-.2-.2-.2-.2-.2h-.2l-.2-.3h-.2l-.2-.3h-.2l-.2-.3h-.2l-.2-.2zm.6-1 .3.3-.3-.2zm-2.7-.7 1-.3.2.2h.2v.2h.2v.2h.4v.2h.2l.2.2h.2l-.6 1-.2-.2h-.2v-.2h-.4v-.2h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2zm-10-31.7.8-.3.3 1 .3 1 .2.8.3 1 .3 1 .3.8.4 1 .3 1 .3 1 .3 1 .3 1 .3.8.2 1 .4 1 .3 1 .2 1 .3 1 .3 1 .4 1 .3 1 .3 1 .3 1 .5 1 .3 1 .3 1 .5 1 .3 1 .3 1 .4 1.2.4 1 .3 1 .4 1.2-1 .3-.3-1-.2-1.2-.3-1-.4-1-.2-1-.3-1-.3-1.2-.4-1-.3-1-.3-1-.4-1-.3-1-.3-1-.3-1-.4-1-.4-1-.3-1-.4-1-.3-1-.3-.8-.3-1-.4-1-.3-1-.3-1-.3-.8-.3-1-.2-1-.3-1-.3-.8-.3-1-.4-.8-.2-1zm15-22-.7.5h.6l-1 .6-1 .4-1 .5-.8.4-1 .5-.7.4-1 .5-.7.5-.8.5-.7.6-.7.6-.5.6-.7.6-.5.6-.6.7-.5.6-.4.7-.4.6-.4.7-.3.7-.3.7-.3.7v.8l-.2.7v.7l-.2.8v2.6l.3.8v1l.3.7-1 .3-.2-1v-.8l-.3-1V159l.2-1 .2-.7.2-.6.3-.8.4-.7.5-1 .4-.6.4-.7.6-.7.5-.7.6-.6.8-.6.6-.6.8-.6.7-.6.7-.5.8-.6 1-.5.7-.5 1-.5 1-.5.8-.5 1-.5 1-.5.6.2zm-.5-.3.4-.2.2.4z"/>
|
||||||
|
<path fill="#edb92e" d="m356 217-.4 2q-4.2.8-7.3.3 5.1-4.9 7.6-2.4z"/>
|
||||||
|
<path fill="#21231e" d="m355 218.8.4-2 1 .2-.3 2-.3.4-.6-.6zm1 0v.5h-.3zm-8 .2.7.7-.3-1 .2.2h4.1l.3-.2h1l.2-.2h.7l.2-.2h.3l.2 1h-.8l-.4.2h-.5l-.4.2h-1.3v.2h-3.7l-.4-1zm.2 1-1-.3.8-.7zm8.2-3-1-.2.2.5v-.2h-.6v-.2h-.6l-.2-.2H353v.2h-.6l-.2.3-.2.2h-.3l-.2.2-.3.2-.2.2-.3.2-.3.2-.2.2-.3.2-.4.3h-.3l-.3.4-.3.3-.7-.7.3-.3.3-.3.3-.3.2-.2.3-.2.3-.2.3-.3h.4l.3-.3h.3l.2-.3h.3l.3-.3h.6l.3-.2h.5l.4-.2h1.3l.2.2h.3l.2.2h.2l.2.2.3.3v.5zm0-.5v.5zm-.5.4h.4-.5zm.3-.5.2.2v.3z"/>
|
||||||
|
<path fill="#edb92e" d="M355.6 212.3c1.8.4 2 .7 2.7 1 1 0 2.4 0 3.3-1 4-2.2 4 2 2.5 5-1 2.4-.5 5 .6 7 1.2 2.2 1 3.2 0 4l-1.6 1q-1.6 1.8-2-.6c-3.8-1.6-5-5.3-6.2-8.6-1.7-4.7-7.8-5.8.8-7.7z"/>
|
||||||
|
<path fill="#21231e" d="M358.4 212.7v1h-.7v-.2h-.4l-.2-.2h-.4l-.2-.2h-.8l-.3-.2.2-1h.4l.2.2h.4l.2.2h.4v.2h.8v.2zm3-.7.5 1v-.2l-.2.2h-.3v.2h-.2l-.2.2h-.2l-.2.2h-2.1v-1h2.2l.2-.2h.2v-.2h.2zm3 5.6-.8-.4v-.3l.3-.4v-.5l.2-.2v-.6l.2-.3v-2.2h-.2v-.2h-.2v-.2h-1.2l-.3.2-.3.2-.3.2-.6-1 .4-.2.3-.2h.5l.3-.2h1.8l.2.2.2.2v.2l.2.3v.4l.2.3v2.4l-.2.3v.3l-.2.4v.3l-.2.3v.3zm.6 6.3-1 .4v-.4l-.2-.2v-.4l-.2-.2v-.2l-.2-.2v-.5l-.2-.2v-.8l-.2-.2v-2.3l.2-.2v-.3l.2-.3v-.3l.2-.2 1 .4-.2.2v.4l-.2.2v.8l-.2.2v2l.2.2v.5l.2.2v.4h.2v.2l.2.2v.4h.2zm0 4.6-.6-1h.2v-.2h.2v-1.6l-.2-.3v-.3l-.2-.2v-.2l-.2-.2v-.2l.8-.5v.2l.2.2v.2l.2.2v.2l.2.2v.2l.2.3v1.7l-.2.2v.2h-.2v.2h-.2v.2l-.2.2zm-1.6 1-.8-.7v-.2h.2l.2-.2h.2v-.2h.4v-.2h.2v-.2h.4v-.2h.2l.6.8h-.2v.2h-.4v.2h-.2l-.2.2h-.2v.2h-.2zm-.8-.7v-.2zm-1.8 0 .4-.8.3.4v.5h.2v.2h.4v-.2h.2v-.2h.2l.8.6-.2.2-.2.2-.3.2h-.2v.2h-1.1l-.2-.2v-.2l-.2-.2v-.4h-.2v-.7zm.4-.8h.3v.4zm-7-8 1-.3.2.2v.3l.2.3v.4l.2.3v.3l.3.3.2.4v.3l.2.3.2.3v.3l.3.3v.2l.2.3v.2l.3.3.2.3.2.2v.3l.3.2.2.3.2.2.2.2.2.2h.2l.3.3.3.2.3.2.3.2h.3l.2.2-.4 1-.3-.2-.4-.2-.2-.2-.4-.2-.3-.2-.2-.3-.3-.3-.2-.2-.3-.3-.3-.2-.2-.3-.2-.3-.2-.3-.3-.4-.2-.3-.2-.3-.2-.3v-.4l-.2-.3v-.3l-.3-.3v-.3l-.3-.2v-.3l-.2-.4v-.2l-.2-.3-.2-.3v-.3l-.2-.4v-.3zm1.4-8.4v1l-.6.2-.7.2h-.6l-.5.3h-.4l-.4.3h-.3v.3l-.2.2v.2h-.2v.2l.2.3.3.2.2.3.2.2.2.3.3.2v.3l.4.3.2.3.3.5.2.3.2.4.3.4.2.4v.5l.3.4-1 .3v-.4l-.3-.5-.2-.4-.2-.3-.3-.3-.2-.3v-.2l-.4-.3-.2-.3-.3-.3v-.4l-.3-.2-.3-.3-.3-.4v-.2l-.2-.3-.2-.4v-.6l.3-.3v-.2l.3-.3h.3l.4-.3.4-.3.5-.2h.4l.7-.3.8-.2zm0 1v-1zm0-1"/>
|
||||||
|
<path fill="#edb92e" d="M357 212.5c3.5 1.2 4-.7 6-.6-.8 5-1.7 7.7-7 5-3-1.6-6.7.7-7.4 2.3-.3.6-2-.6-2.6-2-2.6-5.8 6.7-6.4 11-4.8z"/>
|
||||||
|
<path fill="#21231e" d="m363.6 212-1-.2.5.6H362v.2h-.3l-.2.2h-.2l-.3.2h-.3v.2h-.7l-.2.2h-2l-.4-.2h-.3l-.3-.2.3-1h.3l.3.2h.4l.2.2h1.3l.2-.2h.2l.2-.2h.5l.2-.2h.2l.3-.2h.5l.2-.2h.4l.6.6zm-.5-.6h.7v.6zm-7.2 6 .4-1 .5.4.5.2h.4l.4.3h.3l.4.2h1.7l.2-.2.3-.3.2-.3v-.2l.2-.2.2-.3v-.3l.2-.3v-.3l.2-.2v-.4l.2-.4v-.4l.2-.4v-.9l.2-.5 1 .2v.5l-.2.5v.8l-.2.5v.4l-.2.4v.4l-.3.4v.3l-.2.4-.2.4v.2l-.3.3-.2.3-.2.2-.3.2-.2.2h-.2l-.3.2h-.3l-.3.2h-1.6l-.4-.2-.4-.2h-.4l-.4-.3-.5-.3-.5-.3zm-6.8 2-1-.3h.2v-.3h.3v-.3l.3-.2v-.2l.3-.2h.3l.2-.3.2-.2h.3l.2-.3h.3l.2-.3h.3l.3-.2h.3l.2-.2h.3l.4-.2h2.3l.3.2h.3l.3.2h.2l-.4 1h-.3l-.2-.2h-.5l-.3-.2h-1.8l-.3.2h-.3l-.3.2h-.3l-.2.3h-.2l-.3.2h-.2l-.2.2h-.2v.2l-.2.2h-.2v.3h-.2v.2h-.2v.2zm-3.5-2 1-.3v.4h.2v.4h.2v.2l.2.2h.2v.2l.2.2h.2v.2h.4v.2l.8.5v.2l-.3.2h-1v-.2h-.2l-.3-.2h-.2v-.2l-.2-.2-.2-.2h-.2v-.3h-.2v-.2h-.2v-.3h-.2v-.4h-.2zm11.8-5.4-.3 1-.4-.2h-.5l-.3-.2h-.5l-.5-.2h-5l-.5.2-.4.2h-.4l-.4.3-.4.3-.3.2v.2l-.4.2-.2.2v.3l-.3.2v1.6l.3.4.2.5-1 .5-.2-.5-.2-.6V215l.2-.6.2-.4.2-.4.3-.3.2-.4.3-.3.4-.3.4-.2.4-.2.5-.2h.5l.5-.3h1.1l.5-.2h3.3l.6.2h.5l.4.2.5.2h.3zm0 .5v-.5z"/>
|
||||||
|
<path fill="#fff" d="M458.8 124.5c3.4 0 6.2 2.7 6.2 6 0 3.5-2.8 6.3-6.2 6.3s-6-2.8-6-6.2a6 6 0 0 1 6-6z"/>
|
||||||
|
<path fill="#21231e" d="M465.5 130.6h-1c0-2-1.3-4.2-3.3-5a6 6 0 0 0-2.4-.6v-1a7 7 0 0 1 5.7 3 7 7 0 0 1 1 3.6m-6.7 6.7v-1c2 0 4-1.2 5-3a6 6 0 0 0 .6-2.7h1c0 2.3-1 4.5-3 5.7a7 7 0 0 1-3.6 1m-6.6-6.7h1c0 2 1.3 4.2 3.2 5a6 6 0 0 0 2.4.7v1a7 7 0 0 1-5.6-3.2 7 7 0 0 1-1-3.4zm6.6-6.6v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.6h-1c0-2.2 1.2-4.4 3.2-5.6a7 7 0 0 1 3.4-1"/>
|
||||||
|
<path fill="#fff" d="M446.3 124.5a6 6 0 0 1 6 6c0 3.5-2.6 6.3-6 6.3s-6-2.8-6-6.2 2.6-6 6-6z"/>
|
||||||
|
<path fill="#21231e" d="M453 130.6h-1c0-2-1.3-4-3.2-5a6 6 0 0 0-2.5-.6v-1c2.3 0 4.5 1.2 5.6 3a7 7 0 0 1 1 3.6zm-6.7 6.7v-1c2 0 4-1.3 5-3a7 7 0 0 0 .6-2.7h1c0 2.3-1.3 4.5-3.2 5.7a7 7 0 0 1-3.5 1zm-6.6-6.7h1c0 2 1.2 4.2 3 5a6 6 0 0 0 2.6.7v1a7 7 0 0 1-5.6-3.2 7 7 0 0 1-1-3.4zm6.6-6.6v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.6h-1a7 7 0 0 1 3-5.6 7 7 0 0 1 3.6-1"/>
|
||||||
|
<path fill="#fff" d="M433.8 125.5a6 6 0 0 1 6 6c0 3.5-2.6 6.3-6 6.3s-6-2.8-6-6.2 2.6-6 6-6z"/>
|
||||||
|
<path fill="#21231e" d="M440.4 131.6h-1c0-2-1.2-4-3-5a6 6 0 0 0-2.6-.6v-1a7 7 0 0 1 5.6 3 7 7 0 0 1 1 3.6m-6.6 6.7v-1c2 0 4-1.3 5-3a6 6 0 0 0 .6-2.7h1a7 7 0 0 1-3 5.7 7 7 0 0 1-3.6 1m-6.6-6.7h1c0 2.2 1.3 4.3 3.2 5.2a6 6 0 0 0 2.4.5v1a6 6 0 0 1-5.6-3.2 7 7 0 0 1-1-3.4zm6.6-6.6v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.6h-1c0-2.3 1.3-4.6 3.3-5.7a7 7 0 0 1 3.3-1z"/>
|
||||||
|
<path fill="#fff" d="M422.3 127.7c3.3 0 6 2.8 6 6.2 0 3.3-2.7 6-6 6s-6.2-2.7-6.2-6 3-6.3 6.3-6.3z"/>
|
||||||
|
<path fill="#21231e" d="M429 134h-1a6 6 0 0 0-3.3-5.2 6 6 0 0 0-2.4-.6v-1c2.2 0 4.4 1.2 5.6 3.2a7 7 0 0 1 1 3.5zm-6.7 6.5v-1c2 0 4-1.2 5-3q.6-1.5.6-2.6h1c0 2-1.3 4.3-3.3 5.5a7 7 0 0 1-3.4 1zm-6.7-6.6h1q.2 3.2 3 4.8a5 5 0 0 0 2.7.7v1a7 7 0 0 1-5.7-3 7 7 0 0 1-1-3.6zm6.7-6.8v1c-2 0-4.2 1.3-5 3.2a6 6 0 0 0-.7 2.5h-1q.1-3.9 3.3-6a7 7 0 0 1 3.3-.8z"/>
|
||||||
|
<path fill="#fff" d="M346.3 159.4a6 6 0 0 1 6 6.2c0 3.4-2.6 6.2-6 6.2s-6-2.8-6-6.2a6 6 0 0 1 6-6.2"/>
|
||||||
|
<path fill="#21231e" d="M353 165.6h-1c0-2-1.3-4-3.2-5a6 6 0 0 0-2.5-.7v-1c2.2 0 4.5 1 5.6 3a7 7 0 0 1 1 3.6zm-6.7 6.7v-1c2 0 4-1.2 5-3a7 7 0 0 0 .6-2.7h1c0 2.3-1.3 4.5-3.2 5.7a7 7 0 0 1-3.5 1zm-6.7-6.7h1c0 2 1.3 4.2 3.2 5a6 6 0 0 0 2.5.7v1a7 7 0 0 1-5.6-3 7 7 0 0 1-1-3.7zm6.7-6.7v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.6h-1a7 7 0 0 1 3-5.7 7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M356.8 152.8c3.4 0 6.2 2.7 6.2 6 0 3.6-2.8 6.3-6.2 6.3s-6-2.6-6-6a6 6 0 0 1 6-6.2z"/>
|
||||||
|
<path fill="#21231e" d="M363.5 159h-1c0-2.2-1.3-4.2-3-5a6 6 0 0 0-2.7-.7v-1a7 7 0 0 1 5.7 3 7 7 0 0 1 1 3.6zm-6.7 6.6v-1a5.7 5.7 0 0 0 5.7-5.7h1a7 7 0 0 1-3 5.6 7 7 0 0 1-3.7 1zm-6.6-6.7h1c0 2 1.2 4 3 5a6 6 0 0 0 2.6.6v1a7 7 0 0 1-5.6-3 7 7 0 0 1-1-3.7zm6.6-6.8v1c-2 0-4 1.3-5 3.2a6 6 0 0 0-.6 2.5h-1c0-2.3 1.2-4.6 3-5.8a7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M368.8 147c3.4 0 6 3 6 6.3 0 3.4-2.6 6.2-6 6.2s-6-2.8-6-6.2a6 6 0 0 1 6-6.2z"/>
|
||||||
|
<path fill="#21231e" d="M375.4 153.3h-1c0-2-1.2-4.2-3-5a6 6 0 0 0-2.6-.7v-1a7 7 0 0 1 5.6 3 7 7 0 0 1 1 3.7m-6.6 6.7v-1c2 0 4-1.3 5-3a6 6 0 0 0 .6-2.7h1a7 7 0 0 1-3 5.7 7 7 0 0 1-3.6 1m-6.7-6.7h1c0 2 1.4 4 3.2 5a6 6 0 0 0 2.6.7v1a7 7 0 0 1-5.7-3.2 7 7 0 0 1-1-3.5zm6.8-6.7v1c-2 0-4 1.2-5 3a6 6 0 0 0-.6 2.7h-1c0-2.3 1-4.5 3-5.7a7 7 0 0 1 3.6-1"/>
|
||||||
|
<path fill="#fff" d="M338.2 169.5a6 6 0 0 1 6 6.2c0 3.4-2.6 6.2-6 6.2s-6.2-3-6.2-6.3 2.8-6.2 6.2-6.2z"/>
|
||||||
|
<path fill="#21231e" d="M344.8 175.7h-1c0-2-1.3-4.2-3.2-5a6 6 0 0 0-2.4-.7v-1a7 7 0 0 1 5.6 3 7 7 0 0 1 1 3.7m-6.6 6.7v-1c2 0 4-1.3 5-3a6 6 0 0 0 .6-2.7h1c0 2.3-1.2 4.5-3.2 5.7a7 7 0 0 1-3.4 1m-6.7-6.7h1c0 2 1.2 4 3 5a6 6 0 0 0 2.7.7v1a7 7 0 0 1-5.7-3.2 7 7 0 0 1-1-3.5m6.7-6.7v1c-2 0-4 1.2-5 3a6 6 0 0 0-.7 2.7h-1c0-2.3 1.2-4.5 3.2-5.7a7 7 0 0 1 3.5-1"/>
|
||||||
|
<path fill="#edb92e" d="m408.8 279 22-12.8 23.4 10-22 13z"/>
|
||||||
|
<path fill="#21231e" d="m431 266.7-22 12.8-.4-1 22-12.7h.5v1zm-.4-1h.5-.4zm23.5 11-23.3-10 .4-1 23.4 10v1h-.3zm.4-1 1 .5-1 .5zm-22.4 13 22-13 .5 1-22 12.8h-.5zm.5.8-.2.2h-.3zm-23.4-11 23.4 10-.4 1-23.3-10v-1h.4zm-.3 1-1-.4 1-.4v1z"/>
|
||||||
|
<path fill="#c6363c" d="M431 270.6c5.8-.3 10.5 2.5 10.8 6.3.2 3.7-4.2 7-9.8 7.4-5.7.3-10.4-2.5-10.7-6.3-.2-3.6 4.2-7 9.8-7.4z"/>
|
||||||
|
<path fill="#21231e" d="M442.3 277h-1v-.7l-.2-.3v-.6l-.3-.3v-.2l-.3-.2-.2-.3-.2-.3-.3-.2-.3-.3-.2-.2-.3-.2h-.5l-.3-.3-.3-.2-.5-.2-.4-.2h-.3l-.4-.2h-.4l-.5-.3h-.9l-.5-.2h-.8l-.5-.2h-1l-.5.2v-1h3.6l.4.2.5.2h.5l.4.2.5.2.5.2.4.2.3.2.4.2.4.2.4.2.4.3.3.3.4.3.3.3.3.3.2.4.2.3.2.3.2.4v.3l.3.4.2.4v.8zm-10.3 8v-1l.4-.2h.9l.5-.2h.4l.4-.2h.5l.4-.3h.5l.4-.2.4-.2.4-.2.3-.2.4-.2.4-.3.4-.2.3-.2.3-.3.3-.2.3-.3v-.3l.3-.3.2-.4.2-.2v-.3l.2-.2v-.6l.2-.3v-1.1l1-.2v1.6l-.2.4v.3l-.3.5v.3l-.3.4-.3.3v.3l-.4.4-.3.3-.3.3-.3.2-.3.3-.4.3-.4.2-.4.3-.4.3-.4.2-.4.2-.5.2h-.4l-.4.2-.5.2h-.4l-.5.2h-.4l-.5.2h-1zm-11.2-6.8h1v.5l.2.3v.3l.2.3.2.3v.2l.3.3.2.2v.3l.4.2.2.3.3.2.2.2.3.2.4.2.2.2h.4l.4.3h.4l.4.2.4.2h.4l.5.2h1l.4.2h2.3v1H428l-.5-.2h-.4l-.5-.3h-.6l-.4-.3h-.4l-.5-.3-.3-.2-.3-.3-.4-.2-.4-.2-.3-.3-.3-.2-.3-.3-.3-.3-.3-.3v-.3l-.3-.3-.2-.3-.2-.4-.2-.3v-1l-.2-.4zm10.3-8 .2 1h-1.5l-.5.2h-.5l-.5.2-.4.2h-.5l-.4.3-.5.2-.4.2-.3.2-.4.2-.3.2-.3.3-.4.2-.3.3-.3.2-.2.3-.3.2-.2.3-.2.3-.2.4-.2.3v.3l-.3.3v.8l-.2.3v.6l-1 .2V277l.2-.4v-.3l.2-.4v-.5l.3-.3.2-.4.3-.3.2-.3.3-.3.2-.4.4-.3.4-.3.3-.3.4-.3.5-.2.3-.3.5-.3.4-.2.4-.2.6-.2h.4l.5-.2h.5l.5-.3h.5l.5-.2zl.5-.2z"/>
|
||||||
|
<path fill="#edb92e" d="m382.4 270 23.8 9-19.7 13c-5-1.6-9-19-4-22z"/>
|
||||||
|
<path fill="#21231e" d="m406 279.4-23.8-9 .4-1 23.7 9v1zm.3-1 1 .4-1 .5v-1zm-20 13.3 19.6-13.2.4.8-19.6 13.3h-.4zm.5 1h-.4zm-4.2-23.3-.4 1h.5l-.4.2-.4.3-.4.4-.2.5-.2.4-.2.7-.2.8v1.5l-.2.8v2.8l.3.8v1l.2.8.2 1 .2 1 .3.7v1l.5 1 .3.7.3.7.5.8.4.6.3.6.4.6.4.5.5.4.4.2.3.3.4.2-.3 1-.6-.3-.5-.3-.5-.3-.5-.5-.4-.6-.6-.6-.4-.7-.4-.7-.4-.7-.4-1-.3-.8-.3-1-.3-.8-.4-1-.2-.8-.2-1-.2-1v-.8l-.3-1v-5.2l.3-.8.2-.7.3-.6.3-.6.4-.5.4-.5.5-.4h.6zm-.5 0h.6-.5zm.4.4.2-.4zm-.3-.4h.6-.5z"/>
|
||||||
|
<path fill="#0c4076" d="M382.8 274.3c5.7-.6 10.5 2 11 5.8.3 3.8-4 7.4-9.6 8-3.6.3-6-3-6.3-6.3-.5-3.4 1-7 4.8-7.4z"/>
|
||||||
|
<path fill="#21231e" d="M394.2 280h-1v-.5l-.2-.3v-.3l-.2-.4-.2-.3-.2-.2-.2-.2-.2-.3-.2-.2-.3-.3-.2-.2-.3-.2-.3-.2-.4-.2-.3-.2-.4-.2-.3-.2h-.4l-.5-.3h-.4l-.4-.2h-.8l-.5-.2h-3.6l-.2-1h3.8l.4.2h.5l.4.2.4.2h.4l.5.3.4.2h.5l.4.3.2.2.4.3.3.2.2.3.3.3.3.3.2.2.3.3.2.3.2.3v.5l.3.4v.2l.2.4v.4zm-10 8.4v-1h1l.5-.2h.4l.6-.2.5-.2h.5l.4-.3.4-.2h.4l.4-.3.3-.3.4-.2.2-.2.4-.3.3-.2.2-.3.3-.2.2-.3.3-.3.2-.3.2-.2.2-.3v-.4l.3-.3v-.8l.2-.4v-1h1v1.6l-.2.3v.3l-.2.4-.2.3-.2.4-.2.3-.2.4-.3.4-.2.3-.3.3-.3.3-.5.3-.3.3h-.4l-.4.4-.4.2-.4.3h-.4l-.4.3-.5.2h-.5l-.4.3-.5.2h-.6l-.5.2h-.7l-.5.2h-.5zm-6.8-6.7h1v.7l.2.3v.3l.2.4v.3l.2.2v.2l.2.3.2.2v.3l.3.2.2.3v.2l.2.2h.2l.2.3h.2l.2.3.2.2h.3l.2.2h.2l.2.2h.2l.3.2h1.4v1h-2.1l-.3-.2h-.4l-.3-.3-.3-.2-.3-.2-.3-.2-.2-.2-.3-.2-.3-.2-.2-.2-.3-.3-.2-.2v-.2l-.3-.3-.2-.3v-.2l-.3-.4v-.3l-.3-.3-.2-.3v-.3l-.2-.3v-.6l-.2-.3v-.3zm5.4-8v1h-.2l-.4.2h-.8l-.3.3-.3.2-.3.2-.2.2h-.2l-.2.2v.2l-.3.2v.3h-.3v.3l-.2.3-.2.2v.7l-.2.3v.4l-.2.3v2.2h-1V279l.2-.3v-.3l.2-.2v-.3l.2-.3.2-.3v-.4l.3-.3.2-.3.2-.2v-.3l.3-.2.3-.2h.2l.3-.3.4-.2.3-.2h.3l.3-.2h.7l.4-.2h.4z"/>
|
||||||
|
<path fill="#fff" d="M457.5 280.4c1.7 0 3.2 1.5 3.2 3.2s-1.5 3.3-3.2 3.3-3.2-1.6-3.2-3.4 1.4-3.2 3.2-3.2z"/>
|
||||||
|
<path fill="#21231e" d="M461.2 283.6h-1v-.6l-.2-.2v-.2l-.2-.3v-.2h-.2v-.2h-.2v-.2h-.2l-.2-.2-.3-.2h-.2l-.2-.2h-.7v-1h1l.3.2.2.2h.2l.3.2v.2h.2v.2h.2v.3h.2v.2l.2.2v.2h.2v.2l.2.3v.5l.2.2v.2zm-3.7 3.8v-1h.4l.3-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2.2-.2v-.2l.2-.2v-1h1.2v.8l-.2.2v.3l-.2.2v.2l-.2.2-.2.3h-.2v.3h-.3l-.2.2-.3.2-.3.2h-.6v.2h-.6v.2h-.3zm-3.7-3.8h1v1h.2v.2l.2.2v.2l.2.2h.2v.2h.2v.2h.2l.2.2h.2v.2h1v1.2h-.7l-.2-.2h-.5v-.2h-.3l-.2-.2-.3-.2-.2-.2h-.2v-.3h-.2l-.2-.3-.2-.3v-.2l-.2-.3v-.3l-.2-.3zm3.7-3.7v1h-.9l-.2.2h-.2v.2h-.2v.2h-.2v.2h-.2v.2h-.2v.2l-.2.3v.5h-.2v.8h-1v-1.2l.2-.2v-.3h.2v-.3l.2-.2.2-.2v-.2h.4v-.2l.2-.2h.2v-.2h.5v-.2h.4v-.2h1z"/>
|
||||||
|
<path fill="#fff" d="M408.7 283c1.8 0 3.2 1.6 3.2 3.4s-1.5 3.2-3.3 3.2a3 3 0 0 1-3.2-3.2c0-1.8 1.4-3.3 3.2-3.3z"/>
|
||||||
|
<path fill="#21231e" d="M412.4 286.4h-1v-.9l-.2-.2v-.2l-.2-.2v-.2l-.2-.2-.2-.2h-.2v-.2h-.2v-.2h-.4l-.2-.2h-.8v-1.2h.7v.2h.7v.2h.3v.2h.3v.2h.3l.2.2.2.3h.2l.2.3.2.3v.3h.2v.6l.2.2v.9zm-3.7 3.7v-1h1v-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.3h.2v-.5l.2-.2v-.4h1v.8l-.2.2v.3l-.2.2v.3h-.2v.3h-.2l-.2.3h-.2v.2h-.3l-.2.2h-.2v.2h-.3v.2h-.5l-.2.2h-.8zm-3.7-3.6h1v.8h.2v.5h.2v.2h.2v.2h.2v.2h.2l.2.2h.2v.2h.5v.2h.7v1h-1.2v-.2h-.3v-.2h-.2l-.2-.2-.2-.2h-.2v-.2h-.2v-.3h-.3v-.3h-.2v-.3l-.2-.2v-.2l-.2-.3v-.8zm3.7-3.8v1h-.7v.2h-.5v.2h-.2l-.2.2v.2h-.2l-.2.2v.2l-.2.2v.2h-.2v.7l-.2.3v.4h-1v-1l.2-.2v-.3l.2-.3.2-.3v-.3h.3v-.3h.2l.2-.2.2-.2h.2v-.2h.6l.3-.2h.5l.3-.2h.2z"/>
|
||||||
|
<path fill="#fff" d="M407 268.3c1.8 0 3.2 1.4 3.2 3.2s-1.4 3.2-3.2 3.2a3 3 0 0 1-3.2-3.2c0-1.8 1.4-3.2 3.2-3.2"/>
|
||||||
|
<path fill="#21231e" d="M410.7 271.5h-1v-.7l-.2-.2v-.3l-.2-.2h-.2v-.4h-.2v-.2h-.2l-.2-.2h-.2l-.2-.2h-.5v-.2h-.4v-1h.8v.2h.3l.3.2h.2l.3.2h.2v.2l.3.2.2.2.3.3.2.3v.3h.2v.4l.2.2v1zm-3.7 3.7v-1h1v-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2.2-.2v-.4h.2v-1h1v1.2l-.2.2-.2.3v.3h-.2l-.2.3h-.2v.3h-.3v.2h-.2v.2h-.2l-.2.2h-.5l-.3.2h-.7zm-3.7-3.7h1v.8l.2.2v.4h.2v.2l.2.2v.2h.2v.2h.2v.2h.2l.2.2h.5l.2.2h.5v1h-.9l-.3-.2h-.2l-.3-.2-.3-.2h-.2l-.2-.2-.2-.3h-.3v-.3l-.2-.3v-.3h-.2v-.4l-.2-.2v-.8zm3.7-3.7v1h-.8v.2h-.5l-.2.2h-.2v.2h-.2v.2h-.2v.2h-.2v.2l-.2.2v.3l-.2.2v.7h-1v-1.2h.2v-.6h.2v-.3h.2v-.3h.3l.2-.2v-.2h.3v-.2h.3l.2-.2h.4v-.2h.5l.2-.2z"/>
|
||||||
|
<path fill="#fff" d="M457.3 266.2c1.8 0 3.2 1.4 3.2 3.2s-1.4 3.2-3.2 3.2a3 3 0 0 1-3.2-3.2c0-1.8 1.6-3.2 3.3-3.2z"/>
|
||||||
|
<path fill="#21231e" d="M461 269.4h-1v-.8h-.2v-.3l-.2-.2v-.2h-.2v-.2h-.2v-.2l-.2-.2h-.2l-.2-.2h-.2l-.3-.2h-.7v-1h1.1l.2.2h.3l.3.2.3.2.2.3h.2v.2h.2v.2h.2v.3h.2v.3l.2.2v.5l.2.2zm-3.7 3.7v-1h1.2v-.2h.2l.2-.2.2-.2v-.2h.2v-.2h.2v-.2l.2-.2v-.5h.2v-.8h1v1.1l-.2.2v.2l-.2.2-.2.3-.2.3-.2.2-.2.2-.2.2h-.2v.2h-.3v.2h-.4l-.2.2h-1zm-3.7-3.6h1v.5l.2.2v.3l.2.2v.2h.2l.2.2v.2h.2v.2h.2l.3.2h.2l.2.2h.7v1h-1l-.3-.2h-.3l-.3-.2-.3-.2v-.2l-.3-.2-.2-.2v-.2h-.2v-.3h-.2v-.6l-.2-.2v-.7zv-.2zm3.7-3.7v1h-.7v.2h-.4l-.2.2h-.2v.2h-.2v.2l-.2.2-.2.2v.4h-.2v.8h-1.2v-.5l.2-.3v-.4l.2-.2v-.2h.2v-.3h.2v-.3h.2v-.2h.3v-.3l.3-.2.3-.2h.3l.2-.2z"/>
|
||||||
|
<path fill="#edb92e" d="M371 237.4c2.3-.8 6 4.6 7 4.5 1.2-3-1.2-10.5-5.5-16.3-3-1-3.2-3-3.8-7.3-.2-1.3-2.7-.4-2.7 2 0 2.3-.3 3.6.3 5.6q.4 1.2-.8 2.3c4.5 1.8 5.6 4 5.4 9z"/>
|
||||||
|
<path fill="#21231e" d="m377.6 241.8 1 .3-.5.4h-.4l-.3-.2-.2-.2-.3-.3-.2-.2-.2-.2-.2-.2-.3-.3-.3-.2-.2-.2-.2-.3-.3-.2-.2-.2-.3-.2-.2-.3h-.3l-.3-.3h-.2l-.3-.3-.3-.2h-.2l-.3-.2h-.4l-.2-.2h-.4l-.4-1h1.3l.3.2h.3l.3.2.2.2h.3l.3.3.2.2h.3l.3.3.3.3v.2l.4.2.2.3.3.2v.2l.4.3.2.2.2.2.2.2.2.2.2.2h.2v.2h.3v.2zm1 .3-.2.4h-.3l.5-.3zm-6.3-15.8.4-1 .2.2.3.5.4.5.4.6.4.7.4.5.3.6.5.6.3.6.3.6.4.6.3.6.2.6.2.6.2.5.2.6.2.7.2.5v.6l.3.5v.6l.2.4v.5l.2.4v3.3l-.2.3-1-.2v-.3l.2-.3v-3.6l-.3-.5v-.4l-.2-.5-.2-.5-.2-.5v-.5l-.4-.6-.2-.7-.2-.5-.3-.6-.2-.6-.3-.5-.3-.5-.4-.6-.3-.7-.4-.5-.4-.6-.4-.6-.3-.5-.5-.5-.4-.5.3.2zm.4-1 .2.2-.3-.2zm-4.5-6.7h1v.7l.2.3v1l.2.3v.5l.2.3v.5l.2.2v.4h.2v.3l.2.2v.2h.2v.3h.2l.2.2v.2h.2l.2.2h.2l.2.2h.2l.2.2h.2l.3.2-.4 1h-.2l-.2-.2-.3-.2h-.2l-.2-.2h-.2l-.2-.3-.2-.2h-.2v-.2l-.3-.2-.2-.2-.2-.2v-.2l-.2-.2v-.2l-.2-.3v-.2l-.2-.2v-.3l-.2-.2v-.9l-.2-.3v-.6l-.2-.3V219l-.2-.5zm-1.7 1.8h-1v-1l.2-.2.2-.2v-.4h.2l.2-.3h.2l.2-.2.3-.2h.2l.2-.2h1.2v.2h.2v.3l.2.2v.2h-1.6v.2h-.2v.2h-.2v.2h-.2v.3l-.2.2v.8zm.3 5.6-1 .2v-.6l-.2-.2V224h-.2v-3.6h1v4.2l.2.2v.7l.2.2zm-1 2-.5.8v-1l.2-.2v-.2h.2v-.4h.2v-.4l-.2-.2v-.2l1-.3v1.8h-.2v.4h-.2v.2h-.2v.2l-.2.2h-.2zm-.5.8-.8-.3.7-.6v.8zm5 8.6h1zv-.5l.2-.5v-2l-.2-.3v-.7l-.2-.3v-.6l-.2-.3v-.2l-.2-.3-.2-.3v-.2l-.3-.3v-.2l-.3-.2-.2-.2-.3-.2-.2-.2-.3-.2h-.3l-.3-.3-.2-.2-.3-.2-.4-.2h-.3l-.4-.2.4-1 .4.2.5.2.4.2.3.2.3.2.4.2.3.2.3.2.3.2v.2l.4.3.2.3.3.3v.2l.3.3v.4l.3.3v.3l.3.3v.7l.2.3v.7l.2.4v2.9h-1zm1 0h-1l.5-.5.6.4zm-1 0v-.4h.5l-.4.4z"/>
|
||||||
|
<path fill="#edb92e" d="M368.8 218.5c-.4 1-.6 1.8-.7 3.4q0 1.5 1.2 3 1.2 1.6 3 2.6 1.2.9 2.3 3 1.1 2.3 1.2 4c0 3.5.8 6.7 2.4 7.3 1.2.3 1.6.2 2.4 0-.2-.6 0-1.5-.7-2.4a8 8 0 0 1-1.4-3.7c0-1-.5-1.6-.2-3.3.4-1.2 1-1.6 1-2.7-.3-2-2-6-4.3-6s-3.4-.3-4.6-3.4c-.3-1-1-1.6-1.2-2z"/>
|
||||||
|
<path fill="#21231e" d="m368.6 222-1-.2v-1.2l.2-.2v-1h.2v-.7h.2v-.4l1 .3v.4h-.2v1l-.2.2v1.5zm1 2.8-.8.6v-.2h-.2v-.2l-.2-.2v-.2h-.2v-.4h-.2v-.5l-.2-.2v-.7l-.2-.2v-.7h1v1.1h.2v.7h.2v.4h.2v.4h.2v.2zv.2zm3 2.4-.6 1v-.2h-.4v-.2h-.2v-.2h-.2l-.2-.2h-.2v-.2h-.3v-.2h-.2l-.2-.2v-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2l-.2-.2-.2-.2.8-.6.2.2.2.2v.2h.2l.2.2v.2h.2l.2.2.2.2h.2v.2h.2v.2h.3l.2.2.2.2h.2v.2zm2.4 3.2-1 .4v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.2v-.2h-.2l-.2-.2h-.2l.5-1h.2v.2h.2v.2h.2v.2l.2.2.2.2v.2h.2v.2h.2v.2l.2.2v.2l.2.2v.2h.2v.3h.2v.3h.2zm1.2 4.2h-1v-.8l-.2-.2v-.8h-.2v-.5h-.2v-.4h-.2v-.5h-.2v-.2l-.2-.2 1-.4v.4h.2v.4h.2v.5l.2.2v.3h.2v.6h.2v1h.2zm2 6.8-.3 1h-.3l-.2-.2-.3-.3v-.2l-.3-.2h-.2l-.2-.3v-.2h-.2v-.3l-.2-.2v-.3l-.2-.2v-.3l-.2-.2v-.5l-.2-.3v-.8l-.2-.3V236l-.2-.3v-1.5h1v2.1l.2.3v1.4l.2.2v.8l.2.2v.2l.2.2v.4l.2.2v.2l.2.2.2.2v.2h.2v.2h.2l.2.2zm1.7.8 1-.3-.5.5h-.4v.2h-1.4l-.2-.2h-.4l.3-1h.7v.2h1.2v-.2zm1-.3v.4h-.5l.4-.5zm-1.7-2 .8-.7h.2v.4h.2v.4h.2v.8l.2.2v1l-1 .4V241l-.2-.2v-.8h-.2v-.2zm-1.5-4h1v.8h.2v.8l.2.2v.2l.2.2v.2h.2v.5h.2v.2h.2v.2l-.7.7v-.2l-.2-.2v-.4h-.2v-.5h-.2v-.2l-.2-.3v-.2l-.2-.2v-.2l-.2-.3v-.4l-.2-.2v-.5zm-.2-3.4 1 .2v3l.2.2-1 .2v-.8l-.2-.2zm1-2.5h1v.7h-.2v.6l-.2.2v.2l-.2.2v.2h-.2v.6h-.2v.2l-1-.2v-.4h.2v-.4l.2-.2v-.2l.2-.2v-.2l.2-.2v-.9zm-4-5.5v-1h1.1l.3.2h.2l.2.2h.3l.2.2.2.2v.2l.3.2v.2l.3.2.2.3v.2l.3.3.2.2.2.3v.2l.2.3v.2l.2.3v.3l.2.2v.3l.2.2v.5l.2.3v.8h-1v-1l-.2-.2v-.4l-.2-.3-.2-.4v-.2l-.2-.2v-.3l-.3-.3v-.2l-.2-.2v-.3l-.2-.2-.3-.3-.2-.2-.3-.3-.3-.2-.3-.2h-.8zm-5-3.7 1-.4v.3l.2.3v.2l.2.2v.2l.2.2v.2l.2.2.2.2v.2h.2v.2h.2v.2h.4v.2h.7v.2h1.7v1H373l-.3-.2h-.4v-.2h-.3v-.2h-.3v-.3h-.3v-.2l-.2-.2-.2-.3-.2-.2v-.2l-.2-.2-.2-.4-.2-.2v-.3l-.2-.2zm-.4-2-1-.5h1.2v.4h.2v.2h.2v.2h.2v.4h.2v.2l.2.2v.2l.2.2-1 .4v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2v-.2h-.2v-.2h-.2v-.2zm-1-.5.5-1 .5 1zm.6.2.4.2zm-.5-.2.4-1 .5 1z"/>
|
||||||
|
<path fill="#edb92e" d="m360 227 2-1a6 6 0 0 1 4-.2c5.5 1.7 8 11.6 1.8 14q-1.8.2-3.4-3.3a21 21 0 0 0-4-6.8c-1-1-1-2-.4-2.6z"/>
|
||||||
|
<path fill="#21231e" d="m362 225.5.3 1h-.4v.2h-.4v.2h-.8v.2h-.2l-.2.2-.7-.7.2-.2v-.2h.4v-.2h.8v-.2h.4v-.2h.4zm4-.2-.2 1h-.5v-.2h-2.1v.2h-.7l-.2.2-.5-1h.4l.3-.2h.8v-.2h1.6l.2.2h.4zm2 15-.2-1h-.2l.5-.2h.6l.4-.4.4-.3.3-.4.3-.5.2-.4.2-.3.2-.5v-.5l.2-.5V232l-.3-.4-.2-.5-.2-.5v-.5l-.4-.4-.2-.5-.4-.3-.3-.5-.4-.3-.3-.5-.4-.3-.5-.3-.3-.4-.5-.2h-.4l.2-1h.6l.5.3.7.3.4.4.6.3.5.4.5.6.3.4.4.5.3.5.3.6.3.5.3.7.2.6v.6l.3.5v3.4l-.2.5-.2.6-.3.6-.2.5-.3.5-.4.5-.4.4-.4.3-.6.3-.6.3zm-4-3.6 1-.4v.4l.2.2.2.3.2.3v.2h.2v.5h.2v.2l.2.2.2.2h.2v.2h.2l.2.2h.8v1.2h-.8v-.2h-.5l-.2-.2-.2-.2-.2-.2-.2-.2v-.2h-.2l-.2-.2v-.2h-.2v-.3l-.2-.2v-.2h-.2v-.2l-.2-.2v-.2l-.2-.3-.3-.3zm-4-6.6.7-.6.2.2v.2h.2l.2.3v.3h.2l.2.3v.2l.2.2h.2v.3l.3.2v.2l.2.2.2.2v.4l.2.2.2.3v.3l.2.2.2.3v.3l.2.3v.3l.3.3.2.4v.3l.3.3v.3l-1 .4v-.6l-.2-.2v-.3l-.3-.2v-.3l-.2-.2v-.5l-.3-.3v-.2l-.2-.2v-.3l-.3-.2-.2-.3v-.2l-.3-.2v-.2h-.2v-.3h-.3v-.3l-.2-.2-.3-.3v-.2l-.2-.2h-.2v-.2l-.2-.2zm0-3.4v1l.3-.2v.2h-.2v.8h.2v.4l.2.2.2.2v.2l-.7.7h-.2v-.2l-.2-.2-.2-.2v-.3h-.2V227h.2v-.2h.2zm-.4 0h.4zm.4 1v-1l.3 1zm.3 0h-.2.3z"/>
|
||||||
|
<path fill="#edb92e" d="M362.8 227.7c5.4-2 9.4 8.3 4.3 10.2-2.6 1-2.8-2.4-3.4-4.3-.7-2.5-6-4-.8-6z"/>
|
||||||
|
<path fill="#21231e" d="m367.3 238.4-.4-1h.3l.3-.3.3-.2.2-.3.2-.3.2-.3v-.5l.2-.4v-2.7l-.2-.4-.2-.5-.2-.4-.2-.4-.3-.4-.3-.4-.2-.3-.4-.4-.3-.2-.3-.2-.4-.2-.4-.2h-.4l-.3-.2h-1.3l-.4.2-.4-1h.6l.5-.2h1.6l.5.3.5.2.4.2.4.4.5.4.4.4.3.4.3.5.2.4.3.6.3.5.2.6.2.6v.5l.2.5v1.6l-.2.5v.5l-.3.5-.2.4-.3.5-.4.3-.4.4-.4.2-.5.2zM363 234l1-.4.2.2v.6l.2.2v.8l.2.2v.4l.2.2v.2l.2.3.2.3v.2h.2v.2h.2l.2.2h1.2l.3.8h-.3l-.3.2h-1.2l-.2-.2h-.2v-.2l-.3-.2-.3-.3v-.2l-.2-.2v-.2h-.2v-.3l-.2-.2v-.4h-.2v-.8l-.2-.2v-.8l-.2-.2v-.2zm-.4-6.7.4 1h-.5l-.3.3h-.4l-.2.3h-.2l-.2.2v.2h-.2v.4h.2v.3l.2.2v.2h.3v.3l.3.2h.2l.2.2.2.2.2.2.2.2.2.3.2.3.2.2v.3l.3.2v.6l-1 .3v-.7l-.2-.2h-.2v-.3l-.3-.2h-.2v-.3l-.3-.2-.2-.2-.2-.2-.2-.2v-.2l-.2-.2h-.2v-.3l-.3-.3v-.2l-.2-.2v-.8l.2-.3v-.3l.3-.2.2-.2.3-.3.3-.2h.4l.4-.3.6-.2z"/>
|
||||||
|
<path fill="#0c4076" d="M363.4 229.3c2.6-1 6.2 5 3 6.5-2.3 1-4-.3-4.8-2-.7-2 0-3.8 1.7-4.5z"/>
|
||||||
|
<path fill="#21231e" d="m366.5 236.3-.4-1h.3l.2-.2h.2v-.2l.2-.2v-2l-.2-.3v-.3h-.2v-.4l-.3-.3-.2-.2-.3-.4-.2-.2-.2-.2h-.2v-.2l-.3-.2h-1.1l-.3-1h1.5l.4.3.3.2.2.3.2.2.3.2.2.2.2.3v.3l.3.3.2.4.2.3v.4l.2.4v2.6l-.2.3-.2.4-.2.3-.2.2h-.4l-.3.3zm-5.4-2.5 1-.3.2.3.2.3v.2l.2.3.2.2.2.2h.3v.2h.2l.3.2h.4v.2h1.3l.3-.2.5 1h-.7l-.2.2h-1.9l-.2-.2h-.2v-.2h-.3l-.2-.2-.3-.2-.3-.3-.3-.3h-.2v-.3l-.2-.3-.2-.3v-.2zv-.2zm2.2-5 .3 1h-.3l-.2.2-.2.2-.2.2-.2.2-.2.2v.2h-.2v.7l-.2.2v1.1l.2.2v.3l-1 .3v-.7l-.2-.3v-1.5l.2-.2v-.3l.2-.2.2-.3.2-.3v-.2l.3-.2v-.2h.3l.2-.2.3-.2h.2zm.2.5-.2-.5z"/>
|
||||||
|
<path fill="#fff" d="M400 179.7c3.6 0 6.4 2.8 6.4 6.4s-2.8 6.5-6.3 6.5-6.3-3-6.3-6.4 3-6.3 6.4-6.3z"/>
|
||||||
|
<path fill="#21231e" d="M407 186h-1c0-2-1.4-4.3-3.3-5.2a6 6 0 0 0-2.6-.6v-1c2.4 0 4.7 1.2 6 3.2a7 7 0 0 1 1 3.7zm-7 7v-1a6 6 0 0 0 5.3-3.3 6 6 0 0 0 .6-2.6h1c0 2.4-1.3 4.7-3.3 6a7 7 0 0 1-3.6 1zm-6.8-7h1a6 6 0 0 0 3.3 5.4 6 6 0 0 0 2.6.6v1a7 7 0 0 1-5.7-3.3 7 7 0 0 1-1-3.6zm7-7v1.2a6 6 0 0 0-5.3 3.2 6 6 0 0 0-.8 2.7h-1a7 7 0 0 1 3.3-5.8 7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M395.4 169c3.5 0 6.4 3 6.4 6.4 0 3.6-3 6.4-6.4 6.4s-6.3-2.8-6.3-6.4c0-3.5 3-6.4 6.4-6.4z"/>
|
||||||
|
<path fill="#21231e" d="M402.3 175.4h-1c0-2.2-1.3-4.3-3.2-5.3a6 6 0 0 0-2.6-.5v-1q3.8.2 6 3.2a7 7 0 0 1 1 3.7zm-7 7v-1c2.3 0 4.4-1.4 5.4-3.3a6 6 0 0 0 .6-2.6h1c0 2.4-1.2 4.7-3.2 6a7 7 0 0 1-3.6 1zm-6.7-7h1a6 6 0 0 0 3.2 5.3q1.2.6 2.6.6v1a7 7 0 0 1-5.8-3.2 7 7 0 0 1-1-3.6zm6.8-7v1c-2 0-4.2 1.4-5.2 3.3a6 6 0 0 0-.6 2.7h-1c0-2.3 1.2-4.6 3.2-6a7 7 0 0 1 3.6-1"/>
|
||||||
|
<path fill="#fff" d="M410.7 131.5c3.4 0 6 2.7 6 6s-2.6 6.3-6 6.3-6.2-2.7-6.2-6.2a6 6 0 0 1 6.2-6z"/>
|
||||||
|
<path fill="#21231e" d="M417.3 137.7h-1c0-2.2-1.2-4.2-3-5a6 6 0 0 0-2.6-.7v-1a7 7 0 0 1 5.6 3 7 7 0 0 1 1 3.7m-6.6 6.6v-1c2 0 4-1.2 5-3q.6-1.5.6-2.6h1c0 2.2-1.2 4.5-3 5.6a7 7 0 0 1-3.6 1m-6.7-6.7h1c0 2.2 1.3 4.2 3.2 5a6 6 0 0 0 2.5.7v1a7 7 0 0 1-5.6-3 7 7 0 0 1-1-3.6zm6.7-6.6v1c-2 0-4 1.2-5 3a6 6 0 0 0-.7 2.6h-1a7 7 0 0 1 3-5.6 7 7 0 0 1 3.7-1"/>
|
||||||
|
<path fill="#fff" d="M400.4 136.2c3.5 0 6.3 3 6.3 6.4s-2.8 6.4-6.3 6.4-6.4-3-6.4-6.4 2.8-6.4 6.4-6.4"/>
|
||||||
|
<path fill="#21231e" d="M407.2 142.6h-1a6 6 0 0 0-3.3-5.3 6 6 0 0 0-2.6-.6v-1c2.3 0 4.6 1.2 5.8 3.2a7 7 0 0 1 1 3.6zm-6.8 7v-1c2 0 4.2-1.4 5.2-3.3a6 6 0 0 0 .6-2.7h1c0 2.3-1.2 4.7-3.2 6a7 7 0 0 1-3.6 1m-7-7h1c0 2.2 1.4 4.3 3.3 5.3a6 6 0 0 0 2.7.5v1a7 7 0 0 1-6-3.2 7 7 0 0 1-1-3.7zm7-7v1a6 6 0 0 0-5.3 3.3 6 6 0 0 0-.5 2.6h-1c0-2.4 1.2-4.7 3.2-6a7 7 0 0 1 3.7-1z"/>
|
||||||
|
<path fill="#fff" d="M392.8 145.3c3.5 0 6.3 2.8 6.3 6.4 0 3.5-2.7 6.4-6.2 6.4s-6.4-2.8-6.4-6.3c0-3.6 3-6.4 6.4-6.4z"/>
|
||||||
|
<path fill="#21231e" d="M399.6 151.7h-1a6 6 0 0 0-3.2-5.3 6 6 0 0 0-2.6-.6v-1c2.3 0 4.6 1.2 5.8 3.2a7 7 0 0 1 1 3.7m-6.8 7v-1c2 0 4.2-1.4 5.2-3.4a6 6 0 0 0 .6-2.6h1c0 2.3-1.2 4.6-3.2 6a7 7 0 0 1-3.6 1m-7-7h1c0 2 1.4 4.3 3.3 5.2a6 6 0 0 0 2.8.6v1a7 7 0 0 1-6-3.3 7 7 0 0 1-1-3.6zm7-7v1a6 6 0 0 0-5.3 3.3 6 6 0 0 0-.6 2.7h-1c0-2.4 1-4.7 3.2-6a7 7 0 0 1 3.6-1z"/>
|
||||||
|
<path fill="#fff" d="M391.5 156.7c3.6 0 6.4 2.8 6.4 6.4s-3 6.5-6.5 6.5-6.3-3-6.3-6.4 2.8-6.3 6.3-6.3z"/>
|
||||||
|
<path fill="#21231e" d="M398.4 163h-1c0-2-1.3-4.2-3.2-5.2a6 6 0 0 0-2.7-.6v-1c2.4 0 4.7 1.2 6 3.2a7 7 0 0 1 1 3.7zm-7 7v-1a6 6 0 0 0 5.4-3.3 6 6 0 0 0 .6-2.6h1c0 2.5-1.3 5-3.4 6a7 7 0 0 1-3.5 1zm-6.7-7h1c0 2.3 1.3 4.4 3.2 5.4q1.1.6 2.5.6v1a7 7 0 0 1-5.8-3.2 7 7 0 0 1-1-3.7zm6.8-6.8v1c-2 0-4.2 1.3-5.2 3.2a6 6 0 0 0-.6 2.7h-1c0-2.3 1.2-4.6 3.2-5.8a7 7 0 0 1 3.5-1z"/>
|
||||||
|
<path fill="#edb92e" d="M474.4 198q-4-7.5-8.6-9.6 6.4-19.8 2.7-51-.4-4.8-2.2-4.6-1.6-.1-1.8 5.2c.8 11.6 1.7 21.6 1.2 30.2-.5 7.6-2.3 14-4.2 21.2-.3 1 .4 1.7 3.8 3.7q4 2.4 4.7 5h1.5q3.8 1 3 0z"/>
|
||||||
|
<path fill="#21231e" d="m465.3 188.2 1 .3-.3-.6h.3l.3.2.3.2.2.2h.3l.3.3h.3l.4.4.2.2.3.2.2.3.3.3.3.3.3.3.3.2.3.3.3.3.2.3.2.5.3.3.3.4.2.3.3.4.3.3.2.4.3.4.3.5.2.5.3.4.3.4.3.5.3.5-1 .5-.3-.5-.2-.4-.3-.5-.2-.5-.3-.4-.3-.3-.2-.4-.3-.4-.3-.5-.3-.4-.2-.2-.3-.4-.4-.3-.2-.2-.3-.3v-.3l-.4-.3-.3-.4-.2-.3-.2-.2-.3-.2-.2-.2-.3-.2-.3-.2-.2-.2-.3-.2-.3-.2-.3-.2h-.3l-.3-.3-.4-.2-.3-.6zm.3.6h-.4v-.6zm2.4-51.5h1l.2 2 .2 1.8.2 2 .2 2v1.8l.2 1.8v1.8l.2 1.8v15l-.3 1.5-.2 1.5v1.5l-.3 1.5-.2 1.5-.2 1.4-.2 1.5-.2 1.3-.3 1.4-.3 1.3-.3 1.4-.3 1.3-.4 1.3-.3 1.3-.4 1.2-1-.3.4-1.2.4-1.3.4-1.3.3-1.3.3-1.2.3-1.3.3-1.4.2-1.3.3-1.4.3-1.5.2-1.4V172l.3-1.6V169l.2-1.7v-1.6l.2-1.6v-13.6l-.2-1.8v-2l-.2-1.7-.2-2-.2-1.8-.2-2-.2-2zm-1.7-4v-1h.6v.2h.3v.2h.2l.2.3h.2v.5h.2v.3l.2.2v.3l.2.2v.4l.2.4v.9l.2.3v.4h-1v-.9l-.2-.2v-.6l-.2-.2v-.5h-.2v-.5h-.2v-.2l-.2-.2h-.2v-.2h-.4zM465 138h-1v-1.7l.2-.3v-1l.2-.3v-.7h.2v-.3l.2-.3v-.3h.2l.2-.2.3-.2.2-.2h.5v1h-.2v.2h-.2v.2l-.2.2v.3l-.2.3v.6l-.2.2v1.6l-.2.4v.7zm1.2 30.2h-1V154l-.2-1v-2.8l-.2-1v-2l-.2-1v-1l-.2-1v-2l-.2-1V139l-.2-1 1-.2v1l.2 1.2v2l.2 1v2.2l.2 1v2l.2 1v2l.2 1v2.8l.2 1v12zm-4.3 21.3-1-.3.2-.6v-.7l.3-.8.2-.6.2-.7v-.7l.2-.7.2-.6.2-.7v-.6l.3-.6v-.6l.2-.6.2-.7v-.5l.2-.6.2-.8v-.6l.2-.7.2-.7v-.7l.2-.6v-.7l.2-.6v-.7l.2-.7v-.7l.2-.6V169l.2-.8h1v1.5l-.2.7v1.5l-.2.6v.7l-.2.8v.7l-.2.6v.8l-.2.7-.2.6v.8l-.2.6-.2.7v.7l-.3.7v.6l-.3.7v.6l-.3.6-.2.6v.6l-.3.7-.2.5-.2.7v.6l-.3.7-.2.7-.2.6v.7zm3.6 3-.5 1-.2-.2h-.3l-.3-.3-.3-.2h-.4l-.2-.3h-.2l-.3-.3h-.3v-.2l-.3-.2h-.2l-.2-.2-.2-.3h-.2v-.2h-.2l-.2-.2v-.2l-.2-.2v-1l1 .2v.5h.2v.2l.2.2h.2v.2h.2l.2.2.3.3h.2l.2.2.2.2h.2l.2.3h.3l.3.3.3.2h.3zm4.2 5.2.4 1-.7-.4v-.2l-.2-.2v-.3l-.2-.2v-.3l-.2-.3-.3-.3-.2-.3-.3-.2-.3-.3v-.2h-.3v-.2h-.3v-.2l-.3-.2h-.2v-.3h-.3l-.2-.2h-.3l-.2-.2-.2-.2h-.2l.6-1 .2.2h.2l.3.3.2.2.2.2h.2v.3h.4l.2.3.3.3.3.3.2.2.3.3v.3l.3.2h.2v.3l.3.2v.2h.2v.3l.2.2v.2l.2.3v.3l.2.2v.2l-.6-.3zm.4 1h-.5l-.2-.4zm1.4-1.2-.3 1h-1l-.4-.8h.2l.2-.2zm3.3.5h-1l.7-.4.2.2v.8l-.2.2h-.2v.2h-1.8l-.2-.2h-.3l-.2-.2h-.5l.3-1h.7l.2.2h1v.2h.7v.4zm-1 0v-1l.7.6-.8.4zm0 0h1l-1 .3z"/>
|
||||||
|
<path fill="#edb92e" d="M458.4 214.6c1.6-1 2-.8 2.4-1.4q.8-1.4.4-3.5c-.8-5.3 2.4-4.6 6-2.2 1.7 1 3.5 4 5 5.3s2.8 1.5 3.2 2v9.2c-.4.6-1.7.7-3.2 2-1.5 1.5-3.3 4.2-5 5.4-3.6 2.3-6.8 3-6-2.2q.4-2.1-.4-3.5c-.5-.7-.8-.4-2.4-1.4a7 7 0 0 1-2.3-2.2c-.4-.8 0-1.7 0-2.6s-.4-1.8 0-2.6q.9-1.2 2.4-2.2z"/>
|
||||||
|
<path fill="#21231e" d="m460.4 213 .8.5-.2.2-.2.2h-.4v.2h-.4l-.2.2h-.2l-.2.2h-.2v.2h-.5v.2l-.7-1h.6v-.2h.2l.2-.2h.2l.2-.2h.4v-.2h.4v-.2h.2zm.3-3.2 1-.2v2.9l-.2.3v.2h-.2v.2l-1-.5v-.2h.2v-.4h.2v-2.6zm6.7-2.8-.6 1-.3-.3-.3-.2-.3-.2-.4-.2h-.3l-.3-.2h-.3l-.3-.2h-.3v-.2h-.4l-.2-.2h-1.1v.2h-.2v.2l-.2.2v.6l-.2.3v2l-1 .2v-3l.2-.4v-.3l.3-.2.3-.2.2-.2h.2l.2-.2h.2l.2-.2h1.5v.2h.5l.3.2h.3l.4.3h.3l.3.2.4.2.3.2.3.2zm5 5.4-.6.7-.3-.2-.3-.3-.3-.3-.2-.2-.2-.2-.3-.3-.2-.2v-.2h-.2v-.3l-.3-.2-.3-.3-.2-.2v-.2l-.3-.2v-.2h-.3l-.2-.3-.3-.2-.2-.2h-.2l-.2-.2v-.2h-.3v-.2h-.2l.6-1 .2.2.3.2v.2h.3v.3h.3l.2.2.2.2v.2l.3.2h.2v.3l.3.3.2.2v.2l.3.2.3.3h.2v.3l.3.2.2.2.2.2.3.3v.2h.3v.3h.3v.3h.2zm3.5 2.4h-1.2v.3h-.2l-.2-.2h-.2v-.2h-.4l-.2-.2h-.2v-.2h-.3v-.2h-.3v-.2l-.3-.2-.3-.2-.2-.3.7-.6h.2v.2h.2v.2h.2v.2h.2v.2h.3l.2.2.2.2h.2l.2.2h.2l.2.2h.2v.2h.4v.2l.2.2v.2zm-.2-.2v.2zm0 4.8h-1v-4.5l1-.2v4.5zm-1 0h1zm1 5-1-.7v.3-4.4h1zm0-.4v.2zm-3.3 2.5-.7-.8h.2v-.2l.3-.2.3-.2h.3v-.2h.3v-.2h.2l.2-.2h.2v-.2h.4v-.2h.4v-.2l1 .5-.2.2-.2.2h-.2v.2h-.4l-.2.2h-.2v.2h-.4v.2h-.3v.2h-.2l-.2.2-.2.2v.2h-.3zm-5 5.3-.7-.8h.2v-.2l.3-.2.3-.2.2-.2.3-.3.2-.2.2-.2.2-.2v-.2h.3v-.3l.2-.3.2-.2v-.2l.3-.2.3-.2v-.2l.3-.2.2-.2v-.2h.3v-.3l.3-.2.2-.2h.2v-.3h.3v-.3l.8.8-.2.2-.2.2-.2.2v.2h-.3v.3l-.3.2-.3.2-.2.2-.2.2-.2.2v.2l-.3.2v.2l-.2.2h-.2v.3h-.3l-.2.3v.2h-.2l-.2.3-.2.2-.3.3-.3.3-.3.2zm-6.8-2.7 1 .3v2.6l.2.2v.2h.2v.2h1.7v-.2h.4l.3-.2h.3l.3-.3.3-.3h.3l.2-.3.3-.2.3-.2.6.8-.4.2-.3.3-.3.2h-.4l-.3.3-.3.2h-.3l-.2.3h-.4l-.3.2h-.4l-.3.2h-.9l-.3-.2h-.3l-.2-.3-.2-.2-.2-.2v-.3l-.2-.2v-.7l-.2-.3zm-.3-3 .8-.6v.2h.2v.4h.2v.6l.2.2v2.4l-1-.2v-2.5h-.2v-.4h-.2zm-2.3-1.3.7-.8h.2l.2.2h.2l.2.2h.2l.2.2h.4v.2h.2v.2h.2l.2.2.2.2-.8.6-.2-.2h-.2v-.2h-.4l-.2-.2h-.4v-.2h-.2l-.2-.2h-.2v-.2zm.7-.8.5.2zm-3-1.7.8-.5.2.2v.2h.2v.4h.2v.2h.2v.2h.2v.2h.2l.2.2.2.2h.2v.2h.2l-.5 1v-.2h-.2l-.2-.2-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2l-.2-.2-.2-.2v-.2zm-.3-3h1v2.4l-.7.5v-.2h-.2v-.4l-.2-.2v-2.1zm.3-2.8.8.6v.4l-.2.2v1.8h-1V217h.2v-.4zm2.4-2.4.7 1h-.2v.2h-.2l-.2.2-.2.2-.2.2-.2.2-.2.2-.2.2v.2h-.2v.2h-.2v.2l-1-.5h.2v-.2l.2-.2.2-.2v-.2h.2l.2-.2v-.2h.2v-.2h.2l.2-.2h.2v-.2l.2-.2h.2v-.2h.2zm.7 1 .5-.3z"/>
|
||||||
|
<path fill="#21231e" d="M475.5 220.2v1q-5.8.7-9.7 4.3c1.8-2.7 5.7-4.7 9.7-5.3m0-1.6v-.8q-5.8-.8-9.7-4.4c1.8 2.7 5.7 4.6 9.7 5.2"/>
|
||||||
|
<path fill="#edb92e" d="M461.2 207.3q1.6.6 3.4 2c1.6 1.5.6 4-1.5 6.6q-1 1.2-1 3.5.1 2.1 1 3.5c2.2 2.5 3.2 5 1.6 6.6q-1.6 1.4-3.4 2v-2.4q.4-2.1-.4-3.5c-.5-.7-.8-.4-2.4-1.4a7 7 0 0 1-2.3-2.2c-.4-.8 0-1.7 0-2.6s-.4-1.8 0-2.6q.9-1.2 2.4-2.2c1.6-1 2-.8 2.4-1.4q.8-1.4.4-3.5v-2.4z"/>
|
||||||
|
<path fill="#21231e" d="m465 209-.8.6-.2-.2-.2-.2h-.2v-.2h-.2l-.2-.2h-.2v-.2h-.4v-.2h-.5v-.2h-.2l-.2-.2h-.2l-.2-.2h-.2l.4-1h.2l.2.2h.2l.2.2h.3l.2.2h.2v.2h.3l.2.2h.2v.2h.3v.2h.2l.2.2.2.2.2.2zm-1.5 7.2-.8-.6.2-.3v-.2h.2l.2-.3.2-.3v-.2l.3-.2v-.5l.2-.2v-.2h.2v-.3l.2-.2v-.4h.2v-1.6l-.2-.3-.2-.2-.2-.2.7-.7.3.3v.2l.2.2v.2h.2v.5l.2.2v1l-.2.2v.4l-.2.2v.5l-.2.3v.3l-.3.2v.5l-.3.2v.3l-.3.2-.2.3v.2l-.3.2-.3.3-.2.2zm-1 3.3h-1v-.7l.2-.2v-1.2h.2v-1h.2v-.4l.2-.2v-.2h.2v-.2l1 .6h-.2v.4h-.2v.2l-.2.2v.9h-.2v1.4h-.2v.5zm1 3.2-.8.6v-.2h-.2v-.2h-.2v-.4h-.2v-1.1l-.2-.3V220h-.2v-.5h1v1.1l.2.2v.4l.2.2v.4l.2.2v.2h.2v.2zm1.5 7.3-.8-.8h.2v-.2h.2v-.3l.2-.2v-1.9l-.2-.2v-.2l-.2-.2v-.2l-.2-.3v-.2l-.2-.2-.3-.3v-.3l-.3-.2-.2-.2v-.2l-.3-.3-.3-.2.8-.6.2.2h.2v.4l.2.2.2.3h.2v.3l.3.3v.5l.3.3v.3l.2.2v.5l.2.2v.5l.2.2v.7l-.2.4v.4l-.2.2v.2l-.2.2v.2l-.3.2v.2zm-4.3 1.7 1-.2-.7-.4h.7v-.2h.4l.2-.2h.2l.2-.2h.2v-.2h.4v-.2h.2l.2-.2.2-.2h.2v-.2h.2v-.2l.8.8h-.2v.2h-.2v.2h-.2l-.2.2-.2.2h-.2l-.2.2h-.2v.2h-.4v.2h-.4v.2h-.5v.2h-.2l-.2.2h-.2l-.7-.3zm.7.4-.6.3v-.6l.6.4zm-.7-3 1 .3v2l-1 .3v-2.4zm-.3-3 .8-.6v.2h.2v.4h.2v.6l.2.2v2.4l-1-.2v-2.4h-.2v-.4h-.2zm-2.3-1.3.7-.8h.2l.2.2h.2l.2.2h.2l.2.2h.4v.2h.2l.2.2.2.2.2.2-.8.6-.2-.2h-.2v-.2h-.4l-.2-.2h-.4v-.2h-.2l-.2-.2h-.2v-.2zm0 0-.3-.2zm-2.3-2.4.8-.5.2.2v.2h.2v.4h.2v.2h.2v.2h.2v.2h.2l.2.2h.2v.2h.2v.2h.2l-.5 1v-.2h-.2l-.2-.2-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2l-.2-.2-.2-.2v-.2zm0 0 .3.5zm-.3-3h1v2.4l-.7.5v-.2h-.2v-.4l-.2-.2v-2.2zm.3-2.8.8.6v.4l-.2.2v1.8h-1V217h.2v-.4zm2.4-2.4.7 1h-.2v.2h-.2l-.2.2-.2.2-.2.2-.2.2-.2.2-.2.2v.2h-.2v.2h-.2v.2l-1-.5h.2v-.2l.2-.2.2-.2v-.2h.2l.2-.2v-.2h.2v-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2h.2zm2.4-1 .8.5-.2.2-.2.2h-.4v.2h-.4l-.2.2h-.2l-.2.2h-.2v.2h-.5v.2l-.7-1h.6v-.2h.2l.2-.2h.2l.2-.2h.4v-.2h.4v-.2h.2zm.3-3.2 1-.2v2.9l-.2.2v.2h-.2v.2l-1-.5v-.2h.2v-.4h.2zm.5-2v-1l.5.6v2.1l-1 .2V207l.5.6zm0-1h.6v.6zm0 0v1h-.2zM361 226.3l.7-.3q-2.4-3.2-1.3-6.3c-1.3 1.6-1 4.2.6 6.6"/>
|
||||||
|
<path fill="#edb92e" d="M410.7 200c1.4 1.6 1.2 2 2 2.5q1.4.8 3.3 0c5-2 5 2 3.5 6.4-.7 2-2.8 4.3-3.7 6.2-1 1.7-.7 3-1 3.5l-4 .6-4 .5c-.6-.3-1-1.6-2.6-3s-4.4-3-6-4.6c-3-3.5-4.6-6.8.2-6.7q2 .1 3-1c.4-.7 0-1 .4-3q.5-1.9 1.4-3c.6-.8 1.5-.6 2.3-.7 1-.2 1.6-.7 2.5-.2q1.6.6 2.7 2.3z"/>
|
||||||
|
<path fill="#21231e" d="m413 202-.6 1-.2-.2-.2-.2-.2-.2v-.2h-.2v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.3-.3-.3.8-.6.3.2.2.3v.2h.2v.4h.2v.4h.2v.2l.2.2v.2h.2v.2l.2.2zm2.9 0 .2 1h-.5l-.2.2h-.8v.2h-1.2l-.3-.2h-.2l-.2-.2h-.2l.5-1v.2h.8v.2h1l.2-.2h.4l.2-.2zm4 7-1-.3v-.4l.2-.4v-.5l.2-.4v-.3l.2-.3v-.7l.2-.4v-1.7l-.2-.2v-.4h-.2l-.2-.3h-.2l-.3-.2H417l-.4.3h-.4l-.3-.8.3-.2h.5l.4-.2h1.8l.3.2h.3l.2.3.2.4v.2l.3.3.2.4v3.3l-.2.4v.4l-.2.4v.4l-.2.4v.4zm-3.7 6.4-1-.5v-.2l.2-.2.2-.2v-.2l.2-.2v-.4l.2-.2.2-.2v-.2l.2-.2.2-.2.2-.3.2-.2.2-.3.2-.2v-.2l.2-.2h.2v-.3l.2-.2v-.2l.2-.2.2-.3v-.2l.2-.2v-.2l.2-.3 1 .3v.2l-.2.3v.2l-.2.2-.2.3v.2l-.2.2v.3l-.3.2-.2.2v.2l-.2.2-.2.2v.2l-.2.2-.2.2v.2l-.3.2v.2l-.2.2-.2.2v.2l-.2.2v.4h-.3v.3l-.2.2-.2.2v.2zm-1.5 3.8-.2-1-.3.2v-.4h.2v-1l.2-.2v-.4l.2-.3v-.4l.2-.3v-.2h.2l1 .4-.2.2-.2.3v.3h-.2v.7h-.2v.9l-.2.2v.4l-.2.2v.2zm.3 0h-.2.3zm-4.3.6v-1h1.1v-.2h1.2v-.2h1.1l.3-.2.2 1h-.4l-.3.2h-.9l-.2.2h-1.3v.2h-.7zm0 0v-1 .5-.5zv-.5zm-4 .5.4-1h-.2 1.3l.3-.2h1.9l.2-.2.2 1h-.8v.2h-1.4v.2h-2zm.2 0h-.3.2zm-3-3 .5-1 .2.3.3.3h.2l.2.3.2.2.2.3.2.2v.2h.2v.2h.2v.2l.2.2v.2h.2v.2h.2v.2l-.4 1v-.2h-.2l-.2-.2-.2-.2v-.2h-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.2-.2-.2-.2-.3v-.2h-.2v-.2h-.3v-.3zm-6-4.8.7-.6.2.2.3.3.3.3h.2v.3l.3.2h.3l.2.3h.2l.2.3.3.3.2.2.2.2h.2v.2l.3.2h.2l.2.3.2.2.3.3h.2l.2.3.3.2.2.2h.2v.2l-.5.8h-.3v-.2l-.3-.2h-.2v-.2l-.3-.2h-.3l-.2-.2-.2-.2h-.2v-.2l-.3-.2h-.2l-.2-.3h-.2v-.2h-.3l-.2-.2-.2-.2h-.2v-.3h-.3l-.2-.3-.2-.2h-.2v-.3h-.3v-.3l-.3-.2h-.2v-.3l-.3-.2zm.4-7.5v1H397l-.2.2h-.4l-.2.2v.2h-.2v1.5l.2.3v.3l.2.2.2.3.2.4.2.3.2.3.2.3.2.3.3.3v.2l.4.3.3.4-.8.5-.4-.3-.3-.3-.2-.4-.2-.4-.2-.3-.3-.4-.2-.3v-.4l-.3-.4v-.3l-.3-.3v-.3l-.3-.3V206l.2-.2.2-.2.2-.2.3-.2h.2l.3-.2h.7l.4-.2h.8zm2.5-.8.8.5-.2.2-.2.2h-.2v.2h-.2l-.2.2h-.2l-.2.2h-.5l-.2.2h-1.1v-1h1.4l.2-.2h.2l.2-.2h.2v-.2zv-.2zm.4-2.8 1 .2v.4h-.2v2h-.2v.4l-.2.2-1-.4h.2v-1.4l.2-.3v-.8zm1.4-3.2.8.6-.2.2v.2h-.2v.4h-.2v.4h-.2v.4l-.2.2v.4l-.2.2v.4l-1-.2v-.7l.2-.2v-.4l.2-.2v-.4h.2v-.4h.2v-.4h.2v-.2h.2v-.4zm.8.6.5-.7zm1.8-1.5.2 1H404v.2h-.4v.2h-.2l-.8-.5.2-.2.2-.2h.2v-.2h.4l.2-.2zm.2 1h-.7.6zm2.7-1.2-.5 1H406l-.2.2h-.4l-.2-1h.8l.2-.2h2.1zm3 3h-1v.2-.2h-.2v-.2l-.2-.2-.2-.2-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2l-.2-.2-.2-.2h-.2l-.2-.2.5-1v.2h.2l.2.2h.2v.2h.2v.2h.2l.2.2.2.2.2.2h.2v.2h.2v.2l.2.2.2.2.2.2v.2l.3.3zm-.2-.4h.2v.3-.4zm-.8.3h1l-1 .2v-.3zm0 .2v-.3.2z"/>
|
||||||
|
<path fill="#21231e" d="M410 219.4h-.7q-2.1-5.7-6.3-9.5c2.8 1.4 5.5 5.3 7 9.4zm1.4-.2.8-.2q-.8-5.9 1.5-11c-2 2.5-2.8 7-2.3 11.2"/>
|
||||||
|
<path fill="#edb92e" d="M418.3 202q-.2 2.2-1 4.3c-1 2-3.8 1.3-6.6-.7q-1.4-.9-3.4-.6t-3 1.7c-1.5 2.6-3.4 4-5.2 2.6q-1.6-1.6-2.7-3.5.8-.3 2-.3 2.1.1 3-1c.5-.7 0-1 .6-3q.2-1.9 1.2-3c.6-.8 1.5-.6 2.3-.7 1-.2 1.6-.7 2.5-.2q1.6.6 2.7 2.3c1.4 1.6 1.2 2 2 2.5q1.6.8 3.3 0 1.4-.6 2.3-.5z"/>
|
||||||
|
<path fill="#21231e" d="m417.7 206.5-1-.4.2-.2v-.5l.2-.2v-.4l.2-.2v-.9l.2-.2v-1.2h.2v-.4h1v1.1l-.2.3v1h-.2v.7h-.2v.7h-.2v.3l-.2.3zm-7.2-.5.5-.8.3.2h.2l.3.3h.2l.3.3h.2l.3.2.2.2h.2l.3.2h.4v.2h.7l.2.2h1v-.2h.2l.2-.2.2-.3.2-.2 1 .5-.2.2-.2.3-.3.3v.2h-.2l-.2.2h-.2l-.3.2h-2l-.2-.2h-.2l-.3-.2h-.2l-.2-.2h-.2l-.3-.2-.2-.2h-.2l-.3-.3-.2-.2h-.3l-.3-.3-.2-.2zm-3-.4-.3-1h1.1v-.2h1.3v.2h.6v.2h.4v.2h.2l.2.2-.5.8h-.2v-.2h-.4v-.2zm-2.7 1.3-.8-.6v-.2h.2v-.2h.2v-.2l.2-.2h.2v-.2h.2l.2-.2h.2l.2-.2h.3v-.2h.8l.2-.2h.5l.2 1h-.7v.2h-.4v.2h-.4l-.2.2h-.2v.2h-.2v.2h-.2v.2h-.2zm-6 2.7.6-.8h.3v.2h.5l.2.2h.5l.3-.2h.3v-.2h.3v-.2h.2v-.3h.3v-.3h.3v-.3l.3-.3.2-.2.2-.2v-.2h.3v-.4l.3-.2.8.5v.2l-.3.2v.3l-.3.2h-.2v.3l-.3.2-.2.2-.2.2-.3.3h-.2l-.2.3h-.2v.3h-.3l-.3.2h-.4l-.3.2h-1.2l-.2-.2h-.2l-.2-.2-.2-.2h-.2zm-2.7-4.4.5 1 .3-.8v.3h.2v.2l.2.2v.2h.2v.2l.2.2v.2h.2v.2l.2.2.2.2.2.2v.2h.2v.2h.2v.2h.2v.2h.2v.2h.2l-.6 1-.2-.2-.2-.2-.2-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.3h-.2v-.2h-.2v-.2h-.2v-.3h-.2v-.2h-.2v-.4h-.2v-.2h-.2V205zm0 .8-.4-.4.5-.3v.8zm2.4-1v1H397v.2h-.4l-.5-1h1.2v-.2zm2.5-.8.8.5-.2.2-.2.2h-.2v.2h-.2l-.2.2h-.2l-.2.2h-.7l-.2.2h-1.2v-1h1.4l.2-.2h.4v-.2h.2v-.2h.2v-.2zm.4-2.8 1 .3v.3l-.2.3v1.7h-.2v.7h-.2l-1-.4h.2v-1.4h.2v-1.3zm1.4-3.2.8.6-.2.2v.2h-.2v.4h-.2v.4h-.2v.4l-.2.2v.7h-.2v.5l-1-.3v-.7l.2-.2v-.4l.2-.2v-.4h.2v-.4h.2v-.4h.2v-.2h.2v-.4zm0 0 .4-.5zm2.6-1 .2 1h-.6v.2h-1v.2h-.2l-.2.2-.8-.6.2-.2.2-.2h.2v-.2h.4l.2-.2h1.4zm0 0h.6zm3 0-.6.8H406v.2h-.5l-.2-1h.8v-.2h2.1zm-.6.8.3.2zm3.3 1.6-.6.6-.2-.2h-.2v-.2l-.2-.2-.2-.2-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2l-.2-.2h-.2v-.2h-.2l-.2-.2.5-1v.2h.2l.2.2h.2v.2h.2v.2h.2l.2.2.2.2.2.2h.2v.2h.2v.2l.2.2.2.2.2.2v.2zm2 2.5-.6 1-.2-.2-.2-.2-.2-.2v-.2h-.2v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.3-.2-.3.7-.6.3.2.2.3v.2h.2v.2l.2.2v.4h.2v.2l.2.2v.2h.2v.2l.2.2zm-.6 1-.2-.2h.2zm3.5-1 .2 1h-.5l-.2.2h-.8v.2h-1.3l-.2-.2h-.2l-.2-.2h-.2l.5-1v.2h.6l.2.2h1.1l.2-.2h.4l.2-.2zm2.8 0h-1l.5.5h-1l-.2.2h-.4l-.2.2h-.2l-.3-1 .2-.2h.8v-.2h1.5l.5.4zm-.5-.5h.6l-.2.5-.5-.5z"/>
|
||||||
|
<path fill="#edb92e" d="M391.8 221.2c.7-1 1-.8 1.2-1.3q.2-1.4-1-3.2c-2.2-4.6.3-4.2 3.6-2.4q1.5.7 3 2 1.4 1.5 2.7 2.4c1.6 1.2 2.7 1.2 3 1.7q1.4 3 1.8 4.2.6 1.2 1.6 4.2c0 .4-1 .5-1.8 2-.7 1.2-1.2 3.5-2 4.7q-2 2.6-3.4 2.4-1.5 0-1.8-4.2a4 4 0 0 0-1.5-3c-.5-.6-.6-.4-2-1.2a9 9 0 0 1-2.4-2q-.7-1-1-2c-.3-1-.8-1.6-.7-2.4a4 4 0 0 1 .8-2z"/>
|
||||||
|
<path fill="#21231e" d="m392.5 219.7 1 .3v.2l-.2.2v.2h-.2v.2h-.2v.2h-.2l-.2.2v.2h-.2l-.8-.5v-.2l.2-.2.2-.2.2-.2h.2v-.2h.2zm-.8-2.7 1-.5v.4l.2.2v.4l.2.3v.2l.2.2v2l-1-.3v-1.2l-.2-.2v-.4h-.2v-.4h-.2v-.4zm4.2-3-.6.8h-.3l-.2-.3h-.3l-.3-.3h-.3l-.3-.2h-.3l-.2-.2h-.5l-.2-.2h-.6v1.5l.3.4.2.3.2.4.2.5-1 .5v-.4l-.3-.4-.2-.4v-.7l-.2-.2v-1.5l.2-.2h.2l.2-.2h.2l.2-.2h1.2l.2.2h.6l.3.3.3.2h.3l.3.2.2.2.4.2h.3zm2.8 2-.7 1v-.4h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2l-.2-.2h-.2v-.2h-.2l-.2-.2-.2-.2h-.2l-.2-.2.5-1v.2h.2v.2h.2l.2.2h.2v.2h.2l.2.2h.2v.2h.2v.2h.2l.2.2.2.2.2.2h.2v.2zm2.8 2.4-.6.8h-.2v-.2h-.2v-.2h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2l-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2v-.2h-.2l.6-1v.2h.2l.2.2v.2h.2l.2.2.2.2h.2v.2h.2v.2h.2v.2h.2l.2.2.2.2h.2v.2h.2zm3.3 2-1 .3v.2-.2h-.2v-.2h-.4l-.2-.2h-.2l-.2-.2h-.3l-.2-.2h-.3v-.2h-.3v-.2h-.3v-.2h-.3v-.2l.6-.8.2.2h.2v.2h.3l.2.2h.2l.2.2h.2v.2h.4l.2.2h.4v.2h.2l.2.2h.2v.3zm-.2-.3v.2zm1.7 4.5-1 .4v-.7l-.2-.2v-.4l-.2-.2v-.5h-.2v-.4l-.2-.3v-.3h-.2v-.5h-.2v-.5h-.2v-.3l1-.4v.4l.2.3v.2l.2.3v.2l.2.3v.3l.2.3v.4h.2v.4l.2.2v.4h.2v.4zm0 0-1 .4h.3l.3.2-.5-.5.6.5-.5-.5.6.5-.5-.5.2.4-.2-.3v.2l1-.5v.2-.4.4l-.4-.5.6.5-.5-.5.6.5-.5-.5h.4v.3zm0-.2v.2zm1.6 4.7-1-.2v.3-.5l-.2-.3v-.2l-.2-.3v-.3l-.2-.3v-.2l-.2-.2v-.4l-.2-.2v-.2l-.2-.2v-.4l-.2-.2 1-.5v.4h.2v.5h.2v.5l.2.2v.3l.2.3v.5l.2.2v.3l.2.2v.2l.2.2v.6zm0-.3v.3zm-1.7 2.4-1-.4.2-.2v-.2h.2v-.2l.2-.2v-.2h.2v-.2h.2l.2-.2h.2v-.2h.2v-.2l1 .2v.4h-.2v.2h-.2v.2h-.2l-.2.2-.2.2-.2.2-.2.2-.2.2zm-2.2 5-.7-.7.2-.3v-.2h.2v-.3l.2-.2v-.4l.2-.2v-.5h.2v-.4h.2v-.5h.2v-.2l.2-.3v-.2l.2-.3v-.2l.2-.2 1 .5-.2.2v.3h-.2v.4h-.2v.5l-.2.2v.3l-.2.2v.3l-.2.2v.3l-.2.2v.3l-.2.2v.3h-.2v.3h-.2v.3l-.2.2v.2zm-3.7 2.7.2-1h.4v-.2h.2l.2-.2h.2v-.2h.2l.2-.2.2-.2.2-.2.2-.2.2-.3.2-.2v-.2h.2v-.3l1 .7h-.2v.3h-.3v.3h-.2v.3h-.4v.4h-.2v.2h-.2l-.2.2-.2.2-.2.2h-.3v.2h-.4v.2h-1zm-2.2-5h1v.3l.2.2v1.1h.2v.5l.2.2v.3l.2.2v.2h.2v.2h.2v.2h.2v1h-.8v-.2h-.2l-.2-.2-.2-.2v-.3h-.2v-.2l-.2-.2v-.3l-.2-.3v-.4h-.2v-1l-.2-.2v-1zm-1.2-2.6.7-.6.2.2v.2h.2v.2l.2.2v.2h.2v.3l.2.2v.2h.2v.7h.2v.6l-1 .2v-1h-.2v-.4l-.2-.2v-.2h-.2v-.2h-.2v-.4h-.2v-.2zm-2-1 .4-.8h.3v.2h.4v.2h.4v.2h.4v.2h.4v.2h.2v.2l-.6.7h-.2l-.2-.2h-.4v-.2h-.4l-.2-.2h-.2l-.2-.2h-.3zm-2.5-2 .7-.6.2.2.2.2.2.2.2.2h.2v.2h.2v.2h.2l.2.2h.2v.2h.2l.2.2-.5 1v-.2h-.2v-.2h-.4v-.2l-.2-.2h-.2l-.2-.2-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2v-.2zm-1.2-2.3 1-.4v.2h.2v.4h.2v.5h.2v.4h.2v.2l.2.2-.7.7h-.2v-.4h-.2v-.4h-.2v-.4l-.2-.2v-.8h-.2v-.2zm-.6-2.5h1v.8h.2v.4l.2.2v.2l.2.2v.2l-1 .5v-1h-.2v-.4h-.2v-.6h-.2v-.8zm1 0v.2zm.4-1.5v-1l.3.8v.4h-.2v.2h-.2v.8h-.2v.2h-1v-.9l.2-.2v-.4l.2-.2v-.2h.2v-.4h.2v-.2h.2l.4.7zm0-1h1l-.7.8-.2-.8zm0 0v1l-.4-.8.4-.3zm0 1h-1l.6-.8.4.7z"/>
|
||||||
|
<path fill="#21231e" d="m406.5 226 .2 1q-2.1.6-3.6 1.8c-1.5 1.2-1.7 2-2.3 3.2q.4-2.1 2-3.7a7 7 0 0 1 3.8-2.2zm-.6-1.6-.5-1q-2.4 0-4.7-1t-4.4-2.8q1.6 2 4.3 3.3 2.5 1.1 5.2 1.5z"/>
|
||||||
|
<path fill="#edb92e" d="M391.4 213.8c1 .4 1.6.8 2.8 1.8q1.2 1 1.5 3 0 1.7-.3 3.8-.3 1.4.5 3.6.5 2.3 2 3.5c2.3 2.5 4 5 3.4 6.7a4 4 0 0 1-1.5 1.8q-.7-.8-1.4-2.2a8 8 0 0 0-1.5-3.5c-.5-.6-.7-1.3-2-2.2-1-.5-2-.3-2.6-1-1.2-1.6-3-5.5-1.3-7.2s2.3-2.7 1-5.6q-.6-1.5-.6-2.4z"/>
|
||||||
|
<path fill="#21231e" d="m394.5 215.2-.7.8-.2-.2-.2-.2-.2-.2h-.2v-.2h-.2v-.2h-.4v-.2h-.2l-.2-.2h-.2l-.2-.2h-.4l.3-1h.4v.2h.4v.2h.4v.2h.4v.2h.2v.2h.2l.2.2.2.2h.2v.2h.2v.2zm1.7 3.2-1 .2v-.6h-.2v-.7h-.2v-.4l-.2-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2l.7-.8h.2v.2l.2.2v.2h.2v.2l.2.2v.2h.2v.4l.2.2v.4h.2v.8h.2v.2zm-.3 4h-1V221h.2v-2.4l1-.2v3.3l-.2.2zm-1 0v-.3zm1.4 3.5-1 .2v-.5l-.2-.3v-.4l-.2-.3v-.8h-.2v-1.4h1v1.9h.2v.7h.2v.7h.2zm1.8 3.2-.7.7v-.2l-.2-.2h-.2v-.4h-.2v-.2l-.2-.3v-.2h-.2v-.2l-.2-.3v-.2h-.2v-.5l-.2-.2-.2-.3v-.4h-.2l1-.4v.2l.2.2v.2l.2.3.2.2v.3l.2.2v.2h.2v.2h.2v.4h.2v.2h.2v.2h.2v.2zm3.6 7.2-1-.4.2-.2v-1.3l-.2-.2v-.4l-.2-.2v-.2l-.2-.2-.2-.2v-.2l-.2-.2-.2-.2v-.2l-.3-.2-.2-.3v-.3l-.3-.2-.2-.2-.2-.3-.2-.2-.2-.2-.2-.2-.3-.3h-.2l.7-.8.3.2.2.2.2.3.3.4.3.2.2.2v.3l.3.3.3.2.2.3v.2l.3.2.2.3v.2l.2.2.2.2v.2l.2.2v.2l.2.3v.7l.2.2v1.2l-.2.2zm-2.4 1.8 1-.4-.8-.2.2-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.2l.2-.2v-.2l1 .4v.2l-.2.2v.2l-.2.2v.2h-.2v.2h-.2v.2h-.2v.2h-.2v.2h-.2l-.2.2zm.8.2-.5.3-.3-.5zm-2-2.5 1-.3v.4h.2v.4h.2v.2h.2v.2l.2.2v.2h.2v.4l-.8.4v-.2h-.2v-.2l-.2-.2v-.2h-.2v-.2h-.2v-.4l-.2-.2v-.2zv-.3zm-1.5-3.3.8-.7v.2h.2v.2l.2.2v.2l.2.2v.2h.2v.5h.2v.4l.2.2v.4l.2.3v.7l-.8.2v-.4h-.2v-.5l-.2-.2v-.5h-.2v-.2l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2zm-2-2.2.4-1 .3.2.2.2h.2l.2.2.2.2.2.2v.2h.2v.2h.2v.2l.2.2.2.2v.2h.2v.2l-.7.7-.2-.2v-.2h-.2v-.2h-.2v-.4h-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.4v-.2zm-2.7-1.3.8-.6h.2v.2h.2v.2h.9l.2.2h.4l.2.2h.2l.2.2-.4 1v-.2h-.4l-.2-.2h-.9v-.2h-.4l-.2-.2h-.2l-.2-.2-.2-.2zm-1.2-7.8.7.7v.3h-.2v.3h-.2v2.2l.2.3v.5l.2.2v.2l.2.3v.2l.2.2v.2l.2.2v.2l.3.2v.2h.2v.3l.2.2-.7.7-.2-.3-.2-.3v-.2l-.2-.2v-.2h-.3v-.3l-.2-.3-.2-.4v-.2l-.2-.3v-.2l-.2-.2v-.6l-.2-.3v-2.1l.2-.3v-.2h.2l.2-.3.2-.3zm.7-5 1-.5v.5l.2.2v.3l.2.2v.7h.2v1.7l-.2.2v.2l-.2.2v.2h-.2v.3h-.2v.2l-.2.2-.3.3h-.2l-.2.3-.8-.6.2-.3h.2l.2-.3.2-.3.2-.2v-.3l.2-.2v-.2l.2-.3v-.7l-.2-.2v-.5l-.2-.2-.2-.4v-.2zm0-3v1l.4-.7v1.4h.2v1.1h.2l-1 .6v-.9h-.2v-.7l-.2-.2v-1l.4-.7zm-.6.5-.3-.7h.7l-.5.6zm.4.3v-1zm0-1"/>
|
||||||
|
<path fill="#edb92e" d="M434 226.2c-1.2 1-1.5 1-2 1.6q-.3 1.3.3 3.3c1.5 5-1.4 4.4-5 2.6-1.6-1-3.6-3.2-5.2-4.4-1.5-1-2.6-1-3-1.6l-1-4.3q-.1-1.2-1-4.4c.4-.7 1.4-1 2.5-2.5 1-1.7 2.2-4.7 3.6-6 3-3.2 6-4.5 6 1.3 0 1.4.3 3 1 3.6s1 .3 2.5 1q1.6 1 2.6 2c.7 1 .5 2 .7 2.8 0 1 .5 1.7.2 2.6q-.6 1.4-2 2.4z"/>
|
||||||
|
<path fill="#21231e" d="m432.6 228-1-.5h.2v-.4h.4v-.2h.4v-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l.8.8-.2.2h-.2v.2h-.2l-.2.2-.2.2h-.2v.2h-.4v.2zm0 2.9-.8.3v-.2l-.2-.2v-.7h-.2v-2.3h.2v-.2l1 .4v.2h-.2v2.1h.2v.7zm-5.4 3 .5-.8h.3l.3.3h.3l.3.3h.2l.3.2h.2l.3.2h.7l.2.2h.7v-.2h.2v-.4l.2-.3v-1.1l-.2-.3v-.3l-.2-.4 1-.4v.4l.2.4v.8l.2.3v1.1l-.2.4v.2l-.3.2-.2.2h-.2l-.2.2-.4.2h-1.2l-.3-.2h-.6l-.3-.3-.2-.2h-.3l-.3-.2-.4-.2-.3-.2zm-5.4-4.3.6-1 .2.2.3.2.2.3h.2v.3h.3v.3l.3.2.2.2h.2v.3h.3l.2.3.2.3.2.2.3.3h.2v.2l.2.2h.2l.2.2v.2h.3v.2h.2l.2.2.3.2-.5 1-.2-.2h-.2v-.2l-.3-.2h-.2l-.2-.2-.2-.3h-.2l-.2-.2h-.2v-.3h-.3l-.3-.3h-.2v-.3l-.3-.3h-.2l-.2-.3-.3-.3h-.2v-.2h-.3l-.2-.3-.3-.3-.3-.2-.3-.2h-.2zm-3.4-2 1-.2v-.2h.2v.2h.4l.2.2h.4v.2h.7l.2.2h.2l.2.2.3.2h.2l.2.2-.6 1v-.2h-.3v-.2h-.2l-.2-.2-.2-.2h-.3l-.2-.2h-.2v-.2h-.4l-.2-.2h-.2l-.2-.2h-.2v-.2h-.2v-.3zm.2.3v-.3zm-.6-4.2v-1l.5.4v.4l.2.2v1h.2v1.3h.2v1h.2v.5l-1 .2v-.7l-.2-.3v-.7l-.2-.3v-.7h-.2v-.8l-.2-.2v-.6l.4.4zm0-1h.5v.4zm-.5.6 1-.2v-.2l-.5-.2v1h-.3zm-.8-4.7 1 .4v-.2 1.1h.2v1.1l.2.3v.8l.2.2v.9l-1 .2V222l-.2-.2v-.8l-.2-.3v-.7h-.2v-.8l-.2-.2v-.3zm0 .3v-.3zm2.4-2.8 1 .5-.2.2-.2.2-.2.3-.2.2-.2.2h-.2v.2h-.2l-.2.2h-.2v.2h-.2v.2h-.2v.2h-.2l-1-.3h.2v-.4h.2v-.2h.4v-.2h.2l.2-.2.2-.2.2-.2h.2v-.2l.2-.2.2-.2v-.2h.2zm3.7-6.2.8.7h-.2v.3h-.2v.3l-.3.3v.2l-.2.2v.2h-.2v.3l-.2.3v.4l-.3.2v.2l-.2.2v.2l-.2.2v.2l-.2.2v.3l-.2.3v.2l-.2.2v.2h-.2v.2l-.2.2-.2.2-1-.4.2-.2.2-.3v-.2h.2v-.3l.2-.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.2v-.3l.2-.2v-.2h.2l.2-.3v-.2l.2-.2v-.3l.2-.2v-.2l.3-.2v-.2l.2-.2.2-.3v-.2l.2-.2h.2zm6.8 1.8h-1v-1.4l-.2-.4v-.6l-.2-.2v-.4l-.2-.2h-.3v-.2h-1.2l-.3.2-.3.2h-.2l-.3.3-.2.3-.3.2-.2.2-.3.2-.2.2-.3.2-.2.3-.8-.7.3-.3.3-.3.3-.2.2-.3.3-.3.4-.2.3-.2.2-.2h.2l.3-.3h.2l.3-.2h.3l.2-.2h1.2l.3.2.3.3.2.2.2.2.2.3v.6l.2.5v.4l.2.3v1.5zm1 3.3-.8.8-.2-.2v-.2h-.2v-.2l-.2-.2v-.6l-.2-.2v-.7l-.2-.2V212h1v2h.2v.3l.2.2v.2h.2v.2zm2.2 1-.4 1-.3-.2h-.2l-.3-.2h-.4v-.2h-.9v-.2h-.4v-.2h-.2v-.2l.6-.7v.2h.4v.2h.8l.2.2h.4l.2.2h.2l.2.2zm2.8 2.2-.8.6h-.2v-.2l-.2-.2h-.2v-.4h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.4v-.2h-.2l-.2-.2.3-1 .3.2h.2l.2.2.2.2h.2v.2h.2l.2.2h.2v.2h.2v.2h.2v.2h.2v.2h.2v.2h.2zm-.8.6.4.5zm1.5 2.3-1 .2V220l-.2-.2v-.8h-.2v-.4l.7-.6v.2l.2.2v.2l.2.2v2.2h.2v.2zm0 3-.8-.4v-2.1l-.2-.2v-.4h1v1l.2.2v1.8zm-1.8 2.6-.7-.8h.2v-.2l.2-.2v-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.2l.2-.2v-.2h.2v-.2l1 .4-.2.2v.2l-.2.2v.2h-.2v.4h-.2v.2h-.2v.2l-.2.2-.2.2v.2h-.2l-.2.2zm-.7-.8"/>
|
||||||
|
<path fill="#21231e" d="m418 222.6-.2-1a14 14 0 0 0 7.8-5.3c-1.2 3-4.3 5.3-7.7 6.3zm.2 1.5q0 .6.3 1 5 0 9.2 3c-2-2.3-5.8-3.8-9.5-4"/>
|
||||||
|
<path fill="#edb92e" d="M432.7 233.3a11 11 0 0 1-3.4-1.5c-1.6-1.3-1.2-3.8.3-6.4q.6-1.4.2-3.5a6 6 0 0 0-1.4-3.5c-2.4-2.3-3.8-4.8-2.6-6.6q1.4-1.8 2.8-2.7.4 1 .4 2.5c0 1.4.3 3 1 3.6s1 .3 2.5 1q1.6 1 2.6 2c.7 1 .6 2 .7 2.8 0 1 .5 1.7.2 2.6q-.6 1.4-2 2.4c-1.2 1-1.5 1-2 1.6q-.3 1.3.3 3.3.4 1.4.4 2.3z"/>
|
||||||
|
<path fill="#21231e" d="m429 232.2.6-.8h.2v.2h.2l.2.2h.2l.2.2h.2l.2.2h.4v.2h.4l.2.2h.4l.2.2h.2l-.3 1h-.4v-.2h-.4l-.2-.2h-.3v-.2h-.4l-.2-.2h-.2l-.2-.2h-.2v-.2h-.2l-.3-.2h-.2v-.2zm0-7 1 .5v.2h-.2l-.2.4v.2l-.2.2v.2l-.2.3v.4l-.2.2v.5l-.2.2v1.5l.2.3v.2h.2v.3l.2.2.2.2-.6.8-.2-.2-.3-.3v-.2h-.2v-.3l-.2-.2v-.4l-.2-.3V228h.2v-.7l.2-.3.2-.3v-.3l.2-.2v-.3l.3-.3v-.5zm.3-3.3 1-.2v.8h.2v1.9l-.2.2v.7h-.2v.2l-1-.4h.2v-.6h.2V222zm-1.3-3.2.7-.7v.2h.2v.4h.2v.2l.2.2v.2l.2.2v.2l.2.2v.5h.2v.7l.2.2v.8h-1v-1.2l-.2-.2v-.4h-.2v-.4h-.2v-.2l-.2-.2v-.2h-.2v-.2l-.2-.2zm-2.7-7.2 1 .6h-.2v.7l-.2.2v.7l.2.2v.4l.2.2v.2l.2.2v.2l.2.2v.2l.3.2v.2l.2.2.2.2v.2l.3.3.2.3v.2l.3.2.2.2h.2l-.7.8-.2-.2-.2-.2-.3-.2-.2-.2v-.3l-.2-.2-.2-.2-.2-.2-.2-.2v-.2l-.3-.2-.2-.3h-.2v-.3l-.2-.2v-.3l-.2-.2v-.2l-.2-.3v-.4l-.2-.2v-1l.2-.3v-.2l.2-.2v-.2zm3.8-2.5-.8.4.7.3h-.2l-.2.2h-.2l-.2.2h-.2v.2h-.2v.2h-.2l-.2.2-.2.2-.2.2-.2.2-.2.2v.2h-.2v.2l-.2.2-1-.6h.2v-.2l.2-.2.2-.2v-.2h.2v-.2h.2v-.2h.2v-.2h.2l.2-.2.2-.2h.2v-.2h.2l.2-.2.2-.2h.2v-.2zm-.6 0 .5-.5v.6h-.6zm1 2.8h-1v-2.1h-.2v-.4l1-.3v1h.2v2zm1 3.3-.7.8-.2-.2v-.2h-.2v-.2l-.2-.2v-.7l-.2-.2v-.7l-.2-.2V212h1v1.7h.2v.6l.2.2v.2h.2v.2l.2.2zm2.3 1-.4 1-.3-.2h-.2l-.3-.2h-.4v-.2h-.9v-.2h-.4v-.2h-.2v-.2l.6-.7v.2h.4v.2h.8l.2.2h.4l.2.2h.2l.2.2zm2.8 2.2-.8.6h-.2v-.2l-.2-.2h-.2v-.4h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.4v-.2h-.2l-.2-.2.3-1 .3.2h.2l.2.2.2.2h.2v.2h.2l.2.2h.2v.2h.2v.2h.2v.2h.2v.2h.2v.2h.2zm-.8.6.4.5zm1.5 2.3-1 .2V220h-.2v-.8h-.2v-.4l.7-.6v.2l.2.2v.2l.2.2v2h.2v.2zm0 3-.8-.4v-2.2l-.2-.2v-.4h1v1l.2.2v1.8zm-1.8 2.6-.7-.8h.2v-.2l.2-.2v-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.2h.2v-.4h.2v-.2l1 .4-.2.2v.2l-.2.2v.2l-.2.2v.2h-.2v.2h-.2v.2l-.2.2-.2.2v.2h-.2l-.2.2zm-1.7 1.4-1-.5v-.2h.2v-.2h.2v-.2h.4v-.2h.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l.8.8-.2.2h-.2v.2h-.2l-.2.2-.2.2h-.2l-.2.2h-.2zh-.2v.2zm0 2.9-1 .3v-.7h-.2v-3h.2l1 .4h-.2v2.6h.2v.2zm-.2 2.8.3-1-.6.5v-1h-.2v-.9l-.2-.2 1-.5v.9h.2v1h.2v.7zm.7-.5v.7l-.7-.2zm-.5 0-.2.5zm.5 0v.7l-.7-.2z"/>
|
||||||
|
<path fill="#edb92e" d="M410.6 216.4c4 0 8 3.4 8.4 7.7.6 4.4-2.2 7.7-6 7.8-4 0-7.5-3-8.3-7.3-.7-4 1.8-8 6-8z"/>
|
||||||
|
<path fill="#21231e" d="m419.5 224-1 .2v-.8l-.2-.4v-.3l-.2-.4v-.3l-.2-.4-.2-.3-.2-.3-.2-.4-.2-.3-.2-.3-.3-.2-.2-.3-.3-.3-.2-.2-.3-.3-.3-.2-.3-.2-.4-.2h-.3l-.3-.3h-.4l-.3-.3h-.3l-.4-.2h-.3l-.3-.2h-1.5v-1h1.9l.4.2h.3l.4.3h.4l.4.3.4.2.3.2.5.2.3.3.3.2.4.2.3.3.3.3.3.3.3.4.2.3.2.4.3.4.2.4.2.4.2.4v.4l.2.4v.4l.2.4v.8zm-6.6 8.3v-1h.9l.2-.2h.6l.3-.2h.4l.2-.2.3-.2h.2l.2-.3h.2l.2-.3.3-.3.3-.2v-.2l.3-.3.2-.2v-.3l.3-.3.2-.4v-.3l.2-.3v-.7l.2-.3V224l1-.2v2.5l-.2.4v.8l-.3.4v.3l-.2.3v.4l-.3.3-.2.3-.3.3h-.2l-.2.4-.3.2-.3.3h-.3l-.3.3-.3.2h-.2l-.3.3h-.4l-.2.2h-.4l-.3.2H413zm-8.7-7.7 1-.2v.7l.2.5v.3l.3.4v.3l.3.3.2.4.2.3.2.3.2.3.2.3.3.3.2.3h.3l.2.3.3.3.2.2.3.2.3.2h.4l.3.3.3.2h.3l.3.2h.7l.3.2h1.2v1h-2l-.3-.2h-.4l-.4-.3h-.4l-.4-.3-.3-.2-.4-.2-.3-.2-.4-.2-.4-.2-.3-.3-.2-.2-.2-.2-.3-.3-.2-.3-.3-.3-.2-.4-.2-.3-.2-.3-.2-.4-.2-.3-.2-.4v-.4l-.2-.4-.2-.4v-.8zm6.2-8.7v1h-1l-.3.2h-.4l-.3.2h-.3l-.3.3h-.2l-.2.3-.3.2-.2.2v.2l-.3.2-.2.2-.2.3-.2.3v.2l-.2.3v.4l-.3.3v.6l-.2.3v.7l-.2.3v.7l.2.4v.8l-1 .2v-3.3l.2-.3v-.3l.2-.4v-.3l.3-.4v-.3l.3-.3.2-.3.2-.3.2-.3h.2l.2-.4.3-.2.3-.2.3-.2.3-.2.3-.2.3-.2h.3l.4-.2h.4l.3-.2h.9z"/>
|
||||||
|
<path fill="#edb92e" d="M411 218.6c2.8-.2 5.7 2.4 6 5.5.5 3.3-1.5 5.8-4.4 5.8-2.8 0-5.5-2.3-6-5.3s1.3-5.8 4.3-6z"/>
|
||||||
|
<path fill="#21231e" d="m417.6 224-1 .2v-.6l-.2-.3v-.2l-.2-.3v-.4l-.2-.2v-.2l-.2-.3-.2-.3v-.2l-.3-.2-.2-.2-.2-.3-.2-.2-.2-.2h-.2l-.2-.3h-.2l-.3-.3h-.2l-.2-.2-.4-.2h-.5v-.2h-1.1v-1h1.1l.4.2h.3l.2.2h.2l.3.2h.3l.2.3h.3l.2.2.3.2h.2l.3.3.2.3.2.2.2.2v.2l.3.3h.2v.4l.3.3v.3l.3.2.2.3v.3l.2.3v.8l.2.3zm-5 6.3v-1h1.1l.2-.2h.4l.2-.2h.2v-.2l.3-.2h.3v-.2l.3-.2v-.2h.2l.2-.3v-.2l.2-.2.2-.3v-.7l.2-.2v-1.6h1v2.2l-.2.3v.2l-.2.3v.2l-.3.3-.2.2v.3l-.3.2-.2.2-.2.2-.3.2-.2.2-.2.2h-.2l-.2.2h-.2l-.3.2h-.2l-.2.2zm-6.5-5.8h1l.2.4.2.4v.2l.2.3.2.3v.3l.3.2.2.2.2.2v.2l.3.2.2.2.3.3.2.2.2.2h.2v.3h.3l.2.2h.7l.2.2h1.1v1H411l-.4-.2h-.2l-.3-.2h-.2l-.2-.3h-.3l-.2-.3-.4-.3-.2-.3-.2-.2-.2-.2-.2-.2-.2-.2-.2-.2-.2-.3v-.2l-.3-.2-.2-.2-.2-.3v-.3l-.2-.3v-.3l-.2-.2v-.7zm5-6.4v1h-.6l-.2.2h-.5l-.2.2h-.2l-.2.2-.2.2h-.2l-.2.3v.2l-.3.2-.2.3v.2h-.2v.3l-.2.2v.2l-.2.2v.7l-.2.2v1.7h-1V222h.2v-.4l.2-.3v-.2l.2-.2v-.2l.3-.3v-.2h.2l.2-.3.2-.2.2-.2.2-.2h.2l.2-.2h.3l.2-.3h.2l.2-.2h.6l.3-.2h.6z"/>
|
||||||
|
<path fill="#0c4076" d="M411.2 220.7c2 0 3.6 1.5 4 3.5.2 2-1 3.6-3 3.6a4 4 0 0 1-3.7-3.4c-.3-2 1-3.6 2.7-3.7"/>
|
||||||
|
<path fill="#21231e" d="m415.6 224-1 .3v-.4l-.2-.2v-.4h-.2v-.3l-.2-.2-.2-.3-.2-.2-.2-.2h-.2l-.2-.2-.2-.2h-.2l-.2-.2h-.6v-.2h-.6v-1h1l.2.2h.2l.3.2h.3v.2h.3l.2.3v.2h.3l.2.2.2.2.2.3.3.2v.4h.2v.3l.2.3v.4zm-3.3 4.3v-1h.7l.2-.2h.4v-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.5l.2-.2v-1.1l1-.3v2h-.2v.3l-.2.3-.2.3-.2.2h-.2v.2l-.2.2h-.2v.2h-.3l-.2.2h-.4v.2h-1zm-4.3-3.8 1-.2v.5l.2.3v.4h.2v.2l.2.2.2.2.2.3.2.2.2.2h.3v.2h.4l.3.2h.8v1h-1.3l-.2-.2h-.2l-.3-.2h-.2v-.2h-.2v-.2l-.3-.2-.3-.2v-.2h-.2v-.3h-.3v-.3l-.2-.2v-.2h-.2v-.5l-.2-.2v-.5zm2.7-3.8h1l-.5.5h-.7l-.2.2h-.2v.2h-.2l-.2.2-.2.2-.2.2v.2h-.2v.5h-.2v1.3l-1 .3v-1.8h.2v-.5h.2v-.3h.2v-.3h.3v-.3h.3v-.2h.3v-.2h.3v-.2zl.2-.2h.4l-.5.5zm1 0v.5h-.5zm0 0h-1l.5-.5zm-1 0v-.5h.5z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#fff" d="M482 183c4 0 7 3 7 7s-3 6.7-7 6.7c-3.6 0-6.7-3-6.7-6.8 0-4 3-7 6.8-7z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 189.8h-1c0-2.3-1.4-4.6-3.5-5.6a6 6 0 0 0-3-.7v-1q4.1.1 6.4 3.5a7 7 0 0 1 1 3.8zm-7.4 7.4v-1q4-.2 5.8-3.4a6 6 0 0 0 .7-3h1c0 2.6-1.5 5.2-3.7 6.5a7 7 0 0 1-3.7 1zM475 190h1a6.4 6.4 0 0 0 6.3 6.2v1a7 7 0 0 1-6-3.4 7 7 0 0 1-1.2-4zm7.3-7.5v1a6 6 0 0 0-5.5 3.5 6 6 0 0 0-.7 3h-1q.2-4.2 3.5-6.4a7 7 0 0 1 3.8-1z"/>
|
||||||
|
<path fill="#fff" d="M482 171c4 0 7 3 7 7a6.8 6.8 0 1 1-13.7-.2c0-3.7 3-6.7 6.8-6.7z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 178h-1c0-2.4-1.4-4.7-3.6-5.8a6 6 0 0 0-3-.6v-1q4.1.1 6.4 3.4a7 7 0 0 1 1 4zm-7.4 7.3v-1c2.5 0 4.7-1.4 5.8-3.4a6 6 0 0 0 .7-3h1c0 2.5-1.5 5-3.7 6.4a7 7 0 0 1-3.7 1zM475 178h1a6.4 6.4 0 0 0 6.3 6.3v1a7 7 0 0 1-6-3.4 7 7 0 0 1-1.2-4zm7.3-7.4v1a6 6 0 0 0-5.5 3.5 6 6 0 0 0-.7 3h-1c0-2.6 1.4-5 3.5-6.3a7 7 0 0 1 3.8-1z"/>
|
||||||
|
<path fill="#fff" d="M482 159.6c4 0 7 3 7 7a6.8 6.8 0 1 1-13.7-.2c0-3.7 3-6.8 6.8-6.8z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 166.5h-1c0-2.4-1.4-4.7-3.6-5.7a6 6 0 0 0-3-.7v-1c2.6 0 5 1.5 6.4 3.6a7 7 0 0 1 1 4zM482 174v-1c2.5 0 4.7-1.5 5.8-3.6q.6-1.5.7-3h1c0 2.6-1.4 5-3.5 6.4a7 7 0 0 1-4 1zm-7.2-7.5h1c0 2.4 1.4 4.7 3.6 5.7a6 6 0 0 0 2.7.7v1a7 7 0 0 1-6-3.6 7 7 0 0 1-1.2-4zm7.3-7.4v1a6 6 0 0 0-5.5 3.6 6 6 0 0 0-.7 3h-1c0-2.6 1.4-5 3.5-6.4a7 7 0 0 1 3.8-1z"/>
|
||||||
|
<path fill="#fff" d="M482 127c4 0 7 3.2 7 7s-3 6.8-7 6.8a7 7 0 0 1 .1-13.8z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 134h-1c0-2.4-1.4-4.7-3.6-5.8a6 6 0 0 0-3-.6v-1q4.1.1 6.4 3.4a7 7 0 0 1 1 4zm-7.4 7.3v-1q4-.2 5.8-3.4a6 6 0 0 0 .7-3h1c0 2.5-1.5 5-3.7 6.3a7 7 0 0 1-3.7 1zM475 134h1c0 2.3 1.4 4.6 3.5 5.6a6 6 0 0 0 2.8.7v1a7 7 0 0 1-6-3.4 7 7 0 0 1-1.2-4zm7.3-7.4v1a6 6 0 0 0-5.5 3.5 6 6 0 0 0-.7 3h-1c0-2.6 1.4-5 3.5-6.3a7 7 0 0 1 3.8-1z"/>
|
||||||
|
<path fill="#fff" d="M482 137.2c4 0 7 3 7 6.8s-3 7-7 7c-3.6 0-6.7-3.2-6.7-7s3-6.8 6.8-6.8z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 144h-1c0-2.3-1.4-4.6-3.5-5.7a6 6 0 0 0-3-.6v-1c2.7 0 5.2 1.4 6.5 3.6a7 7 0 0 1 1 3.7m-7.4 7.4v-1c2.5 0 4.7-1.4 5.8-3.4a6 6 0 0 0 .7-3h1c0 2.6-1.5 5-3.7 6.4a7 7 0 0 1-3.7 1zM475 144h1c0 2.4 1.4 4.7 3.5 5.7a6 6 0 0 0 2.8.7v1a7 7 0 0 1-6-3.5 7 7 0 0 1-1.2-4zm7.3-7.4v1a6 6 0 0 0-5.5 3.6q-.7 1.6-.7 2.8h-1c0-2.5 1.4-5 3.5-6.2a7 7 0 0 1 3.8-1.2z"/>
|
||||||
|
<path fill="#fff" d="M482 148.2c4 0 7 3 7 6.8s-3 7-7 7c-3.6 0-6.7-3.2-6.7-7s3-6.8 6.8-6.8z"/>
|
||||||
|
<path fill="#21231e" d="M489.5 155h-1c0-2.3-1.4-4.6-3.6-5.7a6 6 0 0 0-3-.6v-1q4.1.1 6.4 3.4a7 7 0 0 1 1 4zm-7.4 7.4v-1c2.5 0 4.7-1.4 5.8-3.4a6 6 0 0 0 .7-3h1c0 2.6-1.5 5-3.7 6.4a7 7 0 0 1-3.7 1zM475 155h1c0 2.4 1.4 4.7 3.5 5.7a6 6 0 0 0 2.8.7v1a7 7 0 0 1-6-3.4 7 7 0 0 1-1.2-4zm7.3-7.4v1a7 7 0 0 0-5.5 3.5 6 6 0 0 0-.7 3h-1c0-2.5 1.4-5 3.5-6.3a7 7 0 0 1 3.8-1z"/>
|
||||||
|
<path fill="#edb92e" d="M487 195.5c1 1.6.7 2 1.4 2.4q1.3.8 3.4.4c5.3-1 4.5 2.3 2.2 6-1 1.7-3.8 3.5-5.2 5s-1.6 2.8-2 3.2h-9.2c-.5-.4-.7-1.7-2-3.2-1.5-1.5-4.2-3.3-5.3-5-2.4-3.7-3-7 2.2-6q2 .4 3.4-.5c.6-.6.3-1 1.3-2.5a7 7 0 0 1 2.2-2.3c1-.5 1.8-.2 2.6-.2 1 0 2-.3 2.8.2a7 7 0 0 1 2.2 2.3z"/>
|
||||||
|
<path fill="#21231e" d="m488.7 197.5-.7.8-.2-.2h-.2v-.4h-.2v-.4h-.2v-.4l-.2-.2v-.2l-.2-.2-.2-.3v-.2l.8-.6v.2h.2v.3l.2.2v.2h.2v.4h.2v.4h.2v.4h.2zm-.7.8-.3-.3zm3.8-.4v1h-2.4l-.2-.2h-.2l-.2-.2h-.3l-.2-.2.7-1v.2h.2v.2h.8l.2.2h1.9zm2.6 6.6-.8-.6.2-.3.2-.3.2-.4.2-.3v-.3l.3-.3v-.2l.2-.2v-.6l.2-.2v-1.3l-.2-.2h-.2v-.2h-.5l-.2-.2h-2l-.2-1h2.8l.3.2h.2l.2.2.2.2.2.2.2.2v.3l.2.3v1.7l-.2.3v.3l-.3.2v.4l-.3.3v.2l-.3.4v.3l-.3.2-.3.4zm-5.3 5.2-.6-.7.3-.2v-.2h.3l.2-.3v-.2h.3l.2-.2h.2v-.3h.2l.2-.3.2-.2h.2l.2-.3h.2v-.3l.3-.2h.2l.2-.3v-.2l.2-.2.2-.2h.2v-.3h.3v-.4h.3v-.3h.2v-.3l1 .6-.2.2-.2.3v.3l-.2.2h-.2l-.2.3v.2l-.3.2-.3.3v.2h-.3l-.2.3-.2.2h-.2l-.2.3-.2.2-.3.3-.3.3h-.2v.3l-.3.2h-.2v.3l-.3.2h-.2v.3h-.2l-.2.3zm-2.3 3.3v-1l-.3.2v-.2h.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.2v-.2h.2v-.2l.2-.2.2-.3.3-.3.8.6v.2l-.2.2-.2.2-.2.3v.2h-.2v.2l-.2.2v.2l-.2.2v.2h-.2v.4h-.2v.2l-.2.2v.2h-.5zm.3 0h-.2zm-5 .2v-1h4.7v1h-4.5zm0-1h.2zm0 0v1zm-4.7.8.6-.8h-.4 4.2l.2 1h-4.7l-.3-.2zm.3 0h-.2zm-2.4-3.2.7-.7v.2l.2.2.2.3.2.3.2.2v.2h.2v.4h.2v.4h.2v.4h.2v.4h.2v.2l-.5.8-.2-.2v-.2l-.2-.2v-.2h-.2v-.4h-.2v-.4l-.2-.2v-.2h-.2v-.2l-.2-.3-.2-.2-.2-.3-.2-.2zm-5.4-5.2 1-.6v.2l.2.3h.2v.3l.2.2h.2v.3h.3v.3l.2.2h.2l.2.3.2.2v.2l.3.2h.3l.2.3h.2l.2.2v.2h.2l.2.3h.2l.2.3h.2v.3l.3.2h.2v.3h.3v.3l-.6.8-.2-.2-.3-.3-.3-.2-.3-.2v-.2l-.2-.2h-.2l-.2-.3h-.2l-.2-.2h-.2v-.3l-.3-.2h-.2l-.2-.3v-.2h-.3l-.2-.3v-.2h-.3l-.2-.3v-.2l-.3-.2h-.2v-.3l-.3-.2-.2-.2-.2-.2zm2.7-6.7v1l-.6-.2h-2.1l-.2.2h-.2v.2h-.2v.4l-.2.2v1.1l.2.2v.3l.2.3v.2l.2.3.2.4v.3l.3.3.3.4.2.3.2.3-1 .6v-.4l-.3-.3-.2-.4-.2-.4v-.3l-.3-.4v-.4l-.3-.3v-.4l-.2-.3v-.6l-.2-.3v-1l.2-.2v-.2l.3-.2.2-.2.2-.2.4-.2h.3l.3-.2zm3-.5.8.8-.2.2h-.2l-.2.2h-.4l-.2.2h-3v-1h2.5l.2-.2h.4v-.2zm1.4-2.3.7.6v.3l-.2.2v.2h-.2v.4h-.2v.8h-.2v.2h-.2v.2l-.2.2-.8-.7h.2v-.4h.2v-.4h.2v-.2l.2-.2v-.4h.2v-.3h.2v-.3zm0 0 .2-.4zm2.3-2.4.5.8-.2.2h-.2v.2h-.2v.2h-.2l-.2.2-.2.2-.2.2v.2h-.2v.2l-.2.2-.2.2v.2l-1-.6.2-.2.2-.2v-.2h.2v-.2l.2-.2h.2v-.2l.2-.2.2-.2h.2v-.2l.2-.2h.2v-.2h.2zm2.8-.3v1h-2.4l-.5-.7h.2v-.2h.4l.2-.2zm3 .3-.5.8h-.4v-.2H482v-1h2.5l.2.2h.2zm0 0 .3.2zm2 3.2v-1l-.4.8-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.2v-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.4v-.2h-.2v-.2l.4-.8h.2l.2.2.2.2h.2v.2h.2v.2h.2v.2h.2v.2h.2v.2l.2.2.2.2v.2h.2v.2h.2v.2zm.4-.8.5.8h-1zm-.4-.2v1l-.4-.2zm-.4.8-.5-.8h1l-.4.8z"/>
|
||||||
|
<path fill="#21231e" d="m481.4 212.8-1-.2q-.7-5.6-4.3-9.6c2.8 1.8 4.8 5.7 5.4 9.8z"/>
|
||||||
|
<path fill="#edb92e" d="M494.2 198.3q-.6 1.7-2 3.5c-1.5 1.6-4 .5-6.5-1.6q-1.4-1-3.6-1a6 6 0 0 0-3.4 1c-2.5 2-5 3.2-6.6 1.6q-1.4-1.7-2-3.5h2.5q2 .6 3.4-.4c.6-.6.3-1 1.3-2.5a7 7 0 0 1 2.2-2.3c1-.5 1.7-.2 2.6-.2s2-.3 2.8.2q1.2.8 2.2 2.3c1 1.6.7 2 1.3 2.4q1.5.8 3.5.4 1.5-.3 2.4 0z"/>
|
||||||
|
<path fill="#21231e" d="m492.6 202-.7-.6v-.2l.2-.2.2-.2v-.2h.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.2v-.2l.2-.3v-.4l1 .5v.4l-.2.2v.2l-.2.2v.4h-.2v.2l-.2.2v.3h-.2v.2h-.2v.2h-.2v.3l-.2.2-.2.2h-.2zm-7.3-1.4.7-.7h.2l.2.2.3.2.2.2.3.3h.3l.2.2h.2l.2.2h.2l.2.2h.2l.2.2h.4l.2.2h1.6l.2-.2.2-.2h.2v-.2l.8.7-.2.3h-.2v.3h-.3l-.3.2h-.3l-.2.2h-1.9l-.3-.2h-.3l-.2-.2h-.3l-.2-.2h-.3l-.2-.2-.2-.2h-.3l-.2-.3h-.3l-.2-.3-.2-.2-.2-.2-.2-.2-.3-.2zm.7-.7.5.3zm-4-.2v-1h2v.2h.7v.2h.4l.2.2h.2l.2.2h.2v.2h.2l-.7.8h-.2v-.2h-.2v-.2h-1v-.2h-1v-.2zm-3 .8-.7-.7v-.2h.2l.2-.2h.2v-.2h.2l.2-.2h.5v-.2h1.2v-.2h1v1h-1.5v.2h-.5l-.2.2h-.4v.2h-.2zm-7.3 1.5.7-.6.3.2.2.2h.2v.2h1.7v-.2h.5l.2-.2h.2v-.2h.3l.2-.2.2-.2h.3l.2-.3h.2l.2-.3.3-.2v-.2h.3l.7.6-.3.2-.2.2-.3.2-.2.2-.2.2h-.3l-.2.3h-.3l-.2.2h-.2l-.3.2h-.2l-.2.2h-.2l-.2.2h-.3l-.3.2H473l-.4-.2h-.2l-.2-.2-.2-.2zm-1.7-4 .2 1 .3-.7v.2l.2.2v.3l.2.2v.4l.2.2v.2h.2v.4h.2v.4h.2v.2h.2v.2h.2v.2l.2.2-.7.7h-.2v-.2h-.2v-.2h-.2v-.5h-.2v-.2h-.2v-.3l-.2-.2v-.2h-.2v-.4h-.2v-.4l-.2-.2v-.4h-.2l.4-.8zm-.4.7-.2-.6.6-.2zm3-.6-.2 1-.2-.2H470l-.2-1h2.4zm3-.5.7.8-.2.2h-.2l-.2.2h-.4l-.2.2h-2.8v-1h2.3l.2-.2h.4v-.2h.2zm.7.8-.5.4zm.6-3 .7.5v.3l-.2.2v.2h-.2v.4h-.2v.8h-.2v.2h-.2v.2l-.2.2-.8-.7h.2v-.4h.2v-.4h.2v-.2l.2-.2v-.4h.2v-.3h.2zm2.3-2.5.5.8-.2.2h-.2v.2h-.2v.2h-.2l-.2.2-.2.2-.2.2v.2h-.2v.2l-.2.2-.2.2v.2l-1-.6.2-.2.2-.2v-.2h.2v-.2l.2-.2h.2v-.2l.2-.2.2-.2h.2v-.2l.2-.2h.2v-.2h.2zm2.8-.3v1h-2.3l-.5-.7h.2v-.2h.4l.2-.2zm3 .3-.5.8h-.4v-.2H482v-1h2.5l.2.2h.2zm0 0 .3.2zm2.4 2.4-.8.6-.2-.2v-.2h-.2v-.2l-.2-.2-.2-.2-.2-.2v-.2h-.2v-.2h-.2v-.2h-.4v-.2h-.2v-.2l.4-.8h.2l.2.2.2.2h.2v.2h.2v.2h.2v.2h.2v.2h.2v.2l.2.2.2.2v.2h.2v.2h.2zm1.3 2.3-.7.8-.2-.2h-.2v-.4h-.2v-.4h-.2v-.4l-.2-.2v-.2l-.2-.2-.2-.3v-.2l.8-.6v.2h.2v.3l.2.2v.2h.2v.4h.2v.4h.2v.4h.2zm-.7.8-.3-.3zm3.8-.4v1h-2.4l-.2-.2h-.2l-.2-.2h-.4v-.2h-.2l.7-1v.2h.2v.2h.8l.2.2h1.9zm3 .5-1-.4.3.8H492l-.2-1h2.4l.5.7zm-.5-.7h.6l-.3.7zm0 .5.4.2zm0-.5h.6l-.3.7z"/>
|
||||||
|
<path fill="#edb92e" d="M482 211.7c4.4 0 8 3.4 8 7.7s-3.6 7.8-8 7.8-7.6-3.5-7.6-7.8 3.5-7.8 7.7-7.8z"/>
|
||||||
|
<path fill="#21231e" d="m483 212.8.8-.2q.8-5.6 4.3-9.6a15 15 0 0 0-5 9.8z"/>
|
||||||
|
<use xlink:href="#rs-b" width="1350" height="900" transform="matrix(-1 0 0 1 964.3 0)"/>
|
||||||
|
<path fill="#edb92e" stroke="#21231e" d="M577 293.6c2 .3 3 1.5 3 2.8s-1 2.6-3 3l-1.6.2c-60.8 5.2-125.7 5.2-186.5 0l-1.6-.3c-2-.3-3-1.6-3-3 0-1.2 1-2.4 3-2.7l1.5-.3c60.7-5.2 125.6-5.2 186.4 0q.8 0 1.5.3z"/>
|
||||||
|
<path fill="#21231e" d="M412 298.4h-.2v-.2h-.2v-1h.4v-.2.2-.2h2.3l.7-.2h3l.7-.2h3l.7-.2h3l.8-.2h3.3l.7-.2h3.8l.7-.2h.8v1.2h-2.9l-.7.2H428l-.7.2h-2.7l-.8.2H421l.7.2h3.7l.7.2h2.9l.7.2h3.7l.7.2h1.5v1.2h-3.7l-.7-.2h-3.8l-.7-.2h-2.9l-.7-.2H420l-.6-.2h-2.9l-.7-.2H413zv-.2zm46.8-3.3v1.3H453l-.7.2h-6.6l-.7.2h-5l-1 .2h-3.5v-1.2h3.5l.8-.2h5.2l.7-.2h5.9l.7-.2h6.8zm23.3 0v1h-13.7l-.7.2h-8.8v-1h1.4l.8-.2H482zm23.5 0v1.3h-11.1l-.7-.2H482v-1zm23.3 1v1h-5.2l-.8-.2h-5.2l-.7-.2h-6.5l-.8-.2h-4.3V295h.7l.7.2h7.5l.8.2h5l.8.2h5.1l.6.2h2.3zm23.4 1v.2h.4v1h-.2l-.2.2h-3l-.7.2h-2.2l-.7.2h-3.8l-.8.2h-2.8l-.7.2H534l-.8.2h-3.6l-.8.2v-1.2h2.2l.7-.2h3.7l.8-.2h3.7l.7-.2h1.8-1.4l-.8-.2h-2.7l-.7-.2H533l-.6-.2h-3.6V296h6.5l.7.2h3l.8.2h2.8l.7.2h3l.7.2h3l.7.2h1.5zm-116.7 1.4h.2l.2.2v.8h-.2v.2h-.2zm93.3 1.2h-.2l-.2-.2v-.2h-.2v-.8h.2v-.2h.4v1.2zm-50.3-36.2v-1h3.8v1h-4zm-1.2-1h1.2v1h-1.2zm-.6 0h.6v1h-.6zm-1 0h1v1h-1zm-.8 0h.6v1h-.7v-1zm-.5 0h.4v1h-.5v-1zm-.8 0h.8v1h-.8zm-1.2 0h1.2v1h-1.2zm-1 0h1v1h-1zm-.2 0h.2v1h-.2zm-1 0h1v1h-1zm-2.5 1v-1h2.6v1zm-.3-1h.2v1h-.2zm-.2 0h.2v1h-.2zm-1 0h1v1h-1zm-.5 0h.4v1h-.4zm-.4 0h.4v1h-.4zm-1.2 0h1.2v1h-1.2zm0 0v1zm0 1h-.2zm-1.2-1h1.2v1l-1.2.2v-1zm-.4 0h.4v1.2h-.4v-1zm-.8.2h.8v1h-.8zm0 0h.3-.2zm-.3 0h.3v1h-.3zm0 1h-.5zm-1-1h1v1h-1zm-1 0h1v1h-1zm-.2 0v1zm-1 0h1v1h-1zm-.6 0h.5v1h-.4v-1zm0 0v1zm-4.2 1.2v-1h3.5v-.2h.4v1h-2.7l-.2.2h-.8zm-.6-1h.5v1h-.6v-1zm-1.7 0h1.6v1h-1.7zm-.2 0h.3v1h-.2v-1zm-1.3 0h1.4v1h-1.3zm-1 0h1v1h-1zm-.6 0h.7v1h-.6zm0 0v1.2-1zm-1.6.2h1.5v1h-1.5zm0 0v1zm-1.2 0h1.2v1h-1.2zm0 0v1zm-1.2 0h1l.2 1H444v-1zm-.8 0h.8v1h-.7zm-1.6.2h1.6v1h-1.5zm0 0v1zm-6.2 1.3v-1h1.7l.2-.2h3.9v1H436l-.2.2zm-.4-1h.3v1h-.2zm-9 1.4v-1h1.3l.3-.2h3.2l.3-.2h3.5v1h-5.1l-.3.2H426zm-.3-1h.2v1h-.3v-1zm-.5 0h.5v1h-.4zm-18.2 2.2v-1h1.8l.5-.2h1.7l.5-.2h2.4l.6-.2h2.6l.6-.2h2.2l.6-.2h2.8l.6-.2h1.2v1h-2.1l-.6.2h-2.7l-.6.2h-2.3l-.6.2h-2.8l-.6.2h-2.2l-.5.2H408l-.6.2h-.6zm-.3-1h.3v1h-.2zm0 0v1zm-26.3 3.4v-1h.7l1-.2h.8l.8-.2h1.6l.8-.2h1l.7-.2h1.6l.8-.2h1.8l.8-.2h.8l1-.2h1.7l.7-.2h1.7l.8-.2h1.8l.7-.2h1.6l1-.2h1.5l1-.2v1H405l-.7.2h-1.7l-.8.2H400l-.7.2h-.8l-1 .2H396l-.8.2h-1.7l-.8.2H391l-.8.2h-.8l-.8.2H387l-1 .2h-.7l-.8.2H383l-1 .2h-.8l-.8.2zm0-7v1h-.5l-.5.2h-.5l-.3.2-.4.2-.3.2h-.2l-.3.3-.2.2-.3.3v.5l-.2.2v1.1h.2v.3l.2.2.2.2.3.3.3.2h.2l.3.2h.3l.4.2h1v1h-1.6l-.4-.2-.4-.2-.3-.2-.3-.2-.3-.2-.4-.2-.2-.3-.2-.2v-.3l-.3-.3-.2-.3v-1.6l.2-.3.2-.3.2-.4.2-.3.2-.3.4-.3.3-.2.4-.2.3-.2h.4l.5-.2h.6l.5-.2zm26.3-2.3v1H405l-.7.2h-1.7l-.8.2H400l-.7.2H396l-.8.2h-1l-.7.2H392l-1 .2h-1.6l-.8.2h-.8l-.8.2h-1.7l-.8.2h-.8l-.8.2H381l-.8.2v-1h1.7l.8-.2h.8l.8-.2h.8l.8-.2h1.7l.8-.2h1.6l1-.2h.8l.8-.2h1.8l.7-.2h1.7l1-.2h.7l.8-.2h1.8l.7-.2h1.6l1-.2h2.5zm0 1v-1zm37-3.2v1.2H439l-1 .2h-2.3l-1.3.2H431l-1.2.2h-2.2l-1.3.2H424l-1.2.2h-2.3l-1 .2H417l-1.2.2h-1.2l-1 .2h-2.3l-1.3.2h-1l-1.2.2h-1.2v-1h2.2l1.2-.2h1l1.2-.2h2.2l1.3-.2h1l1.2-.2h2.3l1-.2h2.3l1.3-.2h2.4l1.2-.2h2.3l1-.2h3.5l1.2-.2h3.5l1-.2h2.5zm1 1-1 .2v-1h1v1zm.2 0h-.3v-1l.2 1zm.2 0h-.3l-.2-1h.4v1zm1.2 0h-1.2v-1h1.2zm0 0v-1zm15.4-1.4v1h-5.5l-.5.2h-6.5l-.4.2h-2.6v-1h5l.5-.2h7.7l.5-.2zm.8 1h-.8v-1h.8zm0 0v-1zm1.4 0h-1.4v-1h1.4zm.2 0h-.2v-1h.2zm0 0h-.2zm0 0v-1zm1.2 0h-1.2v-1h1.2zm.5 0h-.6v-1h.4v1zm.6 0h-.7l-.2-1h.8zm1 0h-1v-1h1zm.2 0h-.3v-1h.2v1zm.4 0h-.4v-1h.4zm3-1 .2 1H468v-1h3.3zm1.2 1h-1v-1l1-.2v1zm0 0v-1.2 1zm1.3-.2h-1.2v-1h1.2zm3.5-1v1h-3.5v-1zm0 1v-1zm1.3 0h-1.2v-1h1.2zm3.6-1v1h-3.5v-1h3.7zm3.8 0v1H482v-1zm1 1h-1v-1h1.2v1zm.2 0v-1zm3.6-1v1H487v-1h4zm1 1h-1v-1h1.2v1zm.2 0v-1zm1 0h-1v-1h1zm3.3-1v1H493v-1h3zm.5 1h-.4v-1h.5zm.3 0h-.3v-1h.3zm1 0h-1v-1h1zm.6 0h-.7v-1h.8zm.5.2h-.6v-1h.5v1zm1 0h-1v-1h1zm0 0v-1zm0 0h.3zm.3 0h-.2v-1h.3zm1.3 0h-1.3v-1h1.4zm0 0v-1zm0 0h.2zm1 0h-1v-1h1zm15.2-.6v1h-6l-.4-.2H504l-.6-.2h-.9v-1h6l.4.2h6.6l.4.2h1.7zm0 1v-1zm1.3 0h-1v-1h1zm.4 0h-.3v-1h.5v1zm.2 0h-.2v-1h.2zm1 .2h-1v-1h1zm37 1v1h-2.3l-1.2-.2h-1l-1.2-.2h-2.2l-1.3-.2H546l-1-.2h-1.3l-1-.2h-2.3l-1.3-.2H537l-1-.2h-4l-1-.2h-2.6l-1-.2H524l-1-.2h-2.4v-1h4.7l1.2.2h3.3l1.3.2h2.3l1.3.2h2.3l1 .2h2.4l1 .2h2.3l1.3.2h2.3l1 .2h2.3l1.2.2h1.2l1 .2h1.3l1 .2h1.3zm0 1v-1zm26.4 1.4v1H582l-.8-.2h-.8l-.8-.2H578l-.7-.2h-.8l-.8-.2H574l-.8-.2h-.8l-.8-.2h-1.8l-.7-.2h-1.5l-.8-.2H565l-.8-.2h-1.7l-.8-.2H560l-.8-.2h-1.6v-1h3.5l.8.2h1.6l1 .2h1.7l.7.2h1.7l.8.2h1.8l.7.2h.8l.8.2h1.8l.8.2h.8l.8.2h1.8l.7.2h.7l.8.2h1.8zm0 7v-1h1.6l.3-.2h.3l.3-.2.2-.2.2-.2h.2v-.3l.2-.2v-.2h.2v-1.4l-.2-.3v-.2h-.3l-.2-.3-.3-.2h-.2l-.3-.3-.4-.2h-.4l-.4-.2h-.4l-.5-.2v-1h.5l.6.2h.5l.5.3.4.3.4.2.3.2.4.3.3.3.2.3v.3l.3.2v.3l.2.3v1.3l-.3.3v.3l-.3.3-.3.4-.3.2-.3.2-.2.2-.3.2-.4.2h-.3l-.4.2H584zm-26.5-2.4v-1h1.8l.8.2h1.8l.8.2h1.8l.7.2h1.7l1 .2h.7l.8.2h1.8l.7.2h1.6l.8.2h1l.8.2h1.6l.8.2h1l.7.2h1.5l.8.2h1l.7.2v1h-1l-.8-.2h-1.6l-.8-.2h-.8l-.8-.2h-1.7l-.8-.2h-.8l-1-.2h-1.6l-.8-.2h-.8l-1-.2h-1.4l-.8-.2H566l-.7-.2h-1.6l-1-.2H561l-1-.2h-1.6zm0-1v1zm-.2 0h.3v1h-.4v-1zM539 265v-1h2.9l.5.2h3l.5.2h2.3l.6.2h2.2l.6.2h2.3l.5.2h2.4l.5.2v1h-1.7l-.5-.2h-2.3l-.6-.2H550l-.7-.2H547l-.6-.2h-2.9l-.6-.2H540l-.6-.2h-.6zm-.4-1h.5v1h-.4v-1zm-.2 0h.2v1h-.2zm-9.2.5v-1h4l.3.2h3.2l.3.2h1.3v1h-1.6l-.3-.2H533l-.4-.2h-3.3zm-.3-1h.2v1h-.3zm-6Line truncated
|
||||||
|
<path fill="#0c4076" d="M482 269c5.8 0 10.4 3.2 10.4 7s-4.6 7-10.3 7-10-3.2-10-7 4.5-7 10-7z"/>
|
||||||
|
<path fill="#21231e" d="M493 276h-1v-.3l-.2-.3v-.5l-.2-.3v-.3l-.3-.3-.2-.4v-.3l-.3-.2-.2-.2-.2-.3-.3-.2-.3-.3-.3-.2-.3-.2-.2-.2-.4-.2-.4-.2-.3-.2-.4-.2h-.5l-.4-.3h-.4l-.5-.2h-.8l-.5-.2h-1.9v-1.2h1.7l.6.2h.5l.5.2h.5l.5.2h.4l.5.3.4.2.5.2h.5l.4.3.3.2.4.2.3.3.4.3.3.2.3.3.3.3.2.4.3.3.2.4.2.4.2.4v.3l.2.5v.4l.2.2v.8zm-11 7.4v-1h1.6l.5-.2h1.1l.5-.2h.5l.4-.3h.4l.5-.3h.4l.3-.3h.4l.4-.3.3-.2.4-.3.3-.2.3-.2.3-.3.2-.3.2-.2.3-.3v-.3l.3-.3h.2v-.4l.2-.3v-.7l.2-.3v-.3h1v.4l-.2.4v.4l-.2.4v.3l-.2.3-.2.3-.2.4-.2.3-.3.3-.2.3-.3.2-.3.3-.3.3-.4.3-.3.3-.4.2-.4.3-.5.2-.4.2-.4.2h-.4l-.5.3h-.4l-.5.3h-.5l-.5.2h-1.1l-.5.2zm-10.6-7.4h1v1l.2.3v.3l.2.2.2.3v.4l.3.3.2.3.3.2.2.3.3.3.3.2.3.2.2.3.3.2.4.2h.2l.4.3h.4l.4.3h.5l.3.3h.9l.5.2h1.5l.6.2h.5v1h-1.9l-.5-.2h-.5l-.5-.2-.5-.2h-.4l-.5-.3h-.4l-.4-.3-.4-.2-.4-.2-.4-.3-.4-.2-.3-.2-.4-.2-.4-.3-.3-.3-.3-.3-.2-.4-.3-.3-.2-.3-.2-.4v-.3l-.2-.4-.2-.4v-.8l-.2-.4v-.4zm10.7-7.4v1h-1.3l-.5.2h-.3l-.5.2h-.5l-.4.2h-.4l-.6.3h-.4l-.4.3-.4.2-.3.2-.5.2-.3.2-.3.2-.4.2-.3.3-.3.2-.2.3-.3.3-.2.3-.2.3v.3l-.2.2v.3l-.2.3v.7l-.2.3v.3h-1V275l.2-.5.2-.4v-.2l.3-.4.3-.3.2-.3.3-.3.2-.3.3-.2.3-.3.3-.2.3-.3.3-.3.4-.2.4-.2.4-.2h.4l.4-.3.5-.2.5-.2h.5l.5-.2h1l.5-.2h1.1l.6-.2h.5z"/>
|
||||||
|
<path fill="#edb92e" d="M482 211.7c4.4 0 8 3.4 8 7.7s-3.6 7.8-8 7.8-7.6-3.5-7.6-7.8 3.5-7.8 7.7-7.8z"/>
|
||||||
|
<path fill="#21231e" d="M490.4 219.4h-1v-1l-.2-.3v-.4l-.2-.3v-.4l-.2-.4v-.3l-.3-.3-.2-.3-.2-.3v-.3l-.3-.2-.2-.3-.2-.2-.3-.2-.3-.2-.2-.2-.3-.2-.3-.2-.4-.2h-.3l-.3-.3h-.4l-.3-.2h-.4l-.4-.2H482V211l.6.2h1.2l.4.2h.4l.4.2.3.2.4.2h.4l.4.3.3.3.4.2.4.2.3.3.3.3.2.3.3.2.2.3.3.3.2.3.2.5.2.3v.4l.3.4v.8l.2.4v.8l.2.4zm-8.3 8.3v-1h1.5l.3-.2h.3l.3-.2.4-.2h.3l.3-.2h.3l.2-.3.3-.2.2-.2.3-.2.3-.2.2-.3.2-.2.2-.2v-.3l.3-.3.2-.3.2-.4v-.3l.2-.4v-.7l.2-.4v-.8l.2-.4v-.4h1v1.2l-.2.4v.5l-.2.4v.3l-.3.4v.3l-.3.4-.2.3-.2.4-.3.4-.2.3-.3.3-.2.3-.3.3-.3.2-.3.3-.3.3-.3.2-.3.2-.3.2h-.4l-.3.2-.4.2h-.4l-.4.2H482zm-8-8.3h1v1.8l.2.3.2.3v.3l.2.3.2.3.2.2.2.3.2.3.2.3.2.3.2.3.3.2.2.3.3.3.3.2.4.2h.3l.3.3h.3l.4.3h.3l.3.2h.7l.4.2h.7v1h-1.6l-.4-.2h-.3l-.4-.3h-.4l-.4-.3-.4-.3-.3-.2-.5-.2-.3-.2-.3-.2-.4-.2-.3-.3-.3-.3-.2-.3-.3-.3-.2-.3-.2-.3v-.3l-.3-.4v-.3l-.3-.4v-.4l-.2-.5v-.4l-.2-.3zm8-8.3v1.2h-1.3l-.4.2h-.3l-.3.2h-.4l-.3.3h-.3l-.3.2-.3.2-.2.2-.3.2-.2.3-.3.3-.2.2-.2.3-.2.3-.2.4-.2.3-.2.3-.2.3v.3l-.2.3-.2.3v.7l-.2.3v1.1h-1v-1.6l.2-.4v-.4l.2-.4.2-.4v-.4l.3-.3v-.4l.3-.2.2-.3.3-.3.2-.3.3-.4.3-.3.3-.3.2-.2.3-.2.4-.3.2-.2h.4l.3-.2.3-.2h.4l.4-.2h.5l.4-.2h1.1z"/>
|
||||||
|
<path fill="#edb92e" d="M482 213.7c3.3 0 5.8 2.6 5.8 5.7 0 3.2-2.5 5.8-5.7 5.8-3 0-5.5-2.6-5.5-5.8 0-3 2.5-5.7 5.6-5.7z"/>
|
||||||
|
<path fill="#21231e" d="M488.3 219.4h-1V218h-.2v-.7l-.2-.2v-.2l-.2-.3-.2-.2v-.2l-.3-.2-.2-.2-.2-.2-.2-.2h-.2l-.2-.3h-.4l-.2-.3h-.2l-.3-.2h-.6l-.2-.2H482v-1h1.8l.3.2h.3l.2.2h.3l.3.3h.4l.2.3h.2l.3.3.3.2.2.2.2.2.2.3v.2l.3.3v.2l.3.3v.2l.2.3.2.3v.6l.2.2v1.3zm-6.2 6.3v-1h1.1l.2-.2h.3l.2-.2h.2l.2-.2h.4l.2-.2.2-.2h.2l.2-.3.2-.2h.2v-.2l.3-.2v-.2l.3-.3.2-.3v-.2l.2-.3v-.2l.2-.3v-1.3h1v2l-.2.3-.2.3-.2.4-.2.3v.2l-.3.2v.2l-.3.2-.2.3h-.2l-.2.3h-.2l-.2.3-.3.2h-.2l-.2.3-.3.2h-.2l-.3.2h-.3l-.3.2h-1.4zm-6-6.3h1v1.3l.2.3v.2l.2.3v.2l.2.2.2.4.2.2v.2h.3l.2.3.2.3v.2h.3l.2.3h.3l.2.2h.2v.2h.4l.2.2h.2l.3.2h.9v1h-1.5l-.3-.2h-.3l-.3-.2h-.2l-.3-.3-.3-.2-.3-.2-.3-.2h-.2l-.2-.3h-.2l-.2-.4-.2-.2-.2-.2-.2-.3-.2-.3v-.3l-.2-.3h-.2v-.4l-.2-.3v-.6l-.2-.3v-.9zm6-6.2v1H481l-.2.2h-.4l-.3.2-.3.3h-.5l-.2.3h-.2v.3l-.3.2h-.2l-.2.3v.3l-.2.2v.2l-.2.3v.2l-.2.2v.2l-.2.3v.4l-.2.2v.7h-1V218l.2-.4v-.3l.2-.3.2-.3v-.2l.2-.3.2-.2.2-.3.2-.2.2-.3.2-.2.2-.2.2-.2.2-.2h.3l.2-.3h.2l.3-.2h.2l.3-.2h.3l.3-.2h.5l.3-.2h.6z"/>
|
||||||
|
<path fill="#0c4076" d="M482 215.8c2 0 3.7 1.6 3.7 3.6S484 223 482 223c-1.8 0-3.4-1.6-3.4-3.6s1.6-3.6 3.5-3.6z"/>
|
||||||
|
<path fill="#21231e" d="M486.2 219.4h-1v-.8h-.2v-.3l-.2-.2v-.2l-.2-.2-.2-.2-.2-.3h-.2l-.3-.2h-.2l-.2-.2h-.2v-.2H482v-1h1.4v.2h.5v.2h.3v.2h.3v.3h.3v.2l.2.2.2.3.2.3v.2l.2.2.2.3v.4l.2.2v.6zm-4 4.2v-1h.6v-.2h.5l.3-.2h.2l.2-.2v-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2v-.2l.2-.2v-.4h.2v-1h1v1l-.2.2v.2l-.2.2v.2l-.2.2-.2.3-.2.2-.2.2-.3.2-.3.3h-.3l-.3.2-.3.2h-1l-.3.2zm-4-4.2h1v1.3l.2.2v.2l.2.2.2.2.2.2.2.2.2.2h.2v.2h.4l.2.2h.8v1.2h-.7l-.2-.2h-.3v-.2h-.5l-.2-.2-.3-.3h-.2v-.2l-.2-.2h-.2v-.2l-.2-.3-.2-.3v-.2l-.2-.2-.2-.3v-1.4zm4-4v1h-1l-.2.2h-.2l-.2.2h-.2v.2h-.2l-.2.3-.2.2-.2.2-.2.3v.4h-.2v1h-1V218l.2-.2.2-.3v-.2l.2-.2.2-.2.3-.3.2-.2.2-.2h.2l.2-.2h.2l.2-.2h.3l.3-.2h.9z"/>
|
||||||
|
</g>
|
||||||
|
<path fill="#c6363c" d="M135.8 192.6v144.7c0 26 10 49.4 26.2 66.5a87 87 0 0 0 63 27.6c24.8 0 47-10.6 63.3-27.6a96 96 0 0 0 26.2-66.5V192.6z"/>
|
||||||
|
<path fill="#fff" d="M135 192v145.3l.2 2.5v2.4l.2 2.4.2 2.4.3 2.4.2 2.3.3 2.3.5 2.4.5 2.3.5 2.2.6 2.2.6 2.2.7 2.2.6 2.2.8 2.2.8 2 1 2.2.8 2 1 2.2 1 2 1 2 1.2 2 1 2 1.3 1.8 1.3 2 1.2 1.7 1.4 1.7 1.3 1.8 1.4 1.7 1.6 1.7 1.4 1.6 1.5 1.6 1-1-1.5-1.5-1.5-1.6-1.4-1.6-1.3-1.7-1.3-1.8-1.3-1.8-1.2-2-1.2-1.7-1.2-2-1-1.8-1.2-2-1-2-1-2-1-2-1-2-.8-2-.8-2.2-.8-2-.7-2.3-.6-2-.6-2.4-.6-2.2-.5-2.2-.5-2.3-.4-2.2-.4-2.3-.3-2.3v-2l-.3-2.5-.2-2.4V193.4h177.4v144l-.2 2.4v2.4l-.2 2.5-.2 2.3-.2 2.4-.3 2.3-.5 2.3-.4 2.3-.4 2.4-.5 2.2-.5 2.2-.6 2.3-.8 2.2-.7 2.2-.8 2-.8 2.2-1 2-1 2.2-.8 2-1 2-1 2-1.2 2-1 1.8-1.3 2-1.2 1.7-1.3 2-1.3 1.6-1.4 1.7-1.4 1.7-1.4 1.7-1.5 1.7-1.5 1.6-1.5 1.6-1.6 1.4-1.6 1.5-1.5 1.4-1.6 1.4-1.8 1.3-1.8 1.5-1.7 1.3-1.8 1.2-2 1.2-1.8 1-1.8 1.2-2 1-1.8 1-2 1-2 1-2 .8-2 1-2 .6-2 .7-2.2.7-2 .6-2.3.5-2 .5-2.2.5-2.2.4-2.2.3-2.2.3-2.2.2h-2.3l-2.3.2H225v1.3h4.8l2.3-.3 2.3-.2 2.3-.3 2.2-.3 2.2-.5 2.2-.4 2.2-.4 2.2-.6 2.2-.6 2-.7 2.2-.7 2-.8 2-.8 2-1 2-.8 2-1 2-1 2-1 2-1.2 1.8-1 1.8-1.4 1.8-1.2 1.8-1.3 1.7-1.4 1.7-1.2 1.7-1.5 1.6-1.4 1.6-1.7 1.6-1.5 1.5-1.6 1.5-1.6 1.5-1.7 1.5-1.8 1.4-1.7 1.3-1.8 1.2-1.8 1.3-2 1.2-1.8 1.2-2 1-2 1.2-1.8 1-2 1-2 1-2 1-2.2 1-2 .7-2.2.7-2.2.7-2.2.7-2.2.7-2.2.6-2.3.5-2.3.5-2.4.4-2.3.4-2.3.4-2.3.3-2.4.2-2.4V192z"/>
|
||||||
|
<g fill="#fff" stroke="#21231e" stroke-width=".5">
|
||||||
|
<path d="M226 424c.7-.2 2 0 3-1 1.4-1 .8-5 .7-7.7q-.7-20.3-1.8-68.3h-5.6q-1 48-1.8 68.3c0 2.6-.7 6.8.7 7.8 1 1 2.3.8 3 1h1.7z"/>
|
||||||
|
<path d="M234.4 423.5c-.8 0-2 .2-3-.5-1.5-1-1.6-5.2-1.7-7.8q-1-20.3-4-68 1.5-.4 2.6-.3l.4-.2c5 31.6 7 54 10.4 67.5.6 1.8 1.6 6.5 0 8-1 1-2.2 1-3 1l-1.6.2z"/>
|
||||||
|
<path d="M244.4 422c-.8 0-2 .5-3.2 0-1.5-1-1.4-5.2-2-7.7-3.5-13.5-5.5-36-10.6-67.5q1.6-.4 2.7-.4c8.2 30.6 10.6 52.2 17 65.6.5 2 2 6.8.6 8.5-1 1-2.3 1-3 1.3l-1.6.3z"/>
|
||||||
|
<path d="M254.4 419.2c-.8 0-2 1-3.3.4-1.4-.6-1.7-5.2-2.8-7.6-6.3-13.4-8.7-35.2-17-66q1.7-.8 2.7-.8c.7-.3.5-.5 1.6-.7 9.5 30.6 12.6 51.8 21.6 65 1.4 2 2.5 6.4 1.5 7.7-.7 1-2 1.3-2.8 1.6l-1.6.4zm-38.5 4.3c.7 0 2 .2 3-.5 1.4-1 1.5-5.2 1.6-7.8q1-20.3 4-68-1.5-.4-2.6-.3l-.4-.2c-5 31.6-7 54-10.4 67.5-.5 1.8-1.6 6.5 0 8 1 1 2.3 1 3 1l1.7.2z"/>
|
||||||
|
<path d="M206 422c.7 0 2 .5 3 0 1.6-1 1.5-5.2 2.2-7.7 3.4-13.5 5.4-36 10.4-67.5q-1.6-.4-2.6-.4h-.2c-8 30.6-10.5 52.2-16.7 65.6-.5 2-2 6.8-.6 8.5 1 1 2.2 1 3 1.3l1.5.3z"/>
|
||||||
|
<path d="M196 419.2c.7 0 2 1 3.2.4 1.5-.6 1.8-5.2 3-7.6 6.2-13.4 8.6-35.2 16.8-66q-1.6-.8-2.7-.8c-.7-.3-.5-.5-1.7-.7-9.5 30.6-12.5 51.8-21.5 65-1.3 2-2.4 6.4-1.4 7.7.7 1 2 1.3 2.7 1.6zm53-23c-.6 0-1.7.5-2.8 0-1.5-.8-2.5-5-3.3-7.5-6.4-12.5-12.2-34.8-21.8-65.8q1.4-.7 2.4-.7.9-.4 2.3-.6c10.2 30.7 14.8 53 24.8 65 1.7 2 3 6.5 2 7.8-.5 1-1.7 1-2.3 1.3l-1.4.4z"/>
|
||||||
|
<path d="M241 397.8c-.7 0-2 .3-3-.3-1.3-.8-1.8-5-2.3-7.6q-4-20.3-14-67.4l2.4-.4q1.1-.4 2.5-.4c8 31.3 9.8 54.7 17.3 66.7 1.3 2.2 2 6.7 1.2 8-.7.8-2 .8-2.6 1z"/>
|
||||||
|
<path d="M232.7 398.7c-.7 0-1.8.2-2.8-.5-1.4-1-1.4-5-1.6-7.8q-1.6-20.3-6.2-68 1.4-.4 2.4-.2 1-.3 2.5-.3c4.4 31.7 7 54.7 9.6 67.8.5 2.5 1.3 6.7.3 7.8s-2 .8-2.8 1h-1.5z"/>
|
||||||
|
<path d="M201.2 396.2c.7 0 1.8.5 3 0 1.4-.8 2.4-5 3.2-7.5 6.3-12.5 12-34.8 21.6-65.8q-1.4-.7-2.3-.7-.8-.4-2.4-.6c-10.2 30.7-14.8 53-24.8 65-1.7 2-3 6.5-2 7.8.5 1 1.7 1 2.3 1.3l1.4.4z"/>
|
||||||
|
<path d="M209.3 397.8c.7 0 1.8.3 3-.3 1.3-.8 1.8-5 2.3-7.6q4-20.3 14-67.4l-2.4-.4q-1-.4-2.4-.4c-8 31.3-9.8 54.7-17.3 66.7-1.4 2.2-2 6.7-1.2 8 .7.8 2 .8 2.6 1z"/>
|
||||||
|
<path d="M217.6 398.7c.7 0 1.8.2 2.8-.5 1.3-1 1.3-5 1.5-7.8q1.5-20.3 6-68-1.4-.4-2.3-.2-1-.3-2.5-.3c-4.3 31.7-6.8 54.7-9.5 67.8-.5 2.5-1.3 6.7-.3 7.8.8 1 2 .8 2.7 1h1.6z"/>
|
||||||
|
<path d="M226 399c.6 0 1.8 0 2.7-.8 1-1 .7-5.2.6-7.8q-.8-20.4-1.7-68.3-1.5 0-2.5.2-.8-.3-2.3 0-.8 47.9-1.7 68.2c0 2.6-.5 6.8.6 7.8 1 .8 2 .7 2.8.8z"/>
|
||||||
|
<path d="M243.5 377.6c-.6 0-1.4.5-2.2 0-1.2-.8-2-5-2.5-7.5-5-12.4-9.4-34.7-16.6-65.7q1-.5 1.8-.6.6-.4 1.8-.6c7.8 30.8 11.3 53 19 65.2 1.3 2 2.2 6.4 1.6 7.7-.4.8-1.4 1-1.8 1.2l-1 .4z"/>
|
||||||
|
<path d="M237.3 379.2c-.6 0-1.4.3-2.2-.3-1-1-1.4-5-1.8-7.7q-3-20-10.7-67.3 1-.4 2-.3.6-.5 1.7-.5c6 31.3 7.5 54.7 13.2 66.8 1 2 1.6 6.6 1 7.8-.6 1-1.5 1-2 1l-1 .4z"/>
|
||||||
|
<path d="M231 380c-.6 0-1.5.3-2.2-.4-1-1-1-5-1.2-7.8q-1.2-20.3-4.7-68 1-.4 1.7-.2a4 4 0 0 1 2-.2c3.2 31.7 5 54.7 7.2 67.8.3 2.6 1 6.7 0 7.8-.5 1-1.4.8-2 1h-1z"/>
|
||||||
|
<path d="M206.8 377.6c.5 0 1.4.5 2.2 0 1.2-.8 2-5 2.5-7.5 5-12.4 9.3-34.7 16.6-65.7q-.8-.5-1.7-.6-.7-.4-1.8-.6c-7.8 30.8-11.3 53-19 65.2-1.3 2-2.2 6.4-1.6 7.7.3.8 1.2 1 1.7 1.2l1 .4z"/>
|
||||||
|
<path d="M213 379.2c.6 0 1.4.3 2.2-.3 1-1 1.5-5 2-7.7a2735 2735 0 0 1 10.6-67.3q-1-.4-2-.3-.6-.5-1.7-.5c-6 31.3-7.4 54.7-13 66.8-1.2 2-1.8 6.6-1 7.8.5 1 1.4 1 2 1l1 .4z"/>
|
||||||
|
<path d="M219.3 380c.6 0 1.5.3 2.2-.4 1-1 1-5 1.2-7.8q1.1-20.3 4.7-68-1-.4-2-.2a3 3 0 0 0-1.7-.2c-3.3 31.7-5.2 54.7-7.3 67.8-.4 2.6-1 6.7-.2 7.8.6 1 1.5.8 2 1h1z"/>
|
||||||
|
<path d="M225.7 380.4c.5 0 1.4 0 2-.8 1-1 .7-5.2.6-7.8q-.6-20.3-1.3-68.3h-3.7q-.8 48-1.3 68.3c0 2.6-.4 6.8.5 7.8.6.8 1.6.7 2 .8z"/>
|
||||||
|
<path d="M221.3 330.2v7.4l-5.8 1.2c0 2.6 6.4 29.6 9.6 29.6 3.4 0 9.8-27 9.8-29.6l-5.7-1.2v-7.4c-1.2 1-2.5 2.7-4 4.3a27 27 0 0 0-3.7-4.3z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(-32 32)scale(.53333)">
|
||||||
|
<g id="rs-d">
|
||||||
|
<path fill="#edb92e" stroke="#21231e" d="M412 584.5c-3 .2-7 2-18.5 12-5 10.2-10 20.5-17 29.4q-3.4 2.1-6 3.2a84 84 0 0 1-17-.5c-4.4-4-11.3-1.2-9 3.2-1.8 1.6 2.2 7.8 5.3 5.2 3-2.4 13-.2 16.4-1.4-1.3 3-10.4 5-16.8 5.5-5.8.2-5.4 6-3 10.5 2.7 1.8.5-.4 1.4.8 2.6 4 8.2 3.4 8.4-.4.2-4 13.8-9 15.8-12-2.5 6-8.2 11.5-11 17.3-3 3-5 3.7-1 10 4.4-.6 4.6-1.7 7.3.8 6-.6 7-2.4 6-7.2-1-5 3.2-14 4.6-16.6 1.2-2.4 1.5-4.8 2.8-6.3 2.2-2.5 12.6-7.7 15.2-6.3 5 2.7 8.7 1 9.3-3.7-1.6-1.2-.4-8-3.8-8.2-2.8-.3-4.4.6-7 2.7-4 3.2-6 5-7.4 4.6-3-.7 5.8-5 13.8-15.6 11-14.5 11.8-22 11.3-27z"/>
|
||||||
|
<use xlink:href="#rs-c" width="1350" height="900" transform="matrix(-1 0 0 1 801.6 0)"/>
|
||||||
|
<path fill="#edb92e" stroke="#21231e" d="M345.2 634.5c-4-2.3-6.3-2.2-8-.5-.7 1-1.8.5-1.2-1 2.7-6.3 7-7 10.7-4.2l2.8 2.7c-.6 1-3.8 2.3-4.3 3zm57.4-5.6c4.3-3 8.3-3.6 10.5-2 1.3 1 2.5.3 1.4-1.2-4.4-6.2-9.6-6.5-13.5-3q-1.7 1.5-2.8 3c1.3 1.6 4.3 3.2 4.4 3zM369 664.4c.2-.2-1-5.4-2.6-7q-2.2 1.4-4.3 3.5c-4.2 4.5-4 11 2.5 16.7 1.6 1.5 2.4 0 1.7-1.7-1-2.7.3-6.3 3-11.5zM352.6 651c-4 1.6-5.4 3.7-5.2 6.4 0 1.5-1 2-1.5.3-4.8-11.2.2-13.3 7.5-13 .4 2-.7 6.2-.8 6.2z"/>
|
||||||
|
<path fill="#21231e" d="m384 621.3 1 .6h-.2v1.8h.2v.4h.2v.2h.2l.2.3.3.2v.2h.4l.2.3h.2l-.6 1h-.2l-.3-.2h-.2l-.2-.2-.2-.2-.3-.3-.3-.3v-.2h-.2v-.3l-.2-.2v-.2h-.2v-.2l-.2-.2V622l.2-.3v-.2zm6.5.8-.8 1-.2-.2-.3-.2-.2-.2-.2-.2h-.3l-.2-.3-.2-.3-.3-.2-.2-.2-.3-.2h-.7l-.2-.2h-.7v.2h-.2l-.2.2v.2h-.2v.3l-.2.2-1-.7v-.3l.2-.2.2-.2.2-.2.3-.3h.3l.2-.2h.4v-.2h.8l.2.2h.4l.2.2h.2l.2.2h.2l.2.3h.2l.2.3h.2l.3.3.2.3.2.2.3.2.2.2h.3z"/>
|
||||||
|
<path fill="#21231e" d="m387.3 617.6 1 .6v.4h-.2v.8l.2.2v.2l.2.2v.2l.2.2.2.3.3.2h.2l.2.2h.2l.2.2.2.2h.2l-.6 1h-.2l-.3-.2-.3-.3h-.2l-.2-.2-.2-.2-.2-.3v-.2h-.2l-.2-.2v-.2h-.2v-.5h-.2V618h.2v-.3zm6.5.8-.7 1-.2-.3h-.2l-.2-.3-.3-.2-.3-.3h-.2l-.2-.3-.2-.2-.3-.2-.3-.2h-.3v-.2h-.8l-.2.2-.2.2-.2.2v.2l-.3.2-1-.6.2-.3v-.2h.3v-.3h.3l.3-.3h.2l.2-.2h.2v-.2h1.4l.2.2h.2v.2h.3l.3.3h.2l.2.2h.2l.2.3h.2l.3.3v.2l.3.2.3.2z"/>
|
||||||
|
<path fill="#21231e" d="m391 612.8 1 .6-.2.2v.6h-.2v1l.2.3v.3h.2v.4h.2l.2.3v.2h.2l.2.2.2.2.3.3.3.2h.2l.2.2-.6 1-.3-.2-.2-.2-.2-.2-.3-.2-.2-.2v-.2l-.3-.2v-.2l-.3-.2v-.2l-.3-.2v-.4l-.2-.3v-.2h-.2v-.7h-.2v-1l.2-.3v-.4l.2-.3zm7 1-.8 1-.3-.3-.3-.2-.3-.3-.3-.2v-.2l-.3-.2-.3-.2-.2-.2-.2-.2-.2-.2h-.2l-.2-.2-.3-.2h-.2l-.2-.2h-.7l-.2.2-.2.2-.3.3v.2h-.2v.4l-1-.6v-.3l.2-.2.2-.3v-.2l.3-.2.2-.2v-.2h.3l.2-.2h.3l.2-.2h1.2l.2.2h.2l.2.2.3.2h.3l.2.3.2.2h.3l.2.3.3.2.2.2.3.2.2.2.3.2.2.3.3.2z"/>
|
||||||
|
<path fill="#21231e" d="m394.8 607.7 1 .5v.5l-.2.3v1.7l.2.2v.3l.2.2v.2l.2.2.3.3v.2l.3.2.2.2h.2l.2.3.2.2.3.2v.2l-.6.8-.3-.2v-.2l-.4-.2-.2-.3-.3-.3v-.2l-.3-.3-.2-.2v-.2l-.3-.3v-.2l-.3-.2-.2-.2v-.2l-.2-.2v-.3l-.2-.3v-2.1l.2-.2v-.2l.2-.3zm7.6 1.3-1 .8-.2-.3-.3-.3-.3-.2-.3-.3-.3-.3v-.2l-.4-.2-.2-.2-.2-.2-.2-.2-.2-.2h-.2l-.2-.2h-.2l-.2-.2h-.3v-.2h-.7v.2l-.3.2-.2.3-.2.2v.2l-.3.2v.2l-1.2-.5.2-.3.2-.3v-.2l.3-.3.2-.2.2-.2.3-.2.3-.2h.5l.3-.2h.8l.3.2h.2l.3.2h.2l.2.3.3.3.2.2.3.2.2.2h.3l.2.3.3.3.2.2.3.3.3.4.3.3.4.3z"/>
|
||||||
|
<path fill="#21231e" d="m399 602.6 1.2.6v.2l-.2.2-.2.3v2.1l.2.2v.2l.2.2.2.2v.2l.2.2.2.2.2.2v.2l.3.2.2.2.2.2.3.2.2.2.3.2-.8 1-.3-.3-.2-.3-.3-.2-.3-.3-.2-.2-.3-.2-.2-.3v-.2l-.3-.2-.2-.2v-.2l-.3-.2v-.3h-.2v-.4l-.2-.2v-.2l-.2-.3v-2.6l.2-.2v-.2l.2-.2v-.2zm7.7 1.4-1 .8-.2-.3-.3-.3-.2-.3-.3-.3-.3-.3-.2-.2-.2-.3-.3-.3-.2-.2h-.2l-.2-.3-.2-.2h-.2v-.2h-1.3l-.3.2v.2h-.2v.3l-.3.2-.2.3v.2l-1.2-.6.3-.3.2-.3.2-.2.2-.3v-.2l.4-.2h.2l.2-.2h.2l.2-.2h1.7l.2.2.2.2h.2l.2.3h.3l.3.3.2.3h.3l.2.3.3.3.2.2.3.3.3.4.4.3.3.3z"/>
|
||||||
|
<path fill="#fff" stroke="#21231e" d="m462.6 580.6-9.5-5.4c-4 3-6.8 13.5 0 22.2 8.5 11 12 18.7 14 27.5 6.2-8 8.3-16.3 4.4-24.5-5.3-11-8.7-17-8.8-20z"/>
|
||||||
|
<path fill="#fff" stroke="#21231e" d="m470.4 573-9.5-5.4c-4 3-12.5 13.2-5.6 22 8.5 10.8 17.3 19 19.4 27.7 6.2-8 8.8-16.2 5-24.4-5.4-11-9.4-17.2-9.4-20z"/>
|
||||||
|
<path fill="#fff" stroke="#21231e" d="m449.4 569.2-9.5-5.4c-4 3-7 13.5-.2 22.2a65 65 0 0 1 14 27.5c6.2-8 8.4-16.3 4.4-24.4-5.3-11-8.7-17-8.8-19.8z"/>
|
||||||
|
<path fill="#fff" d="M405.5 469c.2 8.7-1.4 66.5-1.8 74.7s-4 13.7-8 17q-5.3-7.5-6-19.3c-.4-4 .4-58.7.7-70q9.4-25.5 15-2.4z"/>
|
||||||
|
<path fill="#21231e" d="M404 543.8h-1l.2-1v-2.6l.2-1.7v-4l.2-2.3v-5l.2-2.7v-2.8l.2-3v-3l.2-3v-6.2l.2-3v-6.2l.2-3v-5.8l.2-2.7v-7.4l.2-2V469h1v11.7l-.2 2.5v8.3l-.2 2.8v6l-.2 3.2v3l-.2 3.2v6l-.2 3v3l-.2 2.8v5.3l-.2 2.5v2.3l-.2 2v3.6l-.2 1.5v2zm-8.7 17 .8-.5h-.6l.3-.4.4-.4.4-.3.4-.3.3-.4.5-.4.3-.3.3-.5.4-.4.4-.4.3-.4.4-.5.3-.4.2-.5.2-.5.3-.5.3-.5.2-.6.2-.5.3-.6.2-.5.2-.5v-.7l.3-.6v-.6l.3-.7v-.7l.2-.7v-.7l.2-.8v-1.5h1v2.3l-.2.7v.8l-.3.6v.8l-.3.7-.2.6-.2.8-.2.6-.2.6-.2.6-.3.6-.4.5-.2.6-.3.5-.2.5-.3.5-.3.5-.3.5-.4.4-.4.5-.3.4-.5.5-.3.4-.4.3-.3.4-.4.3-.2.4-.4.3-.4.3h-.7zm.7.2-.4.3-.3-.4h.7zm-7-19.5h1v.6l.2.8v1.4l.2.7v.6l.2.7v.6l.3.6v1.5l.3.5v.7l.3.6.2.6.2.6.2.6.2.5.2.7.2.6.2.5.3.6.2.5.3.4.2.6.3.5.3.5.3.5.2.4.3.4.3.5.3.3-.7.6-.3-.6-.4-.4-.3-.5-.3-.5-.3-.5-.3-.6-.2-.6-.3-.5-.4-.7-.2-.5-.3-.6v-.6l-.3-.5-.3-.5-.2-.6-.2-.7-.2-.6v-.7l-.3-.6-.2-.6v-.7l-.3-.5v-.7l-.2-.7-.2-.6v-.6l-.2-.7v-1l-.2-.8v-1.5zm1-70.2.8.3v8l-.2 2.3V492l-.2 2.7v11.6l-.2 3v19l-.2 2.2v11h-1v-20.9l.2-2.7v-14.6l.2-2.8V489l.2-2.6v-8.9l.2-2v-4.2zm16-2.3h-1l-.4-1.3-.3-1.3-.4-1-.5-1.2-.4-1-.2-1-.4-.7-.4-.8-.4-.6-.4-.4-.4-.4-.4-.3-.3-.3h-.3l-.4-.2h-.7l-.4.3-.4.3-.5.5-.5.4-.5.5-.5.7-.5.7-.5.8-.5 1-.6 1-.5 1.2-.6 1.3-.6 1.3-.6 1.5-.6 1.6-1-.3.7-1.6.6-1.5.7-1.3.6-1.4.5-1.2.6-1 .5-1 .5-1 .5-.8.6-.7.5-.7.6-.6.6-.4.5-.4.6-.3.5-.2h1.1l.5.3.5.3.5.5.5.5.4.6.4.6.5.8.4.8.4 1 .5 1 .3 1 .4 1.4.4 1.3.4 1.4zm0 0"/>
|
||||||
|
<path fill="#fff" d="m368 454.8-2.6 79.6c-.8 7 8.4 49 13.5 57.4 12.3-5.3 15-33.8 12.6-61.6s.4-51.8 2.3-56.8c0-6.3-1-12.8-1-19.3-8 .5-17.2.4-25 .8z"/>
|
||||||
|
<path fill="#21231e" d="m365 534.4 2.4-79.6h1l-2.5 79.6h-1zm13.7 57 .3.8-.6-.2-.5-1-.6-1-.5-1.2-.7-1.4-.5-1.6-.6-1.6-.6-1.8-.6-2-.5-2-.6-2-.7-2.2-.6-2.2-.6-2.3-.4-2.3-.6-2.3-.5-2.3-.6-2.3-.6-2.3-.4-2.2-.5-2.2-.4-2.2-.3-2-.4-2.2-.3-2-.3-1.8-.3-1.7-.2-1.6v-1.4l-.3-1.3v-2.8h1v4.1l.2 1.5.2 1.6.3 1.7.3 1.8.3 2 .4 2 .4 2.2.5 2.2.4 2.2.4 2.3.5 2.2.4 2.3.6 2.3.5 2.4.6 2.3.7 2.2.5 2.2.6 2 .6 2.2.6 2 .6 2 .5 1.7.7 1.5.6 1.5.5 1.4.5 1.2.5 1 .5 1-.6-.3zm.3.8-.4.2-.2-.4zm12-62h1l.3 2.5v2.7l.3 2.6v2.6l.2 2.6v12.5l-.3 2.4-.2 2.5-.2 2.3v2.3l-.4 2.3-.4 2.2-.3 2-.6 2-.4 2-.6 2-.5 1.7-.8 1.7-.6 1.5-.7 1.5-1 1.5-.7 1.3-1 1.2-1 1-1 1-1 1-1 .6-1.3.6-.3-1 1-.4 1-.7 1-.7 1-1 1-1 .7-1 .8-1.3.8-1.4.7-1.5.6-1.5.6-1.6.5-2 .5-1.7.5-2 .4-2 .3-2 .3-2 .3-2.3v-2.3l.4-2.3V558l.3-2.3V538l-.2-2.6-.2-2.6-.2-2.6zm2.4-56.8h1v.2l-.2.5-.2.7-.2.7-.2 1-.2.8-.2 1-.2 1.2v1.2l-.3 1.3-.2 1.3-.2 1.4v1.6l-.3 1.6v1.7l-.3 1.7v2l-.2 1.8v2l-.2 2V520l.2 2.5.2 2.5.2 2.6v2.5l-1 .2v-2.6l-.3-2.5-.2-2.5V520l-.2-2.5v-20.1l.2-2v-2l.3-2v-1.7l.2-1.7.2-1.8.2-1.6v-1.4l.3-1.5.2-1.3v-1.2l.3-1V477l.3-1 .2-.8.2-.7.2-.7.2-.5zm1 0v.2zm-1.5-18.8v-1l.4.4v3.7l.2.6v2.5l.2.6v1.8l.2.6v2.4l.2.6v3.6l.2.6v2.4h-1V468l-.2-.5V465l-.2-.5v-1.9l-.2-.6v-1.7l-.2-.6v-3l-.2-.6V454l.5.6zm0-1h.4v.4l-.5-.4zm-24.6 1.2h-1l.4-.5h3.9l.8-.2h12.8l.8-.2h4.7l.8-.2h1.7v1h-4.1l-.7.2h-8l-.8.2h-7.1l-.8.2h-3.9l.4-.4zm-1 0v-.4h.4zm.5 0h.4-.5zm-.6 0v-.4h.4z"/>
|
||||||
|
<path fill="#fff" d="m353 468 1.8 79.5c-.4 7 11 48.5 16.4 56.5 12.6-9.8 10-49.8 8.2-62-4-24-3.2-52-2-56.5 0-6.3-.2-13.8-.6-20.4q-12 1.6-23.7 2.8z"/>
|
||||||
|
<path fill="#21231e" d="m354.3 547.5-1.7-79.6 1-.2 1.7 79.6h-1zm16.6 56 .5 1-.7-.2-.5-1-.6-1-.6-1.2-.5-1.2-.7-1.6-.6-1.6-.7-1.8-.7-1.8-.7-2-.5-2-.7-2.2-.7-2.2-.6-2.2-.7-2.3-.7-2.2-.7-2.3-.6-2.3-.6-2.3-.5-2.3-.6-2.2-.6-2.2-.5-2-.6-2-.4-2-.4-2-.4-1.6-.3-1.6-.3-1.3-.2-1.3v-1.2l-.2-1v-.8h1v1.8l.2 1 .2 1.3.2 1.5.2 1.5.3 1.7.4 2 .4 1.8.4 2 .5 2 .6 2.2.4 2.2.6 2.3.7 2.3.6 2.3.7 2.2.6 2.3.7 2.3.7 2.3.7 2.2.7 2 .7 2.2.8 2 .7 1.8.6 1.7.8 1.7.6 1.4.6 1.3.6 1.2.5 1 .6.7h-.7zm.5 1-.4.2-.2-.4h.7zM379 542l1-.2v1.2l.2 1.4.2 1.5.2 1.8.2 1.7v2l.3 2 .2 2v2l.2 2.3v12l-.2 2.4v2.3l-.3 2.4-.4 2.4-.3 2.4-.3 2.2-.4 2.2-.5 2.2-.4 2-.6 2-.7 2-.8 1.6-.8 1.7-1 1.5-1 1.3-1 1.4-1.2 1-.6-.8 1-1 1-1 1-1.4.8-1.4.8-1.6.8-1.7.6-1.8.6-2 .5-2 .6-2 .4-2.2.4-2.3.3-2.3.2-2.4v-2.3l.3-2.4V569l.2-2.2V560l-.2-2.3v-2.2l-.2-2v-2l-.2-2-.2-1.6-.2-1.8-.2-1.5v-1.3l-.3-1.2zm-2-56.7h1v.6l-.2.7-.2.7v1l-.2.8v2.4l-.2 1.3v2.8l-.2 1.6v15.2l.2 2v2.3l.3 2v2.4l.3 2.3v2.2l.3 2.2.4 2.3.2 2.4.3 2.2.2 2.3.3 2.2.4 2.3-1 .2-.5-2.3-.3-2.4-.3-2.3-.4-2.3-.2-2.4-.2-2.3-.2-2.3-.2-2.3-.2-2.2v-2.3l-.3-2.2V512l-.2-2v-16l.2-1.4v-1.3l.2-1.2v-2l.3-1v-.8l.2-.6v-.7.2zm-.2-20v-1l.5.6v3.4l.2.7v3.8l.2.6v6l.2.6v5.3h-1V480l-.2-.7v-5.7l-.2-.6v-4.5l-.2-.7v-2.4l.4.4zm0-1h.4v.6l-.5-.4zm-23.7 4v-1l.8-.2h.8l.7-.2h2.2l.7-.2h.8l.7-.2h.8l.7-.2h1.4l.7-.2h.8l.7-.2h.6l.8-.2h1.7l.6-.2h.7l.8-.2h.8l.7-.2h1.5l.8-.2h.7l.7-.2h.8l.7-.2v1h-.6l-.6.2h-.8l-.7.2h-1.6l-.7.2h-.8l-.8.2h-.8l-.7.2h-1.5l-.7.2h-.8l-.7.2h-.7l-.8.2H362l-.7.2h-.7l-.8.2h-.7l-.6.2H357l-1 .2h-.6l-.7.2h-.8zm.2-1v1l-.6-.6.5-.6zm-.6.4v-.6h.5l-.4.5z"/>
|
||||||
|
<path fill="#fff" d="m342.2 486.7 21-.5c-2 8.6-.5 35.6 3.2 56.2 3.3 19.3 8 69.3-12 81.3-12.5-26.4-3-52-9.2-75-5-24.3-6-44.6-3-62"/>
|
||||||
|
<path fill="#21231e" d="m363 486.7-20.8.5v-1l21-.5.4.6zm0-1h.7v.6zm3.8 56.6-1 .2-.3-2-.3-2-.3-2-.4-2-.3-2-.2-2-.2-2.2-.2-2-.2-2.2-.2-2-.2-2-.2-2-.2-2.2v-2l-.3-2v-2l-.2-2V499l-.2-1.7v-4.5l.2-1.4v-1.2l.2-1.2v-1l.3-1v-1l1 .3v.8l-.2 1v1l-.2 1.3v5.4l-.2 1.6v4.9l.2 1.8v4l.2 1.8v2l.3 2v2l.3 2 .2 2v2l.3 2.2.3 2 .2 2.2.3 2 .3 2 .3 2 .3 2.2.2 2 .3 2 .3 2zM354 624l.8-.6-.7-.2 2-1.2 1.5-1.4 1.5-1.6 1.4-1.8 1.3-2 1-2 1-2.3 1-2.6 1-2.5.6-2.8.6-2.7.4-2.8.4-3 .4-3 .3-3 .2-3v-3l.2-3v-6.2l-.2-3-.2-3v-3l-.4-3-.2-2.7-.3-2.7-.2-2.5-.4-2.5-.2-2.4-.3-2.2-.3-2-.3-2h.8l.4 1.8.3 2 .3 2.3.3 2.3.4 2.5.3 2.7.3 2.7.2 2.8.2 3 .2 2.8.2 3v15.5l-.3 3-.3 3.2-.4 3-.4 2.8-.5 3-.8 2.7-.7 2.7-.8 2.6-1 2.4-1 2.4-1.2 2.2-1.3 2-1.4 2-1.6 1.5-1.7 1.5-1.8 1.3zm.7 0-.5.4-.3-.5h.7zm-10-75.2 1-.2.5 2.2.4 2.2.4 2.2.3 2.2.3 2.2.2 2.3v2l.2 2.4v25.4l.2 2.4v2.4l.3 2.4.3 2.4.3 2.4.5 2.4.4 2.4.5 2.4.7 2.5.7 2.4.8 2.5 1 2.5 1 2.4-.8.5-1.2-2.6-1-2.5-.8-2.6-.8-2.5-.7-2.5-.5-2.5-.5-2.4-.4-2.5-.2-2.3-.3-2.5-.2-2.4V594l-.2-2.4V564l-.2-2.3-.2-2.3-.2-2.2-.4-2.2-.3-2-.5-2.3-.5-2v-.2zm-2.5-62.6v1l.5-.4-.3 1.6-.2 1.7-.2 1.7-.2 1.7-.2 1.7v1.8l-.3 1.8v3.5l-.2 2v7.3l.2 2v1.8l.2 2v2l.2 2 .2 2 .2 2 .2 2 .3 2 .2 2 .3 2.3.3 2 .3 2.2.4 2.2.4 2.2.4 2 .4 2.4.4 2.2-1 .2-.4-2.2-.4-2.3-.4-2.2-.4-2-.3-2.3-.4-2.2-.3-2-.3-2.2-.2-2-.3-2.2-.2-2-.2-2-.2-2v-2l-.2-2v-2l-.2-1.8v-13l.2-1.8v-1.8l.2-1.7.2-1.7.2-1.7.2-1.7.2-1.7.3-1.7.5-.4zm-.5.4v-.4h.5zm.5 0v-.4zm-.5 0v-.4h.5z"/>
|
||||||
|
<path fill="#fff" d="M422 454.4c0 8.7-3.2 66.5-4 74.6s-4.5 13.6-8.4 16.6q-5.1-7.5-5.5-19.3c0-4 2.3-58.7 3-70 7-17 11.8-17.4 15-2z"/>
|
||||||
|
<path fill="#21231e" d="M418.5 529h-1v-1l.2-1v-1.5l.2-1.7v-2l.2-2v-2.3l.3-2.4v-2.5l.3-2.7.2-2.8v-3l.3-3 .2-3 .2-3v-3l.3-3.2.2-3 .2-3.2v-3l.3-3V474l.3-3v-2.5l.2-2.4V464l.2-2v-2l.2-1.8v-3.7h1v5.6l-.2 2v2l-.2 2.3v2.6l-.3 2.6-.2 2.7v2.8l-.3 3-.2 3v3l-.3 3-.2 3v3.2l-.3 3-.2 3.2-.2 3v3l-.2 2.7-.2 2.6v2.6l-.3 2.4v2.3l-.3 2v2l-.2 1.5v1.5l-.2 1.2v1zm-9.3 17 .8-.6-.7-.2.4-.2.3-.3.4-.4.3-.3.4-.3.4-.4.4-.3.3-.4.4-.4.4-.4.3-.5.4-.4.3-.6.3-.4.3-.5.3-.5.3-.5v-.5l.4-.6.2-.5.3-.7v-.6l.3-.6.2-.5.2-.7.2-.6V532l.3-.8v-.8l.2-.7v-.7h1v1.6l-.2.7-.2.7v.7l-.2.7-.2.7-.2.8-.2.6-.2.6-.2.6-.3.6-.2.6-.3.6-.3.5-.2.5-.3.5-.3.5-.5.5-.3.5-.3.4-.4.4-.4.4-.3.4-.5.4-.4.4-.2.4-.4.3-.4.4-.3.4-.4.3-.4.3h-.8zm.7 0-.5.4-.3-.5h.7zm-6.4-19.7h1v2.8l.2.8v.7l.2.7v1.2l.3.6v.8l.2.6.2.7v.7l.3.6v.6l.3.5.2.6.2.6.2.6v.6l.3.5.3.6.2.4.2.5.3.5.3.5.2.5.3.4.4.4.3.5.3.4-.8.5-.3-.6-.4-.4-.3-.5-.3-.6-.3-.6-.3-.5-.3-.6v-.6l-.4-.5-.2-.6-.2-.6-.2-.4-.2-.6-.2-.6-.2-.6-.2-.6-.2-.7-.2-.6v-.7l-.3-.7v-.6l-.2-.7v-.6l-.2-.7v-1.4l-.2-.7v-1.5l-.2-.7v-1.5zm3-70 .8.3v-.2 2.6l-.2 1.6v2l-.2 2v2l-.2 2.4v2.5l-.2 2.6v2.8l-.3 2.8v2.8l-.2 3v2.8l-.2 3v3l-.2 2.8v3l-.3 2.7v2.7l-.2 2.7v7.3l-.2 2v3.8l-.2 1.6v3.3h-1v-6.8l.2-2V513l.2-2.4V508l.2-2.6.2-2.8v-2.8l.2-3V494l.2-3 .2-2.8v-3l.2-2.8v-3l.3-2.7V474l.2-2.5V469l.2-2.4v-4.2l.2-1.8V459l.2-1.5v-1.2zm16-2h-1v.2l-.3-1.4-.3-1.2-.3-1.2-.4-1-.3-1-.4-1-.3-1-.4-.6-.5-.6-.4-.5-.2-.5-.4-.4h-.4l-.3-.2h-1.1l-.4.2-.5.2-.4.4-.5.5-.5.6-.6.7-.5.7-.5 1-.6.8-.5 1-.6 1.2-.7 1.2-.6 1.4-.6 1.4-.6 1.6-1-.4.7-1.6.8-1.4.6-1.4.6-1.2.6-1.2.6-1 .6-1 .6-1 .6-.7.5-.7.6-.6.6-.6.6-.4.5-.3h.6l.5-.3h1.2l.5.3.5.4.5.4.4.5.4.6.4.5.4.8.3 1 .4.8.3 1.2.3 1 .4 1.3.3 1.4.3 1.3zm-.5 0h.6-.5zm.6 0"/>
|
||||||
|
<path fill="#fff" d="M385 407.2c-.6 18.7-1 59-.8 62.6a51 51 0 0 0 6.7 22.6c4-3 7.7-8.5 8.4-16.6.6-6.7.8-39.5 1-56-4.3-2.6-8.7-6-12.2-9.7l-3.2-2.8z"/>
|
||||||
|
<path fill="#21231e" d="M384.7 469.8h-1v-23.4l.2-2.4v-19.6l.2-2.4v-9l.2-2v-3.8h1v7.8l-.2 2.4v9.4l-.2 2.5v12.5l-.2 2.4v25.4zm6 22.2.5.8h-.7l-.3-.6-.3-.5-.4-.6-.4-.4-.3-.6-.4-.6-.3-.7-.3-.6-.2-.6-.3-.7-.3-.7-.3-.7-.3-.7-.3-.8-.2-.7-.3-.7-.3-.8-.2-.8-.2-.7-.2-.8v-.8l-.3-.8v-.8l-.3-.8v-.8l-.2-.8v-.8l-.2-.8v-.7l-.2-.7v-1.5h1v2.5l.2.8v.8l.3.7v.8l.2.8.2.8.2.8v.7l.2.7.3.8.2.7.2.8v.8l.4.7.2.7.3.7v.7l.4.6.3.7.3.7.2.6.3.6.3.5.3.6.4.6.3.6.3.5.3.4h-.7zm.5.8-.4.3-.3-.3zm7.7-17h1l-.2.8v.8l-.2.7v.8l-.3.7-.2.7v.7l-.2.5-.2.7-.2.6-.3.6v.6l-.4.6-.2.6-.3.5-.2.5-.3.6-.3.5-.3.5-.4.4-.4.5-.3.4-.5.6-.3.4-.4.4-.3.4-.4.4-.3.3-.3.4-.4.2-.4.3-.4.3-.6-.8.4-.3.3-.3.4-.3.4-.2.4-.4.4-.3.3-.3.4-.4.4-.4.3-.5.4-.4.3-.3.3-.5.3-.5.3-.4.3-.5.2-.6.3-.5.2-.5.3-.6.2-.6.2-.5.2-.6.2-.8.2-.6.2-.6v-.7l.3-.7v-.7l.2-.8v-.7l.2-.8zm1-55.5.6-.8.2.4v18.8l-.2 2.2v14.6l-.2 2v8.9l-.2 1.5v4.3l-.2 1v2.6h-1V471l.2-1.5v-6.3l.2-1.8v-12l.2-2v-20.8l.2-1.7v-5.1l.2.3zm.6-.8h.2v.4l-.2-.5zm-12.4-9.8v1l.4-1 .3.4.2.5.4.3.3.3.3.5.3.3.4.3.3.4.4.4.3.3.4.4.4.3.4.3.3.3.4.3.3.4.4.3.4.3.5.3.3.2.4.2.4.3.4.3.4.3.4.3.4.3h.4l.4.4.4.3.4.2.4.2.4.3-.6.8-.4-.3-.4-.2-.4-.3-.4-.3-.4-.2-.4-.3-.4-.3-.4-.3-.4-.2-.4-.3-.4-.3-.4-.3-.4-.4-.3-.3-.4-.3-.3-.3-.4-.3-.4-.3-.5-.2-.4-.3-.2-.3-.4-.4-.4-.3-.3-.3-.4-.3-.4-.5-.4-.3-.4-.3-.3-.5-.5-.3-.3-.4-.3-.3.2-.8zm-.3.8-1-.8h1.2l-.3.8zm-2.5-3.3h-1l1-.3.2.2.2.2v.2h.3v.3h.4v.2l.3.2.3.2v.2h.3v.2h.2v.2h.3l.2.2v.2h.2v.2l-.2.8v-1l-.3 1h-.2v-.2h-.2v-.2h-.2v-.2h-.2l-.2-.2v-.2h-.2l-.2-.2-.3-.3h-.3v-.3h-.3v-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2h-.2l1-.4zm-1 0v-1.4l1 1z"/>
|
||||||
|
<path fill="#fff" d="M400 419.3c-.4 20-.2 51.7 0 54.3a34 34 0 0 0 5.4 19.3c4-3.2 8-8.8 8.8-17 .5-5 2-26.4 2-42.8-5.3-1.7-12-10.4-16.3-14z"/>
|
||||||
|
<path fill="#21231e" d="M400.4 473.5h-1v-12.9l-.2-1.7v-33.7l.2-2v-4h1v14.5l-.2 2v37.1l.2.5v.3zm4.7 19 .7.8h-.7l-.3-.6-.3-.5-.3-.5-.2-.5-.3-.5-.3-.5-.2-.6-.3-.5-.2-.5-.3-.5-.2-.6-.2-.6-.2-.6-.2-.6-.2-.6-.2-.6-.2-.6v-.6l-.3-.7v-.6l-.3-.8v-.6l-.2-.7v-.8l-.2-.6v-.7l-.2-.8v-1.5l-.2-.7v-1.4h1v2.8l.2.7v.7l.2.7v.7l.2.7v.7l.2.6v.7l.3.6v.6l.3.5v.6l.3.7.3.6.2.6.2.5.2.5v.6l.4.5.2.7.3.5v.5l.4.5.2.5.3.5.2.5.3.5.3.4zm.7.8-.4.3-.3-.4zm8-17.3h1v1.7l-.3.7v.7l-.3.8v.7l-.2.7-.2.7-.2.5-.3.7v.6l-.4.6-.2.6-.3.5-.2.6-.3.6-.4.6-.2.5-.3.5-.4.5-.2.4-.4.6-.3.4-.3.5-.3.4-.4.3-.4.3-.4.4-.3.3-.5.4-.4.3-.3.3-.6-.8.5-.3.3-.3.4-.4.4-.3.3-.4.3-.4.3-.4.4-.4.3-.4.4-.4.3-.5.3-.4.4-.4.3-.5.3-.3.3-.6.3-.4.2-.5.3-.5.2-.5.3-.6.2-.6.2-.6.2-.6.2-.7.2-.7.2-.7v-.7l.3-.8v-.7l.2-.7v-.8zm2.4-42.3.4-1 .3.5v7.9l-.2 1.6v5.1l-.2 1.6v1.7l-.2 1.7v4.6l-.2 1.6v2.9l-.2 1.3v1.4l-.2 1.3v2.5l-.2 1v2.2l-.2 1v1.6l-.2.7v1.1h-1v-2l.2-.8v-1.8l.2-1v-2.2l.2-1.2v-2.7l.2-1.3v-1.5l.2-1.4V457l.2-1.6v-1.6l.2-1.6V449l.2-1.6V443l.2-1.6V433l.5.5zm.4-1h.3v.5zm-16-13.4h-1l.8-.4.5.2.4.4.5.4.5.4.5.5.4.4.4.4.5.5.4.6.5.4.4.5.5.4.5.6.5.5.5.5.5.6.6.5.5.6.7.5.5.6.5.5.6.5.5.5.5.4.6.4.4.4.5.3.4.3.5.3.4.3.5.2.4.2-.3 1-.5-.2-.5-.3-.5-.2-.6-.3-.6-.4-.6-.3-.5-.4-.6-.4-.5-.4-.6-.5-.5-.5-.5-.5-.5-.5-.6-.5-.5-.5-.7-.5-.5-.6-.5-.5-.6-.5-.5-.6-.5-.4-.5-.5-.5-.5-.5-.5-.5-.5-.5-.4-.4-.4-.5-.4-.4-.5-.6-.4-.4-.3-.4-.4.8-.3zm-1 0v-1l.8.6-.8.2z"/>
|
||||||
|
<path fill="#fff" d="M387 405.6 373 385c-2.8 10.2-6.3 82.8-2.2 88.8a36 36 0 0 0 6.4 19.3c4-3 9.8-8.5 8-16.7-1.6-8.2.4-62.3 2-70.7z"/>
|
||||||
|
<path fill="#21231e" d="m373.3 384.7 14.2 20.6-.8.5-14.3-20.5v-.4zm-1 .2.4-1 .5.7-.8.2zm-1.2 88.7h-1l.2.3-.5-.8-.3-1.2-.3-1.5v-1.8l-.3-2-.2-2.5v-2.6l-.2-2.8v-20.3l.2-3.7V431l.2-4 .2-3.6v-3.8l.3-3.7.2-3.7.2-3.4.3-3.5.2-3.2.3-3 .2-3 .3-2.5.3-2.4.2-2 .3-2 .3-1.3.4-1 1 .2-.3 1-.2 1.5-.3 1.8-.3 2-.2 2.4-.3 2.5-.3 2.8-.2 3-.3 3.3-.3 3.3-.2 3.4-.2 3.6-.2 3.7v3.7l-.3 3.8v3.8l-.2 3.7v3.8l-.2 3.6v19.6l.2 2.6.2 2.3.2 2v1.8l.4 1.5.3 1 .3.7zm0-.2v.2zm5.7 19.2.6.8-.7-.2-.3-.4-.3-.6-.3-.5-.3-.6-.3-.5-.2-.6-.3-.6-.3-.5-.3-.6-.4-.5-.2-.6-.3-.6v-.6l-.4-.6-.2-.6-.2-.6-.3-.6-.3-.6-.2-.7v-.6l-.3-.5-.2-.7v-.7l-.3-.6v-.6l-.2-.7v-.6l-.2-.7v-1.5l-.2-.7v-.7h1v1.4l.2.7v.6l.2.6v.7l.2.6v.7l.2.6.2.6v.6l.3.6.2.7.2.6.2.7v.6l.3.6.2.5.3.6.2.6.3.6.2.5.3.6.3.5.3.5.3.6.3.4.2.5.3.4.3.4.3.5.4.5h-.7zm.6.8-.4.3-.4-.5zm7.4-17 1-.3v.8l.2.7v3l-.2.7-.2.7v.7l-.3.7-.2.6-.2.6-.2.6-.3.6-.3.5-.4.6-.3.4-.4.5-.5.4-.3.5-.4.4-.4.4-.4.4-.5.5-.4.4-.3.5-.4.3-.4.4-.5.3-.4.3-.3.4-.4.3-.6-.8.4-.3.5-.3.4-.2.3-.4.3-.3.4-.3.4-.4.4-.3.4-.4.4-.5.4-.3.4-.4.4-.5.3-.5.5-.5.3-.5.3-.5.4-.5.3-.5.2-.6v-.6l.3-.5.2-.5.2-.7v-.6l.2-.6v-3.1l-.2-.7zM387 406v-1l.6.7-.2 1-.2 1v1.4l-.3 1.7-.2 2-.2 2v2l-.3 2.4v2.5l-.3 2.6v2.8l-.2 2.7-.2 2.8v3l-.2 2.8v3l-.2 3v8.5l-.2 2.8v13.5l.2 2v1.5l.2 1.4v1l.2 1-1 .2v-1l-.2-1v-1.5l-.2-1.7v-4l-.2-2v-15.5l.2-3V441l.2-3v-2.8l.2-3 .2-2.7v-2.8l.2-2.7.2-2.5V419l.3-2.3v-2.2l.3-2v-2l.3-1.5.3-1.4v-1.2l.3-1 .5.7zm0-1h.7v.7zm0 0v1l-.3-.2.4-.7zm0 1h-.2v-.2l.3.3z"/>
|
||||||
|
<path fill="#fff" d="M358 451.2q-1.2 17-1 23 .3 11.6 5.5 19.2c4-3 9-8.5 8.4-16.6-2-31.5 1.4-61 5.4-89.6-3.4-2.5-9-5-12.5-7.6q-10.4 57.3-6 71.6z"/>
|
||||||
|
<path fill="#21231e" d="M357.4 474h-1v-7.4l.2-.7V463l.2-.7V459l.2-1v-1.8l.2-1v-3l.2-1h1v2l-.2 1v2l-.2 1v2.8l-.2.7v3.2l-.2.7v4.2l-.2.6v4.3zm4.8 19 .6.8h-.7l-.3-.6-.3-.5-.3-.5-.2-.5-.2-.5-.3-.5-.3-.6-.2-.4-.3-.6-.2-.5-.3-.6-.2-.7-.2-.6-.2-.6-.2-.5-.2-.6v-.7l-.3-.6v-.7l-.3-.6v-.7l-.2-.6-.2-.6v-.7l-.2-.7v-1.4l-.2-.7v-1.5l-.2-.7v-.7h1v2.2l.2.7v1.2l.2.7v.6l.2.6v.7l.2.5.2.7v.6l.3.6v.6l.3.6.2.6.2.6.2.6.2.6.2.5.2.6.2.6.3.6.2.5.2.5.2.5.3.5.3.5.3.5.3.5.3.4zm.6.8-.4.3-.3-.3h.8zm7.6-17h1v3.7l-.3.7v.6l-.2.7v.7l-.4.6-.2.6-.2.6-.3.6-.3.6-.3.5-.2.6-.3.5-.4.5-.2.5-.4.4-.4.5-.4.4-.3.6-.4.4-.5.4-.4.4-.3.4-.4.3-.4.3-.5.4-.4.3-.2.3-.6-.8.3-.3.4-.3.3-.3.3-.2.4-.4.4-.3.4-.3.4-.4.3-.4.4-.5.3-.4.3-.4.4-.4.3-.5.3-.5.3-.5.2-.5.3-.5.3-.5.2-.6.3-.6.2-.5.2-.6.2-.8v-.6l.2-.6v-.7l.2-.6zm5.7-89.2.7-.8.2.5-.5 2.6-.4 2.6-.3 2.7-.3 2.7-.4 2.7-.3 2.7-.3 2.7-.4 2.8-.3 2.8-.3 2.7-.3 2.8-.2 2.8-.2 2.8-.3 2.7-.2 2.8v3l-.3 2.7-.2 2.8-.2 2.8v2.8l-.2 3v8.5l-.2 3v8.6l.2 3v3l.2 2.8.2 3h-1l-.2-3v-3l-.2-2.8v-5.8l-.2-3V451l.2-2.8v-3l.2-2.7v-2.7l.3-3 .2-2.7.2-2.8V428l.3-2.7.3-2.8.3-2.8V417l.4-2.8.3-2.7.3-2.8.4-2.7.3-2.7.4-2.7.4-2.7.4-2.8.3-2.7.5-2.7.4-2.7v.6zm.7-.8.2.2v.3l-.3-.5zm-12.3-7-1-.3.8-.3.3.2.4.2.2.3h.3l.4.3.3.3.4.2.4.2.5.3.4.2.3.2.4.3.4.2.5.3.5.2.4.2.4.2.4.2.5.3.4.2.5.2.4.2.3.2.4.3h.4l.5.4.4.2.3.2.3.3.4.3.3.3.4.2-.6.8-.2-.2-.4-.3h-.3l-.3-.3-.3-.3-.4-.2-.4-.2-.4-.3-.4-.2-.4-.2-.4-.3-.4-.2-.4-.3-.5-.2-.4-.2-.4-.2-.5-.2-.5-.3-.4-.2-.4-.2-.4-.2-.4-.2-.4-.3h-.4l-.4-.4-.4-.2-.4-.2-.4-.3-.4-.3-.3-.3-.4-.2-.4-.2.8-.3zm-1-.3.2-.8.6.5zm-6 71.4.8.6-.8-.2-.3-1v-1l-.3-1-.2-1.3v-1l-.2-1.4V443l-.2-1.6v-5l.2-1.8v-2l.2-2 .2-2 .2-2.3.2-2.3.2-2.5.3-2.4.3-2.5.3-2.6.4-2.8.4-2.8.4-3 .4-2.8.5-3 .6-3.2.5-3.2.6-3.3.6-3.5.6-3.5.6-3.5 1 .2-.6 3.5-.6 3.5-.6 3.4-.5 3.3-.4 3.3-.5 3-.4 3-.4 3-.4 3-.4 2.7-.3 2.7-.3 2.7-.3 2.5-.2 2.4-.3 2.4-.3 2.3v2.2l-.3 2v2l-.2 2v9.8l.2 1.4v1.2l.3 1 .2 1.2v1l.4 1-1-.3zm.8.6-.5.6-.3-.8zm0 0-.7-.7 1 .3zm-.7-.7 1-1v1.3z"/>
|
||||||
|
<path fill="#fff" d="m343.6 471.8 4 14.7c.2 7.7 5 14 8.3 19 4-3 7.5-8 6.5-16-5.8-46.5-3.8-77.3 6-115-22 15-32.3 46.6-25 97.3z"/>
|
||||||
|
<path fill="#21231e" d="m347 486.6-3.8-14.7 1-.3 3.8 14.6zm8.6 18.7.6.7h-.7l-.3-.6-.3-.5-.4-.5-.4-.5-.3-.5-.5-.6-.3-.6-.4-.5-.3-.7-.4-.5-.2-.6-.4-.5-.3-.6-.2-.5-.4-.6-.3-.7-.2-.6-.3-.6-.3-.5-.3-.7-.3-.6-.3-.7-.2-.7-.2-.6-.2-.7-.2-.7-.2-.7v-.6l-.2-.7v-.8l-.2-.8v-.7h1v1.4l.2.7v.7l.2.7.2.6v.6l.3.7v.5l.4.7.2.6.3.6v.6l.4.7.3.6.3.6.3.4.3.6.4.6.3.6.3.5.4.6.3.5.4.5.3.6.3.4.4.5.3.4.4.5.3.4.3.5.4.3h-.8zm.6.7-.4.3-.3-.4h.8zm5.8-16.5h1v5.5l-.2.6v.7l-.3.6v.4l-.3.6-.2.6-.3.5-.2.6-.2.5-.3.6-.3.5-.3.6-.3.4-.2.4-.3.4-.4.5-.2.3-.3.4-.4.4-.3.3-.3.4-.4.4-.4.3-.3.3-.7-.8.4-.3.4-.4.3-.3.4-.3.4-.3.3-.4.4-.3.4-.4.3-.4.3-.4.3-.4.3-.4.3-.4v-.5l.4-.3.2-.5.2-.5.2-.5.2-.5.2-.5.2-.6v-.6l.2-.5v-.6l.2-.7v-5.1zm6.8-114.7-.6-1 .8.7-1 3.5-.8 3.5-.8 3.4-.7 3.4-.7 3.4-.7 3.3-.6 3.5-.6 3.4-.4 3.2-.5 3.3-.3 3.4-.4 3.2-.4 3.4-.4 3.3-.2 3.3-.2 3.4-.2 3.4v3.6l-.2 3.5v14.7l.3 3.7.3 4 .2 3.8.3 4 .4 3.8.3 4 .5 4.2.4 4.3.6 4.4h-1l-.6-4.2-.4-4.3-.5-4.2-.3-4-.4-4-.3-4-.2-3.8-.2-3.8v-3.7l-.2-3.7v-14.3l.2-3.4.2-3.4.2-3.4.2-3.4.3-3.3.3-3.4.4-3.3.4-3.3.6-3.4.5-3.3.7-3.4.6-3.3.7-3.4.7-3.4.8-3.5.8-3.4.8-3.5 1-3.6.8.6zm-.6-1 1-.6-.2 1.3zm-24 98h-1l-.7-4.6-.5-4.6-.5-4.5-.3-4.3-.3-4.2-.2-4.2v-8l.2-3.8.2-3.7.3-3.7.4-3.4.5-3.4.6-3.3.8-3.3.7-3 1-3 1-3 1-2.8 1-2.8 1.2-2.5 1.3-2.5 1.4-2.4 1.5-2.3 1.4-2 1.6-2.2 1.7-2 1.7-2 2-1.6 1.8-1.7 2-1.6 2-1.5.6.8-2 1.4-2 1.6-1.8 1.6-1.8 1.7-1.8 2-1.6 2-1.6 2-1.5 2-1.4 2.3-1.4 2.4-1.4 2.4-1.2 2.6-1 2.7-1 2.7-1 3-1 3-.7 3-.7 3-.6 3.4-.5 3.3-.5 3.5-.3 3.6-.3 3.7-.2 3.8v12l.4 4.4.3 4.3.5 4.5.5 4.6.6 4.7zm-.6 0h.5-.4zm-.4 0"/>
|
||||||
|
<path fill="#fff" d="m344.8 486.2-20.3-6.7c-2.5 20.2 1.4 45.6 7 79 4.6 29 2 51.6-1.5 66.2 21.6-3 30.7-30.7 20-83-3-13.7-5.8-31.3-5.2-55.5"/>
|
||||||
|
<path fill="#21231e" d="m324.7 479 20.2 6.7-.4 1-20.2-6.7-.3-.6zm-.6.4v-.6l.7.2zm8 79-1 .3-.6-3-.5-3.2-.6-3-.5-3-.5-3-.5-2.8-.4-3-.4-2.7-.4-2.7-.4-2.7-.4-2.7-.3-2.7-.4-2.6-.3-2.6-.3-2.4-.2-2.5-.3-2.3-.2-2.4-.2-2.4v-2.4l-.3-2.3V500l-.2-2v-10.7l.2-2 .2-2v-2l.4-2 1 .3-.2 2-.2 1.8v2l-.2 2v4l-.2 2v4.3l.2 2.3v2.3l.2 2.3.2 2.3.2 2.2.2 2.4.3 2.4.2 2.5.3 2.5.3 2.5.3 2.6.4 2.8.4 2.6.3 2.7.4 3 .6 2.7.4 2.8.4 3 .5 3 .4 2.8.5 3 .4 3 .5 3.2zm-2 65.8v1l-.5-.6.4-1.4.2-1.4.3-1.5.3-1.5.3-1.6.4-1.6.3-1.7.2-1.8.2-1.7.2-1.8.2-2 .2-1.8v-2l.3-2 .2-2v-2l.2-2v-13.4l-.2-2.3-.2-2.4-.2-2.4-.2-2.5-.3-2.5-.3-2.6-.3-2.6-.4-2.6-.4-2.7 1-.2.3 2.7.4 2.7.3 2.5.3 2.6.3 2.6.2 2.5.2 2.6v2.4l.3 2.4v6.8l.2 2.3v4.3l-.2 2v2l-.2 2v2l-.2 2-.2 2-.2 1.8-.3 1.8v1.8l-.4 1.7-.2 1.8-.3 1.6-.2 1.6-.3 1.5-.3 1.5-.4 1.5-.3 1.4-.5-.8zm0 1h-.6v-.6zm19.4-83.5 1-.2 1 5 .8 4.6.7 4.6.5 4.5.5 4.3.4 4 .3 4 .2 4v7.2l-.2 3.4-.2 3.2-.4 3-.4 3-.6 3-.6 2.6-.7 2.6-1 2.4-1 2.3-1 2.2-1 2-1.2 1.8-1.3 1.7-1.4 1.5-1.4 1.4-1.5 1.3-1.5 1-1.8 1-1.8.7-2 .7-2 .5-2 .2v-1l2-.3 1.8-.6 1.8-.6 1.7-.8 1.6-1 1.5-1 1.5-1 1.4-1.5 1.3-1.5 1.3-1.6 1-1.8 1.2-2 1-2 1-2.2.7-2.3.6-2.4.7-2.7.5-3 .4-2.8.4-3 .2-3.3v-3.5l.2-3.5v-3.7l-.3-3.8-.3-4-.3-4-.4-4.3-.7-4.4-.8-4.5-.8-4.7-1-5zm-4.8-55 .3-1 .3.5V503l.2 2 .2 1.8v2l.2 1.8v1.8l.2 1.8.2 1.7.2 1.8.2 1.7.2 1.7.2 1.6.2 1.6.2 1.6.2 1.5.3 1.5.3 1.5.3 1.5.2 1.4.3 1.4.3 1.4.3 1.4.3 1.3v1.3l.4 1.2-1 .2-.2-1.3-.3-1.3-.4-1.2-.2-1.4-.3-1.4-.2-1.4-.2-1.4-.3-1.5-.2-1.5v-1.5l-.4-1.6-.2-1.5-.2-1.6-.2-1.7-.2-1.7-.2-1.7-.2-1.8-.2-1.8v-1.7l-.3-2-.2-1.7v-2l-.3-1.8v-4l-.2-2v-13l.3.5zm.3-1 .3.2v.2l-.4-.5z"/>
|
||||||
|
<path fill="#21231e" d="M343.6 584h1v.7h-.8v-.2h-.2v-.4zm-4.3-40 1-.4.4 1.2.2 1.2v1l.4 1.3.2 1.3.2 1.2.2 1.2.2 1.2.2 1.3.2 1.3.2 1.2.2 1.3.2 1.3.2 1.2.2 1.3v1.4l.3 1.3v2.6l.3 1.3v1.3l.2 1.2v2.6l.2 1.3v9.1h-1v-7.6l-.2-1.2v-2.7l-.2-1.3V570l-.2-1.3v-1.3l-.2-1.3v-1l-.3-1.4v-1.3l-.3-1.3-.2-1.3v-1.2l-.3-1.3-.2-1.3-.2-1.3-.2-1.3v-1.2l-.4-1.2-.2-1.2-.2-1.2-.2-1.2-.3-1.2-.2-1.2-.2-1.2zm-5.5-50.4h1.2v6.4l.2 1.5v3.3l.2 1.5v1.6l.2 1.5v1.6l.3 1.5v3.1l.3 1.5.2 1.7v1.5l.3 1.6.2 1.5.2 1.6.2 1.4.2 1.6.3 1.5v1.6l.4 1.6.2 1.6.3 1.6.2 1.5.3 1.6.3 1.5.3 1.5.4 1.6-1 .3-.5-1.7-.4-1.6-.3-1.6-.3-1.5-.3-1.5-.2-1.6-.3-1.6-.2-1.5-.3-1.6-.2-1.6-.2-1.4-.3-1.6-.2-1.5-.2-1.6v-1.7l-.3-1.5-.2-1.6v-1.5l-.2-1.6v-1.4l-.2-1.6-.2-1.6V508l-.2-1.7V503l-.2-1.4v-6.4l-.2-1.6zm1.2 0h-1.2v-.2h.2v-.2h.2v-.2h.8v.2h.2v.4z"/>
|
||||||
|
<path fill="#fff" d="m344 367.3 13.2 2.2-4.6 23.3c-5 24.3-8.2 53-4.5 84.2q2.2 17.5 7.6 28.5c-14.6-1.6-24-15.8-22.7-40.6q-3.9-43.4 3.2-69.4c1.4-5 5.8-24.2 7.8-28.3z"/>
|
||||||
|
<path fill="#21231e" d="m357 370-13-2.3v-1l13.3 2.3.4.6zm.3-1h.5v.6zm-4.2 24-1-.3.2-.3v-.4l.2-.5v-.6l.2-.7v-.6l.3-.7v-.8l.2-.8v-1l.3-.7.2-1 .2-1v-.8l.2-1 .2-1 .2-1 .2-1 .2-1 .2-.8v-1l.3-.7.2-1v-.7l.3-.7v-.7l.3-.6v-.6l.2-.5v-.4l.2-.3v-.3l1 .2v.6l-.2.5v.5l-.3.6v.5l-.2.7-.2.8-.2.8v1l-.3.7-.2 1v.8l-.2 1-.2 1-.2 1-.2.8-.2 1-.2 1v1l-.3 1-.2.7-.2 1v.7l-.2.7v1l-.2.5-.2.6v.7l-.2.5v.7zm-4.4 84h-1l-.3-2.8-.3-3-.3-3-.2-2.8v-2.8l-.3-3V457l-.2-3v-11l.2-2.7v-2.7l.2-2.7.2-2.8.2-2.6.2-2.6.3-2.6.2-2.6.3-2.6.3-2.5.4-2.5.4-2.5.4-2.5.3-2.4.4-2.4.3-2.4.5-2.4.4-2.3.5-2.3.4-2.3 1 .2-.4 2.2-.4 2.3-.5 2.3-.4 2.4-.4 2.4-.5 2.4-.4 2.4-.2 2.4-.4 2.5-.3 2.5-.2 2.5-.3 2.6-.3 2.5-.2 2.5-.2 2.7-.2 2.6-.2 2.6v2.6l-.2 2.7v2.7l-.2 2.7v11.5l.2 3v2.7l.3 3 .2 2.7.3 3 .3 2.8.3 3zm7 29v-1l-.4.8-.4-.7-.3-.6-.3-.8-.4-.7-.3-1-.3-.6-.3-.8-.4-.8-.3-.8-.2-.8-.2-.8-.3-1-.2-.7-.3-1-.2-.8-.3-1-.2-1-.2-.8v-1l-.3-1-.2-.8-.2-1-.2-1v-1l-.3-1-.2-1v-1l-.3-1-.2-1v-1l-.2-1.2-.2-1 1-.2v1l.3 1v1.2l.2 1v1l.3 1 .2 1 .2 1v1l.2 1 .2 1 .2 1 .2 1 .3.8.3 1 .2.8.2 1 .3.7.2 1 .3.7.2.8.3 1 .3.7.3.8.3.7v.8l.4.8.4.7.3.7.4.7.3.6.3.7-.5.7zm.4-.7.4.8h-1l.6-.7zM332.4 465l1-.2v8.8l.2 2 .2 2 .3 2 .4 1.8.4 1.7.4 1.8.5 1.7.5 1.5.6 1.5.6 1.5.7 1.3.8 1.3.7 1.3.8 1 1 1 .8 1.2 1 1 1 .8 1 .8 1 .8 1 .7 1.2.6 1.2.4 1.2.5 1.2.3 1.2.3 1.3.3h1.4l-.2 1h-1.3l-1.4-.4-1.3-.4-1.3-.4-1.2-.5-1.2-.5-1.2-.7-1-.6-1.2-.8-1-.8-1-1-1-1-1-1-1-1.2-.7-1.2-.8-1.3-.8-1.2-.7-1.4-.7-1.5-.5-1.6-.6-1.6-.5-1.7-.4-1.6-.4-1.8-.3-2-.2-1.8-.2-2-.2-2v-8.9zm3.3-69.6 1 .3-.5 1.6-.4 1.7-.4 1.7-.3 1.7-.3 1.8-.3 1.8-.3 1.8-.2 2-.2 1.8-.3 2-.2 2v2l-.3 2v2l-.2 2-.2 2.3v4.3l-.2 2.2v9.6l.2 2.5v2.4l.2 2.5v2.5l.2 2.6.2 2.6.2 2.5.2 2.6.2 2.8h-1l-.2-2.6-.2-2.6-.2-2.7-.2-2.7v-2.5l-.2-2.6v-2.5l-.2-2.4v-18.5l.3-2v-2.2l.3-2v-2.2l.3-2 .3-2 .3-2 .2-1.8.3-2 .4-1.8.4-1.8.3-1.8.5-1.7.4-1.7.3-1.7.4-1.6zm8.3-28.6v1l.4-.3-.2.4-.2.4-.2.6-.2.6-.3.7-.2.8-.2 1-.2.8-.3 1-.3 1-.2 1-.3 1-.3 1-.3 1.2-.4 1-.3 1.2-.3 1-.2 1.2-.3 1-.3 1.2-.3 1-.3 1-.3 1-.2 1-.3 1-.3 1-.2.8-.2.8-.2.7v.6l-.2.6v.5l-1-.3v-.5l.2-.7.2-.6.2-.8v-1l.4-.7.2-1 .2-.8.3-1 .2-1 .3-1 .2-1.2.3-1 .3-1.2.3-1 .4-1 .2-1.2.3-1 .2-1.2.3-1 .3-1 .3-1 .4-1 .2-1 .3-1v-.8l.4-.8.2-.7.3-.7v-.6l.3-.5.2-.5.5-.2zm-.5.2.2-.3h.3zm.5.3v-.5zm-.5-.3.2-.3h.3z"/>
|
||||||
|
<path fill="#fff" d="m336 370.4 6.4 7.4c-1.8 1.4 1.3 7-.4 18-1 6.4-4.8 13.8-5.6 22-2.5 26.5-2.5 59.8 5.4 81-14.5-1.6-19.5-14.8-20-39.6-.4-20.6-.4-44.7 4.7-72.7q1.5-9.7 9.4-16z"/>
|
||||||
|
<path fill="#21231e" d="m342 378.2-6.5-7.5.8-.6 6.5 7.5v.7h-.7zm.8-.7.4.4-.5.2v-.7zm-.3 18.5h-1l.2-1.2v-1l.2-1V385l-.2-.6v-1.2l-.2-.6v-1l-.2-.4v-2.8l.2-.3.3-.4v-.2l.7.8v.2h-.2v.5h-.2v2.2l.2.4v1.1l.2.6v1.1l.2.7v8.1l-.2 1v1l-.3 1zm-5.6 21.7h-1v-1.8l.2-.7.2-.7v-.7l.3-1v-.6l.3-.8.2-.7.2-.8.2-.7.2-.7.2-.7.2-.6.2-.7.2-.7.3-.7v-.7l.3-.6.2-.7.2-.7.2-.6v-.7l.3-.6.2-.6.2-.5.2-.7v-.6l.2-.6v-.6l.2-.6v-.5h1v1.2l-.3.6v.7l-.2.6v.7l-.3.7-.2.6-.2.6v.7l-.3.7-.2.6-.3.7-.2.6-.2.6-.2.7-.2.7-.2.7-.3.7-.3.7-.2.8-.2.7-.2.7v.7l-.2.6-.2.7v.7l-.3.8v.7l-.2.7v1.5zm4.8 81.6v-1l-.4.7-.8-2-.6-2-.7-2.3-.6-2.2-.5-2.3-.5-2.4-.5-2.4-.4-2.4-.4-2.5-.4-2.5-.3-2.5-.3-2.6-.2-2.7-.2-2.7-.2-2.6v-2.7l-.2-2.7V455l-.2-2.8v-10.9l.2-2.7V436l.2-2.8v-2.7l.3-2.6.3-2.7.2-2.6.2-2.6.2-2.4h1l-.3 2.6-.2 2.6-.2 2.5-.2 2.6v2.6l-.2 2.6v2.7l-.2 2.6v2.7l-.2 2.7V455l.2 2.7v2.7l.2 2.7.2 2.5.2 2.7.2 2.6.3 2.5.3 2.6.4 2.4.4 2.4.4 2.5.4 2.3.5 2.4.7 2.2.6 2.2.6 2.2.6 2 .7 2-.5.7zm.5-.6.3.7h-.8zm-21-39.5h1v2.2l.2 2.3v2l.2 2.2.2 2v2l.3 1.7.3 1.8.3 1.8.2 1.6.4 1.5.4 1.6.4 1.5.5 1.4.4 1.2.6 1.3.6 1.2.6 1 .7 1 .6 1 .8 1 .8 1 .8.6 1 .7 1 .7 1 .6 1 .5 1 .4 1 .4 1.3.3 1.3.2 1.3.2-.2 1-1.4-.2-1.3-.3-1-.4-1.3-.4-1.2-.5-1-.6-1.2-.5-1-.7-1-.7-.8-.7-1-1-.7-1-.7-1-.6-1-.7-1.2-.6-1-.5-1.4-.5-1.3-.5-1.5-.5-1.5-.4-1.5-.3-1.7-.4-1.5-.4-1.8-.3-1.8-.2-2v-2l-.3-2v-2l-.2-2.3v-2.2l-.2-2.3zm4.7-72.8 1 .2-.5 2.6-.4 2.6-.3 2.5-.4 2.6-.3 2.4-.4 2.5-.3 2.4-.3 2.4-.2 2.4-.2 2.5-.3 2.3v2.4l-.3 2.4-.2 2.3v2.4l-.2 2.2v2.3l-.2 2v4.5l-.2 2.2V459h-1v-26.9l.2-2.2v-2l.2-2.4v-2.3l.2-2.3.2-2.3.2-2.4.2-2.3.2-2.4.2-2.5.3-2.3.3-2.4.3-2.5.4-2.5.3-2.6.4-2.5.4-2.6.6-2.6.4-2.6zm10.3-16.3-.8.7h.7l-.4.5-.5.4-.5.4-.4.4-.4.5-.4.3-.4.4-.4.5-.4.4-.4.5-.3.5-.4.5-.3.5-.3.4-.3.4-.3.5-.2.4-.3.5-.2.4-.3.5-.2.4-.2.6-.2.5-.2.5-.2.6-.2.5-.2.7v.5l-.3.6v.6l-.2.6v.6l-1-.2v-.6l.2-.6.2-.6v-.6l.3-.6v-.6l.3-.5.2-.6.2-.6.2-.4.3-.6v-.5l.4-.5.2-.6.3-.5.2-.6.3-.5.3-.6.4-.5.3-.6.4-.5.2-.6.4-.4.4-.5.4-.4.4-.4.5-.4.4-.5.5-.4.4-.4.5-.6.6-.4h.7zm-.7 0 .4-.3.3.4z"/>
|
||||||
|
<path fill="#fff" d="m372.3 390-13.4-1.6c-2.6 12.7-4.5 21.7-5 26.2-1 9 1.8 17 5 23 5-3 7.2-9.4 9.2-18.6s2.7-19.2 4-29z"/>
|
||||||
|
<path fill="#21231e" d="m359 388 13.3 1.5v1l-13.5-1.6-.4-.7.5-.4zm-.6.3v-.4h.5l-.6.3zm-4 26.4h-1l.2-.6v-.8l.2-.6v-1l.2-.6v-.7l.3-.6v-.7l.2-.7v-.7l.3-.7v-1l.2-.7.2-.8.2-1v-.7l.3-1 .2-.8v-1l.2-1 .2-1 .2-1 .2-1 .2-1 .3-1 .2-1 .2-1.2.2-1.2v-1l.4-1.3 1 .2-.3 1.2v1l-.3 1.3-.3 1-.2 1.2-.2 1-.2 1-.2 1-.2 1-.2 1-.2 1v1l-.3 1-.2.8-.2.8v1l-.3.7v.8l-.3.7v.8l-.2.7-.2.7v.6l-.2.7v1.3l-.2.5v1.1l-.2.5v.9zm4.3 22.6.5.8h-.7l-.3-.7-.3-.6-.4-.6-.3-.5-.3-.6-.2-.7-.3-.7-.2-.6-.3-.7-.2-.7-.3-.7-.2-.8-.2-.7v-.7l-.3-.7-.2-.7-.2-.7v-.8l-.3-.7v-.7l-.2-.8v-.8l-.2-.7v-.8l-.2-.8v-6.1h1v6.6l.2 1v1.4l.3.7v1.6l.3.8.2.7v.8l.3.7.2.7.3.7.2.6.2.7.2.6.2.6.2.6.3.7.3.5.2.6.3.7.3.6.3.6.3.5-.7-.2zm.5.8-.5.4-.2-.5h.7zm8.6-19h1l-.3 1-.2.8-.2 1v.7l-.3.8-.2.7-.2.8-.2.7v.8l-.4.7-.2.7-.3.6v.7l-.4.6-.2.7-.3.6-.2.5-.3.5-.3.6-.3.5-.4.7-.3.5-.4.5-.5.4-.3.6-.4.4-.4.4-.4.4-.5.3-.4.5-.4.3-.4.3-.5-.7.4-.3.5-.3.4-.3.3-.4.4-.3.4-.4.4-.4.4-.5.3-.4.4-.4.4-.5.3-.4.4-.5.2-.4.3-.6.2-.5.3-.6.2-.7.3-.7.2-.6.3-.7.2-.6.2-.7v-.8l.3-.7.3-.7.2-.8.2-.8.2-.8.2-.8.2-.8.2-1zm4.5-28.5v-1l.5.5v1l-.3 1v.8l-.2 1v1l-.2.8v1l-.2.8v1l-.2 1v.8l-.2 1v1l-.2.8v1l-.2.8v1l-.2.8v1l-.2 1-.2.8v1l-.2.8-.2 1v.8l-.2 1-.2.8-.2.8v1l-.3.8v1l-.3.7h-1l.3-1v-.8l.3-1 .2-.7v-1l.3-.8v-1l.3-.8.2-1v-1l.3-.8v-1l.2-.8v-1l.2-.8v-1.8l.2-1v-1l.2-.8v-1l.2-.8v-1l.3-1v-1.8l.2-1v-.8l.2-1v-.8l.3-1v-1l.6.7zm0-1h.6l-.2.5zm0 0v1zm0 1"/>
|
||||||
|
<path fill="#fff" d="M407 418.6h-11.8q-.7 16.6-.5 22.3c.4 7.5 2.3 14 5.5 18.7 3.6-3 7-8.3 7.6-16.2.6-8-.6-16.5-.7-25z"/>
|
||||||
|
<path fill="#21231e" d="M395.2 418.2h12v1h-12l-.5-.5zm-.5.5v-.5h.5zm.5 22.2h-1v-11.4l.2-.8v-4.4l.2-.8v-5h1v7.8l-.2.7v5.6l-.2.7v7.7zm4.7 18.4.6.7h-.8l-.3-.5-.3-.4-.3-.4-.4-.5-.2-.4-.3-.5v-.4l-.4-.6-.2-.5-.3-.5v-.6l-.3-.6-.2-.5-.2-.6-.2-.6v-.5l-.2-.6-.2-.7-.2-.6v-.6l-.2-.6-.2-.6v-.7l-.2-.5v-.7l-.2-.7v-.5l-.2-.7v-2.6l-.2-.7h1v1.1l.2.7v1.4l.2.7v.7l.2.6v.7l.2.6v.6l.3.6v.7l.3.6v.7l.3.5v.6l.2.6.2.5.2.5.2.5.3.6.3.5.2.6.3.5.2.6.3.5.2.6.3.5.3.6.4.4.2.5h-.7zm.6.7-.5.5-.2-.5h.8zm6.7-16.6h1v1.6l-.2.7v1.4l-.3.8v.6l-.3.7-.2.7-.2.6-.2.6-.2.5-.2.5-.3.6v.5l-.4.7-.2.5-.3.5-.2.5-.3.4-.3.5-.3.5-.4.4-.3.3-.4.4-.4.4-.3.5-.4.3-.4.4-.4.3-.3.3-.4.3-.7-.6.2-.3.4-.2.3-.3.2-.4.3-.2.4-.3.2-.4.3-.3.3-.4.3-.4.4-.5.3-.4.2-.4.2-.4.3-.5.2-.5.3-.5.2-.5.3-.5.2-.6.2-.4v-.6l.3-.6v-.6l.3-.6v-.7l.3-.6v-1.4l.2-.7V444zm0-24.3v-1l.4.6v5.6l.2 1v4.6l.2.8v2.8l.2.8v9.2h-1v-11.8l-.2-.8V429l-.2-1v-3l-.2-1v-5.3l.4.5zm0-1h.4v.6l-.5-.5z"/>
|
||||||
|
<path fill="#fff" d="m420 424.2-12 .6q0 16.5.3 22.2a37 37 0 0 0 6.2 18.6c3.5-3 6.8-8.6 7-16.5s-1-16.3-1.5-24.8z"/>
|
||||||
|
<path fill="#21231e" d="m408 424.3 12-.6v1l-12 .6-.4-.5.5-.5zm-.4.5v-.5h.5l-.4.5zm1.2 22.2h-1v-4l-.2-.6v-17.8h1V445l.2.4zm5.4 18.2.6.8h-.7l-.2-.5-.3-.5-.3-.5-.3-.5-.4-.5-.3-.5-.3-.5-.2-.5-.3-.5-.3-.5-.2-.6-.2-.5-.3-.7-.2-.6-.2-.6v-.4l-.3-.6-.2-.6v-.7l-.3-.6-.2-.5v-.6l-.3-.8v-.6l-.2-.7v-.8l-.2-.6v-.7l-.2-.8v-2h.8v.7l.2.7v1.4l.2.7.2.7v.7l.2.7v.6l.3.6v.7l.2.6v.5l.3.6.2.7.2.5v.6l.3.6.2.5.2.5.2.5.2.5.2.6.2.6.3.5.2.6.2.4.3.5.3.5.3.4.3.5.3.3h-.8zm.6.8-.4.4-.3-.5h.8zm6.2-17h1v2.3l-.2.7v.7l-.2.7-.2.6v.7l-.3.6v.6l-.2.5-.2.6-.2.7-.2.6-.3.5-.3.6-.3.5-.2.4-.3.5-.4.4-.3.4-.3.5-.3.4-.3.4-.3.5-.2.4-.3.3-.3.4-.4.3-.3.4-.3.3-.4.3-.6-.8.3-.3.3-.4.3-.3.4-.3.3-.4.4-.3.4-.4.2-.5.3-.4.2-.3.3-.4.3-.5.2-.4.3-.5.2-.6.3-.5.2-.6.2-.5.2-.6.2-.6.2-.6.2-.5.2-.6v-.5l.3-.7v-.6l.2-.7v-.6l.2-.8zm-1-25.3v1l.5-.5v2.4l.2.8v1.5l.2.7v3.4l.2.7v1.5l.2.8v.8l.2.8v2.5l.2.7v2.3l.2.8v5.3h-1v-7.4l-.2-.7v-1.6l-.2-.8V437l-.2-1v-1.5l-.2-.7v-1.6l-.2-.8v-1.6l-.2-.8v-1.6l-.2-.8v-2.2l.4-.6zm-.5.6v-.6h.5zm.5.4v-1z"/>
|
||||||
|
<path fill="#fff" d="m395 412.4-12-.8q-1.8 16.4-2.2 22-.4 11.6 4 19.3c3.8-2.8 7.7-8 8.8-15.7 1.2-8 .8-16.4 1.4-25z"/>
|
||||||
|
<path fill="#21231e" d="m383 411 12 1v1l-12-1-.4-.5.5-.4zm-.4.5v-.5h.5l-.4.5zm-1.3 22.2h-1V431l.2-.4V429l.2-.6v-1.2l.2-.6v-2.2l.2-.7v-1.4l.2-.8v-.8l.2-.8v-1l.2-.8v-1l.2-.8v-1l.3-1v-1l.2-1v-1l1 .2v2l-.3 1v1l-.2 1v.8l-.2 1v.7l-.2 1v.8l-.2.8v.8l-.2.8v1.3l-.2.7v1.4l-.2.6v1.1l-.2.6v1.7l-.2.5v2.7zm3.2 18.8.5.8-.7-.2-.2-.4-.2-.5-.2-.4-.3-.5-.2-.4v-.5l-.3-.5-.3-.5v-.6l-.3-.5v-.7l-.3-.6v-.6l-.3-.4v-.6l-.3-.7V443l-.2-.5v-.7l-.2-.6V440l-.2-.8v-5.5h1v6.8l.2.7v.7l.2.6v.6l.2.5v.6l.2.7.2.6v.6l.3.5v.5l.3.6.2.5.2.7.2.5.2.5.2.5.2.5.3.5.2.5.2.5.2.4h-.7zm.5.8-.4.3-.3-.5.8.3zm8.2-16h1l-.2.8v.8l-.3.7v.7l-.3.7-.2.6-.2.6-.2.6-.3.6-.2.6-.3.6-.2.6-.3.5-.3.6-.3.5-.4.5-.3.5-.3.4-.3.5-.3.4-.4.6-.3.4-.4.4-.3.3-.4.4-.3.5-.4.3-.5.3-.3.4-.4.3-.4.3-.3.3-.5-.8.3-.3.4-.2.3-.3.4-.3.2-.3.4-.2.3-.4.3-.3.3-.3.3-.4.2-.4.4-.5.3-.4.2-.3.3-.5.3-.5.3-.4.4-.5.2-.5.3-.6v-.5l.4-.6.2-.5.2-.5.2-.6.2-.7.2-.7.2-.6.2-.7v-.7l.2-.7.2-.8zm1.8-24.4v-1l.5.5v1.5l-.2.8v5.6l-.2.7v9l-.2.8v2.2l-.2.8v.7l-.2.8v1l-.2.6v.8l-1-.2v-.7l.2-.8v-1.5l.2-.7V432l.2-.8V428l.2-.8V421l.2-.7v-4.8l.2-.8v-2.3l.6.5zm0-1h.5v.5z"/>
|
||||||
|
<path fill="#fff" d="m384 399.6-13.2-5.4c-2.2 8.8-5.2 37.8 0 50 4.5-3 9-8.4 10.3-17 1.6-9 2-18.2 3-27.6z"/>
|
||||||
|
<path fill="#21231e" d="m371 393.7 13 5.4-.3 1-13-5.4-.4-.6zm-.7.3.2-.5.5.2zm.3 50 .6.7-.7-.2-.5-1.2-.5-1.3-.3-1.4-.4-1.5-.3-1.5-.3-1.6-.2-1.7-.2-1.7v-1.8l-.2-1.8v-3.6l-.2-2V418l.2-2v-1.8l.2-1.8.2-1.8V409l.3-1.8.2-1.7V404l.3-1.6.2-1.5.3-1.4.2-1.3.2-1.2.2-1v-1l.3-1 1 .3-.2 1v.8l-.3 1.2-.3 1.2-.2 1.3-.2 1.4-.2 1.5-.2 1.6-.2 1.6v1.7l-.3 1.7v1.7l-.2 1.8v1.8l-.2 1.8v3.8l-.2 2v5.4l.2 1.8v1.7l.2 1.8.2 1.7.2 1.6.3 1.6.3 1.5.3 1.3.5 1.4.4 1.2.4 1h-.8zm.6.7-.5.3-.2-.5zm9.4-17.7h1v1l-.3.8-.2.8v.7l-.3.8-.2.8-.3.7-.2.7-.3.7-.3.5-.3.7-.2.6-.3.6-.3.6-.3.6-.5.5-.3.7-.4.5-.4.5-.3.5-.3.4-.4.5-.4.5-.5.4-.4.3-.3.4-.4.4-.4.4-.5.4-.5.3-.4.4-.4.3-.6-.8.4-.4.4-.3.4-.3.4-.3.4-.4.4-.3.4-.4.4-.4.4-.4.3-.4.4-.4.3-.5.3-.4.4-.4.3-.5.4-.4.3-.6.3-.5.3-.6.3-.6.2-.6.3-.7.3-.5.2-.6.3-.8.2-.7.2-.7.3-.7v-.7l.3-.7.2-.8v-.8zm3-27 .4-1 .4.6v1l-.2.8v1.7l-.2 1v1l-.2.8v1.8l-.2.8v1.7l-.2 1v1.7l-.2 1v1.6l-.2 1v.7l-.2 1v.8l-.2.8v1l-.2.7v.8l-.2 1v.8l-.2 1v.7l-.3.8v.8h-1v-.8l.2-1v-1.5l.2-.8v-1l.2-.8v-1l.2-.7v-1l.2-.7v-1l.2-.7v-1.6l.2-1v-.7l.2-1V410l.2-.8v-1.8l.2-.8v-1.8l.2-1v-1.5l.2-1v-.8l.2-1zm.4-1 .4.3v.3l-.4-.5zm0 .6-.3.4.2-.4zm0-.5.4.3v.3l-.4-.5z"/>
|
||||||
|
<path id="rs-c" fill="#edb92e" stroke="#21231e" d="M387.8 701.2c8 6.3 15.2-8.3 8-27.6-5-14.2-30.6-8.7-16.4 10.5.7 1 1.6.6 1.5-.4-1-4 1.6-6.3 5.5-5.5 10.5 2 11 22-.6 17.6-2.2-.2-.4 4 1.7 5.6z"/>
|
||||||
|
<path fill="#edb92e" stroke="#21231e" d="M400 639.2c-1 3.4-5 7.6-8 10.8-4.5 4.8-3.4 12.2-.2 16 3.5 4.4 5 9.2 5.8 13 .5 2.3 1.5 7.4.8 15.5-.4 4-2.7 6-5 7.7q-1.5 1.5 4.3 6c.4.2 1.5 1 2 3 .2.4 2 .4 2.2 0 .4-2 1.6-2.8 2-3 3.7-3 5.4-5 4.2-6-2.2-1.7-4.6-3.8-5-7.7-.6-8 .3-13.2.8-15.5.8-3.8 2.3-8.6 6-13 3-3.8 4.2-11.2-.5-16-3-3.2-7-7.4-8-10.7q0-1.1-.7-1-.6-.1-.7 1z"/>
|
||||||
|
<path fill="#edb92e" d="M395 685.5h11.6c2 0 3.6 1.6 3.6 3.5 0 2-1.6 3.5-3.6 3.5H395c-2 0-3.5-1.5-3.5-3.5s1.6-3.5 3.6-3.5z"/>
|
||||||
|
<path fill="#21231e" d="M406.6 686H395v-1h11.6zm4 3h-1v-1l-.2-.2v-.2l-.2-.2v-.2h-.2v-.2h-.2v-.2h-.3v-.2h-.2v-.2h-.3l-.2-.2h-.5v-.2h-.6v-1h1l.2.2h.4v.2h.3l.2.2h.2v.2h.2v.2l.3.2.2.2v.2l.3.2.2.3v.3l.2.3v.4l.2.2zm-1 0h1zm-3 4v-1h.8l.3-.2h.2l.2-.2.2-.2h.3v-.2l.2-.2v-.2l.2-.2.2-.3v-.3l.2-.2v-.8h1v1.2l-.2.2v.4h-.2v.3l-.2.3h-.2v.3h-.2v.3h-.3l-.3.2v.2h-.3l-.2.2h-.2l-.3.2h-.4l-.2.2h-.8zm-11.5-1h11.6v1H395v-1zm-4-3h1v1l.2.2v.3h.2v.2h.2v.2h.2v.2h.2v.2h.2l.3.2h.2l.2.2.3.2h.6v1h-1.2v-.2h-.3l-.2-.2h-.2l-.2-.2-.2-.2-.3-.2-.3-.3-.2-.3-.2-.3-.2-.2v-.2l-.2-.2v-.4l-.2-.2zm1 0h-1zm3-4v1H394v.2h-.3l-.2.2h-.2v.2h-.2v.2l-.2.2-.2.2-.2.2v.2l-.2.2v.5h-.2v.6h-1v-1.2l.2-.2v-.2h.2v-.3h.2v-.2l.2-.2v-.2h.2l.2-.2v-.2h.3v-.2h.3v-.2h.3l.3-.2h.2l.2-.2h.5l.2-.2h.7z"/>
|
||||||
|
<path fill="#fff" d="m417 579.2.5-11c-4.4-2.2-15.2 0-19.7 10.2a65 65 0 0 1-18 25.2c10 1.8 18.4-.2 23.7-7.4 7.3-9.8 11.2-15.7 13.6-17z"/>
|
||||||
|
<path fill="#21231e" d="m418 568.3-.4 11h-1l.4-11 .7-.5zm-.3-.5.3.2v.3zm-19.5 10.8-1-.4.6-1 .5-.8.5-1 .6-.7.5-.8.5-.8.7-.7.6-.6.7-.7.7-.5.7-.6.6-.4.7-.5.8-.4.8-.4.7-.3.8-.3.7-.2.7-.3h.7l.8-.3h1.2l.7-.2h2l.7.2h.5l.6.2.5.2.4.2-.4 1-.4-.3h-.5l-.5-.2h-.5l-.5-.2h-3l-.7.2h-.6l-.7.3-.7.2-.7.2-.7.2-.8.3-.7.3-.7.4-.7.4-.5.5-.7.5-.7.6-.5.6-.6.6-.7.7-.6.6-.5.7-.6.8-.5 1-.3.7zM380 603l-.2 1-.2-.8.6-.6.6-.5.7-.5.6-.6.7-.6.6-.6.5-.6.6-.6.6-.7.6-.7.5-.6.7-.7.5-.7.6-.7.5-.7.6-.8.4-.8.5-.7.5-.8.5-1 .5-.8.6-.8.6-1 .5-.8.6-1 .6-1 .5-1 .5-1 .5-1 .6-1 .5-1.2.5-1.2 1 .4-.6 1.2-.5 1-.6 1.2-.5 1-.4 1-.6 1-.5 1-.5 1-.6 1-.5 1-.6.8-.6.8-.5.8-.5.8-.6.8-.5.8-.6.7-.4.7-.6.8-.6.6-.5.7-.7.7-.6.6-.6.7-.4.6-.6.5-.6.6-.7.7-.6.5-.5.6-.6.6-.8.5-.2-1zm-.2 1h-1l.8-.8zm23.4-8 .8.5-.6.7-.5.6-.7.6-.6.6-.6.6-.5.5-.6.6-.7.5-.7.5-.7.4-.8.3-.7.4-.7.3-.7.3-.7.2-.7.3-.8.2-.8.2h-.8l-.8.2h-.8l-1 .2h-6.1l-1-.2-.8-.2-1-.2.2-1 1 .2.8.2h1l.8.2h6.6l.8-.3h.8l.7-.3.8-.2.7-.2.7-.3.7-.2.7-.2.8-.4.6-.3.7-.3.7-.4.6-.5.6-.5.5-.5.5-.5.6-.5.5-.6.7-.6.5-.7zm13.4-16.8h1l-.2.4-.2.2h-.2l-.3.3-.2.3-.3.2-.2.3-.3.3-.3.4-.3.3-.2.4-.4.5-.3.4-.3.3-.4.5-.3.5-.4.5-.5.6-.3.5-.4.5-.5.7-.5.6-.5.7-.5.7-.5.7-.5.6-.6.8-.6.8-.5.8-.6 1-.7.7-.7 1-.6.8-.8-.6.6-1 .7-1 .6-.7.7-1 .6-.7.6-.8.5-.7.5-.7.5-.7.5-.7.5-.8.5-.6.5-.6.4-.6.4-.6.4-.6.4-.5.4-.5.3-.5.3-.4.4-.5.3-.4.3-.4.3-.3.4-.3.3-.3.2-.2.3-.3.3-.2.4-.2h.2l.3-.2zm1 0v.3h-.2z"/>
|
||||||
|
<path fill="#fff" d="m425.2 580.6-11.8-5q-7.5 14.9-9.5 20.2c-2.8 7-3.5 13.8-2.3 19.5a22 22 0 0 0 14-11.6c3.8-7 6-15.3 9.5-23z"/>
|
||||||
|
<path fill="#21231e" d="m413.6 575.2 11.7 5-.3 1-11.8-5-.3-.8.6-.2zm-.7.2v-.4l.6.2zm-8.7 20.6-1-.4.2-.3.2-.4.2-.5v-.4l.2-.3.2-.5.2-.4.2-.5.2-.5.3-.5.2-.5.3-.6.2-.5.3-.6.3-.5.3-.7.2-.6.3-.6.3-.7.4-.8.3-.7.4-.8.2-.7.4-.8.4-.8.4-.8.4-.8.4-.8.4-1 .4-.8.5-.8.4-1 .8.5-.4.8-.5 1-.5.8-.4.8-.3 1-.4.7-.4 1-.5.7-.4.7-.3.8-.3.7-.4.6-.3.7-.3.7-.3.6-.2.7-.3.6-.3.5-.3.6-.3.6-.3.6-.2.5-.3.5-.3.5-.2.5-.2.4-.2.5v.5l-.2.4-.2.3v.3l-.3.4zm-2.7 18.8.2 1-.6-.4v-.5l-.2-.7v-.6l-.2-.5v-1.7l-.2-.5v-3.7l.2-.6v-1.2l.2-.7v-.6l.2-.5v-.7l.2-.6.2-.7v-.6l.3-.8v-.6l.3-.7.2-.7.2-.7.2-.7.2-.7.2-.7 1 .4-.3.6v.7l-.4.6-.2.6-.2.6-.2.7v.5l-.3.6v.8l-.3.6v.6l-.2.7v.6l-.2.6v.5l-.2.6v1.9l-.2.5v3.5l.2.5v1.2l.2.5v.7l.2.5-.6-.4zm.2 1h-.5v-.4zm13.5-12.4 1 .5-.5.6-.3.6-.4.6-.3.6-.4.6-.4.5-.5.6-.5.6-.4.5-.4.6-.5.4-.4.5-.5.4-.5.3-.4.4-.6.4-.5.4-.5.4-.5.3-.5.4-.5.3-.4.2-.5.2-.4.2-.5.2-.4.3h-.4l-.5.2-.5.2h-.5l-.4.3h-.5l-.2-1h.9l.4-.3h.4l.4-.3h.5l.4-.3.5-.2.4-.2.5-.2.5-.2.5-.3.5-.3.4-.3.4-.4.4-.3.5-.4.4-.4.4-.4.6-.4.4-.5.4-.4.5-.6.3-.4.4-.5.4-.6.5-.6.4-.5.2-.7.4-.6.4-.6.3-.6zM425 581l.3-.8.3.6-.3.7-.3.8-.3.7-.3.7-.3.8-.2.7-.3.7-.2.7-.3.7-.3.8-.3.7-.2.6-.3.8-.4.7-.3 1-.2.6-.2.7-.3.8-.3.8-.2.7-.3.7-.3.8-.3.7-.2.7-.3.7-.3.8-.3.7-.5.7-.3.7-.3.6-.5.6-.3.7-1-.6.5-.6.3-.7.3-.6.3-.6.4-.7.3-.6.3-.7.3-.7.2-.7.3-.7.2-.7.3-1 .3-.6.3-.7.3-.7v-.8l.4-.7.3-.7.3-.8.2-.7.3-.7.3-.8.3-.7.3-.7.3-.7.3-.7.2-1 .3-.6.3-.8.3-.7.4-.8.3-.8zm.3-.8.5.2-.2.4z"/>
|
||||||
|
<path fill="#fff" d="m428.6 580.6-12.7-2q-3.7 16.1-4.4 22-1.4 11.3 2.7 19.4c4.3-2.3 8.8-7 10.7-14.7 2-7.8 2.2-16.3 3.6-24.7z"/>
|
||||||
|
<path fill="#21231e" d="m416 578.2 12.6 2v1l-12.8-2-.4-.6zm-.6.4v-.5l.6.2-.6.4zm-3.3 22h-1l.2-.5V599l.2-.6v-.5l.2-.6v-.5l.2-.6v-.6l.2-.5v-.6l.2-.6v-.6l.2-.6.2-.7v-.6l.3-.7v-.7l.2-.8v-.8l.3-.8.2-.8v-.8l.3-1 .2-.7.2-1 .2-.8.2-1 .2-1 .2-1 .2-1 1 .3-.2 1-.2 1-.2 1-.2 1-.2.8-.2 1-.2.7-.2 1-.2.7v.8l-.3.8-.2.8v.8l-.2.7v.6l-.2.7-.2.7v.6l-.2.6-.2.7v.4l-.2.6v.6l-.2.5v1.1l-.2.6v.4l-.2.4v1.2zm2 19 .5.8-.7-.2-.2-.5-.3-.5-.2-.5v-.6l-.3-.4-.2-.5-.2-.5-.2-.6v-.6l-.2-.6v-.6l-.3-.6v-.6l-.2-.6v-.6l-.2-.6v-2.8l-.2-.6v-5.4l.2-.7v-1.5h1v3.1l-.2.7v5.1l.2.6v1.2l.2.6v.6l.2.6v.6l.3.6v.5l.2.6v.7l.3.5v.5l.3.6.2.5.2.5.2.5.3.5.2.5-.7-.2zm.5.8-.5.3-.2-.5zm10-15.2 1 .2-.2.7-.2.8v.7l-.3.7-.3.7-.2.5-.3.6-.4.7-.3.6-.3.6-.3.4-.3.6-.4.5-.3.5-.4.5-.4.5-.4.4-.4.5-.4.3-.4.4-.4.4-.4.5-.4.3-.4.4-.4.3-.4.3-.4.3-.4.4-.5.3-.4.3-.4.2-.5.2-.5-.8.4-.2.4-.3h.4l.4-.4.4-.3.4-.3.4-.3.4-.3.4-.3.4-.3.3-.3.4-.4.4-.4.5-.4.3-.4.4-.5.3-.4.4-.6.2-.5.3-.6.3-.5.3-.6.4-.6.3-.5.3-.6.2-.6.3-.5.2-.6.3-.8.2-.7.2-.6zm4-25v1l.5-.5v.8l-.2.8v.7l-.2.8v.8l-.2.8v.8l-.2.8v.7l-.2.8v.8l-.2.8v1.7l-.2.7v.8l-.2.8v.8l-.2.8v.7l-.2.8v1l-.2.6v.8l-.2.8-.2.7v.7l-.2.7-.2.8v.7l-.3.7-.2.7-1-.2.2-.8.2-.7v-.7l.2-.8v-.7l.3-.7v-.8l.2-.8v-.7l.2-.8v-1.5l.2-.8v-.7l.2-.8v-1l.2-.7v-.7l.2-.8v-1.6l.2-.8v-.8l.2-.7v-1l.2-.7v-.8l.2-.8v-.8l.2-1v-.7l.3-.8.5-.4zm-.4.3v-.4h.5l-.4.5zm.5.6v-1z"/>
|
||||||
|
<path fill="#fff" d="M430.4 563.4h7.6c4.4 2 9 12.2 3.7 22a66 66 0 0 0-9.5 29.2c-7.4-6.8-10.8-14.7-8.2-23.3 3.4-11.7 6.8-25.2 6.4-28z"/>
|
||||||
|
<path fill="#21231e" d="M438 564h-7.6v-1h7.8zm0-1h.2zm4 22.5-.8-.5.5-1 .4-.8.4-.8.3-1 .3-.8.2-1v-.7l.3-1V574l-.2-1v-.6l-.3-.8-.2-.7-.3-.8-.2-.7-.3-.6-.3-.7-.3-.6-.3-.6-.3-.4-.3-.5-.4-.3-.4-.4-.3-.4-.4-.4-.5-.3-.4-.3-.3-.2.5-1 .4.4.5.2.5.4.4.4.4.4.4.4.4.5.4.6.3.6.3.6.3.6.3.7.2.7.3.7.2.6.2.8v.7l.3 1v.8l.2.8v3.5l-.2 1v1l-.4.8-.3 1-.3 1-.4.8-.4 1-.5 1zM432 615l.5-.7-.8.3v-.8l.2-1v-2.3l.3-1v-.7l.2-.8.2-1 .2-.8v-1l.2-.7.3-1 .2-.8.2-1 .2-.7.3-1 .3-.8.3-1 .4-1 .4-.8.4-1 .4-1 .4-1 .4-1 .5-1 .4-1 .5-1 .5-1 .5-1.2.6-1 .6-1.2 1 .5-.7 1-.6 1.2-.7 1-.5 1-.5 1.2-.5 1-.4 1-.5 1-.5.8-.4 1-.3 1-.3 1-.4.8-.3 1-.3.8-.2.8-.3 1-.4.8-.2.8-.2 1-.2.7-.2 1v.7l-.3.8v1l-.3.7v.8l-.2.8v1l-.2.7v1.8l-1 .2zm.7-.3v1l-.8-.7.7-.3zm-9.2-23.6 1 .4-.3.8-.2.8v.8l-.2.7v.8l-.2.8v3l.2.8.2.8v.7l.3.7.3.7.2.8.3.7.4.7.4.6.3.7.5.7.4.7.4.7.5.6.4.7.5.7.4.6.6.7.6.6.6.7.7.6.5.7-.6.7-.8-.6-.7-.7-.6-.7-.7-.6-.6-.7-.6-.7-.4-.7-.6-.7-.4-.7-.5-.8-.3-.7-.5-.7-.3-.8-.5-.7-.3-.7-.3-.8-.4-.7-.2-.7-.2-.8-.2-.7v-.8l-.2-.8v-.8l-.2-.8v-2.5l.2-.8v-.8l.3-.8.3-1 .2-.7zm7-28v1l.4-.7v1.8l-.2.5v1.1l-.2.7-.2.7v.8l-.3.8v.8l-.2 1-.2.8-.2 1-.3 1v1l-.4 1-.2 1-.3 1-.2 1-.2 1-.3 1.2-.3 1-.3 1.2-.3 1-.3 1.2-.3 1.2-.3 1-.3 1.2-.2 1-.4 1.2-1-.3.4-1 .3-1 .5-1.2.3-1 .3-1.2.4-1.2.3-1 .3-1.2.3-1 .3-1.2.3-1 .2-1 .2-1 .3-1.2.2-1 .2-1 .2-1 .2-.8.2-1 .2-.8.2-.8v-.8l.3-.7v-.8l.2-.7v-.6l.2-.6v-2l.4-.6zm-.6.5v-.6h.4l-.5.5zm.4 0v-.6.4zm-.5 0v-.6h.4l-.5.5z"/>
|
||||||
|
<path fill="#fff" d="m433.8 558.2-12-4.3q-6.5 15-8.4 20.5-3.4 11-1 19.6a21 21 0 0 0 14-11.6c3.3-7.3 4.5-16.3 7.4-24.2"/>
|
||||||
|
<path fill="#21231e" d="m422 553.4 12 4.3-.4 1-12-4.4-.3-.6zm-.7.3.2-.4h.4l-.7.4zm-7.4 21-1-.3v-.4l.2-.4v-.4l.3-.4v-.4l.3-.5.2-.5v-.5l.3-.5.2-.5.2-.6.3-.6.2-.5.2-.6.3-.6.2-.6.2-.6.3-.7.3-.6.3-.7.4-.7.3-.8.3-.8.3-.7.4-.8.3-1 .4-.8.3-1 .4-.8.5-1 .4-.8.3-1 1 .5-.5 1-.4 1-.4.8-.3 1-.4.8-.4.8-.4.8-.3.8-.4.8-.4.8-.3.7-.3.8-.3.8-.3.7-.3.6v.8l-.4.6-.2.6-.3.6v.6l-.3.6-.2.5-.2.5-.2.5v.5l-.3.5-.2.4v.5l-.3.4v.3l-.2.3v.4zm-1.8 19 .3 1-.6-.4-.3-.6v-.5l-.2-.6v-.5l-.2-.5v-.6l-.2-.7V583l.2-.7V581l.3-.6v-.7l.2-.6v-.6l.3-.6.2-.7v-.6l.3-.7.2-.6.2-.6 1 .3-.3.6-.2.7-.2.7-.2.6v.7l-.2.6v.7l-.2.6-.2.6v.6l-.2.6v1l-.2.7v6.5l.2.6v1.1l.2.4.2.6v.5l.2.5zm.3 1h-.5v-.4l.5.3zM426 582l.8.6-.3.6-.4.7-.2.5-.4.6-.4.7-.4.6-.4.5-.4.5-.4.5-.5.5-.5.5-.5.4-.4.4-.5.4-.4.4-.4.4-.5.3-.5.5-.5.3-.5.3-.5.4-.4.2-.5.3h-.5l-.5.4-.5.2-.5.2-.5.2h-.4l-.6.3-.4.2h-.5l-.3-.8.4-.2h.5l.5-.3.5-.2h.4l.5-.3.4-.2.4-.3.5-.2.5-.2.5-.3.4-.3.5-.3.5-.4.4-.3.6-.3.4-.4.5-.3.4-.4.4-.5.4-.4.4-.4.4-.4.4-.5.4-.5.4-.5.4-.5.4-.6.3-.6.3-.6.3-.6.3-.7zm7.6-23.4.4-1 .3.7-.3.8-.3.8-.2.8-.3.7-.2.8-.2 1-.2.6-.2.8-.3.8v.8l-.3.7-.2.8-.2.8-.2.8v1l-.3.6-.2.8-.2.8-.2.8v.7l-.3.8-.2.8-.3.7-.2.8-.2.7-.3.8-.3.7-.2.7-.3.8-.3.7-.3.7-.2.6-1-.5.4-.5.3-.7.3-.7.3-.6.3-.7.3-.8.2-.8.2-.7.3-.8.2-.7.2-.8.2-.7v-.8l.4-.8.2-.8.2-.8.2-.8.2-.8.2-.7.2-.7.2-.8.2-.8.2-.8.2-1 .2-.6.2-.8.3-.8.3-.8.2-.7.3-.8.3-.8.3-.7.3.6zm.4-1 .4.3v.3zm-.2.6-.2.4zm.2-.5.4.2v.3l-.4-.6z"/>
|
||||||
|
<path fill="#fff" d="m425.6 549.7-2.6-16.4c-2.8-.7-6.5 5.8-10 9.5q-1.6 1.3-2.6 2.8a23 23 0 0 0-3.4 8 62 62 0 0 1-13 27.6c10.2-.2 18-3.8 21.8-12 5-11 7.7-17.7 9.8-19.5"/>
|
||||||
|
<path fill="#21231e" d="m423.4 533.3 2.7 16.4h-1l-2.5-16.3.6-.6.4.5zm-.3-.4h.4v.2l-.3-.4zm-9.7 10.2-.8-.7.4-.4.2-.2.3-.4.4-.4.2-.4.3-.4.4-.4.2-.5.3-.4.4-.5.2-.4.3-.3.4-.5.2-.4.3-.4.4-.3.2-.3.3-.4.3-.3.4-.3.3-.4.3-.2.3-.2.4-.2.4-.3h.3l.4-.3h.3l.4-.2h1.1l-.2 1h-1l-.3.2-.2.2-.3.2-.3.2-.3.2-.3.3v.4l-.5.3-.3.3-.3.3-.4.4-.3.4-.3.5-.4.4-.3.3-.3.4-.3.4-.4.5-.3.5-.3.4-.4.4-.3.4-.4.4-.3.4-.4.4-.4.4-.3.4zm-2.5 2.7-.8-.7v-.2h.2l.2-.2v-.2h.2v-.2h.2v-.2h.2v-.2h.2v-.2l.2-.2h.2v-.2l.2-.2h.2v-.2h.2v-.2h.2v-.2l1 .6h-.2l-.2.2v.2h-.2v.2h-.2l-.2.2v.2h-.2l-.2.2v.2h-.2v.2h-.2v.2h-.2v.2h-.2v.2l-.2.2v.2zm-3.3 7.7-1-.2v-.6l.2-.4v-.3l.2-.3v-.8l.2-.3v-.3l.2-.3v-.3h.2v-.4l.2-.3v-.2l.2-.2v-.2l.3-.3v-.2l.2-.2v-.2l.3-.2v-.3h.2v-.3l.3-.3v-.2l.3-.2v-.2l.2-.3.2-.2.8.6-.3.3v.2l-.2.2-.2.2v.4l-.2.2-.2.2v.3l-.2.3v.2l-.2.3-.2.2v.2l-.2.3v.2l-.2.3v.2l-.2.3v.2l-.2.3v.5l-.2.3v.2l-.2.3v.5l-.2.3v.7zm-13.4 27v1l-.3-.8.5-.8.5-.7.5-.6.5-.8.5-.7.5-.7.4-.6.6-.7.4-.7.5-.8.4-.7.4-.7.4-.8.4-.7.4-.7.4-.8.4-.8.4-.8.4-1 .4-1 .3-.8.3-1 .3-.8.4-1 .3-1 .4-1 .3-1 .3-1.2.3-1 .3-1.3.2-1.3.3-1.2 1 .2-.3 1.2-.3 1-.4 1.3-.3 1.2-.4 1-.4 1-.3 1.2-.4 1-.4 1-.4 1-.3 1-.5.8-.4 1-.3.8-.4.8-.4 1-.5.8-.4.8-.3.7-.5.8-.4.8-.5.8-.4.7-.6.7-.5.7-.5.7-.5.7-.5.8-.5.7-.5.6-.5.8-.5.7-.4-.8zm0 1h-1l.7-.8zm21.3-12.8 1 .4-.5.7-.4 1-.4.6-.4.7-.5.7-.4.6-.5.6-.5.6-.5.6-.6.5-.6.4-.6.5-.7.4-.6.4-.8.4-.7.4-.7.4-.7.3-.7.3-.8.3-.8.2-.8.2-1 .2-.7.2-1 .2-.7.2h-1l-.8.2h-1l-.8.2h-2v-1h2.8l1-.2h.8l.8-.2 1-.2h.7l1-.3.7-.2.7-.3.8-.2.7-.3.7-.3.7-.3.6-.3.7-.3.6-.4.7-.5.6-.4.6-.4.5-.4.6-.6.5-.5.5-.4.5-.6.5-.6.4-.7.5-.6.4-.6.3-.7.3-.8zm9.8-19.2h1v.3l-.3.3v.2l-.3.2-.2.2-.2.2-.2.3-.3.4-.2.2-.2.4v.4l-.4.5-.2.5-.3.5-.2.5-.3.6-.2.6-.3.6-.3.6-.4.7-.3.8-.3.8-.4.7-.3.8-.4 1-.4.7-.4 1-.4.8-.4 1-.4 1-.5.8-.3 1-.5 1-1-.4.6-1 .4-1 .5-1 .4-1 .5-1 .4-.8.3-1 .4-.7.4-.8.4-.8.4-1 .3-.6.4-.7.4-.7.3-.6.4-.7.3-.6.2-.4.2-.6.3-.5.2-.5.3-.4.2-.5.3-.3.2-.4.2-.3.2-.4.2-.3.2-.3.2-.2.3-.2.3-.2-.2.4zm1 0v.3z"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#rs-d" width="1350" height="900" transform="matrix(-1 0 0 1 964.3 0)"/>
|
||||||
|
<path fill="#fff" stroke="#21231e" d="m536.4 415 4.8-1.5a66 66 0 0 1-21.4-22c-7.8-12 3.3-33 8-44.3a53 53 0 0 0 21.2-31c1-7.3-19-4.7-33 2-17 8-19.3 5.7-25.4 7-2.2 10.5 2.4 11.4 6.7 11-9.4 15-13.3 29.8-15 51.4-2-21.6-6-36.5-15.3-51.5 4.3.6 9-.3 6.7-10.7-6-1.4-8.4.8-25.4-7.2-14-6.5-34-9-33-1.8 3 13.7 11.8 24 21.3 31 4.6 11.2 15.7 32.4 8 44.2a67 67 0 0 1-21.5 22q2.5 1 5 1.5c-1.2 1.5-11 4.5-12.3 3.7-7.6-5.3-15-18-21.4-25.2-18.2-20.8-27.3-36.7-24-49.6 4.5-14 20.8-29.6-.8-27-21.7 2.8-39.6 32.5-45.4 68.3l-4.7 33.5c11.8-4.3 17-8.5 20.2-13.7q-2.1 7.5-3.2 14.2c9.7-1.7 16.7-6.4 19.2-15q1.6-5.3 3.6-9c3 1 7 1 11.6 0 1.7 4 15 19 22.2 30 8 12.2 20.2 21.6 24.2 22 2 .4 2.7-4 2.2-10.2 21 .2 42.3.3 63-.4q31.1.9 63 .4c-.6 6 0 10.6 2 10.3 4-.5 16.2-10 24.3-22.2 7-10.8 20.5-25.7 22.2-30a20 20 0 0 0 11.6.2q2 3.7 3.5 9c2.5 8.6 9.5 13.3 19.2 15q-1-6.6-3.2-14c3.2 5 8.4 9.3 20.2 13.6-1.4-9-2.7-20.3-4.8-33.5-5.8-35.8-23.7-65.5-45.4-68.2-21.6-2.6-5.3 13-.7 27 3.3 12.8-5.8 28.7-24 49.5-6.5 7.3-13.8 20-21.4 25.2-1.2.8-11-2.2-12.2-3.8z"/>
|
||||||
|
<g id="rs-e">
|
||||||
|
<path fill="#edb92e" d="M440.6 356.8c-2 1.8-8.8 1.8-18 2.4q-22.3.7-25.3-13.5c5.6 6.6 12.2 10 19.7 8 10.2-2.5 16-4.3 18.3-3.8 1.6.4 4 3.3 5.3 6.8z"/>
|
||||||
|
<path fill="#21231e" d="M422.6 359.7v-1h1.8l.7-.2h2.5l.8-.2h2.8l.8-.2h3l.5-.2h1l.4-.2h.5l.4-.2h.4l.5-.2h.3l.3-.2h.4l.3-.2h.2v-.3h.3l.6.7h-.3l-.3.3-.3.2h-.2l-.3.3h-.4l-.3.2h-.4l-.4.2h-.5l-.5.2h-1l-.5.2h-1.5l-.6.2H432l-.6.2h-2.8l-.8.2h-2.3l-1 .2H423zm-25-14.3-.7.6.8-.4.2 1 .2.7.3.8.3 1 .3.6.5.7.4.7.4.6.5.6.4.6.6.6.6.5.6.4.7.5.6.3.8.5.8.4.8.3 1 .4.8.2 1 .3 1 .2 1 .2h1l1 .3h1.2l1.2.3h6.6v1H416l-1-.2h-1.3l-1-.3h-1.2l-1-.3-1-.3-1-.3-1-.2-1-.4-.8-.3-.8-.4-1-.4-.7-.5-.7-.5-.7-.5-.7-.6-.6-.5-.6-.6-.4-.8-.5-.7-.3-.7-.4-.8-.4-.7-.4-1-.3-.7-.3-1-.2-.8 1-.4zm19.3 8 .2 1h-.8l-.7.2h-1.4l-.7.2h-2.8l-.6-.2-.7-.2h-.7l-.6-.3h-.7l-.6-.3-.8-.3-.6-.3-.6-.2-.6-.3-.6-.4-.6-.3-.6-.4-.6-.4-.6-.6-.6-.4-.6-.5-.5-.6-.6-.6-.4-.6-.6-.5-.5-.6-.6-.7.7-.6.5.6.5.6.6.6.5.5.5.5.6.5.4.5.6.5.6.4.5.5.7.4.6.3.6.3.4.3.6.2.6.3.6.2.7.2h.5l.6.3.7.2h.6l.7.2h4.7l.6-.3h.7zm18.5-4-.3 1h-2.6l-.4.2h-.9l-.5.2-.5.2h-.5l-.6.2h-.5l-.6.3h-.5l-.7.3-.7.2h-.6l-.7.3-.7.2-.8.2-.8.2h-.8l-.8.3-1 .3-.8.2-1 .2-.8.3-.3-1 1-.2.8-.2 1-.2.7-.2 1-.2.7-.2.8-.2.7-.2.8-.2.7-.2.7-.2.6-.2h.6l.6-.3h.6l.6-.3h.6l.5-.2h.6l.5-.2h.6l.4-.2h.9l.3-.2zm5.4 7.8-.7-.8-.2.6v-.7l-.3-.3v-.3l-.3-.3v-.3l-.3-.2v-.3l-.2-.2-.2-.3v-.3l-.3-.2-.2-.3v-.2l-.2-.2-.2-.3v-.2l-.3-.2h-.2v-.3h-.2l-.2-.3v-.2h-.3l-.2-.2v-.2h-.2v-.2h-.4v-.2h-.2l.3-1h.2l.2.2.2.2h.2l.2.2.2.2h.2v.3l.3.2.2.2h.2l.2.3v.2l.2.2.2.2.2.2.2.2.2.3v.2l.3.2.2.3.2.3v.3l.3.4.2.3v.3l.3.3v.3l.3.3v.3l.2.3v.6zm0-.6.2.3-.3.2v-.6zm-21.5-35.4v1.5h-.4v-.4h-.2v-.7l.2-.2h.2v-.2zm15.3 4-.5 1.3-.6-.2-.5-.2h-.6l-.5-.2-.4-.2-.5-.2-.4-.2-.5-.2-.4-.2h-.5l-.4-.3-.5-.2h-.4l-.4-.3h-.5l-.4-.3h-.5l-.4-.2h-.4l-.5-.2h-.4l-.3-.2h-1.3l-.4-.2H420v-2h2.9l.5.2h1l.4.2h.5l.3.2.5.2h.5l.4.2.5.2h.5l.4.3h.6l.5.3.6.2h.5l.6.3.5.2.6.2h.5l.6.3.6.2.5.2.6.2h.5zm9 2.5v1.5H442l-.3-.2h-.4l-.3-.2h-.3l-.4-.2h-.3l-.4-.2h-.4l-.3-.2h-.5l-.3-.2-.4-.2h-.3l-.4-.2h-.2l-.3-.2h-.3l-.3-.3h-.7l-.2-.2h-.4l-.2-.2.4-1.5h.2l.2.2h.2l.2.2h.3v.2h.4l.3.2h.3l.3.2h.4l.3.2.3.2h.4l.3.2h.4l.3.2h.4l.4.2h.3l.4.2h.4l.3.2h.6l.3.2zm0 1.5v-1.5h.4v.2h.2v.2h.2v.9h-.2v.2h-.2v.2h-.2zm23.4 7.5.2-1.2h.2v.2h.2v.8h-.6zm-10-2 .4-1.2h.5l.3.2h.4l.3.2h1.1l.2.2h.6l.2.2h.6l.3.2h.7l.4.2h.8l.3.2h.9l.2.2h.9l.5.2h.8l-.2 1.3h-.9l-.4-.2h-.6l-.4-.2h-.7l-.4-.2h-1.4l-.3-.2h-.7l-.3-.2h-.6l-.3-.2h-.5l-.4-.2h-.5l-.3-.2h-.5l-.2-.2h-.5l-.2-.2zm.4-1.2-.3 1h-.2v-.8h.8z"/>
|
||||||
|
<path fill="#21231e" d="m431.5 332-.7 1.3-.4-.3-.4-.2-.4-.3-.3-.2-.4-.3-.4-.3-.3-.3-.4-.3-.4-.2-.3-.4-.3-.3-.2-.3-.3-.3-.3-.4-.2-.3-.3-.4-.2-.3-.3-.4-.2-.4-.2-.3-.3-.5-.2-.4-.2-.2v-.4l-.3-.4-.2-.3-.2-.4-.2-.3v-.4l-.2-.3-.2-.3-.2-.4 1.4-.7.2.2.2.4v.3l.3.2.2.4.2.3v.3l.3.3.2.4.2.3.2.4v.3l.3.4.3.3.2.4.2.4.2.4.3.3.2.4.3.4.2.3.3.4.3.3.3.3.3.2.3.3.3.3.2.2.4.3.3.2.2.2.3.2zm7.6-5.3 1.5.8-.2.4-.2.3-.2.4-.2.4-.3.5-.3.4-.2.2-.3.4-.3.3-.3.2-.3.3-.3.3-.3.3-.2.4-.3.2-.3.2-.4.2-.3.2-.3.2h-.3l-.5.2h-.3l-.3.2h-2.2l-.4-.2-.3-.2h-.2l.7-1.5h.5l.2.2h1.7l.3-.2h.2l.3-.2h.2l.3-.3.2-.2.3-.2h.2l.3-.3.2-.3.3-.2.2-.3.3-.4.2-.3.3-.3.2-.4.2-.3.3-.4.2-.5.2-.4v-.3z"/>
|
||||||
|
<path fill="#21231e" d="m429.6 331-2.2-2.3q-1.6-1.9 2-5.4l4.2 2q-1.4 4.5-4 5.6zm-6.3 82-.4 1h-.4v-1zm23.8-3.6 1 1-.7.5-.6.4-.7.5-.6.4-.7.5-.7.3-.7.4-.7.4-.7.4-1 .3-.6.3-.8.2-.7.2-1 .2-.7.2h-.8l-.8.2h-5l-1-.2h-.8l-.8-.3h-1l-.8-.4h-1l-.7-.3-1-.3-.8-.3.3-1 .8.2 1 .3 1 .3.7.2 1 .2.7.2h.8l1 .2h.7l.7.2h4.8l.7-.2h1l.6-.3.7-.2h.7l.7-.4.7-.3.7-.3.8-.3.6-.4.7-.4.7-.5.6-.5.6-.5.5-.5zm3-2.8 1 .8h-.2v.2h-.2l-.2.2v.2h-.2v.2h-.2v.2h-.2l-.2.2-.2.2v.2h-.2v.2h-.2l-.2.2-.2.2-.2.2-.2.2v.2h-.2l-1-.8h.2v-.2h.2v-.2h.2l.2-.2.2-.2.2-.2v-.2h.2v-.2h.2l.2-.2.2-.2.2-.2v-.2h.2l.2-.2.2-.2v-.2zm1 .8-1-.8.2-.2h.7v.2h.2z"/>
|
||||||
|
<path fill="#21231e" d="m452.2 401.3-1.2-.2v-.2h.2l.2-.2h.6v.4h.2zm-5.5 16 .3 1-.7-.8.2-.4.2-.4.2-.5v-.4l.3-.4.2-.5.2-.5v-.5l.3-.4.2-.5.2-.5v-.5l.3-.5v-.5l.3-.5v-.5l.3-.5v-.5l.3-.5v-.5l.2-.5.2-.6v-.5l.2-.6v-.5l.2-.4v-.6l.2-.6v-.5l.2-.7v-.6l.2-.6 1.2.3v.6l-.2.5v.6l-.2.6v.6l-.2.6v.5l-.2.6-.2.4v.6l-.2.5v.6l-.3.6v.5l-.3.6v.6l-.3.5v.5l-.2.5-.2.5v.5l-.3.5-.2.5v.5l-.2.4-.2.5-.2.6-.2.5-.2.6-.2.5-.2.5-.2.5-.7-.8zm-.4.2 1 .5v.2h-.2v.2h-.4l-.2-.2v-.2h-.2v-.4zm15.2-9.5 1 .7-.3.4-.4.6-.3.4-.4.4-.2.4-.4.4-.4.4-.4.4-.4.3-.4.4-.5.3-.4.4-.6.3-.4.3-.5.3-.5.3-.5.3-.5.2-.5.3-.5.3-.6.2-.5.3-.7.2-.5.3-.6.2-.6.2-.6.2-.6.2-.6.2-.6.2-.6.2-.6.2-.3-1.2.6-.2h.6l.5-.3.6-.2.6-.2.6-.2h.6l.5-.3.6-.3.4-.2.5-.2.4-.3.5-.2.4-.3.5-.3.4-.4.5-.3.3-.3.5-.3.4-.3.4-.3.4-.2.4-.4.4-.3.4-.3.4-.3.3-.4.3-.4.3-.4.3-.4.2-.4.3-.4zm1 .7-1-.7h.2v-.2h.7v.2h.2z"/>
|
||||||
|
<path fill="#21231e" d="M461.3 400.2H460v-.2h.2v-.2h1v.4zm4.4 15.7.7 1-.8-.3-.3-.4-.3-.4-.2-.5-.3-.5-.2-.4-.3-.4-.2-.5-.3-.5-.2-.4-.2-.5v-.5l-.3-.5-.2-.5-.2-.5-.2-.5v-.5l-.2-.5-.2-.5v-.6l-.3-.5v-.5l-.3-.6v-.4l-.2-.6v-.6l-.2-.5v-.7l-.2-.6v-.6l-.2-.5v-1.2h1v.5l.2.6v.6l.2.5v1.4l.2.5.2.6v.5l.2.5v.6l.3.5v.4l.2.5.2.4v.5l.2.4.2.5.2.4.2.5.2.4.2.4.2.5.2.4.2.5.3.4.3.4.2.4.3.5.2.3.3.4.3.3h-1zm0 .7 1-.7v.8l-.2.2h-.2v.2h-.4v-.2h-.2zm7.6-12.6 1 .4v.6l-.2.5v.5l-.2.5-.2.5-.2.5-.2.5-.2.5-.2.4-.2.4-.2.4-.3.5v.4l-.4.4-.2.5-.3.4v.3l-.4.3-.3.4-.3.4-.2.3-.3.3-.3.4-.3.3-.4.3-.3.3-.3.3-.3.2-.3.3-.3.4-.3.2-.3.3-.7-1 .3-.3.3-.2.3-.3.3-.2.2-.3.3-.3.2-.2.3-.3.3-.4.3-.3.2-.4.3-.4.4-.3.2-.4.3-.4v-.4l.4-.3.2-.5.3-.4v-.3l.3-.4.2-.4.2-.6.2-.4v-.5l.3-.4.2-.4.2-.5v-.4l.3-.5v-.4zm1 .4-1-.3v-.2h.8v.2h.2z"/>
|
||||||
|
<path fill="#edb92e" d="M406 317.8c-16.7 1.8-16 12.6-.4 28 3 3 4 3 4.6-.2 1.2-5.3 7-8.5 10.2-4.4 2 2.7 1.6 4.3 8.6 3.3 9 7.5 16.2 12.8 4.5 12.3-5-.2-9 2.8-15.7 2.5 2 4 2.3 4.3 3.4 4.6 6.5 1.2 13.3-2.8 19.8-1.6 14.3 2.6-23-38.6-22.4-40.3 1-4-9-11.5-12.7-4.2z"/>
|
||||||
|
<path fill="#21231e" d="m406 345.5-.7.7-1.4-1.5-1.5-1.4-1.2-1.4-1.2-1.4-1-1.3-1-1.3-.8-1.3-.8-1.2-.8-1.2-.6-1.2-.5-1-.5-1.2-.3-1-.3-1v-1l-.2-1v-1l.2-1 .2-.8.4-.8.4-.8.6-.7.6-.7.8-.6.8-.6 1-.5 1-.5 1-.4 1.4-.4 1.3-.3 1.3-.2 1.6-.2v1l-1.5.2-1.4.2-1.2.3-1.2.3-1 .4-1 .5-1 .4-.6.5-.7.7-.6.6-.4.6-.4.6-.4.7-.2.8-.2 1v1.7l.2.8.2 1 .4 1 .4 1 .5 1 .5 1.3.7 1 .8 1.3 1 1.4.8 1.2 1 1.3 1.2 1.3 1.2 1.3 1.4 1.4 1.4 1.5zm3.8 0 1 .2v.3l-.2.3v.5l-.2.2v.2l-.2.2v.2l-.2.2-.3.3v.2h-.3l-.2.2H408l-.2-.2h-.2l-.2-.2h-.2l-.2-.3-.2-.2h-.2l-.3-.4h-.2l-.2-.3-.3-.3-.2-.2.7-.7.2.2.3.3.2.2.3.2.2.2.2.2h.2l.2.2.2.2h.3v.2h.6l.2-.2v-.2l.2-.2v-.2l.2-.4v-.4l.2-.3zm11-4.6-.8.5-.3-.4h-.3l-.3-.4-.2-.2-.3-.2h-.3l-.3-.2h-.5l-.3-.2h-1.5l-.3.2-.4.2h-.2l-.4.3-.3.2-.2.2-.4.2-.3.2-.2.3-.3.3-.2.4-.3.3-.2.4-.3.5-.2.4-.2.3v.5l-.2.4v.5l-1-.2v-.5l.2-.5.2-.5.2-.5.3-.5.2-.4.2-.4.3-.4.3-.4.3-.4.4-.3.4-.3.4-.3.4-.2.3-.2.3-.2.5-.2h.4l.4-.2h.4l.4-.2h1l.4.2h.6l.4.3.4.3.3.2.4.3.4.5.4.4zm8.6 3.2-.7.7h.4-.5l-.6.2h-4.5l-.2-.2h-.3l-.2-.2h-.2l-.2-.2-.2-.2-.2-.2h-.2v-.3l-.3-.3v-.2l-.3-.2v-.2h-.3l-.2-.3-.2-.3v-.2l-.3-.2v-.3l-.3-.2.8-.6h.2v.4l.3.2.2.3.3.3v.2l.2.2.2.2.2.2.2.2.2.2h.2v.2h.3l.2.2h.2l.2.2h5.4zm-.4 0 .2-.2.2.2zm4.5 13v-1h4.1l.5-.2h.4l.3-.3h.2v-1l-.2-.3-.2-.4-.3-.5-.4-.4-.5-.4-.4-.4-.4-.5-.6-.5-.6-.6-.6-.6-.7-.5-.6-.6-.7-.7-.7-.7-.8-.6-.8-.7-.8-.7-1-.7.8-.8.8.7.8.5.8.7.8.7.7.5.8.6.8.7.7.6.6.6.7.4.6.6.5.5.5.5.4.5.4.4.4.4.3.4.2.4v2.3l-.4.3-.3.2-.4.3-.6.2h-.6l-.7.2h-3.6zm-15.2 2-1 .4.5-.8h3.4l.6-.2h.5l.5-.2h.5l.5-.2h.5l.4-.2h.5l.5-.2.4-.2h.6l.4-.2.5-.2h.4l.4-.2h.5l.3-.2h.5l.4-.2h.5l.5-.2h2.6v1H431l-.4.2h-.9l-.4.3h-.8l-.4.2h-.5l-.4.3h-.6l-.5.2h-.5l-.5.3h-.5l-.5.2h-.5l-.5.2h-.5l-.6.2h-.6l-.6.2h-3l.5-.7zm-1 .4-.3-.8h.8l-.4.8zm4 4-.2.8h-.4v-.2h-.2l-.2-.2h-.2v-.2h-.2v-.2h-.2v-.2l-.2-.3-.2-.2-.3-.3-.2-.2v-.2h-.2v-.4l-.3-.2v-.3h-.3v-.4l-.3-.3v-.4l-.3-.4 1-.5v.5l.2.3.2.3v.4l.3.3.2.2v.2l.3.2v.2l.2.2v.2h.2v.3l.2.2v.2l.2.2h.2v.2h.4v.2h.4zm19.8-1.7v1l-.7-.2h-4.7l-.6.2h-.7l-.6.2h-.7l-.6.2-.6.2h-.6l-.6.2h-.7l-.7.3-.6.2h-.6l-.6.2h-.6l-.6.2h-4.6l-.6-.2.3-1 .6.2h4.8l.6-.2h.6l.5-.2h.6l.7-.2.7-.2h.6l.6-.2.6-.2h.7l.5-.2h.6l.7-.2h.7l.6-.2h4.9zm-23-40 1 .3.3.3.5.7.6.8.8 1.2 1 1.2 1 1.3 1 1.5 1.3 1.5 1.3 1.7 1.4 1.8 1.4 1.8 1.5 1.8 1.5 2 1.5 1.8 1.4 2 1.4 2 1.3 1.7 1.3 2 1.2 1.7 1 1.7 1 1.6.8 1.5.6 1.4.4 1.3.3 1.2v2l-.6.7-.8.5H441v-1h2l.4-.3.3-.4V360l-.3-1.2-.4-1.2-.6-1.3-.8-1.5-1-1.6-1-1.7-1-1.8-1.3-1.8-1.4-2-1.4-2-1.4-1.8-1.5-2-1.4-1.8-1.5-1.8-1.4-1.8-1.3-1.8-1.3-1.6-1.2-1.6-1-1.5-1.2-1.3-1-1.2-.7-1-.6-1-.5-.7-.3-.4v-.6zm-12-3.7v-1l-.5.2.4-.6.3-.7.5-.6.5-.4.5-.3.6-.3.5-.2.5-.2h2.6l.6.2.6.2.5.3.6.2.6.3.4.4.5.2.4.4.4.4.5.5.4.4.3.4.3.4.3.5.2.5.2.5v.5l.2.5v1l-1-.3v-1l-.2-.4v-.4l-.3-.4-.2-.4-.3-.4-.3-.4-.5-.4-.3-.4-.5-.4-.4-.4-.5-.3-.4-.3-.5-.3h-.7l-.5-.3-.5-.2h-.5l-.6-.2h-2l-.4.3-.5.2-.4.3-.5.5-.4.4-.3.4-.3.6-.4.3zm.4-.3-.2.2h-.2zm-.5-.2v.5zm.4.2-.2.2h-.2z"/>
|
||||||
|
<path fill="#21231e" d="m407 317.2-1.3.4v-.6h.2v-.2h.8v.2h.2v.2zm7.6 10.6-.8 1-.4-.4-.3-.4-.3-.3-.3-.3-.4-.3-.3-.2-.3-.4-.3-.3-.2-.2-.3-.4-.3-.3-.3-.2-.4-.4-.3-.3-.2-.2-.2-.4-.2-.3-.3-.3-.2-.3v-.4l-.4-.3-.2-.4-.2-.4-.2-.4-.2-.4v-.4l-.3-.3-.2-.4v-.3l-.3-.3v-.4l-.3-.4 1.2-.4v.7l.3.3v.3l.3.4v.3l.3.4v.3l.3.4.2.3.2.3.2.4v.3l.3.3.2.4.3.3.2.3.3.4.2.3.3.3.3.3.4.4.2.3.3.3.2.3.3.3.4.2.2.3.3.3.4.4.2.3.4.3zm-.8 1 .8-1v.2h.2v.7h-.4v.2zm-9.5-4.6q1.3.1 1.2 1.5c-.1 1.4-.8 1.3-1.5 1.2q-1.3-.4-1.2-1.6c.1-1.2.8-1.3 1.5-1.2zm-45.9 38.4h.2v-.2h.4v.2h.2v.2l.2.2v.4h-.2zm-22.7 56 .2-1V416l.4-1.5.3-1.5.4-1.7.6-1.8.5-2 .7-2 .6-2 .6-2 .6-2.2.8-2.2.7-2.2 1-2 .7-2.3 1-2.3.7-2.2 1-2.2.8-2 1-2.2.8-2 1-2 .8-2 1-1.7.8-1.5 1-1.6.7-1.5 1-1.3.7-1.2.8-1 .8-.8.8-.6.7 1-.6.5-.7.8-.7 1-.8 1-1 1.3-.7 1.5-1 1.5-.7 1.7-1 1.7-.8 2-1 1.8-.8 2-1 2-.8 2.3-1 2-.7 2.3-1 2.3-.7 2.3-.8 2.2-.7 2.2-.8 2.2-.6 2-.6 2-.6 2.2-.6 2-.5 1.8-.4 1.8-.4 1.6-.5 1.6-.2 1.4-.2 1.3v1H336zm5.6 80.5-.7-2-.7-2-.7-2.2-.6-2.2-.6-2.2-.4-2.4-.4-2.3-.5-2.4-.4-2.5-.3-2.4-.3-2.5-.3-2.7-.2-2.6-.3-2.6-.2-2.5-.2-2.6v-3l-.2-2.7v-18.5l.3-2.7v-2.5l.2-2.6v-2.7l.3-2.6.2-2.5.2-2.5h1.2l-.3 2.6-.2 2.5-.2 2.6-.2 2.6v2.6l-.2 2.6v2.6l-.2 2.7v18.8l.2 2.6v2.6l.3 2.6.3 2.6.3 2.6.3 2.5.2 2.5.3 2.5.4 2.4.3 2.5.5 2.3.5 2.3.5 2.3.6 2.2.6 2 .7 2.2.7 2-1 .4zm1-.4v.8h-.8v-.2l1-.6z"/>
|
||||||
|
</g>
|
||||||
|
<use xlink:href="#rs-e" width="1350" height="900" transform="matrix(-1 0 0 1 964.3 0)"/>
|
||||||
|
<path fill="#21231e" d="M473 398.4h-.2v.5h-.2v.8l.2.7v1.6l.2.8.2.8v.7l.3.8v.8l.3.7.2.8.3.7.2.7.2.7.3.7.3.8.3.7.3.7.2.7.3.6.4.6.2.7.4.5.4.7.4.7.4.5.5.6.4.8.5.6.4.6.5.6.6.6.2.2h.8v-.2l.6-.6.5-.6.5-.6.4-.6.5-.7.5-.5.4-.6.3-.8.4-.6.3-.7.4-.8.4-.6.3-.7.4-.8.3-.7.3-.7.3-.7.3-.6.2-.7.3-.7.3-.7.2-.8v-.7l.3-.7.2-.7v-.7l.3-.8v-1.4l.2-.8v-2.1h-1v.4l-.2 1v1.4l-.2.7v.7l-.2.7-.2.7v.8l-.3.7-.2.7-.2.7v.6l-.3.7-.3.7-.2.7-.3.7-.4.6-.2.7-.4.7-.3.6-.3.7-.4.6-.4.7-.3.6-.4.6-.5.6-.4.6-.4.7-.4.7-.4.6-.5.6h-.2l-.5-.7-.4-.6-.4-.6-.4-.6-.4-.6-.4-.6-.4-.6-.4-.6-.3-.7-.4-.6-.4-.7-.3-.6-.3-.7-.4-.7-.2-.6-.3-.7-.2-.7-.2-.7-.2-.7-.2-.7-.2-.8-.2-.7-.2-.7v-.7l-.3-.6v-.8l-.2-.7v-2.3l-.2-.8v-.4h-.2v-.2h-.5z"/>
|
||||||
|
</g>
|
||||||
|
<g transform="translate(-32 32)scale(.53333)">
|
||||||
|
<path fill="#c6363c" d="M549.2 422v108.6a72 72 0 0 1-19.7 49.8 65 65 0 0 1-94.7.1 73 73 0 0 1-19.7-49.8V422z"/>
|
||||||
|
<use xlink:href="#rs-f" width="1350" height="900" transform="matrix(-1 0 0 1 964.3 -90)"/>
|
||||||
|
<path fill="#fff" stroke="#21231e" d="M549.2 488.6v28h-53v83a64 64 0 0 1-14 1.5 59 59 0 0 1-14-1.5v-83h-53v-28h53V422h28v66.6z"/>
|
||||||
|
<use xlink:href="#rs-f" width="1350" height="900" transform="translate(0 -90)"/>
|
||||||
|
<path id="rs-f" fill="#fff" stroke="#21231e" d="M459 567.3V528c-8.4-2.3-29.3-10.6-30.3 7.6-.5 8.4 10.4 14 13.3 6.5 1.4-3.4 0-6-2-6.7-2.4-.8-4.6 1-4 3.6-8.8-4.7 8-14.6 15.5-1.7 2.2 3.7-3.7 7.6-3.7 10.4 0 2.7 5.8 7 3.7 10.3-7.7 12.8-24.3 3-15.5-1.6-.6 2.7 1.6 4.5 4 3.6 2-.8 3.4-3.3 2-6.8-3-7.5-13.8-2-13.3 6.5 1 18.2 22 10 30.4 7.6z"/>
|
||||||
|
<use xlink:href="#rs-f" width="1350" height="900" transform="matrix(-1 0 0 1 964.3 0)"/>
|
||||||
|
<path fill="#21231e" d="M414 421v113.3l.2 1.7v2l.4 1.7.2 1.8.3 1.7.4 1.8.3 1.8.4 1.7.6 1.7.5 1.7.6 1.5.6 1.7.5 1.6.7 1.6.7 1.6.7 1.6.8 1.6.8 1.5.8 1.5 1 1.5.8 1.5 1 1.4.8 1.4 1 1.4 1 1.4 1 1.3 1 1.5 1.2 1.2 1 1.3 1.2 1.2 1.2 1.2 1.2 1.2 1.2 1 1.3 1.3 1 1 1.4 1 1.3 1 1.4 1 1.5 1 1.4 1 1.3.8 1.4.8 1.5.8 1.4.8 1.6.7 1.5.6 1.5.7 1.5.6 1.6.5 1.6.5 1.5.5 1.6.5 1.8.4 1.6.4 1.7.4 1.8.2 1.7.3 1.7.2h1.7l1.6.2h8.9l1.7-.3 1.8-.3 1.7-.3 1.7-.2 1.7-.4 1.5-.4 1.7-.4 1.6-.5 1.6-.5 1.6-.5 1.5-.6 1.6-.7 1.6-.7 1.6-.7 1.5-.8 1.4-.8 1.6-.8 1.4-1 1.5-.7 1.4-1 1.2-1 1.4-1 1.3-1 1.2-1 1.3-1 1.2-1.3 1.2-1 1-1.3 1.3-1.2 1.2-1.2 1-1.3 1.2-1.2 1-1.4 1-1.2 1-1.4 1-1.4 1-1.4 1-1.4.7-1.5 1-1.5.7-1.5.8-1.5.8-1.6.7-1.6.6-1.6.6-1.6.6-1.6.5-1.7.5-1.6.5-1.8.4-1.7.6-1.7.3-1.8.2-1.8.3-1.7.3-1.8v-.9l.3-2 .2-1.7V421zm2.3 2.2H548V536l-.3 1.6-.2 1.8-.2 1.7-.3 1.8-.3 1.7-.4 1.7-.3 1.7-.5 1.5-.4 1.7-.4 1.6-.5 1.6-.5 1.6-.6 1.5-.8 1.6-.7 1.6-.7 1.5-.7 1.6-.7 1.4-.8 1.5-.8 1.4-1 1.3-.8 1.4-1 1.3-1 1.3-1 1.3-1 1.3-1 1.2-1.2 1.2-1 1.2-1.2 1-1.2 1.3-1.2 1-1.2 1-1.2 1-1.3 1-1.2 1-1.4 1-1.4 1-1.3.8-1.4 1-1.5.7-1.4.8-1.3.7-1.5.8-1.5.6-1.4.7-1.5.7-1.6.5-1.5.6-1.5.5-1.6.4-1.6.4-1.6.5-1.6.3-1.6.3-1.7.4H489l-1.8.3h-1.7l-1.6.2h-3.6l-1.6-.2H477l-1.6-.3h-1.6l-1.7-.4-1.5-.2-1.6-.3-1.7-.4-1.6-.3-1.6-.4-1.4-.5-1.6-.6-1.5-.5-1.5-.6-1.5-.6-1.4-.6-1.4-.8-1.4-.7-1.4-.8-1.4-.8-1.4-1-1.3-.7-1.4-1-1.3-1-1.2-1-1.3-1-1.2-1-1.2-1-1-1-1.3-1.3-1-1-1.2-1.3-1-1.2-1-1.3-1.2-1.4-1-1.3-1-1.3-1-1.3-.8-1.4-.8-1.4-1-1.5-.7-1.5-.8-1.4-.7-1.5-.8-1.4-.6-1.5-.6-1.5-.6-1.5-.5-1.6-.6-1.6-.5-1.6-.5-1.7-.4-1.6-.4-1.8-.4-1.7-.3-1.7-.3-1.7-.2-1.6-.2-1.8-.2-1.7v-5.4z"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 177 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ru" viewBox="0 0 512 512">
|
||||||
|
<path fill="#fff" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#0039a6" d="M0 170.7h512v170.6H0z"/>
|
||||||
|
<path fill="#d52b1e" d="M0 341.3h512V512H0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 233 B |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-se" viewBox="0 0 512 512">
|
||||||
|
<path fill="#005293" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#fecb00" d="M134 0v204.8H0v102.4h134V512h102.4V307.2H512V204.8H236.4V0z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 223 B |
@@ -0,0 +1,18 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-si" viewBox="0 0 512 512">
|
||||||
|
<defs>
|
||||||
|
<clipPath id="si-a">
|
||||||
|
<path fill-opacity=".7" d="M60.2 0h497.3v497.3H60.2z"/>
|
||||||
|
</clipPath>
|
||||||
|
</defs>
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt" clip-path="url(#si-a)" transform="translate(-62)scale(1.0295)">
|
||||||
|
<path fill="#fff" d="M0 0h994.7v497.3H0z"/>
|
||||||
|
<path fill="#d50000" d="M0 331.6h994.7v165.7H0z"/>
|
||||||
|
<path fill="#0000bf" d="M0 165.8h994.7v165.8H0z"/>
|
||||||
|
<path fill="#d50000" d="M282 90.3c-3.9 59.9-6.1 92.7-15.2 107.9-9.9 16.3-19.5 28.2-58 42.6-38.4-14.4-48-26.3-57.9-42.6-9-15.2-11.3-48-15.2-107.9l5.7-1.9c11.4-3.5 20-6.3 26.3-7.5 9-2 16.7-4.1 41-4.6 24.3.4 32 2.7 41 4.6q9.6 2.1 26.6 7.5z"/>
|
||||||
|
<path fill="#0000bf" d="M276.4 88.3c-3.7 59.8-6.7 87.2-11.6 100.3-9.3 22.6-24.1 35-56 46.7-31.8-11.8-46.6-24.1-56-46.7-4.8-13.1-7.9-40.4-11.4-100.3q16.8-5.4 26.3-7.5c9-1.9 16.7-4.2 41-4.6 24.3.4 32.1 2.7 41.1 4.6q9.6 2.1 26.6 7.5"/>
|
||||||
|
<path fill="#ffdf00" d="m208.8 106.6 1.5 3.7 6.7.9-4.3 2.6 4.2 2.8-6.1 1-1.9 3.3-2-3.4-6-.8 4-2.9-4-2.6 6.4-1z"/>
|
||||||
|
<path fill="#fff" d="m262.5 174.5-3.7-3-2.7-4.4-5.2-4.6-2.8-4.6-5.2-4.7-2.6-4.6-2.8-2.3-1.9-1.7-4.7 4.1-2.6 4.6-3.3 2.9-3.5-2.8-2.7-4.7-10-17.7-10 17.7-2.6 4.7-3.6 2.8-3.2-3-2.6-4.5-4.7-4.1-1.9 1.7-2.8 2.3-2.6 4.6-5.2 4.7-2.8 4.6-5.3 4.6-2.6 4.4-3.7 3a64 64 0 0 0 18 35.2c6.4 6.1 19.5 14.4 35.5 19.9a101 101 0 0 0 35.7-20 64 64 0 0 0 18.1-35.1"/>
|
||||||
|
<path fill="#ffdf00" d="m224.8 81.5 1.5 3.6 6.7 1-4.3 2.5 4.2 2.9-6.1 1-1.9 3.3-2-3.4-5.9-.8 4-3-4.1-2.5 6.4-1zm-32 0 1.5 3.5 6.7 1-4.3 2.6 4.2 2.8-6.1 1-1.9 3.3-2.1-3.4-5.8-.8 4-2.9-4.1-2.6 6.4-1 1.5-3.6z"/>
|
||||||
|
<path fill="#0000bf" d="M254.2 197.2H247l-6.7-.5-8.1-4-9.2.1-7.9 3.9-6.2.5-6.3-.5-7.9-3.9h-9.1l-8.1 4-6.7.4h-7.4l-3.5-6 .1-.2 10.9 1.8 6.6-.5 8.1-4h9.2l8 4 6.2.5 6.2-.6 8-3.8h9l8.2 3.9 6.7.5 10.5-1.9.2.3zm-84 9.3 7.2-.5 8.1-4h9.1l8 3.9 6.2.6 6.2-.6 8-3.9h9.1l8.1 4 7.3.5 4.7-5.8-.2-.2-5 1.5-6.7-.5-8.1-4h-9.1l-8 4-6.2.5-6.2-.5-8-4h-9.1l-8.1 4-6.7.5-5-1.2v.2z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-sk" viewBox="0 0 512 512">
|
||||||
|
<path fill="#ee1c25" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#0b4ea2" d="M0 0h512v341.3H0z"/>
|
||||||
|
<path fill="#fff" d="M0 0h512v170.7H0z"/>
|
||||||
|
<path fill="#fff" d="M203.2 395.5c-45.9-22-111.5-66-111.5-152.8s4.1-126.2 4.1-126.2h214.8s4.2 39.4 4.2 126.2S249 373.4 203.2 395.5"/>
|
||||||
|
<path fill="#ee1c25" d="M203.2 384c-42.1-20.3-102.3-60.5-102.3-140.2s3.8-115.8 3.8-115.8h197s3.8 36.2 3.8 115.8-60.2 120-102.3 140.2"/>
|
||||||
|
<path fill="#fff" d="M212.2 223c11.4.2 33.7.6 53.5-6 0 0-.6 7-.6 15.3s.6 15.3.6 15.3a172 172 0 0 0-53.5-6v44h-18v-44a172 172 0 0 0-53.5 6s.6-7 .6-15.3-.6-15.3-.6-15.3c19.9 6.6 42.1 6.2 53.5 6v-27.7a128 128 0 0 0-42.3 6.1s.5-7 .5-15.3-.5-15.4-.5-15.4c17 5.7 31.9 6.2 42.2 6-.5-17.4-5.6-39.4-5.6-39.4s10.5.8 14.7.8 14.7-.8 14.7-.8-5.1 22-5.7 39.5a126 126 0 0 0 42.3-6s-.5 7-.5 15.3.5 15.3.5 15.3c-17-5.7-31.9-6.1-42.3-6z"/>
|
||||||
|
<path fill="#0b4ea2" d="M203.2 280.8c-21.2 0-32.6 29.4-32.6 29.4s-6.3-14-23.6-14c-11.7 0-20.3 10.5-25.8 20.2 21.3 33.8 55.3 54.7 82 67.6 26.7-12.9 60.7-33.8 82-67.7-5.5-9.6-14.1-20-25.8-20-17.3 0-23.6 14-23.6 14s-11.4-29.5-32.6-29.5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ua" viewBox="0 0 512 512">
|
||||||
|
<g fill-rule="evenodd" stroke-width="1pt">
|
||||||
|
<path fill="gold" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#0057b8" d="M0 0h512v256H0z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 232 B |
@@ -0,0 +1,5 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-xk" viewBox="0 0 512 512">
|
||||||
|
<path fill="#244AA5" d="M0 0h512v512H0z"/>
|
||||||
|
<path fill="#D0A650" d="m202.3 245.7 1.3-.7 1.6-.5s.8-.4 1.2-.4 1.3.5 1.5.9c.3.4 1.7 1 1.7 1s1 .4 1.1 0q.3-.4 1-.5c.7-.1.6-.4.6-.7q0-.5.5-1.1c.3-.4 1-.6 1-.6s1-.4 1-1 1.1-1 1.7-1c.5-.1 0-.4 1.3-.2 1.4.1 1.6-.3 1.7-.7 0-.5 0-.9.9-.8q1.3.3 1.3-.4c0-.4.8-.7-.6-1-1.4-.5-1.3-.2-1.4-.8s0-.6-1-.7-.4-.5-.5-1q-.1-.6-.5-1c-.2-.4-.7-.4-.7-.8q.2-.8-.6-.8c-.8 0-.7 0-.7-.4q0-.8.6-.9.9-.2.7-.9v-1.5l.6-.8 2.3-2c.2-.3.9-.6 1.2 0q.4.9.8 1.2c.2.3 1 0 1.2-.2.2-.4.2-1.5.7-2.2q.8-.9.9-1.4 0-.9.9-1c.9-.1.9-.7.9-1.1s.2-.9 1-1c.6 0 1-.5 1.2.1s.4.7.9.9q.7.5 1.4.2c.4-.3.5-.8 1-.5q.8.4 1-.6l.2-1.5s.1-.4 1-.4.2-.5.6-.8c.5-.4.5-.7 1-.5s1.5-.2 1-.7c-.4-.6-1.4-1.4-.9-2 .6-.6.8-1.7.3-1.8s-1.4.1-1.5-.5-.3-1 .3-1.2c.7 0 1.9 0 2.2-.4s1.5-.5 1.5-1.4c0-1 .3-1 .4-1.6.1-.5.1-1.6-.3-2.1l-1.3-1.6a2 2 0 0 0-1.2-1q-1.2-.2-1.3-.8c0-.6.5 0 .5-1.3s-1-1.6-1-1.6l-1.3-.8s-2.4-.6-3-.6-1.4-.7-.7-1.1q1.2-.8.7-1.8c-.5-1-.5-1-1.5-1.3q-1.5-.3-.9-1.1c.5-.6.6-1 1.2-.6q.9.7 2.2.2 1-.6 1.4-.2c.3.3.8.7 1.4.2.5-.6 1.5-.6 2-.4.5.3 1.6.2 2 0s1-.8 1-1.1.5-1 1-1.2c.4-.3 1-1.1 1.5-1.1s.6-.5.6-.9.4-1 .5-1.2c0-.3 1-.7 1.6-.1s1.5.7 1.5.7h1.4q.8-.4 1.8 0c.6.5 1.6.5 1.6.5l.1-1q.2-.7-.1-1.2c-.4-.3-.9-.6-.1-1.2.7-.5.7-.4 1.4-.4l1.8.1c.6 0 1.5.5 1.5 1s.4.9.8 1.1q.9.4.9 1-.4.6.3.7.9 0 .8.6-.2.7.7.9t.8 1l.1 1c.1.4.8 1.1.8 1.1s1 1.2-.3.6-1.6-.8-2-.7l-.8.3-1 .2c-.6 0-1 1-.3 1.1q1.1.1 1 1c-.1.9-.2.4-.6.8s-.7.2-1.3.2q-1 .1-.8 1c.2.9.2.4.5.8q.5.7 1.2.1c.5-.4.9-.7 1.1.1s.7.8 1.2.8q.8 0 1.2-.3.8-.3 1.2.7.1.9 1 1 .6.4.8.9.2.7.8.3.9-.4 1.5 0l.8 1v.8l1.8.6.2.8q0 .6.7.4h1.3s.7-.3.7-.8.3-.6.7-.6c.5 0 1.5.3 1.8-.2q.6-.8 1.7-.4l2.4.9s-.2 0 .3 1c.6.8 1.6.8 1.6.8l.3 1.1c.2.3 1 .5 1.5.2q.6-.7 1.6-.8 1 0 1-.3c0-.3-.6-.6-.6-1.2s.6-.5.7-1 .6 0 1 .3q.6.4.8 1c.2.6.8 1.5 1.2 1.7.5.2 1.7.3 2-.2s1.3-.6 1.3.5c-.1 1-.3 1 .3 1.4s.8 1.6.2 1.9q-.7.3-.7 1-.2 1.1.2 1.6.8.5.8 1c0 .5.5 1.9.5 1.9s.3 1.5.2 1.8-.2 1 .6.9q1.1-.3 1 .6c-.1.9.3 1-.2 1.2s-.7 1-.3 1.3q.8.2 1 .7c0 .4.8 1 1.3.6q.4-.5.9-.6.7 0 .3-.7-.6-.7-.1-1.2c.4-.2.5-1 .5-1s.6-.8 1.2 0c.7.7.8.4 1.2.4q.7.1.7.9c0 .8 1 .3 1 1l.1 2.3s0 1 .6 1.3.4.4.1.9q-.2.7-.1 1.4c.1.7.1.5-.3.9q-.8.5-.4 1.2c.3.6.2.9 1 1.5.7.6.6.9.6 1.3s.7 1 .7 1.4c0 .5.6 1 1 1s1 .9 1 1.3l.2 1.6s-.1.2.4.2 1 .6 1.1 0q.5-.9 1.1-1c.4-.2 1.2-.5 1.5-.3q.7.4 1.2.3h2.2s.4 0 .7.7 1.1 1.4.7 1.8c-.5.3-.5 1 0 1q1-.1 1.2.4-.1.8.5.5.8-.4 1 0t1 .6q.5.4.9-.2c.3-.4 1.4-.7 1.4-.7s1.5-.3 2-.2l1.3.5s.4.9.2 1.4-.4.4-.2 1.2q.1 1-.5.8-.7-.3-.8.6c-.1.9-.4.8-.4 1.4s0 .5-.3.8q-.7.5 0 .8c.6 0 .4.6.4 1v1s0 .5-.6.5q-1.1-.1-1.3.4c-.2.5-.8 1-.2 1.5q.9.6.1.8c-.4.2-.6 1 0 1.3q1 .4.3.9c-.5.3-1.4.6-1.4 1q0 .7-.8.7-1.2.1-.1 1.6c.6.9.8.7 1.3.7s.4-.7.4-.7h2q.9 0 1.5-.2c.4-.2 1.4-.7 1.4.1s.2.7.7 1 .5.1 1 .6q.4.6.6-.5c.3-.6.9-1.2 1.3-.5s.5.5 1 .5q.9 0 .8.7c-.1.7.6.4 1.2.3s.7.4 1 .8q.3.5.8.4.5.1 1-.4c.3-.3.6-.8 1-.2s1.1.4 1.3-.2 1-1.1 1.5-.4q.6.7 1.3.7t1.4.5c.3.5 1.2 1.2.5 1.7s-1.4 1.2-.8 1.3q.9.2 1.3.8c.4.4 1.6.6 2.1.7s1 .2 1.7-.4c.6-.5 1.3-.5 1.2.2s.3 1.5.3 1.5.4 1 .4 1.5q-.2.4.8 1c1 .6.7.9 1.4.4q.9-.8.8-1.2v-1.4s1-.2 1.3-.4 1.9-1 2.2-.7.9.2 1.2 0h1.5q.6.2 1.8-.6c.7-.6.7-.4 1.2 0s.4 1 .2 1.3 0 1 .6 1q1 0 .8.9l.2 1.3c.1.4.1.5 1.1.8 1 .2.9.8 1 1.3q.4.6-.3.7-.9 0-.8.7c-.1.4-1 .6-1 .6l-.8-.2s-.6 0-.8.4c-.3.3-.9 1-.5 1.6q.6.9 0 1.2c-.4.3-.6 1.5-1 1.3s-.8 0-1.1.2-.3 1.4.4 1.8q1.1.9 1 1l.8 1.3-.5.7c-.2.4-.7 1.3-.2 1.5s.3 1-.2 1-1.2-.2-1.3.5-.4.5-1 .4-2-.4-2.3.3q-.3 1-1 .8c-.7 0-.5 1-.3 1.5s.9 1 .9 1 .5.4.5 1.3-.4.8-.7.2-1.1-.5-1.2.1-.2 1.7-.9 1.7q-.8 0-1.2.6c-.2.3-.7.9-.7 1.2s-.1.7-.6.8c-.4 0-.5 1-.5 1s-1.1.2-1.2.6 0 1.1-.9 1.2q-1.3.3-1.7 1.1-.5.7 0 1.2c.4.4.6 1.8 1 2q1 .3.8 1c0 .8-.6 1.3.1 1.3q1.2 0 1 .8c-.3.6-.3.6-.9.9-.5.3-.8.3-.8 1s.2.6-.3.9q-.6.4-.5 1c0 .6-.1.4-.7 1s-1.3.6-1.8.4q-.5 0-.6-.6c-.1-.6-1-.6-1.4-.5-.5.1-1.1.6-1.1.6s-.8.5-.5 1q.5.8-.4 1c-.7 0-2.2.7-2.6 0-.5-.9-1.8-1.4-2-.3s.6.9.5 2c-.1 1-.3 1-.7 1.3q-.4.4-.4 1c-.1.4-.2 1.4.4 1.7s1 1.3 1 1.8 0 1 .6 1q1.1.2 1.3-.7c.1-.6 1.4-.9 1.5.3s0 1.8.6 2.3l1.2 1.5c.6.6 1 2.4 1 2.4-1 .2-1.2 1.4-1.8 1.6l-1 .2-.5.7c-.7.3-.5-.8-1.4-.4-.7.3-.8 1.2-1.8.8-.6-.2-1-2.3-1.1-2.8 0-.3-.7-1.2-.9-1.5-.6-1-1.8 0-2.2-.3-.7-.4-.4-1.2-1.3-1.2-2-.2-.6 1.1-1.4 1.4-1.5.6-1.5-1.4-2.7-.5-.4.3.7.6.4 1.2-.2.4-1.4 1.2-1.3 1.8.1.8 1 1 1.2 1.7.7 1.7-.4 3-2.2 1.6-1-.8-1.1-2.2-2.6-2.1l-.9.4q-.4-.2-1-.2c-.5.2 0 .6-.1.9l-.9.8c-.4.5-.3 1-.9 1.5-.5.3-.9.1-1.4.7-.6.6-.7 1.5-1.4 2.1-.4.3-.6-.9-1 0q-.2 0-.3.2v.6c1 2-1 2.6-1.7 4-.3.5.5 1 .2 1.4l-1.2 1.4q0 .4-.3.8c-.2.2-.7 0-.8.3 0 .3.6.6.8.8s-.1.5 0 .6c1 1 .4.7.9 1.6 0 .2.4 0 .5.3l-.1.6.8 1.1c0 .3-1.5 1-1.8 1-.9 0 .8-1.8-.7-1.8-.6 0-.6 1.4-.8 1.6h-.8c-.9 0-1.5 1.1-1.9 1.2h-1.1c-.4 0-.8.4-1.2.4q-.2 0-.5-.3l-1.6-.5c-.3 0-.8.4-1 .2-.2-.1.3-.7-.4-1l-.6-1.4q.4-1.2.4-2.5c-.2-1-1.5-1.7-2-2.4-.2-.5-.4-2-1-2.3a5 5 0 0 1-1.4-1.7q.2-.3 0-.8-.5-.3-.8-.8c-.1-.4 0-1.5-.4-1.7-.4-.3-3.3-.6-4-.7q-.5 0-.9-.3l-.4-.5-.9.1h-.7c-1 .6-1.1 1.4-2 2-.9.8-1.8.7-2.7 1.5-.6.5-1 1.7-1.5 2.2-.2.3-.6 0-.7.1l-1.6 1.5s0 .3-.2.4l-1 .2h-1c-.4.2-.4.7-2 1l-.3.2-.6.5h-.6q-.6.1-1 .4c-.9 0-2.2-.4-2.8-.1-.4.1-1.5 1.3-2 1.4h-.6c-.2 0-.5 1.4-.6 1.7l-2 1.7-.5.8q-.3.4-.8.4-.7-.1-1.3-.4c-.7 0-2.7-.2-3.3 0-.6 0-.4 1.9-2Line truncated
|
||||||
|
<path fill="#FFF" d="m281.4 124.5 12.9 39.7-33.8-24.5h41.7l-33.7 24.5zm50.2 8.9 12.9 39.7-33.8-24.5h41.8L318.7 173zm47.9 17.4 13 39.7-33.8-24.5h41.7l-33.8 24.5zm-149.1-26.3-13 39.7 33.8-24.5h-41.7l33.8 24.5-13-39.7zm-50.2 8.9-13 39.7 33.8-24.5h-41.7L193 173l-13-39.7zm-47.7 17.4 12.9 39.7-33.8-24.5h41.7l-33.7 24.5z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 7.6 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 116 KiB |
@@ -0,0 +1 @@
|
|||||||
|
{"version":8,"sources":{"ne2_shaded":{"maxzoom":6,"tileSize":256,"tiles":["https://tiles.openfreemap.org/natural_earth/ne2sr/{z}/{x}/{y}.png"],"type":"raster"},"openmaptiles":{"type":"vector","url":"https://tiles.openfreemap.org/planet"}},"sprite":"https://tiles.openfreemap.org/sprites/ofm_f384/ofm","glyphs":"https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf","layers":[{"id":"background","type":"background","paint":{"background-color":"#f8f4f0"}},{"id":"natural_earth","type":"raster","source":"ne2_shaded","maxzoom":7,"paint":{"raster-opacity":["interpolate",["exponential",1.5],["zoom"],0,0.6,6,0.1]}},{"id":"park","type":"fill","source":"openmaptiles","source-layer":"park","paint":{"fill-color":"#d8e8c8","fill-opacity":0.7,"fill-outline-color":"rgba(95, 208, 100, 1)"}},{"id":"park_outline","type":"line","source":"openmaptiles","source-layer":"park","paint":{"line-color":"rgba(228, 241, 215, 1)","line-dasharray":[1,1.5]}},{"id":"landuse_residential","type":"fill","source":"openmaptiles","source-layer":"landuse","maxzoom":12,"filter":["==",["get","class"],"residential"],"paint":{"fill-color":["interpolate",["linear"],["zoom"],9,"hsla(0,3%,85%,0.84)",12,"hsla(35,57%,88%,0.49)"]}},{"id":"landcover_wood","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"wood"],"paint":{"fill-antialias":false,"fill-color":"hsla(98,61%,72%,0.7)","fill-opacity":0.4}},{"id":"landcover_grass","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"grass"],"paint":{"fill-antialias":false,"fill-color":"rgba(176, 213, 154, 1)","fill-opacity":0.3}},{"id":"landcover_ice","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"ice"],"paint":{"fill-antialias":false,"fill-color":"rgba(224, 236, 236, 1)","fill-opacity":0.8}},{"id":"landcover_wetland","type":"fill","source":"openmaptiles","source-layer":"landcover","minzoom":12,"filter":["==",["get","class"],"wetland"],"paint":{"fill-antialias":true,"fill-opacity":0.8,"fill-pattern":"wetland_bg_11","fill-translate-anchor":"map"}},{"id":"landuse_pitch","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"pitch"],"paint":{"fill-color":"#DEE3CD"}},{"id":"landuse_track","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"track"],"paint":{"fill-color":"#DEE3CD"}},{"id":"landuse_cemetery","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"cemetery"],"paint":{"fill-color":"hsl(75,37%,81%)"}},{"id":"landuse_hospital","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"hospital"],"paint":{"fill-color":"#fde"}},{"id":"landuse_school","type":"fill","source":"openmaptiles","source-layer":"landuse","filter":["==",["get","class"],"school"],"paint":{"fill-color":"rgb(236,238,204)"}},{"id":"waterway_tunnel","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["==",["get","brunnel"],"tunnel"],"paint":{"line-color":"#a0c8f0","line-dasharray":[3,3],"line-gap-width":["interpolate",["linear"],["zoom"],12,0,20,6],"line-opacity":1,"line-width":["interpolate",["exponential",1.4],["zoom"],8,1,20,2]}},{"id":"waterway_river","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["all",["==",["get","class"],"river"],["!=",["get","brunnel"],"tunnel"]],"layout":{"line-cap":"round"},"paint":{"line-color":"#a0c8f0","line-width":["interpolate",["exponential",1.2],["zoom"],11,0.5,20,6]}},{"id":"waterway_other","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["all",["!=",["get","class"],"river"],["!=",["get","brunnel"],"tunnel"]],"layout":{"line-cap":"round"},"paint":{"line-color":"#a0c8f0","line-width":["interpolate",["exponential",1.3],["zoom"],13,0.5,20,6]}},{"id":"water","type":"fill","source":"openmaptiles","source-layer":"water","filter":["!=",["get","brunnel"],"tunnel"],"paint":{"fill-color":"rgb(158,189,255)"}},{"id":"landcover_sand","type":"fill","source":"openmaptiles","source-layer":"landcover","filter":["==",["get","class"],"sand"],"paint":{"fill-color":"rgba(247, 239, 195, 1)"}},{"id":"aeroway_fill","type":"fill","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["match",["geometry-type"],["MultiPolygon","Polygon"],true,false],"paint":{"fill-color":"rgba(229, 228, 224, 1)","fill-opacity":0.7}},{"id":"aeroway_runway","type":"line","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","class"],"runway"]],"paint":{"line-color":"#f0ede9","line-width":["interpolate",["exponential",1.2],["zoom"],11,3,20,16]}},{"id":"aeroway_taxiway","type":"line","source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["match",["geometry-type"],["LineString","MultiLineString"],true,false],["==",["get","class"],"taxiway"Line truncated
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).mlcontour=e()}(this,(function(){"use strict";var t,e,i;function r(r,s){if(t)if(e){var n="var sharedChunk = {}; ("+t+")(sharedChunk); ("+e+")(sharedChunk);",o={};t(o),i=s(o),"undefined"!=typeof window&&(i.workerUrl=window.URL.createObjectURL(new Blob([n],{type:"text/javascript"})))}else e=s;else t=s}return r(0,(function(t){class e{constructor(t,e){this.start=t,this.end=e,this.points=[],this.append=this.append.bind(this),this.prepend=this.prepend.bind(this)}append(t,e){this.points.push(Math.round(t),Math.round(e))}prepend(t,e){this.points.splice(0,0,Math.round(t),Math.round(e))}lineString(){return this.toArray()}isEmpty(){return this.points.length<2}appendFragment(t){this.points.push(...t.points),this.end=t.end}toArray(){return this.points}}const i=[[],[[[1,2],[0,1]]],[[[2,1],[1,2]]],[[[2,1],[0,1]]],[[[1,0],[2,1]]],[[[1,2],[0,1]],[[1,0],[2,1]]],[[[1,0],[1,2]]],[[[1,0],[0,1]]],[[[0,1],[1,0]]],[[[1,2],[1,0]]],[[[0,1],[1,0]],[[2,1],[1,2]]],[[[2,1],[1,0]]],[[[0,1],[2,1]]],[[[1,2],[2,1]]],[[[0,1],[1,2]]],[]];function r(t,e,i,r){return(e=2*e+r[0])+(i=2*i+r[1])*(t+1)*2}function s(t,e,i){return(e-t)/(i-t)}function n(t,n,o=4096,a=1){if(!t)return{};const h=o/(n.width-1);let l,c,d,u,f,p;const w={},g=new Map,m=new Map;function b(t,e,i){0===t[0]?i(h*(p-1),h*(f-s(d,e,l))):2===t[0]?i(h*p,h*(f-s(u,e,c))):0===t[1]?i(h*(p-s(c,e,l)),h*(f-1)):i(h*(p-s(u,e,d)),h*f)}for(f=1-a;f<n.height+a;f++){c=n.get(0,f-1),u=n.get(0,f);let s=Math.min(c,u),o=Math.max(c,u);for(p=1-a;p<n.width+a;p++){l=c,d=u,c=n.get(p,f-1),u=n.get(p,f);const a=s,h=o;if(s=Math.min(c,u),o=Math.max(c,u),isNaN(l)||isNaN(c)||isNaN(u)||isNaN(d))continue;const y=Math.min(a,s),v=Math.max(h,o),P=Math.ceil(y/t)*t,x=Math.floor(v/t)*t;for(let s=P;s<=x;s+=t){const t=l>s,o=c>s,a=d>s,h=u>s;for(const l of i[(t?8:0)|(o?4:0)|(h?2:0)|(a?1:0)]){let t=g.get(s);t||g.set(s,t=new Map);let i=m.get(s);i||m.set(s,i=new Map);const o=l[0],a=l[1],h=r(n.width,p,f,o),c=r(n.width,p,f,a);let d,u;if(d=i.get(h))if(i.delete(h),u=t.get(c))if(t.delete(c),d===u){if(b(a,s,d.append),!d.isEmpty()){let t=w[s];t||(w[s]=t=[]),t.push(d.lineString())}}else d.appendFragment(u),i.set(d.end=u.end,d);else b(a,s,d.append),i.set(d.end=c,d);else if(d=t.get(c))t.delete(c),b(o,s,d.prepend),t.set(d.start=h,d);else{const r=new e(h,c);b(o,s,r.append),b(a,s,r.append),t.set(h,r),i.set(c,r)}}}}}for(const[t,e]of g.entries()){let i=null;for(const r of e.values())r.isEmpty()||(null==i&&(i=w[t]||(w[t]=[])),i.push(r.lineString()))}return w}
|
||||||
|
/*! *****************************************************************************
|
||||||
|
Copyright (c) Microsoft Corporation.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
***************************************************************************** */function o(t,e){var i={};for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&e.indexOf(r)<0&&(i[r]=t[r]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var s=0;for(r=Object.getOwnPropertySymbols(t);s<r.length;s++)e.indexOf(r[s])<0&&Object.prototype.propertyIsEnumerable.call(t,r[s])&&(i[r[s]]=t[r[s]])}return i}function a(t,e,i,r){return new(i||(i=Promise))((function(s,n){function o(t){try{h(r.next(t))}catch(t){n(t)}}function a(t){try{h(r.throw(t))}catch(t){n(t)}}function h(t){var e;t.done?s(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(o,a)}h((r=r.apply(t,e||[])).next())}))}function h(t){const e=Object.entries(t);return e.sort((([t],[e])=>t<e?-1:t>e?1:0)),e}function l(t){return h(t).map((([t,e])=>[t,..."number"==typeof e?[e]:e].join("*"))).join("~")}function c(t){return h(t).map((([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`)).join(",")}let d=null;function u(){return null==d&&(d="undefined"!=typeof OffscreenCanvas&&new OffscreenCanvas(1,1).getContext("2d")&&"function"==typeof createImageBitmap),d||!1}let f=null;function p(t,e,i){let r=()=>{};const s=setTimeout((()=>{r(new Error("timed out")),null==i||i.abort()}),t);w(i,(()=>{r(new Error("aborted")),clearTimeout(s)}));const n=new Promise(((t,e)=>{r=e}));return Promise.race([n,e.finally((()=>clearTimeout(s)))])}function w(t,e){e&&(null==t||t.signal.addEventListener("abort",e))}function g(t){var e;return Boolean(null===(e=null==t?void 0:t.signal)||void 0===e?void 0:e.aborted)}let m,b,y,v,P=0;class x{constructor(t=100){this.size=()=>this.items.size,this.get=(t,e,i)=>{let r=this.items.get(t);if(r)r.lastUsed=++P,r.waiting++;else{const i=new AbortController,s=e(t,i);r={abortController:i,item:s,lastUsed:++P,waiting:1},this.items.set(t,r),this.prune()}const s=this.items,n=r.item.then((t=>t),(e=>(s.delete(t),Promise.reject(e))));let o=!1;return w(i,(()=>{var e;r&&r.abortController&&!o&&(o=!0,--r.waiting<=0&&(null===(e=r.abortController)||void 0===e||e.abort(),s.delete(t)))})),n},this.clear=()=>this.items.clear(),this.maxSize=t,this.items=new Map}prune(){if(this.items.size>this.maxSize){let t,e=1/0;this.items.forEach(((i,r)=>{i.lastUsed<e&&(e=i.lastUsed,t=r)})),void 0!==t&&this.items.delete(t)}}}function F(t,e){return m||(m=new OffscreenCanvas(t.width,t.height),b=m.getContext("2d",{willReadFrequently:!0})),V(t,e,m,b)}const k=function(){if(null==f&&(f=!1,u()&&"undefined"!=typeof VideoFrame)){const t=5,e=new OffscreenCanvas(5,5).getContext("2d",{willReadFrequently:!0});if(e){for(let i=0;i<t*t;i++){const r=4*i;e.fillStyle=`rgb(${r},${r+1},${r+2})`,e.fillRect(i%t,Math.floor(i/t),1,1)}const i=e.getImageData(0,0,t,t).data;for(let e=0;e<t*t*4;e++)if(e%4!=3&&i[e]!==e){f=!0;break}}}return f||!1}()?function(t,e,i){return a(this,void 0,void 0,(function*(){var r,s,n;const o=yield createImageBitmap(t);if(g(i))return null;const a=new VideoFrame(o,{timestamp:0});try{if(!((null===(r=null==a?void 0:a.format)||void 0===r?void 0:r.startsWith("BGR"))||(null===(s=null==a?void 0:a.format)||void 0===s?void 0:s.startsWith("RGB"))))throw new Error(`Unrecognized format: ${null==a?void 0:a.format}`);const t=null===(n=null==a?void 0:a.format)||void 0===n?void 0:n.startsWith("BGR"),i=a.allocationSize(),h=new Uint8ClampedArray(i);if(yield a.copyTo(h),t)for(let t=0;t<h.length;t+=4){const e=h[t];h[t]=h[t+2],h[t+2]=e}return M(o.width,o.height,e,h)}catch(t){return g(i)?null:F(o,e)}finally{a.close()}}))}:u()?function(t,e,i){return a(this,void 0,void 0,(function*(){const r=yield createImageBitmap(t);return g(i)?null:F(r,e)}))}:"undefined"!=typeof WorkerGlobalScope&&"undefined"!=typeof self&&self instanceof WorkerGlobalScope?function(t,e,i){return self.actor.send("decodeImage",[],i,void 0,t,e)}:function(t,e,i){return a(this,void 0,void 0,(function*(){y||(y=document.createElement("canvas"),v=y.getContext("2d",{willReadFrequently:!0}));const r=new Image;w(i,(()=>r.src=""));return V(yield new Promise(((e,s)=>{r.onload=()=>{g(i)||e(r),URL.revokeObjectURL(r.src),r.onload=null},r.onerror=()=>s(new Error("Could not load image.")),r.src=t.size?URL.createObjectURL(t):""})),e,y,v)}))};function V(t,e,i,r){if(i.width=t.width,i.height=t.height,!r)throw new Error("failed to get context");r.drawImage(t,0,0,t.width,t.height);const s=r.getImageData(0,0,t.width,t.height).data;return M(t.width,t.height,e,s)}function M(t,e,i,r){const s="mapbox"===i?(t,e,i)=>.1*(256*t*256+256*e+i)-1e4:(t,e,i)=>256*t+e+i/256-32768,n=new Float32Array(t*e);for(let t=0;t<r.length;t+=4)n[t/4]=s(r[t],r[t+1],r[t+2]);return{width:t,height:e,data:n}}class S{constructor(t,e,i){this.split=(t,e,i)=>{if(0===t)return this;const r=1<<t,s=e*this.width/r,n=i*this.height/r;return new S(this.width/r,this.height/r,((t,e)=>this.get(t+s,e+n)))},this.subsamplePixelCenters=t=>{const e=(t,e,i)=>isNaN(t)?e:isNaN(e)?t:t+(e-t)*i;if(t<=1)return this;const i=.5-1/(2*t);return new S(this.width*t,this.height*t,((r,s)=>{const n=r/t-iLine truncated
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Steve Vigneau
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"use strict";var protomaps_themes_base=(()=>{var f=Object.defineProperty;var D=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames,x=Object.getOwnPropertySymbols;var j=Object.prototype.hasOwnProperty,$=Object.prototype.propertyIsEnumerable;var z=(a,e,i)=>e in a?f(a,e,{enumerable:!0,configurable:!0,writable:!0,value:i}):a[e]=i,b=(a,e)=>{for(var i in e||(e={}))j.call(e,i)&&z(a,i,e[i]);if(x)for(var i of x(e))$.call(e,i)&&z(a,i,e[i]);return a};var l=(a,e)=>f(a,"name",{value:e,configurable:!0});var C=(a,e)=>{for(var i in e)f(a,i,{get:e[i],enumerable:!0})},P=(a,e,i,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of T(e))!j.call(a,r)&&r!==i&&f(a,r,{get:()=>e[r],enumerable:!(n=D(e,r))||n.enumerable});return a};var A=a=>P(f({},"__esModule",{value:!0}),a);var W={};C(W,{default:()=>S,labels:()=>I,labelsWithCustomTheme:()=>K,language_script_pairs:()=>m,layers:()=>B,layersWithCustomTheme:()=>V,layersWithPartialCustomTheme:()=>F,namedTheme:()=>H,noLabels:()=>E,noLabelsWithCustomTheme:()=>G});function t(a,e){let i="script";return a==="name"?i="script":a==="name2"?i="script2":a==="name3"&&(i="script3"),[["coalesce",["get",`pgf:${a}`],["get",a]],{"text-font":["case",["==",["get",i],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[e||"Noto Sans Regular"]]]}]}l(t,"get_name_block");function c(a,e,i){let n="name";return i==="name"?n="":i==="name2"?n="2":i==="name3"&&(n="3"),e==="Latin"?["has",`script${n}`]:a==="ja"?["all",["!=",["get",`script${n}`],"Han"],["!=",["get",`script${n}`],"Hiragana"],["!=",["get",`script${n}`],"Katakana"],["!=",["get",`script${n}`],"Mixed-Japanese"]]:["!=",["get",`script${n}`],e]}l(c,"is_not_in_target_script");function o(a){return a==="Devanagari"?{"text-font":["literal",["Noto Sans Devanagari Regular v1"]]}:{}}l(o,"get_font_formatting");function v(a){let e=m.find(i=>i.lang===a);return e===void 0?"Latin":e.script}l(v,"get_default_script");function L(a,e){let i=e||v(a),n;return i==="Devanagari"?n="pgf:":n="",["format",["coalesce",["get",`${n}name:${a}`],["get","name:en"]],o(i)]}l(L,"get_country_name");function s(a,e,i){let n=e||v(a),r;return n==="Devanagari"?r="pgf:":r="",["case",["all",["any",["has","name"],["has","pgf:name"]],["!",["any",["has","name2"],["has","pgf:name2"]]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",c(a,n,"name"),["case",["any",["is-supported-script",["get","name"]],["has","pgf:name"]],["format",["coalesce",["get",`${r}name:${a}`],["get","name:en"]],o(n),`
|
|
||||||
`,{},["case",["all",["!",["has",`${r}name:${a}`]],["has","name:en"],["!",["has","script"]]],"",["coalesce",["get","pgf:name"],["get","name"]]],{"text-font":["case",["==",["get","script"],"Devanagari"],["literal",["Noto Sans Devanagari Regular v1"]],["literal",[i||"Noto Sans Regular"]]]}],["get","name:en"]],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n)]],["all",["any",["has","name"],["has","pgf:name"]],["any",["has","name2"],["has","pgf:name2"]],["!",["any",["has","name3"],["has","pgf:name3"]]]],["case",["all",c(a,n,"name"),c(a,n,"name2")],["format",["get",`${r}name:${a}`],o(n),`
|
|
||||||
`,{},...t("name"),`
|
|
||||||
`,{},...t("name2")],["case",c(a,n,"name2"),["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n),`
|
|
||||||
`,{},...t("name2")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name2"],["get","name2"]],o(n),`
|
|
||||||
`,{},...t("name")]]],["case",["all",c(a,n,"name"),c(a,n,"name2"),c(a,n,"name3")],["format",["get",`${r}name:${a}`],o(n),`
|
|
||||||
`,{},...t("name"),`
|
|
||||||
`,{},...t("name2"),`
|
|
||||||
`,{},...t("name3")],["case",["!",c(a,n,"name")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name"],["get","name"]],o(n),`
|
|
||||||
`,{},...t("name2"),`
|
|
||||||
`,{},...t("name3")],["!",c(a,n,"name2")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name2"],["get","name2"]],o(n),`
|
|
||||||
`,{},...t("name"),`
|
|
||||||
`,{},...t("name3")],["format",["coalesce",["get",`${r}name:${a}`],["get","pgf:name3"],["get","name3"]],o(n),`
|
|
||||||
`,{},...t("name"),`
|
|
||||||
`,{},...t("name2")]]]]}l(s,"get_multiline_name");var m=[{lang:"ar",full_name:"Arabic",script:"Arabic"},{lang:"cs",full_name:"Czech",script:"Latin"},{lang:"bg",full_name:"Bulgarian",script:"Cyrillic"},{lang:"da",full_name:"Danish",script:"Latin"},{lang:"de",full_name:"German",script:"Latin"},{lang:"el",full_name:"Greek",script:"Greek"},{lang:"en",full_name:"English",script:"Latin"},{lang:"es",full_name:"Spanish",script:"Latin"},{lang:"et",full_name:"Estonian",script:"Latin"},{lang:"fa",full_name:"Persian",script:"Arabic"},{lang:"fi",full_name:"Finnish",script:"Latin"},{lang:"fr",full_name:"French",script:"Latin"},{lang:"ga",full_name:"Irish",script:"Latin"},{lang:"he",full_name:"Hebrew",script:"Hebrew"},{lang:"hi",full_name:"Hindi",script:"Devanagari"},{lang:"hr",full_name:"Croatian",script:"Latin"},{lang:"hu",full_name:"Hungarian",script:"Latin"},{lang:"id",full_name:"Indonesian",script:"Latin"},{lang:"it",full_name:"Italian",script:"Latin"},{lang:"ja",full_name:"Japanese",script:""},{lang:"ko",full_name:"Korean",script:"Hangul"},{lang:"lt",full_name:"Lithuanian",script:"Latin"},{lang:"lv",full_name:"Latvian",script:"Latin"},{lang:"ne",full_name:"Nepali",script:"Devanagari"},{lang:"nl",full_name:"Dutch",script:"Latin"},{lang:"no",full_name:"Norwegian",script:"Latin"},{lang:"mr",full_name:"Marathi",script:"Devanagari"},{lang:"mt",full_name:"Maltese",script:"Latin"},{lang:"pl",full_name:"Polish",script:"Latin"},{lang:"pt",full_name:"Portuguese",script:"Latin"},{lang:"ro",full_name:"Romanian",script:"Latin"},{lang:"ru",full_name:"Russian",script:"Cyrillic"},{lang:"sk",full_name:"Slovak",script:"Latin"},{lang:"sl",full_name:"Slovenian",script:"Latin"},{lang:"sv",full_name:"Swedish",script:"Latin"},{lang:"tr",full_name:"Turkish",script:"Latin"},{lang:"uk",full_name:"Ukrainian",script:"Cyrillic"},{lang:"ur",full_name:"Urdu",script:"Arabic"},{lang:"vi",full_name:"Vietnamese",script:"Latin"},{lang:"zh-Hans",full_name:"Chinese (Simplified)",script:"Han"},{lang:"zh-Hant",full_name:"Chinese (Traditional)",script:"Han"}];function _(a,e){return[{id:"background",type:"background",paint:{"background-color":e.background}},{id:"earth",type:"fill",filter:["==","$type","Polygon"],source:a,"source-layer":"earth",paint:{"fill-color":e.earth}},...e.landcover?[{id:"landcover",type:"fill",source:a,"source-layer":"landcover",paint:{"fill-color":["match",["get","kind"],"grassland",e.landcover.grassland,"barren",e.landcover.barren,"urban_area",e.landcover.urban_area,"farmland",e.landcover.farmland,"glacier",e.landcover.glacier,"scrub",e.landcover.scrub,e.landcover.forest],"fill-opacity":["interpolate",["linear"],["zoom"],5,1,7,0]}}]:[],{id:"landuse_park",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course","wood","nature_reserve","forest","scrub","grassland","grass","military","naval_base","airfield"],paint:{"fill-opacity":["interpolate",["linear"],["zoom"],6,0,11,1],"fill-color":["case",["in",["get","kind"],["literal",["national_park","park","cemetery","protected_area","nature_reserve","forest","golf_course"]]],e.park_b,["in",["get","kind"],["literal",["wood","nature_reserve","forest"]]],e.wood_b,["in",["get","kind"],["literal",["scrub","grassland","grass"]]],e.scrub_b,["in",["get","kind"],["literal",["glacier"]]],e.glacier,["in",["get","kind"],["literal",["sand"]]],e.sand,["in",["get","kind"],["literal",["military","naval_base","airfield"]]],e.zoo,e.earth]}},{id:"landuse_urban_green",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","allotments","village_green","playground"],paint:{"fill-color":e.park_b,"fill-opacity":.7}},{id:"landuse_hospital",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","hospital"],paint:{"fill-color":e.hospital}},{id:"landuse_industrial",type:"fill",source:a,"source-layer":"landuse",filter:["==","kind","industrial"],paint:{"fill-color":e.industrial}},{id:"landuse_school",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","school","university","college"],paint:{"fill-color":e.school}},{id:"landuse_beach",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","beach"],paint:{"fill-color":e.beach}},{id:"landuse_zoo",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","zoo"],paint:{"fill-color":e.zoo}},{id:"landuse_aerodrome",type:"fill",source:a,"source-layer":"landuse",filter:["in","kind","aerodrome"],paint:{"fill-color":e.aerodrome}},{id:"roads_runway",type:"line",source:a,"source-layer":"roads",filter:["==","kind_detail","runway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],10,0,12,4,18,30]}},{id:"roads_taxiway",type:"line",source:a,"source-layer":"roads",minzoom:13,filter:["==","kind_detail","taxiway"],paint:{"line-color":e.runway,"line-width":["interpolate",["exponential",1.6],["zoom"],13,0,13.5,1,15,6]}},{id:"landuse_runway",type:"fill",source:a,"source-layer":"landuse",filter:["any",["in
Line truncated
|
|
||||||
//# sourceMappingURL=protomaps-themes-base.js.map
|
|
||||||