feat(icons): derive every icon from nous-girl SVGs + platform backgrounds

The icon pipeline now composes all 35 targets from two source axes instead
of a single hand-edited master:
- girl art: assets/nous-girl-black.svg / nous-girl-white.svg (brand kit)
- backgrounds: assets/backgrounds/ (squircle light/dark, logo frames,
  BrandMark tiles)

assets/icon-master*.svg are generated artifacts (squircle background + girl
nested in the 824px HIG content safe zone). New dark-appearance artifacts
(icon-dark.* containers, appx *-dark logos, logo-dark wordmark,
nous-logo-dark) land everywhere a surface consumes them: docusaurus navbar
srcDark, BrandMark dark tile (keyed off renderedMode).

nous-girl.jpg jpgs are replaced by lossless 8-bit palette PNGs saved with
compress_level=0 (stored deflate blocks, byte-identical across hosts for
the CI freshness lane).

Includes the temp linux-truth harvest step in icons-freshness-check.yml so
the committed compressed bytes can be refreshed from the ubuntu regen.
This commit is contained in:
ethernet committed 2026-09-06 21:12:44 -04:00
1 parent 0b30c2484d
commit 1fa5fea74f
51 files changed
+569 -75

No files matched your search

+27 -5
View File
@@ -1,10 +1,12 @@
name: Icon Assets Freshness
# Guard for the single-master icon pipeline. assets/icon-master.svg is the
# source of truth for all 23 generated icons (see scripts/generate_icons.py);
# this check regenerates every target and byte-compares against the committed
# files. The repo can never hold a hand-edited or stale generated icon, and a
# master change can never land without its regenerated outputs.
# Guard for the icon pipeline. The sources of truth are the nous-girl SVGs
# (assets/nous-girl-*.svg) and the per-platform backgrounds
# (assets/backgrounds/*.svg); assets/icon-master*.svg and every raster target
# are generated from those (see scripts/generate_icons.py). This check
# regenerates everything and byte-compares against the committed files. The
# repo can never hold a hand-edited or stale generated icon, and an art or
# background change can never land without its regenerated outputs.
#
# Byte-determinism note: resvg renders the same SVG to identical bytes on any
# host for pure-shape artwork. If the master ever contains <text>, font
@@ -43,3 +45,23 @@ jobs:
- name: Verify generated icons are in sync with the master
run: uv run python scripts/generate_icons.py --check
continue-on-error: true
# TEMP: upload linux-truth regeneration so the committed set can be
# refreshed from the host CI actually checks against.
- name: Regenerate + upload (temp artifact harvest)
if: always()
shell: bash
run: |
uv run python scripts/generate_icons.py
tar czf icons-linux.tgz \
apps/bootstrap-installer/public/ \
apps/bootstrap-installer/src-tauri/icons/ \
apps/desktop/assets/ \
apps/desktop/public/ \
web/public/favicon.ico website/static/img/
- uses: actions/upload-artifact@v4
if: always()
with:
name: icons-linux
path: icons-linux.tgz
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 27 KiB

@@ -2,12 +2,13 @@ import { cn } from '../lib/utils'
const assetPath = (path: string) => `${import.meta.env.BASE_URL}${path.replace(/^\/+/, '')}`
// Brand badge: nous-girl mark on a white tile, identical in light/dark.
// Brand badge: nous-girl mark on a white tile (the light BrandMark variant).
// Ported from apps/desktop's BrandMark; asset lives in this app's public/.
// The installer has no theme system, so it always uses the light asset.
export function BrandMark({ className, ...props }: React.ComponentProps<'span'>) {
return (
<span className={cn('inline-flex size-14 shrink-0 items-center justify-center bg-white', className)} {...props}>
<img alt="" className="size-full object-contain" src={assetPath('nous-girl.jpg')} />
<img alt="" className="size-full object-contain" src={assetPath('nous-girl.png')} />
</span>
)
}
+8 -3
View File
@@ -99,8 +99,9 @@ for call-site shadow or border inventions.
| `--chrome-action-hover` | hover fill for quiet controls |
| `--theme-primary`, `--ui-accent` | brand/accent |
Never hardcode `border-gray-*`, `bg-white`, `text-black`, etc. The white tile in
`BrandMark` is the one sanctioned literal (the mark needs a fixed backdrop).
Never hardcode `border-gray-*`, `bg-white`, `text-black`, etc. The two tiles in
`BrandMark` (white in light mode, `#0d1117` in dark) are the sanctioned literals
(the mark needs a fixed backdrop).
## Buttons — one component
@@ -253,7 +254,11 @@ Sizes: `default`, `xs`, `overlay` (titlebar glyph counts).
action. Do not introduce a third icon set or mix styles within one control
group.
- **`BrandMark`** (`src/components/brand-mark.tsx`) is the brand glyph — the
`nous-girl` mark on a white tile, softly rounded, identical in light/dark.
`nous-girl` mark on a fixed tile, theme-aware: black girl on white in light
mode, white girl on the `#0d1117` dark tile in dark mode (keyed off
`renderedMode` so bright surfaces in dark skins stay on the light mark).
The marks are generated by `scripts/generate_icons.py` from the girl SVGs
+ tile backgrounds (`assets/nous-girl-*.svg` × `assets/backgrounds/tile-*.svg`).
It replaced scattered Sparkles glyphs in updates / onboarding / about. Use it
for hero/brand moments; don't reintroduce decorative star/sparkle icons.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 856 B

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 B

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+8 -2
View File
@@ -1,19 +1,25 @@
import { cn } from '@/lib/utils'
import { useTheme } from '@/themes'
const assetPath = (path: string) => `${import.meta.env.BASE_URL}${path.replace(/^\/+/, '')}`
// Brand badge: nous-girl mark on a white tile, identical in light/dark.
// Brand badge: nous-girl mark on a fixed tile, theme-aware — light mode shows
// the black girl on white, dark mode the white girl on the nous dark surface.
// Fills the tile (softly rounded); size via className (default size-14).
export function BrandMark({ className, ...props }: React.ComponentProps<'span'>) {
const { renderedMode } = useTheme()
const dark = renderedMode === 'dark'
return (
<span
className={cn(
'inline-flex size-14 shrink-0 items-center justify-center overflow-hidden rounded-md bg-white',
dark && 'bg-[#0d1117]',
className
)}
{...props}
>
<img alt="" className="size-full object-contain" src={assetPath('nous-girl.jpg')} />
<img alt="" className="size-full object-contain" src={assetPath(dark ? 'nous-girl-dark.png' : 'nous-girl.png')} />
</span>
)
}
+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1772 1799">
<!-- website wordmark background, dark: white frame (L/R 14, T 17, B 16) + #0d1117 interior -->
<rect x="0" y="0" width="1772" height="1799" rx="2" fill="#ffffff"/>
<rect x="14" y="17" width="1743" height="1766" fill="#0d1117"/>
</svg>

After

Width:  |  Height:  |  Size: 307 B

+5
View File
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1772 1799">
<!-- website wordmark background, light: black frame (L/R 14, T 17, B 16) + white interior -->
<rect x="0" y="0" width="1772" height="1799" rx="2" fill="#000000"/>
<rect x="14" y="17" width="1743" height="1766" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- app-icon background, dark: #0d1117 rounded square (rx=245), transparent corners -->
<rect x="0" y="0" width="1024" height="1024" rx="245" fill="#0d1117"/>
</svg>

After

Width:  |  Height:  |  Size: 236 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- app-icon background, light: white rounded square (rx=245), transparent corners -->
<rect x="0" y="0" width="1024" height="1024" rx="245" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 235 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<!-- BrandMark tile, dark: solid #0d1117 -->
<rect x="0" y="0" width="256" height="256" fill="#0d1117"/>
</svg>

After

Width:  |  Height:  |  Size: 179 B

+4
View File
@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
<!-- BrandMark tile, light: solid white -->
<rect x="0" y="0" width="256" height="256" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 178 B

+10
View File
@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- app-icon background, dark: #0d1117 rounded square (rx=245), transparent corners -->
<rect x="0" y="0" width="1024" height="1024" rx="245" fill="#0d1117"/>
<svg x="100" y="100" width="824" height="824" viewBox="117.8860869140625 96.45225292968749 5026.234069335937 5390.609247070312">
<path
id="rect179"
style="display:inline;opacity:1;fill:#ffffff;stroke-width:50"
d="M 2727.8496 100.02344 C 2712.1357 100.02344 2634.4158 101.29576 2609.7832 104.69336 C 2577.9307 108.51567 2546.0792 112.33831 2526.543 114.03711 C 2491.7175 117.01001 2388.0908 133.57372 2360.0605 139.51953 L 2358.7852 139.51953 C 2353.6888 140.79363 2341.3738 143.34222 2332.4551 145.04102 C 2331.181 145.46572 2330.3299 145.46484 2329.4805 145.46484 C 2323.9593 146.73894 2315.8909 148.43879 2309.0957 149.71289 C 2303.5746 150.98699 2298.4786 151.83615 2294.6562 152.68555 C 2285.3128 154.80905 2270.8718 157.78167 2261.9531 159.48047 C 2216.5102 168.39918 2129.4468 189.21045 2107.7871 195.15625 C 2105.2389 196.00566 2102.691 196.85352 2100.9922 196.85352 C 2094.197 197.27822 2086.128 198.97739 2074.2363 202.375 C 2073.3869 202.375 2072.1131 203.22461 2071.2637 203.22461 C 2069.5649 203.64931 2064.043 205.34787 2060.2207 206.19727 C 2056.3984 207.04667 2052.5762 207.89581 2048.7539 209.16992 C 2025.8201 215.11572 2018.1763 217.2402 2004.5859 221.0625 C 2001.1883 221.91191 1997.3662 222.76106 1993.1191 224.03516 L 1992.2695 224.03516 C 1987.1731 225.30926 1982.076 227.00715 1977.4043 228.28125 C 1969.7597 230.40475 1965.5128 231.2552 1962.1152 232.5293 C 1939.606 238.89981 1918.797 245.69478 1891.1914 255.46289 C 1870.8058 262.2581 1858.4896 266.08029 1855.9414 266.92969 C 1854.2426 267.3544 1852.5426 267.77951 1850.8438 268.62891 C 1849.9943 268.62891 1847.8718 269.47764 1846.5977 269.90234 C 1842.7754 271.17644 1839.3779 272.45159 1835.1309 274.15039 C 1832.5827 274.99979 1826.6357 277.12347 1822.3887 278.82227 C 1818.9911 280.52108 1815.17 281.79426 1811.3477 283.06836 C 1790.5373 290.28827 1782.0417 293.68617 1776.9453 296.23438 C 1772.2736 297.93318 1760.3826 302.18011 1755.7109 303.87891 C 1752.3133 305.15301 1734.0515 311.52253 1725.1328 315.76953 C 1717.0635 319.16714 1685.6351 332.75773 1667.373 340.40234 C 1546.758 389.66759 1324.6407 503.48803 1257.1133 550.20508 C 1255.8392 551.05448 1251.591 553.60305 1248.1934 555.72656 C 1240.9735 560.39826 1230.7807 566.76925 1220.5879 573.56445 C 1191.2836 591.82657 1139.0463 624.95314 1112.7148 644.91406 C 1109.3172 647.46227 1100.8237 653.8315 1086.8086 663.59961 C 1067.2724 677.19002 976.38541 745.14305 955.15039 762.98047 C 890.59592 815.64332 847.70129 854.29111 792.49023 910.35156 C 755.11659 948.1499 701.17922 1007.6068 683.3418 1032.6641 C 682.4924 1033.5135 681.64432 1034.7884 680.79492 1036.0625 C 677.39731 1040.3095 671.87658 1047.9539 666.35547 1055.1738 C 665.93077 1055.1738 665.50586 1056.0234 665.50586 1056.0234 C 642.99674 1087.0265 600.52562 1151.5803 577.5918 1192.3516 C 539.36876 1258.6048 466.74537 1405.5521 451.88086 1450.5703 C 451.45616 1451.8444 450.60742 1453.1174 450.60742 1453.9668 C 445.08631 1462.4608 440.83855 1472.654 439.56445 1483.2715 C 438.29035 1486.6691 436.16626 1493.039 433.19336 1500.6836 C 432.76866 1501.533 432.34375 1501.9592 432.34375 1502.8086 C 425.54854 1521.9201 409.83584 1568.2114 397.51953 1609.832 C 392.84783 1622.5731 386.47727 1648.0547 381.38086 1668.0156 C 377.55856 1682.4554 373.31059 1701.1431 369.48828 1718.9805 C 364.81658 1741.4896 363.11806 1750.8335 362.69336 1756.7793 C 358.44636 1769.5204 356.32401 1786.0833 355.47461 1791.1797 C 354.62521 1799.6737 351.22768 1822.607 347.83008 1844.6914 C 342.73367 1882.4897 341.88281 1890.1351 341.88281 1990.7891 C 341.88281 2091.4431 342.3071 2108.0065 352.5 2166.6152 L 352.5 2167.0391 C 361.41871 2216.3043 372.88719 2257.0767 390.72461 2303.3691 L 390.72461 2304.6426 C 391.99871 2307.6155 393.27257 2310.5883 393.69727 2312.2871 C 395.82077 2319.9318 399.21696 2327.1507 403.88867 2336.4941 C 408.13567 2345.4128 413.65837 2357.305 418.33008 2366.6484 C 437.4416 2404.4467 448.05819 2422.2853 466.32031 2450.7402 C 483.30833 2477.0717 521.9561 2519.1169 549.98633 2545.873 C 565.37302 2561.6647 585.3918 2580.9301 594.95312 2587.8438 C 595.78656 2588.5837 596.9367 2589.3677 598.40234 2590.4668 C 602.64935 2593.8644 608.17139 2598.1104 614.11719 2602.3574 C 660.83424 2639.3064 751.29459 2690.696 805.65625 2705.5605 C 806.50565 2705.9852 807.78059 2706.834 809.05469 2706.834 C 813.7264 2708.5328 821.36972 2711.0816 827.74023 2713.2051 C 828.16493 2713.6298 829.01475 2713.6309 829.43945 2713.6309 C 878.28 2729.7695 899.09036 2734.0158 913.10547 2734.8652 C 910.98197 2756.9496 908.0098 2784.9806 904.1875 2821.0801 L 904.1875 2821.9297 C 903.3381 2831.6978 902.48699 2843.1638 901.21289 2853.7812 C 899.51409 2872.0433 897.8162 2889.4567 896.9668 2900.0742 C 896.9668 2904.3212 896.11719 2908.5685 896.11719 2913.2402 C 894.84309 2927.2553 893.5701 2943.3949 892.7207 2952.7383 L 891.87109 2959.5332 C 890.59699 2975.2471 888.04841 3001.1539 886.34961 3023.2383 C 885.92491 3027.4853 885.07617 3031.7306 885.07617 3036.4023 C 883.37737 3056.7879 880.82771 3081.8453 879.12891 3092.4629 L 879.12891 3095.0117 C 877.43011 3110.3009 875.73242 3131.1119 875.73242 3141.3047 C 875.73242 3145.127 874.03366 3160.4154 871.91016 3177.8281 L 871.91016 3179.1035 C 865.53965 3236.0134 8Line truncated
</svg>
</svg>

After

Width:  |  Height:  |  Size: 134 KiB

+8 -1
View File
@@ -1,4 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- app-icon background, light: white rounded square (rx=245), transparent corners -->
<rect x="0" y="0" width="1024" height="1024" rx="245" fill="#ffffff"/>
<circle cx="512" cy="512" r="420" fill="#e34c4c"/>
<svg x="100" y="100" width="824" height="824" viewBox="182.1875888671875 150.036837890625 4951.215650390624 5337.024662109375">
<path
id="path14"
style="opacity:1;fill:#000000"
d="M 2780.0999 2572.1925 C 2731.3999 2572.7925 2683.4019 2574.5933 2673.3019 2576.2933 C 2641.3019 2580.3933 2609.7995 2583.9939 2590.1995 2585.7939 C 2558.6995 2588.6939 2461.4006 2604.1934 2436.0006 2609.4934 C 2430.6006 2610.6934 2417.6007 2613.5942 2407.5007 2615.3942 C 2396.9007 2617.7942 2381.4 2620.6937 2373.1 2622.4937 C 2364.3 2624.2937 2350.0005 2627.1938 2341.1005 2628.9938 C 2297.8005 2637.7938 2215.4009 2657.4932 2195.8009 2662.7932 C 2189.3009 2664.5932 2181.499 2666.2947 2179.199 2666.2947 C 2176.899 2666.2947 2169.8014 2668.0943 2163.8014 2669.7943 C 2158.5014 2671.5943 2146.5999 2674.4938 2138.1999 2676.8938 C 2110.2999 2683.9938 2106.2018 2685.2929 2084.8018 2691.1929 C 2072.9018 2694.6929 2059.8019 2698.2929 2056.3019 2699.4929 C 2034.9019 2705.3929 2015.3015 2711.9934 1989.8015 2720.8934 C 1973.8015 2726.1934 1957.1997 2731.5932 1952.9997 2732.7932 C 1948.2997 2734.5932 1942.3011 2736.2934 1939.9011 2737.4934 C 1937.0011 2738.6934 1925.1005 2742.8921 1913.8005 2746.9921 C 1901.9005 2751.0921 1888.8006 2755.7914 1885.3006 2757.6914 C 1881.1006 2759.4914 1869.9003 2763.5918 1860.3003 2767.0918 C 1850.3003 2770.5918 1839.0002 2774.8924 1835.4002 2776.5924 C 1827.7002 2780.0924 1796.302 2793.7927 1777.302 2801.5927 C 1661.602 2848.9927 1452.2004 2956.3923 1391.6004 2998.4923 C 1387.4004 3001.3923 1370.9005 3011.4918 1354.8005 3022.1918 C 1312.0005 3048.8918 1272.9013 3074.3922 1253.9013 3088.6922 C 1249.2013 3092.1922 1237.2994 3100.5915 1227.1994 3107.6915 C 1207.5994 3121.3915 1120.4013 3186.4936 1103.8013 3200.7936 C 1042.1013 3251.1936 1002.2989 3287.3931 950.69897 3339.5931 C 911.59897 3379.3931 864.10036 3432.7915 851.00036 3451.1915 C 849.20036 3453.5915 841.50097 3464.2936 833.80097 3474.2936 C 813.60097 3501.5936 773.80044 3562.0926 752.50044 3599.9926 C 713.90044 3666.9926 647.50056 3803.4919 635.60056 3839.0919 C 632.70056 3847.8919 629.00156 3855.6932 627.80156 3856.2932 C 626.60156 3857.4932 625.99975 3859.7927 625.99975 3861.5927 C 625.99975 3863.3927 621.90105 3876.992 616.50105 3890.692 C 609.40105 3910.892 594.60095 3954.7915 583.30095 3992.6915 C 580.90095 3998.5915 572.69995 4030.0928 568.99995 4044.8928 C 561.79995 4073.3928 552.3999 4117.2925 552.3999 4122.0925 C 552.3999 4126.8925 551.20065 4130.4923 550.00065 4133.9923 C 548.80065 4136.8923 546.50045 4147.6925 545.30045 4157.0925 C 544.10045 4166.4925 540.59977 4190.2931 537.59977 4209.8931 C 532.89977 4242.4931 532.30212 4253.6937 532.30212 4342.7937 C 532.30212 4431.8937 532.89873 4451.2934 541.69873 4502.8934 C 549.49873 4546.1934 559.50006 4582.3942 575.50006 4623.8942 C 578.40006 4630.9942 580.80149 4638.1924 580.80149 4638.6924 C 580.80149 4639.8924 589.70013 4658.2925 600.40013 4680.1925 C 617.00013 4713.9925 625.99914 4728.8923 643.19914 4755.5923 C 655.09914 4773.9923 687.60031 4810.7922 715.60031 4837.4922 C 733.40031 4855.2922 750.00089 4870.6928 751.80089 4871.8928 C 753.60089 4873.0928 762.50077 4880.2942 772.00077 4887.2942 C 815.80077 4922.2942 897.69957 4966.7939 938.09957 4977.3939 C 941.59957 4978.5939 952.89964 4982.0941 962.99964 4985.7941 C 1000.3996 4998.2941 1026.501 5005.3935 1031.801 5002.9935 C 1034.201 5002.3935 1033.6004 5001.1932 1030.6004 5000.0932 C 1017.5004 4994.6932 987.80012 4972.1933 970.60012 4954.9933 C 960.60012 4943.6933 945.70125 4925.8918 938.00125 4914.6918 C 923.20125 4892.7918 892.90185 4838.7923 885.80185 4820.9923 C 883.40185 4815.0923 879.3008 4805.5921 877.0008 4799.6921 C 874.6008 4793.7921 871.70069 4786.5945 870.50069 4784.2945 C 867.60069 4778.3945 857.9995 4751.0939 851.4995 4731.4939 C 846.1995 4716.6939 845.59958 4707.0929 849.69958 4707.0929 C 851.49958 4706.4929 857.39972 4717.0931 864.49972 4730.1931 C 892.39972 4784.7931 936.9016 4841.7937 987.9016 4887.9937 C 1020.5016 4917.6937 1072.1013 4958.5931 1078.0013 4958.5931 C 1083.9013 4958.5931 1082.0991 4960.9939 1085.1991 4964.4939 L 1091.6992 4969.7934 L 1089.9011 4999.4939 C 1088.7011 5028.5939 1085.1995 5067.6918 1076.1995 5153.1918 C 1073.7995 5178.7918 1070.3 5212.5939 1069.1 5227.8939 C 1067.9 5243.2939 1066.2009 5266.9933 1064.9009 5280.0933 C 1063.7009 5293.0933 1060.8001 5323.3922 1059.0001 5347.6922 C 1056.6001 5371.9922 1053.7013 5403.9928 1051.3013 5418.8928 C 1049.5013 5433.6928 1048.401 5450.8931 1048.401 5456.7931 C 1048.401 5462.6931 1046.6021 5481.7921 1044.3021 5499.5921 C 1038.4021 5553.5921 1034.1997 5592.0928 1031.1997 5620.5928 C 1027.6997 5658.4928 1022.3018 5708.9931 1019.3018 5728.4931 C 1016.9018 5748.6931 1010.5013 5801.9917 1008.0013 5828.1917 C 1004.5013 5861.9917 998.50199 5911.893 997.30199 5915.393 C 996.10199 5917.193 994.90048 5927.8919 993.80048 5939.6919 C 989.70048 5974.6919 986.70083 5997.1924 982.00083 6031.0924 C 979.10083 6048.8924 976.10138 6070.7934 974.90138 6080.3934 C 973.70138 6090.4934 972.00176 6099.993 971.40176 6102.293 C 970.80176 6104.093 969.00025 6113.5928 967.90025 6122.4928 C 965.50025 6138.4928 963.20171 6150.994 954.80171 6194.894 C 952.40171 6206.Line truncated
transform="matrix(1.0330354,0,0,1.0330354,-145.41428,-2499.328)" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 113 KiB

+104
View File
@@ -0,0 +1,104 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
version="1.1"
viewBox="0 0 5487.0615 5487.0615"
sodipodi:docname="Nous Girl Without Frame.svg"
width="5487.0615"
height="5487.0615"
inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10">
<rect
style="fill:#000000"
id="rect11"
width="5600"
height="5600"
x="0"
y="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath12">
<rect
style="opacity:0.42;fill:#00ff00;stroke-width:0.993322"
id="rect13"
width="5525.4526"
height="5600"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:antialias-rendering="true"
inkscape:clip-to-page="false"
labelstyle="default"
showgrid="false"
showguides="true"
inkscape:zoom="0.0625"
inkscape:cx="5240"
inkscape:cy="4864"
inkscape:window-width="1635"
inkscape:window-height="1020"
inkscape:window-x="-6"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1">
<inkscape:grid
id="grid10"
units="px"
originx="-56.469222"
originy="-56.469315"
spacingx="1"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="false" />
<sodipodi:guide
position="189.61933,4064.4693"
orientation="1,0"
id="guide17"
inkscape:locked="false" />
<sodipodi:guide
position="5132.0393,3472.4693"
orientation="1,0"
id="guide18"
inkscape:locked="false" />
<sodipodi:guide
position="2726.527,5461.2663"
orientation="0,-1"
id="guide19"
inkscape:locked="false" />
<sodipodi:guide
position="4711.3214,112.93857"
orientation="0,-1"
id="guide20"
inkscape:locked="false" />
</sodipodi:namedview>
<!-- Generator: Adobe Illustrator 30.0.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 123) -->
<path
id="path14"
style="opacity:1;fill:#000000"
inkscape:label="nous girl"
d="M 2780.0999 2572.1925 C 2731.3999 2572.7925 2683.4019 2574.5933 2673.3019 2576.2933 C 2641.3019 2580.3933 2609.7995 2583.9939 2590.1995 2585.7939 C 2558.6995 2588.6939 2461.4006 2604.1934 2436.0006 2609.4934 C 2430.6006 2610.6934 2417.6007 2613.5942 2407.5007 2615.3942 C 2396.9007 2617.7942 2381.4 2620.6937 2373.1 2622.4937 C 2364.3 2624.2937 2350.0005 2627.1938 2341.1005 2628.9938 C 2297.8005 2637.7938 2215.4009 2657.4932 2195.8009 2662.7932 C 2189.3009 2664.5932 2181.499 2666.2947 2179.199 2666.2947 C 2176.899 2666.2947 2169.8014 2668.0943 2163.8014 2669.7943 C 2158.5014 2671.5943 2146.5999 2674.4938 2138.1999 2676.8938 C 2110.2999 2683.9938 2106.2018 2685.2929 2084.8018 2691.1929 C 2072.9018 2694.6929 2059.8019 2698.2929 2056.3019 2699.4929 C 2034.9019 2705.3929 2015.3015 2711.9934 1989.8015 2720.8934 C 1973.8015 2726.1934 1957.1997 2731.5932 1952.9997 2732.7932 C 1948.2997 2734.5932 1942.3011 2736.2934 1939.9011 2737.4934 C 1937.0011 2738.6934 1925.1005 2742.8921 1913.8005 2746.9921 C 1901.9005 2751.0921 1888.8006 2755.7914 1885.3006 2757.6914 C 1881.1006 2759.4914 1869.9003 2763.5918 1860.3003 2767.0918 C 1850.3003 2770.5918 1839.0002 2774.8924 1835.4002 2776.5924 C 1827.7002 2780.0924 1796.302 2793.7927 1777.302 2801.5927 C 1661.602 2848.9927 1452.2004 2956.3923 1391.6004 2998.4923 C 1387.4004 3001.3923 1370.9005 3011.4918 1354.8005 3022.1918 C 1312.0005 3048.8918 1272.9013 3074.3922 1253.9013 3088.6922 C 1249.2013 3092.1922 1237.2994 3100.5915 1227.1994 3107.6915 C 1207.5994 3121.3915 1120.4013 3186.4936 1103.8013 3200.7936 C 1042.1013 3251.1936 1002.2989 3287.3931 950.69897 3339.5931 C 911.59897 3379.3931 864.10036 3432.7915 851.00036 3451.1915 C 849.20036 3453.5915 841.50097 3464.2936 833.80097 3474.2936 C 813.60097 3501.5936 773.80044 3562.0926 752.50044 3599.9926 C 713.90044 3666.9926 647.50056 3803.4919 635.60056 3839.0919 C 632.70056 3847.8919 629.00156 3855.6932 627.80156 3856.2932 C 626.60156 3857.4932 625.99975 3859.7927 625.99975 3861.5927 C 625.99975 3863.3927 621.90105 3876.992 616.50105 3890.692 C 609.40105 3910.892 594.60095 3954.7915 583.30095 3992.6915 C 580.90095 3998.5915 572.69995 4030.0928 568.99995 4044.8928 C 561.79995 4073.3928 552.3999 4117.2925 552.3999 4122.0925 C 552.3999 4126.8925 551.20065 4130.4923 550.00065 4133.9923 C 548.80065 4136.8923 546.50045 4147.6925 545.30045 4157.0925 C 544.10045 4166.4925 540.59977 4190.2931 537.59977 4209.8931 C 532.89977 4242.4931 532.30212 4253.6937 532.30212 4342.7937 C 532.30212 4431.8937 532.89873 4451.2934 541.69873 4502.8934 C 549.49873 4546.1934 559.50006 4582.3942 575.50006 4623.8942 C 578.40006 4630.9942 580.80149 4638.1924 580.80149 4638.6924 C 580.80149 4639.8924 589.70013 4658.2925 600.40013 4680.1925 C 617.00013 4713.9925 625.99914 4728.8923 643.19914 4755.5923 C 655.09914 4773.9923 687.60031 4810.7922 715.60031 4837.4922 C 733.40031 4855.2922 750.00089 4870.6928 751.80089 4871.8928 C 753.60089 4873.0928 762.50077 4880.2942 772.00077 4887.2942 C 815.80077 4922.2942 897.69957 4966.7939 938.09957 4977.3939 C 941.59957 4978.5939 952.89964 4982.0941 962.99964 4985.7941 C 1000.3996 4998.2941 1026.501 5005.3935 1031.801 5002.9935 C 1034.201 5002.3935 1033.6004 5001.1932 1030.6004 5000.0932 C 1017.5004 4994.6932 987.80012 4972.1933 970.60012 4954.9933 C 960.60012 4943.6933 945.70125 4925.8918 938.00125 4914.6918 C 923.20125 4892.7918 892.90185 4838.7923 885.80185 4820.9923 C 883.40185 4815.0923 879.3008 4805.5921 877.0008 4799.6921 C 874.6008 4793.7921 871.70069 4786.5945 870.50069 4784.2945 C 867.60069 4778.3945 857.9995 4751.0939 851.4995 4731.4939 C 846.1995 4716.6939 845.59958 4707.0929 849.69958 4707.0929 C 851.49958 4706.4929 857.39972 4717.0931 864.49972 4730.1931 C 892.39972 4784.7931 936.9016 4841.7937 987.9016 4887.9937 C 1020.5016 4917.6937 1072.1013 4958.5931 1078.0013 4958.5931 C 1083.9013 4958.5931 1082.0991 4960.9939 1085.1991 4964.4939 L 1091.6992 4969.7934 L 1089.9011 4999.4939 C 1088.7011 5028.5939 1085.1995 5067.6918 1076.1995 5153.1918 C 1073.7995 5178.7918 1070.3 5212.5939 1069.1 5227.8939 C 1067.9 5243.2939 1066.2009 5266.9933 1064.9009 5280.0933 C 1063.7009 5293.0933 1060.8001 5323.3922 1059.0001 5347.6922 C 1056.6001 5371.9922 1053.7013 5403.9928 1051.3013 5418.8928 C 1049.5013 5433.6928 1048.401 5450.8931 1048.401 5456.7931 C 1048.401 5462.6931 1046.6021 5481.7921 1044.3021 5499.5921 C 1038.4021 5553.5921 1034.1997 5592.0928 1031.1997 5620.5928 C 1027.6997 5658.4928 1022.3018 5708.9931 1019.3018 5728.4931 C 1016.9018 5748.6931 1010.5013 5801.9917 1008.0013 5828.1917 C 1004.5013 5861.9917 998.50199 5911.893 997.30199 5915.393 C 996.10199 5917.193 994.90048 5927.8919 993.80048 5939.6919 C 989.70048 5974.6919 986.70083 5997.1924 982.00083 6031.0924 C 979.10083 6048.8924 976.10138 6070.7934 974.90138 6080.3934 C 973.70138 6090.4934 972.00176 6099.993 971.40176 6102.293 C 970.80176 6104.093 969.00025 6113.5928 967.90025 6122.4928 C 965.50025 6138.4928 963.20171 6150.994 954.80171 6194.894 C 952.40171 6206.Line truncated
transform="matrix(1.0330354,0,0,1.0330354,-145.41428,-2499.328)" />
</svg>

After

Width:  |  Height:  |  Size: 115 KiB

+113
View File
@@ -0,0 +1,113 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
id="Layer_1"
version="1.1"
viewBox="0 0 5487.0615 5558.8086"
sodipodi:docname="Nous Girl White.svg"
width="5487.0615"
height="5558.8086"
inkscape:version="1.4.4 (dcaf3e7, 2026-05-05)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs10">
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath10">
<rect
style="fill:#000000"
id="rect11"
width="5600"
height="5600"
x="0"
y="0" />
</clipPath>
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath12">
<rect
style="opacity:0.42;fill:#00ff00;stroke-width:0.993322"
id="rect13"
width="5525.4526"
height="5600"
x="0"
y="0" />
</clipPath>
</defs>
<sodipodi:namedview
id="namedview10"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="true"
inkscape:deskcolor="#d1d1d1"
inkscape:antialias-rendering="true"
inkscape:clip-to-page="false"
labelstyle="default"
showgrid="false"
showguides="true"
inkscape:zoom="0.125"
inkscape:cx="792"
inkscape:cy="3304"
inkscape:window-width="1635"
inkscape:window-height="1020"
inkscape:window-x="-6"
inkscape:window-y="-6"
inkscape:window-maximized="1"
inkscape:current-layer="Layer_1">
<inkscape:grid
id="grid10"
units="px"
originx="-56.469223"
originy="-56.469315"
spacingx="1"
spacingy="1"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="5"
enabled="true"
visible="false" />
<sodipodi:guide
position="189.61933,4064.4693"
orientation="1,0"
id="guide17"
inkscape:locked="false" />
<sodipodi:guide
position="5132.0393,3472.4693"
orientation="1,0"
id="guide18"
inkscape:locked="false" />
<sodipodi:guide
position="2726.527,5461.2663"
orientation="0,-1"
id="guide19"
inkscape:locked="false" />
<sodipodi:guide
position="4711.3214,112.93857"
orientation="0,-1"
id="guide20"
inkscape:locked="false" />
<sodipodi:guide
position="982.34964,2852.417"
orientation="1,0"
id="guide1"
inkscape:locked="false" />
<sodipodi:guide
position="2804.3855,-71.74701"
orientation="0,-1"
id="guide179"
inkscape:locked="false" />
</sodipodi:namedview>
<!-- Generator: Adobe Illustrator 30.0.0, SVG Export Plug-In . SVG Version: 2.1.1 Build 123) -->
<path
id="rect179"
style="display:inline;opacity:1;fill:#ffffff;stroke-width:50"
d="M 2727.8496 100.02344 C 2712.1357 100.02344 2634.4158 101.29576 2609.7832 104.69336 C 2577.9307 108.51567 2546.0792 112.33831 2526.543 114.03711 C 2491.7175 117.01001 2388.0908 133.57372 2360.0605 139.51953 L 2358.7852 139.51953 C 2353.6888 140.79363 2341.3738 143.34222 2332.4551 145.04102 C 2331.181 145.46572 2330.3299 145.46484 2329.4805 145.46484 C 2323.9593 146.73894 2315.8909 148.43879 2309.0957 149.71289 C 2303.5746 150.98699 2298.4786 151.83615 2294.6562 152.68555 C 2285.3128 154.80905 2270.8718 157.78167 2261.9531 159.48047 C 2216.5102 168.39918 2129.4468 189.21045 2107.7871 195.15625 C 2105.2389 196.00566 2102.691 196.85352 2100.9922 196.85352 C 2094.197 197.27822 2086.128 198.97739 2074.2363 202.375 C 2073.3869 202.375 2072.1131 203.22461 2071.2637 203.22461 C 2069.5649 203.64931 2064.043 205.34787 2060.2207 206.19727 C 2056.3984 207.04667 2052.5762 207.89581 2048.7539 209.16992 C 2025.8201 215.11572 2018.1763 217.2402 2004.5859 221.0625 C 2001.1883 221.91191 1997.3662 222.76106 1993.1191 224.03516 L 1992.2695 224.03516 C 1987.1731 225.30926 1982.076 227.00715 1977.4043 228.28125 C 1969.7597 230.40475 1965.5128 231.2552 1962.1152 232.5293 C 1939.606 238.89981 1918.797 245.69478 1891.1914 255.46289 C 1870.8058 262.2581 1858.4896 266.08029 1855.9414 266.92969 C 1854.2426 267.3544 1852.5426 267.77951 1850.8438 268.62891 C 1849.9943 268.62891 1847.8718 269.47764 1846.5977 269.90234 C 1842.7754 271.17644 1839.3779 272.45159 1835.1309 274.15039 C 1832.5827 274.99979 1826.6357 277.12347 1822.3887 278.82227 C 1818.9911 280.52108 1815.17 281.79426 1811.3477 283.06836 C 1790.5373 290.28827 1782.0417 293.68617 1776.9453 296.23438 C 1772.2736 297.93318 1760.3826 302.18011 1755.7109 303.87891 C 1752.3133 305.15301 1734.0515 311.52253 1725.1328 315.76953 C 1717.0635 319.16714 1685.6351 332.75773 1667.373 340.40234 C 1546.758 389.66759 1324.6407 503.48803 1257.1133 550.20508 C 1255.8392 551.05448 1251.591 553.60305 1248.1934 555.72656 C 1240.9735 560.39826 1230.7807 566.76925 1220.5879 573.56445 C 1191.2836 591.82657 1139.0463 624.95314 1112.7148 644.91406 C 1109.3172 647.46227 1100.8237 653.8315 1086.8086 663.59961 C 1067.2724 677.19002 976.38541 745.14305 955.15039 762.98047 C 890.59592 815.64332 847.70129 854.29111 792.49023 910.35156 C 755.11659 948.1499 701.17922 1007.6068 683.3418 1032.6641 C 682.4924 1033.5135 681.64432 1034.7884 680.79492 1036.0625 C 677.39731 1040.3095 671.87658 1047.9539 666.35547 1055.1738 C 665.93077 1055.1738 665.50586 1056.0234 665.50586 1056.0234 C 642.99674 1087.0265 600.52562 1151.5803 577.5918 1192.3516 C 539.36876 1258.6048 466.74537 1405.5521 451.88086 1450.5703 C 451.45616 1451.8444 450.60742 1453.1174 450.60742 1453.9668 C 445.08631 1462.4608 440.83855 1472.654 439.56445 1483.2715 C 438.29035 1486.6691 436.16626 1493.039 433.19336 1500.6836 C 432.76866 1501.533 432.34375 1501.9592 432.34375 1502.8086 C 425.54854 1521.9201 409.83584 1568.2114 397.51953 1609.832 C 392.84783 1622.5731 386.47727 1648.0547 381.38086 1668.0156 C 377.55856 1682.4554 373.31059 1701.1431 369.48828 1718.9805 C 364.81658 1741.4896 363.11806 1750.8335 362.69336 1756.7793 C 358.44636 1769.5204 356.32401 1786.0833 355.47461 1791.1797 C 354.62521 1799.6737 351.22768 1822.607 347.83008 1844.6914 C 342.73367 1882.4897 341.88281 1890.1351 341.88281 1990.7891 C 341.88281 2091.4431 342.3071 2108.0065 352.5 2166.6152 L 352.5 2167.0391 C 361.41871 2216.3043 372.88719 2257.0767 390.72461 2303.3691 L 390.72461 2304.6426 C 391.99871 2307.6155 393.27257 2310.5883 393.69727 2312.2871 C 395.82077 2319.9318 399.21696 2327.1507 403.88867 2336.4941 C 408.13567 2345.4128 413.65837 2357.305 418.33008 2366.6484 C 437.4416 2404.4467 448.05819 2422.2853 466.32031 2450.7402 C 483.30833 2477.0717 521.9561 2519.1169 549.98633 2545.873 C 565.37302 2561.6647 585.3918 2580.9301 594.95312 2587.8438 C 595.78656 2588.5837 596.9367 2589.3677 598.40234 2590.4668 C 602.64935 2593.8644 608.17139 2598.1104 614.11719 2602.3574 C 660.83424 2639.3064 751.29459 2690.696 805.65625 2705.5605 C 806.50565 2705.9852 807.78059 2706.834 809.05469 2706.834 C 813.7264 2708.5328 821.36972 2711.0816 827.74023 2713.2051 C 828.16493 2713.6298 829.01475 2713.6309 829.43945 2713.6309 C 878.28 2729.7695 899.09036 2734.0158 913.10547 2734.8652 C 910.98197 2756.9496 908.0098 2784.9806 904.1875 2821.0801 L 904.1875 2821.9297 C 903.3381 2831.6978 902.48699 2843.1638 901.21289 2853.7812 C 899.51409 2872.0433 897.8162 2889.4567 896.9668 2900.0742 C 896.9668 2904.3212 896.11719 2908.5685 896.11719 2913.2402 C 894.84309 2927.2553 893.5701 2943.3949 892.7207 2952.7383 L 891.87109 2959.5332 C 890.59699 2975.2471 888.04841 3001.1539 886.34961 3023.2383 C 885.92491 3027.4853 885.07617 3031.7306 885.07617 3036.4023 C 883.37737 3056.7879 880.82771 3081.8453 879.12891 3092.4629 L 879.12891 3095.0117 C 877.43011 3110.3009 875.73242 3131.1119 875.73242 3141.3047 C 875.73242 3145.127 874.03366 3160.4154 871.91016 3177.8281 L 871.91016 3179.1035 C 865.53965 3236.0134 8Line truncated
inkscape:label="nous girl" />
</svg>

After

Width:  |  Height:  |  Size: 136 KiB

+253 -61
View File
@@ -1,17 +1,33 @@
#!/usr/bin/env python3
"""Generate every app icon in the repo from a single master SVG.
"""Generate every app icon in the repo from the nous-girl art + platform backgrounds.
Usage (from repo root):
.venv/Scripts/python.exe scripts/generate_icons.py # write
.venv/Scripts/python.exe scripts/generate_icons.py --check # verify only
Master: assets/icon-master.svg
A 1024x1024 SVG. The background must be a WHITE ROUNDED SQUARE
(rx ~ 245, spanning the full canvas) with the artwork on top —
the transparent corners outside the squircle are what give the
icons their app-icon shape everywhere downstream. If the master
is missing, a red-circle placeholder is written automatically;
drop in the real artwork and re-run.
Sources of truth — two axes, composed per target:
Girl art (vector): assets/nous-girl-black.svg (black positive space)
assets/nous-girl-white.svg (white positive space)
straight from the Nous brand kit (Inkscape exports,
5487^2 viewBox, one path each).
Backgrounds (per platform surface, light/dark):
assets/backgrounds/squircle-light.svg white rounded
assets/backgrounds/squircle-dark.svg #0d1117 rounded
assets/backgrounds/logo-frame.svg black frame +
white interior (website)
assets/backgrounds/logo-frame-dark.svg white frame +
#0d1117 interior
assets/backgrounds/tile-light.svg solid white (BrandMark)
assets/backgrounds/tile-dark.svg solid #0d1117
The master SVGs (assets/icon-master.svg light, assets/icon-master-dark.svg
dark) are GENERATED artifacts — squircle background + girl nested into the
824px HIG content safe zone — not hand-edited sources. The 1024 master is
what every squircle target renders from.
If the girl SVGs are missing a red-circle placeholder master is written so a
partial checkout still generates; --check then flags the drift.
--check mode regenerates every target in memory and byte-compares it
against the committed file, exiting nonzero on any drift. This is what
@@ -25,36 +41,48 @@ Deps:
Pillow (core dependency), resvg-py (dev extra):
uv sync --extra dev
Outputs (23 files, all measured against the originals):
apps/desktop/assets/icon.png 1024x1024 squircle
apps/desktop/assets/icon.ico 16,24,32,48,64,128,256
apps/desktop/assets/icon.icns 16..1024 (real ICNS)
apps/desktop/assets/appx/Wide310x150Logo.png 310x150, squircle 100 centered
apps/desktop/assets/appx/StoreLogo.png 50x50 squircle
apps/desktop/assets/appx/Square44x44Logo.png 44x44 squircle
apps/desktop/assets/appx/Square150x150Logo.png 150x150 squircle
apps/desktop/public/apple-touch-icon.png 1024x1024 squircle
apps/desktop/public/nous-girl.jpg 256x256 JPEG on white
Outputs (35 files):
assets/icon-master.svg generated light master
assets/icon-master-dark.svg generated dark master
apps/desktop/assets/icon.png 1024x1024 squircle (light)
apps/desktop/assets/icon.ico 16,24,32,48,64,128,256
apps/desktop/assets/icon.icns 16..1024 (real ICNS)
apps/desktop/assets/icon-dark.png 1024x1024 squircle (dark)
apps/desktop/assets/icon-dark.ico 16,24,32,48,64,128,256
apps/desktop/assets/icon-dark.icns 16..1024 (real ICNS)
apps/desktop/assets/appx/Wide310x150Logo.png 310x150, squircle 100 centered
apps/desktop/assets/appx/StoreLogo.png 50x50 squircle
apps/desktop/assets/appx/Square44x44Logo.png 44x44 squircle
apps/desktop/assets/appx/Square150x150Logo.png 150x150 squircle
apps/desktop/assets/appx/*-dark.png dark-appearance logos (ready
to wire into the MSIX
manifest when app-builder-lib
supports contrast images)
apps/desktop/public/apple-touch-icon.png 1024x1024 squircle
apps/desktop/public/nous-girl.png 256x256 PNG, black girl on white (light)
apps/desktop/public/nous-girl-dark.png 256x256 PNG, white girl on #0d1117 (dark)
apps/bootstrap-installer/src-tauri/icons/32x32.png 32x32
apps/bootstrap-installer/src-tauri/icons/128x128.png 128x128
apps/bootstrap-installer/src-tauri/icons/128x128@2x.png 256x256
apps/bootstrap-installer/src-tauri/icons/icon.ico 16,32,64,128,256
apps/bootstrap-installer/src-tauri/icons/icon.icns 16..1024
apps/bootstrap-installer/public/nous-girl.jpg 256x256 JPEG on white
apps/bootstrap-installer/public/nous-girl.png 256x256 PNG, black girl on white (light)
website/static/img/logo.png 1772x1799 black-frame wordmark
website/static/img/logo-dark.png 1772x1799 white-frame wordmark
website/static/img/nous-logo.png 150x150 on white (opaque)
website/static/img/nous-logo-dark.png 150x150 on #0d1117 (opaque)
website/static/img/favicon-16x16.png 16x16
website/static/img/favicon-32x32.png 32x32
website/static/img/apple-touch-icon.png 180x180
website/static/img/favicon.ico 16,32,48
website/static/img/favicon.svg copy of the master
website/static/img/favicon.svg copy of the light master
web/public/favicon.ico 16,32,48
"""
from __future__ import annotations
import io
import shutil
import re
import sys
from pathlib import Path
@@ -69,10 +97,34 @@ except ImportError:
)
ROOT = Path(__file__).resolve().parent.parent
MASTER = ROOT / "assets" / "icon-master.svg"
ASSETS = ROOT / "assets"
BACKGROUNDS = ASSETS / "backgrounds"
GIRL_BLACK = ASSETS / "nous-girl-black.svg"
GIRL_WHITE = ASSETS / "nous-girl-white.svg"
GIRLS = {"black": GIRL_BLACK, "white": GIRL_WHITE}
MASTER = ASSETS / "icon-master.svg"
MASTER_DARK = ASSETS / "icon-master-dark.svg"
# The squircle corner radius measured on the real 1024 icon (~245px).
SQUIRCLE_RADIUS = 245
# The nous dark background (#0d1117) — fixed dark tile/background everywhere.
DARK_HEX = "#0d1117"
DARK_RGB = (13, 17, 23)
# Girl placement per background: (x, y, w, h) in that background's coordinate
# space. Squircle backgrounds put the girl in the 824px HIG content safe zone
# (centered, 100px pad on a 1024 canvas); logo frames fill the white/dark
# interior at ~1.7x; BrandMark tiles take the girl height-fitted (meet).
GIRL_BOXES = {
"squircle-light.svg": (100, 100, 824, 824),
"squircle-dark.svg": (100, 100, 824, 824),
"logo-frame.svg": (14, 17, 1743, 1766),
"logo-frame-dark.svg": (14, 17, 1743, 1766),
"tile-light.svg": (0, 0, 256, 256),
"tile-dark.svg": (0, 0, 256, 256),
}
# The brand-kit SVG canvas (both girl svgs share this viewBox).
GIRL_VIEWBOX = 5487.0615
PLACEHOLDER = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<rect x="0" y="0" width="1024" height="1024" rx="{SQUIRCLE_RADIUS}" fill="#ffffff"/>
@@ -82,23 +134,35 @@ PLACEHOLDER = f'''<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024
# (relpath, kind, arg)
TARGETS: list[tuple[str, str, object]] = [
("assets/icon-master.svg", "svg", None),
("assets/icon-master-dark.svg", "svg_dark", None),
("apps/desktop/assets/icon.png", "png", 1024),
("apps/desktop/assets/icon.ico", "ico", [16, 24, 32, 48, 64, 128, 256]),
("apps/desktop/assets/icon.icns", "icns", None),
("apps/desktop/assets/icon-dark.png", "png_dark", 1024),
("apps/desktop/assets/icon-dark.ico", "ico_dark", [16, 24, 32, 48, 64, 128, 256]),
("apps/desktop/assets/icon-dark.icns", "icns_dark", None),
("apps/desktop/assets/appx/Wide310x150Logo.png", "wide", (310, 150)),
("apps/desktop/assets/appx/StoreLogo.png", "png", 50),
("apps/desktop/assets/appx/Square44x44Logo.png", "png", 44),
("apps/desktop/assets/appx/Square150x150Logo.png", "png", 150),
("apps/desktop/assets/appx/Wide310x150Logo-dark.png", "wide_dark", (310, 150)),
("apps/desktop/assets/appx/StoreLogo-dark.png", "png_dark", 50),
("apps/desktop/assets/appx/Square44x44Logo-dark.png", "png_dark", 44),
("apps/desktop/assets/appx/Square150x150Logo-dark.png", "png_dark", 150),
("apps/desktop/public/apple-touch-icon.png", "png", 1024),
("apps/desktop/public/nous-girl.jpg", "jpg_white", 256),
("apps/desktop/public/nous-girl.png", "girl_light", 256),
("apps/desktop/public/nous-girl-dark.png", "girl_dark", 256),
("apps/bootstrap-installer/src-tauri/icons/32x32.png", "png", 32),
("apps/bootstrap-installer/src-tauri/icons/128x128.png", "png", 128),
("apps/bootstrap-installer/src-tauri/icons/128x128@2x.png", "png", 256),
("apps/bootstrap-installer/src-tauri/icons/icon.ico", "ico", [16, 32, 64, 128, 256]),
("apps/bootstrap-installer/src-tauri/icons/icon.icns", "icns", None),
("apps/bootstrap-installer/public/nous-girl.jpg", "jpg_white", 256),
("apps/bootstrap-installer/public/nous-girl.png", "girl_light", 256),
("website/static/img/logo.png", "logo", None),
("website/static/img/logo-dark.png", "logo_dark", None),
("website/static/img/nous-logo.png", "png_white", 150),
("website/static/img/nous-logo-dark.png", "png_dark_white", 150),
("website/static/img/favicon-16x16.png", "png", 16),
("website/static/img/favicon-32x32.png", "png", 32),
("website/static/img/apple-touch-icon.png", "png", 180),
@@ -107,23 +171,92 @@ TARGETS: list[tuple[str, str, object]] = [
("web/public/favicon.ico", "ico", [16, 32, 48]),
]
# ─── girl art extraction ────────────────────────────────────────────────────
def ensure_master(check: bool = False) -> Path:
if not MASTER.exists():
_path_cache: dict[str, str] = {}
_bbox_cache: dict[str, tuple[float, float, float, float]] = {}
def girl_path(girl: str) -> str:
"""The girl `<path>` element with editor metadata stripped (resvg rejects
undeclared inkscape/sodipodi prefixes)."""
if girl not in _path_cache:
src = GIRLS[girl].read_text(encoding="utf-8")
m = re.search(r"<path\b.*?/>", src, re.S)
assert m, f"no <path> found in {GIRLS[girl].name}"
path = re.sub(r'\s+(inkscape|sodipodi):[a-zA-Z-]+="[^"]*"', "", m.group(0))
_path_cache[girl] = path
return _path_cache[girl]
def girl_bbox(girl: str) -> tuple[float, float, float, float]:
"""Art bounding box in the girl SVG's coordinate space, measured by
rendering once and taking the alpha bbox (robust to art changes)."""
if girl not in _bbox_cache:
data = resvg_py.svg_to_bytes(svg_path=str(GIRLS[girl]), width=512, height=512)
im = Image.open(io.BytesIO(data))
bx, by, bx2, by2 = im.getchannel("A").point(lambda v: 255 if v > 0 else 0).getbbox()
s = GIRL_VIEWBOX / 512.0
_bbox_cache[girl] = (bx * s, by * s, (bx2 - bx) * s, (by2 - by) * s)
return _bbox_cache[girl]
def girl_layer(girl: str, box: tuple[float, float, float, float]) -> str:
"""Nested-svg layer: girl art (bbox as viewBox) placed into `box` — the
box's aspect is preserved via 'meet', so the girl never distorts."""
bx, by, bw, bh = girl_bbox(girl)
x, y, w, h = box
return (
f'<svg x="{x}" y="{y}" width="{w}" height="{h}" viewBox="{bx} {by} {bw} {bh}">\n'
f" {girl_path(girl)}\n"
" </svg>"
)
def background_inner(name: str) -> tuple[str, int, int]:
"""Inner content + (width, height) of a background SVG asset."""
text = (BACKGROUNDS / name).read_text(encoding="utf-8")
m = re.search(r"<svg\b[^>]*viewBox=\"0 0 (\d+(?:\.\d+)?) (\d+(?:\.\d+)?)\"[^>]*>", text)
assert m, f"cannot parse viewBox of {name}"
w, h = float(m.group(1)), float(m.group(2))
inner = re.sub(r"^.*?>\s*", "", text, count=1, flags=re.S)
inner = re.sub(r"\s*</svg>\s*$", "", inner, flags=re.S)
return inner, int(w), int(h)
def compose_master(girl: str, bg: str) -> str:
"""Composed master SVG: background + girl layer."""
inner, w, h = background_inner(bg)
x, y, bx, bh = GIRL_BOXES[bg]
return (
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}">\n'
f" {inner.strip()}\n"
f" {girl_layer(girl, (x, y, bx, bh))}\n"
"</svg>\n"
)
def ensure_masters(check: bool = False) -> None:
"""Write the generated light/dark masters; placeholder fallback if the
girl art is missing."""
missing = [p for p in (GIRL_BLACK, GIRL_WHITE) if not p.exists()]
if missing:
if check:
sys.exit(
f"[check] {MASTER.relative_to(ROOT)} is missing — "
"cannot verify generated icons without a master."
)
MASTER.write_text(PLACEHOLDER, encoding="utf-8")
print(f"[master] wrote placeholder: {MASTER.relative_to(ROOT)}")
return MASTER
sys.exit(f"[check] girl art missing: {[p.name for p in missing]}")
for path, content in ((MASTER, PLACEHOLDER), (MASTER_DARK, PLACEHOLDER)):
path.write_text(content, encoding="utf-8")
print(f"[master] wrote placeholder masters ({[p.name for p in missing]})")
return
MASTER.write_text(compose_master("black", "squircle-light.svg"), encoding="utf-8")
MASTER_DARK.write_text(compose_master("white", "squircle-dark.svg"), encoding="utf-8")
def render(size: int, *, background: str | None = None) -> Image.Image:
"""Render the master to an RGBA PNG of `size`x`size`."""
# ─── rendering ──────────────────────────────────────────────────────────────
def render(master: Path, size: int, *, background: str | None = None) -> Image.Image:
"""Render a master to an RGBA PNG of `size`x`size`."""
data = resvg_py.svg_to_bytes(
svg_path=str(MASTER), width=size, height=size, background=background
svg_path=str(master), width=size, height=size, background=background
)
return Image.open(io.BytesIO(data)).convert("RGBA")
@@ -134,11 +267,37 @@ def paste_centered(canvas: Image.Image, img: Image.Image) -> None:
canvas.alpha_composite(img, (x, y))
def build_logo_image() -> Image.Image:
"""1772x1799: black frame (~14-17px) + white interior + art at ~1.7x.
def girl_mark(kind: str, size: int) -> Image.Image:
"""Composite the girl SVG onto a fixed tile (BrandMark asset), rendered
straight from the vector art (crisp at any size).
Measured from the real logo.png: border L/R 14, T 17, B 16; white
interior spans (15,18)..(1758,1784) = 1743x1766.
girl_light: black girl on white tile. girl_dark: white girl on #0d1117.
"""
if kind == "girl_light":
girl, bg = "black", "tile-light.svg"
else:
girl, bg = "white", "tile-dark.svg"
if not GIRLS[girl].exists():
print(f" [girl] {GIRLS[girl].name} missing — placeholder fallback")
return render(MASTER if kind == "girl_light" else MASTER_DARK, size)
inner, w, h = background_inner(bg)
svg = (
f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {w} {h}">\n'
f" {inner.strip()}\n"
f" {girl_layer(girl, GIRL_BOXES[bg])}\n"
"</svg>\n"
)
data = resvg_py.svg_to_bytes(svg_string=svg, width=size, height=size)
return Image.open(io.BytesIO(data)).convert("RGBA")
def build_logo_image(master: Path, dark: bool = False) -> Image.Image:
"""1772x1799 wordmark: frame (light: black / dark: white, ~14-17px) +
interior (light: white / dark: #0d1117) + art at ~1.7x.
Measured from the real logo.png: border L/R 14, T 17, B 16; interior
spans (15,18)..(1758,1784) = 1743x1766.
"""
from PIL import ImageDraw
@@ -146,55 +305,88 @@ def build_logo_image() -> Image.Image:
art_w, art_h = 1743, 1766
border = 14
top, bottom = 17, 16
canvas = Image.new("RGBA", (W, H), (0, 0, 0, 0))
if dark:
frame, interior = DARK_RGB, (255, 255, 255)
else:
frame, interior = (0, 0, 0), (255, 255, 255)
canvas = Image.new("RGBA", (W, H), (0, 0, 0, 0))
draw = ImageDraw.Draw(canvas)
draw.rounded_rectangle((0, 0, W - 1, H - 1), radius=2, fill=(0, 0, 0, 255))
draw.rounded_rectangle((0, 0, W - 1, H - 1), radius=2, fill=frame)
draw.rectangle(
(border, top, W - 1 - border, H - 1 - bottom),
fill=(255, 255, 255, 255),
fill=interior,
)
# Art: render the master square at ~1.7x, tiny vertical stretch to fill.
art = render(art_w).resize((art_w, art_h), Image.LANCZOS)
art = render(master, art_w).resize((art_w, art_h), Image.LANCZOS)
canvas.alpha_composite(art, (border, top))
return canvas
def target_bytes(kind: str, arg: object) -> bytes:
"""Produce the exact bytes for one target. Shared by write + check."""
if kind == "svg":
return MASTER.read_bytes()
if kind == "svg_dark":
return MASTER_DARK.read_bytes()
if kind == "svg_copy":
return MASTER.read_bytes()
buf = io.BytesIO()
if kind == "png":
render(arg).save(buf, "PNG")
render(MASTER, arg).save(buf, "PNG")
elif kind == "png_dark":
render(MASTER_DARK, arg).save(buf, "PNG")
elif kind == "png_white":
render(arg, background="#ffffff").save(buf, "PNG")
elif kind == "jpg_white":
# JPEG has no alpha: flatten the squircle onto white.
render(arg, background="#ffffff").convert("RGB").save(buf, "JPEG", quality=90)
render(MASTER, arg, background="#ffffff").save(buf, "PNG")
elif kind == "png_dark_white":
render(MASTER_DARK, arg, background=DARK_HEX).save(buf, "PNG")
elif kind == "girl_light" or kind == "girl_dark":
# Flat 2-tone vector-style art: <256 distinct colors, so an 8-bit
# palette PNG holds it pixel-identically. Saved with compress_level=0
# (stored deflate blocks): the icons-freshness lane byte-compares
# against an ubuntu regen, and compressed PNG bytes drift per host
# zlib — stored blocks are spec-locked, identical on every platform.
# ~66KB vs ~15KB compressed is a non-issue for a bundled asset.
girl_mark(kind, arg).convert("RGB").convert("P", palette=Image.ADAPTIVE, colors=256).save(
buf, "PNG", compress_level=0
)
elif kind == "ico":
img = render(max(arg))
img = render(MASTER, max(arg))
img.save(buf, format="ICO", sizes=[(s, s) for s in arg])
elif kind == "ico_dark":
img = render(MASTER_DARK, max(arg))
img.save(buf, format="ICO", sizes=[(s, s) for s in arg])
elif kind == "icns":
img = render(1024)
img = render(MASTER, 1024)
frames = [img.resize((s, s), Image.LANCZOS) for s in (16, 32, 64, 128, 256, 512, 1024)]
img.save(buf, format="ICNS", append_images=frames[1:])
elif kind == "icns_dark":
img = render(MASTER_DARK, 1024)
frames = [img.resize((s, s), Image.LANCZOS) for s in (16, 32, 64, 128, 256, 512, 1024)]
img.save(buf, format="ICNS", append_images=frames[1:])
elif kind == "wide":
w, h = arg
canvas = Image.new("RGBA", (w, h), (0, 0, 0, 0))
paste_centered(canvas, render(100))
paste_centered(canvas, render(MASTER, 100))
canvas.save(buf, "PNG")
elif kind == "wide_dark":
w, h = arg
canvas = Image.new("RGBA", (w, h), (0, 0, 0, 0))
paste_centered(canvas, render(MASTER_DARK, 100))
canvas.save(buf, "PNG")
elif kind == "logo":
build_logo_image().save(buf, "PNG")
build_logo_image(MASTER, dark=False).save(buf, "PNG")
elif kind == "logo_dark":
build_logo_image(MASTER_DARK, dark=True).save(buf, "PNG")
else:
raise ValueError(f"unknown kind {kind!r}")
return buf.getvalue()
def cmd_write() -> None:
ensure_master()
ensure_masters()
written = 0
for rel, kind, arg in TARGETS:
path = ROOT / rel
@@ -204,17 +396,17 @@ def cmd_write() -> None:
written += 1
except Exception as exc: # noqa: BLE001 - report and keep going
print(f" !! {rel}: FAILED ({exc})")
print(f"[ok] wrote {written}/{len(TARGETS)} files from {MASTER.name}")
print(f"[ok] wrote {written}/{len(TARGETS)} files")
print("\n[verify]")
for rel, kind, arg in TARGETS:
path = ROOT / rel
try:
if kind == "svg_copy":
if kind in ("svg", "svg_dark", "svg_copy"):
print(f" {rel}: {path.stat().st_size} bytes SVG")
continue
im = Image.open(path)
if kind == "ico":
if kind in ("ico", "ico_dark"):
sizes = []
try:
for i in range(im.n_frames):
@@ -223,7 +415,7 @@ def cmd_write() -> None:
except Exception:
sizes = [im.size]
print(f" {rel}: ICO {sorted(set(sizes))}")
elif kind == "icns":
elif kind in ("icns", "icns_dark"):
print(f" {rel}: ICNS {im.size} (container)")
else:
print(f" {rel}: {im.format} {im.size}")
@@ -232,7 +424,7 @@ def cmd_write() -> None:
def cmd_check() -> int:
ensure_master(check=True)
ensure_masters(check=True)
drifted: list[str] = []
for rel, kind, arg in TARGETS:
path = ROOT / rel
@@ -249,10 +441,10 @@ def cmd_check() -> int:
drifted.append(f"{rel}: drift ({len(actual)} bytes on disk vs {len(expected)} generated)")
if not drifted:
print(f"[ok] all {len(TARGETS)} generated icons match {MASTER.name}")
print(f"[ok] all {len(TARGETS)} generated icons match the girl art + backgrounds")
return 0
print(f"[check] {len(drifted)} file(s) out of sync with {MASTER.name}:")
print(f"[check] {len(drifted)} file(s) out of sync:")
for line in drifted:
print(f" - {line}")
print(
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+1
View File
@@ -121,6 +121,7 @@ const config: Config = {
logo: {
alt: 'Hermes Agent',
src: 'img/logo.png',
srcDark: 'img/logo-dark.png',
},
items: [
{
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 345 B

After

Width:  |  Height:  |  Size: 458 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 918 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

+8 -1
View File
@@ -1,4 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">
<!-- app-icon background, light: white rounded square (rx=245), transparent corners -->
<rect x="0" y="0" width="1024" height="1024" rx="245" fill="#ffffff"/>
<circle cx="512" cy="512" r="420" fill="#e34c4c"/>
<svg x="100" y="100" width="824" height="824" viewBox="182.1875888671875 150.036837890625 4951.215650390624 5337.024662109375">
<path
id="path14"
style="opacity:1;fill:#000000"
d="M 2780.0999 2572.1925 C 2731.3999 2572.7925 2683.4019 2574.5933 2673.3019 2576.2933 C 2641.3019 2580.3933 2609.7995 2583.9939 2590.1995 2585.7939 C 2558.6995 2588.6939 2461.4006 2604.1934 2436.0006 2609.4934 C 2430.6006 2610.6934 2417.6007 2613.5942 2407.5007 2615.3942 C 2396.9007 2617.7942 2381.4 2620.6937 2373.1 2622.4937 C 2364.3 2624.2937 2350.0005 2627.1938 2341.1005 2628.9938 C 2297.8005 2637.7938 2215.4009 2657.4932 2195.8009 2662.7932 C 2189.3009 2664.5932 2181.499 2666.2947 2179.199 2666.2947 C 2176.899 2666.2947 2169.8014 2668.0943 2163.8014 2669.7943 C 2158.5014 2671.5943 2146.5999 2674.4938 2138.1999 2676.8938 C 2110.2999 2683.9938 2106.2018 2685.2929 2084.8018 2691.1929 C 2072.9018 2694.6929 2059.8019 2698.2929 2056.3019 2699.4929 C 2034.9019 2705.3929 2015.3015 2711.9934 1989.8015 2720.8934 C 1973.8015 2726.1934 1957.1997 2731.5932 1952.9997 2732.7932 C 1948.2997 2734.5932 1942.3011 2736.2934 1939.9011 2737.4934 C 1937.0011 2738.6934 1925.1005 2742.8921 1913.8005 2746.9921 C 1901.9005 2751.0921 1888.8006 2755.7914 1885.3006 2757.6914 C 1881.1006 2759.4914 1869.9003 2763.5918 1860.3003 2767.0918 C 1850.3003 2770.5918 1839.0002 2774.8924 1835.4002 2776.5924 C 1827.7002 2780.0924 1796.302 2793.7927 1777.302 2801.5927 C 1661.602 2848.9927 1452.2004 2956.3923 1391.6004 2998.4923 C 1387.4004 3001.3923 1370.9005 3011.4918 1354.8005 3022.1918 C 1312.0005 3048.8918 1272.9013 3074.3922 1253.9013 3088.6922 C 1249.2013 3092.1922 1237.2994 3100.5915 1227.1994 3107.6915 C 1207.5994 3121.3915 1120.4013 3186.4936 1103.8013 3200.7936 C 1042.1013 3251.1936 1002.2989 3287.3931 950.69897 3339.5931 C 911.59897 3379.3931 864.10036 3432.7915 851.00036 3451.1915 C 849.20036 3453.5915 841.50097 3464.2936 833.80097 3474.2936 C 813.60097 3501.5936 773.80044 3562.0926 752.50044 3599.9926 C 713.90044 3666.9926 647.50056 3803.4919 635.60056 3839.0919 C 632.70056 3847.8919 629.00156 3855.6932 627.80156 3856.2932 C 626.60156 3857.4932 625.99975 3859.7927 625.99975 3861.5927 C 625.99975 3863.3927 621.90105 3876.992 616.50105 3890.692 C 609.40105 3910.892 594.60095 3954.7915 583.30095 3992.6915 C 580.90095 3998.5915 572.69995 4030.0928 568.99995 4044.8928 C 561.79995 4073.3928 552.3999 4117.2925 552.3999 4122.0925 C 552.3999 4126.8925 551.20065 4130.4923 550.00065 4133.9923 C 548.80065 4136.8923 546.50045 4147.6925 545.30045 4157.0925 C 544.10045 4166.4925 540.59977 4190.2931 537.59977 4209.8931 C 532.89977 4242.4931 532.30212 4253.6937 532.30212 4342.7937 C 532.30212 4431.8937 532.89873 4451.2934 541.69873 4502.8934 C 549.49873 4546.1934 559.50006 4582.3942 575.50006 4623.8942 C 578.40006 4630.9942 580.80149 4638.1924 580.80149 4638.6924 C 580.80149 4639.8924 589.70013 4658.2925 600.40013 4680.1925 C 617.00013 4713.9925 625.99914 4728.8923 643.19914 4755.5923 C 655.09914 4773.9923 687.60031 4810.7922 715.60031 4837.4922 C 733.40031 4855.2922 750.00089 4870.6928 751.80089 4871.8928 C 753.60089 4873.0928 762.50077 4880.2942 772.00077 4887.2942 C 815.80077 4922.2942 897.69957 4966.7939 938.09957 4977.3939 C 941.59957 4978.5939 952.89964 4982.0941 962.99964 4985.7941 C 1000.3996 4998.2941 1026.501 5005.3935 1031.801 5002.9935 C 1034.201 5002.3935 1033.6004 5001.1932 1030.6004 5000.0932 C 1017.5004 4994.6932 987.80012 4972.1933 970.60012 4954.9933 C 960.60012 4943.6933 945.70125 4925.8918 938.00125 4914.6918 C 923.20125 4892.7918 892.90185 4838.7923 885.80185 4820.9923 C 883.40185 4815.0923 879.3008 4805.5921 877.0008 4799.6921 C 874.6008 4793.7921 871.70069 4786.5945 870.50069 4784.2945 C 867.60069 4778.3945 857.9995 4751.0939 851.4995 4731.4939 C 846.1995 4716.6939 845.59958 4707.0929 849.69958 4707.0929 C 851.49958 4706.4929 857.39972 4717.0931 864.49972 4730.1931 C 892.39972 4784.7931 936.9016 4841.7937 987.9016 4887.9937 C 1020.5016 4917.6937 1072.1013 4958.5931 1078.0013 4958.5931 C 1083.9013 4958.5931 1082.0991 4960.9939 1085.1991 4964.4939 L 1091.6992 4969.7934 L 1089.9011 4999.4939 C 1088.7011 5028.5939 1085.1995 5067.6918 1076.1995 5153.1918 C 1073.7995 5178.7918 1070.3 5212.5939 1069.1 5227.8939 C 1067.9 5243.2939 1066.2009 5266.9933 1064.9009 5280.0933 C 1063.7009 5293.0933 1060.8001 5323.3922 1059.0001 5347.6922 C 1056.6001 5371.9922 1053.7013 5403.9928 1051.3013 5418.8928 C 1049.5013 5433.6928 1048.401 5450.8931 1048.401 5456.7931 C 1048.401 5462.6931 1046.6021 5481.7921 1044.3021 5499.5921 C 1038.4021 5553.5921 1034.1997 5592.0928 1031.1997 5620.5928 C 1027.6997 5658.4928 1022.3018 5708.9931 1019.3018 5728.4931 C 1016.9018 5748.6931 1010.5013 5801.9917 1008.0013 5828.1917 C 1004.5013 5861.9917 998.50199 5911.893 997.30199 5915.393 C 996.10199 5917.193 994.90048 5927.8919 993.80048 5939.6919 C 989.70048 5974.6919 986.70083 5997.1924 982.00083 6031.0924 C 979.10083 6048.8924 976.10138 6070.7934 974.90138 6080.3934 C 973.70138 6090.4934 972.00176 6099.993 971.40176 6102.293 C 970.80176 6104.093 969.00025 6113.5928 967.90025 6122.4928 C 965.50025 6138.4928 963.20171 6150.994 954.80171 6194.894 C 952.40171 6206.Line truncated
transform="matrix(1.0330354,0,0,1.0330354,-145.41428,-2499.328)" />
</svg>
</svg>

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB