umer-wasim commited on
Commit
fc0cb1f
·
verified ·
1 Parent(s): f269f89

Arx 0.4.3: app renamed to Arx, first-open help (installer window + website), colours

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ app/build/dmg-background@2x.png filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -166,13 +166,16 @@ history. `mobile/` stays because the synthetic data generator uses its evidence
166
 
167
  ## Install on a Mac
168
 
169
- Download from **https://devseis-arx.static.hf.space/#download** (universal app for Apple silicon and
170
- Intel, macOS 13+; files and checksums in [Devseis/endpoint-auditor-releases](https://huggingface.co/datasets/Devseis/endpoint-auditor-releases)).
171
- Drag it to Applications; on first open, allow it once in *System Settings › Privacy & Security › Open Anyway* (the build
172
- is ad-hoc signed, not yet notarised). The app downloads the AI auditor model on first run and follows the model version
173
- in `app/model-version.json` on this Space. Settings for organisations: `~/Library/Application Support/Devseis Endpoint Auditor/`
174
- (`baseline.conf`, `organisational.conf`). Build it yourself: `cd app && npm install && npm run dist`; smoke-test a build
175
- with `DEVSEIS_SMOKE=<folder> "<app>/Contents/MacOS/Devseis Endpoint Auditor"` (add `DEVSEIS_SMOKE_MODEL=1` to include the model).
 
 
 
176
 
177
  ## Role of the language model
178
 
 
166
 
167
  ## Install on a Mac
168
 
169
+ Download from **https://devseis-arx.static.hf.space/#download** (universal app for Apple silicon and Intel, macOS 13+;
170
+ files and checksums in [Devseis/endpoint-auditor-releases](https://huggingface.co/datasets/Devseis/endpoint-auditor-releases)).
171
+ Drag **Arx** to Applications; on first open, allow it once in *System Settings › Privacy & Security › Open Anyway* (the build
172
+ is ad-hoc signed, not yet notarised). The installer window and the website show these steps. The app downloads the AI
173
+ auditor model on first run and follows the model version in `app/model-version.json` on this Space. Settings for
174
+ organisations: `~/Library/Application Support/Devseis Endpoint Auditor/` (`baseline.conf`, `organisational.conf`; the
175
+ folder keeps its old name since the rename to Arx in 0.4.3). Reports: *Documents › Arx Reports*. Build it yourself:
176
+ `cd app && npm install && npm run dist`; smoke-test a build with `DEVSEIS_SMOKE=<folder> "<app>/Contents/MacOS/Arx"`
177
+ (add `DEVSEIS_SMOKE_MODEL=1` to include the model). The installer background is `app/build/dmg-background.html`,
178
+ rendered to `dmg-background.png` / `@2x.png`.
179
 
180
  ## Role of the language model
181
 
app/build/dmg-background.html ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html><html><head><meta charset="utf-8"><style>
2
+ html,body{margin:0;width:640px;height:440px;overflow:hidden}
3
+ body{font-family:-apple-system,'SF Pro Text','Helvetica Neue',sans-serif;color:#111;background:radial-gradient(420px 260px at 10% 0,rgba(237,41,57,.18),transparent 70%),radial-gradient(420px 260px at 95% 10%,rgba(124,58,237,.14),transparent 70%),#fafafa}
4
+ h1{position:absolute;top:26px;left:0;right:0;text-align:center;margin:0;font-size:21px;font-weight:700;letter-spacing:-.01em}
5
+ .sub{position:absolute;top:56px;left:0;right:0;text-align:center;font-size:12.5px;color:#666}
6
+ .arrow{position:absolute;top:150px;left:262px;width:116px;height:40px}
7
+ .steps{position:absolute;left:28px;right:28px;top:278px;display:flex;gap:10px}
8
+ .s{flex:1;background:#fff;border:1px solid #e5e5e5;border-radius:10px;padding:10px 11px;font-size:11.5px;line-height:1.4;color:#444}
9
+ .s b{display:block;color:#111;font-size:12.5px;margin-bottom:3px}
10
+ .n{display:inline-grid;place-items:center;width:18px;height:18px;border-radius:50%;background:#ed2939;color:#fff;font-size:11px;font-weight:700;margin-right:5px;vertical-align:1px}
11
+ .s.hl{border-color:#ed2939;box-shadow:0 0 0 2px rgba(237,41,57,.15)}
12
+ </style></head><body>
13
+ <h1>Install Arx</h1><div class="sub">Drag the Arx icon onto the Applications folder</div>
14
+ <svg class="arrow" viewBox="0 0 116 40"><defs><linearGradient id="g" x1="0" x2="1"><stop offset="0" stop-color="#ed2939"/><stop offset="1" stop-color="#f97316"/></linearGradient></defs><path d="M4 20 H98" stroke="#ed2939" stroke-width="5" stroke-linecap="round"/><path d="M90 8 L108 20 L90 32" fill="none" stroke="#f97316" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/></svg>
15
+ <div class="steps">
16
+ <div class="s"><b><span class="n">1</span>Drag to Applications</b>Drag Arx onto the folder above.</div>
17
+ <div class="s"><b><span class="n">2</span>Open Arx</b>From Applications. If macOS says “Not Opened”, click <b style="display:inline">Done</b>.</div>
18
+ <div class="s hl"><b><span class="n">3</span>Allow it once</b>Apple menu › System Settings › Privacy &amp; Security › <b style="display:inline">Open Anyway</b>.</div>
19
+ </div></body></html>
app/build/dmg-background.png ADDED
app/build/dmg-background@2x.png ADDED

Git LFS Details

  • SHA256: b2ecd1a9b6a286750a2baf32a1874f7035dee0080bd5eccd879391eefa78ed7e
  • Pointer size: 131 Bytes
  • Size of remote file: 214 kB
app/main.cjs CHANGED
@@ -1,7 +1,7 @@
1
- // Devseis Endpoint Auditor - Electron main process.
2
  // Runs the read-only collector for this OS (with the OS's own administrator prompt), streams progress
3
  // to the window, turns the report into a PDF and saves it where the user chooses or, after 60 seconds
4
- // without an answer, in Documents/Devseis Endpoint Audit/<computer>-<date>/.
5
  const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
6
  const { spawn } = require('node:child_process');
7
  const crypto = require('node:crypto');
@@ -10,6 +10,10 @@ const os = require('node:os');
10
  const path = require('node:path');
11
  const vulndb = require('./vulndb.cjs');
12
 
 
 
 
 
13
  // WebGPU (used by WebLLM) is still behind a flag on Linux.
14
  if (process.platform === 'linux') {
15
  app.commandLine.appendSwitch('enable-unsafe-webgpu');
@@ -192,7 +196,7 @@ function defaultFolder(hostname) {
192
  const d = new Date();
193
  const stamp = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}-` +
194
  `${String(d.getHours()).padStart(2, '0')}${String(d.getMinutes()).padStart(2, '0')}${String(d.getSeconds()).padStart(2, '0')}`;
195
- return path.join(SMOKE || app.getPath('documents'), 'Devseis Endpoint Audit', `${hostname.replace(/[^\w.-]/g, '_')}-${stamp}`);
196
  }
197
 
198
  async function renderPdf(html) {
@@ -234,7 +238,7 @@ const CHAT_URL = 'https://devseis-arx.static.hf.space/index.html';
234
  let chatWindow = null;
235
  ipcMain.handle('chat:open', async (event, evidenceText) => {
236
  if (chatWindow && !chatWindow.isDestroyed()) chatWindow.close();
237
- chatWindow = new BrowserWindow({ width: 1000, height: 820, minWidth: 420, minHeight: 560, title: 'Arx · Devseis security assistant',
238
  webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true } });
239
  chatWindow.webContents.setWindowOpenHandler(({ url }) => (url.startsWith('blob:') ? { action: 'allow' } : (shell.openExternal(url), { action: 'deny' })));
240
  try {
 
1
+ // Arx (Devseis Endpoint Auditor) - Electron main process.
2
  // Runs the read-only collector for this OS (with the OS's own administrator prompt), streams progress
3
  // to the window, turns the report into a PDF and saves it where the user chooses or, after 60 seconds
4
+ // without an answer, in Documents/Arx Reports/<computer>-<date>/.
5
  const { app, BrowserWindow, ipcMain, dialog, shell } = require('electron');
6
  const { spawn } = require('node:child_process');
7
  const crypto = require('node:crypto');
 
10
  const path = require('node:path');
11
  const vulndb = require('./vulndb.cjs');
12
 
13
+ // Renamed to Arx in 0.4.3: keep the old settings folder, so baseline.conf, organisational.conf, the vulnerability data
14
+ // and the downloaded AI model stay where earlier versions put them.
15
+ app.setPath('userData', path.join(app.getPath('appData'), 'Devseis Endpoint Auditor'));
16
+
17
  // WebGPU (used by WebLLM) is still behind a flag on Linux.
18
  if (process.platform === 'linux') {
19
  app.commandLine.appendSwitch('enable-unsafe-webgpu');
 
196
  const d = new Date();
197
  const stamp = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}-` +
198
  `${String(d.getHours()).padStart(2, '0')}${String(d.getMinutes()).padStart(2, '0')}${String(d.getSeconds()).padStart(2, '0')}`;
199
+ return path.join(SMOKE || app.getPath('documents'), 'Arx Reports', `${hostname.replace(/[^\w.-]/g, '_')}-${stamp}`);
200
  }
201
 
202
  async function renderPdf(html) {
 
238
  let chatWindow = null;
239
  ipcMain.handle('chat:open', async (event, evidenceText) => {
240
  if (chatWindow && !chatWindow.isDestroyed()) chatWindow.close();
241
+ chatWindow = new BrowserWindow({ width: 1000, height: 820, minWidth: 420, minHeight: 560, title: 'Arx · report',
242
  webPreferences: { contextIsolation: true, nodeIntegration: false, sandbox: true } });
243
  chatWindow.webContents.setWindowOpenHandler(({ url }) => (url.startsWith('blob:') ? { action: 'allow' } : (shell.openExternal(url), { action: 'deny' })));
244
  try {
app/package.json CHANGED
@@ -1,7 +1,7 @@
1
  {
2
  "name": "devseis-endpoint-auditor",
3
- "productName": "Devseis Endpoint Auditor",
4
- "version": "0.4.2",
5
  "description": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act, with a local AI auditor.",
6
  "author": {
7
  "name": "Devseis",
@@ -28,7 +28,7 @@
28
  "homepage": "https://huggingface.co/spaces/Devseis/Arx",
29
  "build": {
30
  "appId": "com.devseis.endpoint-auditor",
31
- "productName": "Devseis Endpoint Auditor",
32
  "copyright": "Copyright 2026 Devseis. Apache-2.0.",
33
  "directories": {
34
  "output": "dist"
@@ -79,10 +79,28 @@
79
  "identity": "-",
80
  "hardenedRuntime": false,
81
  "gatekeeperAssess": false,
82
- "artifactName": "Devseis-Endpoint-Auditor-${version}-mac-universal.${ext}"
83
  },
84
  "dmg": {
85
- "title": "Devseis Endpoint Auditor ${version}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  },
87
  "asar": true,
88
  "asarUnpack": [
@@ -97,7 +115,7 @@
97
  ]
98
  }
99
  ],
100
- "artifactName": "Devseis-Endpoint-Auditor-${version}-windows-x64-setup.${ext}",
101
  "signAndEditExecutable": false
102
  },
103
  "nsis": {
@@ -105,7 +123,7 @@
105
  "perMachine": false,
106
  "allowToChangeInstallationDirectory": true,
107
  "createDesktopShortcut": true,
108
- "shortcutName": "Devseis Endpoint Auditor"
109
  },
110
  "linux": {
111
  "target": [
@@ -115,8 +133,8 @@
115
  "category": "Utility",
116
  "maintainer": "Devseis <noreply@huggingface.co>",
117
  "synopsis": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act",
118
- "artifactName": "Devseis-Endpoint-Auditor-${version}-linux-x86_64.${ext}",
119
- "executableName": "devseis-endpoint-auditor"
120
  },
121
  "deb": {
122
  "depends": [
 
1
  {
2
  "name": "devseis-endpoint-auditor",
3
+ "productName": "Arx",
4
+ "version": "0.4.3",
5
  "description": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act, with a local AI auditor.",
6
  "author": {
7
  "name": "Devseis",
 
28
  "homepage": "https://huggingface.co/spaces/Devseis/Arx",
29
  "build": {
30
  "appId": "com.devseis.endpoint-auditor",
31
+ "productName": "Arx",
32
  "copyright": "Copyright 2026 Devseis. Apache-2.0.",
33
  "directories": {
34
  "output": "dist"
 
79
  "identity": "-",
80
  "hardenedRuntime": false,
81
  "gatekeeperAssess": false,
82
+ "artifactName": "Arx-${version}-mac-universal.${ext}"
83
  },
84
  "dmg": {
85
+ "title": "Arx ${version}",
86
+ "background": "build/dmg-background.png",
87
+ "window": {
88
+ "width": 640,
89
+ "height": 440
90
+ },
91
+ "contents": [
92
+ {
93
+ "x": 170,
94
+ "y": 170,
95
+ "type": "file"
96
+ },
97
+ {
98
+ "x": 470,
99
+ "y": 170,
100
+ "type": "link",
101
+ "path": "/Applications"
102
+ }
103
+ ]
104
  },
105
  "asar": true,
106
  "asarUnpack": [
 
115
  ]
116
  }
117
  ],
118
+ "artifactName": "Arx-${version}-windows-x64-setup.${ext}",
119
  "signAndEditExecutable": false
120
  },
121
  "nsis": {
 
123
  "perMachine": false,
124
  "allowToChangeInstallationDirectory": true,
125
  "createDesktopShortcut": true,
126
+ "shortcutName": "Arx"
127
  },
128
  "linux": {
129
  "target": [
 
133
  "category": "Utility",
134
  "maintainer": "Devseis <noreply@huggingface.co>",
135
  "synopsis": "Read-only endpoint audit for ISO 27001, GDPR and the EU AI Act",
136
+ "artifactName": "Arx-${version}-linux-x86_64.${ext}",
137
+ "executableName": "arx"
138
  },
139
  "deb": {
140
  "depends": [
app/renderer/index.html CHANGED
@@ -4,12 +4,12 @@
4
  <meta charset="utf-8">
5
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; connect-src 'self' https://huggingface.co https://*.huggingface.co https://*.hf.co https://raw.githubusercontent.com; img-src 'self' data:; worker-src 'self' blob:">
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
- <title>Arx · Devseis Endpoint Auditor</title>
8
  <link rel="stylesheet" href="styles.css">
9
  </head>
10
  <body>
11
  <header class="bar">
12
- <div class="brand"><span class="dot"></span> Devseis Endpoint Auditor</div>
13
  <div class="device" id="device"></div>
14
  </header>
15
 
@@ -55,7 +55,7 @@
55
  <div class="actions">
56
  <button class="secondary" id="again">Run another audit</button>
57
  <button class="primary" id="save-again">Save report as PDF</button>
58
- <button class="secondary" id="ask-chat">💬 Ask Arx about this report</button>
59
  </div>
60
  <p class="muted" id="saved-note"></p>
61
  </section>
@@ -73,7 +73,7 @@
73
  <div class="modal-box">
74
  <h2 id="save-title">Save the audit report as PDF?</h2>
75
  <p>Choose where to save it. If you do not answer, it is saved automatically in
76
- <b>Documents › Devseis Endpoint Audit</b> in <b id="countdown">60</b> seconds.</p>
77
  <div class="progress thin"><div id="countdown-bar"></div></div>
78
  <div class="actions">
79
  <button class="secondary" id="save-docs">Save to Documents now</button>
 
4
  <meta charset="utf-8">
5
  <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self'; connect-src 'self' https://huggingface.co https://*.huggingface.co https://*.hf.co https://raw.githubusercontent.com; img-src 'self' data:; worker-src 'self' blob:">
6
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>Arx</title>
8
  <link rel="stylesheet" href="styles.css">
9
  </head>
10
  <body>
11
  <header class="bar">
12
+ <div class="brand"><span class="dot"></span> Arx</div>
13
  <div class="device" id="device"></div>
14
  </header>
15
 
 
55
  <div class="actions">
56
  <button class="secondary" id="again">Run another audit</button>
57
  <button class="primary" id="save-again">Save report as PDF</button>
58
+ <button class="secondary" id="ask-chat">📋 Open report with fixes</button>
59
  </div>
60
  <p class="muted" id="saved-note"></p>
61
  </section>
 
73
  <div class="modal-box">
74
  <h2 id="save-title">Save the audit report as PDF?</h2>
75
  <p>Choose where to save it. If you do not answer, it is saved automatically in
76
+ <b>Documents › Arx Reports</b> in <b id="countdown">60</b> seconds.</p>
77
  <div class="progress thin"><div id="countdown-bar"></div></div>
78
  <div class="actions">
79
  <button class="secondary" id="save-docs">Save to Documents now</button>
app/renderer/report.js CHANGED
@@ -41,7 +41,7 @@ export function buildReportHtml({ evidence, findings, summary, ai, sha256, model
41
  <td>${esc(t.gdpr)}</td><td>${esc(t.action)}</td></tr>`).join('')}
42
  </tbody></table><p class="muted">${esc(ai.log_retention)} The purpose of use was not stated; the classification must be confirmed for each actual use.</p>` : '';
43
 
44
- return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Devseis Endpoint Audit — ${esc(host.hostname)}</title>
45
  <style>
46
  body { font: 10.5pt/1.45 -apple-system, "Segoe UI", Roboto, Arial, sans-serif; color: #16181d; margin: 0; }
47
  .cover { border-top: 6px solid #ED2939; padding: 18px 0 10px; margin-bottom: 14px; }
 
41
  <td>${esc(t.gdpr)}</td><td>${esc(t.action)}</td></tr>`).join('')}
42
  </tbody></table><p class="muted">${esc(ai.log_retention)} The purpose of use was not stated; the classification must be confirmed for each actual use.</p>` : '';
43
 
44
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8"><title>Arx audit — ${esc(host.hostname)}</title>
45
  <style>
46
  body { font: 10.5pt/1.45 -apple-system, "Segoe UI", Roboto, Arial, sans-serif; color: #16181d; margin: 0; }
47
  .cover { border-top: 6px solid #ED2939; padding: 18px 0 10px; margin-bottom: 14px; }
index.html CHANGED
@@ -3,8 +3,8 @@
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
- <meta name="description" content="Arx by Devseis: a free, read-only security audit for your Mac, Windows or Linux computer. 68 checks in about a minute, offline, mapped to ISO 27001, GDPR and the EU AI Act. Nothing is uploaded.">
7
- <meta name="theme-color" content="#f7f7f7">
8
  <title>Arx · Security audit for your computer</title>
9
  <link rel="icon" href="app/build/icon.png">
10
  <link rel="apple-touch-icon" href="app/build/icon.png">
@@ -17,7 +17,7 @@
17
  <body>
18
  <header class="nav">
19
  <div class="wrap nav-inner">
20
- <a class="brand" href="index.html"><img src="app/build/icon.png" alt=""><b>Arx</b><span class="muted hide-xs">by Devseis</span></a>
21
  <nav class="nav-links">
22
  <a href="#checks">Checks</a><a href="#how">How it works</a><a href="#about">About</a>
23
  </nav>
@@ -32,7 +32,7 @@
32
  <main id="landing">
33
  <section class="wrap hero">
34
  <p class="eyebrow"><img src="app/build/icon.png" alt="">Arx security audit</p>
35
- <h1>Is your computer secure?<br><span class="soft">Find out in a minute.</span></h1>
36
  <p class="lead">A free, read-only audit of your Mac, Windows or Linux computer: <b>68 checks</b> mapped to <b>ISO 27001</b>, <b>GDPR</b> and the <b>EU AI Act</b>. It runs offline, and a small AI on your computer writes the report. <u>Nothing is uploaded.</u></p>
37
 
38
  <div class="device-card" id="deviceCard">
@@ -56,15 +56,15 @@
56
  <label class="btn btn-lg" for="reportFile" title="Open the …-evidence.json file Arx saves next to each report">Open a report</label>
57
  <input type="file" id="reportFile" accept=".json,application/json" hidden>
58
  </div>
59
- <p class="tiny muted" id="heroMeta">Free · open source · version 0.4.2</p>
60
  </section>
61
 
62
  <section class="wrap">
63
  <div class="stats">
64
- <div class="stat"><span class="tiny muted">Checks</span><b>68</b><span class="tiny muted">settings &amp; controls</span></div>
65
- <div class="stat"><span class="tiny muted">Time</span><b>~1 min</b><span class="tiny muted">for the whole audit</span></div>
66
- <div class="stat"><span class="tiny muted">Uploaded</span><b>0 bytes</b><span class="tiny muted">runs offline</span></div>
67
- <div class="stat"><span class="tiny muted">Frameworks</span><b>3</b><span class="tiny muted">ISO · GDPR · AI Act</span></div>
68
  </div>
69
  </section>
70
 
@@ -95,7 +95,7 @@
95
  </section>
96
 
97
  <section class="wrap section" id="download">
98
- <div class="section-head"><h2>Download Arx</h2><span class="tiny muted" id="dlVersion">Version 0.4.2</span></div>
99
  <div class="dl-grid">
100
  <article class="dl-card" data-os="mac">
101
  <span class="ribbon ribbon-you hidden">YOUR SYSTEM</span>
@@ -122,16 +122,50 @@
122
  <details class="hash"><summary>SHA-256</summary><code data-hash="linux">loading…</code></details>
123
  </article>
124
  </div>
125
- <p class="tiny muted">If macOS says the app is from an unidentified developer: right-click it, choose Open, then Open again. All files: <a class="link" href="https://huggingface.co/datasets/Devseis/endpoint-auditor-releases">releases</a>.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  </section>
127
 
128
  <section class="wrap section" id="about">
129
  <div class="section-head"><h2>Why trust it</h2></div>
130
  <div class="trust-grid">
131
- <div class="card"><b>🔒 Read-only</b><p class="muted">Arx reads settings and never changes them. You decide what to fix.</p></div>
132
- <div class="card"><b>📴 Offline and private</b><p class="muted">The audit and the AI run on your computer. The report stays there unless you share it.</p></div>
133
- <div class="card"><b>🧾 Grounded report</b><p class="muted">The AI writes only from what the audit found and the Devseis library, and each answer is checked against both. If a check fails, Arx uses a fixed template instead.</p></div>
134
- <div class="card"><b>📖 Open source</b><p class="muted">Code Apache-2.0, data CC BY 4.0. <a class="link" href="https://huggingface.co/spaces/Devseis/Arx/tree/main">Read the source</a>.</p></div>
135
  </div>
136
  <details class="faq"><summary>Is this a certification?</summary><p class="muted">No. ISO 27001 certification needs an accredited auditor, and much of GDPR and AI Act compliance is organisational. Arx collects evidence from the computer and flags the gaps.</p></details>
137
  <details class="faq"><summary>I'm setting this up for a company</summary><p class="muted">Before running, edit <code>baseline.conf</code> (approved admins, approved AI tools, limits) and <code>organisational.conf</code> (8 company questions). They are in <code>~/Library/Application Support/Devseis Endpoint Auditor</code> on Mac, <code>%APPDATA%\Devseis Endpoint Auditor</code> on Windows and <code>~/.config/Devseis Endpoint Auditor</code> on Linux.</p></details>
@@ -162,10 +196,19 @@
162
 
163
  <footer class="foot">
164
  <div class="wrap foot-inner">
165
- <div><b>Arx</b> <span class="muted">by Devseis · a free security audit for your computer</span></div>
166
  <div class="foot-links"><a href="#download">Download</a><a href="https://huggingface.co/spaces/Devseis/Arx/tree/main">Source</a><a href="https://huggingface.co/datasets/Devseis/endpoint-auditor-releases">Releases</a><a href="https://huggingface.co/Devseis/endpoint-auditor-0.5b">AI model</a><a href="CITATION.cff">Cite</a></div>
167
  </div>
168
  </footer>
 
 
 
 
 
 
 
 
 
169
  <div class="toast hidden" id="toast" role="status"></div>
170
  <script type="module" src="site/site.js"></script>
171
  </body>
 
3
  <head>
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
6
+ <meta name="description" content="Arx: a free, read-only security audit for your Mac, Windows or Linux computer. 68 checks in about a minute, offline, mapped to ISO 27001, GDPR and the EU AI Act. Nothing is uploaded.">
7
+ <meta name="theme-color" content="#ed2939">
8
  <title>Arx · Security audit for your computer</title>
9
  <link rel="icon" href="app/build/icon.png">
10
  <link rel="apple-touch-icon" href="app/build/icon.png">
 
17
  <body>
18
  <header class="nav">
19
  <div class="wrap nav-inner">
20
+ <a class="brand" href="index.html"><img src="app/build/icon.png" alt=""><b>Arx</b></a>
21
  <nav class="nav-links">
22
  <a href="#checks">Checks</a><a href="#how">How it works</a><a href="#about">About</a>
23
  </nav>
 
32
  <main id="landing">
33
  <section class="wrap hero">
34
  <p class="eyebrow"><img src="app/build/icon.png" alt="">Arx security audit</p>
35
+ <h1>Is your computer secure?<br><span class="grad">Find out in a minute.</span></h1>
36
  <p class="lead">A free, read-only audit of your Mac, Windows or Linux computer: <b>68 checks</b> mapped to <b>ISO 27001</b>, <b>GDPR</b> and the <b>EU AI Act</b>. It runs offline, and a small AI on your computer writes the report. <u>Nothing is uploaded.</u></p>
37
 
38
  <div class="device-card" id="deviceCard">
 
56
  <label class="btn btn-lg" for="reportFile" title="Open the …-evidence.json file Arx saves next to each report">Open a report</label>
57
  <input type="file" id="reportFile" accept=".json,application/json" hidden>
58
  </div>
59
+ <p class="tiny muted" id="heroMeta">Free · open source · version 0.4.3</p>
60
  </section>
61
 
62
  <section class="wrap">
63
  <div class="stats">
64
+ <div class="stat c-red"><span class="tiny muted">Checks</span><b>68</b><span class="tiny muted">settings &amp; controls</span></div>
65
+ <div class="stat c-blue"><span class="tiny muted">Time</span><b>~1 min</b><span class="tiny muted">for the whole audit</span></div>
66
+ <div class="stat c-green"><span class="tiny muted">Uploaded</span><b>0 bytes</b><span class="tiny muted">runs offline</span></div>
67
+ <div class="stat c-violet"><span class="tiny muted">Frameworks</span><b>3</b><span class="tiny muted">ISO · GDPR · AI Act</span></div>
68
  </div>
69
  </section>
70
 
 
95
  </section>
96
 
97
  <section class="wrap section" id="download">
98
+ <div class="section-head"><h2>Download Arx</h2><span class="tiny muted" id="dlVersion">Version 0.4.3</span></div>
99
  <div class="dl-grid">
100
  <article class="dl-card" data-os="mac">
101
  <span class="ribbon ribbon-you hidden">YOUR SYSTEM</span>
 
122
  <details class="hash"><summary>SHA-256</summary><code data-hash="linux">loading…</code></details>
123
  </article>
124
  </div>
125
+ <p class="tiny muted">Your computer may warn you the first time you open Arx, because it is free and not yet registered with Apple or Microsoft. <a class="link" href="#first-open">See how to open it →</a> All files: <a class="link" href="https://huggingface.co/datasets/Devseis/endpoint-auditor-releases">releases</a>.</p>
126
+ </section>
127
+
128
+ <section class="wrap section" id="first-open">
129
+ <div class="section-head"><h2>Opening Arx the first time</h2><span class="tiny muted">one-time, about 30 seconds</span></div>
130
+ <div class="seg seg-help" id="helpSeg">
131
+ <button type="button" data-help="mac">macOS</button><button type="button" data-help="windows">Windows</button><button type="button" data-help="linux">Linux</button>
132
+ </div>
133
+ <div class="help-panel" data-help="mac">
134
+ <p class="muted help-why">Arx is free and not yet registered with Apple, so macOS blocks it the first time. This is normal: you allow it once and it opens normally after that.</p>
135
+ <ol class="help-steps">
136
+ <li><span class="hs-n">1</span><div><b>Open the downloaded file</b><p class="muted">Double-click <code>Arx-…-mac-universal.dmg</code> in Downloads, then drag <b>Arx</b> onto <b>Applications</b>.</p></div></li>
137
+ <li><span class="hs-n">2</span><div><b>Open Arx from Applications</b><p class="muted">macOS shows <i>"Arx" Not Opened</i> or <i>Apple could not verify…</i>. Click <b>Done</b> (not Move to Bin).</p>
138
+ <div class="mock mock-alert"><div class="mock-icon">⚠️</div><b>"Arx" Not Opened</b><span>Apple could not verify "Arx" is free of malware…</span><div class="mock-btns"><span class="mock-btn">Move to Bin</span><span class="mock-btn mock-blue">Done</span></div></div></div></li>
139
+ <li><span class="hs-n">3</span><div><b>Allow it in System Settings</b><p class="muted">Open the Apple menu › <b>System Settings</b> › <b>Privacy &amp; Security</b>. Scroll down to <b>Security</b> and click <b>Open Anyway</b> next to "Arx" was blocked…, then enter your Mac password.</p>
140
+ <div class="mock mock-settings"><span class="muted">Security</span><div class="mock-row"><span>"Arx" was blocked to protect your Mac.</span><span class="mock-btn mock-hl">Open Anyway</span></div></div></div></li>
141
+ <li><span class="hs-n">4</span><div><b>Click Open</b><p class="muted">macOS asks one last time: click <b>Open Anyway</b>. From now on Arx opens like any other app.</p></div></li>
142
+ </ol>
143
+ </div>
144
+ <div class="help-panel hidden" data-help="windows">
145
+ <p class="muted help-why">Arx is free and not yet signed with a Microsoft certificate, so Windows SmartScreen may warn you once.</p>
146
+ <ol class="help-steps">
147
+ <li><span class="hs-n">1</span><div><b>Run the downloaded file</b><p class="muted">Double-click <code>Arx-…-windows-x64-setup.exe</code> in Downloads.</p></div></li>
148
+ <li><span class="hs-n">2</span><div><b>If you see "Windows protected your PC"</b><p class="muted">Click <b>More info</b>, then <b>Run anyway</b>.</p>
149
+ <div class="mock mock-win"><b>Windows protected your PC</b><span>Microsoft Defender SmartScreen prevented an unrecognised app from starting…</span><span class="mock-link">More info</span><div class="mock-btns"><span class="mock-btn mock-hl">Run anyway</span><span class="mock-btn">Don't run</span></div></div></div></li>
150
+ <li><span class="hs-n">3</span><div><b>Install and open</b><p class="muted">Follow the installer; Arx is then on your desktop and in the Start menu.</p></div></li>
151
+ </ol>
152
+ </div>
153
+ <div class="help-panel hidden" data-help="linux">
154
+ <p class="muted help-why">No warning to get past: install the package or make the AppImage runnable.</p>
155
+ <ol class="help-steps">
156
+ <li><span class="hs-n">1</span><div><b>Ubuntu, Debian, Mint, Pop!_OS</b><p class="muted">Double-click the <code>.deb</code> file to install it with Software, or run <code>sudo apt install ./Arx-…-linux-x86_64.deb</code>.</p></div></li>
157
+ <li><span class="hs-n">2</span><div><b>Any other distribution</b><p class="muted">Run <code>chmod +x Arx-…-linux-x86_64.AppImage</code>, then double-click it.</p></div></li>
158
+ </ol>
159
+ </div>
160
  </section>
161
 
162
  <section class="wrap section" id="about">
163
  <div class="section-head"><h2>Why trust it</h2></div>
164
  <div class="trust-grid">
165
+ <div class="card c-red"><b>🔒 Read-only</b><p class="muted">Arx reads settings and never changes them. You decide what to fix.</p></div>
166
+ <div class="card c-blue"><b>📴 Offline and private</b><p class="muted">The audit and the AI run on your computer. The report stays there unless you share it.</p></div>
167
+ <div class="card c-green"><b>🧾 Grounded report</b><p class="muted">The AI writes only from what the audit found and the Devseis library, and each answer is checked against both. If a check fails, Arx uses a fixed template instead.</p></div>
168
+ <div class="card c-violet"><b>📖 Open source</b><p class="muted">Code Apache-2.0, data CC BY 4.0. <a class="link" href="https://huggingface.co/spaces/Devseis/Arx/tree/main">Read the source</a>.</p></div>
169
  </div>
170
  <details class="faq"><summary>Is this a certification?</summary><p class="muted">No. ISO 27001 certification needs an accredited auditor, and much of GDPR and AI Act compliance is organisational. Arx collects evidence from the computer and flags the gaps.</p></details>
171
  <details class="faq"><summary>I'm setting this up for a company</summary><p class="muted">Before running, edit <code>baseline.conf</code> (approved admins, approved AI tools, limits) and <code>organisational.conf</code> (8 company questions). They are in <code>~/Library/Application Support/Devseis Endpoint Auditor</code> on Mac, <code>%APPDATA%\Devseis Endpoint Auditor</code> on Windows and <code>~/.config/Devseis Endpoint Auditor</code> on Linux.</p></details>
 
196
 
197
  <footer class="foot">
198
  <div class="wrap foot-inner">
199
+ <div><b>Arx</b> <span class="muted">· a free security audit for your computer</span></div>
200
  <div class="foot-links"><a href="#download">Download</a><a href="https://huggingface.co/spaces/Devseis/Arx/tree/main">Source</a><a href="https://huggingface.co/datasets/Devseis/endpoint-auditor-releases">Releases</a><a href="https://huggingface.co/Devseis/endpoint-auditor-0.5b">AI model</a><a href="CITATION.cff">Cite</a></div>
201
  </div>
202
  </footer>
203
+ <div class="modal-back hidden" id="dlModal" role="dialog" aria-modal="true" aria-labelledby="dlModalTitle">
204
+ <div class="modal">
205
+ <button type="button" class="icon-btn modal-x" id="dlModalClose" aria-label="Close">✕</button>
206
+ <p class="eyebrow">⬇️ Downloading Arx…</p>
207
+ <h2 id="dlModalTitle">Next: open Arx</h2>
208
+ <div id="dlModalBody"></div>
209
+ <button type="button" class="btn btn-primary" id="dlModalOk">Got it</button>
210
+ </div>
211
+ </div>
212
  <div class="toast hidden" id="toast" role="status"></div>
213
  <script type="module" src="site/site.js"></script>
214
  </body>
site/site.css CHANGED
@@ -3,7 +3,9 @@
3
  --bg: #f7f7f7; --card: #ffffff; --fg: #0a0a0a; --muted: #737373; --soft: #a3a3a3; --border: #e5e5e5;
4
  --primary: #171717; --primary-fg: #fafafa; --hover: #f2f2f2; --chip: #f2f2f2;
5
  --high-bg: #fee2e2; --high-fg: #b91c1c; --med-bg: #fef3c7; --med-fg: #b45309; --low-bg: #e0f2fe; --low-fg: #0369a1;
6
- --ok-bg: #dcfce7; --ok-fg: #15803d; --brand: #ed2939; --radius: 10px;
 
 
7
  --font: 'Inconsolata', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
8
  color-scheme: light;
9
  }
@@ -12,14 +14,14 @@
12
  --bg: #0a0a0a; --card: #171717; --fg: #fafafa; --muted: #a3a3a3; --soft: #737373; --border: rgba(255,255,255,.1);
13
  --primary: #e5e5e5; --primary-fg: #171717; --hover: #262626; --chip: #262626;
14
  --high-bg: rgba(239,68,68,.15); --high-fg: #f87171; --med-bg: rgba(245,158,11,.15); --med-fg: #fbbf24; --low-bg: rgba(14,165,233,.15); --low-fg: #38bdf8;
15
- --ok-bg: rgba(34,197,94,.15); --ok-fg: #4ade80; color-scheme: dark;
16
  }
17
  }
18
  :root[data-theme="dark"] {
19
  --bg: #0a0a0a; --card: #171717; --fg: #fafafa; --muted: #a3a3a3; --soft: #737373; --border: rgba(255,255,255,.1);
20
  --primary: #e5e5e5; --primary-fg: #171717; --hover: #262626; --chip: #262626;
21
  --high-bg: rgba(239,68,68,.15); --high-fg: #f87171; --med-bg: rgba(245,158,11,.15); --med-fg: #fbbf24; --low-bg: rgba(14,165,233,.15); --low-fg: #38bdf8;
22
- --ok-bg: rgba(34,197,94,.15); --ok-fg: #4ade80; color-scheme: dark;
23
  }
24
 
25
  * { box-sizing: border-box; }
@@ -212,3 +214,53 @@ p { margin: 0; }
212
  .finding { break-inside: avoid; }
213
  details.finding:not([open]) > .f-body, .other-results:not([open]) > .findings { display: flex !important; }
214
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  --bg: #f7f7f7; --card: #ffffff; --fg: #0a0a0a; --muted: #737373; --soft: #a3a3a3; --border: #e5e5e5;
4
  --primary: #171717; --primary-fg: #fafafa; --hover: #f2f2f2; --chip: #f2f2f2;
5
  --high-bg: #fee2e2; --high-fg: #b91c1c; --med-bg: #fef3c7; --med-fg: #b45309; --low-bg: #e0f2fe; --low-fg: #0369a1;
6
+ --ok-bg: #dcfce7; --ok-fg: #15803d; --brand: #ed2939; --brand-2: #f97316; --radius: 10px;
7
+ --red: #ed2939; --blue: #2563eb; --green: #16a34a; --violet: #7c3aed; --amber: #d97706;
8
+ --tint: 8%; --glow1: rgba(237,41,57,.16); --glow2: rgba(249,115,22,.12); --glow3: rgba(124,58,237,.12);
9
  --font: 'Inconsolata', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
10
  color-scheme: light;
11
  }
 
14
  --bg: #0a0a0a; --card: #171717; --fg: #fafafa; --muted: #a3a3a3; --soft: #737373; --border: rgba(255,255,255,.1);
15
  --primary: #e5e5e5; --primary-fg: #171717; --hover: #262626; --chip: #262626;
16
  --high-bg: rgba(239,68,68,.15); --high-fg: #f87171; --med-bg: rgba(245,158,11,.15); --med-fg: #fbbf24; --low-bg: rgba(14,165,233,.15); --low-fg: #38bdf8;
17
+ --ok-bg: rgba(34,197,94,.15); --ok-fg: #4ade80; --red: #f43f5e; --blue: #60a5fa; --green: #4ade80; --violet: #a78bfa; --amber: #fbbf24; --tint: 14%; --glow1: rgba(244,63,94,.18); --glow2: rgba(249,115,22,.12); --glow3: rgba(167,139,250,.14); color-scheme: dark;
18
  }
19
  }
20
  :root[data-theme="dark"] {
21
  --bg: #0a0a0a; --card: #171717; --fg: #fafafa; --muted: #a3a3a3; --soft: #737373; --border: rgba(255,255,255,.1);
22
  --primary: #e5e5e5; --primary-fg: #171717; --hover: #262626; --chip: #262626;
23
  --high-bg: rgba(239,68,68,.15); --high-fg: #f87171; --med-bg: rgba(245,158,11,.15); --med-fg: #fbbf24; --low-bg: rgba(14,165,233,.15); --low-fg: #38bdf8;
24
+ --ok-bg: rgba(34,197,94,.15); --ok-fg: #4ade80; --red: #f43f5e; --blue: #60a5fa; --green: #4ade80; --violet: #a78bfa; --amber: #fbbf24; --tint: 14%; --glow1: rgba(244,63,94,.18); --glow2: rgba(249,115,22,.12); --glow3: rgba(167,139,250,.14); color-scheme: dark;
25
  }
26
 
27
  * { box-sizing: border-box; }
 
214
  .finding { break-inside: avoid; }
215
  details.finding:not([open]) > .f-body, .other-results:not([open]) > .findings { display: flex !important; }
216
  }
217
+
218
+ /* ---------- colour ---------- */
219
+ body { background: radial-gradient(900px 420px at 15% -80px, var(--glow1), transparent 70%), radial-gradient(800px 380px at 85% -60px, var(--glow3), transparent 70%), radial-gradient(700px 360px at 50% 260px, var(--glow2), transparent 70%), var(--bg); background-repeat: no-repeat; }
220
+ .grad { background: linear-gradient(90deg, var(--brand), var(--brand-2) 55%, var(--violet)); -webkit-background-clip: text; background-clip: text; color: transparent; }
221
+ .btn-primary { background: linear-gradient(135deg, var(--brand), #d61f3a); color: #fff; border-color: transparent; box-shadow: 0 6px 18px -8px var(--brand); }
222
+ .btn-primary:hover { background: linear-gradient(135deg, #f0384a, var(--brand)); }
223
+ .step-n { background: linear-gradient(135deg, var(--brand), var(--brand-2)); color: #fff; }
224
+ .pill[aria-pressed="true"] { background: var(--brand); border-color: var(--brand); color: #fff; }
225
+ .pill[aria-pressed="true"] .soft { color: rgba(255,255,255,.75); }
226
+ .c-red { --c: var(--red); } .c-blue { --c: var(--blue); } .c-green { --c: var(--green); } .c-violet { --c: var(--violet); }
227
+ .stat[class*="c-"], .card[class*="c-"] { border-top: 3px solid var(--c); background: linear-gradient(180deg, color-mix(in srgb, var(--c) var(--tint), var(--card)), var(--card) 70%); }
228
+ .stat[class*="c-"] b { color: var(--c); }
229
+ .check-card:hover { border-color: var(--red); box-shadow: 0 8px 24px -14px var(--red); }
230
+ .dl-card { border-top: 3px solid var(--c, var(--border)); }
231
+ .dl-card[data-os="mac"] { --c: var(--violet); } .dl-card[data-os="windows"] { --c: var(--blue); } .dl-card[data-os="linux"] { --c: var(--amber); }
232
+ .dl-card.you { box-shadow: 0 10px 30px -18px var(--c); border-color: var(--c); }
233
+ .bar i { background: linear-gradient(90deg, var(--brand), var(--brand-2)); }
234
+ .eyebrow { color: var(--brand); }
235
+ .link { color: var(--blue); }
236
+ .table tbody tr:hover td { background: color-mix(in srgb, var(--red) 5%, var(--card)); }
237
+
238
+ /* ---------- first-open help ---------- */
239
+ .seg-help { margin-bottom: 12px; }
240
+ .help-why { margin: 0 2px 12px; font-size: 14px; }
241
+ .help-steps { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
242
+ .help-steps li { display: flex; gap: 12px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; }
243
+ .help-steps p { font-size: 14px; margin-top: 4px; }
244
+ .hs-n { flex: 0 0 26px; height: 26px; border-radius: 50%; background: var(--blue); color: #fff; display: grid; place-items: center; font-weight: 800; font-size: 13px; }
245
+ .mock { margin-top: 10px; border-radius: 12px; padding: 12px; font-family: -apple-system, 'Segoe UI', system-ui, sans-serif; font-size: 12px; display: flex; flex-direction: column; gap: 6px; }
246
+ .mock-alert { background: var(--chip); align-items: center; text-align: center; max-width: 260px; }
247
+ .mock-icon { font-size: 22px; }
248
+ .mock-settings { background: var(--chip); }
249
+ .mock-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; background: var(--card); border-radius: 8px; padding: 8px 10px; }
250
+ .mock-win { background: #1e5aa8; color: #fff; border-radius: 4px; }
251
+ .mock-link { text-decoration: underline; }
252
+ .mock-btns { display: flex; gap: 6px; justify-content: center; }
253
+ .mock-btn { padding: 4px 10px; border-radius: 6px; background: var(--card); color: var(--fg); border: 1px solid var(--border); white-space: nowrap; }
254
+ .mock-blue { background: #0a84ff; color: #fff; border-color: #0a84ff; }
255
+ .mock-hl { background: #0a84ff; color: #fff; border-color: #0a84ff; box-shadow: 0 0 0 3px rgba(237,41,57,.55); }
256
+ .mock-win .mock-btns { justify-content: flex-end; }
257
+ .mock-win .mock-btn { background: #fff; color: #000; border-radius: 0; }
258
+
259
+ /* ---------- after-download modal ---------- */
260
+ .modal-back { position: fixed; inset: 0; z-index: 60; background: rgba(0,0,0,.45); display: grid; place-items: center; padding: 16px; }
261
+ .modal { position: relative; width: min(560px, 100%); max-height: calc(100vh - 32px); overflow: auto; background: var(--card); border: 1px solid var(--border); border-top: 4px solid var(--brand); border-radius: 14px; padding: 22px 20px 18px; display: flex; flex-direction: column; gap: 12px; }
262
+ .modal h2 { font-size: 22px; }
263
+ .modal-x { position: absolute; top: 12px; right: 12px; }
264
+ .modal .help-steps { grid-template-columns: 1fr; }
265
+ .modal .eyebrow { margin: 0; }
266
+ @media (max-width: 760px) { .help-steps { grid-template-columns: 1fr; } }
site/site.js CHANGED
@@ -47,8 +47,35 @@ async function detect() {
47
  return { phone, os, browser, version, arch };
48
  }
49
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  function chooseOs(os) {
51
  state.os = os;
 
52
  document.querySelectorAll('#osSeg button').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.os === os)));
53
  document.querySelectorAll('.dl-card').forEach(c => {
54
  c.classList.toggle('you', c.dataset.os === os);
 
47
  return { phone, os, browser, version, arch };
48
  }
49
 
50
+ // ---------- first-open help + the pop-up after a download ----------
51
+ function showHelp(os) {
52
+ document.querySelectorAll('#helpSeg button').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.help === os)));
53
+ document.querySelectorAll('#first-open .help-panel').forEach(p => p.classList.toggle('hidden', p.dataset.help !== os));
54
+ }
55
+ document.querySelectorAll('#helpSeg button').forEach(b => b.addEventListener('click', () => showHelp(b.dataset.help)));
56
+
57
+ function openDlModal(os) {
58
+ const panel = document.querySelector(`#first-open .help-panel[data-help="${os}"]`);
59
+ $('dlModalBody').innerHTML = panel.innerHTML;
60
+ $('dlModal').classList.remove('hidden');
61
+ $('dlModalOk').focus();
62
+ }
63
+ const closeDlModal = () => $('dlModal').classList.add('hidden');
64
+ $('dlModalClose').addEventListener('click', closeDlModal);
65
+ $('dlModalOk').addEventListener('click', closeDlModal);
66
+ $('dlModal').addEventListener('click', e => { if (e.target === $('dlModal')) closeDlModal(); });
67
+ addEventListener('keydown', e => { if (e.key === 'Escape') closeDlModal(); });
68
+ // Any real download link: let the download start, then show what to do next for that system.
69
+ document.addEventListener('click', e => {
70
+ const a = e.target.closest('a[href]');
71
+ if (!a || !a.href.startsWith(RELEASES)) return;
72
+ const os = a.closest('.dl-card')?.dataset.os || state.os;
73
+ setTimeout(() => openDlModal(os), 400);
74
+ });
75
+
76
  function chooseOs(os) {
77
  state.os = os;
78
+ showHelp(os);
79
  document.querySelectorAll('#osSeg button').forEach(b => b.setAttribute('aria-pressed', String(b.dataset.os === os)));
80
  document.querySelectorAll('.dl-card').forEach(c => {
81
  c.classList.toggle('you', c.dataset.os === os);