nio-cat 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/nio.js CHANGED
@@ -46,12 +46,14 @@ const c = {
46
46
  warn: (s) => `\x1b[33m${s}\x1b[0m`,
47
47
  };
48
48
 
49
+ const row = (label, value, note) => console.log(` ${c.dim(label.padEnd(10))} ${value}${note ? c.dim(' ' + note) : ''}`);
50
+
49
51
  const cfg = config.load();
50
52
  const auth = new Auth(undefined, cfg);
51
53
 
52
54
  async function doctor() {
53
55
  const agents = await detectAll(cfg, true);
54
- console.log(c.b('\n Agents instal·lats\n'));
56
+ console.log(c.b('\n Agents\n'));
55
57
  for (const a of agents) {
56
58
  const mark = a.available ? c.ok('✓') : c.dim('·');
57
59
  const v = a.available ? c.dim(a.version || '') : c.dim(`no hi és · ${a.login}`);
@@ -93,8 +95,8 @@ function openDashboard() {
93
95
  const online = !flags.local;
94
96
  // &qr=1: el panell mostra el QR per al mòbil tot just obrir-se, a la mateixa pestanya.
95
97
  const link = pairLink(online ? flags.app || cfg.app : local, local, cfg.token) + '&qr=1';
96
- if (canOpenBrowser() && openUrl(link)) { panelOpened = true; console.log(` ${c.dim('Panell:')} ${online ? (flags.app || cfg.app) + '/app' : local} ${c.dim('(obert al navegador; --no-open per evitar-ho' + (online ? '; --local per fer servir la còpia d\'aquest ordinador' : '') + ')')}`); }
97
- else console.log(` ${c.dim('Panell:')} ${link}`);
98
+ if (canOpenBrowser() && openUrl(link)) { panelOpened = true; row('Panell', online ? (flags.app || cfg.app) + '/app' : local, '(obert al navegador)'); }
99
+ else row('Panell', link);
98
100
  }
99
101
 
100
102
  async function showPair(publicUrl) {
@@ -110,18 +112,26 @@ async function showPair(publicUrl) {
110
112
  const link = pairLink(app, publicUrl, secret), code = makeCode(publicUrl, secret), qrLink = codeLink(app, code);
111
113
  const file = writePairPage(link, code, qrLink);
112
114
  // El QR ha de cabre al terminal: marge normal, marge d'1 mòdul i, si no hi cap, la pàgina del navegador.
113
- const rows = process.stdout.rows || 999, cols = process.stdout.columns || 999;
114
- const fits = (m) => { const z = terminalQrSize(qrLink, m); return z.rows + 9 <= rows && z.cols + 2 <= cols; };
115
- const margin = fits(2) ? 2 : fits(1) ? 1 : 0;
116
- if (margin) {
117
- console.log(`\n ${c.b('Escaneja aquest codi amb el mòbil')}\n`);
118
- console.log(terminalQr(qrLink, { margin }));
115
+ if (panelOpened) {
116
+ console.log(`\n ${c.b('Connecta el mòbil')}`);
117
+ console.log(` Al panell del navegador prem ${c.b('«Continua al mòbil»')} i escaneja el codi.`);
118
+ console.log(c.dim(' També pots enganxar aquest codi a Sessions → Tria un ordinador:\n'));
119
+ console.log(` ${code}\n`);
120
+ console.log(c.dim(' Serveix per a un sol dispositiu i caduca en 15 minuts. Per a un altre: nio pair.\n'));
119
121
  } else {
120
- console.log(`\n ${c.warn('El terminal és massa petit per al QR.')} ${panelOpened ? 'El tens al panell del navegador.' : 'Fes-lo més gran, o obre el panell i prem «Continua al mòbil».'}`);
122
+ const rows = process.stdout.rows || 999, cols = process.stdout.columns || 999;
123
+ const fits = (m) => { const z = terminalQrSize(qrLink, m); return z.rows + 9 <= rows && z.cols + 2 <= cols; };
124
+ const margin = fits(2) ? 2 : fits(1) ? 1 : 0;
125
+ console.log(`\n ${c.b('Connecta el mòbil')}\n`);
126
+ if (margin) {
127
+ console.log(` Escaneja aquest codi amb la càmera:\n`);
128
+ console.log(terminalQr(qrLink, { margin }));
129
+ console.log('');
130
+ }
131
+ console.log(` ${margin ? 'O copia' : 'Copia'} aquest codi i enganxa'l a Sessions → Tria un ordinador:\n`);
132
+ console.log(` ${code}\n`);
133
+ console.log(c.dim(' Serveix per a un sol dispositiu i caduca en 15 minuts. Per a un altre: nio pair.\n'));
121
134
  }
122
- console.log(`\n ${c.b('Codi:')} ${code}`);
123
- console.log(c.dim(' Copia\'l i enganxa\'l a Sessions → Tria un ordinador.'));
124
- console.log(c.dim(' Serveix per a un sol dispositiu i caduca en 15 minuts. Per a un altre: nio pair.\n'));
125
135
  if (flags.open) {
126
136
  const opener = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
127
137
  try {
@@ -168,7 +178,7 @@ function registerMemory(force) {
168
178
  if (which(cfg.agents?.codex?.bin || 'codex')) {
169
179
  try { out.push(`Codex ${mcp.registerCodex()}`); } catch (e) { out.push(`Codex: ${e.message}`); }
170
180
  }
171
- console.log(` ${c.dim('Memòria:')} ${memory.DIR}${out.length ? c.dim(` (${out.join(' · ')})`) : ''}`);
181
+ row('Memòria', memory.DIR, out.length ? `(${out.join(' · ')})` : '');
172
182
  }
173
183
 
174
184
  async function start() {
@@ -181,7 +191,7 @@ async function start() {
181
191
 
182
192
  console.log(`\n ${c.b('nio')} ${c.dim('· agents d’IA al teu ordinador, des del mòbil')}`);
183
193
  await doctor();
184
- console.log(` ${c.dim('Carpetes compartides:')} ${cfg.roots.join(', ')}`);
194
+ row('Carpetes', cfg.roots.join(', '));
185
195
 
186
196
  // Sense cloudflared no hi ha túnel: en lloc de quedar-nos sense codi QR, connectem per la wifi.
187
197
  if (!flags.lan && !flags.url && !flags['no-tunnel'] && !which('cloudflared')) {
@@ -215,7 +225,7 @@ async function start() {
215
225
  const tg = new Telegram({ cfg, hub, audit, available: jobs.available });
216
226
  tg.hookUrl = jobs.hookUrl;
217
227
  tg.start();
218
- if (cfg.telegram?.token) console.log(` ${c.dim('Telegram:')} actiu ${c.dim(`(${(cfg.telegram.allowed || []).length} xats autoritzats)`)}`);
228
+ if (cfg.telegram?.token) row('Telegram', 'actiu', `(${(cfg.telegram.allowed || []).length} xats autoritzats)`);
219
229
  const control = new Control({ cfg, hub, audit });
220
230
  const remote = new Remote({ cfg, audit });
221
231
  const routes = [...memory.routes(), ...jobs.routes(), ...voice.routes(cfg), ...tg.routes(), ...control.routes(), ...remote.routes()];
@@ -228,14 +238,14 @@ async function start() {
228
238
  });
229
239
 
230
240
  server.listen(cfg.port, host, () => {
231
- console.log(` ${c.dim('Local:')} http://127.0.0.1:${cfg.port}`);
241
+ row('Local', `http://127.0.0.1:${cfg.port}`);
232
242
  openDashboard();
233
243
 
234
244
  if (flags.lan) {
235
245
  const ip = lanIps()[0];
236
246
  if (ip) {
237
247
  publicUrl = publicUrl || `http://${ip}:${cfg.port}`;
238
- console.log(` ${c.dim('Xarxa:')} ${publicUrl} ${c.dim('(només la mateixa wifi)')}`);
248
+ row('Xarxa', publicUrl, '(només la mateixa wifi)');
239
249
  }
240
250
  }
241
251
 
@@ -251,7 +261,7 @@ async function start() {
251
261
  const named = cfg.tunnel && cfg.tunnel.mode === 'named' && !flags.quick && !flags.url && !flags['no-tunnel'] && !flags.lan ? cfg.tunnel : null;
252
262
  if (named) {
253
263
  const t = startConfiguredTunnel(named, cfg.port, {
254
- onReady: () => console.log(` ${c.dim('Túnel:')} https://${named.host} ${c.ok('connectat')} ${c.dim('(adreça fixa)')}`),
264
+ onReady: () => row('Túnel', `https://${named.host}`, `${c.ok('connectat')} (adreça fixa)`),
255
265
  onDown: (code, wait) => console.log(c.warn(` El túnel s’ha tallat (codi ${code}). El torno a obrir d’aquí a ${Math.round(wait / 1000)} s.`)),
256
266
  });
257
267
  if (t) {
@@ -264,7 +274,7 @@ async function start() {
264
274
  if (fixed) {
265
275
  const nt = startNamedTunnel(cfg.tunnelToken, () => {
266
276
  publicUrl = cfg.fixedUrl;
267
- console.log(` ${c.dim('Adreça fixa:')} ${publicUrl}`);
277
+ row('Adreça', publicUrl, '(fixa)');
268
278
  cfg.lastUrl = publicUrl;
269
279
  config.save(cfg);
270
280
  showPair(publicUrl);
@@ -274,7 +284,7 @@ async function start() {
274
284
 
275
285
  const t = startTunnel(cfg.port, (url) => {
276
286
  publicUrl = url;
277
- console.log(` ${c.dim('Túnel:')} ${url} ${c.dim('(Cloudflare, sense cost)')}`);
287
+ row('Túnel', url, '(Cloudflare, sense cost)');
278
288
  cfg.lastUrl = url;
279
289
  config.save(cfg);
280
290
  showPair(url);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nio-cat",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "nio: fes servir Claude Code, Codex, Grok, Antigravity i Ollama del teu ordinador des del mòbil.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "f.cat",
package/web/bot.css CHANGED
@@ -51,9 +51,8 @@
51
51
 
52
52
  /* Com a O del nom */
53
53
  .word { display: inline-flex; align-items: baseline; }
54
- /* Mides de la «o» de Manrope 600: 0,547 × 0,578 em, traç 0,111 em (vertical) i 0,099 em (horitzontal),
55
- comença 0,031 em després de l'origen i baixa 0,016 em sota la línia base. */
56
- .word .bot { --s: .56em; width: .547em; height: .578em; border-width: .099em .111em; margin-left: .031em; top: .016em; }
54
+ /* Un cercle ple, una mica més gran que la «o» de Manrope perquè tingui cos i s'equilibri amb «ni». */
55
+ .word .bot { --s: .64em; --bw: .17; width: .64em; height: .64em; border-width: .108em; margin-left: .05em; top: .018em; }
57
56
 
58
57
  @media (prefers-reduced-motion: reduce) {
59
58
  .bot, .bot * { animation: none !important; transition: none !important; }
package/web/index.html CHANGED
@@ -31,77 +31,137 @@
31
31
  <link rel="stylesheet" href="/bot.css">
32
32
  <style>
33
33
  :root {
34
- --bg: #f0ede7;
35
- --fg: #272224;
36
- --fg-muted: #69615c;
37
- --accent: #f46a3b;
38
- --border: #cec7bd;
39
- --lemon: #e7e66a;
40
- --red: #e2382b;
34
+ --bg: #f0ede7; --fg: #272224; --fg-muted: #69615c; --accent: #f46a3b; --border: #cec7bd; --lemon: #e7e66a; --red: #e2382b;
35
+ --peach: #f9d9cc; --blue: #d9dff5; --green: #d5e6d0; --lilac: #e8d9ee; --teal: #d3e7ea; --rose: #f3d0d0;
41
36
  --font: "Manrope", system-ui, sans-serif;
37
+ --ease: cubic-bezier(.22, 1, .36, 1);
42
38
  }
43
39
  * { box-sizing: border-box; margin: 0; padding: 0; -webkit-tap-highlight-color: transparent; }
44
- html { -webkit-text-size-adjust: 100%; }
45
- body { background: var(--bg); color: var(--fg); font: 400 19px/1.6 var(--font); letter-spacing: -.02em; -webkit-font-smoothing: antialiased; }
40
+ html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
41
+ body { background: var(--bg); color: var(--fg); font: 400 18px/1.6 var(--font); letter-spacing: -.02em; -webkit-font-smoothing: antialiased; }
46
42
  a { color: inherit; }
47
43
  ::selection { background: var(--lemon); }
48
- :focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; border-radius: 2px; }
49
-
50
- .page { width: min(640px, 100% - 48px); margin: 0 auto; }
51
- .head { padding: 56px 0 0; display: flex; justify-content: space-between; align-items: baseline; }
52
- .head .enter { display: inline-flex; align-items: center; gap: 8px; height: 34px; padding: 0 14px 0 12px; border: 1px solid var(--border); border-radius: 999px; font-size: 14px; font-weight: 500; color: var(--fg); text-decoration: none; transition: border-color .2s, background-color .2s; }
44
+ :focus-visible { outline: 2px solid var(--fg); outline-offset: 3px; border-radius: 4px; }
45
+ .page { width: min(1080px, 100% - 64px); margin: 0 auto; }
46
+ .head { padding: 40px 0 0; display: flex; justify-content: space-between; align-items: center; }
47
+ .mark { font-size: 30px; font-weight: 600; letter-spacing: -.07em; line-height: 1; text-decoration: none; }
48
+ .head .enter { display: inline-flex; align-items: center; gap: 8px; height: 38px; padding: 0 16px 0 14px; border: 1px solid var(--border); border-radius: 999px; font-size: 14px; font-weight: 500; text-decoration: none; transition: border-color .2s, background-color .2s; }
53
49
  .head .enter svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
54
50
  .head .enter:hover { border-color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); }
55
- .mark { font-size: 28px; font-weight: 600; letter-spacing: -.07em; line-height: 1; text-decoration: none; }
56
51
 
57
- main { padding: 96px 0 96px; }
58
- h1 { font-size: clamp(36px, 6vw, 52px); font-weight: 500; line-height: 1.06; letter-spacing: -.055em; text-wrap: balance; }
59
- .lead { margin-top: 28px; font-size: 21px; line-height: 1.5; color: var(--fg-muted); text-wrap: pretty; }
52
+ .hero { padding: 88px 0 24px; max-width: 760px; }
53
+ h1 { font-size: clamp(38px, 6.4vw, 68px); font-weight: 500; line-height: 1.04; letter-spacing: -.058em; text-wrap: balance; }
54
+ .lead { margin-top: 24px; max-width: 34em; font-size: clamp(18px, 2.2vw, 21px); line-height: 1.5; color: var(--fg-muted); text-wrap: pretty; }
55
+ .install { margin-top: 36px; display: flex; align-items: center; gap: 6px; max-width: 560px; padding: 7px 7px 7px 22px; border-radius: 16px; background: var(--fg); color: var(--bg); }
56
+ .install code { background: none; border-radius: 0; flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 9px 0; font: 500 15.5px ui-monospace, "SF Mono", Menlo, monospace; scrollbar-width: none; }
57
+ .install code::-webkit-scrollbar { display: none; }
58
+ .hero .alt { margin-top: 14px; font-size: 15px; color: var(--fg-muted); }
59
+ .copy { flex: none; display: inline-grid; place-items: center; width: 42px; height: 42px; border: 0; border-radius: 11px; background: rgba(255,255,255,.1); color: inherit; cursor: pointer; transition: background-color .2s, transform .12s; }
60
+ .copy:hover { background: rgba(255,255,255,.2); }
61
+ .copy:active { transform: scale(.95); }
62
+ .copy svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
63
+ .copy .ok { display: none; }
64
+ .copy.done .ok { display: block; animation: copy-ok .2s var(--ease); }
65
+ .copy.done .ic { display: none; }
66
+ @keyframes copy-ok { from { opacity: 0; transform: scale(.6); } }
60
67
 
61
- section { margin-top: 56px; }
62
- h2 { font-size: clamp(26px, 3.4vw, 32px); font-weight: 500; line-height: 1.15; letter-spacing: -.045em; margin-bottom: 18px; }
63
- section p { text-wrap: pretty; }
64
- section p + p, section p + ul, section ul + p, section p + .cmd, section .cmd + p { margin-top: 1em; }
68
+ section { margin-top: 96px; scroll-margin-top: 24px; }
69
+ .sec-head { max-width: 640px; margin-bottom: 32px; }
70
+ .eyebrow { display: inline-block; margin-bottom: 12px; font-size: 13px; font-weight: 600; letter-spacing: -.01em; color: var(--fg-muted); }
71
+ h2 { font-size: clamp(28px, 4vw, 42px); font-weight: 500; line-height: 1.1; letter-spacing: -.05em; text-wrap: balance; }
72
+ .sec-head p { margin-top: 14px; color: var(--fg-muted); text-wrap: pretty; }
65
73
  main a { text-decoration-thickness: 1px; text-underline-offset: 4px; text-decoration-color: var(--accent); }
66
74
  main a:hover { color: var(--accent); }
67
- main ul.list { padding-left: 1.2em; display: grid; gap: .5em; }
68
- main code { font: 500 .88em ui-monospace, "SF Mono", Menlo, monospace; padding: 2px 7px; border-radius: 7px; background: #e6e1d8; }
69
- main .cmd { display: flex; align-items: center; gap: 4px; padding: 6px 6px 6px 18px; border-radius: 12px; background: #e6e1d8; }
70
- main .cmd code { flex: 1; min-width: 0; overflow-x: auto; white-space: nowrap; padding: 8px 0; background: none; border-radius: 0; font-size: 16px; scrollbar-width: none; }
71
- main .cmd code::-webkit-scrollbar { display: none; }
72
- .copy { flex: none; display: inline-grid; place-items: center; width: 38px; height: 38px; padding: 0; border: 0; border-radius: 9px; background: none; color: var(--fg-muted); cursor: pointer; transition: color .2s, background-color .2s, transform .12s; }
73
- .copy:hover { color: var(--fg); }
74
- .copy:active { transform: scale(.97); }
75
- .copy svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
76
- .copy .ok { display: none; }
77
- .copy.done .ok { display: block; color: #5b6551; animation: copy-ok .2s cubic-bezier(.22,1,.36,1); }
78
- @keyframes copy-ok { from { opacity: 0; transform: scale(.6); } }
79
- @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none; transition: none; } }
80
- .copy.done .ic { display: none; }
81
75
  main strong { font-weight: 600; }
82
- .feat { list-style: none; display: grid; gap: 14px; padding: 0; margin-top: 4px; }
83
- .feat li { padding-left: 18px; position: relative; text-wrap: pretty; }
84
- .feat li::before { content: ""; position: absolute; left: 0; top: .72em; width: 6px; height: 6px; border-radius: 50%; background: var(--accent); }
85
- .cmds { display: grid; gap: 8px; margin: 16px 0; }
86
- .cmds > div { display: grid; grid-template-columns: minmax(0, 15.5rem) minmax(0, 1fr); gap: 4px 16px; align-items: baseline; padding: 10px 14px; border-radius: 12px; background: #e6e1d8; }
87
- .cmds dt code { background: #f6f3ee; }
88
- .cmds dd { font-size: .86em; color: var(--fg-muted); }
89
- @media (max-width: 620px) { .cmds > div { grid-template-columns: 1fr; } }
76
+ code { font: 500 .86em ui-monospace, "SF Mono", Menlo, monospace; padding: 2px 7px; border-radius: 7px; background: rgba(255,255,255,.62); }
77
+
78
+ .steps { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; counter-reset: s; }
79
+ .step { position: relative; padding: 28px; border-radius: 22px; counter-increment: s; display: flex; flex-direction: column; gap: 10px; }
80
+ .step::before { content: counter(s); display: grid; place-items: center; width: 34px; height: 34px; border-radius: 50%; background: var(--fg); color: var(--bg); font-size: 15px; font-weight: 600; margin-bottom: 8px; }
81
+ .step h3 { font-size: 22px; font-weight: 500; letter-spacing: -.04em; line-height: 1.15; }
82
+ .step p { font-size: 16px; line-height: 1.55; text-wrap: pretty; }
83
+ .c-peach { background: var(--peach); } .c-blue { background: var(--blue); } .c-green { background: var(--green); } .c-lilac { background: var(--lilac); } .c-teal { background: var(--teal); } .c-rose { background: var(--rose); } .c-lemon { background: var(--lemon); }
84
+
85
+ .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
86
+ .card { padding: 24px; border-radius: 22px; display: flex; flex-direction: column; gap: 8px; transition: transform .3s var(--ease); }
87
+ .card:hover { transform: translateY(-3px); }
88
+ .card .tag { align-self: flex-start; padding: 3px 10px; border-radius: 999px; background: rgba(255,255,255,.6); font-size: 12px; font-weight: 600; letter-spacing: -.01em; }
89
+ .card h3 { margin-top: 8px; font-size: 20px; font-weight: 500; letter-spacing: -.04em; line-height: 1.2; }
90
+ .card p { font-size: 15.5px; line-height: 1.55; text-wrap: pretty; color: color-mix(in srgb, var(--fg) 82%, transparent); }
91
+
92
+ .feature { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: stretch; }
93
+ .panel { padding: 32px; border-radius: 26px; display: flex; flex-direction: column; gap: 14px; }
94
+ .panel h3 { font-size: 26px; font-weight: 500; letter-spacing: -.045em; line-height: 1.12; text-wrap: balance; }
95
+ .panel p { text-wrap: pretty; line-height: 1.55; }
96
+ .panel .muted { font-size: 15.5px; color: color-mix(in srgb, var(--fg) 75%, transparent); }
97
+ .rows { display: grid; margin-top: 4px; }
98
+ .rows > div { display: grid; grid-template-columns: minmax(0, 12.5rem) minmax(0, 1fr); gap: 4px 16px; align-items: baseline; padding: 11px 0; border-top: 1px solid color-mix(in srgb, var(--fg) 14%, transparent); }
99
+ .rows dt code { white-space: nowrap; }
100
+ .rows dd { font-size: 15px; line-height: 1.45; color: color-mix(in srgb, var(--fg) 78%, transparent); }
101
+
102
+ .own { padding: 44px; border-radius: 28px; background: var(--fg); color: var(--bg); display: grid; grid-template-columns: 1.1fr 1fr; gap: 32px; align-items: center; }
103
+ .own h2 { color: var(--bg); }
104
+ .own p { color: color-mix(in srgb, var(--bg) 78%, transparent); text-wrap: pretty; }
105
+ .own a { color: var(--bg); text-decoration-color: var(--accent); }
106
+ .own .btns { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 20px; }
107
+ .btn { display: inline-flex; align-items: center; height: 44px; padding: 0 22px; border-radius: 999px; background: var(--bg); color: var(--fg); font-weight: 600; font-size: 15px; text-decoration: none; }
108
+ .btn.ghost { background: none; color: var(--bg); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bg) 35%, transparent); }
109
+ main .own a.btn:hover { color: var(--fg); background: var(--lemon); }
90
110
 
91
111
 
92
- .footer .page { padding: 40px 0 56px; text-align: center; font-size: 13px; color: var(--fg-muted); }
112
+ .c-lemonsoft { background: #efeeb0; }
113
+ .card.big { grid-column: 1 / -1; display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.05fr); gap: 40px; padding: 36px; border-radius: 26px; align-items: start; }
114
+ .card.big:hover { transform: none; }
115
+ .card.big h3 { font-size: 28px; letter-spacing: -.045em; line-height: 1.12; text-wrap: balance; margin-bottom: 6px; }
116
+ .big-l { display: flex; flex-direction: column; gap: 14px; }
117
+ .big-l p { font-size: 16px; line-height: 1.6; text-wrap: pretty; }
118
+ .big-l .muted { font-size: 15px; color: color-mix(in srgb, var(--fg) 68%, transparent); }
119
+ .card code { background: #e6e2da; color: var(--fg); }
120
+ .card .rows { margin-top: 10px; }
121
+ .card .rows > div { grid-template-columns: minmax(0, 11.5rem) minmax(0, 1fr); gap: 4px 16px; padding: 8px 0; align-items: center; }
122
+ .card .rows dt code { display: inline-block; padding: 4px 9px; }
123
+ .card .rows dd { font-size: 14px; line-height: 1.35; }
124
+ .card .rows dt code { font-size: .82em; }
125
+ .card.big .rows > div { grid-template-columns: 1fr; gap: 6px; }
126
+ .card .rows.stack > div { grid-template-columns: 1fr !important; gap: 6px; }
127
+ .card .note { font-size: 13.5px; color: color-mix(in srgb, var(--fg) 65%, transparent); margin-top: 6px; }
128
+ .card:not(.big) .rows > div { grid-template-columns: minmax(0, 8rem) minmax(0, 1fr); }
129
+ .own a.btn { color: var(--fg); }
130
+ .own a.btn.ghost { color: var(--bg); }
131
+
132
+ .footer .page { padding: 64px 0 48px; text-align: center; font-size: 13px; color: var(--fg-muted); }
93
133
  .footer a { color: var(--fg); text-decoration: underline; text-decoration-color: var(--red); text-decoration-thickness: 1px; text-underline-offset: 3px; }
94
134
  .footer a:hover { color: var(--red); }
135
+ main { padding-bottom: 8px; }
136
+ @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation: none !important; transition: none !important; scroll-behavior: auto !important; } }
95
137
 
138
+ @media (max-width: 920px) {
139
+ .grid { grid-template-columns: repeat(2, 1fr); }
140
+ .steps { grid-template-columns: 1fr; }
141
+ .step { padding: 24px; }
142
+ .card.big { grid-template-columns: 1fr; gap: 24px; padding: 28px; }
143
+ .card .rows.two { grid-template-columns: 1fr; }
144
+ .own { grid-template-columns: 1fr; padding: 32px; }
145
+ }
96
146
  @media (max-width: 620px) {
97
147
  body { font-size: 17px; }
98
148
  .page { width: calc(100% - 40px); }
99
- .head { padding-top: 32px; }
100
- main { padding: 80px 0; }
101
- .lead { font-size: 19px; }
102
- section { margin-top: 64px; }
103
- main .cmd { padding-left: 14px; }
104
- main .cmd code { font-size: 14px; }
149
+ .head { padding-top: 24px; }
150
+ .mark { font-size: 28px; }
151
+ .hero { padding-top: 56px; }
152
+ section { margin-top: 72px; }
153
+ .sec-head { margin-bottom: 24px; }
154
+ .grid { grid-template-columns: 1fr; gap: 12px; }
155
+ .card { padding: 22px; }
156
+ .card.big { padding: 24px; border-radius: 22px; }
157
+ .card.big h3 { font-size: 24px; }
158
+ .card .rows > div, .card:not(.big) .rows > div { grid-template-columns: 1fr; gap: 6px; }
159
+ .panel { padding: 24px; border-radius: 22px; }
160
+ .panel h3 { font-size: 23px; }
161
+ .rows > div { grid-template-columns: 1fr; gap: 2px; }
162
+ .install { padding-left: 16px; }
163
+ .install code { font-size: 13.5px; }
164
+ .own { padding: 28px 24px; border-radius: 22px; }
105
165
  }
106
166
  </style>
107
167
  </head>
@@ -112,93 +172,55 @@ main strong { font-weight: 600; }
112
172
  </header>
113
173
 
114
174
  <main class="page">
115
- <h1>Els teus agents, a les teves màquines, des del mòbil.</h1>
116
- <p class="lead">nio és un comandament per a Claude Code i Codex. Treballen als teus ordinadors (el portàtil, el mini PC de casa, un servidor), amb els teus comptes i les teves claus. Tu els dones feina des del mòbil, els segueixes en directe i aproves el que és arriscat. És personal i gratuït.</p>
175
+ <div class="hero">
176
+ <h1>Els teus agents, a les teves màquines, des del mòbil.</h1>
177
+ <p class="lead">nio és un comandament per a Claude Code, Codex i altres agents. Treballen als teus ordinadors, amb els teus comptes i les teves claus. Tu els dones feina des del mòbil, els segueixes en directe i aproves el que és arriscat. Personal i gratuït.</p>
178
+ <div class="install"><code id="cmd">curl -fsSL https://www.nio.cat/install.sh | sh</code><button class="copy" type="button" data-copy="curl -fsSL https://www.nio.cat/install.sh | sh" aria-label="Copia l’ordre"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3"/></svg><svg class="ok" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></button></div>
179
+ <p class="alt">Linux o Mac. Per provar-ho sense instal·lar: <code>npx nio-cat</code></p>
180
+ </div>
117
181
 
118
182
  <section aria-labelledby="com">
119
- <h2 id="com">Com funciona</h2>
120
- <p><strong>1. A cada ordinador</strong> (Linux o Mac), una sola ordre:</p>
121
- <div class="cmd"><code id="cmd">curl -fsSL https://www.nio.cat/install.sh | sh</code><button class="copy" type="button" data-copy="curl -fsSL https://www.nio.cat/install.sh | sh" aria-label="Copia l’ordre"><svg class="ic" viewBox="0 0 24 24" aria-hidden="true"><rect x="8" y="8" width="12" height="12" rx="2"/><path d="M16 8V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h3"/></svg><svg class="ok" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg></button></div>
122
- <p>Instal·la nio, el deixa engegat i et mostra un codi QR. Si només el vols provar: <code>npx nio-cat</code>.</p>
123
- <p><strong>2. Al mòbil</strong>, obre <a href="/app">nio.cat/app</a> i escaneja el QR. El codi serveix un sol cop i el mòbil en rep una clau pròpia, que pots revocar quan vulguis. Afegeix-lo a la pantalla d’inici i tindràs avisos.</p>
124
- <p><strong>3. Dona-li feina.</strong> Tria l’ordinador, l’agent i la carpeta, i explica què vols. Si l’ordinador es reinicia, el mòbil el torna a trobar sol.</p>
183
+ <div class="sec-head"><span class="eyebrow">Com funciona</span><h2 id="com">Tres passos i ja estàs dins</h2></div>
184
+ <div class="steps">
185
+ <div class="step c-peach"><h3>Instal·la’l a l’ordinador</h3><p>Una sola ordre. Instal·la nio, el deixa engegat i t’obre la web per enllaçar el mòbil.</p></div>
186
+ <div class="step c-blue"><h3>Escaneja’l amb el mòbil</h3><p>Obre <a href="/app">nio.cat/app</a> al mòbil i escaneja el codi QR que et mostra la web. El mòbil rep una clau pròpia, que pots revocar quan vulguis. Afegeix-lo a la pantalla d’inici i tindràs avisos.</p></div>
187
+ <div class="step c-green"><h3>Dona-li feina</h3><p>Tria l’ordinador, l’agent i la carpeta, i explica què vols. Si l’ordinador es reinicia, el mòbil el torna a trobar sol.</p></div>
188
+ </div>
125
189
  </section>
126
190
 
127
191
  <section aria-labelledby="que">
128
- <h2 id="que">Què hi pots fer</h2>
129
- <ul class="feat">
130
- <li><strong>Sessions en directe.</strong> Claude Code i Codex a qualsevol dels teus ordinadors: tria l’agent, la carpeta i el nivell de permisos, i la resposta et va arribant mentre treballa, amb les eines que fa servir.</li>
131
- <li><strong>Aprovacions.</strong> Abans d’editar, executar o clicar res, te’n demana permís amb el motiu, i ho aproves o ho rebutges amb un toc. Tot queda registrat a Activitat.</li>
132
- <li><strong>Avisos al mòbil.</strong> Una notificació quan un agent et demana permís o acaba la feina. Arriba buida: el detall el demana el mòbil directament a l’ordinador.</li>
133
- <li><strong>Feines.</strong> Que es facin soles: ara, més tard o cada dia, i encadenades («quan Claude Code acabi, que Codex ho revisi»).</li>
134
- <li><strong>Memòria compartida.</strong> El que recorda un agent, ho sap l’altre. Són fitxers markdown al teu ordinador.</li>
135
- <li><strong>Ordinadors i servidors.</strong> El teu ordinador o un servidor (VPS), tots a la mateixa llista. Els afegeixes escrivint <code>usuari@servidor</code>.</li>
136
- <li><strong>Pantalla remota.</strong> Mira i controla l’escriptori de l’ordinador des del mòbil, amb zoom amb dos dits i captures. Més avall.</li>
137
- <li><strong>Navegador i escriptori per als agents.</strong> Poden mirar webs i apps; per actuar, sempre et demanen permís amb una captura.</li>
138
- <li><strong>Telegram i veu.</strong> Parla amb el teu bot o amb notes de veu, aprova amb un botó, i escolta les respostes (ElevenLabs o OpenAI). Les claus es queden a l’ordinador.</li>
139
- <li><strong>Git i GitHub.</strong> Clona un repositori, mira els canvis, fes commit i crea un PR des del mòbil, amb el <code>git</code> i el <code>gh</code> de l’ordinador.</li>
140
- <li><strong>Models d’Ollama i xat local.</strong> Descarrega i fes servir models gratuïts a l’ordinador, o un model petit dins del navegador del mòbil.</li>
141
- <li><strong>Pissarra.</strong> Un llenç per fer diagrames, i en Nio te’ls dibuixa si li ho expliques. Més avall.</li>
142
- <li><strong>Com una app.</strong> Afegeix nio a la pantalla d’inici del mòbil; llisca als costats per canviar de pàgina i <code>Ctrl K</code> obre el buscador a l’ordinador.</li>
143
- </ul>
144
- </section>
145
-
146
- <section aria-labelledby="cmds">
147
- <h2 id="cmds">Comandes amb «/»</h2>
148
- <p>Al missatge d’una sessió, escriu <code>/</code> i surten els suggeriments. Les de nio funcionen amb tots els agents; les de l’agent (comandes i skills) se li envien tal qual.</p>
149
- <dl class="cmds">
150
- <div><dt><code>/mode</code></dt><dd>Canvia els permisos: <code>l</code> llegir, <code>p</code> preguntar, <code>t</code> tot</dd></div>
151
- <div><dt><code>/model</code></dt><dd>Canvia de model</dd></div>
152
- <div><dt><code>/stop</code></dt><dd>Atura l’agent ara mateix</dd></div>
153
- <div><dt><code>/nova</code></dt><dd>Nova sessió amb el mateix agent i carpeta</dd></div>
154
- <div><dt><code>/carpeta</code></dt><dd>Canvia la carpeta de la sessió</dd></div>
155
- <div><dt><code>/git</code></dt><dd>Veure canvis, fer commit i crear un PR</dd></div>
156
- <div><dt><code>/nom</code></dt><dd>Posa nom a la sessió</dd></div>
157
- <div><dt><code>/opcions</code></dt><dd>Opcions de la sessió</dd></div>
158
- <div><dt><code>/ajuda</code></dt><dd>Llista de comandes</dd></div>
159
- </dl>
160
- </section>
161
-
162
- <section aria-labelledby="screen">
163
- <h2 id="screen">La pantalla remota</h2>
164
- <p>Obre l’escriptori d’un dels teus ordinadors a pantalla completa, a la mateixa amplada que el mòbil, com un escriptori remot. Al mode <strong>Mirar</strong> només veus què fa l’agent; al mode <strong>Controlar</strong> pots clicar, fer doble clic, clic dret, arrossegar, desplaçar i escriure.</p>
165
- <dl class="cmds">
166
- <div><dt><code>− 100% +</code></dt><dd>Allunya, apropa o torna a enquadrar la imatge</dd></div>
167
- <div><dt><code>1:1</code></dt><dd>Mida real, per llegir text petit</dd></div>
168
- <div><dt><code>Desa captura</code></dt><dd>Baixa el fotograma actual com a imatge</dd></div>
169
- <div><dt><code>Pantalla completa</code></dt><dd>A l’ordinador, amaga tota la resta</dd></div>
170
- </dl>
171
- <p><strong>Zoom amb dos dits.</strong> Pessiga per apropar i arrossega amb dos dits per moure’t; al mode Mirar, doble toc alterna entre enquadrar i apropar. Al mode Controlar, el botó «2 dits» tria si els dos dits desplacen l’ordinador o fan zoom. A l’ordinador, pessiga al trackpad o fes servir <code>Ctrl</code> i la roda. Cal activar «Apps de l’escriptori» a l’ordinador, i al Mac donar al terminal els permisos de Gravació de pantalla i Accessibilitat.</p>
172
- </section>
173
-
174
- <section aria-labelledby="board">
175
- <h2 id="board">La pissarra</h2>
176
- <p>Un llenç infinit per fer fluxos, mapes mentals i organigrames, al mòbil i a l’ordinador. Rectangles, el·lipses, rombes, fletxes que s’enganxen a les figures, línies, llapis, text i goma. Desfés i refés, quadrícula, i exportació a PNG, SVG o JSON.</p>
177
- <p><strong>Zoom amb dos dits.</strong> Al mòbil, pessiga sobre el llenç per apropar o allunyar, i mou-lo amb els dos dits alhora. A l’ordinador, pessiga al trackpad o fes servir <code>Ctrl</code> i la roda del ratolí.</p>
178
- <p><strong>Demana-ho a en Nio.</strong> Obre el xat de la pissarra i escriu què vols:</p>
179
- <dl class="cmds">
180
- <div><dt><code>Flux: A -> B -> C</code></dt><dd>Una fletxa per línia</dd></div>
181
- <div><dt><code>Mapa mental de X: a, b, c</code></dt><dd>Tema central amb branques</dd></div>
182
- <div><dt><code>Organigrama: Direcció &gt; Finances</code></dt><dd>Qui depèn de qui</dd></div>
183
- <div><dt><code>Connecta Banc amb ERP</code></dt><dd>Uneix dues figures</dd></div>
184
- <div><dt><code>Pinta Banc de verd</code></dt><dd>Canvia el color</dd></div>
185
- <div><dt><code>Plantilla d’aprovació</code></dt><dd>Un diagrama de partida</dd></div>
186
- </dl>
192
+ <div class="sec-head"><span class="eyebrow">Què hi pots fer</span><h2 id="que">Tot el que necessites per treballar amb agents</h2></div>
193
+ <div class="grid">
194
+ <article class="card c-blue"><span class="tag">Agents</span><h3>Sessions en directe</h3><p>Claude Code, Codex, Grok i Antigravity a qualsevol dels teus ordinadors. Tries la carpeta i els permisos, i la resposta arriba mentre treballa.</p></article>
195
+ <article class="card c-rose"><span class="tag">Seguretat</span><h3>Aprovacions</h3><p>Abans d’editar, executar o clicar res, te’n demana permís amb el motiu. Ho aproves o ho rebutges amb un toc, i tot queda registrat.</p></article>
196
+ <article class="card c-lilac"><span class="tag">Connexió</span><h3>Avisos al mòbil</h3><p>Una notificació quan un agent et demana permís o acaba. Arriba buida: el detall el demana el mòbil directament a l’ordinador.</p></article>
197
+ <article class="card c-blue"><span class="tag">Agents</span><h3>Feines</h3><p>Que es facin soles: ara, més tard o cada dia, i encadenades («quan Claude Code acabi, que Codex ho revisi»).</p></article>
198
+ <article class="card c-blue"><span class="tag">Agents</span><h3>Memòria compartida</h3><p>El que recorda un agent, ho sap l’altre. Són fitxers markdown al teu ordinador.</p></article>
199
+ <article class="card c-teal"><span class="tag">Ordinador</span><h3>Ordinadors i servidors</h3><p>El teu ordinador o un servidor (VPS), tots a la mateixa llista. Els afegeixes escrivint <code>usuari@servidor</code>.</p></article>
200
+ <article class="card c-blue"><span class="tag">Agents</span><h3>Comandes amb «/»</h3><p>Escriu «/» i surten els suggeriments: <code>/mode</code>, <code>/model</code>, <code>/stop</code>, <code>/nova</code>, <code>/git</code>… Les de nio funcionen amb tots els agents; les de l’agent (comandes i skills) se li envien tal qual.</p></article>
201
+ <article class="card c-peach"><span class="tag">Pantalla remota</span><h3>L’escriptori, a la butxaca</h3><p>Obre l’escriptori de l’ordinador des del mòbil. Amb <strong>Mirar</strong> només veus què fa l’agent; amb <strong>Controlar</strong> hi cliques, arrossegues i escrius. Pessiga amb dos dits per fer zoom. Els agents també hi poden mirar webs i apps, sempre amb permís.</p></article>
202
+ <article class="card c-lemonsoft"><span class="tag">Pissarra</span><h3>Pensar dibuixant</h3><p>Un llenç infinit per a fluxos, mapes mentals i organigrames, amb figures, fletxes i llapis. Si li expliques a en Nio què vols, te’ls dibuixa. Exporta a PNG, SVG o JSON.</p></article>
203
+ <article class="card c-blue"><span class="tag">Agents</span><h3>Git i GitHub</h3><p>Clona un repositori, mira els canvis, fes commit i crea un PR des del mòbil, amb el <code>git</code> i el <code>gh</code> de l’ordinador.</p></article>
204
+ <article class="card c-lilac"><span class="tag">Connexió</span><h3>Telegram i veu</h3><p>Parla amb el teu bot o amb notes de veu, aprova amb un botó i escolta les respostes. Les claus es queden a l’ordinador.</p></article>
205
+ <article class="card c-green"><span class="tag">Xat local</span><h3>Ollama i xat local</h3><p>Fes servir models gratuïts a l’ordinador, o un model petit dins del navegador del mòbil, sense connexió.</p></article>
206
+ </div>
187
207
  </section>
188
208
 
189
209
  <section aria-labelledby="teu">
190
- <h2 id="teu">Tot és teu</h2>
191
- <p>Fas servir els teus agents i les teves claus (Anthropic, OpenAI, ElevenLabs, el bot de Telegram). Les converses, la memòria i les claus es queden als teus ordinadors. nio.cat només serveix l’app i guarda, signada, l’adreça on trobar cada ordinador. Més detalls a <a href="/privadesa">privadesa</a>.</p>
192
- </section>
193
-
194
- <section aria-labelledby="prova">
195
- <h2 id="prova">Prova-ho</h2>
196
- <p>Obre <a href="/app">el panell</a>. Si encara no tens cap ordinador connectat, t’ensenya com fer-ho pas a pas.</p>
210
+ <div class="own">
211
+ <div>
212
+ <h2 id="teu">Tot és teu</h2>
213
+ </div>
214
+ <div>
215
+ <p>Fas servir els teus agents i les teves claus (Anthropic, OpenAI, ElevenLabs, el bot de Telegram). Les converses, la memòria i les claus es queden als teus ordinadors. nio.cat només serveix l’app i guarda, signada, l’adreça on trobar cada ordinador. Més detalls a <a href="/privadesa">privadesa</a>.</p>
216
+ <div class="btns"><a class="btn" href="/app">Obre el panell</a><a class="btn ghost" href="/qui-soc">Qui sóc</a><a class="btn ghost" href="/novetats">Novetats</a></div>
217
+ </div>
218
+ </div>
197
219
  </section>
198
220
  </main>
199
221
 
200
222
  <footer class="footer">
201
- <div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/novetats">Novetats</a> · <a href="/privadesa">Privadesa</a></div>
223
+ <div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/qui-soc">Qui sóc</a> · <a href="/novetats">Novetats</a> · <a href="/privadesa">Privadesa</a></div>
202
224
  </footer>
203
225
  <script src="/bot.js" defer></script>
204
226
  <script>
package/web/og.png CHANGED
Binary file
@@ -141,7 +141,7 @@ main strong { font-weight: 600; }
141
141
  </main>
142
142
 
143
143
  <footer class="footer">
144
- <div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/novetats">Novetats</a> · <a href="/privadesa">Privadesa</a></div>
144
+ <div class="page">© 2026 nio // un projecte de <a href="https://f.cat">f.cat</a> · <a href="mailto:hola@nio.cat">hola@nio.cat</a> · <a href="/qui-soc">Qui sóc</a> · <a href="/novetats">Novetats</a> · <a href="/privadesa">Privadesa</a></div>
145
145
  </footer>
146
146
  <script src="/bot.js" defer></script>
147
147
  </body>
package/web/sw.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // nio · service worker: obre l'app encara que no hi hagi connexió i mostra els avisos.
2
- const SHELL = 'nio-shell-v33';
2
+ const SHELL = 'nio-shell-v35';
3
3
  const FILES = ['/app', '/app/style.css', '/app/app.js', '/bot.css', '/bot.js', '/board.css', '/board.js', '/favicon.svg', '/manifest.webmanifest'];
4
4
 
5
5
  self.addEventListener('install', (e) => {