diff --git a/public/css/mobile.css b/public/css/mobile.css
index 75b7cdc..9ed967b 100644
--- a/public/css/mobile.css
+++ b/public/css/mobile.css
@@ -35,6 +35,9 @@ button { cursor: pointer; }
button:active { filter: brightness(1.3); }
button.wide { width: 100%; margin-top: 10px; font-size: 15px; }
#audio-src { width: 100%; }
+button.danger { border-color: rgba(255,120,120,.5); color: #ffb3b3; background: rgba(255,80,80,.10); }
+button.danger.armed { background: #c0392b; color: #fff; border-color: #c0392b; animation: pulse 1s infinite; }
+@keyframes pulse { 50% { filter: brightness(1.35); } }
input[type=range] { -webkit-appearance: none; appearance: none; background: transparent; padding: 0; height: 34px; border: none; }
input[type=range]::-webkit-slider-runnable-track { height: 6px; border-radius: 4px; background: rgba(255,255,255,.14); }
input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 24px; height: 24px; border-radius: 50%; background: var(--accent); margin-top: -9px; box-shadow: 0 1px 4px rgba(0,0,0,.5); }
diff --git a/public/index.html b/public/index.html
index d7076fc..244f623 100644
--- a/public/index.html
+++ b/public/index.html
@@ -126,6 +126,10 @@
+
+
+
+ Tocca due volte per confermare. Dopo lo spegnimento, per riaccendere stacca e riattacca l'alimentazione.
diff --git a/public/js/control.js b/public/js/control.js
index 0597cf3..dbfd82b 100644
--- a/public/js/control.js
+++ b/public/js/control.js
@@ -576,6 +576,24 @@ function updateRec() {
$('#rec').classList.toggle('recording', recOn);
}
+// Power: reboot / shutdown the Pi. Two-tap confirm to avoid accidental taps.
+function armPower(btnId, action) {
+ const btn = $('#' + btnId); if (!btn) return;
+ const orig = btn.textContent; let armed = false, t = null;
+ btn.addEventListener('click', () => {
+ if (!armed) {
+ armed = true; btn.classList.add('armed'); btn.textContent = '⚠️ Tocca per confermare';
+ t = setTimeout(() => { armed = false; btn.classList.remove('armed'); btn.textContent = orig; }, 4000);
+ return;
+ }
+ clearTimeout(t); armed = false; btn.classList.remove('armed'); btn.textContent = orig;
+ $('#pw-status').textContent = action === 'reboot' ? '🔄 Riavvio in corso… il telecomando si riconnette da solo tra ~40s.' : '⏻ Spegnimento in corso… per riaccendere stacca e riattacca l\'alimentazione.';
+ fetch('/api/power/' + action, { method: 'POST' }).catch(() => {});
+ });
+}
+armPower('pw-reboot', 'reboot');
+armPower('pw-shutdown', 'shutdown');
+
// ---- Reports ---------------------------------------------------------------
function setMeter(id, v) { const e = $('#' + id); if (e) e.style.width = Math.min(100, (v || 0) * 100) + '%'; }
djv.onReport((m) => {
diff --git a/server.js b/server.js
index 2b9442b..9561f15 100644
--- a/server.js
+++ b/server.js
@@ -69,6 +69,16 @@ app.post('/api/upload', upload.array('files'), (req, res) => {
app.get('/api/clients', (_req, res) => { res.json([...wss.clients].map((c) => ({ role: c.role, open: c.readyState === 1 }))); });
+// ---- Power: reboot / shutdown the appliance (sudo is passwordless on the Pi) -
+app.post('/api/power/:action', (req, res) => {
+ const action = req.params.action;
+ if (action !== 'reboot' && action !== 'shutdown') return res.status(400).json({ ok: false, error: 'azione non valida' });
+ res.json({ ok: true, action });
+ // Reply first, then act — the command tears down this very process/host.
+ // Use systemctl explicitly (the service PATH is minimal); sudo is passwordless.
+ setTimeout(() => { try { execFile('sudo', ['systemctl', action === 'reboot' ? 'reboot' : 'poweroff']); } catch (e) {} }, 700);
+});
+
app.get('/api/svgs', (_req, res) => {
let files = [];
try { files = fs.readdirSync(SVG_DIR).filter(f => /\.svg$/i.test(f)); } catch (e) {}