diff --git a/README.md b/README.md
index a9b09a1..625a11d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,119 @@
-# webpaper
+# Webpaper
-Pagine web come sfondi animati
\ No newline at end of file
+Pagina web come sfondo del desktop. Ispirato a
+[Hidamari](https://github.com/jeffshee/hidamari), dal quale è stato estratto e
+ridotto all'essenziale il meccanismo della pagina web come wallpaper: niente
+server DBus, niente video, niente GUI di controllo — solo lo sfondo.
+
+## Come funziona
+
+1. Una `Gtk.ApplicationWindow` piena di una `WebKit2.WebView` (WebKitGTK 4.1)
+ carica l'URI richiesto;
+2. la finestra viene marcata con `Gdk.WindowTypeHint.DESKTOP`: il window
+ manager la tiene in fondo allo stack, sotto tutte le altre finestre,
+ esattamente come lo sfondo nativo;
+3. viene creata una finestra per ogni monitor, dimensionata e posizionata
+ sulla geometria del monitor (`monitor.get_geometry()`), con aggiornamento
+ automatico su eventi `monitor-added` / `monitor-removed` / `size-changed`;
+4. il backend è forzato a **X11** (`GDK_BACKEND=x11`): le finestre DESKTOP non
+ funzionano su Wayland nativo, quindi su una sessione Wayland GDK ricade su
+ XWayland;
+5. workaround NVIDIA: se è presente una GPU NVIDIA, WebKit viene puntato su un
+ render node Mesa o su buffer SHM (il renderer DMA-BUF di WebKitGTK fallisce
+ sui driver NVIDIA e la pagina resterebbe bianca).
+
+## Requisiti
+
+- Python 3 con PyGObject
+- GTK 3
+- WebKitGTK 4.1
+
+Debian/Ubuntu:
+
+```sh
+sudo apt install python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1
+```
+
+Arch:
+
+```sh
+sudo pacman -S python-gobject gtk3 webkit2gtk-4.1
+```
+
+## Uso
+
+```sh
+# da questa cartella, un file HTML locale:
+python3 -m webpaper /percorso/pagina.html
+
+# oppure un URL:
+python3 -m webpaper https://esempio.com
+
+# DUAL MONITOR: una pagina per monitor (prima sorgente = primo monitor,
+# seconda = secondo monitor):
+python3 -m webpaper /percorso/pagina1.html /percorso/pagina2.html
+
+# log di debug:
+python3 -m webpaper -d /percorso/pagina.html
+
+# non sistemare le icone desktop (le icone restano sotto il wallpaper):
+python3 -m webpaper --no-fix-icons /percorso/pagina.html
+```
+
+Con **una sola** sorgente, viene mostrata su tutti i monitor (comportamento
+originale). Con **più sorgenti** la mappatura è 1:1 nell'ordine dei monitor;
+se i monitor sono più delle sorgenti, gli extra ripetono l'ultima sorgente, se
+le sorgenti superano i monitor quelle in eccesso vengono ignorate (con un
+warning nel log).
+
+In alternativa, installabile come comando:
+
+```sh
+pip install -e .
+webpaper /percorso/pagina.html
+```
+
+Per uscire: click destro sulla pagina → **Esci** (oppure `Ctrl+C`).
+
+## Autostart al login
+
+Crea il file `~/.config/autostart/webpaper.desktop` con:
+
+```ini
+[Desktop Entry]
+Type=Application
+Name=Webpaper
+Comment=Pagina web come sfondo del desktop
+Exec=sh -c "cd /percorso/del/progetto && sleep 3 && exec python3 -m webpaper /percorso/pagina.html"
+Terminal=false
+X-GNOME-Autostart-enabled=true
+X-GNOME-Autostart-Delay=0
+```
+
+Nota: il `sleep 3` è importante — dà tempo a `nemo-desktop` (le icone della
+scrivania) di partire per primo. Se webpaper parte prima, nemo-desktop si
+rifiuta di avviarsi ("Desktop already managed") e le icone spariscono del
+tutto.
+
+Per disattivare l'autostart: elimina il file oppure toglilo da *Impostazioni
+di sistema → Applicazioni all'avvio*.
+
+## Note
+
+- Il meccanismo funziona su **X11 e XWayland** (Wayland puro non è supportato,
+ come in Hidamari).
+- **Icone della scrivania**: le estensioni che disegnano le icone desktop
+ (nemo-desktop su Cinnamon, DING su GNOME) usano a loro volta finestre di
+ tipo DESKTOP; il nostro wallpaper, essendo mappato dopo, le copre. All'avvio
+ Webpaper le porta automaticamente sopra il wallpaper (attivandole una a una,
+ via `xdotool`). Requisito: `xdotool` installato (`sudo apt install xdotool`
+ oppure `sudo pacman -S xdotool`). Se non è presente, o con
+ `--no-fix-icons`, le icone restano sotto il wallpaper.
+- La pagina è un vero browser (WebKitGTK): funzionano animazioni CSS,
+ canvas/WebGL, iframe, ecc. L'audio della pagina viene riprodotto; per
+ mutarlo non c'è un'API GTK semplice (in Hidamari è un TODO).
+
+## Licenza
+
+GPL-3.0-or-later, coerente con il progetto originale
+[Hidamari](https://github.com/jeffshee/hidamari) dal quale deriva.
diff --git a/install.sh b/install.sh
new file mode 100755
index 0000000..35f5747
--- /dev/null
+++ b/install.sh
@@ -0,0 +1,202 @@
+#!/usr/bin/env bash
+# install.sh - Script di installazione per Webpaper
+# Requisiti: bash, pip3, xrandr (solitamente preinstallato)
+
+# --- Colori ---
+RED='\033[0;31m'
+GREEN='\033[0;32m'
+YELLOW='\033[1;33m'
+NC='\033[0m'
+
+# --- Variabili ---
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SOLE_HTML="$SCRIPT_DIR/sistema_stellare/sole.html"
+ALPHA_HTML="$SCRIPT_DIR/sistema_stellare/alpha-centauri.html"
+AUTOSTART_DIR="$HOME/.config/autostart"
+
+log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
+log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
+log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
+
+# --- 1. Rilevamento Distribuzione ---
+detect_distro() {
+ if [ -f /etc/os-release ]; then
+ . /etc/os-release
+ echo "$ID"
+ else
+ echo "unknown"
+ fi
+}
+
+DISTRO=$(detect_distro)
+
+# --- 2. Installazione Dipendenze (corretta per tutte le distro) ---
+install_deps() {
+ log_info "Rilevata distribuzione: $DISTRO"
+
+ local sudo_cmd=""
+ if [ "$(id -u)" -ne 0 ]; then
+ sudo_cmd="sudo"
+ fi
+
+ # Definizione dei pacchetti per ogni famiglia
+ declare -A pkg_map
+ pkg_map[ubuntu]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl"
+ pkg_map[debian]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl"
+ pkg_map[linuxmint]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl"
+ pkg_map[pop]="python3-gi gir1.2-gtk-3.0 gir1.2-webkit2-4.1 xdotool wmctrl"
+ pkg_map[arch]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl"
+ pkg_map[manjaro]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl"
+ pkg_map[endeavouros]="python-gobject gtk3 webkit2gtk-4.1 xdotool wmctrl"
+ pkg_map[fedora]="python3-gobject gtk3 webkit2gtk4.1 xdotool wmctrl"
+ pkg_map[rhel]="python3-gobject gtk3 webkit2gtk4.1 xdotool wmctrl"
+
+ local packages=(${pkg_map[$DISTRO]})
+ if [ ${#packages[@]} -eq 0 ]; then
+ log_warn "Distribuzione sconosciuta ($DISTRO). Installa manualmente:"
+ log_warn "- python3-gi (PyGObject)"
+ log_warn "- GTK3"
+ log_warn "- WebKit2GTK 4.1"
+ log_warn "- xdotool"
+ return 0
+ fi
+
+ # Verifica pacchetti installati
+ local missing=()
+ for pkg in "${packages[@]}"; do
+ local installed=false
+ case "$DISTRO" in
+ arch|manjaro|endeavouros)
+ if pacman -Q "$pkg" &>/dev/null; then installed=true; fi
+ ;;
+ fedora|rhel)
+ if rpm -q "$pkg" &>/dev/null; then installed=true; fi
+ ;;
+ *)
+ if dpkg -s "$pkg" &>/dev/null; then installed=true; fi
+ ;;
+ esac
+ if [ "$installed" = false ]; then
+ missing+=("$pkg")
+ fi
+ done
+
+ if [ ${#missing[@]} -eq 0 ]; then
+ log_info "Tutte le dipendenze di sistema sono installate."
+ return 0
+ fi
+
+ log_warn "Pacchetti mancanti: ${missing[*]}"
+ log_info "Installazione in corso..."
+
+ case "$DISTRO" in
+ arch|manjaro|endeavouros)
+ $sudo_cmd pacman -S --noconfirm "${missing[@]}"
+ ;;
+ fedora|rhel)
+ $sudo_cmd dnf install -y "${missing[@]}"
+ ;;
+ *)
+ $sudo_cmd apt-get update && $sudo_cmd apt-get install -y "${missing[@]}"
+ ;;
+ esac
+
+ if [ $? -ne 0 ]; then
+ log_error "Errore durante l'installazione delle dipendenze."
+ exit 1
+ fi
+}
+
+# --- 3. Installazione Python ---
+install_python_pkg() {
+ log_info "Installazione pacchetto Python..."
+
+ if ! command -v pip3 &>/dev/null && ! command -v pip &>/dev/null; then
+ log_error "pip non trovato. Installalo prima."
+ exit 1
+ fi
+
+ # Usa pip3 se disponibile, altrimenti pip
+ local pip_cmd="pip3"
+ if ! command -v pip3 &>/dev/null; then
+ pip_cmd="pip"
+ fi
+
+ $pip_cmd install -e "$SCRIPT_DIR" --user
+ if [ $? -ne 0 ]; then
+ log_error "Errore installazione pacchetto Python."
+ exit 1
+ fi
+ log_info "Pacchetto Python installato."
+}
+
+# --- 4. Rilevamento Monitor ---
+detect_monitors() {
+ local count
+ count=$(xrandr --query 2>/dev/null | grep " connected" | wc -l)
+ if [ "$count" -eq 0 ]; then
+ log_warn "Impossibile rilevare monitor con xrandr. Imposto 1 monitor."
+ echo 1
+ else
+ echo "$count"
+ fi
+}
+
+# --- 5. Configurazione Autostart (CORRETTA) ---
+setup_autostart() {
+ read -p "Vuoi aggiungere Webpaper all'avvio automatico? (y/N): " answer
+ if [[ ! "$answer" =~ ^[Yy]$ ]]; then
+ log_info "Autostart saltato."
+ return
+ fi
+
+ mkdir -p "$AUTOSTART_DIR"
+
+ local monitor_count=$(detect_monitors)
+ local exec_cmd=""
+ if [ "$monitor_count" -ge 2 ]; then
+ exec_cmd="python3 -m webpaper \"$SOLE_HTML\" \"$ALPHA_HTML\""
+ log_info "Rilevati $monitor_count monitor. Configurerò Webpaper per mostrare:"
+ log_info " - Monitor 1: Sole.html"
+ log_info " - Monitor 2: Alpha-Centauri.html"
+ else
+ exec_cmd="python3 -m webpaper \"$SOLE_HTML\""
+ log_info "Rilevato $monitor_count monitor. Configurerò Webpaper per mostrare:"
+ log_info " - Monitor 1: Sole.html"
+ fi
+
+ # IMPORTANTE: qui correggiamo l'here-document e le virgolette
+ cat > "$AUTOSTART_DIR/webpaper.desktop" <<-EOF
+[Desktop Entry]
+Type=Application
+Name=Webpaper
+Comment=Pagina web come sfondo del desktop
+Exec=sh -c "cd \"$SCRIPT_DIR\" && sleep 3 && exec $exec_cmd"
+Terminal=false
+X-GNOME-Autostart-enabled=true
+X-GNOME-Autostart-Delay=0
+Hidden=false
+NoDisplay=false
+EOF
+
+ log_info "File creato in: $AUTOSTART_DIR/webpaper.desktop"
+}
+
+# --- Esecuzione ---
+main() {
+ echo "========================================"
+ echo " Webpaper Installer"
+ echo "========================================"
+
+ install_deps
+ install_python_pkg
+ setup_autostart
+
+ echo "========================================"
+ log_info "Installazione completata!"
+ echo "========================================"
+ echo "Per avviare manualmente: webpaper"
+ echo "Oppure: python3 -m webpaper"
+}
+
+main
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..a2f28b6
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,17 @@
+[build-system]
+requires = ["setuptools>=61"]
+build-backend = "setuptools.build_meta"
+
+[project]
+name = "webpaper"
+version = "0.1.0"
+description = "Pagina web come sfondo del desktop (ispirato a Hidamari)"
+readme = "README.md"
+requires-python = ">=3.8"
+license = { text = "GPL-3.0-or-later" }
+
+[project.scripts]
+webpaper = "webpaper.__main__:main"
+
+[tool.setuptools]
+packages = ["webpaper"]
diff --git a/sistema_stellare/alpha-centauri.html b/sistema_stellare/alpha-centauri.html
new file mode 100644
index 0000000..c04bbfe
--- /dev/null
+++ b/sistema_stellare/alpha-centauri.html
@@ -0,0 +1,380 @@
+
+
+
+
+
+ Alpha Centauri
+
+
+
+
+ Alpha Centauri — Simulazione
+
+
+
+
diff --git a/sistema_stellare/alpha-centauri.ts b/sistema_stellare/alpha-centauri.ts
new file mode 100644
index 0000000..0195354
--- /dev/null
+++ b/sistema_stellare/alpha-centauri.ts
@@ -0,0 +1,445 @@
+// === SISTEMA ALPHA CENTAURI — Simulazione TypeScript ===
+// Sistema triplo: Alpha Cen A + Alpha Cen B + Proxima Centauri
+// Distanza totale: 4.37 anni luce dalla Terra
+
+interface CorpoCeleste {
+ nome: string;
+ raggio: number;
+ asseMaggiore: number;
+ eccentricita: number;
+ velocita: number;
+ colore: string;
+ angoli?: number[];
+ raggiMondi?: number[];
+ coloriMondi?: string[];
+ nomiMondi?: string[];
+ velocitaMondi?: number[];
+ anelli?: boolean;
+ dettagli?: Dettaglio[];
+}
+
+interface Dettaglio {
+ tipo: string;
+ colore: string;
+ x?: number;
+ y?: number;
+ r?: number;
+}
+
+const canvas = document.getElementById("alpha") as HTMLCanvasElement;
+const ctx = canvas.getContext("2d")!;
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+// === DATI ASTROFISICI ===
+// Alpha Centauri A (Rigil Kentaurus): G2V, 1.1 M☉, 1.221 R☉
+// Alpha Centauri B (Toliman): K1V, 0.907 M☉, 0.863 R☉
+// Orbita A-B: a=23.4 UA, e=0.517, P=79.91 anni
+// Proxima Centauri: M5.5Ve, 0.122 M☉
+// Orbita Proxima: a~125 UA, e~0.509, P~547 anni
+
+// === PIANETI CONFERMATI ===
+// Alpha Cen Bb: 0.011 M⊕, 3.236 giorni, 0.064 UA (controverso)
+// Alpha Cen Ab: 1.1 M⊕, 224 giorni, ~1.25 UA (confermato HARPS 2012)
+// Proxima d: 0.26 M⊕, 5.122 giorni, 0.029 UA (2022)
+// Proxima b: 1.07 M⊕, 11.186 giorni, 0.0485 UA (2016)
+// Proxima c: ~7 M⊕, ~5.28 anni, ~1.489 UA (2019)
+
+let tempo = 0;
+
+// Stelle del sistema triplo
+interface Stella {
+ nome: string;
+ tipo: string;
+ colore: string;
+ raggio: number;
+ asseMaggiore: number;
+ eccentricita: number;
+ velocita: number;
+ angoloIniziale: number;
+ massa: string;
+ coloreTesto: string;
+}
+
+const stelle: Stella[] = [
+ {
+ nome: "Alpha Cen A",
+ tipo: "G2V",
+ colore: "#fff4e0",
+ raggio: 18,
+ asseMaggiore: 175,
+ eccentricita: 0.517,
+ velocita: 0.0125, // P=79.91 anni → scala
+ angoloIniziale: 0,
+ massa: "1.1 M☉",
+ coloreTesto: "rgba(255,244,224,0.8)",
+ },
+ {
+ nome: "Alpha Cen B",
+ tipo: "K1V",
+ colore: "#ffcc66",
+ raggio: 14,
+ asseMaggiore: 215,
+ eccentricita: 0.517,
+ velocita: 0.0125,
+ angoloIniziale: Math.PI, // opposto ad A
+ massa: "0.907 M☉",
+ coloreTesto: "rgba(255,204,102,0.8)",
+ },
+ {
+ nome: "Proxima Cen",
+ tipo: "M5.5Ve",
+ colore: "#cc4422",
+ raggio: 10,
+ asseMaggiore: 650,
+ eccentricita: 0.509,
+ velocita: 0.00091, // P=547 anni → scala
+ angoloIniziale: Math.PI * 0.7,
+ massa: "0.122 M☉",
+ coloreTesto: "rgba(255,150,100,0.8)",
+ },
+];
+
+// Pianeti: [stella_ospite, dati_pianeta]
+type Pianeta = [number, CorpoCeleste];
+
+const pianeti: Pianeta[] = [
+ // === SU ALPHA CENTAURI B ===
+ [
+ 1, // Alpha Cen B
+ {
+ nome: "Alpha Cen Ab",
+ raggio: 7,
+ asseMaggiore: 55,
+ eccentricita: 0.02,
+ velocita: 1.63, // 224 giorni → scala: 10s orbita
+ colore: "#5a8ab5",
+ angoli: [0],
+ dettagli: [
+ { tipo: "oceano", colore: "#3a6a9a", x: -2, y: -1, r: 3 },
+ { tipo: "continente", colore: "#4a8a4a", x: 2, y: 1, r: 2.5 },
+ { tipo: "nuvola", colore: "rgba(255,255,255,0.25)", x: 0, y: -2, r: 1.5 },
+ ],
+ },
+ ],
+
+ // === SU PROXIMA CENTAURI ===
+ [
+ 2, // Proxima Centauri
+ {
+ nome: "Proxima d",
+ raggio: 3,
+ asseMaggiore: 30,
+ eccentricita: 0.04,
+ velocita: 2.094, // 10 secondi orbita
+ colore: "#6b7b8d",
+ angoli: [0],
+ dettagli: [
+ { tipo: "superficie", colore: "#5a6a7a", x: -1, y: -1, r: 1.5 },
+ { tipo: "superficie", colore: "#7a8a9a", x: 1, y: 1, r: 1 },
+ ],
+ },
+ ],
+ [
+ 2,
+ {
+ nome: "Proxima b",
+ raggio: 8,
+ asseMaggiore: 50,
+ eccentricita: 0.11,
+ velocita: 0.959, // ~22 secondi orbita
+ colore: "#4a90d9",
+ angoli: [0],
+ dettagli: [
+ { tipo: "oceano", colore: "#2a5a8a", x: -3, y: -2, r: 4 },
+ { tipo: "continente", colore: "#3a7a3a", x: 2, y: 1, r: 3 },
+ { tipo: "continente", colore: "#4a8a4a", x: -1, y: 3, r: 2.5 },
+ { tipo: "nuvola", colore: "rgba(255,255,255,0.3)", x: 0, y: -3, r: 2 },
+ ],
+ },
+ ],
+ [
+ 2,
+ {
+ nome: "Proxima c",
+ raggio: 14,
+ asseMaggiore: 200,
+ eccentricita: 0.04,
+ velocita: 0.00559, // ~62 minuti orbita
+ colore: "#c8944a",
+ angoli: [0],
+ raggiMondi: [35, 55, 75],
+ coloriMondi: ["#d4c4a4", "#b4a484", "#948464"],
+ nomiMondi: ["Candidate-m1", "Candidate-m2", "Candidate-m3"],
+ velocitaMondi: [8, 4, 2.5],
+ anelli: true,
+ dettagli: [
+ { tipo: "banda", colore: "#a8743a", y: -4 },
+ { tipo: "banda", colore: "#b8844a", y: 0 },
+ { tipo: "banda", colore: "#98642a", y: 4 },
+ ],
+ },
+ ],
+];
+
+// === FUNZIONI DI DISEGNO ===
+
+function disegnaStelle() {
+ stelleSfondo.forEach((s) => {
+ ctx.fillStyle = `rgba(255,255,255,${s.a})`;
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
+ ctx.fill();
+ });
+}
+
+function disegnaStella(stella: Stella, x: number, y: number) {
+ // Glow
+ const grad = ctx.createRadialGradient(x, y, stella.raggio * 0.3, x, y, stella.raggio * 4);
+ grad.addColorStop(0, stella.colore);
+ grad.addColorStop(0.3, stella.colore.replace(")", ",0.5)").replace("#", "rgba(").replace(/(..)(..)(..)/, (m, r, g, b) => {
+ const red = parseInt(r, 16);
+ const green = parseInt(g, 16);
+ const blue = parseInt(b, 16);
+ return `rgba(${red},${green},${blue}`;
+ }) + ",0.3)");
+ grad.addColorStop(1, "rgba(0,0,0,0)");
+
+ // Glow semplificato per tipo di stella
+ if (stella.tipo === "G2V") {
+ // Gialla come il Sole
+ const g = ctx.createRadialGradient(x, y, stella.raggio * 0.5, x, y, stella.raggio * 5);
+ g.addColorStop(0, "#fff8e0");
+ g.addColorStop(0.2, "#ffdd44");
+ g.addColorStop(0.5, "rgba(255,221,68,0.2)");
+ g.addColorStop(1, "rgba(255,200,0,0)");
+ ctx.fillStyle = g;
+ } else if (stella.tipo === "K1V") {
+ // Arancione
+ const g = ctx.createRadialGradient(x, y, stella.raggio * 0.5, x, y, stella.raggio * 4);
+ g.addColorStop(0, "#ffe0a0");
+ g.addColorStop(0.2, "#ffaa44");
+ g.addColorStop(0.5, "rgba(255,170,68,0.2)");
+ g.addColorStop(1, "rgba(255,150,0,0)");
+ ctx.fillStyle = g;
+ } else {
+ // Rossa (Proxima)
+ const g = ctx.createRadialGradient(x, y, stella.raggio * 0.3, x, y, stella.raggio * 4);
+ g.addColorStop(0, "rgba(255,100,50,0.8)");
+ g.addColorStop(0.2, "rgba(200,50,20,0.4)");
+ g.addColorStop(0.5, "rgba(150,30,10,0.1)");
+ g.addColorStop(1, "rgba(100,20,5,0)");
+ ctx.fillStyle = g;
+ }
+
+ ctx.beginPath();
+ ctx.arc(x, y, stella.raggio * 5, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Stella
+ ctx.fillStyle = stella.colore;
+ ctx.shadowColor = stella.colore;
+ ctx.shadowBlur = stella.raggio * 2;
+ ctx.beginPath();
+ ctx.arc(x, y, stella.raggio, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.shadowBlur = 0;
+}
+
+function disegnaAnelli(cx: number, cy: number, raggio: number) {
+ ctx.strokeStyle = "rgba(210, 180, 140, 0.4)";
+ ctx.lineWidth = 3;
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, raggio * 2.0, raggio * 0.5, 0, 0, Math.PI * 2);
+ ctx.stroke();
+
+ ctx.strokeStyle = "rgba(190, 160, 120, 0.25)";
+ ctx.lineWidth = 2;
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, raggio * 2.4, raggio * 0.6, 0, 0, Math.PI * 2);
+ ctx.stroke();
+}
+
+function disegnaOrbita(cx: number, cy: number, a: number, b: number) {
+ ctx.strokeStyle = "rgba(255,255,255,0.06)";
+ ctx.lineWidth = 1;
+ ctx.setLineDash([3, 3]);
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, a, b, 0, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+}
+
+function disegnaDettaglioPianeta(
+ dettagli: Dettaglio[],
+ x: number,
+ y: number,
+ raggio: number
+) {
+ dettagli.forEach((d) => {
+ ctx.fillStyle = d.colore;
+ ctx.beginPath();
+ ctx.arc(x + (d.x || 0), y + (d.y || 0), d.r || 1, 0, Math.PI * 2);
+ ctx.fill();
+ });
+}
+
+function disegnaCorpo(
+ c: CorpoCeleste,
+ stellaX: number,
+ stellaY: number,
+ angolo: number
+) {
+ const a = c.asseMaggiore;
+ const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita);
+ const x = stellaX + a * Math.cos(angolo);
+ const y = stellaY + b * Math.sin(angolo);
+
+ // Orbita
+ disegnaOrbita(stellaX, stellaY, a, b);
+
+ // Anelli
+ if (c.anelli) {
+ disegnaAnelli(x, y, c.raggio);
+ }
+
+ // Pianeta
+ ctx.fillStyle = c.colore;
+ ctx.beginPath();
+ ctx.arc(x, y, c.raggio, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Dettagli
+ if (c.dettagli) {
+ disegnaDettaglioPianeta(c.dettagli, x, y, c.raggio);
+ }
+
+ // Lune
+ if (c.raggiMondi && c.coloriMondi) {
+ const coloriM = c.coloriMondi;
+ c.raggiMondi.forEach((raggioM, i) => {
+ const velocitaM = c.velocitaMondi?.[i] ?? 3;
+ const angoloM = angolo + (c.angoli?.[i] || 0) + tempo * velocitaM;
+ const mx = stellaX + Math.cos(angoloM) * raggioM;
+ const my = stellaY + Math.sin(angoloM) * raggioM;
+
+ ctx.strokeStyle = "rgba(255,255,255,0.04)";
+ ctx.lineWidth = 0.5;
+ ctx.setLineDash([2, 2]);
+ ctx.beginPath();
+ ctx.arc(stellaX, stellaY, raggioM, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+
+ ctx.fillStyle = coloriM[i];
+ ctx.beginPath();
+ ctx.arc(mx, my, 2, 0, Math.PI * 2);
+ ctx.fill();
+
+ ctx.fillStyle = "rgba(255,255,255,0.3)";
+ ctx.font = "7px monospace";
+ ctx.fillText(c.nomiMondi?.[i] || "", mx + 3, my - 3);
+ });
+ }
+
+ // Etichetta
+ ctx.fillStyle = "rgba(255,255,255,0.6)";
+ ctx.font = "11px monospace";
+ ctx.fillText(c.nome, x + c.raggio + 5, y - 5);
+}
+
+function disegnaLineaTraStelle(x1: number, y1: number, x2: number, y2: number) {
+ ctx.strokeStyle = "rgba(255,255,255,0.03)";
+ ctx.lineWidth = 1;
+ ctx.setLineDash([5, 10]);
+ ctx.beginPath();
+ ctx.moveTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+}
+
+function animazione() {
+ // Sfondo
+ ctx.fillStyle = "#000010";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Stelle
+ disegnaStelle();
+
+ // Centro del sistema (baricentro A-B)
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+
+ // Calcola posizioni stelle relative al baricentro
+ const posizioniStelle: { x: number; y: number }[] = [];
+
+ stelle.forEach((s) => {
+ const a = s.asseMaggiore;
+ const b = a * Math.sqrt(1 - s.eccentricita * s.eccentricita);
+ const angolo = tempo * s.velocita + s.angoloIniziale;
+ const x = cx + a * Math.cos(angolo);
+ const y = cy + b * Math.sin(angolo);
+ posizioniStelle.push({ x, y });
+ });
+
+ // Calcola baricentro del triangolo formato dalle tre stelle
+ const baricentroX = (posizioniStelle[0].x + posizioniStelle[1].x + posizioniStelle[2].x) / 3;
+ const baricentroY = (posizioniStelle[0].y + posizioniStelle[1].y + posizioniStelle[2].y) / 3;
+
+ // Offset per centrare tutto il sistema sul canvas
+ const offsetX = baricentroX - cx;
+ const offsetY = baricentroY - cy;
+
+ // Linea tra Alpha Cen A e B
+ disegnaLineaTraStelle(
+ posizioniStelle[0].x - offsetX,
+ posizioniStelle[0].y - offsetY,
+ posizioniStelle[1].x - offsetX,
+ posizioniStelle[1].y - offsetY
+ );
+
+ // Disegna stelle
+ stelle.forEach((s, i) => {
+ disegnaStella(s, posizioniStelle[i].x - offsetX, posizioniStelle[i].y - offsetY);
+ });
+
+ // Pianeti
+ pianeti.forEach(([stellaIdx, p]) => {
+ const angolo = tempo * p.velocita;
+ disegnaCorpo(p, posizioniStella[stellaIdx].x - offsetX, posizioniStelle[stellaIdx].y - offsetY, angolo);
+ });
+
+ // Info
+ ctx.fillStyle = "rgba(255,255,255,0.4)";
+ ctx.font = "10px monospace";
+ ctx.fillText(
+ "Alpha Centauri — Sistema triplo (4.37 anni luce) | A+B orbita: 79.9 anni | Proxima orbita: 547 anni",
+ 10,
+ canvas.height - 10
+ );
+
+ tempo += 0.005;
+ requestAnimationFrame(animazione);
+}
+
+// Stelle di sfondo
+const stelleSfondo: { x: number; y: number; r: number; a: number }[] = [];
+for (let i = 0; i < 300; i++) {
+ stelleSfondo.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ r: Math.random() * 1.2,
+ a: Math.random() * 0.5 + 0.2,
+ });
+}
+
+// Resize
+window.addEventListener("resize", () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+});
+
+// Avvia
+animazione();
diff --git a/sistema_stellare/script.sh b/sistema_stellare/script.sh
new file mode 100755
index 0000000..3f62536
--- /dev/null
+++ b/sistema_stellare/script.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# 1. Chromium in modalità app, senza vero fullscreen, dimensionato allo schermo
+chromium index.html \
+ --window-position=0,0 --window-size=1920,1080 \
+ --disable-infobars --noerrdialogs --disable-session-crashed-bubble &
+
+sleep 3
+
+# 2. Trova l'ID finestra
+WID=$(xdotool search --sync --onlyvisible --class "chromium" | head -1)
+
+# 3. Riposiziona/ridimensiona per sicurezza
+xdotool windowmove "$WID" 0 0
+xdotool windowsize "$WID" 1920 1080
+
+# 4. Marcala come finestra "desktop" e mandala in fondo allo stack
+wmctrl -i -r "$WID" -b add,below
+wmctrl -i -r "$WID" -b add,sticky,skip_taskbar,skip_pager
+xprop -id "$WID" -f _NET_WM_WINDOW_TYPE 32a \
+ -set _NET_WM_WINDOW_TYPE _NET_WM_WINDOW_TYPE_DESKTOP
+
+# 5. Forza nemo-desktop a ridisegnarsi sopra (necessario spesso su Mint)
+killall nemo-desktop 2>/dev/null
+nemo-desktop &
diff --git a/sistema_stellare/sistema-solare.ts b/sistema_stellare/sistema-solare.ts
new file mode 100644
index 0000000..ea8f2d1
--- /dev/null
+++ b/sistema_stellare/sistema-solare.ts
@@ -0,0 +1,455 @@
+// === SISTEMA SOLARE - Simulazione TypeScript ===
+
+interface CorpoCeleste {
+ nome: string;
+ raggio: number;
+ asseMaggiore: number;
+ eccentricita: number;
+ velocita: number;
+ colore: string;
+ angoli?: number[];
+ raggiMondi?: number[];
+ coloriMondi?: string[];
+ nomiMondi?: string[];
+ velocitaMondi?: number[];
+ anelli?: boolean;
+}
+
+const canvas = document.getElementById("sistema") as HTMLCanvasElement;
+const ctx = canvas.getContext("2d")!;
+
+canvas.width = window.innerWidth;
+canvas.height = window.innerHeight;
+
+// === COMETA DI HALLEY ===
+// Periodo: ~75.3 anni | Eccentricità: 0.967 | Inclinazione: 162° (retrograda)
+// Perielio: 0.586 UA | Afelio: 35.1 UA | Semiasse maggiore: 17.8 UA
+interface ParticellaCoda {
+ distanza: number;
+ larghezza: number;
+ angoloOffset: number;
+ opacita: number;
+}
+
+interface Cometa {
+ nome: string;
+ asseMaggiore: number;
+ eccentricita: number;
+ velocita: number;
+ angoloIniziale: number;
+ angoloOrbita: number;
+ coda: ParticellaCoda[];
+}
+
+const cometaHalley: Cometa = {
+ nome: "Halley",
+ asseMaggiore: 267, // ~17.8 UA in scala
+ eccentricita: 0.967,
+ velocita: 0.00084, // P=75.3 anni → scala
+ angoloIniziale: Math.PI * 0.45, // posizione iniziale
+ angoloOrbita: Math.PI * 0.85, // rotazione ellisse (simula inclinazione)
+ coda: [], // particelle coda
+};
+
+// Inizializza particelle della coda
+for (let i = 0; i < 60; i++) {
+ cometaHalley.coda.push({
+ distanza: i * 3,
+ larghezza: (60 - i) * 0.08,
+ angoloOffset: (Math.random() - 0.5) * 0.3,
+ opacita: 1 - i / 60,
+ });
+}
+
+const corpo: CorpoCeleste[] = [
+ {
+ nome: "Mercurio",
+ raggio: 4,
+ asseMaggiore: 60,
+ eccentricita: 0.206,
+ velocita: 4.15,
+ colore: "#b5b5b5",
+ },
+ {
+ nome: "Venere",
+ raggio: 7,
+ asseMaggiore: 90,
+ eccentricita: 0.007,
+ velocita: 1.62,
+ colore: "#e8cda0",
+ },
+ {
+ nome: "Terra",
+ raggio: 8,
+ asseMaggiore: 130,
+ eccentricita: 0.017,
+ velocita: 1.0,
+ colore: "#4a90d9",
+ angoli: [0],
+ raggiMondi: [18],
+ coloriMondi: ["#cccccc"],
+ nomiMondi: ["Luna"],
+ },
+ {
+ nome: "Marte",
+ raggio: 6,
+ asseMaggiore: 170,
+ eccentricita: 0.093,
+ velocita: 0.53,
+ colore: "#c1440e",
+ angoli: [0, 0.5],
+ raggiMondi: [12, 16],
+ coloriMondi: ["#aaaaaa", "#999999"],
+ nomiMondi: ["Fobos", "Deimos"],
+ },
+ {
+ nome: "Giove",
+ raggio: 20,
+ asseMaggiore: 240,
+ eccentricita: 0.049,
+ velocita: 0.084,
+ colore: "#c88b3a",
+ angoli: [0, 0.8, 1.6, 2.4],
+ raggiMondi: [30, 36, 42, 48],
+ coloriMondi: ["#dddddd", "#bbbbbb", "#aaaaaa", "#999999"],
+ nomiMondi: ["Io", "Europa", "Ganimede", "Callisto"],
+ },
+ {
+ nome: "Saturno",
+ raggio: 17,
+ asseMaggiore: 320,
+ eccentricita: 0.057,
+ velocita: 0.034,
+ colore: "#e8d191",
+ angoli: [0, 1.2, 2.4],
+ raggiMondi: [30, 38, 46],
+ coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa"],
+ nomiMondi: ["Encelado", "Mimante", "Titano"],
+ anelli: true,
+ },
+ {
+ nome: "Urano",
+ raggio: 12,
+ asseMaggiore: 390,
+ eccentricita: 0.046,
+ velocita: 0.011,
+ colore: "#72b5c4",
+ angoli: [0, 1.5, 3.0],
+ raggiMondi: [20, 26, 32],
+ coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa"],
+ nomiMondi: ["Miranda", "Ariel", "Titania"],
+ },
+ {
+ nome: "Nettuno",
+ raggio: 11,
+ asseMaggiore: 450,
+ eccentricita: 0.011,
+ velocita: 0.006,
+ colore: "#3f54ba",
+ angoli: [0, 1.0],
+ raggiMondi: [20, 26],
+ coloriMondi: ["#cccccc", "#bbbbbb"],
+ nomiMondi: ["Tritone", "Nereide"],
+ },
+ {
+ nome: "Plutone",
+ raggio: 5,
+ asseMaggiore: 590,
+ eccentricita: 0.244,
+ velocita: 0.004,
+ colore: "#a89f91",
+ angoli: [0, 1.2, 2.4, 3.6, 4.8],
+ raggiMondi: [25, 45, 65, 42, 52],
+ coloriMondi: ["#cccccc", "#bbbbbb", "#aaaaaa", "#999999", "#888888"],
+ nomiMondi: ["Caronte", "Nix", "Idra", "Cerbero", "Stige"],
+ velocitaMondi: [10, 2.5, 1.7, 3.2, 5.3],
+ },
+];
+
+let tempo = 0;
+
+function disegnaSole(cx: number, cy: number) {
+ // Glow esterno
+ const grad = ctx.createRadialGradient(cx, cy, 10, cx, cy, 60);
+ grad.addColorStop(0, "#fff700");
+ grad.addColorStop(0.3, "#ff8c00");
+ grad.addColorStop(1, "rgba(255,140,0,0)");
+ ctx.fillStyle = grad;
+ ctx.beginPath();
+ ctx.arc(cx, cy, 60, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Sole
+ ctx.fillStyle = "#ffcc00";
+ ctx.shadowColor = "#ff8800";
+ ctx.shadowBlur = 30;
+ ctx.beginPath();
+ ctx.arc(cx, cy, 25, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.shadowBlur = 0;
+}
+
+function disegnaAnelliSaturno(cx: number, cy: number, raggio: number) {
+ ctx.strokeStyle = "rgba(210, 180, 140, 0.5)";
+ ctx.lineWidth = 4;
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, raggio * 2.2, raggio * 0.6, 0, 0, Math.PI * 2);
+ ctx.stroke();
+
+ ctx.strokeStyle = "rgba(190, 160, 120, 0.3)";
+ ctx.lineWidth = 3;
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, raggio * 2.6, raggio * 0.7, 0, 0, Math.PI * 2);
+ ctx.stroke();
+}
+
+function disegnaOrbita(cx: number, cy: number, a: number, b: number) {
+ ctx.strokeStyle = "rgba(255,255,255,0.08)";
+ ctx.lineWidth = 1;
+ ctx.beginPath();
+ ctx.ellipse(cx, cy, a, b, 0, 0, Math.PI * 2);
+ ctx.stroke();
+}
+
+function disegnaCorpo(c: CorpoCeleste, cx: number, cy: number, angolo: number) {
+ const a = c.asseMaggiore;
+ const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita);
+ const x = cx + a * Math.cos(angolo);
+ const y = cy + b * Math.sin(angolo);
+
+ // Disegna orbita ellittica
+ disegnaOrbita(cx, cy, a, b);
+
+ // Anelli di Saturno (dietro il pianeta)
+ if (c.anelli) {
+ disegnaAnelliSaturno(x, y, c.raggio);
+ }
+
+ // Pianeta
+ ctx.fillStyle = c.colore;
+ ctx.beginPath();
+ ctx.arc(x, y, c.raggio, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Dettaglio Giove (fasce)
+ if (c.nome === "Giove") {
+ ctx.strokeStyle = "rgba(160, 100, 30, 0.4)";
+ ctx.lineWidth = 2;
+ for (let i = -2; i <= 2; i++) {
+ ctx.beginPath();
+ ctx.arc(x, y, c.raggio * 0.6, 0, Math.PI);
+ ctx.stroke();
+ }
+ // Grande macchia rossa
+ ctx.fillStyle = "rgba(200, 80, 50, 0.6)";
+ ctx.beginPath();
+ ctx.ellipse(x + 5, y + 3, 5, 3, 0, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Dettaglio Terra (continenti)
+ if (c.nome === "Terra") {
+ ctx.fillStyle = "rgba(50, 150, 50, 0.5)";
+ ctx.beginPath();
+ ctx.arc(x - 2, y - 2, 3, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.fillStyle = "rgba(50, 150, 50, 0.4)";
+ ctx.beginPath();
+ ctx.arc(x + 3, y + 2, 2, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Disegna lune
+ if (c.raggiMondi && c.coloriMondi) {
+ const coloriM = c.coloriMondi;
+ c.raggiMondi.forEach((raggioM, i) => {
+ const velocitaM = c.velocitaMondi?.[i] ?? 3;
+ const angoloM = angolo + (c.angoli?.[i] || 0) + tempo * velocitaM;
+ const mx = x + Math.cos(angoloM) * raggioM;
+ const my = y + Math.sin(angoloM) * raggioM;
+
+ // Orbita luna (circolare per semplicità)
+ ctx.strokeStyle = "rgba(255,255,255,0.05)";
+ ctx.lineWidth = 0.5;
+ ctx.beginPath();
+ ctx.arc(x, y, raggioM, 0, Math.PI * 2);
+ ctx.stroke();
+
+ // Luna
+ ctx.fillStyle = coloriM[i];
+ ctx.beginPath();
+ ctx.arc(mx, my, 2, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Etichetta
+ ctx.fillStyle = "rgba(255,255,255,0.4)";
+ ctx.font = "8px monospace";
+ ctx.fillText(c.nomiMondi?.[i] || "", mx + 4, my - 4);
+ });
+ }
+
+ // Etichetta pianeta
+ ctx.fillStyle = "rgba(255,255,255,0.6)";
+ ctx.font = "11px monospace";
+ ctx.fillText(c.nome, x + c.raggio + 5, y - 5);
+}
+
+function animazione() {
+ // Sfondo
+ ctx.fillStyle = "#000010";
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+
+ // Stelle
+ disegnaStelle();
+
+ const cx = canvas.width / 2;
+ const cy = canvas.height / 2;
+
+ // Sole
+ disegnaSole(cx, cy);
+
+ // Pianeti
+ corpo.forEach((c) => {
+ const angolo = tempo * c.velocita;
+ disegnaCorpo(c, cx, cy, angolo);
+ });
+
+ // === COMETA DI HALLEY ===
+ disegnaCometaHalley(cx, cy);
+
+ tempo += 0.005;
+ requestAnimationFrame(animazione);
+}
+
+// Stelle di sfondo (statiche)
+const stelle: { x: number; y: number; r: number; a: number }[] = [];
+for (let i = 0; i < 300; i++) {
+ stelle.push({
+ x: Math.random() * canvas.width,
+ y: Math.random() * canvas.height,
+ r: Math.random() * 1.2,
+ a: Math.random() * 0.5 + 0.2,
+ });
+}
+
+function disegnaCometaHalley(cx: number, cy: number) {
+ const c = cometaHalley;
+ const a = c.asseMaggiore;
+ const b = a * Math.sqrt(1 - c.eccentricita * c.eccentricita);
+ const angolo = tempo * c.velocita + c.angoloIniziale;
+
+ // Posizione cometa sull'orbita ellittica
+ const xOrbita = a * Math.cos(angolo);
+ const yOrbita = b * Math.sin(angolo);
+
+ // Ruota l'orbita per simulare inclinazione/retrograda
+ const cosA = Math.cos(c.angoloOrbita);
+ const sinA = Math.sin(c.angoloOrbita);
+ const x = cx + xOrbita * cosA - yOrbita * sinA;
+ const y = cy + xOrbita * sinA + yOrbita * cosA;
+
+ // Disegna orbita (ellisse ruotata)
+ ctx.save();
+ ctx.translate(cx, cy);
+ ctx.rotate(c.angoloOrbita);
+ ctx.strokeStyle = "rgba(150,200,255,0.12)";
+ ctx.lineWidth = 1;
+ ctx.setLineDash([4, 6]);
+ ctx.beginPath();
+ ctx.ellipse(0, 0, a, b, 0, Math.PI * 2);
+ ctx.stroke();
+ ctx.setLineDash([]);
+ ctx.restore();
+
+ // Distanza dal Sole per direzione coda
+ const dxSole = cx - x;
+ const dySole = cy - y;
+ const distSole = Math.sqrt(dxSole * dxSole + dySole * dySole);
+ const direzioneX = dxSole / distSole;
+ const direzioneY = dySole / distSole;
+
+ // Perielio: la cometa è più luminosa quando è vicina al Sole
+ const perielioDist = a * (1 - c.eccentricita); // ~9px in scala
+ const luminosita = Math.min(1, perielioDist / distSole);
+
+ // Coda di polvere (più larga, curva leggermente)
+ for (let i = 0; i < cometaHalley.coda.length; i++) {
+ const p = cometaHalley.coda[i];
+ const tailLen = p.distanza * (0.5 + luminosita * 1.5);
+ const tailW = p.larghezza * (0.5 + luminosita);
+ const angoloCoda = Math.atan2(direzioneY, direzioneX) + p.angoloOffset;
+
+ const px = x - Math.cos(angoloCoda) * tailLen;
+ const py = y - Math.sin(angoloCoda) * tailLen;
+
+ const alpha = p.opacita * luminosita * 0.6;
+ if (alpha > 0.01) {
+ ctx.fillStyle = `rgba(180,220,255,${alpha})`;
+ ctx.beginPath();
+ ctx.arc(px, py, tailW, 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+
+ // Coda ionica (più sottile, dritta, blu)
+ const codaIonicaLunghezza = 40 + luminosita * 80;
+ const angoloIonico = Math.atan2(direzioneY, direzioneX);
+ for (let i = 0; i < 30; i++) {
+ const t = i / 30;
+ const ix = x - Math.cos(angoloIonico) * codaIonicaLunghezza * t;
+ const iy = y - Math.sin(angoloIonico) * codaIonicaLunghezza * t;
+ const alpha = (1 - t) * luminosita * 0.5;
+ if (alpha > 0.01) {
+ ctx.fillStyle = `rgba(100,150,255,${alpha})`;
+ ctx.beginPath();
+ ctx.arc(ix, iy, 1.5 * (1 - t), 0, Math.PI * 2);
+ ctx.fill();
+ }
+ }
+
+ // Coma (chioma) intorno al nucleo
+ if (luminosita > 0.1) {
+ const comaRaggio = 6 + luminosita * 12;
+ const comaGrad = ctx.createRadialGradient(x, y, 2, x, y, comaRaggio);
+ comaGrad.addColorStop(0, `rgba(200,230,255,${luminosita * 0.8})`);
+ comaGrad.addColorStop(0.3, `rgba(150,200,255,${luminosita * 0.3})`);
+ comaGrad.addColorStop(1, "rgba(100,150,255,0)");
+ ctx.fillStyle = comaGrad;
+ ctx.beginPath();
+ ctx.arc(x, y, comaRaggio, 0, Math.PI * 2);
+ ctx.fill();
+ }
+
+ // Nucleo della cometa
+ const nucleoRaggio = 2.5 + luminosita * 1.5;
+ ctx.fillStyle = `rgba(220,240,255,${0.5 + luminosita * 0.5})`;
+ ctx.shadowColor = "rgba(150,200,255,0.8)";
+ ctx.shadowBlur = 8 * luminosita;
+ ctx.beginPath();
+ ctx.arc(x, y, nucleoRaggio, 0, Math.PI * 2);
+ ctx.fill();
+ ctx.shadowBlur = 0;
+
+ // Etichetta
+ ctx.fillStyle = `rgba(180,220,255,${0.4 + luminosita * 0.4})`;
+ ctx.font = "10px monospace";
+ ctx.fillText(c.nome, x + 10, y - 8);
+}
+
+function disegnaStelle() {
+ stelle.forEach((s) => {
+ ctx.fillStyle = `rgba(255,255,255,${s.a})`;
+ ctx.beginPath();
+ ctx.arc(s.x, s.y, s.r, 0, Math.PI * 2);
+ ctx.fill();
+ });
+}
+
+// Resize
+window.addEventListener("resize", () => {
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+});
+
+// Avvia
+animazione();
diff --git a/sistema_stellare/sole.html b/sistema_stellare/sole.html
new file mode 100644
index 0000000..196865b
--- /dev/null
+++ b/sistema_stellare/sole.html
@@ -0,0 +1,353 @@
+
+
+
+
+
+ Sistema Solare
+
+
+
+
+ Sistema Solare — Simulazione
+
+
+
+
diff --git a/webpaper/__init__.py b/webpaper/__init__.py
new file mode 100644
index 0000000..3c14277
--- /dev/null
+++ b/webpaper/__init__.py
@@ -0,0 +1,3 @@
+"""webpaper — pagina web come sfondo del desktop."""
+
+__version__ = "0.1.0"
diff --git a/webpaper/__main__.py b/webpaper/__main__.py
new file mode 100644
index 0000000..c619a0f
--- /dev/null
+++ b/webpaper/__main__.py
@@ -0,0 +1,311 @@
+"""webpaper — pagina web come sfondo del desktop.
+
+Ispirato a Hidamari (https://github.com/jeffshee/hidamari), video wallpaper
+per Linux, dal quale è stato estratto il meccanismo della pagina web:
+
+ una normale Gtk.ApplicationWindow piena di una WebKit2.WebView viene marcata
+ come finestra DESKTOP (Gdk.WindowTypeHint.DESKTOP): il window manager la
+ tiene in fondo allo stack, sotto ogni altra finestra, esattamente come lo
+ sfondo nativo. Viene creata una finestra per ogni monitor, dimensionata e
+ posizionata sulla geometria del monitor stesso.
+
+Uso:
+ python3 -m webpaper /percorso/pagina.html
+ python3 -m webpaper https://esempio.com
+ python3 -m webpaper pagina1.html pagina2.html # dual monitor: una pagina per monitor
+"""
+
+import argparse
+import glob
+import logging
+import os
+import pathlib
+import subprocess
+import sys
+
+# Le finestre con WindowTypeHint.DESKTOP funzionano solo su X11.
+# Su una sessione Wayland, GDK ricade automaticamente su XWayland.
+os.environ["GDK_BACKEND"] = "x11"
+
+import gi # noqa: E402
+
+gi.require_version("Gtk", "3.0")
+gi.require_version("Gdk", "3.0")
+gi.require_version("WebKit2", "4.1")
+from gi.repository import Gdk, GLib, Gtk, WebKit2 # noqa: E402
+
+logger = logging.getLogger("webpaper")
+
+# Disattivabile con --no-fix-icons
+_fix_icons_enabled = True
+
+# Al login le icone desktop (nemo-desktop/DING) potrebbero non esserci ancora:
+# riprova alcune volte prima di rinunciare.
+_fix_attempts = 0
+MAX_FIX_ATTEMPTS = 4
+
+# Classi delle finestre che disegnano le icone desktop, per DE:
+# Cinnamon -> nemo-desktop
+# GNOME (DING/desktop icons) -> org.gnome.NautilusDesktopWindow
+DESKTOP_ICONS_CLASSES = ("nemo-desktop", "org.gnome.NautilusDesktopWindow")
+
+
+def fix_desktop_icons():
+ """Porta sopra le icone della scrivania, sotto il wallpaper.
+
+ Le icone desktop (es. nemo-desktop su Cinnamon, DING su GNOME) sono a loro
+ volta finestre di tipo DESKTOP: quando il nostro wallpaper viene mappato
+ dopo di loro, il window manager lo impila sopra e le icone spariscono.
+ Su Muffin (Cinnamon) funziona attivarle (windowactivate), non alzarle
+ (windowraise). Richiede xdotool: se non è installato o non ci sono
+ finestre corrispondenti, non fa nulla.
+ """
+ if not _fix_icons_enabled:
+ return False
+ global _fix_attempts
+ try:
+ import shutil
+
+ if shutil.which("xdotool") is None:
+ logger.debug("xdotool non disponibile: icone desktop non sistemate")
+ return False
+ except Exception:
+ return False
+ found = False
+ for cls in DESKTOP_ICONS_CLASSES:
+ try:
+ result = subprocess.run(
+ ["xdotool", "search", "--class", cls],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ if result.returncode != 0:
+ continue
+ window_ids = result.stdout.split()
+ if not window_ids:
+ continue
+ found = True
+ # Nota: attivare le finestre una alla volta (non la forma
+ # concatenata `search ... windowactivate`): su Muffin solo cosi'
+ # le finestre DESKTOP vengono rimesse sopra il wallpaper.
+ for window_id in window_ids:
+ subprocess.run(
+ ["xdotool", "windowactivate", window_id],
+ capture_output=True,
+ timeout=5,
+ )
+ logger.info("Icone desktop (%s) portate sopra il wallpaper", cls)
+ except (subprocess.TimeoutExpired, OSError):
+ logger.debug("xdotool non riuscito per %s", cls)
+ _fix_attempts += 1
+ if not found and _fix_attempts < MAX_FIX_ATTEMPTS:
+ return True # per GLib.timeout_add: riprova tra 2 s
+ _fix_attempts = 0
+ return False # per GLib.timeout_add: esegui una sola volta
+
+
+def setup_render_device():
+ """Workaround NVIDIA.
+
+ Il renderer DMA-BUF di WebKitGTK non riesce ad allocare buffer GBM sui
+ driver NVIDIA ("Failed to create GBM buffer: Invalid argument"), e la
+ pagina resterebbe bianca. Se è presente una GPU NVIDIA, si punta WebKit su
+ un render node Mesa oppure, su sistemi solo-NVIDIA, si ripiega su buffer
+ condivisi (SHM). Va chiamato prima della creazione della prima WebView.
+ """
+ overrides = (
+ "WEBKIT_WEB_RENDER_DEVICE_FILE",
+ "WEBKIT_DMABUF_RENDERER_FORCE_SHM",
+ "WEBKIT_DISABLE_DMABUF_RENDERER",
+ )
+ if any(var in os.environ for var in overrides):
+ return
+ drivers = {}
+ for node in sorted(glob.glob("/dev/dri/renderD*")):
+ driver = os.path.realpath(
+ f"/sys/class/drm/{os.path.basename(node)}/device/driver"
+ )
+ drivers[node] = os.path.basename(driver)
+ if "nvidia" not in drivers.values():
+ return
+ for node, driver in drivers.items():
+ if driver != "nvidia":
+ logger.info("Usando render device %s (%s)", node, driver)
+ os.environ["WEBKIT_WEB_RENDER_DEVICE_FILE"] = node
+ return
+ logger.info("Sistema solo-NVIDIA: buffer SHM per WebKit")
+ os.environ["WEBKIT_DMABUF_RENDERER_FORCE_SHM"] = "1"
+
+
+def to_uri(source):
+ """Converte un percorso locale in un URI file://.
+
+ Gli URL (http/https) e gli URI file:// già completi vengono lasciati
+ invariati. Solleva FileNotFoundError se un percorso locale non esiste.
+ """
+ if source.startswith(("http://", "https://", "file://")):
+ return source
+ path = pathlib.Path(source).expanduser()
+ if not path.is_file():
+ raise FileNotFoundError(f"File non trovato: {source}")
+ return path.resolve().as_uri()
+
+
+class WebWindow(Gtk.ApplicationWindow):
+ """Finestra DESKTOP che contiene la pagina web (una WebView a tutto schermo)."""
+
+ def __init__(self, uri, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.uri = uri
+ self.webview = WebKit2.WebView()
+ self.add(self.webview)
+ self.webview.show()
+ # Click destro: menu con Ricarica / Esci
+ self.webview.connect("button-press-event", self._on_button_press_event)
+ self._menu = None
+
+ def load(self):
+ self.webview.load_uri(self.uri)
+
+ def _on_button_press_event(self, widget, event):
+ if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 3:
+ if self._menu is None:
+ self._menu = Gtk.Menu()
+ reload_item = Gtk.MenuItem(label="Ricarica")
+ reload_item.connect("activate", lambda *_: self.webview.reload())
+ self._menu.append(reload_item)
+ quit_item = Gtk.MenuItem(label="Esci")
+ quit_item.connect("activate", lambda *_: self.get_application().quit())
+ self._menu.append(quit_item)
+ self._menu.show_all()
+ self._menu.popup_at_pointer()
+ return True
+ return False
+
+
+class WebWallpaper(Gtk.Application):
+ """Applicazione: una finestra DESKTOP per ogni monitor."""
+
+ def __init__(self, uris):
+ super().__init__(application_id="org.webpaper.wallpaper")
+ self.uris = uris
+ self.windows = {} # Gdk.Monitor -> WebWindow
+ self._monitor_detect()
+ if len(self.uris) > len(self.windows):
+ logger.warning(
+ "%d sorgenti ma solo %d monitor: le sorgenti in eccesso verranno ignorate",
+ len(self.uris),
+ len(self.windows),
+ )
+
+ def _uri_for_index(self, i):
+ """Sorgente per l'i-esimo monitor.
+
+ Una sola sorgente => mostrata su tutti i monitor (compatibilità col
+ comportamento originale). Con N sorgenti => mappatura 1:1 nell'ordine
+ dei monitor; eventuali monitor in più ripetono l'ultima sorgente.
+ """
+ return self.uris[min(i, len(self.uris) - 1)]
+
+ # -- gestione monitor -------------------------------------------------
+
+ def _monitor_detect(self):
+ display = Gdk.Display.get_default()
+ screen = display.get_default_screen()
+ for i in range(display.get_n_monitors()):
+ self.windows[display.get_monitor(i)] = None
+ screen.connect("size-changed", self._on_size_changed)
+ display.connect("monitor-added", self._on_monitor_added)
+ display.connect("monitor-removed", self._on_monitor_removed)
+
+ def _on_size_changed(self, *args):
+ logger.info("size-changed")
+ for monitor, window in self.windows.items():
+ if window is not None:
+ self._place(window, monitor)
+
+ def _on_monitor_added(self, _, gdk_monitor, *args):
+ logger.info("monitor-added")
+ self.windows[gdk_monitor] = None
+ self._create_windows()
+
+ def _on_monitor_removed(self, _, gdk_monitor, *args):
+ logger.info("monitor-removed")
+ window = self.windows.pop(gdk_monitor, None)
+ if window is not None:
+ window.close()
+
+ # -- finestre ---------------------------------------------------------
+
+ @staticmethod
+ def _place(window, monitor):
+ rect = monitor.get_geometry()
+ window.set_size_request(rect.width, rect.height)
+ window.move(rect.x, rect.y)
+
+ def _create_windows(self):
+ for i, (monitor, window) in enumerate(self.windows.items()):
+ uri = self._uri_for_index(i)
+ if window is None:
+ logger.info("Monitor %d -> %s", i, uri)
+ window = WebWindow(uri, application=self)
+ # Il trucco: hint DESKTOP => il WM la tratta come sfondo
+ window.set_type_hint(Gdk.WindowTypeHint.DESKTOP)
+ self._place(window, monitor)
+ window.load()
+ self.windows[monitor] = window
+ window.present()
+ # Lascia assestare il WM, poi porta le icone desktop sopra il wallpaper
+ # (riprovando un paio di volte: al login nemo-desktop potrebbe tardare)
+ GLib.timeout_add(1500, fix_desktop_icons)
+
+ def do_activate(self):
+ self._create_windows()
+
+
+def main(argv=None):
+ parser = argparse.ArgumentParser(
+ prog="webpaper",
+ description="Usa una pagina web come sfondo del desktop (X11/XWayland).",
+ )
+ parser.add_argument(
+ "source",
+ nargs="+",
+ help=(
+ "file HTML oppure URL (http://, https://, file://). "
+ "Con due sorgenti e due monitor, ogni monitor mostra la rispettiva pagina "
+ "(prima sorgente = primo monitor, seconda = secondo)."
+ ),
+ )
+ parser.add_argument("-d", "--debug", action="store_true", help="log di debug")
+ parser.add_argument(
+ "--no-fix-icons",
+ action="store_true",
+ help="non sistemare le icone desktop (le icone resterebbero sotto il wallpaper)",
+ )
+ args = parser.parse_args(argv)
+
+ logging.basicConfig(
+ level=logging.DEBUG if args.debug else logging.INFO,
+ format="%(asctime)s %(levelname)s %(name)s: %(message)s",
+ )
+
+ try:
+ uris = [to_uri(source) for source in args.source]
+ except FileNotFoundError as e:
+ parser.error(str(e))
+
+ setup_render_device()
+
+ global _fix_icons_enabled
+ _fix_icons_enabled = not args.no_fix_icons
+
+ app = WebWallpaper(uris)
+ # Gtk.Application parsa sys.argv: togliamo i nostri argomenti
+ sys.argv = [sys.argv[0]]
+ return app.run(sys.argv)
+
+
+if __name__ == "__main__":
+ sys.exit(main())