4 Commits
Author SHA1 Message Date
thepraandClaude Opus 5.5 50eaf7d0b3 "me-web?" 0.14.1: the glow around the path is round again
Build / Desktop, server and tools (push) Successful in 1m15s
Release / Every platform, the server, the release (push) Successful in 10m44s
- The soft glow around each step of the path from your device to the internet was cut off straight at the edge of
  the path. It now fades out evenly all the way round, and so does the pulse while a step is being checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 02:10:54 +02:00
thepraandClaude Opus 5.5 3d6315fe1e "me-web?" 0.14.0: the first three cards, drawn again
Build / Desktop, server and tools (push) Successful in 1m15s
Release / Every platform, the server, the release (push) Successful in 10m13s
- The three pictures at the top are new: a small planet at dusk under twinkling stars, with town lights on its night
  side, drifting clouds, smoke from the chimney and trees that sway.
- The house and the block of flats now have lit walls, a tiled roof, glowing windows and soft shadows.
- The photo now flies like a little print and the visitor is a letter, because a letter needs an address. Each flies
  along its route with a glowing trail. At the house the door lights up green; at the block of flats the letter stops
  at the main door while the windows light up one by one, and a question mark appears.
- The path from your device to the internet glows in each step's colour. While a step is checked, an arc circles it
  and small lights travel along the wires, and a ring spreads out when its answer arrives.
- In right-to-left languages the small picture badge stays in its corner instead of covering the drawing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 01:49:41 +02:00
thepraandClaude Opus 5.5 91c92c80e9 "me-web?" 0.13.0: updates on Linux arrive through your package manager
Build / Desktop, server and tools (push) Successful in 1m34s
Release / Every platform, the server, the release (push) Successful in 12m28s
- Installing the deb or the rpm now also adds the Arasaka package repository, packages.arasaka.software, so later
  versions of “me-web?” arrive through apt, dnf, zypper or your software centre, like any other program.
- The rpm is now signed. On openSUSE, import the key once with
  sudo rpm --import https://packages.arasaka.software/arasaka.asc
- Removing the package keeps the repository until no Arasaka package is left (purge on Debian and Ubuntu); a
  repository file you edited by hand is never touched.
- The download and privacy pages say so; the repository server keeps no access log.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-16 22:05:08 +02:00
thepraandClaude Opus 5.5 50c40195d3 "me-web?" 0.12.0: every address block travels with the app
Build / Desktop, server and tools (push) Successful in 1m15s
Release / Every platform, the server, the release (push) Successful in 26m55s
- The app now carries the whole RIPE Database list of address blocks, all five million, small single-customer blocks
  included, so the provider card and the path can name the block your connection belongs to without a connection to
  the publisher's server. The downloads are about 25 MB larger for it.
- When the publisher's server can't be reached, the provider card still names your network and its block, from the
  public address your router reports or the one the STUN test sees.
- The table is read a small piece at a time, so it takes almost no memory on a phone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CzABvBkbcFqoHdmi8b9WB
2026-10-16 19:41:56 +02:00
31 changed files with 1691 additions and 583 deletions

No files matched your search

+1
View File
@@ -7,6 +7,7 @@ for attempt in $(seq 1 240); do
for file in ip2asn.bin networks.bin carriers.json registry.bin; do for file in ip2asn.bin networks.bin carriers.json registry.bin; do
case "$body" in *"\"$file\""*) ;; *) missing="$missing $file" ;; esac case "$body" in *"\"$file\""*) ;; *) missing="$missing $file" ;; esac
done done
case "$body" in *'"format":2,'*) ;; *) missing="$missing format-2" ;; esac
if [ -n "$body" ] && [ -z "$missing" ]; then if [ -n "$body" ] && [ -z "$missing" ]; then
echo "network data ready: $(printf '%s' "$body" | head -c 200)" echo "network data ready: $(printf '%s' "$body" | head -c 200)"
exit 0 exit 0
+10
View File
@@ -43,6 +43,16 @@ jobs:
MEWEB_KEYSTORE_PASS: ${{ secrets.MEWEB_KEYSTORE_PASS }} MEWEB_KEYSTORE_PASS: ${{ secrets.MEWEB_KEYSTORE_PASS }}
run: .gitea/scripts/android.sh run: .gitea/scripts/android.sh
- name: The deb and rpm on packages.arasaka.software, the rpm signed
if: ${{ github.ref_type == 'tag' }}
env:
YOLO_TOKEN: ${{ secrets.YOLO_TOKEN }}
YOLO_GITEA: http://localhost:3000
YOLO_MIN: "0.1.0"
run: |
yolo repo submit --project meweb --version "$VERSION" --replace-signed "releases/$VERSION/MeWeb-$VERSION-linux-x64.deb" "releases/$VERSION/MeWeb-$VERSION-linux-x64.rpm"
yolo sums "releases/$VERSION"
- name: Publish the release - name: Publish the release
if: ${{ github.ref_type == 'tag' || inputs.publish == 'true' }} if: ${{ github.ref_type == 'tag' || inputs.publish == 'true' }}
env: env:
+33 -11
View File
@@ -62,7 +62,7 @@ The Android release key: alias `meweb`, certificate SHA-256
|---|---| |---|---|
| `src/MeWeb.Shared` | `Endpoints.cs` (routes and hosts), wire contracts, `MeWebJsonContext`, generated `BuildInfo` | | `src/MeWeb.Shared` | `Endpoints.cs` (routes and hosts), wire contracts, `MeWebJsonContext`, generated `BuildInfo` |
| `src/MeWeb.Core` | the checks, no UI: `Net/` (address kinds, local network, TTL route, STUN, the probe's `Answerer`, DNS, speed, the device), `Router/` (UPnP IGD, NAT-PMP, PCP; `PortMapper` opens and removes mappings, `Leftovers` remembers them across a crash), `Mail/` (port 25, reverse name, blocklists through the user's resolver), `Knowledge/` (provider map and profiles), `History/` (checks, shared submissions), `Checks/` (`CheckRunner`, `Verdicts`, `Reach`, `Uses`, `ServerProbe`, `SharePayload`) | | `src/MeWeb.Core` | the checks, no UI: `Net/` (address kinds, local network, TTL route, STUN, the probe's `Answerer`, DNS, speed, the device), `Router/` (UPnP IGD, NAT-PMP, PCP; `PortMapper` opens and removes mappings, `Leftovers` remembers them across a crash), `Mail/` (port 25, reverse name, blocklists through the user's resolver), `Knowledge/` (provider map and profiles), `History/` (checks, shared submissions), `Checks/` (`CheckRunner`, `Verdicts`, `Reach`, `Uses`, `ServerProbe`, `SharePayload`) |
| `src/MeWeb.App` | Avalonia views and view models, `Services/Localizer.cs` (+ `{s:Tr Key}`), `Strings/<code>.json`, `Resources/Colors.axaml`; `Resources/Icons.axaml` (Lucide, 24 units, for cards and lists) and `Resources/Art.axaml` (the path's drawings, 48 units: `Art<Name>Base`, up to three `Beat`s that pulse in turn while the step is checked and take its verdict colour, and a `Spin` for the globe). The path is a vertical zig-zag of `PathNodeViewModel`s, and the same nodes carry the result cards (`Cards`, regrouped by `MainViewModel.Home`), so a step in the path and its group of cards are one object | | `src/MeWeb.App` | Avalonia views and view models, `Services/Localizer.cs` (+ `{s:Tr Key}`), `Strings/<code>.json`, `Resources/Colors.axaml`; `Resources/Icons.axaml` (Lucide, 24 units, for cards and lists) and `Resources/Art.axaml` (the path's drawings, 48 units: `Art<Name>Base`, up to three `Beat`s that pulse in turn while the step is checked and take its verdict colour, and a `Spin` for the globe; then the intro planet's still pieces as `DrawingImage`s with a fixed `Viewbox`: `ArtHouse`, `ArtBlock`, `ArtPine`, `ArtOak`, `ArtPhoto`, `ArtLetter`, `ArtServer`). The path is a vertical zig-zag of `PathNodeViewModel`s, and the same nodes carry the result cards (`Cards`, regrouped by `MainViewModel.Home`), so a step in the path and its group of cards are one object |
| `src/MeWeb.Desktop` | desktop head (`meweb` executable), Windows manifest | | `src/MeWeb.Desktop` | desktop head (`meweb` executable), Windows manifest |
| `src/MeWeb.Android` | Android head (`software.arasaka.meweb`), resources, manifest | | `src/MeWeb.Android` | Android head (`software.arasaka.meweb`), resources, manifest |
| `src/MeWeb.Server` | `meweb.arasaka.software`: `/api/info`, `/api/ip`, the download page `/` and `/privacy` (en, it), `/download/{platform}` from the latest Gitea release, the probe (`/api/probe/ticket`, `/api/probe/{id}`) and shared findings (`/api/share`, files under `/var/lib/meweb/shares`, swept after `KeepDays`) | | `src/MeWeb.Server` | `meweb.arasaka.software`: `/api/info`, `/api/ip`, the download page `/` and `/privacy` (en, it), `/download/{platform}` from the latest Gitea release, the probe (`/api/probe/ticket`, `/api/probe/{id}`) and shared findings (`/api/share`, files under `/var/lib/meweb/shares`, swept after `KeepDays`) |
@@ -72,7 +72,7 @@ The Android release key: alias `meweb`, certificate SHA-256
| `data/observed.json` | notes counted from shared findings and accepted by a person: per AS number and line (`home`, `mobile`), IPv4 shared or public, IPv6 offered or not, port 25, each with how many networks agree; embedded in every release | | `data/observed.json` | notes counted from shared findings and accepted by a person: per AS number and line (`home`, `mobile`), IPv4 shared or public, IPv6 offered or not, port 25, each with how many networks agree; embedded in every release |
| `src/MeWeb.Server/ObservedNotes.cs` | every six hours (with the share sweep) counts the shared findings into `/var/lib/meweb/observed.json` on the box: one vote per /24 or /48, the latest check of each network, the publisher's own network left out; nothing is served | | `src/MeWeb.Server/ObservedNotes.cs` | every six hours (with the share sweep) counts the shared findings into `/var/lib/meweb/observed.json` on the box: one vote per /24 or /48, the latest check of each network, the publisher's own network left out; nothing is served |
| `tools/observed.cs` | `show` prints that count, `accept <file> AS<n>/<line> ...` copies the ready notes (at least 5 networks, 4 in 5 agreeing) into `data/observed.json` | | `tools/observed.cs` | `show` prints that count, `accept <file> AS<n>/<line> ...` copies the ready notes (at least 5 networks, 4 in 5 agreeing) into `data/observed.json` |
| `src/MeWeb.Server/KnowledgeFeeder.cs` | refreshes the open network data under `/var/lib/meweb/knowledge` every six hours (iptoasn daily, DB-IP monthly, eight RouteViews RIBs weekly through `bzcat` (one or two collectors per continent, `Collectors`; each counts once per network in `UpstreamTally`, and a path's first AS, the collector's own neighbour, is never taken for an upstream), the AOSP carrier list weekly, RIPE's `inetnum` and `inet6num` split files weekly), builds `ip2asn.bin`, `networks.bin`, `carriers.json`, `registry.bin` and `SOURCES.txt` with `MeWeb.Core/Knowledge/Sources.cs`, and serves them at `/api/knowledge`; it also writes `registry-full.bin` (`RegistryIndex`), every RIPE block, which never leaves the box | | `src/MeWeb.Server/KnowledgeFeeder.cs` | refreshes the open network data under `/var/lib/meweb/knowledge` every six hours (iptoasn daily, DB-IP monthly, eight RouteViews RIBs weekly through `bzcat` (one or two collectors per continent, `Collectors`; each counts once per network in `UpstreamTally`, and a path's first AS, the collector's own neighbour, is never taken for an upstream), the AOSP carrier list weekly, RIPE's `inetnum` and `inet6num` split files weekly), builds `ip2asn.bin`, `networks.bin`, `carriers.json`, `registry.bin` (every RIPE block, `RegistryBuilder`) and `SOURCES.txt` with `MeWeb.Core/Knowledge/Sources.cs`, and serves them at `/api/knowledge` |
| `tools/knowledge.cs` | takes the server's knowledge files (`MEWEB_KNOWLEDGE_URL`, default the public server; the release sets `http://127.0.0.1:5480` after deploying the server first) into `artifacts/knowledge/`, checking each SHA-256, and falls back to building `ip2asn.bin` alone from iptoasn (`--local` forces that); `MeWeb.Core` embeds whatever is there; `tools/release.cs` runs it before every app build | | `tools/knowledge.cs` | takes the server's knowledge files (`MEWEB_KNOWLEDGE_URL`, default the public server; the release sets `http://127.0.0.1:5480` after deploying the server first) into `artifacts/knowledge/`, checking each SHA-256, and falls back to building `ip2asn.bin` alone from iptoasn (`--local` forces that); `MeWeb.Core` embeds whatever is there; `tools/release.cs` runs it before every app build |
| `tools/setup.cs` + `setup.json` | pinned build tools | | `tools/setup.cs` + `setup.json` | pinned build tools |
| `tools/Screenshots` | headless renders of every view in every language | | `tools/Screenshots` | headless renders of every view in every language |
@@ -129,14 +129,27 @@ The Android release key: alias `meweb`, certificate SHA-256
persons, contacts, `descr` or `remarks`), RIPE credited with a link, the extract's date and a note that it may lag; persons, contacts, `descr` or `remarks`), RIPE credited with a link, the extract's date and a note that it may lag;
`SOURCES.txt` (the release notes) and `RegistryCredit` carry that. The RIR's own allocations (`ALLOCATED PA`, `SOURCES.txt` (the release notes) and `RegistryCredit` carry that. The RIR's own allocations (`ALLOCATED PA`,
`ALLOCATED UNSPECIFIED`, `ALLOCATED-BY-RIR`) are skipped: their names are registry-made (`IT-FASTWEB-20101230`). `ALLOCATED UNSPECIFIED`, `ALLOCATED-BY-RIR`) are skipped: their names are registry-made (`IT-FASTWEB-20101230`).
- **No block is left out, but the apps cannot carry all of them.** Every RIPE block compresses to about 24 MB and took - **Every RIPE block travels with the app, because the app works offline.** The owner, 16 October 2026: "this is an
2.2 GB to build, so `registry.bin` (about 0.7 MB) holds the blocks of 2,048+ IPv4 addresses or a /40, plus those of offline first capable app it needs to hold value even in absence of connection". `registry.bin` holds all five
256+ or a /48 whose name recurs (a provider's pool label, `FASTWEB-L3-PAT_NAT`); `RegistryIndex` keeps all five million blocks, flattened so the narrowest one wins (`RegistryTable.Narrowest`), in Brotli chunks of 16,384 rows
million on disk (about 160 MB, binary search with `RandomAccess`, nothing resident) and `/api/ip` answers each behind an index: about 26 MB, opened in a millisecond, one chunk decompressed per lookup, nothing else resident. It
caller with the block of the address it sees, never another. The app prefers that answer and falls back to the is not inside `MeWeb.Core`, because the APK stores every assembly once per processor type: the desktop head and the
embedded table. `RegistryIndex.Build` reads IPv4 and then IPv6 and streams the flattened ranges out screenshot tool embed it in their entry assembly, the Android head ships it as an uncompressed asset
(`RegistryTable.Narrowest`, the narrowest block wins): about 280 MB peak, under the unit's 512 MB. The first build (`AndroidStoreUncompressedFileExtensions`) that `AndroidPlatform.OpenRegistry` reads in place through `OpenFd`.
after a deploy takes some fifteen minutes, so `wait-knowledge.sh` waits for `registry.bin` in the manifest. `RegistryBuilder` reads IPv4, then IPv6, with the names in a temporary file: about 200 MB peak, under the unit's
512 MB, and some three minutes on top of the RouteViews tally. `/api/ip` still answers each caller with its own
block from the server's newer copy; without that answer the app looks the address up itself, and when the server
cannot be reached at all it uses the router's public outside address or the STUN answer, so the provider card names
the network and the block without the publisher's server. A change to any knowledge file's format bumps
`KnowledgeFeeder.Format` and the `"format":N` that `wait-knowledge.sh` waits for: the feeder rebuilds only when a
source changes or the format differs, and a release must not embed the previous build.
- **The deb and rpm reach packages.arasaka.software through YoloCICD.** `release.yml` runs `yolo repo submit
--replace-signed` on tags (org secret `YOLO_TOKEN`, `yolo` installed on nuvola by YoloCICD): the files go to the
bot's private inbox, YoloCICD's `publish.yml` checks this run against the tag, signs the rpm, gates on an upgrade
check in containers and publishes; the signed rpm replaces the local one, then `yolo sums` rewrites SHA256SUMS.
`packaging/arasaka-repository.sh` is generated (`yolo repo kit --out`, never edited by hand) and is written into
both maintainer scripts by `tools/release.cs`; the package owns `/usr/share/arasaka-repository/meweb`, the marker
that keeps the repository configured while any Arasaka package is installed.
- **The server cannot ping**: `NoNewPrivileges` drops `ping`'s file capability and `ping_group_range` is closed, so - **The server cannot ping**: `NoNewPrivileges` drops `ping`'s file capability and `ping_group_range` is closed, so
the trace back to a caller uses `tracepath` (UDP, unprivileged) and keeps only public hops outside `Probe:Refuse`, the trace back to a caller uses `tracepath` (UDP, unprivileged) and keeps only public hops outside `Probe:Refuse`,
so the box's own network never reaches an app. so the box's own network never reaches an app.
@@ -192,7 +205,12 @@ The Android release key: alias `meweb`, certificate SHA-256
`Views/IntroScene` is one small planet in three `Step`s: 0, a phone far away sends a photo into the house and on to `Views/IntroScene` is one small planet in three `Step`s: 0, a phone far away sends a photo into the house and on to
the parents' tablet (heart); 1, a house with its own number and a small always-on computer in a callout lets a the parents' tablet (heart); 1, a house with its own number and a small always-on computer in a callout lets a
visitor in (green door, tick); 2, that house next to a block of flats with one number for all, where the visitor visitor in (green door, tick); 2, that house next to a block of flats with one number for all, where the visitor
stops at the main door (question mark). The third card asks whether the reader's home has an address of its own and stops at the main door (question mark). The visitor is a letter, because a letter needs an address; it and the photo
fly along the dotted routes with a comet trail (a gradient stroke whose ends are set each frame in
`RelativeUnit.Absolute`), and the world around them keeps moving (stars, town lights on the night side, clouds,
chimney smoke, swaying trees). The still drawings are `DrawingImage`s in `Art.axaml`; only what moves is a control,
and it moves through its `RenderTransform` (scale, rotate, translate), never `Canvas.SetLeft`, so a frame does not
lay the canvas out again. The third card asks whether the reader's home has an address of its own and
offers two answers: "Yes, let's find out" runs the check, "Not for me" shows `Goodbye` for two seconds and closes the offers two answers: "Yes, let's find out" runs the check, "Not for me" shows `Goodbye` for two seconds and closes the
app (`IPlatform.Quit`, the desktop lifetime's `Shutdown`). The six uses follow as chips, the QR codes wait folded app (`IPlatform.Quit`, the desktop lifetime's `Shutdown`). The six uses follow as chips, the QR codes wait folded
at the bottom. at the bottom.
@@ -203,6 +221,10 @@ The Android release key: alias `meweb`, certificate SHA-256
is the frame loop the scenes share; `Resting` parks a scene that is folded or off screen, so it starts again from the is the frame loop the scenes share; `Resting` parks a scene that is folded or off screen, so it starts again from the
beginning when it comes back. The screenshot tool calls `Hold()` to draw each at its `Poster` moment, because the beginning when it comes back. The screenshot tool calls `Hold()` to draw each at its `Poster` moment, because the
headless clock is real time. headless clock is real time.
- **Fluent's `Button` and `ItemsControl` both clip to their own bounds.** The path's verdict aura and running pulse
reach past the 90-pixel node button, and the nodes sit on the path `ItemsControl`'s left and right edges, so both
turn `ClipToBounds` off (`Button.hop` with its `ContentPresenter`, and the path's `ItemsControl`). With either one
left on, the glow is cut straight; 0.14.0 shipped with the `ItemsControl` cut.
- **Nothing that is being checked is left empty.** From the moment a check starts, every use has a card with its - **Nothing that is being checked is left empty.** From the moment a check starts, every use has a card with its
title, a "Checking" chip, two skeleton lines and a sweeping glint (`UseViewModel` with no finding), and every title, a "Checking" chip, two skeleton lines and a sweeping glint (`UseViewModel` with no finding), and every
running check card shows the same; the verdicts replace them as they arrive. While a check runs, the share card sits running check card shows the same; the verdicts replace them as they arrive. While a check runs, the share card sits
+1 -1
View File
@@ -1,6 +1,6 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<Version>0.11.0</Version> <Version>0.14.1</Version>
<Product>“me-web?”</Product> <Product>“me-web?”</Product>
<Company>Arasaka di Hvozdikov Yevheniy</Company> <Company>Arasaka di Hvozdikov Yevheniy</Company>
<Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright> <Copyright>© 2026 Arasaka di Hvozdikov Yevheniy</Copyright>
+10 -5
View File
@@ -47,6 +47,12 @@ Planned: the builds for Mac announced once they have been tried on a real Mac.
Linux (deb, rpm), Windows, macOS and Android. Builds are published as releases of this repository. Linux (deb, rpm), Windows, macOS and Android. Builds are published as releases of this repository.
On Linux the deb and the rpm add the Arasaka package repository, packages.arasaka.software, when they are installed,
so later versions arrive through the package manager. The rpm is signed with the repository's key; on openSUSE,
import it first with `sudo rpm --import https://packages.arasaka.software/arasaka.asc`. The script the packages run
is `packaging/arasaka-repository.sh`, generated by YoloCICD (`yolo repo kit`); it never calls the package manager,
leaves files you edited alone, and removes the repository only when no Arasaka package is left (purge or erase).
## Privacy ## Privacy
“me-web?” sends nothing anywhere unless a check needs it, and lists every host it contacts ("What this app contacts" “me-web?” sends nothing anywhere unless a check needs it, and lists every host it contacts ("What this app contacts"
@@ -74,11 +80,10 @@ The APK is signed with the key whose certificate SHA-256 is
- mobile operators by network code: the Android Open Source Project's carrier list, Apache License 2.0; - mobile operators by network code: the Android Open Source Project's carrier list, Apache License 2.0;
- the name of the block of addresses a connection belongs to: the [RIPE Database](https://www.ripe.net/manage-ips-and-asns/db/) - the name of the block of addresses a connection belongs to: the [RIPE Database](https://www.ripe.net/manage-ips-and-asns/db/)
of the RIPE NCC, used with the RIPE NCC's permission (16 October 2026) on condition that it is never put behind of the RIPE NCC, used with the RIPE NCC's permission (16 October 2026) on condition that it is never put behind
a paywall or used to make money. Only each block's range, name and country are kept, nothing about people. The a paywall or used to make money. Only each block's range, name and country are kept, nothing about people.
apps carry every block of at least 2,048 IPv4 addresses or a /40 in IPv6, and the smaller ones, down to 256 Every block travels with the apps (about 26 MB), so the name is there without a connection; the server also
addresses or a /48, whose name recurs; the server keeps every block and tells each app only the block of the tells each app the block of the address it sees, from its own weekly copy. The data may lag behind the live
address it sees. The database; the release notes give its date.
data may lag behind the live database; the release notes give its date.
- Provider notes: each operator's own technical sheet (AGCOM 156/23 in Italy) and users' reports on - Provider notes: each operator's own technical sheet (AGCOM 156/23 in Italy) and users' reports on
[FibraClick](https://forum.fibra.click), each cited in `data/isps/`; and, for any country, totals from findings [FibraClick](https://forum.fibra.click), each cited in `data/isps/`; and, for any country, totals from findings
people chose to share (`data/observed.json`), published only after a person has reviewed them and only where at people chose to share (`data/observed.json`), published only after a person has reviewed them and only where at
+126
View File
@@ -0,0 +1,126 @@
arasaka_repository_url='https://packages.arasaka.software'
arasaka_repository_known=' 39c1037ea150a946870135d45c7e0a150b6cf07612961ab7d4ebf7356e3d6206 8e98c23290cd1afa016eed46b6bc6643a44f07d1d398b4c74221891a3dee0ef1 f8a29b044c4b538af072041b201e31cbcc6328f709c3b960fbfccef819cb2572 fe762297efbcb217cbfa51ac28dce057695205bd46f59bc69369d92765edebfc '
arasaka_repository_sources='Types: deb
URIs: https://packages.arasaka.software/apt
Suites: stable
Components: main
Architectures: amd64
Signed-By:
-----BEGIN PGP PUBLIC KEY BLOCK-----
.
mQINBGrSft8BEAC02AviPvhvjkSZooT5/n9pNBivqRr2xwr75RVLaPh9qhYV3X5R
fyYjvMatwqqDvF83JqUjkvweTxTu+GpvkFNuvBTF5ZbOdzp5GLo/4LzLmCP1dNsP
WkIGkDEa4b+VzuOYc1jglIncfmcnoEj8nufPXsCINTQcWv1WSB5VhWjdovdZDSHI
TSZs01BmaTupQhbDelpv4r52YnnH4god25QcImgdSF8aO4bYCeMO3NFoQfSBcnou
tDQjJ7JhSSCX+/K2hzbgGDhep2DL9ndsNZuUN5GokwQUM5cVsyoR/A2VdwkWvzLl
ib9IwngTjVRLCAm+XkPhL/0GHBtV+oOz4s+RWKtGC2qmWMXUfdjOyLsxAIGy8wgc
dcvBoK77INSTccYUGCDAGOoQHnD8USNjTCOcsajHy/FpaNxihrjq1C16LXKEMEIw
eK75YbWP8BzClEKZ7wd+0QMlo4CNJEE6mVEZe1R+yC8FEmP86qQR2wpG8IBrYwln
+IyYIII1GLDUewjRcwo0mYbuvC4LQtDXpm9WrMP9GyAd60glBHPRYhE7GLHDBxNi
CjLQ9vnB+zzmDD7LvIzHNb3t0lG118p1RmazsvyBbPGCC/pIxiofimglRXmawsxs
06dzA57CWprlZEP5/hU9mKxSUYKFboUh3+LRyTwU0/EPM2KQ22yQE3ORcwARAQAB
tCxBcmFzYWthIHBhY2thZ2VzIChwYWNrYWdlcy5hcmFzYWthLnNvZnR3YXJlKYkC
UQQTAQoAOxYhBPksFm6bHBPt0WKedoiNz5adEz+yBQJq0n7fAhsDBQsJCAcCAiIC
BhUKCQgLAgQWAgMBAh4HAheAAAoJEIiNz5adEz+y66oQAJl49Hd0/fuAOjckFkSH
u/LBspsmVO58Sa2QbZn85VGHhmRi2RqrJfIbRXl2SnmbpTE+q4aMSOCAjvodPEh9
JkiU3nl8HXL7e6tVp7VMsC2zk0GRGZ+R3+KaWbb4xCiIF4Gly4vtcQL1u+tRl0GO
5yb5OXAYgGktaBSjMSe/ZFATrb2pbbWtLozpLBrEKJel8kYZg6ogrDepf08KbS5d
ZajlVJltlDu93s7fkGt/+CSlDBRSvIsg6VsD9FaCB1XsfRt9nmldH5DeKsPCiohn
d7KeYPntE0fr+x1NM5hKolZs6E7N3Z7iHHVIt7wzfl35EnJyzGSGUXWAT6dYrN1o
VzkHzQS+KWzAhFnfZtQFHcwZxSTXpX4Q0ORshs8aO7Pzb1Uq4jWYBwoW7/rEV3si
1H4BEol8/MtmsdjC9udDSZWMpu4JVZey2y5J+tnjuHJO4kgj5bVrmOGWvy0dTCu4
ute3grHGR7wrjxsNUdUljsUu/q85+rJ0wx7GmmKlwISGmGr/LaHPza25aAQwzjQE
rN+vQB98iHplrv+6KM5lfs+wTh5ajrHTV3yW5aoDWHHJDFWrTzWu18enrUxwlF5k
o6rC8YtE28cCw4XQHP4blvJEwZWhln61IFDvyOQsQhdJPaSTVHnLZj8Q79KH8Odc
r2UH5jw/3noMMPLA+GMyfUdq
=FhjR
-----END PGP PUBLIC KEY BLOCK-----
'
arasaka_repository_key='-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGrSft8BEAC02AviPvhvjkSZooT5/n9pNBivqRr2xwr75RVLaPh9qhYV3X5R
fyYjvMatwqqDvF83JqUjkvweTxTu+GpvkFNuvBTF5ZbOdzp5GLo/4LzLmCP1dNsP
WkIGkDEa4b+VzuOYc1jglIncfmcnoEj8nufPXsCINTQcWv1WSB5VhWjdovdZDSHI
TSZs01BmaTupQhbDelpv4r52YnnH4god25QcImgdSF8aO4bYCeMO3NFoQfSBcnou
tDQjJ7JhSSCX+/K2hzbgGDhep2DL9ndsNZuUN5GokwQUM5cVsyoR/A2VdwkWvzLl
ib9IwngTjVRLCAm+XkPhL/0GHBtV+oOz4s+RWKtGC2qmWMXUfdjOyLsxAIGy8wgc
dcvBoK77INSTccYUGCDAGOoQHnD8USNjTCOcsajHy/FpaNxihrjq1C16LXKEMEIw
eK75YbWP8BzClEKZ7wd+0QMlo4CNJEE6mVEZe1R+yC8FEmP86qQR2wpG8IBrYwln
+IyYIII1GLDUewjRcwo0mYbuvC4LQtDXpm9WrMP9GyAd60glBHPRYhE7GLHDBxNi
CjLQ9vnB+zzmDD7LvIzHNb3t0lG118p1RmazsvyBbPGCC/pIxiofimglRXmawsxs
06dzA57CWprlZEP5/hU9mKxSUYKFboUh3+LRyTwU0/EPM2KQ22yQE3ORcwARAQAB
tCxBcmFzYWthIHBhY2thZ2VzIChwYWNrYWdlcy5hcmFzYWthLnNvZnR3YXJlKYkC
UQQTAQoAOxYhBPksFm6bHBPt0WKedoiNz5adEz+yBQJq0n7fAhsDBQsJCAcCAiIC
BhUKCQgLAgQWAgMBAh4HAheAAAoJEIiNz5adEz+y66oQAJl49Hd0/fuAOjckFkSH
u/LBspsmVO58Sa2QbZn85VGHhmRi2RqrJfIbRXl2SnmbpTE+q4aMSOCAjvodPEh9
JkiU3nl8HXL7e6tVp7VMsC2zk0GRGZ+R3+KaWbb4xCiIF4Gly4vtcQL1u+tRl0GO
5yb5OXAYgGktaBSjMSe/ZFATrb2pbbWtLozpLBrEKJel8kYZg6ogrDepf08KbS5d
ZajlVJltlDu93s7fkGt/+CSlDBRSvIsg6VsD9FaCB1XsfRt9nmldH5DeKsPCiohn
d7KeYPntE0fr+x1NM5hKolZs6E7N3Z7iHHVIt7wzfl35EnJyzGSGUXWAT6dYrN1o
VzkHzQS+KWzAhFnfZtQFHcwZxSTXpX4Q0ORshs8aO7Pzb1Uq4jWYBwoW7/rEV3si
1H4BEol8/MtmsdjC9udDSZWMpu4JVZey2y5J+tnjuHJO4kgj5bVrmOGWvy0dTCu4
ute3grHGR7wrjxsNUdUljsUu/q85+rJ0wx7GmmKlwISGmGr/LaHPza25aAQwzjQE
rN+vQB98iHplrv+6KM5lfs+wTh5ajrHTV3yW5aoDWHHJDFWrTzWu18enrUxwlF5k
o6rC8YtE28cCw4XQHP4blvJEwZWhln61IFDvyOQsQhdJPaSTVHnLZj8Q79KH8Odc
r2UH5jw/3noMMPLA+GMyfUdq
=FhjR
-----END PGP PUBLIC KEY BLOCK-----
'
arasaka_repository_dnf='[arasaka]
name=Arasaka
baseurl=https://packages.arasaka.software/rpm/
enabled=1
type=rpm-md
gpgcheck=1
repo_gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-arasaka
priority=150
metadata_expire=6h
'
arasaka_repository_zypp='[arasaka]
name=Arasaka
baseurl=https://packages.arasaka.software/rpm/
enabled=1
autorefresh=1
type=rpm-md
gpgcheck=1
repo_gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-arasaka
priority=150
'
arasaka_repository_write() {
[ -d "${1%/*}" ] || return 0
if [ -f "$1" ]; then
arasaka_repository_old=$(sha256sum "$1" | cut -d ' ' -f 1)
[ "$arasaka_repository_old" = "$(printf '%s' "$2" | sha256sum | cut -d ' ' -f 1)" ] && return 0
case "$arasaka_repository_known" in *" $arasaka_repository_old "*) ;; *) echo "arasaka-repository: $1 was changed by hand, left as it is"; return 0 ;; esac
fi
printf '%s' "$2" > "$1.arasaka-new" && chmod 644 "$1.arasaka-new" && mv -f "$1.arasaka-new" "$1"
}
arasaka_repository_elsewhere() {
grep -rlsF "$arasaka_repository_url" "$@" 2>/dev/null | grep -qvx -e /etc/apt/sources.list.d/arasaka.sources -e /etc/yum.repos.d/arasaka.repo -e /etc/zypp/repos.d/arasaka.repo
}
arasaka_repository_configure() {
case "${1:-}" in
configure)
arasaka_repository_elsewhere /etc/apt/sources.list /etc/apt/sources.list.d && return 0
arasaka_repository_write /etc/apt/sources.list.d/arasaka.sources "$arasaka_repository_sources" ;;
[0-9]*)
mkdir -p /etc/pki/rpm-gpg
arasaka_repository_write /etc/pki/rpm-gpg/RPM-GPG-KEY-arasaka "$arasaka_repository_key"
arasaka_repository_elsewhere /etc/yum.repos.d /etc/zypp/repos.d && return 0
arasaka_repository_write /etc/yum.repos.d/arasaka.repo "$arasaka_repository_dnf"
arasaka_repository_write /etc/zypp/repos.d/arasaka.repo "$arasaka_repository_zypp" ;;
esac
return 0
}
arasaka_repository_cleanup() {
case "${1:-}" in purge|0) ;; *) return 0 ;; esac
[ -n "$(ls -A /usr/share/arasaka-repository 2>/dev/null)" ] && return 0
for arasaka_repository_file in /etc/apt/sources.list.d/arasaka.sources /etc/yum.repos.d/arasaka.repo /etc/zypp/repos.d/arasaka.repo /etc/pki/rpm-gpg/RPM-GPG-KEY-arasaka; do
[ -f "$arasaka_repository_file" ] || continue
case "$arasaka_repository_known" in *" $(sha256sum "$arasaka_repository_file" | cut -d ' ' -f 1) "*) rm -f "$arasaka_repository_file" ;; esac
done
rmdir /usr/share/arasaka-repository 2>/dev/null || true
return 0
}
+17
View File
@@ -7,6 +7,8 @@ using Android.Telephony;
using MeWeb.Core.Net; using MeWeb.Core.Net;
using Microsoft.Win32.SafeHandles;
namespace MeWeb.Android; namespace MeWeb.Android;
public class AndroidPlatform : SystemPlatform public class AndroidPlatform : SystemPlatform
@@ -144,6 +146,21 @@ public class AndroidPlatform : SystemPlatform
} }
} }
public override Stream OpenRegistry()
{
try
{
using var asset = Context.Assets?.OpenFd("registry.bin");
if (asset?.ParcelFileDescriptor is null) return null;
using var copy = asset.ParcelFileDescriptor.Dup();
return new FileStream(new SafeFileHandle(copy.DetachFd(), true), FileAccess.Read) { Position = asset.StartOffset };
}
catch (Exception)
{
return null;
}
}
public override void Quit() public override void Quit()
{ {
MainActivity.Current?.FinishAndRemoveTask(); MainActivity.Current?.FinishAndRemoveTask();
+5
View File
@@ -13,6 +13,7 @@
<ApplicationVersion>$([MSBuild]::Add($([MSBuild]::Multiply($(VersionMajor), 10000)), $([MSBuild]::Add($([MSBuild]::Multiply($(VersionMinor), 100)), $(VersionPatch)))))</ApplicationVersion> <ApplicationVersion>$([MSBuild]::Add($([MSBuild]::Multiply($(VersionMajor), 10000)), $([MSBuild]::Add($([MSBuild]::Multiply($(VersionMinor), 100)), $(VersionPatch)))))</ApplicationVersion>
<AndroidPackageFormats>apk</AndroidPackageFormats> <AndroidPackageFormats>apk</AndroidPackageFormats>
<AndroidEnableProfiledAot>false</AndroidEnableProfiledAot> <AndroidEnableProfiledAot>false</AndroidEnableProfiledAot>
<AndroidStoreUncompressedFileExtensions>.bin</AndroidStoreUncompressedFileExtensions>
<JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault> <JsonSerializerIsReflectionEnabledByDefault>false</JsonSerializerIsReflectionEnabledByDefault>
</PropertyGroup> </PropertyGroup>
@@ -24,4 +25,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MeWeb.App\MeWeb.App.csproj" /> <ProjectReference Include="..\MeWeb.App\MeWeb.App.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<AndroidAsset Include="..\..\artifacts\knowledge\registry.bin" Link="Assets\registry.bin" Condition="Exists('..\..\artifacts\knowledge\registry.bin')" />
</ItemGroup>
</Project> </Project>
+261 -14
View File
@@ -33,24 +33,271 @@
<StreamGeometry x:Key="ArtGlobeBase">M 6 24 A 18 18 0 1 0 42 24 A 18 18 0 1 0 6 24 Z M 6 24 H 42 M 8.4 15 H 39.6 M 8.4 33 H 39.6</StreamGeometry> <StreamGeometry x:Key="ArtGlobeBase">M 6 24 A 18 18 0 1 0 42 24 A 18 18 0 1 0 6 24 Z M 6 24 H 42 M 8.4 15 H 39.6 M 8.4 33 H 39.6</StreamGeometry>
<StreamGeometry x:Key="ArtGlobeSpin">M 24 6 A 9 18 0 0 0 24 42 A 9 18 0 0 0 24 6 Z</StreamGeometry> <StreamGeometry x:Key="ArtGlobeSpin">M 24 6 A 9 18 0 0 0 24 42 A 9 18 0 0 0 24 6 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseFront">M 4 72 L 4 40 L 28 18 L 52 40 L 52 72 Z</StreamGeometry> <StreamGeometry x:Key="ArtHouseDoor">M 15 80 V 67 A 6 6 0 0 1 27 67 V 80 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseSide">M 52 40 L 76 30 L 76 62 L 52 72 Z</StreamGeometry> <StreamGeometry x:Key="ArtBlockGate">M 22.5 124 V 106.5 H 37.5 V 124 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseRoof">M 28 18 L 52 8 L 76 30 L 52 40 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseEdge">M 2 41.5 L 28 17 L 54 41.5</StreamGeometry>
<StreamGeometry x:Key="ArtHouseDoor">M 21 72 V 57 A 7 7 0 0 1 35 57 V 72 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseWindow">M 58 45 L 70 40 L 70 51 L 58 56 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHouseMullions">M 64 42.5 V 53.5 M 58 50.5 L 70 45.5</StreamGeometry>
<StreamGeometry x:Key="ArtBlockFront">M 4 110 V 14 H 52 V 110 Z</StreamGeometry>
<StreamGeometry x:Key="ArtBlockSide">M 52 14 L 68 7 V 103 L 52 110 Z</StreamGeometry>
<StreamGeometry x:Key="ArtBlockTop">M 4 14 L 20 7 H 68 L 52 14 Z</StreamGeometry>
<StreamGeometry x:Key="ArtBlockGate">M 22 110 V 100 A 6 6 0 0 1 34 100 V 110 Z</StreamGeometry>
<StreamGeometry x:Key="ArtDigitSeven">M 0 0 H 6 L 2 9</StreamGeometry> <StreamGeometry x:Key="ArtDigitSeven">M 0 0 H 6 L 2 9</StreamGeometry>
<StreamGeometry x:Key="ArtDigitFour">M 4.5 9 V 0 L 0 6 H 6.5</StreamGeometry> <StreamGeometry x:Key="ArtDigitFour">M 4.5 9 V 0 L 0 6 H 6.5</StreamGeometry>
<StreamGeometry x:Key="ArtQuestion">M 1 3.2 A 2.7 2.7 0 1 1 5.4 5.3 C 4.2 6.1 3.6 6.6 3.6 7.8 M 3.6 10.6 V 10.8</StreamGeometry> <StreamGeometry x:Key="ArtQuestion">M 1 3.2 A 2.7 2.7 0 1 1 5.4 5.3 C 4.2 6.1 3.6 6.6 3.6 7.8 M 3.6 10.6 V 10.8</StreamGeometry>
<StreamGeometry x:Key="ArtHeart">M 12 20.5 C 12 20.5 3 14.6 3 8.7 A 4.6 4.6 0 0 1 12 6.6 A 4.6 4.6 0 0 1 21 8.7 C 21 14.6 12 20.5 12 20.5 Z</StreamGeometry> <StreamGeometry x:Key="ArtHeart">M 12 20.5 C 12 20.5 3 14.6 3 8.7 A 4.6 4.6 0 0 1 12 6.6 A 4.6 4.6 0 0 1 21 8.7 C 21 14.6 12 20.5 12 20.5 Z</StreamGeometry>
<StreamGeometry x:Key="ArtHills">M 0 13 L 6 6 L 9.5 9.5 L 12.5 6.5 L 18 13 Z</StreamGeometry> <StreamGeometry x:Key="ArtSparkle">M 5 0 Q 5.7 4.3 10 5 Q 5.7 5.7 5 10 Q 4.3 5.7 0 5 Q 4.3 4.3 5 0 Z</StreamGeometry>
<StreamGeometry x:Key="ArtCloud">M 4 18 A 6 6 0 0 1 10 8.5 A 8.5 8.5 0 0 1 25 5 A 7 7 0 0 1 36 11 A 4.6 4.6 0 0 1 41 18 Z</StreamGeometry>
<StreamGeometry x:Key="ArtPhoneSheen">M 3.5 30 L 24.5 9 V 17 L 11.5 30 Z</StreamGeometry>
<StreamGeometry x:Key="ArtTabletSheen">M 5 25 L 26.5 3.5 H 36 L 11 29 Z</StreamGeometry>
<StreamGeometry x:Key="ArtOrbit">M 32 4 A 28 28 0 0 1 59.6 36.9</StreamGeometry>
<DrawingImage x:Key="ArtHouse" Viewbox="0,0,86,84">
<DrawingGroup>
<GeometryDrawing Geometry="M 8 80 V 44 L 31 21 L 54 44 V 80 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="30%,0%" EndPoint="60%,100%">
<GradientStop Offset="0" Color="{StaticResource WallLit}" />
<GradientStop Offset="1" Color="{StaticResource WallShade}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 54 44 L 78 34 V 70 L 54 80 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="{StaticResource SideLit}" />
<GradientStop Offset="1" Color="{StaticResource SideShade}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource EaveBrush}" Geometry="M 54 44 L 78 34 V 39 L 54 49 Z" />
<GeometryDrawing Brush="{StaticResource SideShadeBrush}" Geometry="M 8 80 V 76.5 H 54 V 80 Z M 54 80 V 76.5 L 78 66.5 V 70 Z" />
<GeometryDrawing Geometry="M 8 79 V 44 L 31 21">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimSoftBrush}" Thickness="1.1" LineCap="Round" LineJoin="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource SideShadeBrush}" Geometry="M 13.5 80 V 67 A 7.5 7.5 0 0 1 28.5 67 V 80 Z" />
<GeometryDrawing Brush="{StaticResource DoorBrush}" Geometry="M 15 80 V 67 A 6 6 0 0 1 27 67 V 80 Z" />
<GeometryDrawing Brush="{StaticResource WindowCoreBrush}" Geometry="M 17.2 66.6 A 3.8 3.8 0 0 1 24.8 66.6 Z" />
<GeometryDrawing Brush="{StaticResource TrimBrush}" Geometry="M 23.4 74 A 0.9 0.9 0 1 0 25.2 74 A 0.9 0.9 0 1 0 23.4 74 Z" />
<GeometryDrawing Brush="{StaticResource WallLitBrush}" Geometry="M 12 78.6 H 30 V 80.6 H 12 Z" />
<GeometryDrawing Brush="{StaticResource SideShadeBrush}" Geometry="M 35 53 H 49 V 66 H 35 Z" />
<GeometryDrawing Geometry="M 36 54 H 48 V 65 H 36 Z">
<GeometryDrawing.Brush>
<RadialGradientBrush GradientOrigin="35%,30%" Center="35%,30%" RadiusX="90%" RadiusY="90%">
<GradientStop Offset="0" Color="{StaticResource WindowCore}" />
<GradientStop Offset="1" Color="{StaticResource Caution}" />
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 42 54 V 65 M 36 59.5 H 48">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource SideShadeBrush}" Thickness="1.1" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource TrimBrush}" Geometry="M 34.4 65.6 H 49.6 V 67.2 H 34.4 Z" />
<GeometryDrawing Brush="{StaticResource DeviceShadeBrush}" Geometry="M 59 50.9 L 73 45.1 V 59.1 L 59 64.9 Z" />
<GeometryDrawing Geometry="M 60 51.5 L 72 46.5 V 58.5 L 60 63.5 Z">
<GeometryDrawing.Brush>
<RadialGradientBrush GradientOrigin="40%,45%" Center="40%,45%" RadiusX="80%" RadiusY="80%">
<GradientStop Offset="0" Color="{StaticResource WindowCore}" />
<GradientStop Offset="1" Color="{StaticResource Caution}" />
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 66 49 V 61 M 60 57.5 L 72 52.5">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource DeviceShadeBrush}" Thickness="1.1" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource SideShadeBrush}" Geometry="M 25.5 34 A 5.5 5.5 0 1 0 36.5 34 A 5.5 5.5 0 1 0 25.5 34 Z" />
<GeometryDrawing Geometry="M 26.6 34 A 4.4 4.4 0 1 0 35.4 34 A 4.4 4.4 0 1 0 26.6 34 Z">
<GeometryDrawing.Brush>
<RadialGradientBrush GradientOrigin="35%,35%" Center="35%,35%" RadiusX="80%" RadiusY="80%">
<GradientStop Offset="0" Color="{StaticResource WindowCore}" />
<GradientStop Offset="1" Color="{StaticResource Caution}" />
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 31 29.6 V 38.4 M 26.6 34 H 35.4">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource SideShadeBrush}" Thickness="1" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 31 19 L 55 9 L 82.5 36.5 L 58.5 46.5 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="40%,0%" EndPoint="60%,100%">
<GradientStop Offset="0" Color="{StaticResource BrandRedLight}" />
<GradientStop Offset="0.55" Color="{StaticResource BrandRed}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedDark}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 37.9 25.9 L 61.9 15.9 M 44.8 32.8 L 68.8 22.8 M 51.6 39.6 L 75.6 29.6">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BrandRedDarkBrush}" Thickness="0.9" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 58.5 46.5 L 82.5 36.5">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BrandRedDarkBrush}" Thickness="2.4" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 4.5 45.5 L 31 19 L 58.5 46.5">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BrandRedDarkBrush}" Thickness="3.8" LineCap="Round" LineJoin="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 4.8 44 L 31 17.6 L 57.6 44.2">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BrandRedLightBrush}" Thickness="0.9" LineCap="Round" LineJoin="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 31 18.6 L 55 8.6">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BrandRedLightBrush}" Thickness="1.6" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource WallShadeBrush}" Geometry="M 50 5 H 56 V 25.3 L 50 19.3 Z" />
<GeometryDrawing Brush="{StaticResource SideShadeBrush}" Geometry="M 56 5 L 59.8 3.4 V 23.7 L 56 25.3 Z" />
<GeometryDrawing Brush="{StaticResource TrimBrush}" Geometry="M 49 3 L 53 1.3 H 61 L 57 3 Z" />
<GeometryDrawing Brush="{StaticResource WallLitBrush}" Geometry="M 49 3 H 57 V 5.6 H 49 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtBlock" Viewbox="0,-10,76,136">
<DrawingGroup>
<GeometryDrawing Geometry="M 20 13 V -6 M 17 -2 H 23 M 18 2.5 H 22">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimSoftBrush}" Thickness="1" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 6 124 V 16 H 54 V 124 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="30%,0%" EndPoint="70%,100%">
<GradientStop Offset="0" Color="{StaticResource BlockLit}" />
<GradientStop Offset="1" Color="{StaticResource BlockShade}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource BlockSideBrush}" Geometry="M 54 16 L 72 8 V 116 L 54 124 Z" />
<GeometryDrawing Brush="{StaticResource BlockTopBrush}" Geometry="M 6 16 L 24 8 H 72 L 54 16 Z" />
<GeometryDrawing Brush="{StaticResource SideLitBrush}" Geometry="M 41 12.5 V 4 A 5 1.6 0 0 0 51 4 V 12.5 A 5 1.6 0 0 1 41 12.5 Z" />
<GeometryDrawing Brush="{StaticResource WallLitBrush}" Geometry="M 41 4 A 5 1.6 0 0 1 51 4 A 5 1.6 0 0 1 41 4 Z" />
<GeometryDrawing Geometry="M 6 16 H 54 L 72 8">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimSoftBrush}" Thickness="1.6" LineCap="Round" LineJoin="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 6 38.5 H 54 L 72 30.5 M 6 55.5 H 54 L 72 47.5 M 6 72.5 H 54 L 72 64.5 M 6 89.5 H 54 L 72 81.5">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BlockShadeBrush}" Thickness="1" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource WindowDarkBrush}" Geometry="M 57.6 22.4 L 61.56 20.64 V 31.64 L 57.6 33.4 Z M 57.6 39.4 L 61.56 37.64 V 48.64 L 57.6 50.4 Z M 64.44 36.36 L 68.4 34.6 V 45.6 L 64.44 47.36 Z M 57.6 56.4 L 61.56 54.64 V 65.64 L 57.6 67.4 Z M 64.44 53.36 L 68.4 51.6 V 62.6 L 64.44 64.36 Z M 64.44 70.36 L 68.4 68.6 V 79.6 L 64.44 81.36 Z" />
<GeometryDrawing Brush="{StaticResource CautionBrush}" Geometry="M 64.44 19.36 L 68.4 17.6 V 28.6 L 64.44 30.36 Z M 57.6 73.4 L 61.56 71.64 V 82.64 L 57.6 84.4 Z" />
<GeometryDrawing Brush="{StaticResource BlockSideBrush}" Geometry="M 6 124 V 120.5 H 54 V 124 Z M 54 124 V 120.5 L 72 112.5 V 116 Z" />
<GeometryDrawing Geometry="M 6 123 V 16">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimSoftBrush}" Thickness="1.1" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource BlockShadeBrush}" Geometry="M 21 124 V 105 H 39 V 124 Z" />
<GeometryDrawing Geometry="M 22.5 124 V 106.5 H 37.5 V 124 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource WindowCore}" />
<GradientStop Offset="1" Color="{StaticResource Caution}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="M 30 106.5 V 124">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BlockShadeBrush}" Thickness="1.3" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource WallLitBrush}" Geometry="M 17 101 L 20 99.6 H 46 L 43 101 Z" />
<GeometryDrawing Brush="{StaticResource TrimBrush}" Geometry="M 17 101 H 43 V 104 H 17 Z" />
<GeometryDrawing Brush="{StaticResource BlockTopBrush}" Geometry="M 43 101 L 46 99.6 V 102.6 L 43 104 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtPine" Viewbox="0,0,20,36">
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource BarkBrush}" Geometry="M 9 26 H 11 V 36 H 9 Z" />
<GeometryDrawing Brush="{StaticResource LeafBrush}" Geometry="M 10 13 L 0 28 H 10 Z" />
<GeometryDrawing Brush="{StaticResource LeafShadeBrush}" Geometry="M 10 13 L 20 28 H 10 Z" />
<GeometryDrawing Brush="{StaticResource LeafBrush}" Geometry="M 10 6 L 1.5 21 H 10 Z" />
<GeometryDrawing Brush="{StaticResource LeafShadeBrush}" Geometry="M 10 6 L 18.5 21 H 10 Z" />
<GeometryDrawing Brush="{StaticResource LeafBrush}" Geometry="M 10 0 L 3.5 13 H 10 Z" />
<GeometryDrawing Brush="{StaticResource LeafShadeBrush}" Geometry="M 10 0 L 16.5 13 H 10 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtOak" Viewbox="0,0,26,36">
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource BarkBrush}" Geometry="M 11.6 36 L 12.2 23 H 13.8 L 14.4 36 Z" />
<GeometryDrawing Geometry="M 13 30 L 17.5 25.5">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource BarkBrush}" Thickness="1.3" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource LeafShadeBrush}" Geometry="F1 M 1 20 A 8.5 8.5 0 1 1 18 20 A 8.5 8.5 0 1 1 1 20 Z M 9 15 A 8.5 8.5 0 1 1 26 15 A 8.5 8.5 0 1 1 9 15 Z M 4 10.5 A 7.5 7.5 0 1 1 19 10.5 A 7.5 7.5 0 1 1 4 10.5 Z" />
<GeometryDrawing Brush="{StaticResource LeafBrush}" Geometry="F1 M 2.4 18 A 7 7 0 1 1 16.4 18 A 7 7 0 1 1 2.4 18 Z M 5.4 10 A 6.2 6.2 0 1 1 17.8 10 A 6.2 6.2 0 1 1 5.4 10 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtHamlet" Viewbox="0,0,10,10">
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource PlanetLitBrush}" Geometry="M 0.5 10 V 4.6 L 5 0.6 L 9.5 4.6 V 10 Z" />
<GeometryDrawing Brush="{StaticResource CautionBrush}" Geometry="M 3.8 5.6 H 6.2 V 8 H 3.8 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtPhoto" Viewbox="0,0,24,18">
<DrawingGroup>
<GeometryDrawing Geometry="M 0 0 H 24 V 18 H 0 Z">
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource PhotoSky}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedLight}" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource WindowCoreBrush}" Geometry="M 13.2 5.6 A 2.9 2.9 0 1 0 19 5.6 A 2.9 2.9 0 1 0 13.2 5.6 Z" />
<GeometryDrawing Brush="{StaticResource BrandRedBrush}" Geometry="M 0 18 V 11 Q 5 6 10 10 Q 14 13 18 9 Q 21 7 24 9.5 V 18 Z" />
<GeometryDrawing Brush="{StaticResource BrandRedDarkBrush}" Geometry="M 0 18 V 14.5 Q 7 10.5 13 14 Q 18 16.5 24 13 V 18 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtLetter" Viewbox="0,0,22,15">
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource TextStrongBrush}" Geometry="M 1.6 0.5 H 20.4 A 1.6 1.6 0 0 1 22 2.1 V 12.9 A 1.6 1.6 0 0 1 20.4 14.5 H 1.6 A 1.6 1.6 0 0 1 0 12.9 V 2.1 A 1.6 1.6 0 0 1 1.6 0.5 Z" />
<GeometryDrawing Geometry="M 1 13.8 L 8.6 7.6 M 21 13.8 L 13.4 7.6">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimBrush}" Thickness="0.9" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource TrimBrush}" Geometry="M 0.9 1.2 L 11 9.2 L 21.1 1.2 Z" />
<GeometryDrawing Brush="{StaticResource BrandRedBrush}" Geometry="M 8.4 9 A 2.6 2.6 0 1 0 13.6 9 A 2.6 2.6 0 1 0 8.4 9 Z" />
<GeometryDrawing Brush="{StaticResource BrandRedLightBrush}" Geometry="M 9.6 8.4 A 0.9 0.9 0 1 0 11.4 8.4 A 0.9 0.9 0 1 0 9.6 8.4 Z" />
</DrawingGroup>
</DrawingImage>
<DrawingImage x:Key="ArtServer" Viewbox="0,0,40,30">
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource DeviceEdgeBrush}" Geometry="M 6 11 L 20 5 L 34 11 L 20 17 Z" />
<GeometryDrawing Brush="{StaticResource DeviceLitBrush}" Geometry="M 6 11 L 20 17 V 26 L 6 20 Z" />
<GeometryDrawing Brush="{StaticResource DeviceShadeBrush}" Geometry="M 20 17 L 34 11 V 20 L 20 26 Z" />
<GeometryDrawing Geometry="M 23 20.2 L 31 16.8 M 23 23 L 31 19.6">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource DeviceLitBrush}" Thickness="1" LineCap="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Geometry="M 6.2 11.2 L 20 17 L 33.8 11.2">
<GeometryDrawing.Pen>
<Pen Brush="{StaticResource TrimBrush}" Thickness="0.8" LineCap="Round" LineJoin="Round" />
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="{StaticResource GoodBrush}" Geometry="M 8.6 15.6 A 1.2 1.2 0 1 0 11 15.6 A 1.2 1.2 0 1 0 8.6 15.6 Z" />
</DrawingGroup>
</DrawingImage>
<StreamGeometry x:Key="ArtNodeOrbit">M 45 -1 A 46 46 0 0 1 84.84 68 M 45 91 A 46 46 0 0 1 5.16 22</StreamGeometry>
<StreamGeometry x:Key="WireToEnd">M 0 0 C 0 0.62 1 0.38 1 1</StreamGeometry> <StreamGeometry x:Key="WireToEnd">M 0 0 C 0 0.62 1 0.38 1 1</StreamGeometry>
<StreamGeometry x:Key="WireToStart">M 1 0 C 1 0.62 0 0.38 0 1</StreamGeometry> <StreamGeometry x:Key="WireToStart">M 1 0 C 1 0.62 0 0.38 0 1</StreamGeometry>
+71
View File
@@ -25,6 +25,56 @@
<Color x:Key="Glint">#1FF0EAEF</Color> <Color x:Key="Glint">#1FF0EAEF</Color>
<Color x:Key="GlintClear">#00F0EAEF</Color> <Color x:Key="GlintClear">#00F0EAEF</Color>
<Color x:Key="SkyHigh">#0D0911</Color>
<Color x:Key="SkyLow">#2E1834</Color>
<Color x:Key="Dusk">#5CC2456F</Color>
<Color x:Key="DuskClear">#00C2456F</Color>
<Color x:Key="Atmosphere">#70EC7A82</Color>
<Color x:Key="AtmosphereClear">#00EC7A82</Color>
<Color x:Key="PlanetLit">#48324F</Color>
<Color x:Key="PlanetShade">#150E18</Color>
<Color x:Key="TownGlow">#4DF2C14E</Color>
<Color x:Key="Contact">#B30A070D</Color>
<Color x:Key="ContactClear">#000A070D</Color>
<Color x:Key="Vignette">#A60A070D</Color>
<Color x:Key="WallLit">#8E7695</Color>
<Color x:Key="WallShade">#5F4B67</Color>
<Color x:Key="SideLit">#4D3C56</Color>
<Color x:Key="SideShade">#352840</Color>
<Color x:Key="Trim">#D4C3D6</Color>
<Color x:Key="TrimSoft">#73D4C3D6</Color>
<Color x:Key="Eave">#661B141C</Color>
<Color x:Key="Door">#33202F</Color>
<Color x:Key="WindowCore">#FFE9AE</Color>
<Color x:Key="WindowDark">#231A29</Color>
<Color x:Key="WindowCool">#8DB6EE</Color>
<Color x:Key="BlockLit">#5C5779</Color>
<Color x:Key="BlockShade">#3F3A58</Color>
<Color x:Key="BlockSide">#2C283E</Color>
<Color x:Key="BlockTop">#746D93</Color>
<Color x:Key="Leaf">#3E8670</Color>
<Color x:Key="LeafShade">#1F4C44</Color>
<Color x:Key="Bark">#4C3137</Color>
<Color x:Key="DeviceLit">#4F4459</Color>
<Color x:Key="DeviceShade">#211B26</Color>
<Color x:Key="DeviceEdge">#A898AD</Color>
<Color x:Key="ScreenHigh">#41365A</Color>
<Color x:Key="ScreenLow">#16111C</Color>
<Color x:Key="Sheen">#26F0EAEF</Color>
<Color x:Key="PhotoSky">#F7B27A</Color>
<Color x:Key="Cloud">#26362848</Color>
<Color x:Key="CloudLit">#6B8A4470</Color>
<Color x:Key="Smoke">#80B3A6B2</Color>
<Color x:Key="SmokeClear">#00B3A6B2</Color>
<Color x:Key="Beam">#38F0EAEF</Color>
<Color x:Key="BeamClear">#0AF0EAEF</Color>
<Color x:Key="GoodLight">#B4F0CF</Color>
<Color x:Key="GoodGlow">#996FCF97</Color>
<Color x:Key="GoodClear">#006FCF97</Color>
<Color x:Key="CautionLight">#FBE3A1</Color>
<Color x:Key="BadGlow">#88F06B79</Color>
<Color x:Key="BadClear">#00F06B79</Color>
<SolidColorBrush x:Key="BrandRedBrush" Color="{StaticResource BrandRed}" /> <SolidColorBrush x:Key="BrandRedBrush" Color="{StaticResource BrandRed}" />
<SolidColorBrush x:Key="BrandRedDarkBrush" Color="{StaticResource BrandRedDark}" /> <SolidColorBrush x:Key="BrandRedDarkBrush" Color="{StaticResource BrandRedDark}" />
<SolidColorBrush x:Key="BrandRedLightBrush" Color="{StaticResource BrandRedLight}" /> <SolidColorBrush x:Key="BrandRedLightBrush" Color="{StaticResource BrandRedLight}" />
@@ -42,4 +92,25 @@
<SolidColorBrush x:Key="BadTintBrush" Color="{StaticResource BadTint}" /> <SolidColorBrush x:Key="BadTintBrush" Color="{StaticResource BadTint}" />
<SolidColorBrush x:Key="NeutralTintBrush" Color="{StaticResource NeutralTint}" /> <SolidColorBrush x:Key="NeutralTintBrush" Color="{StaticResource NeutralTint}" />
<SolidColorBrush x:Key="ScrimBrush" Color="{StaticResource Scrim}" /> <SolidColorBrush x:Key="ScrimBrush" Color="{StaticResource Scrim}" />
<SolidColorBrush x:Key="TrimBrush" Color="{StaticResource Trim}" />
<SolidColorBrush x:Key="TrimSoftBrush" Color="{StaticResource TrimSoft}" />
<SolidColorBrush x:Key="EaveBrush" Color="{StaticResource Eave}" />
<SolidColorBrush x:Key="DoorBrush" Color="{StaticResource Door}" />
<SolidColorBrush x:Key="WallLitBrush" Color="{StaticResource WallLit}" />
<SolidColorBrush x:Key="WallShadeBrush" Color="{StaticResource WallShade}" />
<SolidColorBrush x:Key="SideLitBrush" Color="{StaticResource SideLit}" />
<SolidColorBrush x:Key="SideShadeBrush" Color="{StaticResource SideShade}" />
<SolidColorBrush x:Key="WindowDarkBrush" Color="{StaticResource WindowDark}" />
<SolidColorBrush x:Key="WindowCoreBrush" Color="{StaticResource WindowCore}" />
<SolidColorBrush x:Key="BlockShadeBrush" Color="{StaticResource BlockShade}" />
<SolidColorBrush x:Key="BlockSideBrush" Color="{StaticResource BlockSide}" />
<SolidColorBrush x:Key="BlockTopBrush" Color="{StaticResource BlockTop}" />
<SolidColorBrush x:Key="LeafBrush" Color="{StaticResource Leaf}" />
<SolidColorBrush x:Key="LeafShadeBrush" Color="{StaticResource LeafShade}" />
<SolidColorBrush x:Key="BarkBrush" Color="{StaticResource Bark}" />
<SolidColorBrush x:Key="DeviceLitBrush" Color="{StaticResource DeviceLit}" />
<SolidColorBrush x:Key="DeviceShadeBrush" Color="{StaticResource DeviceShade}" />
<SolidColorBrush x:Key="DeviceEdgeBrush" Color="{StaticResource DeviceEdge}" />
<SolidColorBrush x:Key="PlanetLitBrush" Color="{StaticResource PlanetLit}" />
<SolidColorBrush x:Key="SheenBrush" Color="{StaticResource Sheen}" />
</ResourceDictionary> </ResourceDictionary>
+412 -110
View File
@@ -124,6 +124,11 @@
<Setter Property="Fill" Value="{StaticResource SurfaceRaisedBrush}" /> <Setter Property="Fill" Value="{StaticResource SurfaceRaisedBrush}" />
<Setter Property="Stroke" Value="{StaticResource LineBrush}" /> <Setter Property="Stroke" Value="{StaticResource LineBrush}" />
<Setter Property="StrokeThickness" Value="3" /> <Setter Property="StrokeThickness" Value="3" />
<Setter Property="Transitions">
<Transitions>
<BrushTransition Property="Stroke" Duration="0:0:0.5" />
</Transitions>
</Setter>
</Style> </Style>
<Style Selector="Ellipse.disc.good"> <Style Selector="Ellipse.disc.good">
<Setter Property="Stroke" Value="{StaticResource GoodBrush}" /> <Setter Property="Stroke" Value="{StaticResource GoodBrush}" />
@@ -208,6 +213,9 @@
<Setter Property="Cursor" Value="Hand" /> <Setter Property="Cursor" Value="Hand" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
</Style> </Style>
<Style Selector="Button.hop, Button.hop /template/ ContentPresenter">
<Setter Property="ClipToBounds" Value="False" />
</Style>
<Style Selector="Button.hop:pointerover /template/ ContentPresenter, Button.hop:pressed /template/ ContentPresenter"> <Style Selector="Button.hop:pointerover /template/ ContentPresenter, Button.hop:pressed /template/ ContentPresenter">
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
</Style> </Style>
@@ -293,6 +301,127 @@
</Animation> </Animation>
</Style.Animations> </Style.Animations>
</Style> </Style>
<Style Selector="Ellipse.aura">
<Setter Property="Opacity" Value="0" />
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Transitions">
<Transitions>
<DoubleTransition Property="Opacity" Duration="0:0:0.6" />
</Transitions>
</Setter>
</Style>
<Style Selector="Ellipse.aura.good">
<Setter Property="Opacity" Value="0.55" />
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0.5" Color="{StaticResource GoodGlow}" />
<GradientStop Offset="1" Color="{StaticResource GoodClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.aura.caution">
<Setter Property="Opacity" Value="0.55" />
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0.5" Color="{StaticResource CautionGlow}" />
<GradientStop Offset="1" Color="{StaticResource CautionClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.aura.bad">
<Setter Property="Opacity" Value="0.55" />
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0.5" Color="{StaticResource BadGlow}" />
<GradientStop Offset="1" Color="{StaticResource BadClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.gloss">
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource Glint}" />
<GradientStop Offset="0.55" Color="{StaticResource GlintClear}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Path.spinner">
<Setter Property="StrokeThickness" Value="3" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="Stroke">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="{StaticResource BrandRedLight}" />
<GradientStop Offset="0.5" Color="{StaticResource BrandRedClear}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedLight}" />
</LinearGradientBrush>
</Setter>
<Style.Animations>
<Animation Duration="0:0:1.6" IterationCount="Infinite">
<KeyFrame Cue="0%">
<Setter Property="RotateTransform.Angle" Value="0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="RotateTransform.Angle" Value="360" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Ellipse.burst">
<Setter Property="Opacity" Value="0" />
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
<Style Selector="Ellipse.burst.good">
<Setter Property="Stroke" Value="{StaticResource GoodBrush}" />
</Style>
<Style Selector="Ellipse.burst.caution">
<Setter Property="Stroke" Value="{StaticResource CautionBrush}" />
</Style>
<Style Selector="Ellipse.burst.bad">
<Setter Property="Stroke" Value="{StaticResource BadBrush}" />
</Style>
<Style Selector="Ellipse.burst.good, Ellipse.burst.caution, Ellipse.burst.bad">
<Style.Animations>
<Animation Duration="0:0:1.1" Easing="CubicEaseOut" FillMode="Forward">
<KeyFrame Cue="0%">
<Setter Property="Opacity" Value="0" />
<Setter Property="ScaleTransform.ScaleX" Value="1" />
<Setter Property="ScaleTransform.ScaleY" Value="1" />
</KeyFrame>
<KeyFrame Cue="12%">
<Setter Property="Opacity" Value="0.9" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="Opacity" Value="0" />
<Setter Property="ScaleTransform.ScaleX" Value="1.5" />
<Setter Property="ScaleTransform.ScaleY" Value="1.5" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Path.packets">
<Setter Property="Stretch" Value="Fill" />
<Setter Property="Opacity" Value="0" />
<Setter Property="IsHitTestVisible" Value="False" />
<Setter Property="Stroke" Value="{StaticResource WindowCoreBrush}" />
<Setter Property="StrokeThickness" Value="5" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeDashArray" Value="0.01,9" />
</Style>
<Style Selector="Path.packets.flowing">
<Setter Property="Opacity" Value="1" />
<Style.Animations>
<Animation Duration="0:0:1.1" IterationCount="Infinite">
<KeyFrame Cue="0%">
<Setter Property="StrokeDashOffset" Value="0" />
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="StrokeDashOffset" Value="-9.01" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
<Style Selector="Ellipse.disc.thin"> <Style Selector="Ellipse.disc.thin">
<Setter Property="StrokeThickness" Value="2" /> <Setter Property="StrokeThickness" Value="2" />
</Style> </Style>
@@ -478,14 +607,6 @@
<Setter Property="Width" Value="170" /> <Setter Property="Width" Value="170" />
<Setter Property="Height" Value="170" /> <Setter Property="Height" Value="170" />
</Style> </Style>
<Style Selector="Ellipse.glow">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource BrandRedGlow}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.statusChip"> <Style Selector="Border.statusChip">
<Setter Property="Background" Value="{StaticResource SurfaceRaisedBrush}" /> <Setter Property="Background" Value="{StaticResource SurfaceRaisedBrush}" />
<Setter Property="CornerRadius" Value="14" /> <Setter Property="CornerRadius" Value="14" />
@@ -578,81 +699,137 @@
<Style Selector="TextBlock.lead.strong"> <Style Selector="TextBlock.lead.strong">
<Setter Property="Foreground" Value="{StaticResource TextStrongBrush}" /> <Setter Property="Foreground" Value="{StaticResource TextStrongBrush}" />
</Style> </Style>
<Style Selector="Ellipse.star"> <Style Selector="Rectangle.sky">
<Setter Property="Fill" Value="{StaticResource TextStrongBrush}" />
</Style>
<Style Selector="Ellipse.planet">
<Setter Property="Fill"> <Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,22%"> <LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource SurfaceRaised}" /> <GradientStop Offset="0" Color="{StaticResource SkyHigh}" />
<GradientStop Offset="1" Color="{StaticResource Ground}" /> <GradientStop Offset="0.72" Color="{StaticResource SkyLow}" />
</LinearGradientBrush> </LinearGradientBrush>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Ellipse.meridian"> <Style Selector="Ellipse.dusk">
<Setter Property="Stroke" Value="{StaticResource LineBrush}" /> <Setter Property="Fill">
<Setter Property="StrokeThickness" Value="1" /> <RadialGradientBrush>
<Setter Property="Opacity" Value="0.7" /> <GradientStop Offset="0" Color="{StaticResource Dusk}" />
<GradientStop Offset="1" Color="{StaticResource DuskClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.star, Path.sparkle">
<Setter Property="Fill" Value="{StaticResource TextStrongBrush}" />
</Style>
<Style Selector="Path.sparkle">
<Setter Property="Data" Value="{StaticResource ArtSparkle}" />
<Setter Property="Width" Value="10" />
<Setter Property="Height" Value="10" />
</Style>
<Style Selector="Border.meteor">
<Setter Property="CornerRadius" Value="1" />
<Setter Property="Background">
<LinearGradientBrush StartPoint="0%,50%" EndPoint="100%,50%">
<GradientStop Offset="0" Color="{StaticResource GlintClear}" />
<GradientStop Offset="1" Color="{StaticResource TextStrong}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Path.cloud">
<Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource Cloud}" />
<GradientStop Offset="1" Color="{StaticResource CloudLit}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.halo">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource BrandRedGlow}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.atmosphere">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0.86" Color="{StaticResource AtmosphereClear}" />
<GradientStop Offset="0.896" Color="{StaticResource Atmosphere}" />
<GradientStop Offset="1" Color="{StaticResource AtmosphereClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.planet">
<Setter Property="Fill">
<RadialGradientBrush Center="46%,0%" GradientOrigin="46%,0%" RadiusX="50%" RadiusY="50%">
<GradientStop Offset="0" Color="{StaticResource PlanetLit}" />
<GradientStop Offset="0.62" Color="{StaticResource PlanetShade}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.town">
<Setter Property="Fill" Value="{StaticResource WindowCoreBrush}" />
</Style>
<Style Selector="Ellipse.townGlow">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource TownGlow}" />
<GradientStop Offset="1" Color="{StaticResource CautionClear}" />
</RadialGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Ellipse.rim"> <Style Selector="Ellipse.rim">
<Setter Property="Stroke" Value="{StaticResource BrandRedLightBrush}" /> <Setter Property="StrokeThickness" Value="1.4" />
<Setter Property="StrokeThickness" Value="1.5" /> <Setter Property="Stroke">
<Setter Property="Opacity" Value="0.45" /> <LinearGradientBrush StartPoint="0%,50%" EndPoint="100%,50%">
<GradientStop Offset="0.12" Color="{StaticResource BrandRedClear}" />
<GradientStop Offset="0.5" Color="{StaticResource BrandRedLight}" />
<GradientStop Offset="0.88" Color="{StaticResource BrandRedClear}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.contact">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource Contact}" />
<GradientStop Offset="1" Color="{StaticResource ContactClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Rectangle.vignette">
<Setter Property="Fill">
<RadialGradientBrush Center="50%,42%" GradientOrigin="50%,42%" RadiusX="72%" RadiusY="85%">
<GradientStop Offset="0.55" Color="{StaticResource ContactClear}" />
<GradientStop Offset="1" Color="{StaticResource Vignette}" />
</RadialGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Path.trail"> <Style Selector="Path.trail">
<Setter Property="Stroke" Value="{StaticResource BrandRedLightBrush}" /> <Setter Property="Stroke" Value="{StaticResource BrandRedLightBrush}" />
<Setter Property="StrokeThickness" Value="1.5" /> <Setter Property="StrokeThickness" Value="1.6" />
<Setter Property="StrokeDashArray" Value="2,4" /> <Setter Property="StrokeDashArray" Value="0.1,3.2" />
<Setter Property="StrokeLineCap" Value="Round" /> <Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="Opacity" Value="0.55" /> <Setter Property="Opacity" Value="0.6" />
</Style> </Style>
<Style Selector="Ellipse.shadow"> <Style Selector="Path.comet">
<Setter Property="Fill" Value="{StaticResource GroundBrush}" /> <Setter Property="StrokeThickness" Value="2.6" />
<Setter Property="Opacity" Value="0.7" />
</Style>
<Style Selector="Path.wall">
<Setter Property="Fill" Value="{StaticResource SurfaceRaisedBrush}" />
<Setter Property="Stroke" Value="{StaticResource LineBrush}" />
<Setter Property="StrokeThickness" Value="1.2" />
<Setter Property="StrokeJoin" Value="Round" />
</Style>
<Style Selector="Path.wall.side">
<Setter Property="Fill" Value="{StaticResource SurfaceBrush}" />
</Style>
<Style Selector="Path.roof">
<Setter Property="Fill" Value="{StaticResource BrandRedBrush}" />
<Setter Property="Stroke" Value="{StaticResource BrandRedDarkBrush}" />
<Setter Property="StrokeThickness" Value="1" />
<Setter Property="StrokeJoin" Value="Round" />
</Style>
<Style Selector="Path.roofEdge">
<Setter Property="Stroke" Value="{StaticResource BrandRedBrush}" />
<Setter Property="StrokeThickness" Value="3" />
<Setter Property="StrokeLineCap" Value="Round" /> <Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" />
</Style> </Style>
<Style Selector="Path.flatRoof"> <Style Selector="Ellipse.spark">
<Setter Property="Fill" Value="{StaticResource LineBrush}" /> <Setter Property="Fill" Value="{StaticResource WindowCoreBrush}" />
<Setter Property="Stroke" Value="{StaticResource LineBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style> </Style>
<Style Selector="Path.door"> <Style Selector="Ellipse.ripple">
<Setter Property="Fill" Value="{StaticResource GroundBrush}" /> <Setter Property="Stroke" Value="{StaticResource TextStrongBrush}" />
<Setter Property="Stroke" Value="{StaticResource LineBrush}" /> <Setter Property="StrokeThickness" Value="1.4" />
<Setter Property="StrokeThickness" Value="1" />
</Style> </Style>
<Style Selector="Path.doorLight"> <Style Selector="Ellipse.smoke">
<Setter Property="Fill" Value="{StaticResource GoodBrush}" /> <Setter Property="Fill">
<Setter Property="Opacity" Value="0" /> <RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource Smoke}" />
<GradientStop Offset="1" Color="{StaticResource SmokeClear}" />
</RadialGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Path.window, Ellipse.window, Rectangle.flat"> <Style Selector="Ellipse.windowGlow">
<Setter Property="Fill" Value="{StaticResource CautionBrush}" /> <Setter Property="IsHitTestVisible" Value="False" />
</Style>
<Style Selector="Path.mullion">
<Setter Property="Stroke" Value="{StaticResource SurfaceBrush}" />
<Setter Property="StrokeThickness" Value="1" />
</Style>
<Style Selector="Ellipse.warmGlow">
<Setter Property="Fill"> <Setter Property="Fill">
<RadialGradientBrush> <RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource CautionGlow}" /> <GradientStop Offset="0" Color="{StaticResource CautionGlow}" />
@@ -660,57 +837,159 @@
</RadialGradientBrush> </RadialGradientBrush>
</Setter> </Setter>
</Style> </Style>
<Style Selector="Ellipse.doorSpill">
<Setter Property="Opacity" Value="0" />
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource GoodGlow}" />
<GradientStop Offset="1" Color="{StaticResource GoodClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Path.doorLight">
<Setter Property="Opacity" Value="0" />
<Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource GoodLight}" />
<GradientStop Offset="1" Color="{StaticResource Good}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Path.gateLight">
<Setter Property="Opacity" Value="0" />
<Setter Property="Fill" Value="{StaticResource WindowCoreBrush}" />
</Style>
<Style Selector="Rectangle.flat">
<Setter Property="Fill" Value="{StaticResource CautionBrush}" />
</Style>
<Style Selector="Rectangle.flat.dark">
<Setter Property="Fill" Value="{StaticResource WindowDarkBrush}" />
</Style>
<Style Selector="Rectangle.flat.cool">
<Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource WindowCool}" />
<GradientStop Offset="1" Color="{StaticResource BlockShade}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Rectangle.scan">
<Setter Property="Fill" Value="{StaticResource WindowCoreBrush}" />
</Style>
<Style Selector="Ellipse.beacon">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource BrandRedLight}" />
<GradientStop Offset="0.45" Color="{StaticResource BrandRed}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.plate"> <Style Selector="Border.plate">
<Setter Property="Background" Value="{StaticResource TextStrongBrush}" /> <Setter Property="Background" Value="{StaticResource TextStrongBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource BrandRedDarkBrush}" />
<Setter Property="BorderThickness" Value="0.8" />
<Setter Property="CornerRadius" Value="2" /> <Setter Property="CornerRadius" Value="2" />
</Style> </Style>
<Style Selector="Path.digit"> <Style Selector="Path.digit">
<Setter Property="Stroke" Value="{StaticResource GroundBrush}" /> <Setter Property="Stroke" Value="{StaticResource GroundBrush}" />
<Setter Property="StrokeThickness" Value="1.5" /> <Setter Property="StrokeThickness" Value="1.25" />
<Setter Property="StrokeLineCap" Value="Round" /> <Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" /> <Setter Property="StrokeJoin" Value="Round" />
<Setter Property="Width" Value="6.5" /> <Setter Property="Stretch" Value="Uniform" />
<Setter Property="Height" Value="9" /> <Setter Property="Width" Value="4.8" />
<Setter Property="Height" Value="6.6" />
<Setter Property="HorizontalAlignment" Value="Center" /> <Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
</Style> </Style>
<Style Selector="Border.device"> <Style Selector="Border.device">
<Setter Property="Background" Value="{StaticResource SurfaceRaisedBrush}" /> <Setter Property="BorderBrush" Value="{StaticResource DeviceEdgeBrush}" />
<Setter Property="BorderBrush" Value="{StaticResource TextSoftBrush}" /> <Setter Property="BorderThickness" Value="1.2" />
<Setter Property="BorderThickness" Value="1.5" /> <Setter Property="Background">
<LinearGradientBrush StartPoint="0%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="{StaticResource DeviceLit}" />
<GradientStop Offset="1" Color="{StaticResource DeviceShade}" />
</LinearGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Border.screen"> <Style Selector="Border.screen">
<Setter Property="Background" Value="{StaticResource GroundBrush}" /> <Setter Property="ClipToBounds" Value="True" />
<Setter Property="Background">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource ScreenHigh}" />
<GradientStop Offset="1" Color="{StaticResource ScreenLow}" />
</LinearGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Border.flash"> <Style Selector="Border.flash">
<Setter Property="Background" Value="{StaticResource TextStrongBrush}" /> <Setter Property="Background" Value="{StaticResource TextStrongBrush}" />
<Setter Property="Opacity" Value="0" /> <Setter Property="Opacity" Value="0" />
</Style> </Style>
<Style Selector="Border.photoFrame"> <Style Selector="Path.sheen">
<Setter Property="Fill" Value="{StaticResource SheenBrush}" />
<Setter Property="IsHitTestVisible" Value="False" />
</Style>
<Style Selector="Ellipse.lens">
<Setter Property="Fill" Value="{StaticResource GroundBrush}" />
<Setter Property="Stroke" Value="{StaticResource DeviceEdgeBrush}" />
<Setter Property="StrokeThickness" Value="0.5" />
</Style>
<Style Selector="Rectangle.homeBar">
<Setter Property="Fill" Value="{StaticResource DeviceEdgeBrush}" />
</Style>
<Style Selector="Ellipse.screenGlow">
<Setter Property="Opacity" Value="0" />
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource BrandRedGlow}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Border.polaroid">
<Setter Property="Background" Value="{StaticResource TextStrongBrush}" /> <Setter Property="Background" Value="{StaticResource TextStrongBrush}" />
<Setter Property="CornerRadius" Value="2" /> <Setter Property="CornerRadius" Value="1.5" />
<Setter Property="Padding" Value="2" /> <Setter Property="Padding" Value="2,2,2,5" />
</Style> </Style>
<Style Selector="Canvas.photo"> <Style Selector="Path.heart, Path.heartlet">
<Setter Property="Background" Value="{StaticResource BrandRedLightBrush}" /> <Setter Property="Fill">
</Style> <LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<Style Selector="Path.hills"> <GradientStop Offset="0" Color="{StaticResource BrandRedLight}" />
<Setter Property="Fill" Value="{StaticResource BrandRedDarkBrush}" /> <GradientStop Offset="1" Color="{StaticResource BrandRed}" />
</Style> </LinearGradientBrush>
<Style Selector="Ellipse.sun"> </Setter>
<Setter Property="Fill" Value="{StaticResource CautionBrush}" />
</Style>
<Style Selector="Ellipse.packet">
<Setter Property="Fill" Value="{StaticResource TextStrongBrush}" />
</Style>
<Style Selector="Path.heart">
<Setter Property="Fill" Value="{StaticResource BrandRedBrush}" />
</Style> </Style>
<Style Selector="Ellipse.badge.good"> <Style Selector="Ellipse.badge.good">
<Setter Property="Fill" Value="{StaticResource GoodBrush}" /> <Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource GoodLight}" />
<GradientStop Offset="1" Color="{StaticResource Good}" />
</LinearGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Ellipse.badge.caution"> <Style Selector="Ellipse.badge.caution">
<Setter Property="Fill" Value="{StaticResource CautionBrush}" /> <Setter Property="Fill">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="50%,100%">
<GradientStop Offset="0" Color="{StaticResource CautionLight}" />
<GradientStop Offset="1" Color="{StaticResource Caution}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.badgeGlow.good">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource GoodGlow}" />
<GradientStop Offset="1" Color="{StaticResource GoodClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.badgeGlow.caution">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0" Color="{StaticResource CautionGlow}" />
<GradientStop Offset="1" Color="{StaticResource CautionClear}" />
</RadialGradientBrush>
</Setter>
</Style> </Style>
<Style Selector="Path.badgeMark"> <Style Selector="Path.badgeMark">
<Setter Property="Stroke" Value="{StaticResource GroundBrush}" /> <Setter Property="Stroke" Value="{StaticResource GroundBrush}" />
@@ -718,6 +997,46 @@
<Setter Property="StrokeLineCap" Value="Round" /> <Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="StrokeJoin" Value="Round" /> <Setter Property="StrokeJoin" Value="Round" />
</Style> </Style>
<Style Selector="Path.beam">
<Setter Property="Opacity" Value="0" />
<Setter Property="Fill">
<LinearGradientBrush StartPoint="0%,100%" EndPoint="100%,0%">
<GradientStop Offset="0" Color="{StaticResource Beam}" />
<GradientStop Offset="1" Color="{StaticResource BeamClear}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.callout">
<Setter Property="StrokeThickness" Value="1.2" />
<Setter Property="Stroke" Value="{StaticResource TrimSoftBrush}" />
<Setter Property="Fill">
<RadialGradientBrush Center="40%,30%" GradientOrigin="40%,30%" RadiusX="70%" RadiusY="70%">
<GradientStop Offset="0" Color="{StaticResource SurfaceRaised}" />
<GradientStop Offset="1" Color="{StaticResource Ground}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.calloutGlow">
<Setter Property="Fill">
<RadialGradientBrush>
<GradientStop Offset="0.6" Color="{StaticResource BrandRedGlow}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedClear}" />
</RadialGradientBrush>
</Setter>
</Style>
<Style Selector="Path.orbit">
<Setter Property="StrokeThickness" Value="2" />
<Setter Property="StrokeLineCap" Value="Round" />
<Setter Property="Stroke">
<LinearGradientBrush StartPoint="50%,0%" EndPoint="100%,100%">
<GradientStop Offset="0" Color="{StaticResource BrandRedClear}" />
<GradientStop Offset="1" Color="{StaticResource BrandRedLight}" />
</LinearGradientBrush>
</Setter>
</Style>
<Style Selector="Ellipse.led">
<Setter Property="Fill" Value="{StaticResource CautionBrush}" />
</Style>
<Style Selector="Border.story"> <Style Selector="Border.story">
<Setter Property="Background" Value="{StaticResource SurfaceBrush}" /> <Setter Property="Background" Value="{StaticResource SurfaceBrush}" />
@@ -734,7 +1053,6 @@
<Setter Property="Background" Value="{StaticResource GroundBrush}" /> <Setter Property="Background" Value="{StaticResource GroundBrush}" />
<Setter Property="CornerRadius" Value="21,21,0,0" /> <Setter Property="CornerRadius" Value="21,21,0,0" />
<Setter Property="ClipToBounds" Value="True" /> <Setter Property="ClipToBounds" Value="True" />
<Setter Property="Padding" Value="0,8,0,0" />
</Style> </Style>
<Style Selector="Border.storyMark"> <Style Selector="Border.storyMark">
<Setter Property="Width" Value="44" /> <Setter Property="Width" Value="44" />
@@ -780,22 +1098,6 @@
<Setter Property="Fill" Value="{StaticResource BrandRedLightBrush}" /> <Setter Property="Fill" Value="{StaticResource BrandRedLightBrush}" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
</Style> </Style>
<Style Selector="Ellipse.callout">
<Setter Property="Fill" Value="{StaticResource SurfaceBrush}" />
<Setter Property="Stroke" Value="{StaticResource TextSoftBrush}" />
<Setter Property="StrokeThickness" Value="1.2" />
</Style>
<Style Selector="Path.calloutLine">
<Setter Property="Stroke" Value="{StaticResource TextSoftBrush}" />
<Setter Property="StrokeThickness" Value="1.2" />
<Setter Property="StrokeDashArray" Value="2,3" />
</Style>
<Style Selector="Rectangle.slot">
<Setter Property="Fill" Value="{StaticResource LineBrush}" />
</Style>
<Style Selector="Ellipse.led">
<Setter Property="Fill" Value="{StaticResource GoodBrush}" />
</Style>
<Style Selector="Border.farewell"> <Style Selector="Border.farewell">
<Setter Property="Background" Value="{StaticResource GroundBrush}" /> <Setter Property="Background" Value="{StaticResource GroundBrush}" />
</Style> </Style>
+108 -65
View File
@@ -5,102 +5,145 @@
FlowDirection="LeftToRight" FlowDirection="LeftToRight"
Width="360" Height="212" ClipToBounds="True"> Width="360" Height="212" ClipToBounds="True">
<Canvas Width="360" Height="212"> <Canvas Width="360" Height="212">
<Rectangle Classes="sky" Width="360" Height="212" />
<Ellipse Classes="dusk" Canvas.Left="-90" Canvas.Top="30" Width="540" Height="240" />
<Canvas x:Name="Sky" /> <Canvas x:Name="Sky" />
<Ellipse x:Name="Halo" Classes="glow" Width="240" Height="170" /> <Border x:Name="Meteor" Classes="meteor" Width="54" Height="1.6" Canvas.Left="-27" Canvas.Top="-0.8" />
<Canvas x:Name="Clouds">
<Path Classes="cloud" Data="{StaticResource ArtCloud}" Canvas.Left="-21" Canvas.Top="-10" />
<Path Classes="cloud" Data="{StaticResource ArtCloud}" Canvas.Left="-21" Canvas.Top="-10" />
<Path Classes="cloud" Data="{StaticResource ArtCloud}" Canvas.Left="-21" Canvas.Top="-10" />
</Canvas>
<Ellipse x:Name="Halo" Classes="halo" Width="260" Height="190" />
<Ellipse Classes="atmosphere" Canvas.Left="-144" Canvas.Top="96" Width="648" Height="648" />
<Ellipse Classes="planet" Canvas.Left="-110" Canvas.Top="130" Width="580" Height="580" /> <Ellipse Classes="planet" Canvas.Left="-110" Canvas.Top="130" Width="580" Height="580" />
<Ellipse Classes="meridian" Canvas.Left="58" Canvas.Top="130" Width="244" Height="580" /> <Canvas x:Name="Towns" />
<Ellipse Classes="meridian" Canvas.Left="-57" Canvas.Top="130" Width="474" Height="580" />
<Ellipse Classes="meridian" Canvas.Left="25" Canvas.Top="161" Width="310" Height="28" />
<Ellipse Classes="meridian" Canvas.Left="-35" Canvas.Top="207" Width="430" Height="36" />
<Ellipse Classes="rim" Canvas.Left="-110" Canvas.Top="130" Width="580" Height="580" /> <Ellipse Classes="rim" Canvas.Left="-110" Canvas.Top="130" Width="580" Height="580" />
<Canvas x:Name="Hamlets">
<Image Source="{StaticResource ArtHamlet}" Width="10" Height="10" />
<Image Source="{StaticResource ArtHamlet}" Width="10" Height="10" />
<Image Source="{StaticResource ArtHamlet}" Width="10" Height="10" />
<Image Source="{StaticResource ArtHamlet}" Width="10" Height="10" />
</Canvas>
<Canvas x:Name="Trees">
<Image Source="{StaticResource ArtPine}" Width="20" Height="36" />
<Image Source="{StaticResource ArtPine}" Width="20" Height="36" />
<Image Source="{StaticResource ArtOak}" Width="26" Height="36" />
</Canvas>
<Path x:Name="TrailOne" Classes="trail" /> <Path x:Name="TrailOne" Classes="trail" />
<Path x:Name="TrailTwo" Classes="trail" /> <Path x:Name="TrailTwo" Classes="trail" />
<Canvas x:Name="Block" Width="72" Height="112"> <Canvas x:Name="Block" Width="76" Height="126">
<Ellipse Classes="shadow" Canvas.Left="-4" Canvas.Top="105" Width="80" Height="10" /> <Ellipse Classes="contact" Canvas.Left="-8" Canvas.Top="114" Width="94" Height="18" />
<Path Classes="wall" Data="{StaticResource ArtBlockFront}" /> <Image Source="{StaticResource ArtBlock}" Canvas.Top="-10" Width="76" Height="136" />
<Path Classes="wall side" Data="{StaticResource ArtBlockSide}" />
<Path Classes="flatRoof" Data="{StaticResource ArtBlockTop}" />
<Canvas x:Name="Flats" /> <Canvas x:Name="Flats" />
<Path Classes="door" Data="{StaticResource ArtBlockGate}" /> <Ellipse x:Name="ScanGlow" Classes="windowGlow" Canvas.Left="-11" Canvas.Top="-10.5" Width="30" Height="32" />
<Border Classes="plate" Canvas.Left="21" Canvas.Top="86" Width="14" Height="11"> <Rectangle x:Name="Scan" Classes="scan" Width="8" Height="11" RadiusX="1" RadiusY="1" />
<Path x:Name="GateLight" Classes="gateLight" Data="{StaticResource ArtBlockGate}" />
<Ellipse x:Name="Beacon" Classes="beacon" Canvas.Left="17.5" Canvas.Top="-9.5" Width="5" Height="5" />
<Ellipse x:Name="BlockPlateGlow" Classes="windowGlow" Canvas.Left="12" Canvas.Top="76" Width="36" Height="36" />
<Border Classes="plate" Canvas.Left="23" Canvas.Top="89" Width="14" Height="10">
<Path Classes="digit" Data="{StaticResource ArtDigitFour}" /> <Path Classes="digit" Data="{StaticResource ArtDigitFour}" />
</Border> </Border>
</Canvas> </Canvas>
<Canvas x:Name="House" Width="80" Height="76"> <Canvas x:Name="Smoke" />
<Ellipse Classes="shadow" Canvas.Left="-6" Canvas.Top="67" Width="92" Height="10" />
<Ellipse x:Name="WindowGlow" Classes="warmGlow" Canvas.Left="14" Canvas.Top="6" Width="86" Height="64" /> <Canvas x:Name="House" Width="86" Height="84">
<Path Classes="wall" Data="{StaticResource ArtHouseFront}" /> <Ellipse Classes="contact" Canvas.Left="-6" Canvas.Top="70" Width="98" Height="18" />
<Path Classes="wall side" Data="{StaticResource ArtHouseSide}" /> <Ellipse x:Name="DoorSpill" Classes="doorSpill" Canvas.Left="-2" Canvas.Top="70" Width="46" Height="16" />
<Path Classes="roof" Data="{StaticResource ArtHouseRoof}" /> <Ellipse x:Name="Pool" Classes="windowGlow" Canvas.Left="44" Canvas.Top="66" Width="56" Height="20" />
<Path Classes="roofEdge" Data="{StaticResource ArtHouseEdge}" /> <Image Source="{StaticResource ArtHouse}" Width="86" Height="84" />
<Path Classes="door" Data="{StaticResource ArtHouseDoor}" /> <Ellipse x:Name="WindowGlow" Classes="windowGlow" Canvas.Left="44" Canvas.Top="33" Width="44" Height="44" />
<Path x:Name="DoorLight" Classes="doorLight" Data="{StaticResource ArtHouseDoor}" /> <Path x:Name="DoorLight" Classes="doorLight" Data="{StaticResource ArtHouseDoor}" />
<Path Classes="window" Data="{StaticResource ArtHouseWindow}" /> <Ellipse x:Name="PlateGlow" Classes="windowGlow" Canvas.Left="12" Canvas.Top="15" Width="38" Height="38" />
<Path Classes="mullion" Data="{StaticResource ArtHouseMullions}" /> <Border x:Name="Plate" Classes="plate" Canvas.Left="24" Canvas.Top="28.5" Width="14" Height="11">
<Ellipse x:Name="Round" Classes="window" Canvas.Left="23.5" Canvas.Top="28.5" Width="9" Height="9" />
<Ellipse x:Name="PlateGlow" Classes="warmGlow" Canvas.Left="8" Canvas.Top="15" Width="40" Height="35" />
<Border x:Name="Plate" Classes="plate" Canvas.Left="21" Canvas.Top="27" Width="14" Height="11">
<Path Classes="digit" Data="{StaticResource ArtDigitSeven}" /> <Path Classes="digit" Data="{StaticResource ArtDigitSeven}" />
</Border> </Border>
</Canvas> </Canvas>
<Path x:Name="CalloutLine" Classes="calloutLine" /> <Path x:Name="Beam" Classes="beam" />
<Grid x:Name="Callout" Width="58" Height="58"> <Grid x:Name="Callout" Width="64" Height="64" Canvas.Left="-32" Canvas.Top="-32">
<Ellipse Classes="calloutGlow" Margin="-12" />
<Ellipse Classes="callout" /> <Ellipse Classes="callout" />
<Canvas Width="32" Height="22" HorizontalAlignment="Center" VerticalAlignment="Center"> <Canvas Width="40" Height="30" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="0,4,0,0" RenderTransform="scale(1.2)">
<Border Classes="device" Width="32" Height="22" CornerRadius="4" /> <Ellipse Classes="contact" Canvas.Left="3" Canvas.Top="20" Width="34" Height="10" />
<Rectangle Classes="slot" Canvas.Left="6" Canvas.Top="7" Width="13" Height="2" /> <Image Source="{StaticResource ArtServer}" Width="40" Height="30" />
<Rectangle Classes="slot" Canvas.Left="6" Canvas.Top="13" Width="13" Height="2" /> <Ellipse x:Name="Led" Classes="led" Canvas.Left="12" Canvas.Top="16.2" Width="2.6" Height="2.6" />
<Ellipse x:Name="Led" Classes="led" Canvas.Left="23" Canvas.Top="9" Width="4.5" Height="4.5" />
</Canvas> </Canvas>
<Path x:Name="Orbit" Classes="orbit" Data="{StaticResource ArtOrbit}" />
</Grid> </Grid>
<Canvas x:Name="Phone" Width="24" Height="40"> <Canvas x:Name="Phone" Width="28" Height="58">
<Border Classes="device" Width="24" Height="40" CornerRadius="5" /> <Ellipse Classes="contact" Canvas.Left="1" Canvas.Top="50" Width="26" Height="7" />
<Border Classes="screen" Canvas.Left="3" Canvas.Top="5" Width="18" Height="29" CornerRadius="1.5" /> <Canvas x:Name="PhoneBody" Width="28" Height="48">
<Border x:Name="Flash" Classes="flash" Canvas.Left="3" Canvas.Top="5" Width="18" Height="29" CornerRadius="1.5" /> <Border Classes="device" Canvas.Left="1" Width="26" Height="46" CornerRadius="6" />
</Canvas> <Border Classes="screen" Canvas.Left="3.5" Canvas.Top="5" Width="21" Height="34" CornerRadius="2.5">
<Grid>
<Canvas x:Name="Tablet" Width="44" Height="32"> <Image x:Name="Viewfinder" Source="{StaticResource ArtPhoto}" Stretch="UniformToFill" />
<Border Classes="device" Width="44" Height="32" CornerRadius="4" /> <Image x:Name="Outbox" Source="{StaticResource ArtLetter}" Width="13" Height="9" Margin="0,4,0,0" />
<Border Classes="screen" Canvas.Left="3" Canvas.Top="3" Width="38" Height="26" CornerRadius="1.5" /> </Grid>
<Viewbox x:Name="Received" Canvas.Left="3" Canvas.Top="3" Width="38" Height="26" Stretch="Fill">
<Canvas Classes="photo" Width="18" Height="13">
<Ellipse Classes="sun" Canvas.Left="11" Canvas.Top="2" Width="4" Height="4" />
<Path Classes="hills" Data="{StaticResource ArtHills}" />
</Canvas>
</Viewbox>
</Canvas>
<Border x:Name="Photo" Classes="photoFrame" Width="24" Height="19">
<Viewbox Stretch="Fill">
<Canvas Classes="photo" Width="18" Height="13">
<Ellipse Classes="sun" Canvas.Left="11" Canvas.Top="2" Width="4" Height="4" />
<Path Classes="hills" Data="{StaticResource ArtHills}" />
</Canvas>
</Viewbox>
</Border> </Border>
<Border x:Name="Flash" Classes="flash" Canvas.Left="3.5" Canvas.Top="5" Width="21" Height="34" CornerRadius="2.5" />
<Path Classes="sheen" Data="{StaticResource ArtPhoneSheen}" />
<Ellipse Classes="lens" Canvas.Left="12.8" Canvas.Top="1.8" Width="2.4" Height="2.4" />
<Rectangle Classes="homeBar" Canvas.Left="10" Canvas.Top="41.6" Width="8" Height="1.4" RadiusX="0.7" RadiusY="0.7" />
</Canvas>
</Canvas>
<Grid x:Name="Packet" Width="22" Height="22"> <Canvas x:Name="Tablet" Width="54" Height="48">
<Ellipse Classes="glow" /> <Ellipse Classes="contact" Canvas.Left="3" Canvas.Top="40" Width="48" Height="8" />
<Ellipse Classes="packet" Width="8" Height="8" /> <Canvas x:Name="TabletBody" Width="54" Height="40">
</Grid> <Ellipse x:Name="TabletGlow" Classes="screenGlow" Canvas.Left="-14" Canvas.Top="-16" Width="82" Height="68" />
<Border Classes="device" Canvas.Left="2" Width="50" Height="35" CornerRadius="5" />
<Border Classes="screen" Canvas.Left="5" Canvas.Top="3.5" Width="44" Height="28" CornerRadius="2">
<Image x:Name="Received" Source="{StaticResource ArtPhoto}" Stretch="UniformToFill" />
</Border>
<Path Classes="sheen" Data="{StaticResource ArtTabletSheen}" />
</Canvas>
</Canvas>
<Path x:Name="Heart" Classes="heart" Data="{StaticResource ArtHeart}" Width="24" Height="24" /> <Path x:Name="Comet" Classes="comet">
<Path.Stroke>
<LinearGradientBrush>
<GradientStop Offset="0" Color="{StaticResource BrandRedClear}" />
<GradientStop Offset="0.7" Color="{StaticResource BrandRedLight}" />
<GradientStop Offset="1" Color="{StaticResource TextStrong}" />
</LinearGradientBrush>
</Path.Stroke>
</Path>
<Canvas x:Name="Sparks" />
<Ellipse x:Name="RippleOne" Classes="ripple" Canvas.Left="-20" Canvas.Top="-20" Width="40" Height="40" />
<Ellipse x:Name="RippleTwo" Classes="ripple" Canvas.Left="-20" Canvas.Top="-20" Width="40" Height="40" />
<Grid x:Name="Yes" Width="22" Height="22"> <Border x:Name="Photo" Classes="polaroid" Canvas.Left="-13" Canvas.Top="-12" Width="26" Height="24">
<Image Source="{StaticResource ArtPhoto}" Stretch="UniformToFill" />
</Border>
<Image x:Name="Letter" Source="{StaticResource ArtLetter}" Canvas.Left="-11" Canvas.Top="-7.5" Width="22" Height="15" />
<Path x:Name="Heart" Classes="heart" Data="{StaticResource ArtHeart}" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24" />
<Canvas x:Name="Hearts">
<Path Classes="heartlet" Data="{StaticResource ArtHeart}" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24" />
<Path Classes="heartlet" Data="{StaticResource ArtHeart}" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24" />
<Path Classes="heartlet" Data="{StaticResource ArtHeart}" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24" />
</Canvas>
<Grid x:Name="Yes" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24">
<Ellipse Classes="badgeGlow good" Margin="-10" />
<Ellipse Classes="badge good" /> <Ellipse Classes="badge good" />
<Viewbox Width="13" Height="13"> <Viewbox Width="14" Height="14">
<Path Classes="badgeMark" Data="{StaticResource IconCheck}" /> <Path Classes="badgeMark" Data="{StaticResource IconCheck}" />
</Viewbox> </Viewbox>
</Grid> </Grid>
<Grid x:Name="Unsure" Canvas.Left="-12" Canvas.Top="-12" Width="24" Height="24">
<Grid x:Name="Unsure" Width="22" Height="22"> <Ellipse Classes="badgeGlow caution" Margin="-10" />
<Ellipse Classes="badge caution" /> <Ellipse Classes="badge caution" />
<Path Classes="badgeMark" Data="{StaticResource ArtQuestion}" Width="7" Height="11" <Path Classes="badgeMark" Data="{StaticResource ArtQuestion}" Width="7" Height="11"
HorizontalAlignment="Center" VerticalAlignment="Center" /> HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid> </Grid>
<Rectangle Classes="vignette" Width="360" Height="212" IsHitTestVisible="False" />
</Canvas> </Canvas>
</v:Animated> </v:Animated>
+324 -124
View File
@@ -14,54 +14,96 @@ public partial class IntroScene : Animated
const double Radius = 290; const double Radius = 290;
const double Loop = 8; const double Loop = 8;
static readonly (double X, double Y, double Pace)[] StarSpots = static readonly double[] Hamlet = [9, 23, 337, 351];
[ static readonly int[] Search = [5, 0, 10, 3, 8, 1, 11, 6, 2, 9, 4, 7];
(22, 18, 1.1), (64, 46, 0.7), (98, 12, 1.4), (140, 34, 0.9), (205, 14, 1.2), (250, 42, 0.8), (292, 16, 1.3), static readonly int[] Television = [3, 10];
(334, 50, 1.0), (318, 94, 0.6), (30, 88, 1.2), (110, 72, 0.9), (268, 86, 1.1), (350, 20, 0.7), (176, 8, 1.0), static readonly double[][] Groves = [[112, 126, 244], [104, 117, 284], [292, 306, 186]];
]; static readonly double[][] Growth = [[1, 0.72, 0.95], [1, 0.72, 0.95], [1, 0.72, 0.8]];
static readonly double[] FlatLight = [0.9, 0.25, 0.7, 0.35, 0.85, 0.2, 0.6, 0.9, 0.3, 0.75, 0.4, 0.8]; readonly (Control Star, double Pace, double Shift, double Glow)[] stars;
readonly (Control Light, double Pace, double Shift)[] lights;
readonly Ellipse[] stars;
readonly Rectangle[] flats; readonly Rectangle[] flats;
readonly Ellipse[] puffs;
readonly Ellipse[] sparks;
readonly double[] lean = new double[3];
Point from; Point from;
Point home; Point home;
Point to; Point to;
Point bend; Point bend;
Point turn; Point turn;
double heartTop; Point chimney;
Point heart;
Point badge;
Point doubt;
Point plate;
Point callout;
public IntroScene() public IntroScene()
{ {
InitializeComponent(); InitializeComponent();
stars = StarSpots.Select(spot => stars = Enumerable.Range(1, 56)
.Select(i => new Point(Fraction(i * 0.7548776662) * 356 + 2, Fraction(i * 0.5698402910) * 160 + 3))
.Where(spot => spot.Y < Ground(spot.X) - 16)
.Select((spot, i) =>
{ {
var star = new Ellipse { Width = 2.4, Height = 2.4 }; var bright = i % 6 == 2;
star.Classes.Add("star"); var size = bright ? 10 : 0.9 + 1.5 * Fraction(i * 0.381966) * Fraction(i * 0.381966);
Canvas.SetLeft(star, spot.X); var star = bright ? (Control)new Avalonia.Controls.Shapes.Path() : new Ellipse { Width = size, Height = size };
Canvas.SetTop(star, spot.Y); star.Classes.Add(bright ? "sparkle" : "star");
star.RenderTransform = new ScaleTransform();
Canvas.SetLeft(star, spot.X - size / 2);
Canvas.SetTop(star, spot.Y - size / 2);
Sky.Children.Add(star); Sky.Children.Add(star);
return star; return (star, 0.6 + 1.6 * Fraction(i * 0.2763932), i * 2.39996, 0.3 + 0.7 * Fraction(i * 0.6180339887));
})
.ToArray();
lights = Enumerable.Range(0, 7).SelectMany(town =>
{
var x = 22 + town * 53 + 10 * Math.Sin(town * 2.1);
var y = Math.Min(Ground(x) + 24 + 26 * Fraction(town * 0.618034), 204);
var glow = new Ellipse { Width = 46, Height = 16 };
glow.Classes.Add("townGlow");
Canvas.SetLeft(glow, x - 23);
Canvas.SetTop(glow, y - 8);
Towns.Children.Add(glow);
return Enumerable.Range(1, 6).Select(i =>
{
var size = 0.9 + 1.1 * Fraction((town * 6 + i) * 0.381966);
var light = new Ellipse { Width = size, Height = size };
light.Classes.Add("town");
Canvas.SetLeft(light, x + 30 * (Fraction((town * 6 + i) * 0.7548776662) - 0.5));
Canvas.SetTop(light, y + 9 * (Fraction((town * 6 + i) * 0.5698402910) - 0.5));
Towns.Children.Add(light);
return ((Control)light, 0.4 + 0.9 * Fraction(i * 0.2763932), town * 1.3 + i * 2.39996);
});
}).ToArray(); }).ToArray();
flats = Enumerable.Range(0, 12).Select(i => flats = Enumerable.Range(0, 12).Select(i =>
{ {
var flat = new Rectangle { Width = 8, Height = 9 }; var flat = new Rectangle { Width = 8, Height = 11, RadiusX = 1, RadiusY = 1 };
flat.Classes.Add("flat"); flat.Classes.Add("flat");
Canvas.SetLeft(flat, 10 + i % 3 * 14); if (i is 1 or 5 or 8) flat.Classes.Add("dark");
Canvas.SetTop(flat, 22 + i / 3 * 16); if (Television.Contains(i)) flat.Classes.Add("cool");
Canvas.SetLeft(flat, 12 + i % 3 * 14);
Canvas.SetTop(flat, 24 + i / 3 * 17);
Flats.Children.Add(flat); Flats.Children.Add(flat);
return flat; return flat;
}).ToArray(); }).ToArray();
foreach (var mover in new Control[] { Photo, Packet, Heart, Yes, Unsure, Callout }) puffs = Enumerable.Range(0, 5).Select(_ => Speck(Smoke, "smoke", 10)).ToArray();
{ sparks = Enumerable.Range(0, 7).Select(_ => Speck(Sparks, "spark", 3.4)).ToArray();
mover.RenderTransform = new ScaleTransform(); foreach (var mover in new Control[] { Photo, Letter, Heart, Yes, Unsure, Callout, RippleOne, RippleTwo, Meteor }
mover.RenderTransformOrigin = RelativePoint.Center; .Concat(Clouds.Children).Concat(Hearts.Children)) mover.RenderTransform = Mobile();
} Orbit.RenderTransform = new RotateTransform();
PhoneBody.RenderTransform = new TranslateTransform();
TabletBody.RenderTransform = new TranslateTransform();
Received.RenderTransform = new ScaleTransform();
Outbox.RenderTransform = new TranslateTransform();
Scan.RenderTransform = new TranslateTransform();
ScanGlow.RenderTransform = new TranslateTransform();
Lay(); Lay();
Place(Poster); Place(Poster);
} }
protected override double Poster => Step switch { 0 => 4.5, 1 => 5.0, _ => 5.2 }; protected override double Poster => Step switch { 0 => 4.7, 1 => 5.0, _ => 5.3 };
public int Step public int Step
{ {
@@ -81,50 +123,87 @@ public partial class IntroScene : Animated
{ {
var sharing = Step == 0; var sharing = Step == 0;
var single = Step == 1; var single = Step == 1;
Tablet.IsVisible = Photo.IsVisible = Heart.IsVisible = Round.IsVisible = sharing; Tablet.IsVisible = Photo.IsVisible = Heart.IsVisible = Hearts.IsVisible = Viewfinder.IsVisible = sharing;
Packet.IsVisible = Yes.IsVisible = Plate.IsVisible = !sharing; Letter.IsVisible = Yes.IsVisible = Plate.IsVisible = Outbox.IsVisible = DoorLight.IsVisible = DoorSpill.IsVisible = !sharing;
Callout.IsVisible = CalloutLine.IsVisible = PlateGlow.IsVisible = single; Callout.IsVisible = Beam.IsVisible = PlateGlow.IsVisible = single;
Block.IsVisible = Unsure.IsVisible = Step == 2; Block.IsVisible = Unsure.IsVisible = Step == 2;
TrailTwo.IsVisible = !single; TrailTwo.IsVisible = !single;
var phone = Stand(Phone, sharing ? 56 : 44, 12, 40, 1.25); for (var i = 0; i < Hamlet.Length; i++) Stand(Hamlets.Children[i], Hamlet[i], 5, 10, 1);
var house = Stand(House, Step switch { 0 => 180, 1 => 204, _ => 128 }, 40, 72, Step switch { 0 => 1.5, 1 => 1.6, _ => 1.25 }); for (var i = 0; i < lean.Length; i++) lean[i] = Stand(Trees.Children[i], Groves[Step][i], i == 2 ? 13 : 10, 36, Growth[Step][i]).Degrees;
from = Turn(phone, 0, -44); var phone = Stand(Phone, sharing ? 60 : 48, 14, 56, 1.2, 0.5);
Canvas.SetLeft(Halo, house.At.X - 120); var house = Stand(House, Step switch { 0 => 180, 1 => 194, _ => 128 }, 42, 80, Step switch { 0 => 1.45, 1 => 1.5, _ => 1.2 });
Canvas.SetTop(Halo, house.At.Y - 130); from = Turn(phone, 0, -54);
chimney = Turn(house, 12, -80);
var glow = Turn(house, 12, -42);
Canvas.SetLeft(Halo, glow.X - Halo.Width / 2);
Canvas.SetTop(Halo, glow.Y - Halo.Height / 2);
if (sharing) if (sharing)
{ {
var tablet = Stand(Tablet, 304, 22, 32, 1.25); var tablet = Stand(Tablet, 300, 27, 46, 1.15, 0.5);
home = Turn(house, -6, -30); home = Turn(house, 24, -25);
to = Turn(tablet, 0, -16); to = Turn(tablet, 0, -27);
bend = new Point((from.X + home.X) / 2, 18); bend = new Point((from.X + home.X) / 2, 4);
turn = new Point((home.X + to.X) / 2, 18); turn = new Point((home.X + to.X) / 2, 10);
Put(Heart, Turn(tablet, 10, -44)); heart = Turn(tablet, 16, -58);
TrailOne.Data = Curve(from, bend, home); TrailOne.Data = Arc(from, bend, home);
TrailTwo.Data = Curve(home, turn, to); TrailTwo.Data = Arc(home, turn, to);
return; return;
} }
home = Turn(house, -12, -8); home = Turn(house, -21, -9);
bend = new Point((from.X + home.X) / 2, 40); bend = new Point((from.X + home.X) / 2, single ? 22 : 40);
Put(Yes, Turn(house, -4, single ? -73 : -82)); badge = Turn(house, -43, -34);
TrailOne.Data = Curve(from, bend, home); TrailOne.Data = Arc(from, bend, home);
if (single) if (single)
{ {
var window = Turn(house, 24, -26); plate = Turn(house, -11, -46);
var bubble = new Point(318, 46); callout = new Point(310, 56);
Put(Callout, bubble); Beam.Data = Funnel(Turn(house, 24, -25), callout);
CalloutLine.Data = Geometry.Parse(FormattableString.Invariant($"M {window.X:0.##} {window.Y:0.##} L {bubble.X - 22:0.##} {bubble.Y + 18:0.##}"));
return; return;
} }
var block = Stand(Block, 240, 34, 110, 0.95); var block = Stand(Block, 248, 38, 124, 0.95);
to = Turn(block, -6, -5); to = Turn(block, -8, -9);
turn = new Point((from.X + to.X) / 2, -14); turn = new Point((from.X + to.X) / 2 + 20, -18);
Put(Unsure, Turn(block, 2, -122)); doubt = Turn(block, -48, -42);
TrailTwo.Data = Curve(from, turn, to); TrailTwo.Data = Arc(from, turn, to);
} }
protected override void Place(double seconds) protected override void Place(double seconds)
{ {
for (var i = 0; i < stars.Length; i++) stars[i].Opacity = 0.2 + 0.45 * (0.5 + 0.5 * Math.Sin(seconds * StarSpots[i].Pace + i * 1.7)); foreach (var (star, pace, shift, glow) in stars)
{
var shine = 0.5 + 0.5 * Math.Sin(seconds * pace + shift);
star.Opacity = glow * (0.3 + 0.7 * shine);
if (star is Ellipse) continue;
var scale = (ScaleTransform)star.RenderTransform;
scale.ScaleX = scale.ScaleY = 0.35 + 0.45 * shine;
}
foreach (var (light, pace, shift) in lights) light.Opacity = 0.55 + 0.45 * Math.Sin(seconds * pace + shift);
for (var i = 0; i < Clouds.Children.Count; i++)
{
var drift = Fraction(i * 0.41 + seconds * (0.004 + 0.0025 * i)) * 470 - 55;
Move(Clouds.Children[i], new Point(drift, 46 + i * 24), 1.5 - 0.3 * i, 0, 0.55 + 0.2 * i);
}
for (var k = 0; k < puffs.Length; k++)
{
var rise = Fraction(seconds / 3.6 + (double)k / puffs.Length);
var at = new Point(chimney.X + rise * 11 + Math.Sin(rise * 5 + k) * 1.8, chimney.Y - rise * 32);
Move(puffs[k], at, 0.35 + 1.15 * rise, 0, 0.8 * Math.Sin(Math.PI * rise));
}
for (var i = 0; i < lean.Length; i++)
{
var sway = (RotateTransform)((TransformGroup)Trees.Children[i].RenderTransform).Children[1];
sway.Angle = lean[i] + 1.6 * Math.Sin(seconds * 1.15 + i * 2.1);
}
foreach (var i in Television) flats[i].Opacity = 0.55 + 0.45 * Math.Abs(Math.Sin(seconds * 5.3 + i) * Math.Sin(seconds * 2.9));
((TranslateTransform)PhoneBody.RenderTransform).Y = -2.5 - 1.8 * Math.Sin(seconds * 1.7);
((TranslateTransform)TabletBody.RenderTransform).Y = -2.5 - 1.8 * Math.Sin(seconds * 1.7 + 2);
((RotateTransform)Orbit.RenderTransform).Angle = seconds * 110 % 360;
Led.Opacity = Math.Sin(seconds * 13) * Math.Sin(seconds * 4.7) > 0.05 ? 1 : 0.25;
Beacon.Opacity = Math.Pow(0.5 + 0.5 * Math.Cos(seconds * 3.4), 6);
var streak = Fraction((seconds + 5) / 17) * 17 / 0.8;
Move(Meteor, new Point(310 - 140 * streak, 10 + 48 * streak), 1, 161, streak < 1 ? Math.Sin(Math.PI * streak) : 0);
Comet.Opacity = Photo.Opacity = Letter.Opacity = 0;
foreach (var spark in sparks) spark.Opacity = 0;
var t = seconds % Loop; var t = seconds % Loop;
if (Step == 0) Share(t); if (Step == 0) Share(t);
else if (Step == 1) Explain(t); else if (Step == 1) Explain(t);
@@ -133,68 +212,169 @@ public partial class IntroScene : Animated
void Share(double t) void Share(double t)
{ {
Flash.Opacity = 0.9 * Envelope(t, 0.25, 0.08, 0.35, 0.45); Flash.Opacity = 0.95 * Envelope(t, 0.3, 0.06, 0.36, 0.45);
var glow = Envelope(t, 2.05, 0.25, 2.7, 1.8); Ripple(RippleOne, t < 2 ? from : to, t < 2 ? (t - 0.32) / 0.8 : (t - 4.1) / 0.9);
WindowGlow.Opacity = 0.35 + 0.65 * glow; Ripple(RippleTwo, home, (t - 1.95) / 0.8);
Halo.Opacity = 0.55 + 0.45 * glow; var inside = Envelope(t, 1.95, 0.2, 2.6, 1.6);
Received.Opacity = Envelope(t, 4.05, 0.3, 7.3, 0.5); WindowGlow.Opacity = 0.35 + 0.65 * inside;
if (t is >= 0.5 and < 2.0) Halo.Opacity = 0.45 + 0.55 * inside;
if (t is >= 0.55 and < 2.0) Fly(Photo, from, bend, home, (t - 0.55) / 1.45, 0.3, true);
else if (t is >= 2.6 and < 4.15) Fly(Photo, home, turn, to, (t - 2.6) / 1.55, 0.3, true);
var shown = Envelope(t, 4.1, 0.3, 7.3, 0.5);
Received.Opacity = shown;
var reveal = (ScaleTransform)Received.RenderTransform;
reveal.ScaleX = reveal.ScaleY = 1.3 - 0.3 * EaseOut(Clamp((t - 4.1) / 0.7));
TabletGlow.Opacity = shown * (0.75 + 0.25 * Math.Sin(t * 2.4));
var love = t - 4.2;
Move(Heart, new Point(heart.X, heart.Y - 3 * Math.Sin(love * 2)), Spring(love), 10 * Math.Sin(love * 4) * Math.Exp(-love * 1.6),
Envelope(t, 4.2, 0.05, 6.9, 0.5));
for (var k = 0; k < Hearts.Children.Count; k++)
{ {
var u = (t - 0.5) / 1.5; var rise = (t - 4.5 - k * 0.45) / 1.9;
Show(Photo, Along(from, bend, home, Smooth(u)), 0.6 + 0.55 * Math.Sin(Math.PI * u), 1); var little = Hearts.Children[k];
} if (rise is < 0 or > 1)
else if (t is >= 2.0 and < 2.3) Show(Photo, home, 0.6 * (1 - (t - 2.0) / 0.3), 1);
else if (t is >= 2.55 and < 4.1)
{ {
var u = (t - 2.55) / 1.55; little.Opacity = 0;
Show(Photo, Along(home, turn, to, Smooth(u)), 0.45 + 0.6 * Math.Sin(Math.PI * u), 1); continue;
} }
else Photo.Opacity = 0; Move(little, new Point(heart.X + (k - 1) * 10 + Math.Sin(rise * 7 + k) * 3, heart.Y - 12 - rise * 30),
var heart = (t - 4.15) / 1.7; 0.3 + 0.12 * Math.Sin(Math.PI * rise), 0, 0.9 * Math.Sin(Math.PI * rise));
if (heart is < 0 or > 1)
{
Heart.Opacity = 0;
return;
} }
Grow(Heart, Pop(heart * 3), heart < 0.6 ? 1 : 1 - (heart - 0.6) / 0.4);
Canvas.SetTop(Heart, heartTop - 16 * heart);
} }
void Explain(double t) void Explain(double t)
{ {
Led.Opacity = 0.35 + 0.65 * (0.5 + 0.5 * Math.Sin(t * 2 * Math.PI / 1.4)); var open = Envelope(t, 0.2, 0.05, 7.2, 0.5);
Grow(Callout, Pop((t - 0.2) / 0.5), Envelope(t, 0.2, 0.1, 7.3, 0.5)); Move(Callout, callout, Spring(t - 0.2), 0, open);
CalloutLine.Opacity = 0.8 * Envelope(t, 0.5, 0.3, 7.3, 0.5); Beam.Opacity = Envelope(t, 0.5, 0.4, 7.2, 0.5);
WindowGlow.Opacity = 0.35 + 0.65 * Envelope(t, 0.2, 0.4, 7.3, 0.5); WindowGlow.Opacity = 0.4 + 0.6 * open;
PlateGlow.Opacity = Envelope(t, 1.2, 0.3, 7.2, 0.6) * (0.75 + 0.25 * Math.Sin((t - 1.2) * 5)); Halo.Opacity = 0.5 + 0.3 * open;
DoorLight.Opacity = 0.8 * Envelope(t, 3.7, 0.25, 7.2, 0.5); PlateGlow.Opacity = Envelope(t, 1.2, 0.3, 7.2, 0.6) * (0.75 + 0.25 * Math.Sin((t - 1.2) * 4));
Grow(Yes, Pop((t - 3.75) / 0.45), Envelope(t, 3.75, 0.1, 7.2, 0.5)); Ripple(RippleTwo, plate, (t - 1.25) / 0.9);
if (t is >= 2.2 and < 3.7) Show(Packet, Along(from, bend, home, Smooth((t - 2.2) / 1.5)), 1, 1); Send(t, 2.2);
else Packet.Opacity = 0; if (t is >= 2.2 and < 3.7) Fly(Letter, from, bend, home, (t - 2.2) / 1.5, 0.5, true);
var admitted = Envelope(t, 3.6, 0.2, 7.2, 0.5);
DoorLight.Opacity = 0.9 * admitted;
DoorSpill.Opacity = admitted;
Ripple(RippleOne, home, (t - 3.6) / 0.8);
Badge(Yes, badge, t - 3.75, Envelope(t, 3.75, 0.05, 7.2, 0.5));
} }
void Knock(double t) void Knock(double t)
{ {
DoorLight.Opacity = 0.8 * Envelope(t, 1.8, 0.25, 7.2, 0.5); Halo.Opacity = 0.5;
Grow(Yes, Pop((t - 1.85) / 0.45), Envelope(t, 1.85, 0.1, 7.2, 0.5)); WindowGlow.Opacity = 0.45;
Grow(Unsure, Pop((t - 4.45) / 0.45), Envelope(t, 4.45, 0.1, 7.2, 0.5)); Send(t, t < 2 ? 0.3 : 2.3);
if (t is >= 0.4 and < 1.8) Show(Packet, Along(from, bend, home, Smooth((t - 0.4) / 1.4)), 1, 1); if (t is >= 0.3 and < 1.6) Fly(Letter, from, bend, home, (t - 0.3) / 1.3, 0.5, true);
else if (t is >= 2.6 and < 4.2) Show(Packet, Along(from, turn, to, Smooth((t - 2.6) / 1.6)), 1, 1); else if (t is >= 2.3 and < 3.9) Fly(Letter, from, turn, to, (t - 2.3) / 1.6, 0.5, false);
else if (t is >= 4.2 and < 4.9) else if (t is >= 3.9 and < 6.9)
{ {
var u = (t - 4.2) / 0.7; var wait = t - 3.9;
Show(Packet, new Point(to.X - 7 * Math.Sin(Math.PI * u), to.Y - 3 * Math.Sin(Math.PI * u)), 1 - 0.5 * u, 1 - u); var shake = Math.Sin(wait * 12) * Math.Exp(-wait * 1.1);
Move(Letter, new Point(to.X + 3 * shake, to.Y - 3 - 2 * Math.Sin(wait * 2.4)), 0.8, 8 * shake, 1 - Smooth((t - 6.3) / 0.5));
} }
else Packet.Opacity = 0; var admitted = Envelope(t, 1.55, 0.2, 7.2, 0.5);
var searching = t is >= 4.45 and < 6.6 ? (int)((t - 4.45) / 0.16) % flats.Length : -1; DoorLight.Opacity = 0.9 * admitted;
for (var i = 0; i < flats.Length; i++) flats[i].Opacity = i == searching ? 1 : FlatLight[i] * 0.8; DoorSpill.Opacity = admitted;
Ripple(RippleOne, home, (t - 1.55) / 0.8);
Badge(Yes, badge, t - 1.7, Envelope(t, 1.7, 0.05, 7.2, 0.5));
Ripple(RippleTwo, to, (t - 3.9) / 0.8);
var waiting = Envelope(t, 3.9, 0.2, 6.6, 0.6);
GateLight.Opacity = 0.7 * waiting * (0.6 + 0.4 * Math.Sin(t * 6));
BlockPlateGlow.Opacity = waiting;
var look = t is >= 4.2 and < 6.6 ? Search[(int)((t - 4.2) / 0.2) % Search.Length] : -1;
Scan.Opacity = ScanGlow.Opacity = look < 0 ? 0 : 1;
if (look >= 0)
{
var x = 12 + look % 3 * 14;
var y = 24 + look / 3 * 17;
foreach (var lit in new[] { (TranslateTransform)Scan.RenderTransform, (TranslateTransform)ScanGlow.RenderTransform })
{
lit.X = x;
lit.Y = y;
}
}
Badge(Unsure, doubt, t - 4.4, Envelope(t, 4.4, 0.05, 7.2, 0.5));
} }
(Point At, double Degrees, double Size) Stand(Control art, double x, double baseX, double baseY, double size) void Send(double t, double leaving)
{ {
var dx = x - CenterX; var gone = Smooth((t - leaving + 0.25) / 0.3);
var at = new Point(x, CenterY - Math.Sqrt(Radius * Radius - dx * dx) + 1); var back = Smooth((t - leaving - 0.6) / 0.4);
var degrees = Math.Asin(dx / Radius) * 180 / Math.PI; ((TranslateTransform)Outbox.RenderTransform).Y = back > 0 ? 8 * (1 - back) : -14 * gone;
Outbox.Opacity = back > 0 ? back : 1 - gone;
}
void Fly(Control mover, Point a, Point c, Point b, double u, double tilt, bool enters)
{
var e = Ease(u);
var at = Along(a, c, b, e);
var heading = (c - a) * (1 - e) + (b - c) * e;
var degrees = Math.Clamp(Math.Atan2(heading.Y, heading.X) * 180 / Math.PI, -70, 70) * tilt;
var lift = 0.9 + 0.3 * Math.Sin(Math.PI * u);
var land = enters ? 1 - 0.65 * Smooth((u - 0.82) / 0.18) : 1 - 0.2 * Smooth((u - 0.8) / 0.2);
Move(mover, at, (0.45 + 0.55 * Smooth(u / 0.15)) * land * lift, degrees, enters ? 1 - Smooth((u - 0.92) / 0.08) : 1);
var tail = Math.Max(0, e - 0.32);
var start = Along(a, c, b, tail);
Comet.Data = Arc(start, Blossom(a, c, b, tail, e), at);
var brush = (LinearGradientBrush)Comet.Stroke;
brush.StartPoint = new RelativePoint(start, RelativeUnit.Absolute);
brush.EndPoint = new RelativePoint(at, RelativeUnit.Absolute);
var trail = Smooth(u / 0.1) * (1 - Smooth((u - 0.8) / 0.2));
Comet.Opacity = trail;
for (var k = 0; k < sparks.Length; k++)
{
var back = e - 0.045 * (k + 1);
if (back < 0) break;
var dust = Along(a, c, b, back);
var jitter = new Vector(Math.Sin(k * 2.3 + u * 17) * 2.6, Math.Cos(k * 1.7 + u * 13) * 2.6);
Move(sparks[k], dust + jitter, 1 - 0.11 * k, 0, trail * (1 - (double)k / sparks.Length));
}
}
static void Ripple(Control ring, Point at, double u)
{
if (u is < 0 or > 1)
{
ring.Opacity = 0;
return;
}
Move(ring, at, 0.2 + 1.1 * EaseOut(u), 0, 0.85 * (1 - u) * (1 - u));
}
static void Badge(Control badge, Point at, double since, double opacity) =>
Move(badge, new Point(at.X, at.Y - 2 * Math.Sin(since * 2.2)), Spring(since), 0, opacity);
static Ellipse Speck(Canvas layer, string kind, double size)
{
var speck = new Ellipse { Width = size, Height = size, RenderTransform = Mobile(), Opacity = 0 };
speck.Classes.Add(kind);
Canvas.SetLeft(speck, -size / 2);
Canvas.SetTop(speck, -size / 2);
layer.Children.Add(speck);
return speck;
}
static TransformGroup Mobile() => new() { Children = { new ScaleTransform(), new RotateTransform(), new TranslateTransform() } };
static void Move(Control mover, Point at, double size, double degrees, double opacity)
{
var group = (TransformGroup)mover.RenderTransform;
var scale = (ScaleTransform)group.Children[0];
scale.ScaleX = scale.ScaleY = Math.Max(0, size);
((RotateTransform)group.Children[1]).Angle = degrees;
var shift = (TranslateTransform)group.Children[2];
shift.X = at.X;
shift.Y = at.Y;
mover.Opacity = opacity;
}
static double Ground(double x) => CenterY - Math.Sqrt(Radius * Radius - (x - CenterX) * (x - CenterX));
static (Point At, double Degrees, double Size) Stand(Control art, double x, double baseX, double baseY, double size, double tilt = 1)
{
var at = new Point(x, Ground(x) + 1);
var degrees = tilt * Math.Asin((x - CenterX) / Radius) * 180 / Math.PI;
Canvas.SetLeft(art, at.X - baseX); Canvas.SetLeft(art, at.X - baseX);
Canvas.SetTop(art, at.Y - baseY); Canvas.SetTop(art, at.Y - baseY);
art.RenderTransformOrigin = new RelativePoint(baseX, baseY, RelativeUnit.Absolute); art.RenderTransformOrigin = new RelativePoint(baseX, baseY, RelativeUnit.Absolute);
@@ -210,13 +390,55 @@ public partial class IntroScene : Animated
return new Point(stand.At.X + x * Math.Cos(radians) - y * Math.Sin(radians), stand.At.Y + x * Math.Sin(radians) + y * Math.Cos(radians)); return new Point(stand.At.X + x * Math.Cos(radians) - y * Math.Sin(radians), stand.At.Y + x * Math.Sin(radians) + y * Math.Cos(radians));
} }
static Geometry Curve(Point a, Point c, Point b) => static Geometry Arc(Point a, Point c, Point b)
Geometry.Parse(FormattableString.Invariant($"M {a.X:0.##} {a.Y:0.##} Q {c.X:0.##} {c.Y:0.##} {b.X:0.##} {b.Y:0.##}")); {
var geometry = new StreamGeometry();
using var context = geometry.Open();
context.BeginFigure(a, false);
context.QuadraticBezierTo(c, b);
context.EndFigure(false);
return geometry;
}
static Geometry Funnel(Point from, Point to)
{
var along = (Vector)(to - from);
var across = new Vector(-along.Y, along.X) / along.Length;
var geometry = new StreamGeometry();
using var context = geometry.Open();
context.BeginFigure(from + across * 3, true);
context.LineTo(to + across * 30);
context.LineTo(to - across * 30);
context.LineTo(from - across * 3);
context.EndFigure(true);
return geometry;
}
static Point Along(Point a, Point c, Point b, double u) => static Point Along(Point a, Point c, Point b, double u) =>
new((1 - u) * (1 - u) * a.X + 2 * (1 - u) * u * c.X + u * u * b.X, (1 - u) * (1 - u) * a.Y + 2 * (1 - u) * u * c.Y + u * u * b.Y); new((1 - u) * (1 - u) * a.X + 2 * (1 - u) * u * c.X + u * u * b.X, (1 - u) * (1 - u) * a.Y + 2 * (1 - u) * u * c.Y + u * u * b.Y);
static double Smooth(double u) => u * u * (3 - 2 * u); static Point Blossom(Point a, Point c, Point b, double s, double u) =>
new((1 - s) * (1 - u) * a.X + ((1 - s) * u + s * (1 - u)) * c.X + s * u * b.X, (1 - s) * (1 - u) * a.Y + ((1 - s) * u + s * (1 - u)) * c.Y + s * u * b.Y);
static double Fraction(double x) => x - Math.Floor(x);
static double Clamp(double u) => Math.Clamp(u, 0, 1);
static double Smooth(double u)
{
u = Clamp(u);
return u * u * (3 - 2 * u);
}
static double Ease(double u)
{
u = Clamp(u);
return u < 0.5 ? 4 * u * u * u : 1 - Math.Pow(2 - 2 * u, 3) / 2;
}
static double EaseOut(double u) => 1 - Math.Pow(1 - Clamp(u), 3);
static double Spring(double since) => since <= 0 ? 0 : 1 - Math.Exp(-7 * since) * Math.Cos(13 * since);
static double Envelope(double t, double start, double rise, double end, double fall) static double Envelope(double t, double start, double rise, double end, double fall)
{ {
@@ -224,26 +446,4 @@ public partial class IntroScene : Animated
if (t < start + rise) return (t - start) / rise; if (t < start + rise) return (t - start) / rise;
return t <= end ? 1 : 1 - (t - end) / fall; return t <= end ? 1 : 1 - (t - end) / fall;
} }
static double Pop(double u) => u <= 0 ? 0 : u >= 1 ? 1 : u < 0.6 ? 1.18 * u / 0.6 : 1.18 - 0.18 * (u - 0.6) / 0.4;
void Put(Control mover, Point at)
{
Canvas.SetLeft(mover, at.X - mover.Width / 2);
Canvas.SetTop(mover, at.Y - mover.Height / 2);
if (mover == Heart) heartTop = at.Y - mover.Height / 2;
}
void Show(Control mover, Point at, double size, double opacity)
{
Put(mover, at);
Grow(mover, size, opacity);
}
static void Grow(Control mover, double size, double opacity)
{
mover.Opacity = opacity;
var scale = (ScaleTransform)mover.RenderTransform;
scale.ScaleX = scale.ScaleY = Math.Max(0, size);
}
} }
+15 -3
View File
@@ -114,7 +114,7 @@
<Border x:Name="WishCard" Classes="story"> <Border x:Name="WishCard" Classes="story">
<StackPanel> <StackPanel>
<Border Classes="scene"> <Border Classes="scene">
<Grid> <Grid FlowDirection="LeftToRight">
<Viewbox Stretch="Uniform"> <Viewbox Stretch="Uniform">
<v:IntroScene x:Name="WishScene" Step="0" /> <v:IntroScene x:Name="WishScene" Step="0" />
</Viewbox> </Viewbox>
@@ -133,7 +133,7 @@
<Border x:Name="WayCard" Classes="story"> <Border x:Name="WayCard" Classes="story">
<StackPanel> <StackPanel>
<Border Classes="scene"> <Border Classes="scene">
<Grid> <Grid FlowDirection="LeftToRight">
<Viewbox Stretch="Uniform"> <Viewbox Stretch="Uniform">
<v:IntroScene x:Name="WayScene" Step="1" /> <v:IntroScene x:Name="WayScene" Step="1" />
</Viewbox> </Viewbox>
@@ -225,7 +225,7 @@
<StackPanel Spacing="6" IsVisible="{Binding HasResults}"> <StackPanel Spacing="6" IsVisible="{Binding HasResults}">
<TextBlock Classes="section" Text="{s:Tr PathTitle}" HorizontalAlignment="Center" /> <TextBlock Classes="section" Text="{s:Tr PathTitle}" HorizontalAlignment="Center" />
<ItemsControl ItemsSource="{Binding Path}" MaxWidth="580" Margin="16,8,16,0"> <ItemsControl ItemsSource="{Binding Path}" MaxWidth="580" Margin="16,8,16,0" ClipToBounds="False">
<ItemsControl.ItemTemplate> <ItemsControl.ItemTemplate>
<DataTemplate x:DataType="vm:PathNodeViewModel"> <DataTemplate x:DataType="vm:PathNodeViewModel">
<StackPanel> <StackPanel>
@@ -234,6 +234,10 @@
Classes.flowing="{Binding IsRunning}" Classes.lit="{Binding IsLit}" /> Classes.flowing="{Binding IsRunning}" Classes.lit="{Binding IsLit}" />
<Path Classes="wire" Data="{StaticResource WireToStart}" IsVisible="{Binding WireToStart}" <Path Classes="wire" Data="{StaticResource WireToStart}" IsVisible="{Binding WireToStart}"
Classes.flowing="{Binding IsRunning}" Classes.lit="{Binding IsLit}" /> Classes.flowing="{Binding IsRunning}" Classes.lit="{Binding IsLit}" />
<Path Classes="packets" Data="{StaticResource WireToEnd}" IsVisible="{Binding WireToEnd}"
Classes.flowing="{Binding IsRunning}" />
<Path Classes="packets" Data="{StaticResource WireToStart}" IsVisible="{Binding WireToStart}"
Classes.flowing="{Binding IsRunning}" />
<StackPanel Spacing="6" Margin="0,12" HorizontalAlignment="Center" VerticalAlignment="Center"> <StackPanel Spacing="6" Margin="0,12" HorizontalAlignment="Center" VerticalAlignment="Center">
<Border Classes="linkTag" IsVisible="{Binding ShowLink}"> <Border Classes="linkTag" IsVisible="{Binding ShowLink}">
<StackPanel Orientation="Horizontal" Spacing="6"> <StackPanel Orientation="Horizontal" Spacing="6">
@@ -269,10 +273,18 @@
AutomationProperties.AutomationId="{Binding AutomationId}" AutomationProperties.AutomationId="{Binding AutomationId}"
AutomationProperties.Name="{Binding Title}"> AutomationProperties.Name="{Binding Title}">
<Grid Width="90" Height="90" Classes="node" Classes.lit="{Binding IsLit}"> <Grid Width="90" Height="90" Classes="node" Classes.lit="{Binding IsLit}">
<Ellipse Classes="aura" Margin="-18"
Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}"
Classes.bad="{Binding IsBad}" />
<Ellipse Classes="pulse" Width="90" Height="90" IsVisible="{Binding IsRunning}" /> <Ellipse Classes="pulse" Width="90" Height="90" IsVisible="{Binding IsRunning}" />
<Ellipse Classes="disc" Width="90" Height="90" <Ellipse Classes="disc" Width="90" Height="90"
Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}" Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}"
Classes.bad="{Binding IsBad}" Classes.unknown="{Binding IsUnknown}" /> Classes.bad="{Binding IsBad}" Classes.unknown="{Binding IsUnknown}" />
<Ellipse Classes="gloss" Margin="5" />
<Path Classes="spinner" Data="{StaticResource ArtNodeOrbit}" IsVisible="{Binding IsRunning}" />
<Ellipse Classes="burst" Width="90" Height="90"
Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}"
Classes.bad="{Binding IsBad}" />
<Viewbox Width="58" Height="58"> <Viewbox Width="58" Height="58">
<Grid Classes="art" Width="48" Height="48" Classes.running="{Binding IsRunning}" <Grid Classes="art" Width="48" Height="48" Classes.running="{Binding IsRunning}"
Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}" Classes.good="{Binding IsGood}" Classes.caution="{Binding IsCaution}"
+11 -1
View File
@@ -1,3 +1,5 @@
using System.Net;
using MeWeb.Core.History; using MeWeb.Core.History;
using MeWeb.Core.Knowledge; using MeWeb.Core.Knowledge;
using MeWeb.Core.Net; using MeWeb.Core.Net;
@@ -190,6 +192,14 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
await Step(CheckId.History, async () => await Step(CheckId.History, async () =>
{ {
await Task.WhenAll(onlineDone, provider, nat, behaviour, mail, names, device, speed); await Task.WhenAll(onlineDone, provider, nat, behaviour, mail, names, device, speed);
if (run.PublicV4 is null && run.PublicV6 is null)
{
var wan = Addresses.Parse(run.Router?.WanAddress);
var seen = Addresses.IsPublic(wan) ? wan : IPEndPoint.TryParse(run.Stun?.FirstMapped ?? run.Stun?.SecondMapped ?? "", out var mapped) && Addresses.IsPublic(mapped.Address) ? mapped.Address : null;
run.NetworkV4 = knowledge.Asn?.Find(seen);
run.BlockV4 = knowledge.Registry?.Find(seen);
run.Provider = knowledge.ProviderFor(run.NetworkV4?.Number ?? 0);
}
var number = (run.NetworkV4 ?? run.NetworkV6)?.Number ?? 0; var number = (run.NetworkV4 ?? run.NetworkV6)?.Number ?? 0;
var network = $"{number}/{run.Local?.GatewayV4 ?? run.Local?.Active?.Kind.ToString() ?? "none"}"; var network = $"{number}/{run.Local?.GatewayV4 ?? run.Local?.Active?.Kind.ToString() ?? "none"}";
run.History = history.Append(new HistoryEntry run.History = history.Append(new HistoryEntry
@@ -203,7 +213,7 @@ public class CheckRunner(IPlatform platform, ServerProbe server, KnowledgeBase k
PortLow = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.Low : 0, PortLow = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.Low : 0,
PortHigh = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.High : 0, PortHigh = run.Stun?.Ports?.Pattern is PortPattern.InOrder or PortPattern.Block ? run.Stun.Ports.High : 0,
}); });
Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.BlockV4, run.BlockV6, run.PublicV4 is not null || run.PublicV6 is not null, run.Local?.Active?.Kind == LinkKind.Mobile, run)); Settle(Verdicts.Provider(knowledge, run.NetworkV4, run.NetworkV6, run.BlockV4, run.BlockV6, run.PublicV4 is not null || run.PublicV6 is not null || run.NetworkV4 is not null, run.Local?.Active?.Kind == LinkKind.Mobile, run));
Settle(Verdicts.History(run.History)); Settle(Verdicts.History(run.History));
run.Uses = Uses.Decide(run); run.Uses = Uses.Decide(run);
}); });
+16 -5
View File
@@ -1,6 +1,8 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using MeWeb.Core.Net;
namespace MeWeb.Core.Knowledge; namespace MeWeb.Core.Knowledge;
public class ProviderFact public class ProviderFact
@@ -33,12 +35,12 @@ public partial class KnowledgeJsonContext : JsonSerializerContext
{ {
} }
public sealed class KnowledgeBase public sealed class KnowledgeBase(IPlatform platform)
{ {
readonly Lazy<AsnTable> asn = new(LoadAsn); readonly Lazy<AsnTable> asn = new(LoadAsn);
readonly Lazy<List<ProviderProfile>> providers = new(LoadProviders); readonly Lazy<List<ProviderProfile>> providers = new(LoadProviders);
readonly Lazy<NetworkTable> networks = new(LoadNetworks); readonly Lazy<NetworkTable> networks = new(LoadNetworks);
readonly Lazy<RegistryTable> registry = new(LoadRegistry); readonly Lazy<RegistryTable> registry = new(() => LoadRegistry(platform));
readonly Lazy<Dictionary<string, string>> carriers = new(LoadCarriers); readonly Lazy<Dictionary<string, string>> carriers = new(LoadCarriers);
readonly Lazy<List<ObservedNetwork>> observed = new(LoadObserved); readonly Lazy<List<ObservedNetwork>> observed = new(LoadObserved);
@@ -67,10 +69,19 @@ public sealed class KnowledgeBase
return stream is null ? null : NetworkTable.Read(stream); return stream is null ? null : NetworkTable.Read(stream);
} }
static RegistryTable LoadRegistry() static RegistryTable LoadRegistry(IPlatform platform)
{ {
using var stream = typeof(KnowledgeBase).Assembly.GetManifestResourceStream("registry.bin"); var stream = platform.OpenRegistry();
return stream is null ? null : RegistryTable.Read(stream); if (stream is null) return null;
try
{
return RegistryTable.Open(stream);
}
catch (Exception ex) when (ex is IOException or InvalidDataException or EndOfStreamException)
{
stream.Dispose();
return null;
}
} }
static Dictionary<string, string> LoadCarriers() static Dictionary<string, string> LoadCarriers()
+156 -84
View File
@@ -22,114 +22,109 @@ public class RegistryRange
public string Country { get; set; } public string Country { get; set; }
} }
public sealed class RegistryTable public sealed class RegistryTable : IDisposable
{ {
const uint Magic = 0x334B574D; internal const uint Magic = 0x354B574D;
internal const int HeaderBytes = 32;
RegistryBlock[] blocks; readonly Lock gate = new();
uint[] v4Start; Stream stream;
int[] v4Block; long origin;
UInt128[] v6Start; UInt128[][] starts;
int[] v6Block; long[][] offsets;
int cachedFamily = -1;
int cachedChunk = -1;
List<(UInt128 Start, RegistryBlock Block)> cached = [];
public DateTimeOffset SourceDate { get; private set; } public DateTimeOffset SourceDate { get; private set; }
public int Count => blocks.Length; public int Count { get; private set; }
public static RegistryTable Open(Stream seekable)
{
var table = new RegistryTable { stream = seekable, origin = seekable.Position };
using var reader = new BinaryReader(seekable, Encoding.UTF8, leaveOpen: true);
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a “me-web?” registry table");
table.SourceDate = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt64());
table.Count = reader.ReadInt32();
var index = reader.ReadInt64();
seekable.Position = table.origin + index;
table.starts = new UInt128[2][];
table.offsets = new long[2][];
var at = (long)HeaderBytes;
for (var family = 0; family < 2; family++)
{
var count = reader.Read7BitEncodedInt();
table.starts[family] = new UInt128[count];
table.offsets[family] = new long[count + 1];
var previous = UInt128.Zero;
for (var i = 0; i < count; i++)
{
previous += AsnTable.ReadBig(reader);
table.starts[family][i] = previous;
table.offsets[family][i] = at;
at += reader.Read7BitEncodedInt64();
}
table.offsets[family][count] = at;
}
return table;
}
public RegistryBlock Find(IPAddress address) public RegistryBlock Find(IPAddress address)
{ {
if (address is null) return null; if (address is null) return null;
if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4(); if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4();
var index = address.AddressFamily == AddressFamily.InterNetwork var family = address.AddressFamily == AddressFamily.InterNetwork ? 0 : 1;
? AsnTable.Search(v4Start, BinaryPrimitives.ReadUInt32BigEndian(address.GetAddressBytes()), v4Block) var value = family == 0 ? BinaryPrimitives.ReadUInt32BigEndian(address.GetAddressBytes()) : BinaryPrimitives.ReadUInt128BigEndian(address.GetAddressBytes());
: AsnTable.Search(v6Start, BinaryPrimitives.ReadUInt128BigEndian(address.GetAddressBytes()), v6Block); var chunk = Last(starts[family], value, s => s);
return index < 0 ? null : blocks[index]; if (chunk < 0) return null;
lock (gate)
{
if (cachedFamily != family || cachedChunk != chunk) Load(family, chunk);
var entry = Last(cached, value, e => e.Start);
return entry < 0 ? null : cached[entry].Block;
}
} }
public static RegistryTable Read(Stream compressed) public void Dispose() => stream?.Dispose();
void Load(int family, int chunk)
{ {
using var brotli = new BrotliStream(compressed, CompressionMode.Decompress); var compressed = new byte[offsets[family][chunk + 1] - offsets[family][chunk]];
stream.Position = origin + offsets[family][chunk];
stream.ReadExactly(compressed);
using var brotli = new BrotliStream(new MemoryStream(compressed), CompressionMode.Decompress);
using var reader = new BinaryReader(brotli, Encoding.UTF8); using var reader = new BinaryReader(brotli, Encoding.UTF8);
if (reader.ReadUInt32() != Magic) throw new InvalidDataException("not a “me-web?” registry table");
var table = new RegistryTable { SourceDate = DateTimeOffset.FromUnixTimeSeconds(reader.ReadInt64()) };
table.blocks = new RegistryBlock[reader.Read7BitEncodedInt()];
for (var i = 0; i < table.blocks.Length; i++)
table.blocks[i] = new RegistryBlock { Name = reader.ReadString(), Country = reader.ReadString() };
var count = reader.Read7BitEncodedInt(); var count = reader.Read7BitEncodedInt();
table.v4Start = new uint[count]; var entries = new List<(UInt128 Start, RegistryBlock Block)>(count);
table.v4Block = new int[count]; var start = starts[family][chunk];
var previous4 = 0u;
for (var i = 0; i < count; i++) for (var i = 0; i < count; i++)
{ {
previous4 += (uint)reader.Read7BitEncodedInt64(); start += AsnTable.ReadBig(reader);
table.v4Start[i] = previous4; var name = reader.ReadString();
table.v4Block[i] = reader.Read7BitEncodedInt() - 1; entries.Add((start, name.Length == 0 ? null : new RegistryBlock { Name = name, Country = reader.ReadString() }));
}
cached = entries;
cachedFamily = family;
cachedChunk = chunk;
} }
count = reader.Read7BitEncodedInt(); static int Last<T>(IReadOnlyList<T> list, UInt128 value, Func<T, UInt128> start)
table.v6Start = new UInt128[count];
table.v6Block = new int[count];
var previous6 = UInt128.Zero;
for (var i = 0; i < count; i++)
{ {
previous6 += AsnTable.ReadBig(reader); var low = 0;
table.v6Start[i] = previous6; var high = list.Count - 1;
table.v6Block[i] = reader.Read7BitEncodedInt() - 1; var found = -1;
} while (low <= high)
return table;
}
public static void Write(Stream output, DateTimeOffset sourceDate, List<RegistryRange> ranges)
{ {
var index = new Dictionary<(string, string), int>(); var middle = low + (high - low) / 2;
var list = new List<RegistryBlock>(); if (start(list[middle]) <= value)
var v4 = new List<(uint Start, uint End, int Block)>();
var v6 = new List<(UInt128 Start, UInt128 End, int Block)>();
foreach (var range in ranges.OrderBy(r => r.Name, StringComparer.Ordinal).ThenBy(r => r.Country, StringComparer.Ordinal))
{ {
var key = (range.Name ?? "", range.Country ?? ""); found = middle;
if (!index.TryGetValue(key, out var block)) low = middle + 1;
{
index[key] = block = list.Count;
list.Add(new RegistryBlock { Name = key.Item1, Country = key.Item2 });
} }
if (range.V6) v6.Add((range.Start, range.End, block)); else high = middle - 1;
else v4.Add(((uint)range.Start, (uint)range.End, block));
}
using var brotli = new BrotliStream(output, CompressionLevel.SmallestSize, leaveOpen: true);
using var writer = new BinaryWriter(brotli, Encoding.UTF8);
writer.Write(Magic);
writer.Write(sourceDate.ToUnixTimeSeconds());
writer.Write7BitEncodedInt(list.Count);
foreach (var block in list)
{
writer.Write(block.Name);
writer.Write(block.Country);
}
var flat4 = new List<(uint Start, int Block)>();
Narrowest(v4, uint.MaxValue, (start, block) => flat4.Add((start, block)));
writer.Write7BitEncodedInt(flat4.Count);
var previous4 = 0u;
foreach (var (start, block) in flat4)
{
writer.Write7BitEncodedInt64(start - previous4);
writer.Write7BitEncodedInt(block + 1);
previous4 = start;
}
var flat6 = new List<(UInt128 Start, int Block)>();
Narrowest(v6, UInt128.MaxValue, (start, block) => flat6.Add((start, block)));
writer.Write7BitEncodedInt(flat6.Count);
var previous6 = UInt128.Zero;
foreach (var (start, block) in flat6)
{
AsnTable.WriteBig(writer, start - previous6);
writer.Write7BitEncodedInt(block + 1);
previous6 = start;
} }
return found;
} }
public static void Narrowest<T>(List<(T Start, T End, int Block)> ranges, T max, Action<T, int> write) where T : struct, IBinaryInteger<T> public static void Narrowest<T>(List<(T Start, T End, int Block)> ranges, T max, Action<T, int> write) where T : struct, IBinaryInteger<T>
@@ -172,3 +167,80 @@ public sealed class RegistryTable
if (pending is { } last) write(last.Start, last.Block); if (pending is { } last) write(last.Start, last.Block);
} }
} }
public sealed class RegistryWriter : IDisposable
{
const int ChunkEntries = 16384;
readonly Stream output;
readonly DateTimeOffset date;
readonly List<(UInt128 Start, string Name, string Country)> pending = [];
readonly List<(UInt128 Start, long Length)>[] index = [[], []];
int family;
int count;
public RegistryWriter(Stream output, DateTimeOffset date)
{
this.output = output;
this.date = date;
output.Write(new byte[RegistryTable.HeaderBytes]);
}
public void Add(bool v6, UInt128 start, string name, string country)
{
var next = v6 ? 1 : 0;
if (next < family) throw new InvalidOperationException("IPv4 rows come before IPv6 rows");
if (next != family || pending.Count == ChunkEntries) Flush();
family = next;
pending.Add((start, name ?? "", country ?? ""));
if (!string.IsNullOrEmpty(name)) count++;
}
public void Dispose()
{
Flush();
var at = output.Position;
using var writer = new BinaryWriter(output, Encoding.UTF8, leaveOpen: true);
foreach (var chunks in index)
{
writer.Write7BitEncodedInt(chunks.Count);
var previous = UInt128.Zero;
foreach (var (start, length) in chunks)
{
AsnTable.WriteBig(writer, start - previous);
writer.Write7BitEncodedInt64(length);
previous = start;
}
}
writer.Flush();
var end = output.Position;
output.Position = 0;
writer.Write(RegistryTable.Magic);
writer.Write(date.ToUnixTimeSeconds());
writer.Write(count);
writer.Write(at);
writer.Flush();
output.Position = end;
}
void Flush()
{
if (pending.Count == 0) return;
var before = output.Position;
using (var brotli = new BrotliStream(output, CompressionLevel.SmallestSize, leaveOpen: true))
using (var writer = new BinaryWriter(brotli, Encoding.UTF8))
{
writer.Write7BitEncodedInt(pending.Count);
var previous = pending[0].Start;
foreach (var (start, name, country) in pending)
{
AsnTable.WriteBig(writer, start - previous);
writer.Write(name);
if (name.Length > 0) writer.Write(country);
previous = start;
}
}
index[family].Add((pending[0].Start, output.Position - before));
pending.Clear();
}
}
-23
View File
@@ -15,11 +15,6 @@ public class Carrier
public static class Sources public static class Sources
{ {
static readonly HashSet<string> Allocations = ["ALLOCATED PA", "ALLOCATED UNSPECIFIED", "ALLOCATED-BY-RIR"]; static readonly HashSet<string> Allocations = ["ALLOCATED PA", "ALLOCATED UNSPECIFIED", "ALLOCATED-BY-RIR"];
static readonly UInt128 WideV4 = 2048;
static readonly UInt128 NarrowV4 = 256;
static readonly UInt128 WideV6 = UInt128.One << 88;
static readonly UInt128 NarrowV6 = UInt128.One << 80;
const int Repeated = 3;
public static IEnumerable<AsnRange> IpToAsn(Stream gzip) public static IEnumerable<AsnRange> IpToAsn(Stream gzip)
{ {
@@ -82,24 +77,6 @@ public static class Sources
if (range?.Name is not null && !Allocations.Contains(status ?? "")) yield return range; if (range?.Name is not null && !Allocations.Contains(status ?? "")) yield return range;
} }
public static List<RegistryRange> RipeSelection(IEnumerable<RegistryRange> blocks)
{
var counts = new Dictionary<string, int>(StringComparer.Ordinal);
foreach (var block in blocks)
if (block.End - block.Start >= (block.V6 ? NarrowV6 : NarrowV4) - 1) counts[block.Name] = counts.GetValueOrDefault(block.Name) + 1;
var names = new Dictionary<string, string>(StringComparer.Ordinal);
var kept = new List<RegistryRange>();
foreach (var block in blocks)
{
var size = block.End - block.Start;
if (size < (block.V6 ? NarrowV6 : NarrowV4) - 1 || (size < (block.V6 ? WideV6 : WideV4) - 1 && counts[block.Name] < Repeated)) continue;
block.Name = Intern(names, block.Name);
block.Country = Intern(names, block.Country ?? "");
kept.Add(block);
}
return kept;
}
static RegistryRange RipeRange(string line) static RegistryRange RipeRange(string line)
{ {
if (line.StartsWith("inetnum:", StringComparison.Ordinal)) if (line.StartsWith("inetnum:", StringComparison.Ordinal))
-1
View File
@@ -12,7 +12,6 @@
<EmbeddedResource Include="$(KnowledgeFolder)ip2asn.bin" LogicalName="ip2asn.bin" Condition="Exists('$(KnowledgeFolder)ip2asn.bin')" /> <EmbeddedResource Include="$(KnowledgeFolder)ip2asn.bin" LogicalName="ip2asn.bin" Condition="Exists('$(KnowledgeFolder)ip2asn.bin')" />
<EmbeddedResource Include="$(KnowledgeFolder)networks.bin" LogicalName="networks.bin" Condition="Exists('$(KnowledgeFolder)networks.bin')" /> <EmbeddedResource Include="$(KnowledgeFolder)networks.bin" LogicalName="networks.bin" Condition="Exists('$(KnowledgeFolder)networks.bin')" />
<EmbeddedResource Include="$(KnowledgeFolder)carriers.json" LogicalName="carriers.json" Condition="Exists('$(KnowledgeFolder)carriers.json')" /> <EmbeddedResource Include="$(KnowledgeFolder)carriers.json" LogicalName="carriers.json" Condition="Exists('$(KnowledgeFolder)carriers.json')" />
<EmbeddedResource Include="$(KnowledgeFolder)registry.bin" LogicalName="registry.bin" Condition="Exists('$(KnowledgeFolder)registry.bin')" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
+5
View File
@@ -1,4 +1,5 @@
using System.Net; using System.Net;
using System.Reflection;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -82,6 +83,8 @@ public interface IPlatform
Task<CellularReport> ReadCellular(CancellationToken ct); Task<CellularReport> ReadCellular(CancellationToken ct);
IDisposable AllowMulticast(); IDisposable AllowMulticast();
Stream OpenRegistry();
} }
public class SystemPlatform : IPlatform public class SystemPlatform : IPlatform
@@ -148,6 +151,8 @@ public class SystemPlatform : IPlatform
public virtual IDisposable AllowMulticast() => new Nothing(); public virtual IDisposable AllowMulticast() => new Nothing();
public virtual Stream OpenRegistry() => Assembly.GetEntryAssembly()?.GetManifestResourceStream("registry.bin");
public virtual Task<CellularReport> ReadCellular(CancellationToken ct) => Task.FromResult<CellularReport>(null); public virtual Task<CellularReport> ReadCellular(CancellationToken ct) => Task.FromResult<CellularReport>(null);
static readonly string[] Placeholders = ["o.e.m", "system product", "system manufacturer", "system version", "default string", "not applicable", "not specified", "to be filled", "none"]; static readonly string[] Placeholders = ["o.e.m", "system product", "system manufacturer", "system version", "default string", "not applicable", "not specified", "to be filled", "none"];
+4
View File
@@ -17,4 +17,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\MeWeb.App\MeWeb.App.csproj" /> <ProjectReference Include="..\MeWeb.App\MeWeb.App.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\..\artifacts\knowledge\registry.bin" LogicalName="registry.bin" Condition="Exists('..\..\artifacts\knowledge\registry.bin')" />
</ItemGroup>
</Project> </Project>
+3 -1
View File
@@ -24,6 +24,7 @@ public static class DownloadPage
["WindowsNote"] = "The file isn't signed with a paid certificate, so Windows may say it protected your PC: choose “More info”, then “Run anyway”.", ["WindowsNote"] = "The file isn't signed with a paid certificate, so Windows may say it protected your PC: choose “More info”, then “Run anyway”.",
["Linux"] = "Linux", ["Linux"] = "Linux",
["LinuxNote"] = "Install with your package manager, for example", ["LinuxNote"] = "Install with your package manager, for example",
["LinuxRepo"] = "Installing it also adds the Arasaka package repository, packages.arasaka.software, so your package manager brings the updates. On openSUSE, first run",
["Mac"] = "Apple computers", ["Mac"] = "Apple computers",
["MacNote"] = "Builds for Mac are attached to the release, but they haven't been tried on a Mac yet.", ["MacNote"] = "Builds for Mac are attached to the release, but they haven't been tried on a Mac yet.",
["Sums"] = "Checksums (SHA-256)", ["Sums"] = "Checksums (SHA-256)",
@@ -46,6 +47,7 @@ public static class DownloadPage
["WindowsNote"] = "Il file non è firmato con un certificato a pagamento, quindi Windows può dire di aver protetto il PC: scelga “Ulteriori informazioni”, poi “Esegui comunque”.", ["WindowsNote"] = "Il file non è firmato con un certificato a pagamento, quindi Windows può dire di aver protetto il PC: scelga “Ulteriori informazioni”, poi “Esegui comunque”.",
["Linux"] = "Linux", ["Linux"] = "Linux",
["LinuxNote"] = "Si installa con il gestore dei pacchetti, per esempio", ["LinuxNote"] = "Si installa con il gestore dei pacchetti, per esempio",
["LinuxRepo"] = "Installandolo si aggiunge anche il repository dei pacchetti Arasaka, packages.arasaka.software, e gli aggiornamenti arrivano dal gestore dei pacchetti. Su openSUSE esegua prima",
["Mac"] = "Computer Apple", ["Mac"] = "Computer Apple",
["MacNote"] = "Le versioni per Mac sono allegate alla release, ma non sono ancora state provate su un Mac.", ["MacNote"] = "Le versioni per Mac sono allegate alla release, ma non sono ancora state provate su un Mac.",
["Sums"] = "Somme di controllo (SHA-256)", ["Sums"] = "Somme di controllo (SHA-256)",
@@ -79,7 +81,7 @@ public static class DownloadPage
var deb = ReleaseFeed.Asset(release, "linux-deb")?.Name; var deb = ReleaseFeed.Asset(release, "linux-deb")?.Name;
var rpm = ReleaseFeed.Asset(release, "linux-rpm")?.Name; var rpm = ReleaseFeed.Asset(release, "linux-rpm")?.Name;
Block(html, release, words["Linux"], [("linux-deb", ".deb · Debian, Ubuntu"), ("linux-rpm", ".rpm · Fedora, openSUSE")], Block(html, release, words["Linux"], [("linux-deb", ".deb · Debian, Ubuntu"), ("linux-rpm", ".rpm · Fedora, openSUSE")],
$"{E(words["LinuxNote"])} <code>sudo apt install ./{E(deb)}</code> · <code>sudo dnf install ./{E(rpm)}</code>"); $"{E(words["LinuxNote"])} <code>sudo apt install ./{E(deb)}</code> · <code>sudo dnf install ./{E(rpm)}</code>. {E(words["LinuxRepo"])} <code>sudo rpm --import https://packages.arasaka.software/arasaka.asc</code>.");
Block(html, release, words["Mac"], [("macos-arm64", "Apple Silicon"), ("macos-x64", "Intel")], E(words["MacNote"])); Block(html, release, words["Mac"], [("macos-arm64", "Apple Silicon"), ("macos-x64", "Intel")], E(words["MacNote"]));
html.Append($"<p><a href=\"{Endpoints.Downloads.GET_M_Platform("checksums")}\">{E(words["Sums"])}</a> · <a href=\"{E(release.Page ?? Endpoints.Gitea.Releases)}\">{E(words["All"])}</a></p>"); html.Append($"<p><a href=\"{Endpoints.Downloads.GET_M_Platform("checksums")}\">{E(words["Sums"])}</a> · <a href=\"{E(release.Page ?? Endpoints.Gitea.Releases)}\">{E(words["All"])}</a></p>");
} }
+25 -12
View File
@@ -23,6 +23,7 @@ public class KnowledgeOptions
public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options, IHttpClientFactory factory, ILogger<KnowledgeFeeder> log) : BackgroundService public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options, IHttpClientFactory factory, ILogger<KnowledgeFeeder> log) : BackgroundService
{ {
public const string Client = "knowledge"; public const string Client = "knowledge";
public const int Format = 2;
public static readonly string[] Published = ["ip2asn.bin", "networks.bin", "carriers.json", "registry.bin", "SOURCES.txt"]; public static readonly string[] Published = ["ip2asn.bin", "networks.bin", "carriers.json", "registry.bin", "SOURCES.txt"];
const string IpToAsn = "iptoasn"; const string IpToAsn = "iptoasn";
@@ -57,7 +58,7 @@ public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options,
[RipeV6] = RipeLicence, [RipeV6] = RipeLicence,
}; };
const string RipeLicence = "RIPE Database extract, RIPE NCC (https://www.ripe.net), used with the RIPE NCC's permission (16 October 2026, ticket #1122623) for a free app with no commercial use; only each block's range, name and country are kept, from blocks of at least 2048 IPv4 addresses or a /40, or of at least 256 addresses or a /48 whose name recurs; it may lag behind the live database"; const string RipeLicence = "RIPE Database extract, RIPE NCC (https://www.ripe.net), used with the RIPE NCC's permission (16 October 2026, ticket #1122623) for a free app with no commercial use; only each block's range, name and country are kept, of every block but the RIPE NCC's own allocations; it may lag behind the live database";
public string Folder => options.CurrentValue.Folder; public string Folder => options.CurrentValue.Folder;
@@ -79,7 +80,20 @@ public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options,
while (await timer.WaitForNextTickAsync(stoppingToken)); while (await timer.WaitForNextTickAsync(stoppingToken));
} }
public RegistryBlock BlockFor(IPAddress address) => RegistryIndex.Find(Path.Combine(Folder, "current", RegistryIndex.FileName), address); public RegistryBlock BlockFor(IPAddress address)
{
var path = Path.Combine(Folder, "current", "registry.bin");
if (!File.Exists(path)) return null;
try
{
using var table = RegistryTable.Open(File.OpenRead(path));
return table.Find(address);
}
catch (Exception ex) when (ex is IOException or InvalidDataException)
{
return null;
}
}
public ViewKnowledge Manifest() public ViewKnowledge Manifest()
{ {
@@ -142,7 +156,7 @@ public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options,
} }
} }
if (!changed && Manifest() is not null) return; if (!changed && Manifest()?.Format == Format) return;
Build(sources, known.Values.ToList(), ct); Build(sources, known.Values.ToList(), ct);
} }
@@ -258,6 +272,7 @@ public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options,
var manifest = new ViewKnowledge var manifest = new ViewKnowledge
{ {
Format = Format,
BuiltAt = built, BuiltAt = built,
Sources = known.OrderBy(s => s.Name, StringComparer.Ordinal).ToList(), Sources = known.OrderBy(s => s.Name, StringComparer.Ordinal).ToList(),
Files = Published.Where(f => File.Exists(Path.Combine(staging, f))).Select(f => new ViewKnowledgeFile Files = Published.Where(f => File.Exists(Path.Combine(staging, f))).Select(f => new ViewKnowledgeFile
@@ -274,19 +289,17 @@ public partial class KnowledgeFeeder(IOptionsMonitor<KnowledgeOptions> options,
if (Directory.Exists(previous)) Directory.Delete(previous, true); if (Directory.Exists(previous)) Directory.Delete(previous, true);
if (Directory.Exists(current)) Directory.Move(current, previous); if (Directory.Exists(current)) Directory.Move(current, previous);
Directory.Move(staging, current); Directory.Move(staging, current);
log.LogInformation("{Service} built {Files} in {Seconds:0.0} s: {Networks} networks, upstreams from {Collectors} routing tables, {Carriers} carriers, {Kept} of {Blocks} registry blocks embedded", log.LogInformation("{Service} built {Files} in {Seconds:0.0} s: {Networks} networks, upstreams from {Collectors} routing tables, {Carriers} carriers, {Blocks} registry blocks",
nameof(KnowledgeFeeder), string.Join(", ", manifest.Files.Select(f => $"{f.Name} {f.Bytes / 1024} KiB")), clock.Elapsed.TotalSeconds, nameof(KnowledgeFeeder), string.Join(", ", manifest.Files.Select(f => $"{f.Name} {f.Bytes / 1024} KiB")), clock.Elapsed.TotalSeconds,
organisations.Keys.Union(upstreams.Keys).Count(), tally.Collectors, list.Count, registry.Kept, registry.All); organisations.Keys.Union(upstreams.Keys).Count(), tally.Collectors, list.Count, registry);
} }
static (int Kept, int All) Registry(string sources, string staging, DateTimeOffset date) static int Registry(string sources, string staging, DateTimeOffset date)
{ {
var v4 = Read(Path.Combine(sources, "ripe-inetnum.gz")); var v4 = Path.Combine(sources, "ripe-inetnum.gz");
var v6 = Read(Path.Combine(sources, "ripe-inet6num.gz")); var v6 = Path.Combine(sources, "ripe-inet6num.gz");
var kept = Sources.RipeSelection(v4.Concat(v6)); if (!File.Exists(v4) && !File.Exists(v6)) return 0;
if (kept.Count == 0) return (0, 0); return RegistryBuilder.Build(Read(v4), Read(v6), Path.Combine(staging, "registry.bin"), date);
using (var output = File.Create(Path.Combine(staging, "registry.bin"))) RegistryTable.Write(output, date, kept);
return (kept.Count, RegistryIndex.Build(v4, v6, Path.Combine(staging, RegistryIndex.FileName), date));
static IEnumerable<RegistryRange> Read(string path) static IEnumerable<RegistryRange> Read(string path)
{ {
+6 -2
View File
@@ -7,7 +7,7 @@ public static class PrivacyPage
{ {
static readonly Dictionary<string, (string Title, string Other, string Publisher, string Footer, string[] Sections)> Texts = new() static readonly Dictionary<string, (string Title, string Other, string Publisher, string Footer, string[] Sections)> Texts = new()
{ {
["en"] = ("“me-web?” · privacy", "Italiano", "Published by Arasaka di Hvozdikov Yevheniy, Busto Arsizio, Italy · VAT IT03929200123", "Last changed 15 October 2026.", ["en"] = ("“me-web?” · privacy", "Italiano", "Published by Arasaka di Hvozdikov Yevheniy, Busto Arsizio, Italy · VAT IT03929200123", "Last changed 16 October 2026.",
[ [
"## Who is responsible", "## Who is responsible",
"Arasaka di Hvozdikov Yevheniy, a sole trader in Busto Arsizio (VA), Italy, VAT number IT03929200123, publishes “me-web?” and runs its server, meweb.arasaka.software, on its own hardware in Italy. Questions and requests: through the form at [https://arasaka.software/contact](https://arasaka.software/contact).", "Arasaka di Hvozdikov Yevheniy, a sole trader in Busto Arsizio (VA), Italy, VAT number IT03929200123, publishes “me-web?” and runs its server, meweb.arasaka.software, on its own hardware in Italy. Questions and requests: through the form at [https://arasaka.software/contact](https://arasaka.software/contact).",
@@ -19,12 +19,14 @@ public static class PrivacyPage
"If you press “Send” after reading exactly what will be sent, the server stores those findings, which include your public addresses, your provider, your router's make and model and the results of the checks, as a file on the same server in Italy. The purpose is to improve the notes on providers in later versions of “me-web?”; only totals without addresses are ever published, after a person has reviewed them, and only for a provider whose findings come from at least five different networks, four in five of them agreeing; the total says how many. The legal basis is your consent (Article 6(1)(a) GDPR). Each submission is deleted automatically after twelve months, or earlier when you delete it in the app, or ask for it with its code (MW-…).", "If you press “Send” after reading exactly what will be sent, the server stores those findings, which include your public addresses, your provider, your router's make and model and the results of the checks, as a file on the same server in Italy. The purpose is to improve the notes on providers in later versions of “me-web?”; only totals without addresses are ever published, after a person has reviewed them, and only for a provider whose findings come from at least five different networks, four in five of them agreeing; the total says how many. The legal basis is your consent (Article 6(1)(a) GDPR). Each submission is deleted automatically after twelve months, or earlier when you delete it in the app, or ask for it with its code (MW-…).",
"## The STUN servers", "## The STUN servers",
"To see how your router or your provider translates addresses for calls and games, the app sends one short message to stun.nextcloud.com, run by Nextcloud GmbH in Germany, which sees your public address as any server you contact does, and about twenty to stun.meweb.arasaka.software, run by the publisher, some of them two minutes apart to see how long an idle connection stays open.", "To see how your router or your provider translates addresses for calls and games, the app sends one short message to stun.nextcloud.com, run by Nextcloud GmbH in Germany, which sees your public address as any server you contact does, and about twenty to stun.meweb.arasaka.software, run by the publisher, some of them two minutes apart to see how long an idle connection stays open.",
"## Updates on Linux",
"If you install the Linux package, it adds the Arasaka package repository to your system, and your package manager then checks packages.arasaka.software for updates, as it does with every repository it knows. That server keeps no access log.",
"## Nothing else", "## Nothing else",
"No cookies, no analytics, no advertising, no crash reports. The app keeps your earlier checks on your device only, and you can forget them in the app.", "No cookies, no analytics, no advertising, no crash reports. The app keeps your earlier checks on your device only, and you can forget them in the app.",
"## Your rights", "## Your rights",
"You can ask for access to, correction or erasure of your data, restrict its use, take it with you, and withdraw your consent at any time, through the form above. You can also complain to the Italian supervisory authority, the Garante per la protezione dei dati personali ([https://www.garanteprivacy.it](https://www.garanteprivacy.it)).", "You can ask for access to, correction or erasure of your data, restrict its use, take it with you, and withdraw your consent at any time, through the form above. You can also complain to the Italian supervisory authority, the Garante per la protezione dei dati personali ([https://www.garanteprivacy.it](https://www.garanteprivacy.it)).",
]), ]),
["it"] = ("«me-web?» · privacy", "English", "Pubblicato da Arasaka di Hvozdikov Yevheniy, Busto Arsizio · P.IVA 03929200123", "Ultima modifica 15 ottobre 2026.", ["it"] = ("«me-web?» · privacy", "English", "Pubblicato da Arasaka di Hvozdikov Yevheniy, Busto Arsizio · P.IVA 03929200123", "Ultima modifica 16 ottobre 2026.",
[ [
"## Chi è responsabile", "## Chi è responsabile",
"Arasaka di Hvozdikov Yevheniy, ditta individuale di Busto Arsizio (VA), P.IVA 03929200123, pubblica «me-web?» e gestisce il suo server, meweb.arasaka.software, su hardware proprio in Italia. Domande e richieste: con il modulo su [https://arasaka.software/it/contatti](https://arasaka.software/it/contatti).", "Arasaka di Hvozdikov Yevheniy, ditta individuale di Busto Arsizio (VA), P.IVA 03929200123, pubblica «me-web?» e gestisce il suo server, meweb.arasaka.software, su hardware proprio in Italia. Domande e richieste: con il modulo su [https://arasaka.software/it/contatti](https://arasaka.software/it/contatti).",
@@ -36,6 +38,8 @@ public static class PrivacyPage
"Se preme «Invia» dopo aver letto esattamente che cosa verrà inviato, il server conserva quei risultati, che comprendono i suoi indirizzi pubblici, l'operatore, marca e modello del router e gli esiti delle verifiche, come file sullo stesso server in Italia. Lo scopo è migliorare le note sugli operatori nelle prossime versioni di «me-web?»; si pubblicano solo totali senza indirizzi, dopo la revisione di una persona, e solo per un operatore i cui risultati vengono da almeno cinque reti diverse, d'accordo almeno quattro volte su cinque; il totale dice quante. La base giuridica è il suo consenso (art. 6, par. 1, lett. a GDPR). Ogni invio viene cancellato automaticamente dopo dodici mesi, oppure prima, quando lo cancella nell'app o lo chiede indicando il suo codice (MW-…).", "Se preme «Invia» dopo aver letto esattamente che cosa verrà inviato, il server conserva quei risultati, che comprendono i suoi indirizzi pubblici, l'operatore, marca e modello del router e gli esiti delle verifiche, come file sullo stesso server in Italia. Lo scopo è migliorare le note sugli operatori nelle prossime versioni di «me-web?»; si pubblicano solo totali senza indirizzi, dopo la revisione di una persona, e solo per un operatore i cui risultati vengono da almeno cinque reti diverse, d'accordo almeno quattro volte su cinque; il totale dice quante. La base giuridica è il suo consenso (art. 6, par. 1, lett. a GDPR). Ogni invio viene cancellato automaticamente dopo dodici mesi, oppure prima, quando lo cancella nell'app o lo chiede indicando il suo codice (MW-…).",
"## I server STUN", "## I server STUN",
"Per vedere come il router o l'operatore traducono gli indirizzi per chiamate e giochi, l'app invia un breve messaggio a stun.nextcloud.com, gestito da Nextcloud GmbH in Germania, che vede il suo indirizzo pubblico come qualsiasi server che lei contatta, e una ventina a stun.meweb.arasaka.software, gestito dall'editore, alcuni a due minuti di distanza per vedere quanto resta aperta una connessione inattiva.", "Per vedere come il router o l'operatore traducono gli indirizzi per chiamate e giochi, l'app invia un breve messaggio a stun.nextcloud.com, gestito da Nextcloud GmbH in Germania, che vede il suo indirizzo pubblico come qualsiasi server che lei contatta, e una ventina a stun.meweb.arasaka.software, gestito dall'editore, alcuni a due minuti di distanza per vedere quanto resta aperta una connessione inattiva.",
"## Gli aggiornamenti su Linux",
"Se installa il pacchetto per Linux, questo aggiunge al sistema il repository dei pacchetti Arasaka, e il gestore dei pacchetti controlla poi gli aggiornamenti su packages.arasaka.software, come fa con ogni repository che conosce. Quel server non tiene registri di accesso.",
"## Nient'altro", "## Nient'altro",
"Niente cookie, niente statistiche, niente pubblicità, niente rapporti sugli arresti anomali. L'app conserva le verifiche precedenti solo sul suo dispositivo, e può dimenticarle dall'app.", "Niente cookie, niente statistiche, niente pubblicità, niente rapporti sugli arresti anomali. L'app conserva le verifiche precedenti solo sul suo dispositivo, e può dimenticarle dall'app.",
"## I suoi diritti", "## I suoi diritti",
+52
View File
@@ -0,0 +1,52 @@
using System.Numerics;
using System.Text;
using MeWeb.Core.Knowledge;
namespace MeWeb.Server;
public static class RegistryBuilder
{
public static int Build(IEnumerable<RegistryRange> v4Blocks, IEnumerable<RegistryRange> v6Blocks, string path, DateTimeOffset date)
{
var namesPath = path + ".names";
var count = 0;
using (var output = File.Create(path))
using (var writer = new RegistryWriter(output, date))
{
Family(false, v4Blocks, uint.MaxValue, 5_000_000);
Family(true, v6Blocks, UInt128.MaxValue, 1_200_000);
void Family<T>(bool v6, IEnumerable<RegistryRange> blocks, T max, int capacity) where T : struct, IBinaryInteger<T>
{
var ranges = new List<(T Start, T End, int Block)>(capacity);
using (var names = File.Create(namesPath))
using (var labels = new BinaryWriter(names, Encoding.UTF8))
{
foreach (var block in blocks.Where(b => b.V6 == v6))
{
ranges.Add((T.CreateTruncating(block.Start), T.CreateTruncating(block.End), checked((int)names.Position)));
labels.Write(block.Name);
labels.Write(block.Country ?? "");
count++;
}
}
using var handle = File.OpenHandle(namesPath);
var buffer = new byte[640];
RegistryTable.Narrowest(ranges, max, (start, at) =>
{
if (at < 0)
{
writer.Add(v6, UInt128.CreateTruncating(start), null, null);
return;
}
var read = RandomAccess.Read(handle, buffer, at);
using var reader = new BinaryReader(new MemoryStream(buffer, 0, read), Encoding.UTF8);
writer.Add(v6, UInt128.CreateTruncating(start), reader.ReadString(), reader.ReadString());
});
}
}
File.Delete(namesPath);
return count;
}
}
-112
View File
@@ -1,112 +0,0 @@
using System.Buffers.Binary;
using System.Net;
using System.Net.Sockets;
using System.Text;
using MeWeb.Core.Knowledge;
namespace MeWeb.Server;
public static class RegistryIndex
{
public const string FileName = "registry-full.bin";
const uint Magic = 0x344B574D;
const int HeaderBytes = 32;
const int V4Bytes = 8;
const int V6Bytes = 20;
public static int Build(IEnumerable<RegistryRange> v4Blocks, IEnumerable<RegistryRange> v6Blocks, string path, DateTimeOffset date)
{
var namesPath = path + ".names";
var count = 0;
using (var names = File.Create(namesPath))
using (var labels = new BinaryWriter(names, Encoding.UTF8))
using (var output = File.Create(path))
using (var writer = new BinaryWriter(output))
{
int Label(RegistryRange block)
{
var at = checked((int)names.Position);
labels.Write(block.Name);
labels.Write(block.Country ?? "");
count++;
return at;
}
writer.Write(new byte[HeaderBytes]);
var v4 = new List<(uint Start, uint End, int Block)>(5_000_000);
foreach (var block in v4Blocks.Where(b => !b.V6)) v4.Add(((uint)block.Start, (uint)block.End, Label(block)));
var count4 = 0;
RegistryTable.Narrowest(v4, uint.MaxValue, (start, block) =>
{
writer.Write(start);
writer.Write(block);
count4++;
});
v4 = null;
var v6 = new List<(UInt128 Start, UInt128 End, int Block)>(1_200_000);
foreach (var block in v6Blocks.Where(b => b.V6)) v6.Add((block.Start, block.End, Label(block)));
var count6 = 0;
RegistryTable.Narrowest(v6, UInt128.MaxValue, (start, block) =>
{
writer.Write((ulong)(start >> 64));
writer.Write((ulong)start);
writer.Write(block);
count6++;
});
v6 = null;
labels.Flush();
writer.Flush();
var namesAt = output.Position;
names.Position = 0;
names.CopyTo(output);
output.Position = 0;
writer.Write(Magic);
writer.Write(date.ToUnixTimeSeconds());
writer.Write(count4);
writer.Write(count6);
writer.Write(namesAt);
}
File.Delete(namesPath);
return count;
}
public static RegistryBlock Find(string path, IPAddress address)
{
if (address is null || !File.Exists(path)) return null;
if (address.IsIPv4MappedToIPv6) address = address.MapToIPv4();
using var handle = File.OpenHandle(path);
Span<byte> header = stackalloc byte[HeaderBytes];
if (RandomAccess.Read(handle, header, 0) < HeaderBytes || BinaryPrimitives.ReadUInt32LittleEndian(header) != Magic) return null;
var count4 = BinaryPrimitives.ReadInt32LittleEndian(header[12..]);
var count6 = BinaryPrimitives.ReadInt32LittleEndian(header[16..]);
var namesAt = BinaryPrimitives.ReadInt64LittleEndian(header[20..]);
var v4 = address.AddressFamily == AddressFamily.InterNetwork;
var value = v4 ? BinaryPrimitives.ReadUInt32BigEndian(address.GetAddressBytes()) : BinaryPrimitives.ReadUInt128BigEndian(address.GetAddressBytes());
var first = v4 ? HeaderBytes : HeaderBytes + (long)count4 * V4Bytes;
var size = v4 ? V4Bytes : V6Bytes;
Span<byte> record = stackalloc byte[V6Bytes];
var low = 0;
var high = (v4 ? count4 : count6) - 1;
var block = -1;
while (low <= high)
{
var middle = low + (high - low) / 2;
RandomAccess.Read(handle, record[..size], first + (long)middle * size);
var start = v4
? BinaryPrimitives.ReadUInt32LittleEndian(record)
: ((UInt128)BinaryPrimitives.ReadUInt64LittleEndian(record) << 64) | BinaryPrimitives.ReadUInt64LittleEndian(record[8..]);
if (start <= value)
{
block = BinaryPrimitives.ReadInt32LittleEndian(record[(size - 4)..]);
low = middle + 1;
}
else high = middle - 1;
}
if (block < 0) return null;
var text = new byte[640];
var read = RandomAccess.Read(handle, text, namesAt + block);
using var reader = new BinaryReader(new MemoryStream(text, 0, read), Encoding.UTF8);
return new RegistryBlock { Name = reader.ReadString(), Country = reader.ReadString() };
}
}
+1
View File
@@ -17,6 +17,7 @@ public class ViewIp
public class ViewKnowledge public class ViewKnowledge
{ {
public int Format { get; set; }
public DateTimeOffset BuiltAt { get; set; } public DateTimeOffset BuiltAt { get; set; }
public List<ViewKnowledgeFile> Files { get; set; } = []; public List<ViewKnowledgeFile> Files { get; set; } = [];
public List<ViewKnowledgeSource> Sources { get; set; } = []; public List<ViewKnowledgeSource> Sources { get; set; } = [];
+4
View File
@@ -15,4 +15,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\MeWeb.App\MeWeb.App.csproj" /> <ProjectReference Include="..\..\src\MeWeb.App\MeWeb.App.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<EmbeddedResource Include="..\..\artifacts\knowledge\registry.bin" LogicalName="registry.bin" Condition="Exists('..\..\artifacts\knowledge\registry.bin')" />
</ItemGroup>
</Project> </Project>
+2 -4
View File
@@ -73,11 +73,9 @@ void Report()
NetworkTable networks = null; NetworkTable networks = null;
if (File.Exists(path)) if (File.Exists(path))
using (var input = File.OpenRead(path)) networks = NetworkTable.Read(input); using (var input = File.OpenRead(path)) networks = NetworkTable.Read(input);
RegistryTable registry = null;
path = Path.Combine(output, "registry.bin"); path = Path.Combine(output, "registry.bin");
if (File.Exists(path)) using var registry = File.Exists(path) ? RegistryTable.Open(File.OpenRead(path)) : null;
using (var input = File.OpenRead(path)) registry = RegistryTable.Read(input); Console.WriteLine($"knowledge: {table.Count} networks in the provider map (source {table.SourceDate:yyyy-MM-dd}), {networks?.Count ?? 0} with names or upstreams, {registry?.Count ?? 0} named address blocks");
Console.WriteLine($"knowledge: {table.Count} networks in the provider map (source {table.SourceDate:yyyy-MM-dd}), {networks?.Count ?? 0} with names or upstreams, {registry?.Count ?? 0} address block names");
} }
static string FindRoot() static string FindRoot()
+10 -4
View File
@@ -138,14 +138,16 @@ string Linux()
StartupWMClass=meweb StartupWMClass=meweb
"""); """);
File.WriteAllText(Path.Combine(stage, "postinstall.sh"), """ var caches = """
#!/bin/sh #!/bin/sh
if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi if command -v update-desktop-database >/dev/null 2>&1; then update-desktop-database -q /usr/share/applications || true; fi
if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q -f -t /usr/share/icons/hicolor || true; fi if command -v gtk-update-icon-cache >/dev/null 2>&1; then gtk-update-icon-cache -q -f -t /usr/share/icons/hicolor || true; fi
exit 0
"""); """;
File.Copy(Path.Combine(stage, "postinstall.sh"), Path.Combine(stage, "postremove.sh"), overwrite: true); var kit = File.ReadAllText(Path.Combine(root, "packaging", "arasaka-repository.sh"));
File.WriteAllText(Path.Combine(stage, "postinstall.sh"), $"{caches}{kit}arasaka_repository_configure \"$@\"\nexit 0\n");
File.WriteAllText(Path.Combine(stage, "postremove.sh"), $"{caches}{kit}arasaka_repository_cleanup \"$@\"\nexit 0\n");
File.WriteAllText(Path.Combine(stage, "arasaka-repository"), "");
var png = Path.Combine(branding, "meweb-512.png"); var png = Path.Combine(branding, "meweb-512.png");
var svg = Path.Combine(branding, "meweb.svg"); var svg = Path.Combine(branding, "meweb.svg");
var yaml = Path.Combine(stage, "nfpm.yaml"); var yaml = Path.Combine(stage, "nfpm.yaml");
@@ -183,6 +185,10 @@ string Linux()
dst: /usr/share/icons/hicolor/scalable/apps/software.arasaka.meweb.svg dst: /usr/share/icons/hicolor/scalable/apps/software.arasaka.meweb.svg
file_info: file_info:
mode: 0644 mode: 0644
- src: {stage}/arasaka-repository
dst: /usr/share/arasaka-repository/meweb
file_info:
mode: 0644
scripts: scripts:
postinstall: {stage}/postinstall.sh postinstall: {stage}/postinstall.sh
postremove: {stage}/postremove.sh postremove: {stage}/postremove.sh
+2 -1
View File
@@ -8,9 +8,10 @@ done
if [ -z "$dotnet_dir" ]; then if [ -z "$dotnet_dir" ]; then
dotnet_dir="$HOME/.dotnet" dotnet_dir="$HOME/.dotnet"
echo ".NET SDK $sdk not found: installing it into $dotnet_dir" echo ".NET SDK $sdk not found: installing it into $dotnet_dir"
script="${TMPDIR:-/tmp}/dotnet-install.sh" script=$(mktemp "${TMPDIR:-/tmp}/dotnet-install.XXXXXX")
curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$script" curl -fsSL https://dot.net/v1/dotnet-install.sh -o "$script"
bash "$script" --version "$sdk" --install-dir "$dotnet_dir" bash "$script" --version "$sdk" --install-dir "$dotnet_dir"
rm -f "$script"
fi fi
export DOTNET_ROOT="$dotnet_dir" PATH="$dotnet_dir:$PATH" DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1 export DOTNET_ROOT="$dotnet_dir" PATH="$dotnet_dir:$PATH" DOTNET_CLI_TELEMETRY_OPTOUT=1 DOTNET_NOLOGO=1
cd "$root" cd "$root"