Initial commit: NexusOS - local AI assistant platform

This commit is contained in:
Jon Wingender
2026-07-21 22:48:38 -05:00
commit 714b9fc890
850 changed files with 28278 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 955 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 921 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Code chevrons + slash -->
<path d="M 52 42 L 34 64 L 52 86" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 76 42 L 94 64 L 76 86" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="71" y1="38" x2="57" y2="90" stroke="#f2f2f2"
stroke-width="8" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 636 B

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Gamepad -->
<rect x="36" y="50" width="56" height="30" rx="15" fill="#f2f2f2"/>
<rect x="48" y="62.5" width="16" height="5" rx="1" fill="#5e0066"/>
<rect x="53.5" y="57" width="5" height="16" rx="1" fill="#5e0066"/>
<circle cx="76" cy="60" r="3.5" fill="#5e0066"/>
<circle cx="84" cy="68" r="3.5" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 549 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Framed picture: sun + mountains -->
<rect x="34" y="40" width="60" height="48" rx="5" fill="none" stroke="#f2f2f2" stroke-width="7"/>
<circle cx="51" cy="55" r="6" fill="#f2f2f2"/>
<path d="M 40 84 L 56 63 L 67 74 L 78 58 L 88 84 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 481 B

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Globe -->
<circle cx="64" cy="64" r="28" fill="none" stroke="#f2f2f2" stroke-width="7"/>
<ellipse cx="64" cy="64" rx="12" ry="28" fill="none" stroke="#f2f2f2" stroke-width="7"/>
<line x1="38" y1="55" x2="90" y2="55" stroke="#f2f2f2" stroke-width="7" stroke-linecap="round"/>
<line x1="38" y1="73" x2="90" y2="73" stroke="#f2f2f2" stroke-width="7" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 605 B

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Play triangle -->
<path d="M 51 40 L 51 88 L 92 64 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 298 B

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Document with folded corner + text lines -->
<path d="M 44 32 L 74 32 L 86 44 L 86 96 L 44 96 Z" fill="#f2f2f2"/>
<path d="M 74 32 L 74 44 L 86 44 Z" fill="#5e0066"/>
<line x1="52" y1="58" x2="78" y2="58" stroke="#5e0066" stroke-width="5" stroke-linecap="round"/>
<line x1="52" y1="70" x2="78" y2="70" stroke="#5e0066" stroke-width="5" stroke-linecap="round"/>
<line x1="52" y1="82" x2="70" y2="82" stroke="#5e0066" stroke-width="5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 693 B

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Erlenmeyer flask -->
<path d="M 56 34 L 56 58 L 38 88 Q 35 95 43 95 L 85 95 Q 93 95 90 88 L 72 58 L 72 34"
fill="none" stroke="#f2f2f2" stroke-width="7" stroke-linejoin="round"/>
<line x1="51" y1="34" x2="77" y2="34" stroke="#f2f2f2" stroke-width="8" stroke-linecap="round"/>
<path d="M 51 76 L 77 76 L 86 88 Q 89 93 83 93 L 45 93 Q 39 93 42 88 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 604 B

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Monitor -->
<rect x="32" y="36" width="64" height="44" rx="5" fill="#f2f2f2"/>
<rect x="40" y="44" width="48" height="28" rx="2" fill="#5e0066"/>
<rect x="56" y="80" width="16" height="9" fill="#f2f2f2"/>
<rect x="44" y="89" width="40" height="7" rx="3" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 504 B

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Open-end wrench -->
<g transform="rotate(45 64 64)">
<rect x="58" y="44" width="12" height="44" rx="5" fill="#f2f2f2"/>
<circle cx="64" cy="44" r="14" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="17" fill="#5e0066"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 474 B

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Settings cog -->
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(0 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(45 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(90 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(135 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(180 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(225 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(270 64 64)" fill="#f2f2f2"/>
<rect x="57" y="27" width="14" height="21" rx="2" transform="rotate(315 64 64)" fill="#f2f2f2"/>
<circle cx="64" cy="64" r="25" fill="#f2f2f2"/>
<circle cx="64" cy="64" r="10" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Crescent moon + Z -->
<path d="M 66 39 A 28 28 0 1 0 66 91 A 28 28 0 0 1 66 39 Z"
fill="#f2f2f2"/>
<path d="M 78 34 L 99 34 L 78 56 L 99 56" fill="none" stroke="#f2f2f2"
stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 481 B

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Padlock -->
<path d="M 52 64 L 52 50 A 12 12 0 0 1 76 50 L 76 64" fill="none"
stroke="#f2f2f2" stroke-width="8" stroke-linecap="round"/>
<rect x="40" y="62" width="48" height="40" rx="7" fill="#f2f2f2"/>
<circle cx="64" cy="78" r="6" fill="#5e0066"/>
<rect x="61" y="80" width="6" height="13" rx="3" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 558 B

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Doorway bracket, open on the right -->
<path d="M 72 34 L 42 34 L 42 94 L 72 94" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Arrow exiting to the right -->
<path d="M 56 64 L 92 64" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round"/>
<path d="M 80 50 L 96 64 L 80 78" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 695 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Circular arrow -->
<path d="M 64 40 A 24 24 0 1 1 41 71" fill="none" stroke="#f2f2f2"
stroke-width="9" stroke-linecap="round"/>
<path d="M 50 32 L 70 40 L 56 56 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 418 B

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Power symbol: broken ring (gap at top) + stem -->
<path d="M 55 51 A 22 22 0 1 0 73 51" fill="none" stroke="#f2f2f2"
stroke-width="10" stroke-linecap="round"/>
<rect x="59" y="35" width="10" height="31" rx="5" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 464 B

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Crescent moon: big disc minus a disc shifted right -->
<path d="M 74 33 A 33 33 0 1 0 74 95 A 33 33 0 0 1 74 33 Z"
fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 367 B

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="52" fill="#5e0066" stroke="#8cc63f" stroke-width="6"/>
<!-- Two person silhouettes -->
<circle cx="78" cy="56" r="13" fill="#f2f2f2"/>
<path d="M 56 96 Q 56 72 78 72 Q 100 72 100 96 Z" fill="#f2f2f2"/>
<circle cx="48" cy="60" r="12" fill="#f2f2f2"/>
<path d="M 28 96 Q 28 76 48 76 Q 60 76 65 84 L 56 88
Q 56 84 56 96 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 521 B

+254
View File
@@ -0,0 +1,254 @@
#!/usr/bin/env python3
"""
NexusOS folder icon builder.
Emits one SVG per variant into places/, then rasterizes to
~/.icons/NexusOS/<size>/places/<name>.png at 16/22/24/32/48/64/128.
Run: python3 build.py # builds SVGs + renders all PNGs
python3 build.py --svg-only # just rewrite SVGs
"""
from pathlib import Path
import base64
import shutil
import subprocess
import sys
ROOT = Path(__file__).resolve().parent
PLACES = ROOT / "places"
ICONS_OUT = Path.home() / ".icons" / "NexusOS"
SIZES = [16, 22, 24, 32, 48, 64, 128]
# N logo source. Inkscape's headless renderer doesn't follow external href
# references, so we inline it as a base64 data URI.
LOGO_PATH = Path.home() / "nexus-core" / "assets" / "n-small.png"
LOGO_DATA_URI = (
"data:image/png;base64,"
+ base64.b64encode(LOGO_PATH.read_bytes()).decode("ascii")
)
# ── master folder geometry ────────────────────────────────────────────
# viewBox 0 0 128 128. Badge area: roughly (32,52)-(96,108).
MASTER = """\
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
"""
# Open-folder geometry used by folder-open and folder-drag-accept.
# The front face is angled forward, exposing the back as a triangle on top.
OPEN_MASTER = """\
<!-- Folder back, same as closed -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face, tilted forward — wider at bottom, narrower at top -->
<path d="M 22 44 L 106 44 L 118 110 Q 118 114 114 114
L 14 114 Q 10 114 10 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on tilted front face -->
<path d="M 22 44 L 106 44 L 106.5 46 L 21.5 46 Z" fill="#88008f"/>
"""
# ── badge fragments ───────────────────────────────────────────────────
# All badges drawn in white (#f2f2f2) and centered around (64, 78).
# Keep glyphs in roughly a 40-50px box for legibility at 22-32 sizes.
BADGES = {
"folder": "", # the base, no badge
"folder-documents": """\
<!-- Document with folded corner -->
<path d="M 48 58 L 72 58 L 80 66 L 80 98 L 48 98 Z"
fill="#f2f2f2"/>
<path d="M 72 58 L 72 66 L 80 66 Z" fill="#cccccc"/>
<rect x="52" y="72" width="24" height="2" fill="#5e0066"/>
<rect x="52" y="78" width="24" height="2" fill="#5e0066"/>
<rect x="52" y="84" width="20" height="2" fill="#5e0066"/>
<rect x="52" y="90" width="24" height="2" fill="#5e0066"/>
""",
"folder-download": """\
<!-- Down arrow over a tray -->
<rect x="60" y="56" width="8" height="22" fill="#f2f2f2"/>
<path d="M 50 76 L 78 76 L 64 92 Z" fill="#f2f2f2"/>
<rect x="48" y="96" width="32" height="4" rx="1" fill="#f2f2f2"/>
""",
"folder-music": """\
<!-- Eighth note -->
<rect x="68" y="58" width="4" height="32" fill="#f2f2f2"/>
<path d="M 68 58 L 84 62 L 84 70 L 72 66 Z" fill="#f2f2f2"/>
<ellipse cx="62" cy="92" rx="9" ry="6" fill="#f2f2f2"/>
""",
"folder-pictures": """\
<!-- Landscape: sun + mountains in a frame -->
<rect x="44" y="58" width="40" height="40" rx="2" fill="#f2f2f2"/>
<circle cx="74" cy="68" r="4" fill="#8cc63f"/>
<path d="M 46 96 L 60 78 L 70 88 L 78 80 L 82 96 Z" fill="#5e0066"/>
""",
"folder-videos": """\
<!-- Filmstrip with play triangle -->
<rect x="44" y="60" width="40" height="36" rx="2" fill="#f2f2f2"/>
<rect x="44" y="60" width="40" height="4" fill="#5e0066"/>
<rect x="44" y="92" width="40" height="4" fill="#5e0066"/>
<rect x="46" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="56" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="66" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="76" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="46" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="56" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="66" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="76" y="92" width="4" height="4" fill="#f2f2f2"/>
<path d="M 58 70 L 74 78 L 58 86 Z" fill="#8cc63f"/>
""",
"folder-templates": """\
<!-- Pencil over a page -->
<path d="M 50 60 L 70 60 L 78 68 L 78 96 L 50 96 Z" fill="#f2f2f2"/>
<path d="M 70 60 L 70 68 L 78 68 Z" fill="#cccccc"/>
<!-- Pencil at 45deg -->
<path d="M 60 88 L 80 68 L 84 72 L 64 92 Z" fill="#8cc63f"/>
<path d="M 60 88 L 64 92 L 58 94 Z" fill="#5e0066"/>
""",
"folder-publicshare": """\
<!-- Three small people silhouettes -->
<circle cx="52" cy="68" r="6" fill="#f2f2f2"/>
<path d="M 42 92 Q 42 78 52 78 Q 62 78 62 92 Z" fill="#f2f2f2"/>
<circle cx="76" cy="68" r="6" fill="#f2f2f2"/>
<path d="M 66 92 Q 66 78 76 78 Q 86 78 86 92 Z" fill="#f2f2f2"/>
<circle cx="64" cy="60" r="5" fill="#f2f2f2"/>
<path d="M 56 76 Q 56 66 64 66 Q 72 66 72 76 Z" fill="#f2f2f2"/>
""",
"folder-home": """\
<!-- House silhouette -->
<path d="M 64 56 L 88 76 L 84 76 L 84 96 L 70 96 L 70 84
L 58 84 L 58 96 L 44 96 L 44 76 L 40 76 Z"
fill="#f2f2f2"/>
""",
"folder-recent": """\
<!-- Clock face -->
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
<circle cx="64" cy="80" r="20" fill="none" stroke="#5e0066" stroke-width="2"/>
<rect x="63" y="66" width="2" height="16" fill="#5e0066"/>
<rect x="63" y="79" width="14" height="2" fill="#5e0066"/>
""",
"folder-remote": """\
<!-- Globe / network -->
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
<ellipse cx="64" cy="80" rx="20" ry="8" fill="none" stroke="#5e0066" stroke-width="1.5"/>
<ellipse cx="64" cy="80" rx="8" ry="20" fill="none" stroke="#5e0066" stroke-width="1.5"/>
<line x1="44" y1="80" x2="84" y2="80" stroke="#5e0066" stroke-width="1.5"/>
<line x1="64" y1="60" x2="64" y2="100" stroke="#5e0066" stroke-width="1.5"/>
""",
"folder-saved-search": """\
<!-- Magnifying glass -->
<circle cx="58" cy="74" r="12" fill="none" stroke="#f2f2f2" stroke-width="4"/>
<line x1="68" y1="84" x2="82" y2="98" stroke="#f2f2f2" stroke-width="5" stroke-linecap="round"/>
""",
"folder-drag-accept": "USE_OPEN_MASTER", # uses OPEN_MASTER, no extra badge
"folder-open": "USE_OPEN_MASTER",
"folder-nexus-core": f"""\
<!-- N logo (base64 of assets/n-small.png), centered on front face. -->
<!-- Source is 598x341 (aspect 1.754:1). -->
<image xlink:href="{LOGO_DATA_URI}" x="28" y="58" width="72" height="41"
preserveAspectRatio="xMidYMid meet"/>
""",
}
def svg_for(name: str, badge: str) -> str:
if badge == "USE_OPEN_MASTER":
body = OPEN_MASTER
extra = ""
else:
body = MASTER
extra = badge
return f"""<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
{body}{extra}</svg>
"""
def write_svgs() -> list[str]:
PLACES.mkdir(parents=True, exist_ok=True)
written = []
for name, badge in BADGES.items():
path = PLACES / f"{name}.svg"
path.write_text(svg_for(name, badge))
written.append(name)
return written
def render_pngs(names: list[str]) -> None:
for name in names:
svg = PLACES / f"{name}.svg"
for size in SIZES:
out_dir = ICONS_OUT / f"{size}x{size}" / "places"
out_dir.mkdir(parents=True, exist_ok=True)
out = out_dir / f"{name}.png"
subprocess.run(
[
"inkscape",
str(svg),
"--export-type=png",
f"--export-filename={out}",
f"--export-width={size}",
f"--export-height={size}",
],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" rendered {name} @ {size}")
def backup_existing_nexus_core() -> None:
"""Move the existing chunky 3D folder-nexus-core PNGs aside before overwriting."""
backup = ROOT / "_backup" / "folder-nexus-core-original"
backup.mkdir(parents=True, exist_ok=True)
for size in SIZES:
src = ICONS_OUT / f"{size}x{size}" / "places" / "folder-nexus-core.png"
if src.exists():
shutil.copy2(src, backup / f"{size}.png")
print(f" backed up original folder-nexus-core to {backup}")
def main() -> None:
svg_only = "--svg-only" in sys.argv
names = write_svgs()
print(f"Wrote {len(names)} SVGs into {PLACES}")
if svg_only:
return
backup_existing_nexus_core()
render_pngs(names)
# Refresh the GTK icon cache so file managers pick up the new icons.
subprocess.run(
["gtk-update-icon-cache", "-f", str(ICONS_OUT)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f"\nDone. Set theme to NexusOS in your DE to see the icons.")
if __name__ == "__main__":
main()
@@ -0,0 +1,278 @@
#!/usr/bin/env python3
"""
NexusOS action-icon generator.
Builds the icons the XFCE logoff dialog (xfce4-session-logout) and the
Whisker menu command buttons request — shutdown / reboot / log-out /
suspend / hibernate / switch-user / lock-screen / settings — in the
NexusOS palette (white glyph on a deep-purple disc with a lime ring).
Emits one SVG per icon into actions/, rasterizes to
~/.icons/NexusOS/<size>/actions/<name>.png at 16/22/24/32/48/64/128,
then writes the alias copies (xfsm-*, system-suspend-hibernate, and the
settings-manager app-icon names that Whisker's Settings button uses).
Run: python3 build_actions.py # SVGs + PNGs + aliases
python3 build_actions.py --svg-only # just rewrite SVGs
"""
from pathlib import Path
import shutil
import subprocess
import sys
ROOT = Path(__file__).resolve().parent
ACTIONS = ROOT / "actions"
ICONS_OUT = Path.home() / ".icons" / "NexusOS"
SIZES = [16, 22, 24, 32, 48, 64, 128]
# ── colors (mirror gtk-3.0/colors.css) ────────────────────────────────
BODY = "#5e0066" # brand_purple_dark — disc fill
RING = "#8cc63f" # brand_green — disc ring
GLYPH = "#f2f2f2" # text_primary — glyph
# ── master: purple disc + lime ring, viewBox 0 0 128 128 ──────────────
# Glyph area: a ~70px box centered on (64, 64).
DISC = f' <circle cx="64" cy="64" r="52" fill="{BODY}" ' \
f'stroke="{RING}" stroke-width="6"/>\n'
def _gear_glyph() -> str:
"""Settings cog: 8 teeth on a white disc with a punched centre hole."""
teeth = "".join(
f' <rect x="57" y="27" width="14" height="21" rx="2" '
f'transform="rotate({k * 45} 64 64)" fill="{GLYPH}"/>\n'
for k in range(8)
)
return (
" <!-- Settings cog -->\n"
+ teeth
+ f' <circle cx="64" cy="64" r="25" fill="{GLYPH}"/>\n'
+ f' <circle cx="64" cy="64" r="10" fill="{BODY}"/>\n'
)
# ── glyph fragments — white, centered on (64, 64) ─────────────────────
GLYPHS = {
"system-shutdown": f"""\
<!-- Power symbol: broken ring (gap at top) + stem -->
<path d="M 55 51 A 22 22 0 1 0 73 51" fill="none" stroke="{GLYPH}"
stroke-width="10" stroke-linecap="round"/>
<rect x="59" y="35" width="10" height="31" rx="5" fill="{GLYPH}"/>
""",
"system-reboot": f"""\
<!-- Circular arrow -->
<path d="M 64 40 A 24 24 0 1 1 41 71" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round"/>
<path d="M 50 32 L 70 40 L 56 56 Z" fill="{GLYPH}"/>
""",
"system-log-out": f"""\
<!-- Doorway bracket, open on the right -->
<path d="M 72 34 L 42 34 L 42 94 L 72 94" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Arrow exiting to the right -->
<path d="M 56 64 L 92 64" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round"/>
<path d="M 80 50 L 96 64 L 80 78" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
""",
"system-suspend": f"""\
<!-- Crescent moon: big disc minus a disc shifted right -->
<path d="M 74 33 A 33 33 0 1 0 74 95 A 33 33 0 0 1 74 33 Z"
fill="{GLYPH}"/>
""",
"system-hibernate": f"""\
<!-- Crescent moon + Z -->
<path d="M 66 39 A 28 28 0 1 0 66 91 A 28 28 0 0 1 66 39 Z"
fill="{GLYPH}"/>
<path d="M 78 34 L 99 34 L 78 56 L 99 56" fill="none" stroke="{GLYPH}"
stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
""",
"system-switch-user": f"""\
<!-- Two person silhouettes -->
<circle cx="78" cy="56" r="13" fill="{GLYPH}"/>
<path d="M 56 96 Q 56 72 78 72 Q 100 72 100 96 Z" fill="{GLYPH}"/>
<circle cx="48" cy="60" r="12" fill="{GLYPH}"/>
<path d="M 28 96 Q 28 76 48 76 Q 60 76 65 84 L 56 88
Q 56 84 56 96 Z" fill="{GLYPH}"/>
""",
"system-lock-screen": f"""\
<!-- Padlock -->
<path d="M 52 64 L 52 50 A 12 12 0 0 1 76 50 L 76 64" fill="none"
stroke="{GLYPH}" stroke-width="8" stroke-linecap="round"/>
<rect x="40" y="62" width="48" height="40" rx="7" fill="{GLYPH}"/>
<circle cx="64" cy="78" r="6" fill="{BODY}"/>
<rect x="61" y="80" width="6" height="13" rx="3" fill="{BODY}"/>
""",
"preferences-desktop": _gear_glyph(),
# ── Whisker menu application-category glyphs ──────────────────────
"applications-internet": f"""\
<!-- Globe -->
<circle cx="64" cy="64" r="28" fill="none" stroke="{GLYPH}" stroke-width="7"/>
<ellipse cx="64" cy="64" rx="12" ry="28" fill="none" stroke="{GLYPH}" stroke-width="7"/>
<line x1="38" y1="55" x2="90" y2="55" stroke="{GLYPH}" stroke-width="7" stroke-linecap="round"/>
<line x1="38" y1="73" x2="90" y2="73" stroke="{GLYPH}" stroke-width="7" stroke-linecap="round"/>
""",
"applications-development": f"""\
<!-- Code chevrons + slash -->
<path d="M 52 42 L 34 64 L 52 86" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M 76 42 L 94 64 L 76 86" fill="none" stroke="{GLYPH}"
stroke-width="9" stroke-linecap="round" stroke-linejoin="round"/>
<line x1="71" y1="38" x2="57" y2="90" stroke="{GLYPH}"
stroke-width="8" stroke-linecap="round"/>
""",
"applications-multimedia": f"""\
<!-- Play triangle -->
<path d="M 51 40 L 51 88 L 92 64 Z" fill="{GLYPH}"/>
""",
"applications-office": f"""\
<!-- Document with folded corner + text lines -->
<path d="M 44 32 L 74 32 L 86 44 L 86 96 L 44 96 Z" fill="{GLYPH}"/>
<path d="M 74 32 L 74 44 L 86 44 Z" fill="{BODY}"/>
<line x1="52" y1="58" x2="78" y2="58" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
<line x1="52" y1="70" x2="78" y2="70" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
<line x1="52" y1="82" x2="70" y2="82" stroke="{BODY}" stroke-width="5" stroke-linecap="round"/>
""",
"applications-graphics": f"""\
<!-- Framed picture: sun + mountains -->
<rect x="34" y="40" width="60" height="48" rx="5" fill="none" stroke="{GLYPH}" stroke-width="7"/>
<circle cx="51" cy="55" r="6" fill="{GLYPH}"/>
<path d="M 40 84 L 56 63 L 67 74 L 78 58 L 88 84 Z" fill="{GLYPH}"/>
""",
"applications-system": f"""\
<!-- Monitor -->
<rect x="32" y="36" width="64" height="44" rx="5" fill="{GLYPH}"/>
<rect x="40" y="44" width="48" height="28" rx="2" fill="{BODY}"/>
<rect x="56" y="80" width="16" height="9" fill="{GLYPH}"/>
<rect x="44" y="89" width="40" height="7" rx="3" fill="{GLYPH}"/>
""",
"applications-utilities": f"""\
<!-- Open-end wrench -->
<g transform="rotate(45 64 64)">
<rect x="58" y="44" width="12" height="44" rx="5" fill="{GLYPH}"/>
<circle cx="64" cy="44" r="14" fill="{GLYPH}"/>
<rect x="57" y="27" width="14" height="17" fill="{BODY}"/>
</g>
""",
"applications-games": f"""\
<!-- Gamepad -->
<rect x="36" y="50" width="56" height="30" rx="15" fill="{GLYPH}"/>
<rect x="48" y="62.5" width="16" height="5" rx="1" fill="{BODY}"/>
<rect x="53.5" y="57" width="5" height="16" rx="1" fill="{BODY}"/>
<circle cx="76" cy="60" r="3.5" fill="{BODY}"/>
<circle cx="84" cy="68" r="3.5" fill="{BODY}"/>
""",
"applications-science": f"""\
<!-- Erlenmeyer flask -->
<path d="M 56 34 L 56 58 L 38 88 Q 35 95 43 95 L 85 95 Q 93 95 90 88 L 72 58 L 72 34"
fill="none" stroke="{GLYPH}" stroke-width="7" stroke-linejoin="round"/>
<line x1="51" y1="34" x2="77" y2="34" stroke="{GLYPH}" stroke-width="8" stroke-linecap="round"/>
<path d="M 51 76 L 77 76 L 86 88 Q 89 93 83 93 L 45 93 Q 39 93 42 88 Z" fill="{GLYPH}"/>
""",
}
# Extra filenames that render the same art (XFCE's xfsm-* lookup names,
# the hybrid-sleep alias, and the Whisker command-button names).
ALIASES = {
"xfsm-logout": "system-log-out",
"xfsm-reboot": "system-reboot",
"xfsm-shutdown": "system-shutdown",
"xfsm-suspend": "system-suspend",
"xfsm-hibernate": "system-hibernate",
"xfsm-switch-user": "system-switch-user",
"system-suspend-hibernate": "system-suspend",
"xfsm-lock": "system-lock-screen",
"preferences-system": "preferences-desktop",
"org.xfce.settings.manager": "preferences-desktop",
"xfce4-settings-manager": "preferences-desktop",
# XFCE's "Accessories" menu uses xfce-accessories.directory
# (Icon=applications-accessories), NOT the freedesktop Utility name, so
# it needs the same wrench/tools badge as applications-utilities.
"applications-accessories": "applications-utilities",
}
def svg_for(glyph: str) -> str:
return (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" '
'viewBox="0 0 128 128">\n'
f"{DISC}{glyph}</svg>\n"
)
def write_svgs() -> list[str]:
ACTIONS.mkdir(parents=True, exist_ok=True)
for name, glyph in GLYPHS.items():
(ACTIONS / f"{name}.svg").write_text(svg_for(glyph))
return list(GLYPHS)
def render_pngs(names: list[str]) -> None:
for name in names:
svg = ACTIONS / f"{name}.svg"
for size in SIZES:
out_dir = ICONS_OUT / f"{size}x{size}" / "actions"
out_dir.mkdir(parents=True, exist_ok=True)
out = out_dir / f"{name}.png"
subprocess.run(
[
"inkscape",
str(svg),
"--export-type=png",
f"--export-filename={out}",
f"--export-width={size}",
f"--export-height={size}",
],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" rendered {name} @ {size}")
def write_aliases() -> None:
for alias, src in ALIASES.items():
for size in SIZES:
d = ICONS_OUT / f"{size}x{size}" / "actions"
src_png = d / f"{src}.png"
if src_png.exists():
shutil.copy2(src_png, d / f"{alias}.png")
print(f" alias {alias} -> {src}")
def main() -> None:
svg_only = "--svg-only" in sys.argv
names = write_svgs()
print(f"Wrote {len(names)} SVGs into {ACTIONS}")
if svg_only:
return
render_pngs(names)
write_aliases()
subprocess.run(
["gtk-update-icon-cache", "-f", str(ICONS_OUT)],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print("\nDone. Reload: xfdesktop --reload & xfce4-panel -r")
if __name__ == "__main__":
main()
@@ -0,0 +1,54 @@
#!/usr/bin/env python3
"""
NexusOS battery-icon recolorizer.
xfce4-power-manager draws the panel battery from the icon theme's
`battery-*-charging-symbolic` icons. Papirus-Dark (which NexusOS
inherits from) paints the charging bolt/fill in its own material green
(#4caf50). This script copies every charging battery symbolic icon into
the NexusOS theme with that green swapped for brand_green (#8cc63f), so
the charging indicator matches the rest of the NexusOS accent.
Only the green is touched — the gray battery body (#dfdfdf) is left
alone (it already reads fine on the dark panel). Symlinked aliases
(good/low/medium/caution/empty-charging) are resolved and written as
real recolored files so every requested name overrides Papirus.
Run: python3 build_battery.py
Then: gtk-update-icon-cache -f -t ../NexusOS-icons
xfce4-panel -r # reload panel to pick up new icons
"""
from pathlib import Path
ROOT = Path(__file__).resolve().parent
OUT = ROOT.parent / "NexusOS-icons" / "scalable" / "status"
SRC = Path("/usr/share/icons/Papirus-Dark/symbolic/status")
PAPIRUS_GREEN = "#4caf50"
NEXUS_GREEN = "#8cc63f" # brand_green
def main() -> None:
OUT.mkdir(parents=True, exist_ok=True)
# Every charging battery symbolic icon (charged-100 has no green, so
# the replace is a harmless no-op there; we copy it too for a
# consistent battery set under the NexusOS theme).
sources = sorted(SRC.glob("battery-*charg*-symbolic.svg"))
written = 0
for src in sources:
# read_text follows symlinks, so alias names get the real content
content = src.read_text()
if PAPIRUS_GREEN not in content:
# charged/full icons with no green — skip; let them fall
# through to Papirus unchanged.
continue
recolored = content.replace(PAPIRUS_GREEN, NEXUS_GREEN)
(OUT / src.name).write_text(recolored)
written += 1
print(f"Recolored {written} charging battery icons "
f"({PAPIRUS_GREEN} -> {NEXUS_GREEN}) into {OUT}")
if __name__ == "__main__":
main()
@@ -0,0 +1,270 @@
#!/usr/bin/env python3
"""
NexusOS folder icon generator.
Composes each variant SVG from a shared master + a per-variant badge fragment,
then rasterizes each to PNG at 16/22/24/32/48/64/128 and drops the result into
~/.icons/NexusOS/<size>/places/<name>.png.
Run from this directory:
python3 build_icons.py [--svg-only] [--no-cache-update]
"""
from __future__ import annotations
import argparse
import base64
import pathlib
import shutil
import subprocess
import sys
from textwrap import dedent
ROOT = pathlib.Path(__file__).resolve().parent
PLACES_DIR = ROOT / "places"
LOGO_SRC = ROOT.parent.parent / "assets" / "n-small.png"
ICONS_OUT = pathlib.Path.home() / ".icons" / "NexusOS"
SIZES = (16, 22, 24, 32, 48, 64, 128)
# ── colors (mirror gtk-3.0/colors.css) ───────────────────────────────────────
BODY = "#5e0066" # brand_purple_dark — folder body
HIGHLIGHT = "#88008f" # brand_purple — top-of-face highlight stripe
TAB = "#8cc63f" # brand_green — folder tab
TAB_BRIGHT = "#b8e373" # brand_green_light — drag-accept tab
LIP = "#f2f2f2" # text_primary — inner lip stripe & badge fill
# Embedded logo (base64) populated at runtime
_LOGO_B64: str | None = None
def logo_data_uri() -> str:
global _LOGO_B64
if _LOGO_B64 is None:
_LOGO_B64 = base64.b64encode(LOGO_SRC.read_bytes()).decode("ascii")
return f"data:image/png;base64,{_LOGO_B64}"
def folder_svg(badge: str, *, tab_color: str = TAB) -> str:
"""Build a full variant SVG by injecting `badge` into the master template."""
return dedent(f"""\
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30 L 110 30
Q 114 30 114 34 L 114 110 Q 114 114 110 114 L 18 114
Q 14 114 14 110 Z" fill="{tab_color}"/>
<rect x="14" y="36" width="100" height="3" fill="{LIP}"/>
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114 L 18 114
Q 14 114 14 110 Z" fill="{BODY}"/>
<rect x="14" y="39" width="100" height="2" fill="{HIGHLIGHT}"/>
<g id="badge">{badge}</g>
</svg>
""")
# ── badge fragments (centered on front face ~x=64, y=78) ─────────────────────
# Each is a simple white-on-purple glyph kept readable at 16-22px.
BADGES: dict[str, str] = {
# Base folder — no badge
"folder": "",
# Folder-open: lighter tab + slight perspective on front (simple variant)
"folder-open": (
f'<path d="M 14 41 L 22 39 L 106 39 L 114 41 L 114 110 '
f'Q 114 114 110 114 L 18 114 Q 14 114 14 110 Z" '
f'fill="{HIGHLIGHT}" opacity="0.55"/>'
),
# Documents — page with folded corner
"folder-documents": (
f'<path d="M 50 58 L 70 58 L 80 68 L 80 100 L 50 100 Z" fill="{LIP}"/>'
f'<path d="M 70 58 L 70 68 L 80 68" fill="none" '
f'stroke="{BODY}" stroke-width="2" stroke-linejoin="miter"/>'
),
# Download — down arrow
"folder-download": (
f'<path d="M 60 58 L 68 58 L 68 80 L 78 80 L 64 98 L 50 80 L 60 80 Z" '
f'fill="{LIP}"/>'
),
# Drag-accept — uses bright green tab, plus white plus glyph
"folder-drag-accept": (
f'<path d="M 60 64 L 68 64 L 68 76 L 80 76 L 80 84 L 68 84 L 68 96 '
f'L 60 96 L 60 84 L 48 84 L 48 76 L 60 76 Z" fill="{LIP}"/>'
),
# Home — house
"folder-home": (
f'<path d="M 64 56 L 84 74 L 80 74 L 80 100 L 70 100 L 70 86 L 58 86 '
f'L 58 100 L 48 100 L 48 74 L 44 74 Z" fill="{LIP}"/>'
),
# Music — eighth note
"folder-music": (
f'<rect x="70" y="56" width="4" height="32" fill="{LIP}"/>'
f'<path d="M 70 56 L 70 64 Q 78 60 82 64 L 82 56 Z" fill="{LIP}"/>'
f'<ellipse cx="64" cy="90" rx="8" ry="6" fill="{LIP}"/>'
),
# Pictures — sun over mountains
"folder-pictures": (
f'<rect x="44" y="60" width="40" height="40" rx="3" fill="{LIP}"/>'
f'<circle cx="74" cy="70" r="3" fill="{BODY}"/>'
f'<path d="M 44 100 L 56 82 L 66 92 L 74 84 L 84 100 Z" fill="{BODY}"/>'
),
# Publicshare — two people
"folder-publicshare": (
f'<circle cx="56" cy="66" r="6" fill="{LIP}"/>'
f'<circle cx="72" cy="66" r="6" fill="{LIP}"/>'
f'<path d="M 46 100 L 46 88 Q 46 78 56 78 L 62 78 Q 64 78 64 80 '
f'Q 64 78 66 78 L 72 78 Q 82 78 82 88 L 82 100 Z" fill="{LIP}"/>'
),
# Recent — clock face
"folder-recent": (
f'<circle cx="64" cy="80" r="14" fill="{LIP}"/>'
f'<path d="M 64 70 L 64 80 L 71 84" fill="none" stroke="{BODY}" '
f'stroke-width="2.5" stroke-linecap="round"/>'
),
# Remote — globe (circle + ellipse + meridian)
"folder-remote": (
f'<circle cx="64" cy="80" r="14" fill="{LIP}"/>'
f'<ellipse cx="64" cy="80" rx="14" ry="6" fill="none" '
f'stroke="{BODY}" stroke-width="2"/>'
f'<line x1="64" y1="66" x2="64" y2="94" stroke="{BODY}" stroke-width="2"/>'
),
# Saved search — magnifying glass
"folder-saved-search": (
f'<circle cx="60" cy="76" r="10" fill="none" stroke="{LIP}" stroke-width="4"/>'
f'<line x1="67" y1="83" x2="78" y2="94" stroke="{LIP}" stroke-width="5" '
f'stroke-linecap="round"/>'
),
# Templates — page with horizontal lines
"folder-templates": (
f'<rect x="48" y="58" width="32" height="42" rx="2" fill="{LIP}"/>'
f'<rect x="53" y="64" width="22" height="2" fill="{BODY}"/>'
f'<rect x="53" y="70" width="22" height="2" fill="{BODY}"/>'
f'<rect x="53" y="76" width="18" height="2" fill="{BODY}"/>'
f'<rect x="53" y="82" width="20" height="2" fill="{BODY}"/>'
f'<rect x="53" y="88" width="14" height="2" fill="{BODY}"/>'
),
# Videos — play triangle
"folder-videos": (
f'<path d="M 54 62 L 54 98 L 82 80 Z" fill="{LIP}"/>'
),
}
def nexus_core_svg() -> str:
"""folder-nexus-core embeds the bicolor N logo via base64 data URI."""
return dedent(f"""\
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
width="128" height="128" viewBox="0 0 128 128">
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30 L 110 30
Q 114 30 114 34 L 114 110 Q 114 114 110 114 L 18 114
Q 14 114 14 110 Z" fill="{TAB}"/>
<rect x="14" y="36" width="100" height="3" fill="{LIP}"/>
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114 L 18 114
Q 14 114 14 110 Z" fill="{BODY}"/>
<rect x="14" y="39" width="100" height="2" fill="{HIGHLIGHT}"/>
<image xlink:href="{logo_data_uri()}" x="32" y="58" width="64" height="37"
preserveAspectRatio="xMidYMid meet"/>
</svg>
""")
def write_variants() -> list[tuple[str, pathlib.Path]]:
PLACES_DIR.mkdir(parents=True, exist_ok=True)
written: list[tuple[str, pathlib.Path]] = []
for name, badge in BADGES.items():
tab = TAB_BRIGHT if name == "folder-drag-accept" else TAB
path = PLACES_DIR / f"{name}.svg"
path.write_text(folder_svg(badge, tab_color=tab))
written.append((name, path))
nx_path = PLACES_DIR / "folder-nexus-core.svg"
nx_path.write_text(nexus_core_svg())
written.append(("folder-nexus-core", nx_path))
return written
def render_one(svg_path: pathlib.Path, name: str) -> None:
for size in SIZES:
out_dir = ICONS_OUT / f"{size}x{size}" / "places"
out_dir.mkdir(parents=True, exist_ok=True)
out_png = out_dir / f"{name}.png"
subprocess.run(
[
"inkscape",
str(svg_path),
"--export-type=png",
f"--export-filename={out_png}",
f"--export-width={size}",
f"--export-height={size}",
],
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
print(f" {name}{size}x{size}")
def backup_existing() -> None:
backup = ROOT / "_backup" / "folder-nexus-core-pre-rebuild"
if backup.exists():
return
backup.mkdir(parents=True, exist_ok=True)
for size in SIZES:
src = ICONS_OUT / f"{size}x{size}" / "places" / "folder-nexus-core.png"
if src.exists():
shutil.copy2(src, backup / f"{size}-folder-nexus-core.png")
print(f"Backed up existing folder-nexus-core PNGs → {backup}")
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--svg-only", action="store_true",
help="only write SVG sources, skip rasterization")
parser.add_argument("--no-cache-update", action="store_true",
help="skip gtk-update-icon-cache at the end")
args = parser.parse_args()
if not LOGO_SRC.exists():
print(f"ERROR: logo source missing at {LOGO_SRC}", file=sys.stderr)
return 1
print("Writing variant SVGs…")
variants = write_variants()
print(f" {len(variants)} SVGs written to {PLACES_DIR}")
if args.svg_only:
return 0
backup_existing()
print("Rendering PNGs…")
for name, svg_path in variants:
render_one(svg_path, name)
if not args.no_cache_update:
print("Refreshing icon cache…")
subprocess.run(
["gtk-update-icon-cache", "-f", "-t", str(ICONS_OUT)],
check=False,
)
print(f"\nDone. Rendered {len(variants)} variants × {len(SIZES)} sizes "
f"= {len(variants) * len(SIZES)} PNGs.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,398 @@
#!/usr/bin/env python3
"""
NexusOS network status icons generator.
Generates flat symbolic-style SVGs for nm-tray and any other Qt/GTK app
that requests network-* status icons. Output goes to
scalable/status/ of the NexusOS-icons theme.
Design language:
* 16×16 viewBox, scalable SVG (no rasterization needed — Qt5 + GTK3
both handle SVG fine in scalable/ dirs)
* Light off-white stroke for base shape (wifi fan, ethernet plug,
globe) — readable on the dark NexusOS surfaces
* Lime brand accent for connected / active states
* Purple lock for encryption
* Red for error, orange for acquiring / no-route
Run: python3 build_status.py
"""
from pathlib import Path
import math
ROOT = Path(__file__).resolve().parent
OUT = ROOT.parent / "NexusOS-icons" / "scalable" / "status"
# Palette — mirrors gtk-3.0/colors.css.
BASE = "#f2f2f2" # text_primary
MUTED = "#6e7173" # text_disabled
GOOD = "#8cc63f" # brand_green
LOCK = "#f2f2f2" # text_primary (white) — neutral attribute indicator
# while green is reserved for active signal strength.
ERROR = "#da4453" # error_color
WARN = "#f67400" # warning_color
SW = 1.6
# Wifi fan arc geometry: half-angle from vertical. Smaller half-angle ⇒
# narrower sweep but allows bigger radius (taller fan) before endpoints
# run off the sides at 16x16. 40° (80° total sweep) is a sweet spot.
ARC_HALF_ANGLE = 40
S_ARC = math.sin(math.radians(ARC_HALF_ANGLE)) # x offset coefficient
C_ARC = math.cos(math.radians(ARC_HALF_ANGLE)) # y offset coefficient
def svg(body: str) -> str:
return (
'<svg xmlns="http://www.w3.org/2000/svg" '
'viewBox="0 0 16 16" width="16" height="16">\n'
+ body.rstrip()
+ "\n</svg>\n"
)
# ── wifi fan ────────────────────────────────────────────────────────
# Fan sits low in the viewBox with arcs reaching almost the top edge so
# the rendered icon matches the height of neighboring tray icons (shield,
# bluetooth, etc.). Outermost r=11.5 with the narrower 80° arc sweep
# keeps endpoints inside x=[0.61, 15.39] — fits with stroke-cap margin.
WIFI_CX, WIFI_CY = 8, 13.0
WIFI_R = [3.0, 5.5, 8.5, 11.5]
DOT_R = 1.3
def _arc(r: float, color: str, opacity: float = 1.0) -> str:
x0 = WIFI_CX - r * S_ARC
x1 = WIFI_CX + r * S_ARC
y = WIFI_CY - r * C_ARC
return (
f' <path d="M {x0:.2f} {y:.2f} A {r} {r} 0 0 1 {x1:.2f} {y:.2f}" '
f'fill="none" stroke="{color}" stroke-width="{SW}" '
f'stroke-linecap="round" opacity="{opacity}"/>'
)
def wifi_fan(level: int, active_color: str = GOOD) -> str:
"""level 0 (none) .. 4 (excellent). Filled arcs use brand_green by
default so signal strength reads as the primary visual element."""
parts = []
for i, r in enumerate(WIFI_R):
if i < level:
parts.append(_arc(r, active_color, 1.0))
else:
parts.append(_arc(r, MUTED, 0.35))
if level > 0:
parts.append(
f' <circle cx="{WIFI_CX}" cy="{WIFI_CY}" r="{DOT_R}" '
f'fill="{active_color}"/>'
)
else:
parts.append(
f' <circle cx="{WIFI_CX}" cy="{WIFI_CY}" r="{DOT_R}" '
f'fill="{MUTED}" opacity="0.45"/>'
)
return "\n".join(parts)
# ── ethernet plug (RJ45) ────────────────────────────────────────────
# Sized to fill ~75% of the viewBox vertically (y=2→14) and horizontally
# (x=2→14) so the rendered icon matches the height of neighboring tray
# icons (shield, notification, bluetooth) instead of looking like a tiny
# crate in the middle of the slot.
def ethernet_plug(color: str = GOOD, opacity: float = 1.0) -> str:
return (
f' <g stroke="{color}" stroke-width="{SW}" stroke-linecap="round" '
f'stroke-linejoin="round" fill="none" opacity="{opacity}">\n'
# Pentagonal RJ45 outline
' <path d="M 2 2 L 14 2 L 14 10.5 L 12 14 L 4 14 L 2 10.5 Z"/>\n'
# Three pins, spread wider to match the bigger body
' <line x1="5" y1="4.5" x2="5" y2="8.5"/>\n'
' <line x1="8" y1="4.5" x2="8" y2="8.5"/>\n'
' <line x1="11" y1="4.5" x2="11" y2="8.5"/>\n'
" </g>"
)
# ── globe (generic network) ─────────────────────────────────────────
def globe(color: str = BASE, opacity: float = 1.0) -> str:
return (
f' <g stroke="{color}" stroke-width="{SW}" stroke-linecap="round" '
f'stroke-linejoin="round" fill="none" opacity="{opacity}">\n'
' <circle cx="8" cy="8" r="5.5"/>\n'
' <ellipse cx="8" cy="8" rx="2.5" ry="5.5"/>\n'
' <line x1="2.5" y1="8" x2="13.5" y2="8"/>\n'
" </g>"
)
# ── key (vpn) ───────────────────────────────────────────────────────
def vpn_key(color: str = GOOD, opacity: float = 1.0) -> str:
"""Horizontal key: round bow on the left, shaft to the right with
two teeth hanging down. Stroke-only so it doesn't visually dominate
the way the filled globe did."""
return (
f' <g stroke="{color}" stroke-width="1.7" stroke-linecap="round" '
f'stroke-linejoin="round" fill="none" opacity="{opacity}">\n'
# Bow (handle) — outline circle on the left
' <circle cx="4.6" cy="7" r="2.7"/>\n'
# Shaft
' <path d="M 7.3 7 H 14"/>\n'
# Two teeth pointing down
' <path d="M 11.7 7 V 9.6 M 13.6 7 V 8.7"/>\n'
" </g>"
)
# ── state overlays / badges ─────────────────────────────────────────
# nm-tray's MultiIconDelegate stacks state icons on top of the signal-
# level base, so each overlay icon is *just the badge* — no wifi fan,
# no globe. That way stacking signal-good + encrypted gives fan + lock
# instead of fan + fan + lock, and adding -disconnected on top of an
# unsaved network adds a single X instead of another fan.
def standalone_lock(color: str = LOCK) -> str:
"""Centered padlock filling most of the 16x16 box. This is the icon
nm-tray actually loads for encryption — it requests `security-high`
/ `security-high-symbolic` (NOT `network-wireless-encrypted`) and
overlays it on top of a `network-wireless-signal-*` base via its
MultiIconDelegate, scaling our full-size lock into a corner badge.
Keyhole detail intentionally omitted: at the corner-badge render
size nm-tray uses, it's invisible anyway, and a dark keyhole on a
white body reads as visual noise rather than as a lock detail."""
return (
# Body: 8.5w x 6h, centered horizontally, sitting low
f' <rect x="3.75" y="7.5" width="8.5" height="6.5" rx="1.0" '
f'fill="{color}"/>\n'
# Shackle: pronounced U above the body
f' <path d="M 5.5 7.5 V 4.5 a 2.5 2.5 0 0 1 5 0 V 7.5" '
f'fill="none" stroke="{color}" stroke-width="1.8" '
f'stroke-linecap="round" stroke-linejoin="round"/>'
)
def badge_lock() -> str:
"""Lock badge: body 4.4w × 3.8h + shackle 3w × 2h on top, lower-right
corner. Made deliberately larger and with a thicker shackle stroke
than a typical badge — at 16-24px render, a small thin-stroke lock
blurs into a shield-like rounded shape. The big-shackle proportions
here keep the lock recognizable down to ~18px icon size."""
return (
' <g transform="translate(9.5 9.0)">\n'
f' <rect x="0" y="3.0" width="4.4" height="3.8" rx="0.45" '
f'fill="{LOCK}"/>\n'
f' <path d="M 0.7 3.0 V 1.1 a 1.5 1.5 0 0 1 3.0 0 V 3.0" '
f'fill="none" stroke="{LOCK}" stroke-width="1.15" '
f'stroke-linecap="round" stroke-linejoin="round"/>\n'
" </g>"
)
def badge_x(color: str = ERROR) -> str:
return (
f' <g stroke="{color}" stroke-width="1.4" stroke-linecap="round" '
'transform="translate(10 10)">\n'
' <line x1="0" y1="0" x2="4.5" y2="4.5"/>\n'
' <line x1="4.5" y1="0" x2="0" y2="4.5"/>\n'
" </g>"
)
def badge_dots(color: str = WARN) -> str:
return (
f' <g fill="{color}">\n'
' <circle cx="10.5" cy="13" r="0.9"/>\n'
' <circle cx="13" cy="13" r="0.9"/>\n'
' <circle cx="15.5" cy="13" r="0.9"/>\n'
" </g>"
)
def badge_excl(color: str = ERROR) -> str:
return (
f' <g fill="{color}" stroke="none">\n'
' <rect x="11.4" y="9" width="1.4" height="3" rx="0.5"/>\n'
' <circle cx="12.1" cy="13.5" r="0.85"/>\n'
" </g>"
)
def badge_slash(color: str = MUTED) -> str:
return (
f' <line x1="2.5" y1="13.5" x2="13.5" y2="2.5" '
f'stroke="{color}" stroke-width="1.6" stroke-linecap="round"/>'
)
def badge_arrow_up(color: str = GOOD) -> str:
return (
f' <path d="M 12.5 14 V 9 M 10.5 11 L 12.5 9 L 14.5 11" '
f'fill="none" stroke="{color}" stroke-width="1.4" '
'stroke-linecap="round" stroke-linejoin="round"/>'
)
def badge_arrow_down(color: str = GOOD) -> str:
return (
f' <path d="M 3.5 9 V 14 M 1.5 12 L 3.5 14 L 5.5 12" '
f'fill="none" stroke="{color}" stroke-width="1.4" '
'stroke-linecap="round" stroke-linejoin="round"/>'
)
# ── icon catalog ────────────────────────────────────────────────────
def cell_tech(label: str) -> str:
"""Wifi fan + tiny generation-tech label (2G/3G/4G/5G/E/G/H/U/1x)."""
return svg(
wifi_fan(3) + "\n"
f' <text x="11" y="13" font-family="sans-serif" font-size="4.5" '
f'font-weight="bold" fill="{BASE}">{label}</text>'
)
ICONS = {
# wireless signal levels
"network-wireless-signal-none": svg(wifi_fan(0)),
"network-wireless-signal-weak": svg(wifi_fan(1)),
"network-wireless-signal-ok": svg(wifi_fan(2)),
"network-wireless-signal-good": svg(wifi_fan(3)),
"network-wireless-signal-excellent": svg(wifi_fan(4)),
# wireless — nm-tray picks one icon per row, so each name is a
# self-contained composite (signal-level fan + state badge if any).
"network-wireless": svg(wifi_fan(3)),
"network-wireless-connected": svg(wifi_fan(4, GOOD)),
# Disconnected: faded-empty fan. NO X badge — nm-tray overlays this
# on top of saved-network rows, so an X here would visually pile up
# with the encrypted-lock overlay. The dim level-0 fan already reads
# as "no signal/disconnected" without the extra badge.
"network-wireless-disconnected": svg(wifi_fan(0)),
"network-wireless-disabled": svg(wifi_fan(0) + "\n" + badge_slash()),
"network-wireless-acquiring": svg(wifi_fan(2) + "\n" + badge_dots()),
"network-wireless-error": svg(wifi_fan(2) + "\n" + badge_excl()),
"network-wireless-no-route": svg(wifi_fan(3) + "\n" + badge_excl(WARN)),
"network-wireless-offline": svg(wifi_fan(0)),
"network-wireless-encrypted": svg(wifi_fan(3) + "\n" + badge_lock()),
"network-wireless-hotspot": svg(wifi_fan(4, GOOD) + "\n" + badge_dots(GOOD)),
"network-wireless-hardware-disabled": svg(wifi_fan(0) + "\n" + badge_slash(ERROR)),
# wired
"network-wired": svg(ethernet_plug()),
"network-wired-disconnected": svg(ethernet_plug(MUTED, 0.55) + "\n" + badge_x()),
"network-wired-acquiring": svg(ethernet_plug() + "\n" + badge_dots()),
"network-wired-error": svg(ethernet_plug() + "\n" + badge_excl()),
"network-wired-no-route": svg(ethernet_plug() + "\n" + badge_excl(WARN)),
"network-wired-offline": svg(ethernet_plug(MUTED, 0.55)),
# vpn — green key. Stroke-only so it doesn't dominate the menu row.
"network-vpn": svg(vpn_key()),
"network-vpn-connected": svg(vpn_key()),
"network-vpn-acquiring": svg(vpn_key() + "\n" + badge_dots()),
"network-vpn-disconnected": svg(vpn_key(MUTED, 0.55) + "\n" + badge_x()),
"network-vpn-error": svg(vpn_key(ERROR)),
"network-vpn-no-route": svg(vpn_key() + "\n" + badge_excl(WARN)),
"network-vpn-offline": svg(vpn_key(MUTED, 0.55)),
# generic
"network-error": svg(globe() + "\n" + badge_excl()),
"network-idle": svg(globe()),
"network-offline": svg(globe(MUTED, 0.55) + "\n" + badge_slash()),
"network-no-route": svg(globe() + "\n" + badge_excl(WARN)),
"network-acquiring": svg(globe() + "\n" + badge_dots()),
"network-transmit": svg(globe() + "\n" + badge_arrow_up()),
"network-receive": svg(globe() + "\n" + badge_arrow_down()),
"network-transmit-receive": svg(globe() + "\n" + badge_arrow_up() + "\n" + badge_arrow_down()),
# cellular — same fan, with state badges
"network-cellular-signal-none": svg(wifi_fan(0)),
"network-cellular-signal-weak": svg(wifi_fan(1)),
"network-cellular-signal-ok": svg(wifi_fan(2)),
"network-cellular-signal-good": svg(wifi_fan(3)),
"network-cellular-signal-excellent": svg(wifi_fan(4)),
"network-cellular-acquiring": svg(wifi_fan(2) + "\n" + badge_dots()),
"network-cellular-connected": svg(wifi_fan(4, GOOD)),
"network-cellular-disabled": svg(wifi_fan(0) + "\n" + badge_slash()),
"network-cellular-disconnected": svg(wifi_fan(0) + "\n" + badge_x()),
"network-cellular-error": svg(wifi_fan(2) + "\n" + badge_excl()),
"network-cellular-hardware-disabled": svg(wifi_fan(0) + "\n" + badge_slash(ERROR)),
"network-cellular-no-route": svg(wifi_fan(3) + "\n" + badge_excl(WARN)),
"network-cellular-offline": svg(wifi_fan(0)),
# security indicators — nm-tray's actual encryption overlay icon
"security-high": svg(standalone_lock()),
"security-high-symbolic": svg(standalone_lock()),
"security-medium": svg(standalone_lock(WARN)),
"security-medium-symbolic": svg(standalone_lock(WARN)),
"security-low": svg(standalone_lock(ERROR)),
"security-low-symbolic": svg(standalone_lock(ERROR)),
# cellular generation-tech labels
"network-cellular-2g": cell_tech("2G"),
"network-cellular-3g": cell_tech("3G"),
"network-cellular-4g": cell_tech("4G"),
"network-cellular-5g": cell_tech("5G"),
"network-cellular-edge": cell_tech("E"),
"network-cellular-gprs": cell_tech("G"),
"network-cellular-cdma-1x": cell_tech("1x"),
"network-cellular-hspa": cell_tech("H"),
"network-cellular-umts": cell_tech("U"),
}
# ── genmon panel applet PNGs ─────────────────────────────────────────
# bin/panel/network-applet.sh (the XFCE genmon network applet) loads PNGs
# directly from assets/panel-icons/ by path — it does NOT go through the
# icon theme. Rasterize the relevant status icons there too so the applet
# stays in sync with the canonical SVG design above (otherwise the PNGs
# drift into a different look, e.g. solid filled cones vs. arc bars).
PANEL_PNG_DIR = ROOT.parent.parent / "panel-icons" # assets/panel-icons
PANEL_PNG_SIZE = 22
PANEL_PNG_NAMES = [
"network-wireless-signal-none",
"network-wireless-signal-weak",
"network-wireless-signal-ok",
"network-wireless-signal-good",
"network-wireless-signal-excellent",
"network-wired",
"network-offline",
]
def _rasterize_panel_pngs() -> int:
import subprocess
if not PANEL_PNG_DIR.is_dir():
return 0
n = 0
for name in PANEL_PNG_NAMES:
svg_path = OUT / f"{name}.svg"
if not svg_path.exists():
continue
subprocess.run(
["inkscape", str(svg_path), "--export-type=png",
f"--export-filename={PANEL_PNG_DIR / (name + '.png')}",
f"--export-width={PANEL_PNG_SIZE}",
f"--export-height={PANEL_PNG_SIZE}",
"--export-background-opacity=0"],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
n += 1
return n
def main() -> None:
OUT.mkdir(parents=True, exist_ok=True)
# Sweep any leftover Papirus fallback symlinks from the same dir.
cleaned = 0
for entry in OUT.iterdir():
if entry.is_symlink():
entry.unlink()
cleaned += 1
for name, body in ICONS.items():
(OUT / f"{name}.svg").write_text(body)
print(f"Cleared {cleaned} symlinks, wrote {len(ICONS)} SVGs to {OUT}")
n = _rasterize_panel_pngs()
print(f"Rasterized {n} genmon panel PNGs to {PANEL_PNG_DIR}")
if __name__ == "__main__":
main()
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<defs>
<radialGradient id="ring" cx="50%" cy="30%" r="70%">
<stop offset="0%" stop-color="#8cc63f"/>
<stop offset="100%" stop-color="#5a9020"/>
</radialGradient>
<radialGradient id="frame" cx="50%" cy="30%" r="70%">
<stop offset="0%" stop-color="#b040c0"/>
<stop offset="100%" stop-color="#4a0050"/>
</radialGradient>
</defs>
<!-- Lime outer ring -->
<rect x="0" y="0" width="128" height="128" rx="30" fill="url(#ring)"/>
<!-- Purple gradient frame inset 8px from ring -->
<rect x="8" y="8" width="112" height="112" rx="26" fill="url(#frame)"/>
<!-- Dark inner border -->
<rect x="12" y="12" width="104" height="104" rx="22" fill="#120018"/>
<!-- App icon face -->
<rect x="18" y="18" width="92" height="92" rx="16" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 933 B

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
<defs>
<radialGradient id="frame" cx="50%" cy="30%" r="70%">
<stop offset="0%" stop-color="#b040c0"/>
<stop offset="100%" stop-color="#4a0050"/>
</radialGradient>
</defs>
<!-- Outer purple gradient frame -->
<rect x="0" y="0" width="128" height="128" rx="30" fill="url(#frame)"/>
<!-- Dark inner border -->
<rect x="8" y="8" width="112" height="112" rx="24" fill="#120018"/>
<!-- App icon face -->
<rect x="14" y="14" width="100" height="100" rx="18" fill="#262626"/>
</svg>

After

Width:  |  Height:  |  Size: 642 B

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Document with folded corner -->
<path d="M 48 58 L 72 58 L 80 66 L 80 98 L 48 98 Z"
fill="#f2f2f2"/>
<path d="M 72 58 L 72 66 L 80 66 Z" fill="#cccccc"/>
<rect x="52" y="72" width="24" height="2" fill="#5e0066"/>
<rect x="52" y="78" width="24" height="2" fill="#5e0066"/>
<rect x="52" y="84" width="20" height="2" fill="#5e0066"/>
<rect x="52" y="90" width="24" height="2" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Down arrow over a tray -->
<rect x="60" y="56" width="8" height="22" fill="#f2f2f2"/>
<path d="M 50 76 L 78 76 L 64 92 Z" fill="#f2f2f2"/>
<rect x="48" y="96" width="32" height="4" rx="1" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 994 B

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Folder back, same as closed -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face, tilted forward — wider at bottom, narrower at top -->
<path d="M 22 44 L 106 44 L 118 110 Q 118 114 114 114
L 14 114 Q 10 114 10 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on tilted front face -->
<path d="M 22 44 L 106 44 L 106.5 46 L 21.5 46 Z" fill="#88008f"/>
</svg>

After

Width:  |  Height:  |  Size: 802 B

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- House silhouette -->
<path d="M 64 56 L 88 76 L 84 76 L 84 96 L 70 96 L 70 84
L 58 84 L 58 96 L 44 96 L 44 76 L 40 76 Z"
fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 942 B

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Eighth note -->
<rect x="68" y="58" width="4" height="32" fill="#f2f2f2"/>
<path d="M 68 58 L 84 62 L 84 70 L 72 66 Z" fill="#f2f2f2"/>
<ellipse cx="62" cy="92" rx="9" ry="6" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 981 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 247 KiB

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Folder back, same as closed -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face, tilted forward — wider at bottom, narrower at top -->
<path d="M 22 44 L 106 44 L 118 110 Q 118 114 114 114
L 14 114 Q 10 114 10 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on tilted front face -->
<path d="M 22 44 L 106 44 L 106.5 46 L 21.5 46 Z" fill="#88008f"/>
</svg>

After

Width:  |  Height:  |  Size: 802 B

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Landscape: sun + mountains in a frame -->
<rect x="44" y="58" width="40" height="40" rx="2" fill="#f2f2f2"/>
<circle cx="74" cy="68" r="4" fill="#8cc63f"/>
<path d="M 46 96 L 60 78 L 70 88 L 78 80 L 82 96 Z" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 1014 B

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Three small people silhouettes -->
<circle cx="52" cy="68" r="6" fill="#f2f2f2"/>
<path d="M 42 92 Q 42 78 52 78 Q 62 78 62 92 Z" fill="#f2f2f2"/>
<circle cx="76" cy="68" r="6" fill="#f2f2f2"/>
<path d="M 66 92 Q 66 78 76 78 Q 86 78 86 92 Z" fill="#f2f2f2"/>
<circle cx="64" cy="60" r="5" fill="#f2f2f2"/>
<path d="M 56 76 Q 56 66 64 66 Q 72 66 72 76 Z" fill="#f2f2f2"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Clock face -->
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
<circle cx="64" cy="80" r="20" fill="none" stroke="#5e0066" stroke-width="2"/>
<rect x="63" y="66" width="2" height="16" fill="#5e0066"/>
<rect x="63" y="79" width="14" height="2" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Globe / network -->
<circle cx="64" cy="80" r="20" fill="#f2f2f2"/>
<ellipse cx="64" cy="80" rx="20" ry="8" fill="none" stroke="#5e0066" stroke-width="1.5"/>
<ellipse cx="64" cy="80" rx="8" ry="20" fill="none" stroke="#5e0066" stroke-width="1.5"/>
<line x1="44" y1="80" x2="84" y2="80" stroke="#5e0066" stroke-width="1.5"/>
<line x1="64" y1="60" x2="64" y2="100" stroke="#5e0066" stroke-width="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Magnifying glass -->
<circle cx="58" cy="74" r="12" fill="none" stroke="#f2f2f2" stroke-width="4"/>
<line x1="68" y1="84" x2="82" y2="98" stroke="#f2f2f2" stroke-width="5" stroke-linecap="round"/>
</svg>

After

Width:  |  Height:  |  Size: 984 B

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Pencil over a page -->
<path d="M 50 60 L 70 60 L 78 68 L 78 96 L 50 96 Z" fill="#f2f2f2"/>
<path d="M 70 60 L 70 68 L 78 68 Z" fill="#cccccc"/>
<!-- Pencil at 45deg -->
<path d="M 60 88 L 80 68 L 84 72 L 64 92 Z" fill="#8cc63f"/>
<path d="M 60 88 L 64 92 L 58 94 Z" fill="#5e0066"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
<!-- Filmstrip with play triangle -->
<rect x="44" y="60" width="40" height="36" rx="2" fill="#f2f2f2"/>
<rect x="44" y="60" width="40" height="4" fill="#5e0066"/>
<rect x="44" y="92" width="40" height="4" fill="#5e0066"/>
<rect x="46" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="56" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="66" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="76" y="60" width="4" height="4" fill="#f2f2f2"/>
<rect x="46" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="56" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="66" y="92" width="4" height="4" fill="#f2f2f2"/>
<rect x="76" y="92" width="4" height="4" fill="#f2f2f2"/>
<path d="M 58 70 L 74 78 L 58 86 Z" fill="#8cc63f"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128" viewBox="0 0 128 128">
<!-- Back of folder: tab + body silhouette in green -->
<path d="M 14 24 Q 14 20 18 20 L 50 20 Q 53 20 55 22 L 62 30
L 110 30 Q 114 30 114 34 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#8cc63f"/>
<!-- White inner lip stripe -->
<rect x="14" y="36" width="100" height="3" fill="#f2f2f2"/>
<!-- Front face: deep purple body -->
<path d="M 14 39 L 114 39 L 114 110 Q 114 114 110 114
L 18 114 Q 14 114 14 110 Z" fill="#5e0066"/>
<!-- Top-edge highlight on the front face -->
<rect x="14" y="39" width="100" height="2" fill="#88008f"/>
</svg>

After

Width:  |  Height:  |  Size: 776 B