Initial commit: NexusOS project + desktop theme baseline

Captures the known-good state after theme consolidation and
consistency reconciliation:
- NexusOS GTK/xfwm4/icon theme assets (assets/themes, management/Mint-Y-Nexus)
- Tightened right-click menus, visible separators, no menu icons
- assets/themes/install-theme.sh: idempotent restore of all wiring
  (symlinks, xfconf xsettings+xfwm4, GTK 3/4 settings.ini)
- .gitignore excludes venv/ollama/models/runtime/db

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jon
2026-05-18 13:39:48 -05:00
commit b0aa0438af
1264 changed files with 19255 additions and 0 deletions
@@ -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())