Compare commits

...
Author SHA1 Message Date
bvandeusen 237380b122 New brand mark, and both clients stop fetching their fonts at runtime (#128)
test-web / test (push) Successful in 54s
android / Build + lint + test (push) Successful in 5m36s
release / Build signed APK (tag releases only) (push) Successful in 4m36s
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Successful in 2s
2026-09-09 14:36:15 -04:00
bvandeusenandClaude Opus 5 c27f9d484a test(android): guard that the typefaces stay bundled
android / Build + lint + test (push) Successful in 5m17s
The web side has no-external-assets.test.ts; Android had nothing, so the
font provider could come back with no test noticing. This is the Android
half.

Expectations are read out of Typography.kt rather than hardcoded, which
is what makes it a structural pin instead of a list that rots: the guard
extracts every Font(R.font.X, FontWeight.WN) declaration and checks that
X.ttf exists, is really TrueType, and reports N as its OS/2
usWeightClass. Add a face without vendoring it and this fails; change a
declared weight without refetching the matching static instance and it
fails too.

usWeightClass is the check worth having. css2 silently collapses a
multi-weight request to 400 for legacy clients, so Medium comes back as
Regular — a valid TrueType file that renders at the wrong weight
everywhere, and the only field that distinguishes it.

Comments are stripped before the absence check, so the KDoc explaining
why there is no GoogleFont reference cannot satisfy the assertion that
forbids it.

Falsified by mirroring every predicate and byte offset against the real
files: it passes on what is committed, and trips on HEAD~1's
Typography.kt via both the forbidden-symbol check and the
no-declarations-found check. A 400 file asserted against a declared 500
fails, so the weight comparison is not vacuous.

Compilation itself is unverified locally — no Gradle run here — so CI is
the first thing to actually build this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
2026-09-09 14:28:56 -04:00
bvandeusenandClaude Opus 5 b52a00df66 fix(android): bundle the typefaces instead of fetching them at runtime
android / Build + lint + test (push) Successful in 4m19s
Typography.kt resolved Fraunces, Inter and JetBrains Mono through the Play
Services font provider, which fetches them over the network on first use.
Same rule-164 problem the web client had, with a second failure mode on
top: the provider is absent entirely on devices without Play Services, so
the app fell back to the platform default and stopped looking like
Minstrel — quietly, with no error.

The five static instances now live in res/font, vendored by the same
tools/vendor-fonts.py that produces the web bundle. Both clients draw
from one list of faces so they cannot drift apart. Cost is ~0.86 MB of
APK; the runtime path is removed rather than kept as a fallback — the
ui-text-google-fonts dependency, its version-catalog entry and the
provider certificate hashes in font_certs.xml are all gone.

Two things about fetching TTFs that are worth writing down, because both
fail by succeeding:

Google Fonts picks the format from the User-Agent, and there is no
parameter to ask for one. A modern UA gets woff2, which res/font cannot
load. The obvious "use an old UA" fix gets EOT — an IE-only format that
downloads happily, has a plausible size, and is entirely useless here. An
Android 4.4 UA is what actually yields TrueType.

css2 also collapses a multi-weight request to 400 for legacy clients, so
asking for Medium silently returns Regular: a valid TrueType file that
renders at the wrong weight everywhere. Each weight is therefore fetched
on its own URL, and the script now asserts OS/2 usWeightClass on every
download — that field is the only thing distinguishing the two files.

Verified before wiring: all five carry TrueType magic, the 400/500 pairs
differ, and their usWeightClass reads 400/400/500/500/400 as declared
beside them in the FontFamily.

Not covered: there is no guard for this on the Android side. The web
equivalent is asserted by no-external-assets.test.ts, but the Android
tree has no source-inspection test pattern to follow and no way to
falsify one without a local Gradle run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
2026-09-09 14:13:57 -04:00
bvandeusenandClaude Opus 5 16005054eb fix(web): vendor the web fonts instead of loading them from Google
test-web / test (push) Successful in 42s
app.html linked its stylesheet straight from fonts.googleapis.com, with
preconnects to that host and fonts.gstatic.com. A deployed instance has
no outbound network, so those requests never arrive and the whole UI
renders in fallback faces — Georgia for the display face, whatever the
system has for Inter and JetBrains Mono.

This is invisible in development, which is why it survived: the dev
machine has internet, so the fonts load and everything looks right. Only
a real deployment shows the failure.

tools/vendor-fonts.py fetches the three families once and writes them
under web/static/fonts with a generated stylesheet. static/ is copied
into the SvelteKit build, which Go embeds, so the faces travel inside the
binary. 32 woff2 files, 912K.

Two details that matter for correctness rather than size:

Urls in the generated CSS are relative (./Inter-400-latin.woff2), not
absolute. A url() resolves against the stylesheet's own address, so the
directory keeps working when the app is served under a base path;
/fonts/... would not.

Every subset Google slices is kept, with unicode-range intact. The
browser still fetches only the ranges a page uses, so this costs
repository bytes rather than request bytes — and a library full of
Cyrillic or Greek artist names renders instead of falling back mid-list.

The guard asserts the property, not the vendor: any absolute url in a
resource-loading attribute fails, whoever hosts it, since naming Google
would pass the day someone reached for a different CDN. It also checks
preconnect separately (those carry no fetch of their own, so the url
check misses them), strips HTML comments before asserting an absence so
prose describing the forbidden thing cannot satisfy the check, and pins
the font families to tokens.json rather than a hardcoded list.

Falsified against the pre-change app.html: it trips both the external-url
and preconnect assertions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
2026-09-09 13:49:47 -04:00
bvandeusenandClaude Opus 5 b06a1adfe8 feat(brand): draw a reduced mark so the favicon reads at 16px
The full hat does not resolve below ~32px, which the header worked around
by sizing up. A browser tab cannot: it renders the favicon at 16px and
does not ask. There the mark was a blob.

Deriving a small form from the traced art does not work, and this is the
non-obvious part. Hole-filling, morphological smoothing and dropping
components were all tried; every one of them preserves the overall
silhouette, and the overall silhouette — dominated by a long diagonal
plume — is precisely what fails. The result each time was a diagonal
smear that reads as no object at all.

So the reduced form is drawn rather than derived: a strong horizontal
brim under a crown that peaks left of centre, a band slit so the two do
not fuse, and a short pointed plume. Same lean and proportions as the
full mark, detail removed instead of minified.

favicon.svg and favicon.png now use it; apple-touch, icon-512 and the
Android launcher icons keep the full art, being large enough for it. The
plume carries the accent, which measures 3.04:1 on obsidian and 5.43:1 on
the light ground — both clear of the 3:1 graphics floor.

Also corrects the accent-on-iron figure in the generator's comment from
2.80:1 to 2.70:1. The real --fs-iron is #1E2228; 2.80 came from measuring
against a value I had guessed rather than read.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
2026-09-09 13:49:31 -04:00
bvandeusenandClaude Opus 5 5593f7ce17 feat(brand): replace the M mark with the traced bard-hat logo
test-web / test (push) Successful in 44s
android / Build + lint + test (push) Successful in 5m9s
The mark is now a feathered hat with an arc of eighth notes, traced from
the operator's reference artwork at 99.74% IoU. The hat takes the text
colour and the note arc holds the accent — the same construction the M
used, and for the same reason: parchment on a light surface is invisible,
so the silhouette has to flip with its background while the accent stays
constant.

This reverses the subject-neutrality argument recorded in Minstrel's
design system, which held that depicting a bard would tell a new user the
app is for renaissance-faire music and had twice rejected a hat. The
operator commissioned this artwork and chose it with that objection on the
table; the record is updated rather than silently contradicted.

Both accent-filled alternatives were measured and rejected: #4A6B5C is
3.04:1 on obsidian and 2.80:1 on the raised iron, so an accent hat drops
under the 3:1 graphics floor as soon as it sits on a card.

tools/gen-brand-assets.py is the single source for the four copies, which
cannot share a file because each needs a different colour mechanism —
currentColor inlined, a prefers-color-scheme swap in the favicon, literal
fills in mark.svg, flat pixels in the rasters. Hand-copying 20KB of path
data four ways is how a silhouette change lands in three of them.

Two notes on the trace, both non-obvious: it runs on the original
antialiased greyscale rather than a binary mask, because tracing a
supersampled mask scores ~100% IoU by reproducing the pixel staircase
exactly — a perfect number for jagged art at 120KB of path, versus 99.74%
at 20KB. And potrace reads PBM where bit 1 is black, so the ink mask is
inverted going in; backwards, it traces the background and still emits a
plausible-looking SVG.

The header lockup moves 20px → 28px: the hat carries far more detail than
the M and does not resolve below ~32px. The 16px browser-tab favicon is
still a blob at that size and is not addressed here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQ31KQpYbStyK5y58UmPLH
2026-09-09 12:52:57 -04:00
66 changed files with 2053 additions and 163 deletions
-1
View File
@@ -150,7 +150,6 @@ dependencies {
implementation(libs.compose.ui) implementation(libs.compose.ui)
implementation(libs.compose.ui.graphics) implementation(libs.compose.ui.graphics)
implementation(libs.compose.material3) implementation(libs.compose.material3)
implementation(libs.compose.ui.text.google.fonts)
debugImplementation(libs.compose.ui.tooling) debugImplementation(libs.compose.ui.tooling)
implementation(libs.compose.ui.tooling.preview) implementation(libs.compose.ui.tooling.preview)
@@ -2,72 +2,45 @@ package com.fabledsword.minstrel.theme
import androidx.compose.material3.Typography import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.googlefonts.Font
import androidx.compose.ui.text.googlefonts.GoogleFont
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.fabledsword.minstrel.R import com.fabledsword.minstrel.R
/** /**
* Google Fonts provider — fetches font files via Play Services Fonts at * Bundled typefaces, vendored into res/font by tools/vendor-fonts.py.
* runtime, caches them across launches. Matches the Flutter client's *
* `google_fonts` package behaviour (no bundled .ttf files in either tree). * These were fetched at runtime through the Play Services font provider until
* 2026-09-09. That is a network dependency for rendering, and a deployed
* instance is not guaranteed one — the provider is also absent entirely on
* devices without Play Services, where the app silently fell back to the
* platform default and stopped looking like Minstrel. Bundling costs ~0.86 MB
* of APK and removes both failure modes.
* *
* Per FabledSword design system: * Per FabledSword design system:
* - Fraunces — display + headline (mythic serif) * - Fraunces — display + headline (mythic serif)
* - Inter — body + label (clean sans for UI text) * - Inter — body + label (clean sans for UI text)
* - JetBrains Mono — technical / monospace * - JetBrains Mono — technical / monospace
* Weights are restricted to 400 (regular) and 500 (medium) only. * Weights are restricted to 400 (regular) and 500 (medium) only.
*
* Each res/font entry is a single static instance, not a variable font: the
* weight declared beside it here must match the file's own OS/2
* usWeightClass, which the vendoring script asserts on download.
*/ */
private val GoogleFontProvider = GoogleFont.Provider(
providerAuthority = "com.google.android.gms.fonts",
providerPackage = "com.google.android.gms",
certificates = R.array.com_google_android_gms_fonts_certs,
)
private val FrauncesFont = GoogleFont("Fraunces")
private val InterFont = GoogleFont("Inter")
private val JetBrainsMonoFont = GoogleFont("JetBrains Mono")
private val Fraunces = FontFamily( private val Fraunces = FontFamily(
Font( Font(R.font.fraunces_regular, FontWeight.W400, FontStyle.Normal),
googleFont = FrauncesFont, Font(R.font.fraunces_medium, FontWeight.W500, FontStyle.Normal),
fontProvider = GoogleFontProvider,
weight = FontWeight.W400,
style = FontStyle.Normal,
),
Font(
googleFont = FrauncesFont,
fontProvider = GoogleFontProvider,
weight = FontWeight.W500,
style = FontStyle.Normal,
),
) )
private val Inter = FontFamily( private val Inter = FontFamily(
Font( Font(R.font.inter_regular, FontWeight.W400, FontStyle.Normal),
googleFont = InterFont, Font(R.font.inter_medium, FontWeight.W500, FontStyle.Normal),
fontProvider = GoogleFontProvider,
weight = FontWeight.W400,
style = FontStyle.Normal,
),
Font(
googleFont = InterFont,
fontProvider = GoogleFontProvider,
weight = FontWeight.W500,
style = FontStyle.Normal,
),
) )
private val JetBrainsMono = FontFamily( private val JetBrainsMono = FontFamily(
Font( Font(R.font.jetbrains_mono_regular, FontWeight.W400, FontStyle.Normal),
googleFont = JetBrainsMonoFont,
fontProvider = GoogleFontProvider,
weight = FontWeight.W400,
style = FontStyle.Normal,
),
) )
/** /**
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 25 KiB

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Google Fonts provider certificate hashes for downloadable fonts via
androidx.compose.ui.text.googlefonts.GoogleFont.Provider. Standard
values published by Google; copied verbatim from the AndroidX docs. -->
<resources>
<array name="com_google_android_gms_fonts_certs">
<item>@array/com_google_android_gms_fonts_certs_dev</item>
<item>@array/com_google_android_gms_fonts_certs_prod</item>
</array>
<string-array name="com_google_android_gms_fonts_certs_dev">
<item>MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpoyLcfobBPv6yyz8x1IxWWmF9c1IGN3vSL6BLNJEUyMEPzC2WZdwT4ZG2cuJTtzeETl6jWFKx68ETtZxNVHe9Iy9NMxEljDqVZ4y6+FlHaiYJqq3LcJpJVuKYz4kvOcyf3M0nDA8mUlVdfsOlw/H4uoNQ7VrAQUKB4kAyfxsKp/RZmnZSJ7+8Ag9aTC+oguTd1iFNuMqDUlpePo6CGuh73iKuq8mYvtdQQ0Yz+mF4j2YWB7Gj0R1k2cCAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs=</item>
</string-array>
<string-array name="com_google_android_gms_fonts_certs_prod">
<item>MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAwEAAaOB1zCB1DAdBgNVHQ4EFgQUhzkS9E6G+x8U7eIYZVgWyN4j2u4wgaQGA1UdIwSBnDCBmYAUhzkS9E6G+x8U7eIYZVgWyN4j2u6heKR2MHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZIIJAMLgh0ZkSjCNMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABywqUAtNkXf2EVQuRGiI3pnNvIYx7N5xj4LMtloEdEqMpEcMa6Qe87qDx2hsArOR1nzQAFGsT/8YIIfX0fAJjQuP1lAcExSxVKbFICEvFBaWuhGgOOZ7CYzfHB6tEzJFLR2DQHQrXLT2HKDDhxhe9hKzqIRDSc5Hjr3jY5MMzfYM5lFvKK9pLqEsP6/Ad9SDhupcVoOWVrSCNKfRb6jpJbZuxJhCnq8tmlV4iy5tEW0a3VBYzpRoBdAaORWqHQTUlt+iL3aH7C5OxhgN/JuxvxXBL/3kkc0wK1ZNuk+sb4lNXmHnVqQYTcyowQHRPCRsPzCCl4ANULRpZjxAd0xUgg=</item>
</string-array>
</resources>
@@ -0,0 +1,125 @@
package com.fabledsword.minstrel.theme
import org.junit.jupiter.api.Test
import java.io.File
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/**
* Guards that the typefaces ship inside the APK instead of being fetched at
* runtime.
*
* Until 2026-09-09 these were resolved through the Play Services font
* provider. That needs a network the deployed app is not guaranteed, and a
* provider that devices without Play Services do not have at all. Both
* failures are silent — text just renders in the platform default, which
* reads as a styling regression rather than a missing dependency.
*
* Expectations are read out of Typography.kt itself rather than hardcoded, so
* this cannot drift away from what the app actually declares: adding a face
* without vendoring its file fails here, and so does changing a declared
* weight without refetching the matching static instance.
*/
class BundledFontsTest {
@Test
fun `typography builds its families from bundled resources`() {
val source = typographySource()
assertTrue(
source.contains("R.font."),
"Typography.kt should build its families from res/font resources",
)
FORBIDDEN.forEach { symbol ->
assertTrue(
!source.contains(symbol),
"Typography.kt must not reference $symbol — fonts are bundled, not fetched",
)
}
}
@Test
fun `every declared face is vendored as TrueType at its declared weight`() {
val declared = FACE_PATTERN.findAll(typographySource()).toList()
assertTrue(
declared.isNotEmpty(),
"no Font(R.font.…, FontWeight.W…) declarations found — the guard would pass vacuously",
)
declared.forEach { match ->
val (name, weight) = match.destructured
val file = File(appDir(), "src/main/res/font/$name.ttf")
assertTrue(file.isFile, "res/font/$name.ttf is missing — run tools/vendor-fonts.py")
val bytes = file.readBytes()
assertTrue(
bytes.copyOfRange(0, TTF_MAGIC.size).contentEquals(TTF_MAGIC),
"$name.ttf is not TrueType — res/font cannot load a woff2 or an eot",
)
// The decisive check. Google's css2 endpoint silently collapses a
// multi-weight request to 400 for legacy clients, so Medium can
// come back as Regular: a valid TrueType file that renders at the
// wrong weight everywhere. usWeightClass is the only field that
// tells the two apart.
assertEquals(
weight.toInt(),
weightClass(bytes),
"$name.ttf carries a different OS/2 usWeightClass than the FontWeight declared beside it",
)
}
}
/** Typography.kt with comments removed, so prose naming the forbidden
* symbols cannot satisfy — or trip — the absence check above. */
private fun typographySource(): String =
File(appDir(), TYPOGRAPHY)
.readText()
.replace(BLOCK_COMMENT, "")
.replace(LINE_COMMENT, "")
/** Gradle's working directory for tests is the module dir, but don't rely
* on it: walk up until the module is found, and say so if it isn't. */
private fun appDir(): File {
var dir: File? = File("").absoluteFile
while (dir != null) {
if (File(dir, TYPOGRAPHY).isFile) return dir
if (File(dir, "app/$TYPOGRAPHY").isFile) return File(dir, "app")
dir = dir.parentFile
}
error("could not locate the app module from ${File("").absolutePath}")
}
private fun weightClass(bytes: ByteArray): Int {
val tables = readU16(bytes, NUM_TABLES)
for (i in 0 until tables) {
val record = TABLE_DIRECTORY + i * TABLE_RECORD
if (String(bytes, record, TAG_LENGTH, Charsets.US_ASCII) == "OS/2") {
return readU16(bytes, readU32(bytes, record + OFFSET_FIELD) + WEIGHT_FIELD)
}
}
error("no OS/2 table in the font")
}
private fun readU16(bytes: ByteArray, at: Int): Int =
((bytes[at].toInt() and BYTE_MASK) shl Byte.SIZE_BITS) or (bytes[at + 1].toInt() and BYTE_MASK)
private fun readU32(bytes: ByteArray, at: Int): Int =
(readU16(bytes, at) shl Short.SIZE_BITS) or readU16(bytes, at + 2)
private companion object {
const val TYPOGRAPHY = "src/main/java/com/fabledsword/minstrel/theme/Typography.kt"
val FORBIDDEN = listOf("GoogleFont", "googlefonts")
val FACE_PATTERN = Regex("""R\.font\.(\w+)\s*,\s*FontWeight\.W(\d+)""")
val BLOCK_COMMENT = Regex("""/\*[\s\S]*?\*/""")
val LINE_COMMENT = Regex("""//.*""")
val TTF_MAGIC = byteArrayOf(0x00, 0x01, 0x00, 0x00)
// Offsets into the TrueType table directory, per the OpenType spec.
const val NUM_TABLES = 4
const val TABLE_DIRECTORY = 12
const val TABLE_RECORD = 16
const val TAG_LENGTH = 4
const val OFFSET_FIELD = 8
const val WEIGHT_FIELD = 4
const val BYTE_MASK = 0xFF
}
}
-1
View File
@@ -53,7 +53,6 @@ compose-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-material3 = { module = "androidx.compose.material3:material3" } compose-material3 = { module = "androidx.compose.material3:material3" }
compose-ui-text-google-fonts = { module = "androidx.compose.ui:ui-text-google-fonts" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" } hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

+377
View File
@@ -0,0 +1,377 @@
#!/usr/bin/env python3
"""Trace the Minstrel logo and emit every brand asset that derives from it.
Run by hand after the source artwork changes, never in CI — it needs potrace
and Pillow, which the build images don't carry, and its outputs are committed
(the app must render itself with no outbound network).
python3 tools/gen-brand-assets.py
Why a generator at all: the mark ships in four places that cannot share one
file, because each needs a different colour mechanism — currentColor in the
component, literal fills in mark.svg, a prefers-color-scheme swap in
favicon.svg, and flattened pixels in the rasters. Hand-copying ~20KB of path
data four ways is how a silhouette change lands in three of them and not the
fourth. The paths have one source here instead.
The trace pipeline, and why each step is what it is:
* Components are separated first (connected-component labelling) so the hat
and the note arc can take different fills. Grouping is by component id
against the source raster, which is stable as long as SOURCE doesn't move.
* Tracing runs on the ORIGINAL antialiased greyscale, upsampled and blurred
— not on the binary mask. Tracing the mask supersampled hits 100% IoU by
reproducing the pixel staircase exactly: a perfect score for jagged art
and a 120KB path. Smoothing first finds a sub-pixel boundary instead,
which is 99.74% IoU at 20KB.
* potrace reads PBM, where bit 1 is BLACK, so the ink mask is inverted on
the way in. Getting this backwards traces the background and still
produces a plausible-looking SVG.
"""
import re, shutil, subprocess, sys
from pathlib import Path
import numpy as np
from PIL import Image, ImageDraw, ImageFilter
ROOT = Path(__file__).resolve().parent.parent
SOURCE = ROOT / "docs/brand/minstrel-logo-source.png"
PARCHMENT = "#E8E4D8"
ACCENT = "#4A6B5C" # --fs-accent; the one row Minstrel overrides
OBSIDIAN = "#14171A" # plate for rasters — see the contrast note in README
# Component ids from the source raster: the hat body, brim, feather, band and
# jewel take the text colour; the noteheads and the arc swashes take the accent.
HAT = [396, 58, 251, 497, 442]
NOTES = [31, 207, 30, 247, 317, 135, 78, 161, 186, 107, 215]
FRAME = [1] # the source's rounded-rect border — deliberately NOT traced: an
# adaptive icon gets masked to the launcher's own shape, so a
# baked border would be clipped raggedly, and at header size it
# is noise around a mark that is already busy.
SUPERSAMPLE, BLUR = 4, 1.5
POTRACE = ["-s", "--flat", "-a", "1.334", "-O", "0.2", "-u", "4", "-t", "20"]
def components(gray):
"""Label 8-connected runs of ink. Two-pass union-find; no scipy here."""
ink = gray > 128
H, W = ink.shape
parent, labels, nxt = {}, np.zeros((H, W), np.int32), 1
def find(x):
r = x
while parent[r] != r:
r = parent[r]
while parent[x] != r:
parent[x], x = r, parent[x]
return r
for y in range(H):
if not ink[y].any():
continue
prev = labels[y - 1] if y else None
for x in np.nonzero(ink[y])[0]:
nb = [labels[y, x - 1]] if x and labels[y, x - 1] else []
if y:
nb += [prev[xx] for xx in (x - 1, x, x + 1) if 0 <= xx < W and prev[xx]]
if nb:
m = min(nb)
labels[y, x] = m
for n in nb:
rm, rn = find(m), find(n)
if rm != rn:
parent[max(rm, rn)] = min(rm, rn)
else:
parent[nxt] = nxt
labels[y, x] = nxt
nxt += 1
flat = np.array([0] + [find(i) for i in range(1, nxt)], np.int32)
return flat[labels]
def trace(labels, gray, ids, tmp):
keep = np.isin(labels, ids)
# Dilate the group mask so the component's own antialiased fringe survives;
# thresholding a hard-cut mask would shave a half-pixel off every edge.
grown = Image.fromarray((keep * 255).astype(np.uint8)).filter(ImageFilter.MaxFilter(5))
img = Image.fromarray(np.where(np.asarray(grown), gray, 0.0).astype(np.uint8))
H, W = labels.shape
img = img.resize((W * SUPERSAMPLE, H * SUPERSAMPLE), Image.BICUBIC)
img = img.filter(ImageFilter.GaussianBlur(BLUR))
img.point(lambda p: 0 if p > 128 else 255).convert("1", dither=Image.NONE).save(tmp / "g.pbm")
subprocess.run(["potrace", *POTRACE, str(tmp / "g.pbm"), "-o", str(tmp / "g.svg")], check=True)
s = (tmp / "g.svg").read_text()
return (" ".join(re.findall(r'<path d="([^"]+)"', s)),
re.search(r'<g transform="([^"]+)"', s).group(1))
def wrap(d, width=78, indent=" " * 6):
"""Re-wrap path data so diffs stay reviewable instead of one endless line."""
out, line = [], ""
for tok in d.split():
if len(line) + len(tok) + 1 > width:
out.append(line)
line = tok
else:
line = f"{line} {tok}".strip()
out.append(line)
return ("\n" + indent).join(out)
# ---------------------------------------------------------------------------
# The reduced mark, for small sizes.
#
# NOT a simplification of the traced art — that was tried and does not work.
# The source composition is dominated by a long diagonal plume, so every
# mechanical reduction of it (hole-filling, morphological smoothing, dropping
# components) collapses at 16px into a diagonal smear that reads as no object
# at all. What reads at that size is a strong horizontal brim under a leaning
# crown, which has to be DRAWN rather than derived.
#
# So this is hand-authored geometry, tuned against the real thing: the crown
# peaks left of centre with a long right flank, the brim rides up at the right
# tip, and a band slit keeps crown and brim from fusing into one lump. It is a
# family member of the full mark, not a copy of it.
# ---------------------------------------------------------------------------
RN = 640 # design-space square for the reduced mark
def _bez(pts, n=60):
"""Sample a chain of cubic beziers given as [P0, C1,C2,P1, C1,C2,P2, ...]."""
out = [pts[0]]
for i in range(1, len(pts), 3):
p0, (c1, c2, p1) = out[-1], pts[i:i + 3]
for t in np.linspace(0, 1, n)[1:]:
u = 1 - t
out.append((u**3 * p0[0] + 3*u*u*t * c1[0] + 3*u*t*t * c2[0] + t**3 * p1[0],
u**3 * p0[1] + 3*u*u*t * c1[1] + 3*u*t*t * c2[1] + t**3 * p1[1]))
return out
def _reduced(plume):
"""The reduced hat. plume=False gives crown+brim only; the plume is taken
as the difference between the two so it can be filled separately."""
im = Image.new("L", (RN, RN), 0)
d = ImageDraw.Draw(im)
by = 336
# Brim: the single strongest horizontal in the mark, and the shape that
# says "hat" at 16px, so it stays thick and unbroken end to end.
rt = by - 74
d.polygon(_bez([(38, by + 10),
(150, by - 58), (430, by - 72), (590, rt),
(614, rt + 10), (610, rt + 40), (586, rt + 54),
(450, by + 56), (150, by + 58), (38, by + 30),
(22, by + 26), (22, by + 16), (38, by + 10)]), fill=255)
# Crown: peak pushed left of centre with a long right flank — the lean is
# what keeps this recognisably the same hat as the traced one.
px, top = 260, by - 256
d.polygon(_bez([(190, by + 16),
(182, by - 96), (px - 92, top + 66), (px - 40, top + 10),
(px - 6, top - 14), (px + 44, top + 10), (px + 62, top + 56),
(px + 108, top + 150), (422, by - 120), (440, by - 34),
(448, by - 4), (424, by + 18), (190, by + 16)]), fill=255)
if plume:
# A pointed leaf, deliberately short: a long thin plume is exactly what
# turns the whole mark into a diagonal bar at small sizes.
d.polygon(_bez([(404, by - 158),
(466, by - 240), (546, by - 296), (586, by - 310),
(580, by - 270), (532, by - 196), (458, by - 132)]), fill=255)
a = np.asarray(im) > 128
# Band slit: across the crown base only. Never across the brim — breaking
# the brim costs more legibility than the band gap buys.
g = Image.new("L", (RN, RN), 0)
ImageDraw.Draw(g).polygon(_bez([(192, by - 30),
(256, by - 50), (376, by - 54), (438, by - 58),
(446, by - 32), (300, by - 26), (192, by - 6)]), fill=255)
return a & ~(np.asarray(g) > 128)
def trace_reduced(tmp):
"""Trace the reduced hat and plume as two fills, on one shared viewBox."""
full, hat_only = _reduced(True), _reduced(False)
plume_only = full & ~hat_only
ys, xs = np.nonzero(full)
pad = 8
box = (xs.min() - pad, ys.min() - pad, xs.max() + pad, ys.max() + pad)
def one(mask, name):
# Ink BRIGHT here, matching what trace() feeds potrace: the point()
# below is what inverts to PBM's ink-is-black. Passing an already-dark
# mask double-inverts and silently traces the background instead.
img = Image.fromarray((mask * 255).astype(np.uint8))
img = img.resize((RN * SUPERSAMPLE, RN * SUPERSAMPLE), Image.BICUBIC)
img = img.filter(ImageFilter.GaussianBlur(BLUR))
img.point(lambda p: 0 if p > 128 else 255).convert("1", dither=Image.NONE).save(tmp / f"{name}.pbm")
subprocess.run(["potrace", *POTRACE, str(tmp / f"{name}.pbm"), "-o", str(tmp / f"{name}.svg")], check=True)
s = (tmp / f"{name}.svg").read_text()
return (" ".join(re.findall(r'<path d="([^"]+)"', s)),
re.search(r'<g transform="([^"]+)"', s).group(1))
hat_d, tr = one(hat_only, "rhat")
plume_d, _ = one(plume_only, "rplume")
S = SUPERSAMPLE
vb = f"{box[0]*S} {box[1]*S} {(box[2]-box[0])*S} {(box[3]-box[1])*S}"
return hat_d, plume_d, tr, vb
def svg_doc(vb, tr, hat_fill, notes_fill, hat_d, notes_d, style="", head=""):
return (f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="{vb}">\n'
f'{head}{style}'
f' <g transform="{tr}" fill-rule="evenodd">\n'
f' <path fill="{hat_fill}" d="{wrap(hat_d)}"/>\n'
f' <path fill="{notes_fill}" d="{wrap(notes_d)}"/>\n'
f' </g>\n</svg>\n')
def rasterize(svg_path, out, px, plate=None, radius_frac=0.0, art_frac=1.0):
"""Render the mark to a PNG, optionally on a rounded plate.
Rasters carry their own background because a PNG cannot answer to a colour
scheme and iOS composites the touch icon onto white regardless. Obsidian
rather than the raised iron: the accent clears the 3:1 graphics floor
against obsidian (3.04:1) and fails against iron (2.70:1). Lightening the
plate makes this worse, not better, because the accent is a dark colour.
"""
art = round(px * art_frac)
subprocess.run(["rsvg-convert", "-w", str(art), "-o", str(out), str(svg_path)], check=True)
mark = Image.open(out).convert("RGBA")
canvas = Image.new("RGBA", (px, px), (0, 0, 0, 0))
if plate:
r = round(px * radius_frac)
layer = Image.new("RGBA", (px, px), (0, 0, 0, 0))
ImageDraw.Draw(layer).rounded_rectangle([0, 0, px - 1, px - 1], radius=r, fill=plate)
canvas.alpha_composite(layer)
canvas.alpha_composite(mark, ((px - mark.width) // 2, (px - mark.height) // 2))
canvas.save(out)
def main():
tmp = ROOT / ".brand-tmp"
tmp.mkdir(exist_ok=True)
if not SOURCE.exists():
sys.exit(f"source artwork missing: {SOURCE}")
a = np.asarray(Image.open(SOURCE).convert("RGB")).astype(np.float32)
gray = 0.299 * a[:, :, 0] + 0.587 * a[:, :, 1] + 0.114 * a[:, :, 2]
labels = components(gray)
hat_d, tr = trace(labels, gray, HAT, tmp)
notes_d, _ = trace(labels, gray, NOTES, tmp)
# Tight viewBox around the traced art (frame excluded), in potrace's user
# space, which is source pixels x SUPERSAMPLE.
ys, xs = np.nonzero(np.isin(labels, HAT + NOTES))
pad = 6
x0, x1 = xs.min() - pad, xs.max() + pad
y0, y1 = ys.min() - pad, ys.max() + pad
S = SUPERSAMPLE
vb = f"{x0*S} {y0*S} {(x1-x0)*S} {(y1-y0)*S}"
w, h = (x1 - x0) * S, (y1 - y0) * S
(ROOT / "web/static/brand/mark.svg").write_text(svg_doc(
vb, tr, PARCHMENT, ACCENT, hat_d, notes_d,
head=" <!-- Generated by tools/gen-brand-assets.py — do not edit by hand.\n"
" Literal colours, for any consumer that cannot inline the SVG and\n"
" therefore cannot supply a currentColor. -->\n"))
# The reduced mark, for anywhere the full art cannot resolve.
rhat_d, rplume_d, rtr, rvb = trace_reduced(tmp)
(ROOT / "web/static/brand/mark-small.svg").write_text(svg_doc(
rvb, rtr, PARCHMENT, ACCENT, rhat_d, rplume_d,
head=" <!-- Generated by tools/gen-brand-assets.py — do not edit by hand.\n"
" The reduced mark, literal colours. For use at or below ~24px,\n"
" where the full art collapses into a diagonal smear. -->\n"))
# Favicon uses the REDUCED mark: a browser tab renders it at 16px, and the
# full art is unreadable there. This is the one consumer whose size is not
# ours to choose, so it takes the form drawn for that size.
(ROOT / "web/static/brand/favicon.svg").write_text(svg_doc(
rvb, rtr, "currentColor", ACCENT, rhat_d, rplume_d,
head=" <!-- Generated by tools/gen-brand-assets.py — do not edit by hand.\n"
" The REDUCED mark, because a tab renders this at 16px.\n"
" The hat flips with the viewer's scheme because a favicon sits on\n"
" browser chrome we don't control: parchment would vanish on a light\n"
" tab strip, obsidian on a dark one. The plume holds the accent in\n"
" both — teal reads against either (3.04:1 on obsidian, 5.43:1 on\n"
" the light ground). -->\n",
style=" <style>\n"
f" svg {{ color: {PARCHMENT}; }}\n"
f" @media (prefers-color-scheme: light) {{ svg {{ color: {OBSIDIAN}; }} }}\n"
" </style>\n"))
plated = ROOT / ".brand-tmp/plated.svg"
plated.write_text(svg_doc(vb, tr, PARCHMENT, ACCENT, hat_d, notes_d))
# Web rasters: plate radius and art scale measured off the icons these
# replace, so the new mark sits where the old one did.
for out, px in [("web/static/brand/icon-512.png", 512),
("web/static/apple-touch-icon.png", 180)]:
rasterize(plated, ROOT / out, px, plate=OBSIDIAN, radius_frac=0.20, art_frac=0.88)
# 32px fallback favicon: reduced art, same reasoning as favicon.svg.
plated_small = ROOT / ".brand-tmp/plated-small.svg"
plated_small.write_text(svg_doc(rvb, rtr, PARCHMENT, ACCENT, rhat_d, rplume_d))
rasterize(plated_small, ROOT / "web/static/favicon.png", 32,
plate=OBSIDIAN, radius_frac=0.20, art_frac=0.90)
# Adaptive icon foreground: transparent, art inside the 66/108dp safe zone
# so no launcher mask can clip it. The plate comes from the XML instead.
res = ROOT / "android/app/src/main/res"
for d, fg, legacy in [("mdpi", 108, 48), ("hdpi", 162, 72), ("xhdpi", 216, 96),
("xxhdpi", 324, 144), ("xxxhdpi", 432, 192)]:
rasterize(plated, res / f"mipmap-{d}/ic_launcher_foreground.png", fg, art_frac=0.58)
rasterize(plated, res / f"mipmap-{d}/ic_launcher.png", legacy,
plate=OBSIDIAN, radius_frac=0.20, art_frac=0.88)
(ROOT / "web/src/lib/components/MinstrelMark.svelte").write_text(
COMPONENT.format(vb=vb, tr=tr, w=w, h=h,
hat=wrap(hat_d, indent=" " * 6), notes=wrap(notes_d, indent=" " * 6),
accent=ACCENT))
print(f"viewBox {vb} aspect {w/h:.4f} hat {len(hat_d)/1024:.1f}KB notes {len(notes_d)/1024:.1f}KB")
shutil.rmtree(tmp)
COMPONENT = '''<script lang="ts">
// The Minstrel mark: a feathered bard's hat with an arc of notes overhead.
//
// Generated by tools/gen-brand-assets.py — do not edit the paths by hand.
// The same silhouette ships in web/static/brand/{{mark,favicon}}.svg and the
// Android launcher icons; regenerate all of them together.
//
// Inlined rather than <img src="mark.svg"> on purpose — an <img> cannot
// inherit currentColor, and inheriting it is the whole point: the hat takes
// the surrounding text colour, so it reads on both the dark and light
// palettes without a second asset. Parchment-on-parchment is invisible,
// which is exactly the bug a fixed fill would reintroduce.
//
// The notes keep the accent in both modes — one of the places the design
// system sanctions the accent (the wordmark).
//
// aria-hidden: every current use sits directly beside the words "Minstrel",
// so labelling it would make a screen reader announce the name twice. A
// STANDALONE use would need its own label.
let {{ size = 28, class: klass = '' }}: {{ size?: number; class?: string }} = $props();
</script>
<svg
viewBox="{vb}"
width={{size * {w} / {h}}}
height={{size}}
class={{klass}}
aria-hidden="true"
focusable="false"
>
<g transform="{tr}" fill-rule="evenodd">
<path fill="currentColor" d="{hat}"/>
<path fill="{accent}" d="{notes}"/>
</g>
</svg>
'''
if __name__ == "__main__":
main()
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/env python3
"""Download the fonts from Google Fonts and vendor them into the repo.
Covers BOTH clients — the web bundle (woff2, subsetted) and the Android app
(static ttf, whole-font) — because they draw from the same three families and
letting them drift is how one of them quietly stops matching the other.
Run by hand when the font set changes, never at build or run time:
python3 tools/vendor-fonts.py
The app must render itself with no outbound network, so nothing here may
happen while it is running. This script fetches once, writes the woff2 files
and a stylesheet that points at them by RELATIVE url, and everything is
committed. `web/static/` is copied verbatim into the SvelteKit build, which
Go then embeds — so the fonts travel inside the binary.
Relative urls in the stylesheet (`./Inter-latin.woff2`, not `/fonts/...`)
are deliberate: a CSS url() resolves against the stylesheet's own address, so
the whole directory keeps working when the app is served under a base path.
Subsets are kept as Google slices them, with their `unicode-range` intact.
That is not a size compromise — the browser downloads only the ranges a page
actually uses, so vendoring every subset costs repository bytes rather than
request bytes, and a library full of non-English artist names renders instead
of falling back mid-list.
"""
import re
import struct
import sys
import urllib.request
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
OUT = ROOT / "web/static/fonts"
ANDROID_OUT = ROOT / "android/app/src/main/res/font"
# Matches the faces the design system actually permits: two weights only, 400
# and 500 (never 600/700), and Fraunces' optical-size axis across its range.
FAMILIES = (
"family=Fraunces:opsz,wght@9..144,400;9..144,500"
"&family=Inter:wght@400;500"
"&family=JetBrains+Mono:wght@400;500"
)
CSS_URL = f"https://fonts.googleapis.com/css2?{FAMILIES}&display=swap"
# The UA is load-bearing, not cosmetic: Google Fonts serves a different FORMAT
# per client, and there is no parameter to ask for one directly.
# modern Chrome -> woff2 (what the browser wants)
# old Android -> ttf (what Android's res/font requires)
# MSIE 6 -> eot (an IE-only format; the obvious "old UA" choice
# and completely useless here — it downloads and
# looks plausible until you check the magic bytes)
UA = ("Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36")
UA_TTF = ("Mozilla/5.0 (Linux; U; Android 4.4.2; en-us) AppleWebKit/534.30 "
"(KHTML, like Gecko) Version/4.0 Mobile Safari/534.30")
# Android res/font resource names: lowercase, digits and underscore only.
# Each entry is one static weight — Compose selects by the FontWeight declared
# alongside it in Typography.kt, so the file must genuinely be that instance.
# css2 collapses a multi-weight request to 400 for legacy clients, so each
# weight is fetched on its own URL.
ANDROID_FACES = [
("fraunces_regular", "Fraunces:opsz,wght@9..144,400", 400),
("fraunces_medium", "Fraunces:opsz,wght@9..144,500", 500),
("inter_regular", "Inter:wght@400", 400),
("inter_medium", "Inter:wght@500", 500),
("jetbrains_mono_regular", "JetBrains+Mono:wght@400", 400),
]
TTF_MAGIC = (b"\x00\x01\x00\x00", b"true", b"OTTO")
def weight_class(data):
"""Read OS/2 usWeightClass out of a TrueType file.
Worth the twenty lines: css2 silently collapses a multi-weight request to
400 for legacy clients, so asking for Medium and getting Regular is a real
and quiet failure. The file downloads, has valid TrueType magic, and
renders — just at the wrong weight, everywhere, forever. This is the only
field that actually distinguishes them.
"""
count = struct.unpack(">H", data[4:6])[0]
for i in range(count):
off = 12 + i * 16
if data[off:off + 4] == b"OS/2":
table = struct.unpack(">I", data[off + 8:off + 12])[0]
return struct.unpack(">H", data[table + 4:table + 6])[0]
return None
def fetch(url, timeout=30, ttf=False):
req = urllib.request.Request(url, headers={"User-Agent": UA_TTF if ttf else UA})
with urllib.request.urlopen(req, timeout=timeout) as r:
return r.read()
def vendor_android():
"""Fetch static TTFs for the Android client's res/font."""
ANDROID_OUT.mkdir(parents=True, exist_ok=True)
for old in ANDROID_OUT.glob("*.ttf"):
old.unlink()
total = 0
for name, spec, want_weight in ANDROID_FACES:
css = fetch(f"https://fonts.googleapis.com/css2?family={spec}", ttf=True).decode()
url = re.search(r"url\((https://[^)]+)\)", css)
if not url:
sys.exit(f"no font url for {spec}")
data = fetch(url.group(1), ttf=True)
# Assert the FORMAT, because every wrong one still downloads happily
# and only fails later, on a device, as a silently missing typeface.
if not data.startswith(TTF_MAGIC):
sys.exit(f"{name}: expected TrueType, got magic {data[:4].hex()} "
f"(eot/woff means the UA negotiation broke)")
got = weight_class(data)
if got != want_weight:
sys.exit(f"{name}: wanted weight {want_weight}, file reports {got} "
f"(css2 collapsed the request to a single weight)")
(ANDROID_OUT / f"{name}.ttf").write_bytes(data)
total += len(data)
print(f" {name + '.ttf':<30} {len(data)/1024:7.1f} KB weight {got}")
print(f"{len(ANDROID_FACES)} ttf, {total/1024/1024:.2f} MB total -> "
f"{ANDROID_OUT.relative_to(ROOT)}")
def main():
css = fetch(CSS_URL).decode()
# Google emits a comment naming each subset before its @font-face block.
blocks = re.split(r"(?=/\*\s*[a-z0-9-]+\s*\*/)", css)
OUT.mkdir(parents=True, exist_ok=True)
for old in OUT.glob("*.woff2"):
old.unlink()
out_css, seen, total = [], {}, 0
for block in blocks:
subset = re.search(r"/\*\s*([a-z0-9-]+)\s*\*/", block)
family = re.search(r"font-family:\s*'([^']+)'", block)
weight = re.search(r"font-weight:\s*([^;]+);", block)
url = re.search(r"url\((https://[^)]+\.woff2)\)", block)
if not (subset and family and url):
continue
w = weight.group(1).strip().replace(" ", "-") if weight else "400"
name = f"{family.group(1).replace(' ', '')}-{w}-{subset.group(1)}.woff2"
if name not in seen:
data = fetch(url.group(1))
(OUT / name).write_bytes(data)
seen[name] = len(data)
total += len(data)
out_css.append(block.replace(url.group(1), f"./{name}"))
header = (
"/* Generated by tools/vendor-fonts.py — do not edit by hand.\n"
" *\n"
" * Vendored from Google Fonts and served from our own origin: the app\n"
" * has to render with no outbound network, so a font provider link is\n"
" * not an option. Urls are relative so this keeps working under a base\n"
" * path. unicode-range is preserved, so the browser still fetches only\n"
" * the subsets a page needs.\n"
" */\n"
)
(OUT / "fonts.css").write_text(header + "".join(out_css))
for n, size in sorted(seen.items()):
print(f" {n:<44} {size/1024:7.1f} KB")
print(f"{len(seen)} files, {total/1024/1024:.2f} MB total -> {OUT.relative_to(ROOT)}")
print()
vendor_android()
if __name__ == "__main__":
sys.exit(main())
+7 -7
View File
@@ -3,7 +3,7 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<!-- <!--
SVG first: it flips the M with the viewer's colour scheme, which the PNG SVG first: it flips the hat with the viewer's colour scheme, which the PNG
can't. The PNG is the fallback for browsers without SVG-favicon support can't. The PNG is the fallback for browsers without SVG-favicon support
and is plated for the same reason apple-touch-icon is — see brand/. and is plated for the same reason apple-touch-icon is — see brand/.
Ordering matters: browsers take the last icon they understand, so the Ordering matters: browsers take the last icon they understand, so the
@@ -30,12 +30,12 @@
} }
})(); })();
</script> </script>
<link rel="preconnect" href="https://fonts.googleapis.com" /> <!-- Fonts are vendored into web/static/fonts by tools/vendor-fonts.py and
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> served from our own origin. A deployed instance has no outbound
<link network, so a provider stylesheet would simply never arrive and the
rel="stylesheet" UI would render in fallback faces — hence no font-provider link and
href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500&family=Inter:wght@400;500&family=JetBrains+Mono:wght@400;500&display=swap" no preconnect to a host we don't run. -->
/> <link rel="stylesheet" href="%sveltekit.assets%/fonts/fonts.css" />
%sveltekit.head% %sveltekit.head%
<!-- MINSTREL_TOKENS --> <!-- MINSTREL_TOKENS -->
</head> </head>
+279 -36
View File
@@ -1,57 +1,300 @@
<script lang="ts"> <script lang="ts">
// The Minstrel mark: a Didone M whose right leg is an eighth note. // The Minstrel mark: a feathered bard's hat with an arc of notes overhead.
//
// Generated by tools/gen-brand-assets.py — do not edit the paths by hand.
// The same silhouette ships in web/static/brand/{mark,favicon}.svg and the
// Android launcher icons; regenerate all of them together.
// //
// Inlined rather than <img src="mark.svg"> on purpose — an <img> cannot // Inlined rather than <img src="mark.svg"> on purpose — an <img> cannot
// inherit currentColor, and inheriting it is the whole point: the letter // inherit currentColor, and inheriting it is the whole point: the hat takes
// takes the surrounding text colour, so it reads on both the dark and light // the surrounding text colour, so it reads on both the dark and light
// palettes without a second asset. Parchment-on-parchment is invisible, // palettes without a second asset. Parchment-on-parchment is invisible,
// which is exactly the bug a fixed fill would reintroduce. // which is exactly the bug a fixed fill would reintroduce.
// //
// The note keeps the accent in both modes — one of the places the design // The notes keep the accent in both modes — one of the places the design
// system sanctions the accent (the wordmark). // system sanctions the accent (the wordmark).
// //
// ⚠ These paths are duplicated in web/static/brand/favicon.svg and
// web/static/brand/mark.svg, which need literal colours instead of
// currentColor (a favicon has no cascade to inherit from). Change the
// silhouette here, change it there.
//
// aria-hidden: every current use sits directly beside the words "Minstrel", // aria-hidden: every current use sits directly beside the words "Minstrel",
// so labelling it would make a screen reader announce the name twice. A // so labelling it would make a screen reader announce the name twice. A
// STANDALONE use would need its own label. // STANDALONE use would need its own label.
let { size = 20, class: klass = '' }: { size?: number; class?: string } = $props(); let { size = 28, class: klass = '' }: { size?: number; class?: string } = $props();
</script> </script>
<svg <svg
viewBox="202 251 902 723" viewBox="608 592 3928 3568"
width={size * 902 / 723} width={size * 3928 / 3568}
height={size} height={size}
class={klass} class={klass}
aria-hidden="true" aria-hidden="true"
focusable="false" focusable="false"
> >
<g transform="translate(0,1254) scale(0.1,-0.1)" fill-rule="evenodd"> <g transform="translate(0.000000,5016.000000) scale(0.250000,-0.250000)" fill-rule="evenodd">
<path fill="currentColor" d="M2252 9878 l3 -152 109 -22 c342 -72 492 -184 538 -405 16 -74 24 <path fill="currentColor" d="M17412 17089 c-134 -7 -375 -42 -530 -77 -90 -20 -238 -58 -296 -76 -13 -4 -60
-4823 9 -5024 -20 -266 -73 -375 -236 -484 -116 -77 -332 -150 -543 -181 -114 -19 -104 -32 -44 -14 -90 -28 -102 -32 -60 -21 -156 -55 -206 -75 -31 -12 -72
-18 -107 -6 -110 -165 -1 -76 2 -143 7 -148 9 -9 2607 -11 2623 -1 14 9 10 -28 -92 -35 -20 -7 -56 -22 -80 -32 -24 -10 -50 -21 -58 -24 -17 -6 -79 -34 -133
280 -4 291 -7 5 -49 15 -93 22 -380 60 -638 193 -720 374 -63 136 -59 -12 -61 -59 -21 -10 -59 -27 -84 -38 -42 -19 -167 -81 -221 -111 -13 -7 -40 -21 -59 -32
2247 -3 1999 -2 2054 15 2015 116 -253 646 -1520 1183 -2825 71 -173 216 -524 -35 -18 -44 -23 -169 -97 -371 -217 -625 -419 -799 -637 -69 -87 -107 -141 -152
322 -780 206 -494 266 -640 370 -895 97 -237 68 -210 226 -210 l135 0 23 50 -216 -70 -117 -115 -204 -163 -314 -11 -26 -23 -54 -26 -61 -12 -26 -42 -108 -64
c13 27 95 212 182 410 134 307 747 1685 1015 2285 92 205 726 1599 910 2000 -173 -48 -144 -92 -356 -106 -516 -3 -27 -6 -63 -8 -78 -2 -15 -4 -64 -4 -108 0
65 140 126 274 137 297 22 50 52 71 74 52 12 -10 14 -266 14 -1864 l0 -1852 -77 -2 -94 -11 -89 -5 3 -16 39 -29 91 -54 222 -65 467 -32 690 12 78 31 167 48
-82 -7 c-347 -29 -716 -203 -973 -460 -710 -712 -343 -1645 650 -1649 453 -2 220 40 125 60 175 113 286 25 53 28 61 18 63 -9 2 -145 -92 -227 -157 -25 -20
892 184 1206 510 193 202 295 397 351 676 l23 112 0 2357 c0 1297 0 2358 1 -56 -44 -68 -54 -39 -30 -124 -107 -197 -180 -112 -109 -216 -229 -317 -364 -107
2358 12 0 142 -49 179 -67 342 -173 607 -545 715 -1004 85 -361 66 -801 -51 -142 -234 -379 -307 -568 -69 -177 -120 -389 -135 -558 -2 -26 -6 -68 -8 -92 -7
-1215 -43 -151 -40 -173 18 -174 53 0 284 377 396 650 243 590 303 1238 163 -80 -4 -224 8 -360 8 -89 28 -204 68 -392 8 -35 32 -125 40 -146 2 -5 8 -27 14
1754 -178 652 -643 1100 -1294 1248 -93 21 -122 22 -716 25 -707 4 -649 12 -48 6 -21 20 -65 31 -97 10 -33 19 -62 19 -64 0 -8 -17 11 -34 37 -33 51 -118
-696 -90 -15 -34 -78 -172 -140 -307 -593 -1297 -1123 -2469 -1717 -3800 -210 211 -158 298 -46 100 -102 256 -134 373 -56 211 -78 413 -67 635 4 78 17 180 30
-472 -193 -437 -204 -418 -11 19 -173 423 -630 1568 -214 536 -394 986 -400 241 12 51 -5 41 -49 -31 -16 -25 -33 -52 -38 -60 -65 -100 -169 -296 -217 -408
1000 -6 14 -76 187 -156 385 -80 198 -182 452 -228 565 -46 113 -140 346 -209 -42 -98 -90 -228 -118 -324 -37 -125 -87 -343 -103 -452 -22 -157 -30 -269 -30
518 -205 507 -225 554 -244 568 -14 11 -209 13 -1055 14 l-1038 0 3 -152z"/> -446 0 -178 6 -273 26 -406 2 -14 8 -54 12 -88 8 -58 49 -272 63 -326 21 -87 73
<path fill="#4A6B5C" d="M8830 7450 l0 -2582 -32 6 c-517 106 -1064 -47 -1442 -405 -598 -566 -265 94 -324 11 -31 29 -85 41 -120 12 -35 29 -83 38 -106 9 -23 24 -62 34 -86
-501 -1354 196 -1598 489 -170 1134 -19 1548 363 234 217 350 418 423 736 l22 22 -56 49 -122 69 -168 9 -19 15 -37 14 -40 -3 -5 -26 22 -62 74 -17 25 -41 60
95 3 2373 c2 1961 5 2372 16 2372 27 0 132 -41 205 -81 315 -169 569 -524 675 -52 76 -21 30 -118 181 -134 208 -4 8 -21 35 -36 61 -37 60 -133 247 -170 329
-944 50 -198 60 -285 60 -525 0 -288 -27 -487 -105 -756 -34 -120 -35 -130 -54 123 -107 261 -131 345 -7 23 -10 29 -16 29 -9 0 -10 -3 -16 -68 -16 -187 -15
-11 -143 33 -18 64 11 154 144 227 334 402 795 469 1235 37 238 34 646 -4 843 -298 4 -473 14 -124 47 -294 94 -485 25 -97 27 -106 83 -274 57 -173 84 -249 124
-149 761 -637 1271 -1353 1418 -98 20 -147 23 -466 27 l-358 4 0 -2582z"/> -348 16 -41 41 -103 55 -138 14 -35 45 -105 68 -156 24 -51 49 -106 57 -124 39
-87 106 -217 205 -398 25 -45 53 -98 63 -117 11 -19 21 -36 23 -37 7 -4 221 -3
225 1 2 3 6 20 7 40 2 19 6 44 8 55 2 11 5 31 6 44 1 13 6 49 10 80 4 31 12 83
16 116 18 132 52 331 112 658 4 23 13 73 20 110 19 101 46 237 66 330 10 45 22
99 26 120 20 91 35 161 48 214 15 62 33 136 40 162 4 14 21 79 37 144 17 65 35
134 40 154 6 20 18 63 27 96 36 134 103 348 152 486 14 39 32 90 40 114 9 24 23
65 32 90 9 25 24 66 32 90 8 24 24 65 34 90 10 25 28 72 40 104 26 70 43 112 119
292 9 21 20 48 25 60 5 12 20 46 33 76 157 352 355 737 536 1042 76 127 135 224
149 246 8 12 22 36 32 52 123 215 410 628 606 872 180 225 306 368 466 528 173
173 333 313 513 449 72 55 91 67 91 58 0 -6 -8 -14 -123 -135 -81 -86 -168 -183
-283 -316 -135 -156 -335 -426 -496 -668 -81 -123 -202 -319 -270 -441 -21 -36
-52 -91 -70 -123 -52 -91 -166 -313 -209 -408 -22 -47 -48 -101 -57 -120 -9 -19
-25 -54 -36 -78 -17 -39 -35 -79 -99 -224 -9 -20 -23 -54 -32 -76 -23 -55 -65
-155 -87 -208 -19 -46 -48 -118 -80 -202 -10 -27 -27 -70 -36 -94 -10 -24 -29
-76 -44 -116 -15 -40 -30 -81 -34 -92 -4 -11 -15 -42 -24 -68 -9 -26 -19 -54 -22
-62 -6 -15 -26 -74 -50 -144 -8 -25 -23 -69 -33 -98 -30 -86 -124 -379 -151 -472
-9 -31 -26 -87 -38 -124 -12 -37 -27 -89 -34 -114 -7 -25 -19 -66 -26 -90 -7 -24
-17 -59 -22 -78 -9 -34 -28 -101 -47 -164 -5 -19 -15 -54 -21 -78 -13 -51 -28
-105 -48 -180 -5 -18 -16 -59 -24 -92 -8 -33 -21 -81 -28 -106 -24 -87 -61 -229
-92 -358 -11 -44 -25 -98 -30 -120 -21 -80 -86 -345 -94 -380 -12 -52 -17 -75
-38 -158 -21 -87 -35 -147 -44 -186 -15 -67 -47 -211 -54 -244 -5 -20 -12 -50
-16 -68 -24 -105 -33 -145 -42 -188 -6 -26 -13 -60 -16 -74 -9 -41 -19 -101 -20
-116 0 -14 0 -14 58 -43 32 -16 61 -29 65 -29 4 0 23 24 52 67 55 80 95 133 200
257 108 129 221 243 347 350 20 18 47 41 60 51 12 11 45 37 74 59 29 22 65 50 80
61 34 26 135 97 182 128 19 12 58 38 88 57 30 19 63 41 74 48 11 7 39 25 62 39
77 49 110 70 192 127 45 31 89 61 96 66 22 15 121 95 164 132 57 50 156 149 208
209 57 64 151 193 193 263 31 51 38 68 34 73 -2 1 -43 -18 -92 -42 -49 -24 -100
-48 -113 -54 -79 -34 -112 -48 -148 -60 -86 -31 -283 -95 -338 -111 -32 -8 -91
-25 -132 -37 -78 -21 -317 -101 -398 -133 -26 -10 -64 -24 -84 -32 -20 -7 -48
-19 -64 -25 -26 -12 -46 -17 -46 -10 0 4 12 13 74 60 87 65 101 74 216 143 99 59
294 161 394 205 71 32 147 67 236 111 17 8 51 24 76 35 122 54 319 160 433 235
150 97 274 196 384 307 124 125 209 240 286 385 20 38 53 108 63 136 3 8 13 37
24 66 31 84 61 193 72 263 6 38 6 47 -2 47 -1 0 -17 -12 -34 -26 -39 -32 -127
-96 -175 -127 -110 -72 -280 -155 -429 -211 -42 -15 -181 -58 -260 -79 -162 -43
-418 -79 -644 -91 -105 -5 -130 -5 -130 2 0 7 16 12 67 24 176 40 443 130 669
225 93 39 268 127 356 178 62 36 198 123 240 155 263 195 443 402 602 694 52 97
104 228 139 354 15 53 36 152 51 246 20 123 31 317 18 322 -8 4 -15 -1 -29 -19
-26 -33 -92 -101 -137 -141 -107 -96 -288 -218 -431 -290 -49 -24 -149 -71 -169
-78 -8 -3 -27 -11 -44 -18 -16 -7 -34 -14 -40 -16 -5 -2 -23 -8 -38 -14 -67 -25
-143 -50 -206 -68 -10 -3 -46 -13 -80 -23 -63 -19 -115 -32 -186 -47 -22 -5 -55
-12 -74 -16 -19 -4 -47 -10 -62 -14 -15 -3 -44 -8 -64 -10 -20 -3 -57 -8 -82 -12
-63 -10 -80 -12 -80 -8 0 6 15 16 42 26 15 6 56 24 90 40 34 16 91 42 126 57 95
42 300 147 399 205 152 88 190 110 293 178 197 129 349 246 490 379 139 129 221
225 306 357 159 244 256 534 310 924 3 26 7 50 26 174 6 42 19 112 34 187 15 78
53 213 83 299 31 89 67 168 120 268 100 185 247 349 415 460 53 35 163 86 230
106 62 19 57 22 -74 46 -83 15 -107 18 -190 24 -69 5 -270 7 -340 3z M10962
13252 c-22 -4 -51 -10 -64 -14 -13 -4 -38 -11 -56 -17 -141 -42 -381 -164 -597
-303 -12 -8 -31 -21 -44 -29 -71 -45 -259 -181 -345 -250 -207 -164 -321 -263
-550 -480 -94 -88 -287 -283 -354 -358 -17 -18 -70 -76 -120 -130 -94 -102 -172
-190 -256 -287 -27 -32 -62 -72 -78 -90 -16 -18 -61 -70 -100 -116 -155 -182
-267 -303 -416 -450 -127 -125 -183 -178 -212 -202 -13 -11 -37 -32 -54 -46 -74
-65 -108 -89 -108 -76 0 4 31 51 70 104 14 19 37 52 52 74 15 22 54 76 87 119 53
72 67 95 55 95 -6 0 -79 -23 -140 -45 -239 -85 -448 -207 -599 -353 -44 -41 -107
-111 -146 -160 -57 -73 -143 -211 -203 -330 -41 -80 -115 -241 -150 -326 -8 -20
-20 -47 -26 -60 -26 -56 -106 -258 -163 -412 -34 -92 -132 -383 -161 -476 -72
-235 -91 -299 -115 -394 -42 -157 -53 -203 -67 -268 -6 -27 -12 -56 -14 -64 -6
-23 -16 -77 -16 -88 0 -12 23 -34 74 -67 71 -47 136 -74 276 -115 176 -51 387
-77 708 -86 153 -5 357 -1 476 8 26 2 76 6 110 8 139 9 375 34 504 54 21 3 57 9
80 12 82 11 129 18 197 30 38 7 78 14 90 16 11 2 36 6 54 10 18 3 57 11 87 16 63
12 227 46 290 61 78 17 195 45 246 58 28 6 60 14 72 17 25 6 134 36 382 104 37
10 76 21 86 24 41 11 281 85 380 116 250 79 681 228 878 302 54 20 195 73 344
128 48 18 107 40 131 48 24 9 47 18 51 21 5 3 21 26 37 51 16 25 53 78 81 118 87
122 116 167 147 231 54 109 73 181 73 281 0 90 -6 109 -96 298 -78 165 -170 375
-229 524 -37 93 -108 303 -133 394 -36 130 -85 346 -102 446 -23 143 -30 193 -34
242 -2 29 -6 77 -8 108 -8 91 -5 301 4 414 16 192 38 322 90 528 34 136 90 308
130 399 32 75 95 200 116 235 9 14 16 28 16 32 0 10 -27 41 -100 116 -105 107
-159 150 -247 193 -114 58 -250 78 -371 57z m-574 -653 c0 -3 -11 -19 -24 -35
-56 -66 -193 -249 -280 -372 -11 -16 -37 -53 -58 -82 -36 -52 -121 -175 -270
-396 -75 -111 -134 -197 -180 -262 -11 -16 -37 -53 -56 -82 -20 -29 -50 -72 -68
-96 -17 -24 -51 -71 -75 -104 -23 -33 -53 -73 -67 -90 -13 -16 -45 -57 -70 -90
-25 -33 -54 -71 -66 -84 -11 -14 -36 -44 -56 -68 -211 -257 -378 -416 -712 -676
-202 -158 -451 -314 -678 -427 -89 -44 -108 -51 -108 -43 0 6 60 71 172 186 36
37 101 106 144 152 43 46 93 99 110 118 35 36 141 155 190 212 17 19 46 53 66 75
106 116 265 304 348 409 22 28 50 62 62 76 23 26 112 138 204 254 45 57 121 152
238 294 56 69 157 186 190 222 14 15 39 42 54 60 81 93 266 279 418 420 102 95
305 256 444 353 125 86 128 88 128 76z M15278 9526 c-254 -10 -537 -42 -734 -84
-25 -6 -76 -16 -112 -24 -36 -8 -77 -17 -90 -20 -13 -3 -40 -9 -60 -14 -53 -12
-94 -23 -148 -39 -26 -7 -67 -19 -90 -26 -105 -29 -86 -17 -169 -106 -108 -115
-183 -200 -183 -207 0 -3 1 -6 3 -7 2 -1 60 12 129 30 238 60 436 99 672 133 378
53 762 62 1100 26 394 -43 669 -112 950 -237 86 -38 120 -56 199 -104 127 -76
190 -124 288 -220 56 -55 72 -75 59 -75 -3 0 -36 11 -75 24 -104 35 -245 74 -334
92 -333 70 -595 86 -899 56 -432 -43 -871 -187 -1332 -438 -230 -125 -518 -327
-752 -525 -121 -103 -206 -178 -288 -255 -42 -39 -93 -86 -112 -104 -177 -163
-532 -518 -810 -808 -46 -48 -129 -135 -184 -192 -55 -58 -148 -155 -207 -216
-100 -105 -332 -337 -427 -428 -81 -78 -251 -228 -320 -282 -287 -228 -476 -351
-804 -523 -150 -78 -582 -266 -840 -365 -243 -93 -485 -179 -676 -241 -74 -24
-251 -78 -274 -85 -11 -3 -36 -10 -56 -16 -106 -31 -150 -44 -280 -80 -47 -13
-154 -41 -238 -60 -23 -6 -56 -14 -74 -18 -49 -12 -99 -23 -152 -34 -26 -5 -77
-16 -113 -24 -67 -15 -315 -60 -391 -72 -24 -4 -62 -10 -84 -14 -52 -10 -236 -33
-245 -31 -14 3 25 18 115 45 164 49 282 91 490 173 160 63 238 98 416 183 31 15
73 35 93 45 168 79 470 256 681 398 361 243 556 410 872 745 370 392 569 586 811
794 96 82 247 200 349 274 154 112 324 220 486 308 97 53 271 133 347 160 36 13
31 15 98 -39 67 -55 115 -85 214 -132 36 -17 125 -51 135 -51 14 0 12 8 -3 17
-85 50 -155 106 -231 184 -78 80 -123 140 -167 230 -67 131 -93 230 -104 379 -10
146 6 266 54 414 47 145 132 289 232 396 80 84 173 154 279 208 60 31 97 45 164
64 90 25 155 34 256 34 89 0 130 -5 210 -22 159 -36 312 -120 437 -242 73 -71
127 -143 178 -242 24 -45 36 -73 65 -154 9 -25 18 -35 27 -28 4 2 29 20 56 40 28
20 52 40 55 45 5 11 -14 67 -52 154 -62 139 -127 230 -246 343 -51 49 -83 74
-130 105 -131 85 -243 133 -370 159 -57 12 -167 24 -215 24 -43 0 -151 -9 -193
-16 -197 -33 -383 -125 -554 -272 -110 -94 -212 -233 -282 -384 -36 -76 -52 -123
-78 -226 -28 -109 -38 -196 -35 -317 2 -72 1 -86 -4 -91 -3 -3 -6 -6 -6 -6 -1 0
-28 -10 -97 -35 -24 -9 -67 -24 -96 -35 -126 -45 -218 -78 -238 -86 -12 -5 -27
-10 -32 -12 -6 -2 -36 -13 -66 -25 -78 -30 -130 -50 -214 -81 -55 -20 -147 -55
-232 -88 -69 -26 -139 -54 -188 -72 -71 -27 -114 -43 -168 -64 -30 -12 -76 -30
-102 -40 -80 -31 -85 -33 -194 -76 -58 -23 -124 -48 -146 -56 -22 -8 -73 -28
-114 -44 -41 -16 -91 -36 -112 -44 -21 -8 -50 -19 -64 -25 -14 -5 -38 -14 -52
-20 -14 -5 -41 -16 -60 -23 -19 -8 -51 -20 -72 -28 -21 -8 -49 -19 -62 -24 -13
-5 -46 -18 -74 -28 -27 -10 -65 -25 -84 -32 -19 -7 -47 -18 -62 -24 -15 -6 -36
-14 -46 -18 -10 -4 -31 -12 -46 -18 -15 -6 -34 -13 -42 -16 -8 -3 -25 -9 -38 -14
-13 -5 -30 -11 -38 -14 -8 -3 -61 -23 -118 -44 -57 -22 -113 -43 -124 -47 -11 -4
-69 -25 -128 -47 -59 -22 -118 -43 -130 -48 -18 -7 -73 -27 -198 -72 -11 -4 -35
-13 -54 -20 -19 -7 -69 -25 -112 -41 -43 -15 -97 -34 -120 -43 -23 -8 -67 -24
-98 -35 -31 -11 -66 -24 -78 -29 -27 -10 -65 -24 -118 -42 -22 -7 -74 -25 -116
-40 -42 -15 -91 -32 -110 -38 -19 -6 -47 -16 -64 -22 -16 -6 -61 -21 -98 -34 -37
-13 -113 -39 -168 -58 -55 -19 -160 -54 -234 -79 -74 -24 -162 -53 -196 -65 -66
-23 -147 -50 -304 -101 -54 -18 -151 -50 -216 -71 -65 -21 -164 -53 -220 -71
-224 -70 -333 -103 -388 -119 -32 -9 -81 -24 -110 -33 -59 -18 -258 -73 -362
-101 -38 -10 -102 -27 -142 -38 -367 -99 -775 -177 -1038 -196 -98 -8 -291 -7
-360 1 -187 23 -313 72 -405 158 -49 46 -73 87 -87 147 -10 40 -10 52 0 100 43
209 194 467 432 740 139 159 315 325 508 480 88 71 120 95 250 191 56 41 254 173
304 203 16 10 46 29 67 41 41 27 244 142 307 176 172 90 263 134 484 231 46 20
129 52 202 78 88 31 137 49 145 54 5 2 18 22 29 45 31 59 73 139 100 186 24 41
27 50 20 55 -5 3 -108 -15 -168 -30 -25 -6 -56 -13 -68 -16 -100 -22 -195 -52
-362 -111 -217 -78 -354 -137 -565 -242 -216 -109 -445 -244 -633 -374 -169 -116
-247 -176 -402 -305 -78 -65 -78 -64 -205 -186 -201 -190 -371 -390 -508 -594
-62 -93 -121 -196 -178 -309 -19 -37 -23 -47 -60 -138 -39 -94 -61 -169 -81 -269
-30 -159 -29 -288 2 -424 23 -101 47 -163 97 -254 139 -249 392 -463 778 -657 66
-34 155 -75 203 -94 34 -13 55 -22 82 -33 35 -14 183 -68 218 -80 15 -5 43 -14
62 -19 19 -6 52 -16 74 -23 84 -26 175 -51 248 -68 104 -25 98 -24 220 -50 306
-65 634 -107 994 -126 100 -5 385 -11 458 -9 144 3 321 10 372 13 137 9 232 16
288 22 232 23 225 22 460 52 169 22 440 68 672 114 33 6 70 13 82 16 32 6 187 40
282 62 24 6 59 14 78 18 19 4 66 15 104 24 39 9 81 19 94 22 68 15 513 133 614
163 19 6 57 17 84 25 28 8 70 21 94 28 24 7 79 23 122 36 43 12 89 26 102 30 13
4 48 15 78 24 168 52 316 100 446 146 42 15 107 37 144 50 132 45 359 128 502
183 61 23 141 53 178 67 37 14 91 35 118 46 28 11 73 29 100 40 99 38 343 137
458 186 171 72 333 141 384 164 31 14 79 35 108 47 54 23 129 56 222 99 30 14 86
39 124 56 39 17 118 54 177 82 59 27 108 50 109 50 1 0 46 22 101 48 54 27 122
60 151 73 98 46 561 281 637 324 17 10 64 35 105 57 344 182 837 498 1136 730
178 137 220 171 330 264 68 57 138 116 204 170 27 22 62 51 78 64 17 14 53 42 82
64 29 22 77 59 108 82 120 91 338 241 431 297 14 9 62 38 106 65 104 64 270 158
363 207 40 21 92 49 117 62 25 14 77 41 116 60 39 20 116 58 171 86 55 27 114 56
130 63 17 7 65 31 109 52 43 22 97 48 120 58 73 33 280 130 304 143 15 8 24 15
25 21 3 12 -21 90 -44 140 -22 48 -70 133 -106 188 -43 65 -110 143 -189 220
-117 115 -171 158 -301 237 -91 55 -165 95 -220 120 -24 11 -54 24 -66 30 -71 32
-189 73 -324 112 -54 16 -219 52 -294 65 -109 18 -272 36 -380 42 -88 4 -259 8
-312 6z M12694 8423 c-3 -7 -6 -19 -8 -27 -5 -24 -26 -84 -47 -132 -43 -96 -79
-152 -141 -214 -80 -81 -161 -130 -292 -178 -26 -9 -40 -16 -40 -20 0 -4 12 -10
36 -17 161 -51 276 -136 366 -270 42 -63 97 -192 120 -281 9 -35 20 -34 28 2 10
46 53 165 78 212 44 86 102 158 170 214 70 57 129 89 222 120 52 17 55 23 20 33
-118 35 -241 107 -308 180 -78 84 -148 224 -177 359 -8 35 -17 42 -27 19z M5933
7589 c-7 -5 -39 -97 -53 -154 -7 -27 -15 -59 -18 -71 -16 -59 -50 -198 -58 -230
-4 -20 -16 -73 -26 -118 -19 -86 -24 -107 -40 -196 -29 -151 -30 -170 -18 -231
25 -131 110 -235 254 -311 83 -44 266 -98 352 -104 32 -3 32 -3 96 23 35 14 91
36 124 49 80 31 216 86 258 104 19 8 55 23 80 33 55 21 143 57 185 75 16 7 83 34
148 60 64 25 122 48 127 51 9 4 70 28 144 58 17 7 61 24 98 39 37 15 97 38 132
52 73 29 135 53 216 84 31 12 68 26 82 32 14 6 58 22 96 37 64 24 131 50 246 95
24 10 71 28 104 40 33 12 66 25 74 28 8 3 27 10 42 16 15 6 34 13 42 16 19 8 98
38 144 55 61 23 177 66 194 71 9 3 48 17 88 32 40 15 93 34 118 42 53 17 92 34
92 39 0 6 -35 2 -148 -15 -21 -3 -69 -10 -106 -14 -37 -5 -91 -12 -120 -16 -223
-33 -612 -69 -900 -84 -387 -19 -756 -12 -1049 20 -168 18 -373 60 -496 100 -165
54 -271 105 -368 177 -43 32 -106 90 -116 108 -7 11 -11 13 -20 8z"/>
<path fill="#4A6B5C" d="M11642 17427 c4 -332 10 -651 14 -799 7 -251 10 -586 5 -589 -2 -2 -16 2 -31 7
-43 15 -81 19 -148 17 -47 -1 -68 -4 -100 -12 -101 -26 -179 -72 -254 -147 -67
-67 -115 -151 -141 -248 -7 -28 -9 -45 -9 -92 0 -73 10 -119 38 -178 25 -50 48
-79 89 -110 124 -94 277 -104 429 -29 169 84 285 250 316 453 7 45 8 269 2 534
-2 92 -5 254 -6 359 -3 182 -3 191 4 194 4 1 11 0 17 -3 5 -3 26 -17 48 -30 117
-70 178 -120 234 -190 68 -85 98 -225 73 -346 -13 -62 -52 -158 -78 -190 -12 -16
-20 -29 -20 -36 0 -25 127 73 179 137 40 51 79 136 102 225 42 157 12 352 -73
488 -71 112 -130 174 -264 274 -104 78 -211 178 -269 252 -31 40 -97 143 -124
193 -15 26 -21 35 -28 35 -8 0 -8 -1 -5 -169z M9054 17478 c-44 -17 -84 -32 -90
-34 -5 -2 -34 -13 -64 -24 -30 -11 -90 -34 -134 -50 -92 -34 -137 -51 -188 -71
-32 -13 -121 -46 -204 -76 -42 -16 -95 -36 -165 -63 -35 -14 -69 -27 -77 -30 -78
-29 -158 -59 -196 -74 -16 -6 -62 -23 -102 -38 -40 -15 -82 -30 -94 -35 -12 -4
-28 -10 -36 -13 -8 -3 -45 -17 -82 -31 -98 -39 -154 -60 -201 -77 -62 -22 -59
-18 -58 -86 1 -32 3 -89 5 -128 2 -38 6 -140 8 -226 2 -86 6 -186 8 -222 2 -36 6
-125 8 -198 2 -73 7 -195 10 -272 3 -77 8 -199 10 -272 2 -73 6 -162 8 -198 6
-101 5 -198 -1 -203 -4 -3 -11 -2 -27 3 -29 10 -89 19 -136 23 -73 4 -157 -13
-231 -49 -132 -63 -232 -160 -302 -294 -54 -103 -78 -243 -61 -348 16 -92 49
-158 110 -220 45 -46 94 -75 164 -98 32 -11 38 -12 106 -12 66 0 75 1 112 11 95
25 152 54 220 111 117 99 204 229 239 360 28 104 32 152 27 330 -2 77 -6 208 -8
292 -2 84 -6 207 -8 274 -2 67 -7 208 -10 312 -3 105 -8 237 -10 294 -8 244 -9
300 -3 307 5 6 26 16 65 29 9 3 38 14 64 24 26 10 85 32 130 48 45 17 105 39 132
50 28 11 82 32 122 46 90 33 112 41 134 50 27 11 86 34 118 46 15 6 34 13 42 16
19 8 77 30 154 58 75 28 161 60 210 80 58 23 159 57 170 58 12 0 12 0 15 -34 1
-19 2 -71 3 -116 0 -78 3 -202 12 -494 2 -73 5 -212 6 -309 3 -204 7 -188 -38
-173 -87 27 -205 27 -295 -1 -167 -53 -300 -170 -379 -333 -64 -133 -74 -282 -26
-407 32 -82 102 -158 182 -197 61 -30 98 -39 171 -41 50 -2 64 -1 99 7 155 33
282 127 389 285 51 77 87 173 106 287 5 36 6 66 6 202 -1 88 -3 235 -5 326 -6
240 -15 700 -18 860 -1 76 -4 212 -6 302 -2 90 -5 234 -6 320 -2 93 -5 158 -7
161 -2 3 -7 5 -10 5 -4 0 -43 -14 -87 -30z M10590 16917 c-241 -7 -571 -39 -792
-76 -20 -3 -48 -7 -62 -9 -59 -9 -147 -29 -177 -41 -54 -21 -82 -53 -117 -131
-27 -61 -25 -67 22 -58 96 19 176 34 190 36 9 1 54 8 100 16 199 32 325 50 476
64 33 3 76 8 96 10 166 19 676 42 990 46 76 1 78 1 79 9 3 19 -49 74 -84 89 -39
17 -188 37 -327 44 -76 4 -295 4 -394 1z M12612 16699 c-3 -5 -3 -12 2 -32 3 -14
7 -38 8 -54 4 -35 9 -41 45 -49 12 -3 47 -11 77 -19 30 -7 76 -19 102 -26 105
-26 423 -127 520 -165 26 -10 57 -22 68 -26 56 -20 134 -52 186 -75 32 -14 74
-32 94 -40 20 -7 89 -40 153 -72 64 -32 118 -57 119 -56 5 4 -39 38 -122 93 -47
31 -100 65 -118 76 -18 11 -35 22 -38 24 -3 3 -22 13 -42 24 -20 11 -50 27 -68
37 -53 30 -201 103 -260 128 -48 21 -131 54 -236 95 -24 10 -209 70 -260 86 -22
6 -219 56 -224 56 0 0 -3 -2 -6 -5z M7107 16128 c-9 -5 -50 -24 -92 -43 -323
-143 -696 -369 -1031 -626 -221 -170 -375 -306 -558 -494 -111 -115 -112 -116
-93 -140 6 -6 22 -29 36 -51 39 -59 40 -60 50 -56 5 2 42 35 82 72 203 190 371
336 516 449 58 45 189 143 234 174 18 12 46 32 62 44 28 21 70 49 173 115 29 18
64 41 78 50 14 9 47 29 72 44 25 15 57 34 70 42 33 20 113 66 206 119 44 24 88
50 98 58 11 7 35 24 53 37 18 12 41 30 51 39 18 17 19 18 22 49 3 18 4 53 3 78
-1 52 -3 55 -32 40z M10520 15886 c-173 -8 -506 -53 -752 -102 -120 -24 -324 -75
-333 -83 -7 -6 -7 -14 -6 -92 0 -59 2 -88 5 -92 5 -6 11 -5 75 10 317 80 557 129
797 163 163 23 352 47 410 51 32 3 57 7 61 10 3 3 10 16 15 30 5 14 17 40 26 58
20 36 21 44 11 46 -8 2 -265 2 -309 1z M12215 15662 c-35 -7 -64 -14 -65 -15 -7
-7 3 -15 25 -19 13 -2 46 -10 75 -16 29 -6 64 -14 78 -16 32 -6 243 -58 314 -78
145 -41 396 -121 476 -151 112 -43 195 -71 205 -71 7 0 20 8 36 20 28 22 32 29
25 38 -7 9 -174 90 -254 124 -174 74 -276 111 -414 151 -86 25 -89 26 -205 37
-147 14 -213 13 -296 -4z M4890 15080 c-8 -13 -6 -751 6 -2098 2 -253 3 -483 1
-512 -4 -61 1 -57 -55 -38 -75 26 -179 34 -265 22 -80 -12 -190 -56 -270 -108
-89 -56 -190 -168 -241 -264 -60 -114 -78 -180 -81 -300 -1 -61 -1 -83 5 -112 12
-67 38 -129 81 -194 63 -95 171 -162 292 -182 214 -34 443 73 605 283 93 120 153
267 172 421 3 25 4 228 3 690 0 360 0 742 0 850 1 108 1 200 1 205 0 6 2 9 7 9 6
0 108 -66 205 -132 102 -70 218 -182 280 -270 132 -187 162 -383 91 -596 -31 -93
-101 -215 -158 -275 -24 -25 -28 -35 -15 -35 10 0 96 43 123 61 94 64 168 140
219 227 23 37 69 140 84 185 32 96 46 187 46 309 0 121 -11 186 -50 305 -33 100
-73 178 -148 287 -55 82 -102 140 -174 216 -117 125 -256 277 -304 332 -158 184
-324 442 -415 646 -29 63 -32 69 -38 71 -2 1 -6 -1 -7 -3z M6454 14216 c-4 -5
-30 -27 -56 -49 -162 -137 -326 -292 -326 -310 0 -5 11 -29 25 -54 14 -25 34 -64
44 -87 10 -23 21 -44 24 -46 5 -3 35 24 125 111 108 105 152 145 251 231 19 16
35 33 36 37 2 4 -3 14 -12 24 -23 28 -50 71 -68 111 -19 42 -28 49 -43 32z M4552
13379 c-42 -45 -92 -104 -122 -143 -16 -21 -44 -57 -62 -80 -51 -65 -155 -224
-237 -360 -26 -43 -73 -127 -160 -286 -82 -152 -244 -485 -311 -640 -26 -60 -68
-161 -76 -184 -4 -11 -11 -30 -16 -42 -8 -21 -21 -56 -61 -168 -10 -27 -34 -99
-53 -158 -19 -59 -36 -112 -38 -118 -2 -5 -6 -20 -10 -32 -3 -12 -14 -53 -24 -90
-27 -101 -59 -231 -78 -326 -15 -70 -48 -260 -60 -338 -50 -343 -62 -711 -32
-1028 10 -108 22 -209 30 -254 12 -65 39 -198 46 -232 4 -19 12 -56 18 -82 12
-55 16 -69 46 -175 11 -38 72 -221 86 -256 14 -37 36 -96 49 -127 5 -14 24 -57
40 -94 56 -126 184 -370 194 -370 7 0 -2 30 -33 102 -39 93 -68 173 -118 332 -10
33 -21 67 -24 76 -12 35 -34 119 -68 256 -25 104 -49 226 -74 376 -20 125 -23
156 -36 300 -11 120 -16 255 -16 400 0 314 25 562 88 879 7 33 15 73 18 90 20
104 73 321 115 461 32 110 105 326 145 430 5 13 15 39 22 58 7 19 18 46 25 60 12
26 12 26 13 138 0 97 2 118 9 160 27 144 62 233 136 348 44 67 121 150 207 222
36 30 36 30 118 174 78 136 124 212 202 336 117 185 143 228 156 267 5 14 5 21 0
55 -11 66 -16 86 -23 87 -5 1 -16 -8 -31 -24z M4548 11050 c-43 -28 -87 -50 -120
-59 -47 -14 -47 -14 -60 -34 -36 -51 -112 -312 -159 -541 -36 -173 -50 -258 -69
-422 -19 -166 -28 -402 -20 -546 11 -213 12 -221 40 -432 9 -70 54 -300 71 -366
50 -190 136 -430 215 -598 84 -179 112 -228 129 -228 10 0 29 16 29 24 0 3 -11
30 -24 61 -74 171 -142 387 -200 631 -15 62 -53 270 -64 348 -41 293 -46 648 -14
964 12 113 55 379 82 504 35 163 93 371 156 559 24 73 44 137 44 141 0 12 -11 10
-36 -6z"/>
</g> </g>
</svg> </svg>
+1 -1
View File
@@ -71,7 +71,7 @@
href="/" href="/"
class="flex items-center gap-2 font-semibold text-sm md:text-base whitespace-nowrap justify-self-start" class="flex items-center gap-2 font-semibold text-sm md:text-base whitespace-nowrap justify-self-start"
> >
<MinstrelMark size={20} class="shrink-0" /> <MinstrelMark size={28} class="shrink-0" />
{appName()} {appName()}
</a> </a>
+80
View File
@@ -0,0 +1,80 @@
import { readFileSync, readdirSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, expect, test } from 'vitest';
// A deployed instance has no outbound network, so every asset the app draws
// itself with has to come from our own origin. This regressed once already
// (the font stylesheet was linked straight from Google), and the failure is
// invisible in development — the dev machine HAS internet, so the fonts load
// fine and only a real deployment renders in fallback faces.
//
// The assertion is on the PROPERTY, not on a vendor: any absolute URL in a
// resource-loading attribute fails, whoever is hosting it. Naming
// `fonts.googleapis.com` would pass the day someone reached for a different
// CDN, which is the same bug.
// fileURLToPath rather than import.meta.dirname: the latter needs Node 20.11+
// and this repo's toolchain is still on 18, where it is silently undefined.
const WEB = join(dirname(fileURLToPath(import.meta.url)), '../..');
/** Strip comments before asserting an ABSENCE — otherwise prose describing
* the forbidden thing satisfies the check that forbids it. */
const stripComments = (html: string) => html.replace(/<!--[\s\S]*?-->/g, '');
/** href/src on anything that makes the browser fetch a subresource. */
const RESOURCE_URL = /<(?:link|script|img|source|iframe)\b[^>]*?\b(?:href|src)\s*=\s*["']([^"']+)["']/gi;
const externalUrlsIn = (html: string) =>
[...stripComments(html).matchAll(RESOURCE_URL)]
.map((m) => m[1])
.filter((u) => /^(?:https?:)?\/\//i.test(u));
describe('the app ships every asset it draws itself with', () => {
test('app.html loads no subresource from a third-party origin', () => {
const html = readFileSync(join(WEB, 'src/app.html'), 'utf8');
expect(externalUrlsIn(html)).toEqual([]);
});
test('app.html does not preconnect or dns-prefetch to any host', () => {
// preconnect carries no href-fetch of its own, so the check above misses
// it — but it is the fingerprint of a third-party asset about to be added.
const html = stripComments(readFileSync(join(WEB, 'src/app.html'), 'utf8'));
const hints = [...html.matchAll(/<link\b[^>]*\brel\s*=\s*["']([^"']+)["'][^>]*>/gi)]
.filter((m) => /\b(?:preconnect|dns-prefetch)\b/i.test(m[1]));
expect(hints.map((m) => m[0])).toEqual([]);
});
test('the vendored font stylesheet exists and points only at local files', () => {
const dir = join(WEB, 'static/fonts');
const css = readFileSync(join(dir, 'fonts.css'), 'utf8');
const urls = [...css.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/gi)].map((m) => m[1]);
expect(urls.length).toBeGreaterThan(0);
expect(urls.filter((u) => /^(?:https?:)?\/\//i.test(u))).toEqual([]);
// Every referenced file is actually present — a stylesheet pointing at a
// woff2 nobody committed fails exactly like a CDN link would, offline.
const present = new Set(readdirSync(dir));
const missing = urls
.map((u) => u.replace(/^\.\//, ''))
.filter((f) => !present.has(f));
expect(missing).toEqual([]);
});
test('every font family the tokens name is actually vendored', () => {
// Pins the families to the TOKENS rather than to a hardcoded list, so
// swapping a typeface in tokens.json fails here until it is vendored.
const tokens = JSON.parse(
readFileSync(join(WEB, 'src/lib/styles/tokens.json'), 'utf8')
) as { fonts: Record<string, string> };
const files = readdirSync(join(WEB, 'static/fonts'));
for (const family of Object.values(tokens.fonts)) {
const prefix = `${family.replace(/\s+/g, '')}-`;
expect(
files.some((f) => f.startsWith(prefix) && f.endsWith('.woff2')),
`no vendored woff2 for "${family}"`
).toBe(true);
}
});
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+215 -31
View File
@@ -1,35 +1,219 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="157 116 992 992"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="72 72 2384 1488">
<!-- Minstrel mark. The M flips with the viewer's scheme because a favicon <!-- Generated by tools/gen-brand-assets.py — do not edit by hand.
sits on browser chrome we don't control: parchment would vanish on a The REDUCED mark, because a tab renders this at 16px.
light tab strip, obsidian on a dark one. The note keeps the accent in The hat flips with the viewer's scheme because a favicon sits on
both — teal holds against either. --> browser chrome we don't control: parchment would vanish on a light
tab strip, obsidian on a dark one. The plume holds the accent in
both — teal reads against either (3.04:1 on obsidian, 5.3:1 on
the light ground). -->
<style> <style>
.m { fill: #E8E4D8; } svg { color: #E8E4D8; }
@media (prefers-color-scheme: light) { .m { fill: #14171A; } } @media (prefers-color-scheme: light) { svg { color: #14171A; } }
</style> </style>
<g transform="translate(0,1254) scale(0.1,-0.1)" fill-rule="evenodd"> <g transform="translate(0.000000,2560.000000) scale(0.250000,-0.250000)" fill-rule="evenodd">
<path class="m" d="M2252 9878 l3 -152 109 -22 c342 -72 492 -184 538 -405 16 -74 24 <path fill="currentColor" d="M3904 8952 c-8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -7 -6 -15 -8 -32 -8 -17 0
-4823 9 -5024 -20 -266 -73 -375 -236 -484 -116 -77 -332 -150 -543 -181 -114 -25 -2 -32 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -7 -6 -15 -8 -32 -8 -21 0
-18 -107 -6 -110 -165 -1 -76 2 -143 7 -148 9 -9 2607 -11 2623 -1 14 9 10 -24 -1 -40 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
280 -4 291 -7 5 -49 15 -93 22 -380 60 -638 193 -720 374 -63 136 -59 -12 -61 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -8 0 -16 -8 -16 -17 0 -9 -38 -47 -48 -47
2247 -3 1999 -2 2054 15 2015 116 -253 646 -1520 1183 -2825 71 -173 216 -524 -10 0 -80 -70 -80 -80 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -4 -11 -8
322 -780 206 -494 266 -640 370 -895 97 -237 68 -210 226 -210 l135 0 23 50 -16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -7 -15 -16
c13 27 95 212 182 410 134 307 747 1685 1015 2285 92 205 726 1599 910 2000 -24 -9 -9 -16 -20 -16 -24 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -7
65 140 126 274 137 297 22 50 52 71 74 52 12 -10 14 -266 14 -1864 l0 -1852 -15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4
-82 -7 c-347 -29 -716 -203 -973 -460 -710 -712 -343 -1645 650 -1649 453 -2 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0
892 184 1206 510 193 202 295 397 351 676 l23 112 0 2357 c0 1297 0 2358 1 -4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0 -9 -3 -18 -8 -24 -4 -5 -8 -12 -8 -16 0
2358 12 0 142 -49 179 -67 342 -173 607 -545 715 -1004 85 -361 66 -801 -51 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0
-1215 -43 -151 -40 -173 18 -174 53 0 284 377 396 650 243 590 303 1238 163 -9 -3 -18 -8 -24 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -4 -5 -8 -12 -8 -16 0
1754 -178 652 -643 1100 -1294 1248 -93 21 -122 22 -716 25 -707 4 -649 12 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0
-696 -90 -15 -34 -78 -172 -140 -307 -593 -1297 -1123 -2469 -1717 -3800 -210 -9 -3 -18 -8 -24 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -6 -7 -8 -15 -8 -32 0
-472 -193 -437 -204 -418 -11 19 -173 423 -630 1568 -214 536 -394 986 -400 -17 -2 -25 -8 -32 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -7 -8 -8 -15 -8 -64
1000 -6 14 -76 187 -156 385 -80 198 -182 452 -228 565 -46 113 -140 346 -209 0 -49 -1 -56 -8 -64 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -7 -8 -8 -15 -8
518 -205 507 -225 554 -244 568 -14 11 -209 13 -1055 14 l-1038 0 3 -152z"/> -64 0 -49 -1 -56 -8 -64 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -8 -9 -8 -15
<path fill="#4A6B5C" d="M8830 7450 l0 -2582 -32 6 c-517 106 -1064 -47 -1442 -405 -598 -566 -8 -104 0 -89 0 -95 -8 -104 -8 -9 -8 -15 -8 -496 0 -481 0 -487 8 -496 8 -9 8
-501 -1354 196 -1598 489 -170 1134 -19 1548 363 234 217 350 418 423 736 l22 -15 8 -160 0 -145 0 -151 8 -160 8 -9 8 -15 8 -128 0 -113 0 -119 8 -128 7 -8 8
95 3 2373 c2 1961 5 2372 16 2372 27 0 132 -41 205 -81 315 -169 569 -524 675 -15 8 -56 0 -47 0 -47 -12 -60 -12 -11 -14 -12 -44 -12 -25 0 -32 -1 -40 -8 -8
-944 50 -198 60 -285 60 -525 0 -288 -27 -487 -105 -756 -34 -120 -35 -130 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8
-11 -143 33 -18 64 11 154 144 227 334 402 795 469 1235 37 238 34 646 -4 843 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40
-149 761 -637 1271 -1353 1418 -98 20 -147 23 -466 27 l-358 4 0 -2582z"/> -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1
-40 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3
-24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2
-32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -7 -6 -15 -8 -32 -8 -17 0 -25
-2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -6 -5 -15 -8 -24 -8 -9 0 -18
-3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18
-3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32 -8 -17 0
-25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9
0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -18 -11 -32 -24 -14 -13 -28 -24 -32 -24 -4 0 -11 -4 -16 -8 -7 -6 -15 -8 -32
-8 -17 0 -25 -2 -32 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -13 -8 -17
-8 -4 0 -27 -20 -51 -44 -44 -44 -44 -44 -44 -92 0 -48 0 -48 36 -84 20 -20 39
-36 43 -36 4 0 12 -4 17 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 7 -6 15 -8 32 -8
17 0 25 -2 32 -8 8 -7 15 -8 40 -8 25 0 32 -1 40 -8 8 -7 15 -8 56 -8 41 0 48 -1
56 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 8 -7
15 -8 56 -8 41 0 48 -1 56 -8 8 -7 15 -8 56 -8 41 0 48 -1 56 -8 8 -7 15 -8 56
-8 41 0 48 -1 56 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8 -7 15 -8 64 -8 49 0 56
-1 64 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8
-7 15 -8 64 -8 49 0 56 -1 64 -8 9 -8 15 -8 144 -8 129 0 135 0 144 -8 8 -7 15
-8 72 -8 57 0 64 -1 72 -8 9 -8 15 -8 152 -8 137 0 143 0 152 -8 9 -8 15 -8 160
-8 145 0 151 0 160 -8 9 -8 15 -8 576 -8 561 0 567 0 576 8 9 8 15 8 176 8 161 0
167 0 176 8 9 8 15 8 168 8 153 0 159 0 168 8 9 7 15 8 88 8 73 0 79 1 88 8 9 8
15 8 168 8 153 0 159 0 168 8 9 7 15 8 88 8 73 0 79 1 88 8 9 7 15 8 88 8 73 0
79 1 88 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 9 7 15 8
88 8 73 0 79 1 88 8 8 7 15 8 80 8 65 0 72 1 80 8 9 7 15 8 88 8 73 0 79 1 88 8
8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0
32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 8 7 15 8 80 8 65 0 72 1 80 8 8 7 15 8
40 8 25 0 32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 8 7 15 8 40 8 25 0 32 1 40 8
8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0
32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 6 5 15 8
24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 8 7
15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1
40 8 8 7 15 8 40 8 25 0 32 1 40 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9
0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8
24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5
15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24
8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18
3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9
0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8
24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5
15 8 24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24
8 5 4 12 8 16 8 4 0 15 7 24 16 9 9 20 16 24 16 4 0 11 4 16 8 5 4 12 8 16 8 4 0
11 4 16 8 5 4 12 8 16 8 4 0 15 7 24 16 9 9 20 16 24 16 4 0 11 4 16 8 5 4 12 8
16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5
15 8 24 8 14 0 19 3 48 32 18 18 36 32 40 32 10 0 128 118 128 128 0 4 7 15 16
24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4 11 8 16 4 5 8 12 8 16
0 4 4 11 8 16 5 6 8 15 8 24 0 9 3 18 8 24 7 8 8 15 8 48 0 33 1 40 8 48 7 8 8
15 8 40 0 25 -1 32 -8 40 -7 8 -8 15 -8 40 0 25 -1 32 -8 40 -4 5 -8 12 -8 16 0
4 -3 11 -7 14 -4 4 -8 15 -9 25 -1 11 -5 19 -8 21 -18 8 -22 12 -24 21 -2 5 -10
17 -17 25 -8 8 -15 18 -15 22 0 8 -8 16 -16 16 -4 0 -18 11 -32 24 -14 13 -28 24
-32 24 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -9 8 -15 8 -368 8
-353 0 -359 0 -368 -8 -9 -8 -15 -8 -296 -8 -281 0 -287 0 -296 -8 -9 -8 -15 -8
-160 -8 -145 0 -151 0 -160 -8 -9 -8 -15 -8 -160 -8 -145 0 -151 0 -160 -8 -9 -8
-15 -8 -160 -8 -145 0 -151 0 -160 -8 -9 -8 -15 -8 -160 -8 -151 0 -151 0 -164
12 -6 7 -12 16 -12 20 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4
-29 37 -64 72 -36 36 -64 68 -64 72 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 11
-134 144 -144 144 -5 0 -33 25 -72 64 -37 37 -68 64 -72 64 -4 0 -18 11 -32 24
-14 13 -28 24 -32 24 -4 0 -35 27 -72 64 -38 38 -67 64 -72 64 -10 0 -48 38 -48
48 0 10 -54 64 -64 64 -10 0 -80 70 -80 80 0 10 -22 32 -32 32 -4 0 -17 9 -28 20
-11 11 -20 24 -20 28 0 4 -6 13 -12 20 -7 6 -16 12 -20 12 -10 0 -48 38 -48 48 0
4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -18 26 -40 48 -22 22 -40 44 -40 48 0 4
-18 26 -40 48 -22 22 -40 44 -40 48 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4
11 -8 16 -4 5 -8 12 -8 16 0 4 -14 22 -32 40 -18 18 -32 36 -32 40 0 4 -4 11 -8
16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4
11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4
5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -5 6 -8 15 -8
24 0 9 -3 18 -8 24 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11
-8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -11 18 -24 32 -13 14 -24 28 -24 32 0 4
-11 18 -24 32 -13 14 -24 28 -24 32 0 8 -8 16 -16 16 -10 0 -32 23 -32 32 0 4
-11 18 -24 32 -13 14 -24 28 -24 32 0 8 -8 16 -17 16 -3 0 -28 22 -55 48 -27 27
-52 48 -56 48 -4 0 -22 14 -40 32 -18 18 -36 32 -40 32 -4 0 -11 4 -16 8 -5 4
-12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16
8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -15
7 -24 16 -9 9 -20 16 -24 16 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8
-7 6 -15 8 -32 8 -17 0 -25 2 -32 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -7 6 -15
8 -32 8 -17 0 -25 2 -32 8 -8 7 -15 8 -48 8 -33 0 -40 1 -48 8 -9 8 -15 8 -104 8
-89 0 -95 0 -104 -8z m3115 -3165 c8 -8 6 -48 -3 -59 -4 -5 -8 -13 -8 -17 0 -4
-16 -23 -36 -43 -20 -20 -39 -36 -43 -36 -4 0 -12 -4 -17 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8
-9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -7 -6 -15 -8 -32 -8
-17 0 -25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -8 -7 -15 -8 -64 -8
-49 0 -56 -1 -64 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32
-8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8
-72 -8 -57 0 -64 -1 -72 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15
-8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -80 -8 -65 0 -72 -1 -80 -8 -8 -7
-15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8
-7 -15 -8 -80 -8 -65 0 -72 -1 -80 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8
-9 -7 -15 -8 -88 -8 -73 0 -79 -1 -88 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40
-8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -9 -7 -15 -8 -88 -8 -73 0 -79 -1
-88 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40
-1 -48 -8 -9 -7 -15 -8 -96 -8 -81 0 -87 -1 -96 -8 -8 -7 -15 -8 -48 -8 -33 0
-40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -40 -8 -25
0 -32 -1 -40 -8 -9 -7 -15 -8 -96 -8 -81 0 -87 -1 -96 -8 -8 -7 -15 -8 -48 -8
-33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48
-8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8
-48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15
-8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -72 -8 -57 0 -64 -1 -72 -8 -5 -4
-12 -8 -16 -8 -4 0 -9 -3 -12 -6 -3 -3 -8 -6 -12 -6 -8 0 -8 7 -8 193 0 201 0
203 16 203 4 0 15 7 24 16 17 16 17 16 64 16 41 0 48 1 56 8 6 5 15 8 24 8 9 0
18 3 24 8 7 6 15 8 32 8 17 0 25 2 32 8 7 6 15 8 32 8 17 0 25 2 32 8 6 5 15 8
24 8 9 0 18 3 24 8 7 6 15 8 32 8 17 0 25 2 32 8 7 6 15 8 32 8 17 0 25 2 32 8 8
7 15 8 64 8 49 0 56 1 64 8 8 7 15 8 40 8 25 0 32 1 40 8 7 6 15 8 32 8 17 0 25
2 32 8 8 7 15 8 64 8 49 0 56 1 64 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 64 8
49 0 56 1 64 8 8 7 15 8 72 8 57 0 64 1 72 8 8 7 15 8 80 8 65 0 72 1 80 8 7 6
15 8 32 8 17 0 25 2 32 8 8 7 15 8 72 8 57 0 64 1 72 8 9 8 15 8 112 8 97 0 103
0 112 8 8 7 15 8 80 8 65 0 72 1 80 8 9 8 15 8 104 8 89 0 95 0 104 8 8 7 15 8
80 8 65 0 72 1 80 8 9 8 15 8 104 8 89 0 95 0 104 8 9 8 15 8 144 8 129 0 135 0
144 8 9 8 15 8 136 8 121 0 127 0 136 8 9 8 15 8 128 8 113 0 119 0 128 8 9 8 15
8 152 8 137 0 143 0 152 8 9 8 15 8 120 8 80 0 112 -1 115 -5z"/>
<path fill="#4A6B5C" d="M9328 9816 c-5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -13 0 -19 -3 -40 -24 -21 -21 -27 -24 -40 -24
-12 0 -18 -3 -32 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16
-8 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -6 -5 -15 -8
-24 -8 -12 0 -18 -3 -32 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9
-20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16
-9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7
-24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0
-15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16
-4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -22 -14 -40 -32 -18 -18 -36 -32
-40 -32 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -26 -18 -48 -40 -22 -22
-44 -40 -48 -40 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -18 -11 -32 -24
-14 -13 -28 -24 -32 -24 -4 0 -26 -18 -48 -40 -22 -22 -44 -40 -48 -40 -4 0 -29
-21 -56 -48 -27 -26 -52 -48 -56 -48 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16
-4 0 -29 -22 -56 -48 -28 -27 -52 -48 -56 -48 -10 0 -48 -38 -48 -48 0 -10 -54
-64 -64 -64 -5 0 -36 -29 -80 -72 -43 -43 -75 -72 -80 -72 -10 0 -48 -38 -48 -48
0 -10 -54 -64 -64 -64 -10 0 -48 -38 -48 -48 0 -10 -54 -64 -64 -64 -10 0 -48
-38 -48 -48 0 -5 -32 -40 -80 -88 -47 -47 -80 -83 -80 -88 0 -4 -11 -18 -24 -32
-13 -14 -24 -28 -24 -32 0 -10 -54 -64 -64 -64 -10 0 -48 -38 -48 -48 0 -4 -11
-18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -24 -32 -56 -64 -32 -32 -56 -60 -56
-64 0 -4 -11 -18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -11 -18 -24 -32 -13 -14
-24 -28 -24 -32 0 -4 -11 -18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -4 -11 -8
-16 -4 -5 -8 -12 -8 -15 0 -4 -3 -10 -6 -14 -8 -8 -5 -19 5 -19 4 0 12 -4 17 -8
5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8
16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11
-4 16 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5
-4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16
-8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4
16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4
12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8
4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 6 -5 15 -8 24 -8 9 0 18 -3 24
-8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12
-8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0
11 -4 16 -8 5 -4 12 -8 16 -8 4 0 9 -3 12 -6 3 -3 8 -6 11 -6 9 0 25 19 25 28 0
10 54 64 64 64 4 0 18 11 32 24 14 13 28 24 32 24 10 0 48 38 48 48 0 10 54 64
64 64 11 0 416 405 416 416 0 10 54 64 64 64 10 0 48 38 48 48 0 5 29 37 72 80
42 42 72 75 72 80 0 4 11 18 24 32 13 14 24 28 24 32 0 4 18 26 40 48 22 22 40
44 40 48 0 4 21 29 48 56 26 27 48 52 48 56 0 4 7 15 16 24 9 9 16 20 16 24 0 4
11 18 24 32 13 14 24 28 24 32 0 4 18 26 40 48 22 22 40 44 40 48 0 4 7 15 16 24
9 9 16 20 16 24 0 4 11 18 24 32 13 14 24 28 24 32 0 4 7 15 16 24 9 9 16 20 16
24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4 11 8 16
4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7
15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16
20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4
11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16
0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 7 15 16 24 9 9 16
20 16 24 0 4 4 11 8 16 5 6 8 15 8 24 0 9 3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16
4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11
8 16 5 6 8 15 8 24 0 9 3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16 5 6 8 15 8 24 0 9
3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16 7 8 8 15 8 40 0 25 1 32 8 40 5 6 8 15 8
24 0 9 3 18 8 24 6 7 8 15 8 32 0 17 2 25 8 32 6 7 8 15 8 31 0 15 2 25 6 30 12
12 5 19 -21 19 -18 0 -26 -2 -33 -8z"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 47 KiB

+211
View File
@@ -0,0 +1,211 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="72 72 2384 1488">
<!-- Generated by tools/gen-brand-assets.py — do not edit by hand.
The reduced mark, literal colours. For use at or below ~24px,
where the full art collapses into a diagonal smear. -->
<g transform="translate(0.000000,2560.000000) scale(0.250000,-0.250000)" fill-rule="evenodd">
<path fill="#E8E4D8" d="M3904 8952 c-8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -7 -6 -15 -8 -32 -8 -17 0
-25 -2 -32 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -7 -6 -15 -8 -32 -8 -21 0
-24 -1 -40 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -8 0 -16 -8 -16 -17 0 -9 -38 -47 -48 -47
-10 0 -80 -70 -80 -80 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -4 -11 -8
-16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -7 -15 -16
-24 -9 -9 -16 -20 -16 -24 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -7
-15 -16 -24 -9 -9 -16 -20 -16 -24 0 -4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4
-4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -7 -15 -16 -24 -9 -9 -16 -20 -16 -24 0
-4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0 -9 -3 -18 -8 -24 -4 -5 -8 -12 -8 -16 0
-4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0
-9 -3 -18 -8 -24 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -4 -5 -8 -12 -8 -16 0
-4 -4 -11 -8 -16 -4 -5 -8 -12 -8 -16 0 -4 -4 -11 -8 -16 -5 -6 -8 -15 -8 -24 0
-9 -3 -18 -8 -24 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -6 -7 -8 -15 -8 -32 0
-17 -2 -25 -8 -32 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -7 -8 -8 -15 -8 -64
0 -49 -1 -56 -8 -64 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -7 -8 -8 -15 -8
-64 0 -49 -1 -56 -8 -64 -6 -7 -8 -15 -8 -32 0 -17 -2 -25 -8 -32 -8 -9 -8 -15
-8 -104 0 -89 0 -95 -8 -104 -8 -9 -8 -15 -8 -496 0 -481 0 -487 8 -496 8 -9 8
-15 8 -160 0 -145 0 -151 8 -160 8 -9 8 -15 8 -128 0 -113 0 -119 8 -128 7 -8 8
-15 8 -56 0 -47 0 -47 -12 -60 -12 -11 -14 -12 -44 -12 -25 0 -32 -1 -40 -8 -8
-7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8
-7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40
-8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1
-40 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3
-24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2
-32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -7 -6 -15 -8 -32 -8 -17 0 -25
-2 -32 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -6 -5 -15 -8 -24 -8 -9 0 -18
-3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18
-3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32 -8 -17 0
-25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0
-18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9
0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4
0 -18 -11 -32 -24 -14 -13 -28 -24 -32 -24 -4 0 -11 -4 -16 -8 -7 -6 -15 -8 -32
-8 -17 0 -25 -2 -32 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -13 -8 -17
-8 -4 0 -27 -20 -51 -44 -44 -44 -44 -44 -44 -92 0 -48 0 -48 36 -84 20 -20 39
-36 43 -36 4 0 12 -4 17 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 7 -6 15 -8 32 -8
17 0 25 -2 32 -8 8 -7 15 -8 40 -8 25 0 32 -1 40 -8 8 -7 15 -8 56 -8 41 0 48 -1
56 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 8 -7
15 -8 56 -8 41 0 48 -1 56 -8 8 -7 15 -8 56 -8 41 0 48 -1 56 -8 8 -7 15 -8 56
-8 41 0 48 -1 56 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8 -7 15 -8 64 -8 49 0 56
-1 64 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8 -7 15 -8 64 -8 49 0 56 -1 64 -8 8
-7 15 -8 64 -8 49 0 56 -1 64 -8 9 -8 15 -8 144 -8 129 0 135 0 144 -8 8 -7 15
-8 72 -8 57 0 64 -1 72 -8 9 -8 15 -8 152 -8 137 0 143 0 152 -8 9 -8 15 -8 160
-8 145 0 151 0 160 -8 9 -8 15 -8 576 -8 561 0 567 0 576 8 9 8 15 8 176 8 161 0
167 0 176 8 9 8 15 8 168 8 153 0 159 0 168 8 9 7 15 8 88 8 73 0 79 1 88 8 9 8
15 8 168 8 153 0 159 0 168 8 9 7 15 8 88 8 73 0 79 1 88 8 9 7 15 8 88 8 73 0
79 1 88 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 9 7 15 8
88 8 73 0 79 1 88 8 8 7 15 8 80 8 65 0 72 1 80 8 9 7 15 8 88 8 73 0 79 1 88 8
8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0
32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 8 7 15 8 80 8 65 0 72 1 80 8 8 7 15 8
40 8 25 0 32 1 40 8 8 7 15 8 48 8 33 0 40 1 48 8 8 7 15 8 40 8 25 0 32 1 40 8
8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0
32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 6 5 15 8
24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 8 7
15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 40 8 25 0 32 1
40 8 8 7 15 8 40 8 25 0 32 1 40 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9
0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8
24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5
15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24
8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18
3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9
0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5 15 8
24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 6 5
15 8 24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24
8 5 4 12 8 16 8 4 0 15 7 24 16 9 9 20 16 24 16 4 0 11 4 16 8 5 4 12 8 16 8 4 0
11 4 16 8 5 4 12 8 16 8 4 0 15 7 24 16 9 9 20 16 24 16 4 0 11 4 16 8 5 4 12 8
16 8 4 0 11 4 16 8 6 5 15 8 24 8 9 0 18 3 24 8 5 4 12 8 16 8 4 0 11 4 16 8 6 5
15 8 24 8 14 0 19 3 48 32 18 18 36 32 40 32 10 0 128 118 128 128 0 4 7 15 16
24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4 11 8 16 4 5 8 12 8 16
0 4 4 11 8 16 5 6 8 15 8 24 0 9 3 18 8 24 7 8 8 15 8 48 0 33 1 40 8 48 7 8 8
15 8 40 0 25 -1 32 -8 40 -7 8 -8 15 -8 40 0 25 -1 32 -8 40 -4 5 -8 12 -8 16 0
4 -3 11 -7 14 -4 4 -8 15 -9 25 -1 11 -5 19 -8 21 -18 8 -22 12 -24 21 -2 5 -10
17 -17 25 -8 8 -15 18 -15 22 0 8 -8 16 -16 16 -4 0 -18 11 -32 24 -14 13 -28 24
-32 24 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -9 8 -15 8 -368 8
-353 0 -359 0 -368 -8 -9 -8 -15 -8 -296 -8 -281 0 -287 0 -296 -8 -9 -8 -15 -8
-160 -8 -145 0 -151 0 -160 -8 -9 -8 -15 -8 -160 -8 -145 0 -151 0 -160 -8 -9 -8
-15 -8 -160 -8 -145 0 -151 0 -160 -8 -9 -8 -15 -8 -160 -8 -151 0 -151 0 -164
12 -6 7 -12 16 -12 20 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4
-29 37 -64 72 -36 36 -64 68 -64 72 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 11
-134 144 -144 144 -5 0 -33 25 -72 64 -37 37 -68 64 -72 64 -4 0 -18 11 -32 24
-14 13 -28 24 -32 24 -4 0 -35 27 -72 64 -38 38 -67 64 -72 64 -10 0 -48 38 -48
48 0 10 -54 64 -64 64 -10 0 -80 70 -80 80 0 10 -22 32 -32 32 -4 0 -17 9 -28 20
-11 11 -20 24 -20 28 0 4 -6 13 -12 20 -7 6 -16 12 -20 12 -10 0 -48 38 -48 48 0
4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -18 26 -40 48 -22 22 -40 44 -40 48 0 4
-18 26 -40 48 -22 22 -40 44 -40 48 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4
11 -8 16 -4 5 -8 12 -8 16 0 4 -14 22 -32 40 -18 18 -32 36 -32 40 0 4 -4 11 -8
16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9 -16 20 -16 24 0 4 -4
11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4
5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -5 6 -8 15 -8
24 0 9 -3 18 -8 24 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11
-8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -7 15 -16 24 -9 9
-16 20 -16 24 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -4 11 -8 16 -4 5 -8 12 -8
16 0 4 -4 11 -8 16 -4 5 -8 12 -8 16 0 4 -11 18 -24 32 -13 14 -24 28 -24 32 0 4
-11 18 -24 32 -13 14 -24 28 -24 32 0 8 -8 16 -16 16 -10 0 -32 23 -32 32 0 4
-11 18 -24 32 -13 14 -24 28 -24 32 0 8 -8 16 -17 16 -3 0 -28 22 -55 48 -27 27
-52 48 -56 48 -4 0 -22 14 -40 32 -18 18 -36 32 -40 32 -4 0 -11 4 -16 8 -5 4
-12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16
8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -15
7 -24 16 -9 9 -20 16 -24 16 -4 0 -11 4 -16 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8
-7 6 -15 8 -32 8 -17 0 -25 2 -32 8 -5 4 -12 8 -16 8 -4 0 -11 4 -16 8 -7 6 -15
8 -32 8 -17 0 -25 2 -32 8 -8 7 -15 8 -48 8 -33 0 -40 1 -48 8 -9 8 -15 8 -104 8
-89 0 -95 0 -104 -8z m3115 -3165 c8 -8 6 -48 -3 -59 -4 -5 -8 -13 -8 -17 0 -4
-16 -23 -36 -43 -20 -20 -39 -36 -43 -36 -4 0 -12 -4 -17 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8
-4 0 -11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8
-9 0 -18 -3 -24 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -7 -6 -15 -8 -32 -8
-17 0 -25 -2 -32 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -8 -7 -15 -8 -64 -8
-49 0 -56 -1 -64 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32
-8 -17 0 -25 -2 -32 -8 -7 -6 -15 -8 -32 -8 -17 0 -25 -2 -32 -8 -8 -7 -15 -8
-72 -8 -57 0 -64 -1 -72 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15
-8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -80 -8 -65 0 -72 -1 -80 -8 -8 -7
-15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8
-7 -15 -8 -80 -8 -65 0 -72 -1 -80 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8
-9 -7 -15 -8 -88 -8 -73 0 -79 -1 -88 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40
-8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -9 -7 -15 -8 -88 -8 -73 0 -79 -1
-88 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40
-1 -48 -8 -9 -7 -15 -8 -96 -8 -81 0 -87 -1 -96 -8 -8 -7 -15 -8 -48 -8 -33 0
-40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -40 -8 -25
0 -32 -1 -40 -8 -9 -7 -15 -8 -96 -8 -81 0 -87 -1 -96 -8 -8 -7 -15 -8 -48 -8
-33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48
-8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8
-48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15 -8 -48 -8 -33 0 -40 -1 -48 -8 -8 -7 -15
-8 -40 -8 -25 0 -32 -1 -40 -8 -8 -7 -15 -8 -72 -8 -57 0 -64 -1 -72 -8 -5 -4
-12 -8 -16 -8 -4 0 -9 -3 -12 -6 -3 -3 -8 -6 -12 -6 -8 0 -8 7 -8 193 0 201 0
203 16 203 4 0 15 7 24 16 17 16 17 16 64 16 41 0 48 1 56 8 6 5 15 8 24 8 9 0
18 3 24 8 7 6 15 8 32 8 17 0 25 2 32 8 7 6 15 8 32 8 17 0 25 2 32 8 6 5 15 8
24 8 9 0 18 3 24 8 7 6 15 8 32 8 17 0 25 2 32 8 7 6 15 8 32 8 17 0 25 2 32 8 8
7 15 8 64 8 49 0 56 1 64 8 8 7 15 8 40 8 25 0 32 1 40 8 7 6 15 8 32 8 17 0 25
2 32 8 8 7 15 8 64 8 49 0 56 1 64 8 8 7 15 8 40 8 25 0 32 1 40 8 8 7 15 8 64 8
49 0 56 1 64 8 8 7 15 8 72 8 57 0 64 1 72 8 8 7 15 8 80 8 65 0 72 1 80 8 7 6
15 8 32 8 17 0 25 2 32 8 8 7 15 8 72 8 57 0 64 1 72 8 9 8 15 8 112 8 97 0 103
0 112 8 8 7 15 8 80 8 65 0 72 1 80 8 9 8 15 8 104 8 89 0 95 0 104 8 8 7 15 8
80 8 65 0 72 1 80 8 9 8 15 8 104 8 89 0 95 0 104 8 9 8 15 8 144 8 129 0 135 0
144 8 9 8 15 8 136 8 121 0 127 0 136 8 9 8 15 8 128 8 113 0 119 0 128 8 9 8 15
8 152 8 137 0 143 0 152 8 9 8 15 8 120 8 80 0 112 -1 115 -5z"/>
<path fill="#4A6B5C" d="M9328 9816 c-5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -9 0 -18 -3 -24 -8 -5 -4 -12 -8 -16 -8 -4 0
-11 -4 -16 -8 -6 -5 -15 -8 -24 -8 -13 0 -19 -3 -40 -24 -21 -21 -27 -24 -40 -24
-12 0 -18 -3 -32 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16
-8 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -11 -4 -16 -8 -6 -5 -15 -8
-24 -8 -12 0 -18 -3 -32 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9
-20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16
-9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7
-24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0
-15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16
-4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -22 -14 -40 -32 -18 -18 -36 -32
-40 -32 -4 0 -11 -4 -16 -8 -5 -4 -12 -8 -16 -8 -4 0 -26 -18 -48 -40 -22 -22
-44 -40 -48 -40 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16 -4 0 -18 -11 -32 -24
-14 -13 -28 -24 -32 -24 -4 0 -26 -18 -48 -40 -22 -22 -44 -40 -48 -40 -4 0 -29
-21 -56 -48 -27 -26 -52 -48 -56 -48 -4 0 -15 -7 -24 -16 -9 -9 -20 -16 -24 -16
-4 0 -29 -22 -56 -48 -28 -27 -52 -48 -56 -48 -10 0 -48 -38 -48 -48 0 -10 -54
-64 -64 -64 -5 0 -36 -29 -80 -72 -43 -43 -75 -72 -80 -72 -10 0 -48 -38 -48 -48
0 -10 -54 -64 -64 -64 -10 0 -48 -38 -48 -48 0 -10 -54 -64 -64 -64 -10 0 -48
-38 -48 -48 0 -5 -32 -40 -80 -88 -47 -47 -80 -83 -80 -88 0 -4 -11 -18 -24 -32
-13 -14 -24 -28 -24 -32 0 -10 -54 -64 -64 -64 -10 0 -48 -38 -48 -48 0 -4 -11
-18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -24 -32 -56 -64 -32 -32 -56 -60 -56
-64 0 -4 -11 -18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -11 -18 -24 -32 -13 -14
-24 -28 -24 -32 0 -4 -11 -18 -24 -32 -13 -14 -24 -28 -24 -32 0 -4 -4 -11 -8
-16 -4 -5 -8 -12 -8 -15 0 -4 -3 -10 -6 -14 -8 -8 -5 -19 5 -19 4 0 12 -4 17 -8
5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8
16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11
-4 16 -8 6 -5 15 -8 24 -8 9 0 18 -3 24 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5
-4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16
-8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4
16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4
12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8
4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 6 -5 15 -8 24 -8 9 0 18 -3 24
-8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12
-8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0 11 -4 16 -8 5 -4 12 -8 16 -8 4 0
11 -4 16 -8 5 -4 12 -8 16 -8 4 0 9 -3 12 -6 3 -3 8 -6 11 -6 9 0 25 19 25 28 0
10 54 64 64 64 4 0 18 11 32 24 14 13 28 24 32 24 10 0 48 38 48 48 0 10 54 64
64 64 11 0 416 405 416 416 0 10 54 64 64 64 10 0 48 38 48 48 0 5 29 37 72 80
42 42 72 75 72 80 0 4 11 18 24 32 13 14 24 28 24 32 0 4 18 26 40 48 22 22 40
44 40 48 0 4 21 29 48 56 26 27 48 52 48 56 0 4 7 15 16 24 9 9 16 20 16 24 0 4
11 18 24 32 13 14 24 28 24 32 0 4 18 26 40 48 22 22 40 44 40 48 0 4 7 15 16 24
9 9 16 20 16 24 0 4 11 18 24 32 13 14 24 28 24 32 0 4 7 15 16 24 9 9 16 20 16
24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4 11 8 16
4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7
15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16
20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 7 15 16 24 9 9 16 20 16 24 0 4 4
11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16
0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 7 15 16 24 9 9 16
20 16 24 0 4 4 11 8 16 5 6 8 15 8 24 0 9 3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16
4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11 8 16 4 5 8 12 8 16 0 4 4 11
8 16 5 6 8 15 8 24 0 9 3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16 5 6 8 15 8 24 0 9
3 18 8 24 4 5 8 12 8 16 0 4 4 11 8 16 7 8 8 15 8 40 0 25 1 32 8 40 5 6 8 15 8
24 0 9 3 18 8 24 6 7 8 15 8 32 0 17 2 25 8 32 6 7 8 15 8 31 0 15 2 25 6 30 12
12 5 19 -21 19 -18 0 -26 -2 -33 -8z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

+272 -25
View File
@@ -1,27 +1,274 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="202 251 902 723"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="608 592 3928 3568">
<g transform="translate(0,1254) scale(0.1,-0.1)" fill-rule="evenodd"> <!-- Generated by tools/gen-brand-assets.py — do not edit by hand.
<path fill="currentColor" d="M2252 9878 l3 -152 109 -22 c342 -72 492 -184 538 -405 16 -74 24 Literal colours, for any consumer that cannot inline the SVG and
-4823 9 -5024 -20 -266 -73 -375 -236 -484 -116 -77 -332 -150 -543 -181 -114 therefore cannot supply a currentColor. -->
-18 -107 -6 -110 -165 -1 -76 2 -143 7 -148 9 -9 2607 -11 2623 -1 14 9 10 <g transform="translate(0.000000,5016.000000) scale(0.250000,-0.250000)" fill-rule="evenodd">
280 -4 291 -7 5 -49 15 -93 22 -380 60 -638 193 -720 374 -63 136 -59 -12 -61 <path fill="#E8E4D8" d="M17412 17089 c-134 -7 -375 -42 -530 -77 -90 -20 -238 -58 -296 -76 -13 -4 -60
2247 -3 1999 -2 2054 15 2015 116 -253 646 -1520 1183 -2825 71 -173 216 -524 -19 -104 -32 -44 -14 -90 -28 -102 -32 -60 -21 -156 -55 -206 -75 -31 -12 -72
322 -780 206 -494 266 -640 370 -895 97 -237 68 -210 226 -210 l135 0 23 50 -28 -92 -35 -20 -7 -56 -22 -80 -32 -24 -10 -50 -21 -58 -24 -17 -6 -79 -34 -133
c13 27 95 212 182 410 134 307 747 1685 1015 2285 92 205 726 1599 910 2000 -59 -21 -10 -59 -27 -84 -38 -42 -19 -167 -81 -221 -111 -13 -7 -40 -21 -59 -32
65 140 126 274 137 297 22 50 52 71 74 52 12 -10 14 -266 14 -1864 l0 -1852 -35 -18 -44 -23 -169 -97 -371 -217 -625 -419 -799 -637 -69 -87 -107 -141 -152
-82 -7 c-347 -29 -716 -203 -973 -460 -710 -712 -343 -1645 650 -1649 453 -2 -216 -70 -117 -115 -204 -163 -314 -11 -26 -23 -54 -26 -61 -12 -26 -42 -108 -64
892 184 1206 510 193 202 295 397 351 676 l23 112 0 2357 c0 1297 0 2358 1 -173 -48 -144 -92 -356 -106 -516 -3 -27 -6 -63 -8 -78 -2 -15 -4 -64 -4 -108 0
2358 12 0 142 -49 179 -67 342 -173 607 -545 715 -1004 85 -361 66 -801 -51 -77 -2 -94 -11 -89 -5 3 -16 39 -29 91 -54 222 -65 467 -32 690 12 78 31 167 48
-1215 -43 -151 -40 -173 18 -174 53 0 284 377 396 650 243 590 303 1238 163 220 40 125 60 175 113 286 25 53 28 61 18 63 -9 2 -145 -92 -227 -157 -25 -20
1754 -178 652 -643 1100 -1294 1248 -93 21 -122 22 -716 25 -707 4 -649 12 -56 -44 -68 -54 -39 -30 -124 -107 -197 -180 -112 -109 -216 -229 -317 -364 -107
-696 -90 -15 -34 -78 -172 -140 -307 -593 -1297 -1123 -2469 -1717 -3800 -210 -142 -234 -379 -307 -568 -69 -177 -120 -389 -135 -558 -2 -26 -6 -68 -8 -92 -7
-472 -193 -437 -204 -418 -11 19 -173 423 -630 1568 -214 536 -394 986 -400 -80 -4 -224 8 -360 8 -89 28 -204 68 -392 8 -35 32 -125 40 -146 2 -5 8 -27 14
1000 -6 14 -76 187 -156 385 -80 198 -182 452 -228 565 -46 113 -140 346 -209 -48 6 -21 20 -65 31 -97 10 -33 19 -62 19 -64 0 -8 -17 11 -34 37 -33 51 -118
518 -205 507 -225 554 -244 568 -14 11 -209 13 -1055 14 l-1038 0 3 -152z"/> 211 -158 298 -46 100 -102 256 -134 373 -56 211 -78 413 -67 635 4 78 17 180 30
<path fill="#4A6B5C" d="M8830 7450 l0 -2582 -32 6 c-517 106 -1064 -47 -1442 -405 -598 -566 241 12 51 -5 41 -49 -31 -16 -25 -33 -52 -38 -60 -65 -100 -169 -296 -217 -408
-501 -1354 196 -1598 489 -170 1134 -19 1548 363 234 217 350 418 423 736 l22 -42 -98 -90 -228 -118 -324 -37 -125 -87 -343 -103 -452 -22 -157 -30 -269 -30
95 3 2373 c2 1961 5 2372 16 2372 27 0 132 -41 205 -81 315 -169 569 -524 675 -446 0 -178 6 -273 26 -406 2 -14 8 -54 12 -88 8 -58 49 -272 63 -326 21 -87 73
-944 50 -198 60 -285 60 -525 0 -288 -27 -487 -105 -756 -34 -120 -35 -130 -265 94 -324 11 -31 29 -85 41 -120 12 -35 29 -83 38 -106 9 -23 24 -62 34 -86
-11 -143 33 -18 64 11 154 144 227 334 402 795 469 1235 37 238 34 646 -4 843 22 -56 49 -122 69 -168 9 -19 15 -37 14 -40 -3 -5 -26 22 -62 74 -17 25 -41 60
-149 761 -637 1271 -1353 1418 -98 20 -147 23 -466 27 l-358 4 0 -2582z"/> -52 76 -21 30 -118 181 -134 208 -4 8 -21 35 -36 61 -37 60 -133 247 -170 329
-54 123 -107 261 -131 345 -7 23 -10 29 -16 29 -9 0 -10 -3 -16 -68 -16 -187 -15
-298 4 -473 14 -124 47 -294 94 -485 25 -97 27 -106 83 -274 57 -173 84 -249 124
-348 16 -41 41 -103 55 -138 14 -35 45 -105 68 -156 24 -51 49 -106 57 -124 39
-87 106 -217 205 -398 25 -45 53 -98 63 -117 11 -19 21 -36 23 -37 7 -4 221 -3
225 1 2 3 6 20 7 40 2 19 6 44 8 55 2 11 5 31 6 44 1 13 6 49 10 80 4 31 12 83
16 116 18 132 52 331 112 658 4 23 13 73 20 110 19 101 46 237 66 330 10 45 22
99 26 120 20 91 35 161 48 214 15 62 33 136 40 162 4 14 21 79 37 144 17 65 35
134 40 154 6 20 18 63 27 96 36 134 103 348 152 486 14 39 32 90 40 114 9 24 23
65 32 90 9 25 24 66 32 90 8 24 24 65 34 90 10 25 28 72 40 104 26 70 43 112 119
292 9 21 20 48 25 60 5 12 20 46 33 76 157 352 355 737 536 1042 76 127 135 224
149 246 8 12 22 36 32 52 123 215 410 628 606 872 180 225 306 368 466 528 173
173 333 313 513 449 72 55 91 67 91 58 0 -6 -8 -14 -123 -135 -81 -86 -168 -183
-283 -316 -135 -156 -335 -426 -496 -668 -81 -123 -202 -319 -270 -441 -21 -36
-52 -91 -70 -123 -52 -91 -166 -313 -209 -408 -22 -47 -48 -101 -57 -120 -9 -19
-25 -54 -36 -78 -17 -39 -35 -79 -99 -224 -9 -20 -23 -54 -32 -76 -23 -55 -65
-155 -87 -208 -19 -46 -48 -118 -80 -202 -10 -27 -27 -70 -36 -94 -10 -24 -29
-76 -44 -116 -15 -40 -30 -81 -34 -92 -4 -11 -15 -42 -24 -68 -9 -26 -19 -54 -22
-62 -6 -15 -26 -74 -50 -144 -8 -25 -23 -69 -33 -98 -30 -86 -124 -379 -151 -472
-9 -31 -26 -87 -38 -124 -12 -37 -27 -89 -34 -114 -7 -25 -19 -66 -26 -90 -7 -24
-17 -59 -22 -78 -9 -34 -28 -101 -47 -164 -5 -19 -15 -54 -21 -78 -13 -51 -28
-105 -48 -180 -5 -18 -16 -59 -24 -92 -8 -33 -21 -81 -28 -106 -24 -87 -61 -229
-92 -358 -11 -44 -25 -98 -30 -120 -21 -80 -86 -345 -94 -380 -12 -52 -17 -75
-38 -158 -21 -87 -35 -147 -44 -186 -15 -67 -47 -211 -54 -244 -5 -20 -12 -50
-16 -68 -24 -105 -33 -145 -42 -188 -6 -26 -13 -60 -16 -74 -9 -41 -19 -101 -20
-116 0 -14 0 -14 58 -43 32 -16 61 -29 65 -29 4 0 23 24 52 67 55 80 95 133 200
257 108 129 221 243 347 350 20 18 47 41 60 51 12 11 45 37 74 59 29 22 65 50 80
61 34 26 135 97 182 128 19 12 58 38 88 57 30 19 63 41 74 48 11 7 39 25 62 39
77 49 110 70 192 127 45 31 89 61 96 66 22 15 121 95 164 132 57 50 156 149 208
209 57 64 151 193 193 263 31 51 38 68 34 73 -2 1 -43 -18 -92 -42 -49 -24 -100
-48 -113 -54 -79 -34 -112 -48 -148 -60 -86 -31 -283 -95 -338 -111 -32 -8 -91
-25 -132 -37 -78 -21 -317 -101 -398 -133 -26 -10 -64 -24 -84 -32 -20 -7 -48
-19 -64 -25 -26 -12 -46 -17 -46 -10 0 4 12 13 74 60 87 65 101 74 216 143 99 59
294 161 394 205 71 32 147 67 236 111 17 8 51 24 76 35 122 54 319 160 433 235
150 97 274 196 384 307 124 125 209 240 286 385 20 38 53 108 63 136 3 8 13 37
24 66 31 84 61 193 72 263 6 38 6 47 -2 47 -1 0 -17 -12 -34 -26 -39 -32 -127
-96 -175 -127 -110 -72 -280 -155 -429 -211 -42 -15 -181 -58 -260 -79 -162 -43
-418 -79 -644 -91 -105 -5 -130 -5 -130 2 0 7 16 12 67 24 176 40 443 130 669
225 93 39 268 127 356 178 62 36 198 123 240 155 263 195 443 402 602 694 52 97
104 228 139 354 15 53 36 152 51 246 20 123 31 317 18 322 -8 4 -15 -1 -29 -19
-26 -33 -92 -101 -137 -141 -107 -96 -288 -218 -431 -290 -49 -24 -149 -71 -169
-78 -8 -3 -27 -11 -44 -18 -16 -7 -34 -14 -40 -16 -5 -2 -23 -8 -38 -14 -67 -25
-143 -50 -206 -68 -10 -3 -46 -13 -80 -23 -63 -19 -115 -32 -186 -47 -22 -5 -55
-12 -74 -16 -19 -4 -47 -10 -62 -14 -15 -3 -44 -8 -64 -10 -20 -3 -57 -8 -82 -12
-63 -10 -80 -12 -80 -8 0 6 15 16 42 26 15 6 56 24 90 40 34 16 91 42 126 57 95
42 300 147 399 205 152 88 190 110 293 178 197 129 349 246 490 379 139 129 221
225 306 357 159 244 256 534 310 924 3 26 7 50 26 174 6 42 19 112 34 187 15 78
53 213 83 299 31 89 67 168 120 268 100 185 247 349 415 460 53 35 163 86 230
106 62 19 57 22 -74 46 -83 15 -107 18 -190 24 -69 5 -270 7 -340 3z M10962
13252 c-22 -4 -51 -10 -64 -14 -13 -4 -38 -11 -56 -17 -141 -42 -381 -164 -597
-303 -12 -8 -31 -21 -44 -29 -71 -45 -259 -181 -345 -250 -207 -164 -321 -263
-550 -480 -94 -88 -287 -283 -354 -358 -17 -18 -70 -76 -120 -130 -94 -102 -172
-190 -256 -287 -27 -32 -62 -72 -78 -90 -16 -18 -61 -70 -100 -116 -155 -182
-267 -303 -416 -450 -127 -125 -183 -178 -212 -202 -13 -11 -37 -32 -54 -46 -74
-65 -108 -89 -108 -76 0 4 31 51 70 104 14 19 37 52 52 74 15 22 54 76 87 119 53
72 67 95 55 95 -6 0 -79 -23 -140 -45 -239 -85 -448 -207 -599 -353 -44 -41 -107
-111 -146 -160 -57 -73 -143 -211 -203 -330 -41 -80 -115 -241 -150 -326 -8 -20
-20 -47 -26 -60 -26 -56 -106 -258 -163 -412 -34 -92 -132 -383 -161 -476 -72
-235 -91 -299 -115 -394 -42 -157 -53 -203 -67 -268 -6 -27 -12 -56 -14 -64 -6
-23 -16 -77 -16 -88 0 -12 23 -34 74 -67 71 -47 136 -74 276 -115 176 -51 387
-77 708 -86 153 -5 357 -1 476 8 26 2 76 6 110 8 139 9 375 34 504 54 21 3 57 9
80 12 82 11 129 18 197 30 38 7 78 14 90 16 11 2 36 6 54 10 18 3 57 11 87 16 63
12 227 46 290 61 78 17 195 45 246 58 28 6 60 14 72 17 25 6 134 36 382 104 37
10 76 21 86 24 41 11 281 85 380 116 250 79 681 228 878 302 54 20 195 73 344
128 48 18 107 40 131 48 24 9 47 18 51 21 5 3 21 26 37 51 16 25 53 78 81 118 87
122 116 167 147 231 54 109 73 181 73 281 0 90 -6 109 -96 298 -78 165 -170 375
-229 524 -37 93 -108 303 -133 394 -36 130 -85 346 -102 446 -23 143 -30 193 -34
242 -2 29 -6 77 -8 108 -8 91 -5 301 4 414 16 192 38 322 90 528 34 136 90 308
130 399 32 75 95 200 116 235 9 14 16 28 16 32 0 10 -27 41 -100 116 -105 107
-159 150 -247 193 -114 58 -250 78 -371 57z m-574 -653 c0 -3 -11 -19 -24 -35
-56 -66 -193 -249 -280 -372 -11 -16 -37 -53 -58 -82 -36 -52 -121 -175 -270
-396 -75 -111 -134 -197 -180 -262 -11 -16 -37 -53 -56 -82 -20 -29 -50 -72 -68
-96 -17 -24 -51 -71 -75 -104 -23 -33 -53 -73 -67 -90 -13 -16 -45 -57 -70 -90
-25 -33 -54 -71 -66 -84 -11 -14 -36 -44 -56 -68 -211 -257 -378 -416 -712 -676
-202 -158 -451 -314 -678 -427 -89 -44 -108 -51 -108 -43 0 6 60 71 172 186 36
37 101 106 144 152 43 46 93 99 110 118 35 36 141 155 190 212 17 19 46 53 66 75
106 116 265 304 348 409 22 28 50 62 62 76 23 26 112 138 204 254 45 57 121 152
238 294 56 69 157 186 190 222 14 15 39 42 54 60 81 93 266 279 418 420 102 95
305 256 444 353 125 86 128 88 128 76z M15278 9526 c-254 -10 -537 -42 -734 -84
-25 -6 -76 -16 -112 -24 -36 -8 -77 -17 -90 -20 -13 -3 -40 -9 -60 -14 -53 -12
-94 -23 -148 -39 -26 -7 -67 -19 -90 -26 -105 -29 -86 -17 -169 -106 -108 -115
-183 -200 -183 -207 0 -3 1 -6 3 -7 2 -1 60 12 129 30 238 60 436 99 672 133 378
53 762 62 1100 26 394 -43 669 -112 950 -237 86 -38 120 -56 199 -104 127 -76
190 -124 288 -220 56 -55 72 -75 59 -75 -3 0 -36 11 -75 24 -104 35 -245 74 -334
92 -333 70 -595 86 -899 56 -432 -43 -871 -187 -1332 -438 -230 -125 -518 -327
-752 -525 -121 -103 -206 -178 -288 -255 -42 -39 -93 -86 -112 -104 -177 -163
-532 -518 -810 -808 -46 -48 -129 -135 -184 -192 -55 -58 -148 -155 -207 -216
-100 -105 -332 -337 -427 -428 -81 -78 -251 -228 -320 -282 -287 -228 -476 -351
-804 -523 -150 -78 -582 -266 -840 -365 -243 -93 -485 -179 -676 -241 -74 -24
-251 -78 -274 -85 -11 -3 -36 -10 -56 -16 -106 -31 -150 -44 -280 -80 -47 -13
-154 -41 -238 -60 -23 -6 -56 -14 -74 -18 -49 -12 -99 -23 -152 -34 -26 -5 -77
-16 -113 -24 -67 -15 -315 -60 -391 -72 -24 -4 -62 -10 -84 -14 -52 -10 -236 -33
-245 -31 -14 3 25 18 115 45 164 49 282 91 490 173 160 63 238 98 416 183 31 15
73 35 93 45 168 79 470 256 681 398 361 243 556 410 872 745 370 392 569 586 811
794 96 82 247 200 349 274 154 112 324 220 486 308 97 53 271 133 347 160 36 13
31 15 98 -39 67 -55 115 -85 214 -132 36 -17 125 -51 135 -51 14 0 12 8 -3 17
-85 50 -155 106 -231 184 -78 80 -123 140 -167 230 -67 131 -93 230 -104 379 -10
146 6 266 54 414 47 145 132 289 232 396 80 84 173 154 279 208 60 31 97 45 164
64 90 25 155 34 256 34 89 0 130 -5 210 -22 159 -36 312 -120 437 -242 73 -71
127 -143 178 -242 24 -45 36 -73 65 -154 9 -25 18 -35 27 -28 4 2 29 20 56 40 28
20 52 40 55 45 5 11 -14 67 -52 154 -62 139 -127 230 -246 343 -51 49 -83 74
-130 105 -131 85 -243 133 -370 159 -57 12 -167 24 -215 24 -43 0 -151 -9 -193
-16 -197 -33 -383 -125 -554 -272 -110 -94 -212 -233 -282 -384 -36 -76 -52 -123
-78 -226 -28 -109 -38 -196 -35 -317 2 -72 1 -86 -4 -91 -3 -3 -6 -6 -6 -6 -1 0
-28 -10 -97 -35 -24 -9 -67 -24 -96 -35 -126 -45 -218 -78 -238 -86 -12 -5 -27
-10 -32 -12 -6 -2 -36 -13 -66 -25 -78 -30 -130 -50 -214 -81 -55 -20 -147 -55
-232 -88 -69 -26 -139 -54 -188 -72 -71 -27 -114 -43 -168 -64 -30 -12 -76 -30
-102 -40 -80 -31 -85 -33 -194 -76 -58 -23 -124 -48 -146 -56 -22 -8 -73 -28
-114 -44 -41 -16 -91 -36 -112 -44 -21 -8 -50 -19 -64 -25 -14 -5 -38 -14 -52
-20 -14 -5 -41 -16 -60 -23 -19 -8 -51 -20 -72 -28 -21 -8 -49 -19 -62 -24 -13
-5 -46 -18 -74 -28 -27 -10 -65 -25 -84 -32 -19 -7 -47 -18 -62 -24 -15 -6 -36
-14 -46 -18 -10 -4 -31 -12 -46 -18 -15 -6 -34 -13 -42 -16 -8 -3 -25 -9 -38 -14
-13 -5 -30 -11 -38 -14 -8 -3 -61 -23 -118 -44 -57 -22 -113 -43 -124 -47 -11 -4
-69 -25 -128 -47 -59 -22 -118 -43 -130 -48 -18 -7 -73 -27 -198 -72 -11 -4 -35
-13 -54 -20 -19 -7 -69 -25 -112 -41 -43 -15 -97 -34 -120 -43 -23 -8 -67 -24
-98 -35 -31 -11 -66 -24 -78 -29 -27 -10 -65 -24 -118 -42 -22 -7 -74 -25 -116
-40 -42 -15 -91 -32 -110 -38 -19 -6 -47 -16 -64 -22 -16 -6 -61 -21 -98 -34 -37
-13 -113 -39 -168 -58 -55 -19 -160 -54 -234 -79 -74 -24 -162 -53 -196 -65 -66
-23 -147 -50 -304 -101 -54 -18 -151 -50 -216 -71 -65 -21 -164 -53 -220 -71
-224 -70 -333 -103 -388 -119 -32 -9 -81 -24 -110 -33 -59 -18 -258 -73 -362
-101 -38 -10 -102 -27 -142 -38 -367 -99 -775 -177 -1038 -196 -98 -8 -291 -7
-360 1 -187 23 -313 72 -405 158 -49 46 -73 87 -87 147 -10 40 -10 52 0 100 43
209 194 467 432 740 139 159 315 325 508 480 88 71 120 95 250 191 56 41 254 173
304 203 16 10 46 29 67 41 41 27 244 142 307 176 172 90 263 134 484 231 46 20
129 52 202 78 88 31 137 49 145 54 5 2 18 22 29 45 31 59 73 139 100 186 24 41
27 50 20 55 -5 3 -108 -15 -168 -30 -25 -6 -56 -13 -68 -16 -100 -22 -195 -52
-362 -111 -217 -78 -354 -137 -565 -242 -216 -109 -445 -244 -633 -374 -169 -116
-247 -176 -402 -305 -78 -65 -78 -64 -205 -186 -201 -190 -371 -390 -508 -594
-62 -93 -121 -196 -178 -309 -19 -37 -23 -47 -60 -138 -39 -94 -61 -169 -81 -269
-30 -159 -29 -288 2 -424 23 -101 47 -163 97 -254 139 -249 392 -463 778 -657 66
-34 155 -75 203 -94 34 -13 55 -22 82 -33 35 -14 183 -68 218 -80 15 -5 43 -14
62 -19 19 -6 52 -16 74 -23 84 -26 175 -51 248 -68 104 -25 98 -24 220 -50 306
-65 634 -107 994 -126 100 -5 385 -11 458 -9 144 3 321 10 372 13 137 9 232 16
288 22 232 23 225 22 460 52 169 22 440 68 672 114 33 6 70 13 82 16 32 6 187 40
282 62 24 6 59 14 78 18 19 4 66 15 104 24 39 9 81 19 94 22 68 15 513 133 614
163 19 6 57 17 84 25 28 8 70 21 94 28 24 7 79 23 122 36 43 12 89 26 102 30 13
4 48 15 78 24 168 52 316 100 446 146 42 15 107 37 144 50 132 45 359 128 502
183 61 23 141 53 178 67 37 14 91 35 118 46 28 11 73 29 100 40 99 38 343 137
458 186 171 72 333 141 384 164 31 14 79 35 108 47 54 23 129 56 222 99 30 14 86
39 124 56 39 17 118 54 177 82 59 27 108 50 109 50 1 0 46 22 101 48 54 27 122
60 151 73 98 46 561 281 637 324 17 10 64 35 105 57 344 182 837 498 1136 730
178 137 220 171 330 264 68 57 138 116 204 170 27 22 62 51 78 64 17 14 53 42 82
64 29 22 77 59 108 82 120 91 338 241 431 297 14 9 62 38 106 65 104 64 270 158
363 207 40 21 92 49 117 62 25 14 77 41 116 60 39 20 116 58 171 86 55 27 114 56
130 63 17 7 65 31 109 52 43 22 97 48 120 58 73 33 280 130 304 143 15 8 24 15
25 21 3 12 -21 90 -44 140 -22 48 -70 133 -106 188 -43 65 -110 143 -189 220
-117 115 -171 158 -301 237 -91 55 -165 95 -220 120 -24 11 -54 24 -66 30 -71 32
-189 73 -324 112 -54 16 -219 52 -294 65 -109 18 -272 36 -380 42 -88 4 -259 8
-312 6z M12694 8423 c-3 -7 -6 -19 -8 -27 -5 -24 -26 -84 -47 -132 -43 -96 -79
-152 -141 -214 -80 -81 -161 -130 -292 -178 -26 -9 -40 -16 -40 -20 0 -4 12 -10
36 -17 161 -51 276 -136 366 -270 42 -63 97 -192 120 -281 9 -35 20 -34 28 2 10
46 53 165 78 212 44 86 102 158 170 214 70 57 129 89 222 120 52 17 55 23 20 33
-118 35 -241 107 -308 180 -78 84 -148 224 -177 359 -8 35 -17 42 -27 19z M5933
7589 c-7 -5 -39 -97 -53 -154 -7 -27 -15 -59 -18 -71 -16 -59 -50 -198 -58 -230
-4 -20 -16 -73 -26 -118 -19 -86 -24 -107 -40 -196 -29 -151 -30 -170 -18 -231
25 -131 110 -235 254 -311 83 -44 266 -98 352 -104 32 -3 32 -3 96 23 35 14 91
36 124 49 80 31 216 86 258 104 19 8 55 23 80 33 55 21 143 57 185 75 16 7 83 34
148 60 64 25 122 48 127 51 9 4 70 28 144 58 17 7 61 24 98 39 37 15 97 38 132
52 73 29 135 53 216 84 31 12 68 26 82 32 14 6 58 22 96 37 64 24 131 50 246 95
24 10 71 28 104 40 33 12 66 25 74 28 8 3 27 10 42 16 15 6 34 13 42 16 19 8 98
38 144 55 61 23 177 66 194 71 9 3 48 17 88 32 40 15 93 34 118 42 53 17 92 34
92 39 0 6 -35 2 -148 -15 -21 -3 -69 -10 -106 -14 -37 -5 -91 -12 -120 -16 -223
-33 -612 -69 -900 -84 -387 -19 -756 -12 -1049 20 -168 18 -373 60 -496 100 -165
54 -271 105 -368 177 -43 32 -106 90 -116 108 -7 11 -11 13 -20 8z"/>
<path fill="#4A6B5C" d="M11642 17427 c4 -332 10 -651 14 -799 7 -251 10 -586 5 -589 -2 -2 -16 2 -31 7
-43 15 -81 19 -148 17 -47 -1 -68 -4 -100 -12 -101 -26 -179 -72 -254 -147 -67
-67 -115 -151 -141 -248 -7 -28 -9 -45 -9 -92 0 -73 10 -119 38 -178 25 -50 48
-79 89 -110 124 -94 277 -104 429 -29 169 84 285 250 316 453 7 45 8 269 2 534
-2 92 -5 254 -6 359 -3 182 -3 191 4 194 4 1 11 0 17 -3 5 -3 26 -17 48 -30 117
-70 178 -120 234 -190 68 -85 98 -225 73 -346 -13 -62 -52 -158 -78 -190 -12 -16
-20 -29 -20 -36 0 -25 127 73 179 137 40 51 79 136 102 225 42 157 12 352 -73
488 -71 112 -130 174 -264 274 -104 78 -211 178 -269 252 -31 40 -97 143 -124
193 -15 26 -21 35 -28 35 -8 0 -8 -1 -5 -169z M9054 17478 c-44 -17 -84 -32 -90
-34 -5 -2 -34 -13 -64 -24 -30 -11 -90 -34 -134 -50 -92 -34 -137 -51 -188 -71
-32 -13 -121 -46 -204 -76 -42 -16 -95 -36 -165 -63 -35 -14 -69 -27 -77 -30 -78
-29 -158 -59 -196 -74 -16 -6 -62 -23 -102 -38 -40 -15 -82 -30 -94 -35 -12 -4
-28 -10 -36 -13 -8 -3 -45 -17 -82 -31 -98 -39 -154 -60 -201 -77 -62 -22 -59
-18 -58 -86 1 -32 3 -89 5 -128 2 -38 6 -140 8 -226 2 -86 6 -186 8 -222 2 -36 6
-125 8 -198 2 -73 7 -195 10 -272 3 -77 8 -199 10 -272 2 -73 6 -162 8 -198 6
-101 5 -198 -1 -203 -4 -3 -11 -2 -27 3 -29 10 -89 19 -136 23 -73 4 -157 -13
-231 -49 -132 -63 -232 -160 -302 -294 -54 -103 -78 -243 -61 -348 16 -92 49
-158 110 -220 45 -46 94 -75 164 -98 32 -11 38 -12 106 -12 66 0 75 1 112 11 95
25 152 54 220 111 117 99 204 229 239 360 28 104 32 152 27 330 -2 77 -6 208 -8
292 -2 84 -6 207 -8 274 -2 67 -7 208 -10 312 -3 105 -8 237 -10 294 -8 244 -9
300 -3 307 5 6 26 16 65 29 9 3 38 14 64 24 26 10 85 32 130 48 45 17 105 39 132
50 28 11 82 32 122 46 90 33 112 41 134 50 27 11 86 34 118 46 15 6 34 13 42 16
19 8 77 30 154 58 75 28 161 60 210 80 58 23 159 57 170 58 12 0 12 0 15 -34 1
-19 2 -71 3 -116 0 -78 3 -202 12 -494 2 -73 5 -212 6 -309 3 -204 7 -188 -38
-173 -87 27 -205 27 -295 -1 -167 -53 -300 -170 -379 -333 -64 -133 -74 -282 -26
-407 32 -82 102 -158 182 -197 61 -30 98 -39 171 -41 50 -2 64 -1 99 7 155 33
282 127 389 285 51 77 87 173 106 287 5 36 6 66 6 202 -1 88 -3 235 -5 326 -6
240 -15 700 -18 860 -1 76 -4 212 -6 302 -2 90 -5 234 -6 320 -2 93 -5 158 -7
161 -2 3 -7 5 -10 5 -4 0 -43 -14 -87 -30z M10590 16917 c-241 -7 -571 -39 -792
-76 -20 -3 -48 -7 -62 -9 -59 -9 -147 -29 -177 -41 -54 -21 -82 -53 -117 -131
-27 -61 -25 -67 22 -58 96 19 176 34 190 36 9 1 54 8 100 16 199 32 325 50 476
64 33 3 76 8 96 10 166 19 676 42 990 46 76 1 78 1 79 9 3 19 -49 74 -84 89 -39
17 -188 37 -327 44 -76 4 -295 4 -394 1z M12612 16699 c-3 -5 -3 -12 2 -32 3 -14
7 -38 8 -54 4 -35 9 -41 45 -49 12 -3 47 -11 77 -19 30 -7 76 -19 102 -26 105
-26 423 -127 520 -165 26 -10 57 -22 68 -26 56 -20 134 -52 186 -75 32 -14 74
-32 94 -40 20 -7 89 -40 153 -72 64 -32 118 -57 119 -56 5 4 -39 38 -122 93 -47
31 -100 65 -118 76 -18 11 -35 22 -38 24 -3 3 -22 13 -42 24 -20 11 -50 27 -68
37 -53 30 -201 103 -260 128 -48 21 -131 54 -236 95 -24 10 -209 70 -260 86 -22
6 -219 56 -224 56 0 0 -3 -2 -6 -5z M7107 16128 c-9 -5 -50 -24 -92 -43 -323
-143 -696 -369 -1031 -626 -221 -170 -375 -306 -558 -494 -111 -115 -112 -116
-93 -140 6 -6 22 -29 36 -51 39 -59 40 -60 50 -56 5 2 42 35 82 72 203 190 371
336 516 449 58 45 189 143 234 174 18 12 46 32 62 44 28 21 70 49 173 115 29 18
64 41 78 50 14 9 47 29 72 44 25 15 57 34 70 42 33 20 113 66 206 119 44 24 88
50 98 58 11 7 35 24 53 37 18 12 41 30 51 39 18 17 19 18 22 49 3 18 4 53 3 78
-1 52 -3 55 -32 40z M10520 15886 c-173 -8 -506 -53 -752 -102 -120 -24 -324 -75
-333 -83 -7 -6 -7 -14 -6 -92 0 -59 2 -88 5 -92 5 -6 11 -5 75 10 317 80 557 129
797 163 163 23 352 47 410 51 32 3 57 7 61 10 3 3 10 16 15 30 5 14 17 40 26 58
20 36 21 44 11 46 -8 2 -265 2 -309 1z M12215 15662 c-35 -7 -64 -14 -65 -15 -7
-7 3 -15 25 -19 13 -2 46 -10 75 -16 29 -6 64 -14 78 -16 32 -6 243 -58 314 -78
145 -41 396 -121 476 -151 112 -43 195 -71 205 -71 7 0 20 8 36 20 28 22 32 29
25 38 -7 9 -174 90 -254 124 -174 74 -276 111 -414 151 -86 25 -89 26 -205 37
-147 14 -213 13 -296 -4z M4890 15080 c-8 -13 -6 -751 6 -2098 2 -253 3 -483 1
-512 -4 -61 1 -57 -55 -38 -75 26 -179 34 -265 22 -80 -12 -190 -56 -270 -108
-89 -56 -190 -168 -241 -264 -60 -114 -78 -180 -81 -300 -1 -61 -1 -83 5 -112 12
-67 38 -129 81 -194 63 -95 171 -162 292 -182 214 -34 443 73 605 283 93 120 153
267 172 421 3 25 4 228 3 690 0 360 0 742 0 850 1 108 1 200 1 205 0 6 2 9 7 9 6
0 108 -66 205 -132 102 -70 218 -182 280 -270 132 -187 162 -383 91 -596 -31 -93
-101 -215 -158 -275 -24 -25 -28 -35 -15 -35 10 0 96 43 123 61 94 64 168 140
219 227 23 37 69 140 84 185 32 96 46 187 46 309 0 121 -11 186 -50 305 -33 100
-73 178 -148 287 -55 82 -102 140 -174 216 -117 125 -256 277 -304 332 -158 184
-324 442 -415 646 -29 63 -32 69 -38 71 -2 1 -6 -1 -7 -3z M6454 14216 c-4 -5
-30 -27 -56 -49 -162 -137 -326 -292 -326 -310 0 -5 11 -29 25 -54 14 -25 34 -64
44 -87 10 -23 21 -44 24 -46 5 -3 35 24 125 111 108 105 152 145 251 231 19 16
35 33 36 37 2 4 -3 14 -12 24 -23 28 -50 71 -68 111 -19 42 -28 49 -43 32z M4552
13379 c-42 -45 -92 -104 -122 -143 -16 -21 -44 -57 -62 -80 -51 -65 -155 -224
-237 -360 -26 -43 -73 -127 -160 -286 -82 -152 -244 -485 -311 -640 -26 -60 -68
-161 -76 -184 -4 -11 -11 -30 -16 -42 -8 -21 -21 -56 -61 -168 -10 -27 -34 -99
-53 -158 -19 -59 -36 -112 -38 -118 -2 -5 -6 -20 -10 -32 -3 -12 -14 -53 -24 -90
-27 -101 -59 -231 -78 -326 -15 -70 -48 -260 -60 -338 -50 -343 -62 -711 -32
-1028 10 -108 22 -209 30 -254 12 -65 39 -198 46 -232 4 -19 12 -56 18 -82 12
-55 16 -69 46 -175 11 -38 72 -221 86 -256 14 -37 36 -96 49 -127 5 -14 24 -57
40 -94 56 -126 184 -370 194 -370 7 0 -2 30 -33 102 -39 93 -68 173 -118 332 -10
33 -21 67 -24 76 -12 35 -34 119 -68 256 -25 104 -49 226 -74 376 -20 125 -23
156 -36 300 -11 120 -16 255 -16 400 0 314 25 562 88 879 7 33 15 73 18 90 20
104 73 321 115 461 32 110 105 326 145 430 5 13 15 39 22 58 7 19 18 46 25 60 12
26 12 26 13 138 0 97 2 118 9 160 27 144 62 233 136 348 44 67 121 150 207 222
36 30 36 30 118 174 78 136 124 212 202 336 117 185 143 228 156 267 5 14 5 21 0
55 -11 66 -16 86 -23 87 -5 1 -16 -8 -31 -24z M4548 11050 c-43 -28 -87 -50 -120
-59 -47 -14 -47 -14 -60 -34 -36 -51 -112 -312 -159 -541 -36 -173 -50 -258 -69
-422 -19 -166 -28 -402 -20 -546 11 -213 12 -221 40 -432 9 -70 54 -300 71 -366
50 -190 136 -430 215 -598 84 -179 112 -228 129 -228 10 0 29 16 29 24 0 3 -11
30 -24 61 -74 171 -142 387 -200 631 -15 62 -53 270 -64 348 -41 293 -46 648 -14
964 12 113 55 379 82 504 35 163 93 371 156 559 24 73 44 137 44 141 0 12 -11 10
-36 -6z"/>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 908 B

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+296
View File
@@ -0,0 +1,296 @@
/* Generated by tools/vendor-fonts.py — do not edit by hand.
*
* Vendored from Google Fonts and served from our own origin: the app
* has to render with no outbound network, so a font provider link is
* not an option. Urls are relative so this keeps working under a base
* path. unicode-range is preserved, so the browser still fetches only
* the subsets a page needs.
*/
/* vietnamese */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Fraunces-400-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Fraunces-400-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Fraunces-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* vietnamese */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Fraunces-500-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Fraunces-500-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Fraunces';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Fraunces-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-greek.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Inter-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-greek-ext.woff2) format('woff2');
unicode-range: U+1F00-1FFF;
}
/* greek */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-greek.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'Inter';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./Inter-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-greek.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./JetBrainsMono-400-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-cyrillic-ext.woff2) format('woff2');
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-cyrillic.woff2) format('woff2');
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-greek.woff2) format('woff2');
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-vietnamese.woff2) format('woff2');
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-latin-ext.woff2) format('woff2');
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
font-family: 'JetBrains Mono';
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(./JetBrainsMono-500-latin.woff2) format('woff2');
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}