device-center 0.2.0-beta.2 → 0.2.0-beta.4

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/src/onboarding.js CHANGED
@@ -1,22 +1,26 @@
1
+ import { localizedInstallPrompt } from './install-prompt.js';
2
+ import { normalizeLocale } from './i18n.js';
3
+
1
4
  export const OFFICIAL_CONTROL_ORIGIN = 'https://devices.owenshen.top';
2
- // Pin the CLI shipped with this release; never silently install a moving tag.
3
- export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.2';
5
+ // Release-pinned candidate: publish this CLI before deploying the matching guide.
6
+ export const PUBLISHED_DRIVER_VERSION = '0.2.0-beta.4';
7
+ const inputError = (key, message) => Object.assign(new Error(message), { translationKey: key });
4
8
  export function connectCommand(value, shareSsh = false) {
5
9
  let url;
6
- try { url = new URL(value); } catch { throw new Error('请输入完整的 HTTPS 域名,例如 https://devices.example.com'); }
10
+ try { url = new URL(value); } catch { throw inputError('error.https', '请输入完整的 HTTPS 域名,例如 https://devices.example.com'); }
7
11
  if (url.protocol !== 'https:' || url.origin !== value || url.username || url.password ||
8
- !/^(?:[a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname)) throw new Error('请输入 HTTPS 控制面地址,不包含路径、账号或参数。');
12
+ !/^(?:[a-z0-9.-]+|\[[a-f0-9:]+\])$/i.test(url.hostname)) throw inputError('error.origin', '请输入 HTTPS 控制面地址,不包含路径、账号或参数。');
9
13
  // The allowlist excludes shell syntax. Quotes also work in POSIX shells and PowerShell.
10
- return `npx --yes device-center@${PUBLISHED_DRIVER_VERSION} connect --cloud '${url.origin}'${shareSsh ? ' --share-ssh' : ''} --confirm`;
14
+ return `npx --yes device-center@${PUBLISHED_DRIVER_VERSION} connect --cloud '${url.origin}'${shareSsh ? ' --share-ssh' : ''} --browser --confirm`;
11
15
  }
12
16
 
13
17
  // A reproducible handoff, not an installer. Reading/copying it has no machine effects.
14
18
  export function agentInstallPlan({ mode = 'local', origin, port = 5174, background = true } = {}) {
15
- if (!['local', 'official', 'self-hosted'].includes(mode) || !Number.isInteger(port) || port < 1024 || port > 65535 || typeof background !== 'boolean') throw new Error('安装模式或本机端口无效。');
19
+ if (!['local', 'official', 'self-hosted'].includes(mode) || !Number.isInteger(port) || port < 1024 || port > 65535 || typeof background !== 'boolean') throw inputError('error.install', '安装模式或本机端口无效。');
16
20
  const controlOrigin = mode === 'local' ? null : mode === 'official' ? OFFICIAL_CONTROL_ORIGIN : origin;
17
21
  if (controlOrigin) connectCommand(controlOrigin); // Same strict HTTPS and shell-safe origin validation.
18
- if (mode === 'self-hosted' && !controlOrigin) throw new Error('请输入自建控制面的 HTTPS 地址。');
19
- if (origin !== undefined && mode !== 'self-hosted') throw new Error('只有自建模式可以填写控制面地址。');
22
+ if (mode === 'self-hosted' && !controlOrigin) throw inputError('error.custom', '请输入自建控制面的 HTTPS 地址。');
23
+ if (origin !== undefined && mode !== 'self-hosted') throw inputError('error.selfOnly', '只有自建模式可以填写控制面地址。');
20
24
  const cli = '"$HOME/.local/bin/device-center"';
21
25
  const url = `http://127.0.0.1:${port}`;
22
26
  return { mode, version: PUBLISHED_DRIVER_VERSION, controlOrigin, background, url, mcpUrl: `${url}/mcp`, commands: {
@@ -26,13 +30,14 @@ export function agentInstallPlan({ mode = 'local', origin, port = 5174, backgrou
26
30
  serviceInstall: `${cli} service install --port ${port} --confirm`,
27
31
  foreground: `${cli} start --port ${port}`,
28
32
  pairingPlan: controlOrigin ? `${cli} plan --cloud '${controlOrigin}' --port ${port}` : null,
29
- connect: controlOrigin ? `${cli} connect --cloud '${controlOrigin}' --port ${port} --confirm` : null,
33
+ connect: controlOrigin ? `${cli} connect --cloud '${controlOrigin}' --port ${port} --browser --confirm` : null,
30
34
  uninstallService: `${cli} service uninstall --port ${port} --confirm`,
31
35
  } };
32
36
  }
33
37
 
34
38
  export function agentInstallPrompt(options) {
35
39
  const plan = agentInstallPlan(options), c = plan.commands;
40
+ if (options?.locale && normalizeLocale(options.locale) !== 'zh-CN') return localizedInstallPrompt(plan, options.locale);
36
41
  return `请帮我在正在操作的这台电脑安装 Device Center,并接入这台电脑上的智能体。
37
42
 
38
43
  我的选择:${plan.mode === 'local' ? '本机使用,无需账号,不申请云端绑定' : plan.mode === 'official' ? '使用 Device Center 官方托管控制面和加密中转' : '接入我已经部署的自建控制面'}。
@@ -46,17 +51,40 @@ ${plan.controlOrigin ? `控制面:${plan.controlOrigin}。设备 Driver 仍在
46
51
  ${c.version}
47
52
  不修改全局 npm 配置或 shell 启动文件;后续使用上面的绝对命令路径。
48
53
  4. ${plan.background ? `先查看并核对后台服务计划:\n ${c.servicePlan}\n 确认是用户级、只监听本机回环后安装:\n ${c.serviceInstall}\n 再核验:${c.serviceStatus}\n 若配置已存在或健康检查失败,保留现场并排查,不自动清理重装。Linux 需要可用的 systemd --user,macOS 需要当前登录用户的 launchd;不支持时如实提示可改用前台模式。` : `启动并保持前台进程:\n ${plan.controlOrigin ? '先在下一步完成 connect;它会在批准后启动本机 Driver。' : c.foreground}\n 关闭网页不会停止进程;关闭终端会停止回报。`}
49
- 5. ${plan.controlOrigin ? `查看云端回报范围,然后申请绑定:\n ${c.pairingPlan}\n ${c.connect}\n 请我在 ${plan.controlOrigin} 登录自己的账号,在本机终端核对完整公钥指纹并手动批准绑定。不要代我批准新增设备访问权限,不读取账号密码、cookie 或私钥。配对码留在本机终端,不粘贴到智能体对话、脚本或链接;若工具会把配对码自动记录进对话,让我仅在本机终端执行这条 connect 命令,你继续完成其他检查。批准后核验设备确实回报资源;不能仅凭本地 configured=true 声称云端连通。` : '不执行 connect、不生成设备身份或申请账号。若发现已有云端绑定,先说明 start/后台服务可能恢复它;不能把已有绑定伪装成本机专用模式。'}
54
+ 5. ${plan.controlOrigin ? `查看云端回报范围,然后申请绑定:\n ${c.pairingPlan}\n ${c.connect}\n 由你执行 --browser 接入并保持等待进程;它自动打开这台电脑的浏览器,临时登记凭据不输出到对话。只请我在打开的页面登录自己的账号,核对上面输出的完整公钥指纹并批准一次。不要再让我去终端执行同一命令,也不要要求我回对话回复“已批准”:你持续观察进程,批准后自动核验 firstReport.verified=true,并继续 MCP 配置。不要代我批准新增设备访问权限,不读取账号密码、cookie 或私钥,不尝试获取浏览器链接。不能仅凭本地 configured=true 声称云端连通。若此电脑没有浏览器或打开失败,再说明原因,让我在本机终端运行去掉 --browser 的 connect;配对码留在本机终端,不贴进对话。若控制面不支持新版浏览器交接,先提示升级控制面,不自动降级输出凭据。` : '不执行 connect、不生成设备身份或申请账号。若发现已有云端绑定,先说明 start/后台服务可能恢复它;不能把已有绑定伪装成本机专用模式。'}
50
55
  6. 读取 ${plan.url}/api/connector/setup,使用当前智能体支持的官方 MCP 配置接口添加 Streamable HTTP 地址 ${plan.mcpUrl}。复用一致的配置,不覆盖其他 MCP。若当前工具不能配置,只让我粘贴这一个地址;不要编造已接通。
51
56
  7. 核验 /api/health、/api/overview 的本机真实内存与磁盘,以及智能体实际调用 device_center_list_resources。最后报告版本、面板/MCP 地址、前台或后台状态、云端绑定结果(如适用)与尚未完成的步骤。后台卸载命令仅供我需要时使用:${c.uninstallService},默认保留数据和身份。
52
57
 
53
58
  默认不导入或连接 SSH 主机、不分享 SSH、不扫描局域网、不开放入站端口、不配置隧道、不授予日志/命令权限。安装和配对不等于远程操作授权;后续访问目标设备需另行授权。
54
59
  此说明固定使用 ${plan.version},包含设备说明与手动采集。已有旧版需先核对升级;不能假称未运行的设备已接通。`;
55
60
  }
61
+
62
+ // Alternative for first-time users without an Agent. Copying does not execute it.
63
+ // Existing installations deliberately stop before npm can overwrite a live CLI/service.
64
+ export function terminalInstallScript({ browser = true, ...options } = {}) {
65
+ if (typeof browser !== 'boolean') throw inputError('error.install', '浏览器接入选项无效。');
66
+ const plan = agentInstallPlan(options), c = plan.commands;
67
+ // Headless targets use the same pairing protocol, with the code shown only at runtime.
68
+ const connect = c.connect && (browser ? c.connect : c.connect.replace(' --browser', ''));
69
+ return `(
70
+ set -eu
71
+ case "$(uname -s)" in Darwin|Linux) ;; *) printf '%s\\n' 'macOS / Linux only. Windows native Driver is not supported.'; exit 1 ;; esac
72
+ command -v node >/dev/null 2>&1 && command -v npm >/dev/null 2>&1 || { printf '%s\\n' 'Install Node.js 24+ and npm from nodejs.org, then retry.'; exit 1; }
73
+ node -e 'if (Number(process.versions.node.split(".")[0]) < 24) { console.error("Node.js 24+ required"); process.exit(1); }'
74
+ if command -v device-center >/dev/null 2>&1 || [ -e "$HOME/.local/bin/device-center" ] || [ -L "$HOME/.local/bin/device-center" ] || [ -e "$HOME/.local/lib/node_modules/device-center" ]; then
75
+ printf '%s\\n' 'Device Center already exists. Use status and service status first; reuse it or explicitly upgrade. This script did not change it.'
76
+ exit 1
77
+ fi
78
+ ${c.install}
79
+ ${c.version}
80
+ ${plan.background ? `${c.servicePlan}\n${c.serviceInstall}\n${c.serviceStatus}` : ''}
81
+ ${plan.controlOrigin ? `${c.pairingPlan}\n${connect}` : plan.background ? `printf '%s\\n' 'Panel: ${plan.url}' 'MCP: ${plan.mcpUrl}'` : c.foreground}
82
+ )`;
83
+ }
56
84
  export function logAccessCommands({ manager, target, path, label = 'app' }) {
57
- if (![manager, target].every(value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)) || manager === target) throw new Error('请选择两台不同的已绑定设备。');
58
- if (typeof path !== 'string' || !path.startsWith('/') || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw new Error('填写目标设备上日志的绝对路径。');
59
- if (!/^[a-zA-Z0-9_-]{1,40}$/.test(label)) throw new Error('标签仅允许字母、数字、下划线或连字符。');
85
+ if (![manager, target].every(value => typeof value === 'string' && /^[a-f0-9]{64}$/.test(value)) || manager === target) throw inputError('error.devices', '请选择两台不同的已绑定设备。');
86
+ if (typeof path !== 'string' || !path.startsWith('/') || path.length > 4096 || /[\x00-\x1f\x7f]/.test(path)) throw inputError('error.path', '填写目标设备上日志的绝对路径。');
87
+ if (!/^[a-zA-Z0-9_-]{1,40}$/.test(label)) throw inputError('error.label', '标签仅允许字母、数字、下划线或连字符。');
60
88
  const quotedPath = "'" + path.replaceAll("'", "'\"'\"'") + "'";
61
89
  return { trust: `device-center access trust --peer ${target} --confirm`, grant: `device-center access grant --peer ${manager} --log ${quotedPath} --label ${label} --minutes 60 --confirm` };
62
90
  }
package/src/pair.css ADDED
@@ -0,0 +1,23 @@
1
+ :root { color-scheme: light; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #242b28; background: #f2f1eb; }
2
+ * { box-sizing: border-box; }
3
+ body { margin: 0; min-height: 100dvh; display: flex; flex-direction: column; }
4
+ header { display: flex; align-items: center; justify-content: space-between; padding: 18px 5vw; }
5
+ a { color: inherit; text-underline-offset: 4px; }
6
+ .brand { display: flex; gap: 10px; align-items: center; text-decoration: none; font-size: 16px; font-weight: 650; }
7
+ main { width: min(560px, calc(100% - 32px)); margin: auto; padding: clamp(24px, 4vw, 36px); background: #fff; border: 1px solid #dddcd4; box-shadow: 0 12px 48px #303c2b08; border-radius: 4px; }
8
+ .eyebrow { margin: 0 0 24px; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: #737b70; }
9
+ h1 { font-size: clamp(26px, 5vw, 34px); line-height: 1.2; margin: 0 0 16px; letter-spacing: -.04em; }
10
+ p { font-size: 14px; line-height: 1.8; color: #646d63; }
11
+ .device { padding: 20px; margin: 24px 0; background: #f6f7f2; border-left: 3px solid #bbceab; }
12
+ .device strong { font-size: 20px; overflow-wrap: anywhere; }
13
+ .device p { margin: 4px 0 0; font-size: 12px; }
14
+ .fingerprint { display: block; padding: 14px; background: #f7f6f2; font-size: 12px; line-height: 1.8; overflow-wrap: anywhere; user-select: all; }
15
+ .scope { display: flex; gap: 10px; align-items: start; font-size: 13px; margin: 20px 0; }
16
+ button, .primary { display: block; width: 100%; padding: 14px 20px; margin-top: 24px; background: #263d2e; color: #fff; border: 0; border-radius: 3px; font: inherit; font-size: 14px; cursor: pointer; text-align: center; text-decoration: none; }
17
+ button:disabled { opacity: .5; cursor: wait; }
18
+ .secondary { margin-top: 14px; padding: 6px; background: transparent; color: #697066; }
19
+ button:focus-visible, a:focus-visible { outline: 2px solid #517543; outline-offset: 4px; }
20
+ .error { color: #a13d32; }
21
+ .status-mark { font-size: 32px; margin: 4px 0 20px; color: #517543; }
22
+ footer { text-align: center; padding: 20px 16px; color: #777e72; font-size: 12px; }
23
+ @media (prefers-reduced-motion: no-preference) { main { animation: appear .2s ease-out; } @keyframes appear { from { opacity: 0; transform: translateY(5px); } } }
package/src/pair.js ADDED
@@ -0,0 +1,60 @@
1
+ import { capturePairing, clearPairing } from './pairing-handoff.js';
2
+ import { api } from './api.js';
3
+ import { initLanguage, getLocale, t, h } from './i18n.js';
4
+
5
+ let handoff = capturePairing();
6
+ document.querySelector('link[href="/src/login.css"]').href = '/src/pair.css';
7
+ document.body.innerHTML = `<header><a class="brand" href="/start" data-language-link><img src="/favicon.svg" alt="" width="26" height="26">Device Center</a><div class="language-picker" data-language-picker></div></header><main id="pair-root" aria-live="polite" aria-busy="true"></main><footer data-i18n="pair.boundary"></footer>`;
8
+ document.querySelector('title').dataset.i18n = 'pair.title';
9
+ const root = document.querySelector('#pair-root');
10
+ const escape = value => String(value).replace(/[&<>"']/g, c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[c]);
11
+ let phase = handoff ? 'loading' : 'missing', pairing, shareSsh = false, error = null, timer, expiresTimer;
12
+ const heading = key => `<p class="eyebrow">DEVICE CENTER / ${h('pair.step')}</p><h1>${h(key)}</h1>`;
13
+ const dashboard = () => `<a class="primary" href="/?lang=${getLocale()}">${h('pair.dashboard')}</a>`;
14
+ function render() {
15
+ root.setAttribute('aria-busy', String(['loading', 'approving', 'waiting'].includes(phase)));
16
+ if (['loading', 'approving'].includes(phase)) root.innerHTML = heading('pair.loading') + `<p>${h('pair.wait')}</p>`;
17
+ else if (phase === 'ready') root.innerHTML = heading('pair.title') + `<p>${h('pair.intro')}</p><div class="device"><strong>${escape(pairing.name)}</strong><p>${escape(pairing.platform)} · ${escape(new Date(pairing.expiresAt).toLocaleTimeString(getLocale()))}</p></div><p>${h('pair.fingerprint')}</p><code class="fingerprint">${escape(pairing.fingerprint)}</code><p>${h('pair.scope')}</p>${pairing.requestedSsh ? `<label class="scope"><input id="share" type="checkbox" ${shareSsh ? 'checked' : ''}>${h('panel.349')}</label>` : ''}${error ? `<p role="alert" class="error">${h(error)}</p>` : ''}<button id="approve">${h('pair.approve')}</button><button id="cancel" class="secondary">${h('pair.cancel')}</button>`;
18
+ else if (phase === 'waiting') root.innerHTML = heading('pair.bound') + `<p>${h('pair.reporting')}</p><p>${h('pair.noReply')}</p>${dashboard()}`;
19
+ else if (phase === 'done') root.innerHTML = `<div class="status-mark" aria-hidden="true">✓</div>` + heading('pair.done') + `<p>${h('pair.doneBody')}</p><p>${h('pair.noReply')}</p>${dashboard()}`;
20
+ else if (phase === 'report-timeout') root.innerHTML = heading('pair.bound') + `<p>${h('pair.reportTimeout')}</p>${dashboard()}`;
21
+ else if (phase === 'approval-unknown') root.innerHTML = heading('pair.approvalUnknown') + `<p>${h('pair.approvalUnknownBody')}</p>${dashboard()}`;
22
+ else root.innerHTML = heading(phase === 'cancelled' ? 'pair.cancelled' : 'pair.missing') + `<p>${h(phase === 'cancelled' ? 'pair.cancelledBody' : 'pair.missingBody')}</p><a class="primary" href="/start?lang=${getLocale()}">${h('pair.start')}</a>`;
23
+ }
24
+ async function watchReport(driverId, deadline) {
25
+ try {
26
+ const { devices } = await api.overview();
27
+ if (devices.some(device => device.managementDriver?.id === driverId && device.kind === 'management' && device.state === 'online')) { phase = 'done'; render(); return; }
28
+ } catch { /* Binding is still real; don't claim that resource delivery succeeded. */ }
29
+ if (Date.now() >= deadline) { phase = 'report-timeout'; render(); return; }
30
+ timer = setTimeout(() => void watchReport(driverId, deadline), 3000);
31
+ }
32
+ root.addEventListener('change', event => { if (event.target.id === 'share') shareSsh = event.target.checked; });
33
+ root.addEventListener('click', async event => {
34
+ if (event.target.id === 'cancel' && phase === 'ready') { clearTimeout(expiresTimer); clearPairing(); handoff = null; phase = 'cancelled'; render(); return; }
35
+ if (event.target.id !== 'approve' || phase !== 'ready' || !handoff) return;
36
+ phase = 'approving'; render();
37
+ try {
38
+ const result = await api.approveBrowserPairing({ id: pairing.id, browserTicket: handoff.ticket, fingerprint: pairing.fingerprint, shareSsh: Boolean(pairing.requestedSsh && shareSsh) });
39
+ clearTimeout(expiresTimer); clearPairing(); handoff = null;
40
+ phase = 'waiting'; render(); void watchReport(result.driverId, Date.now() + 90_000);
41
+ } catch (cause) {
42
+ if (cause.message === 'authentication_required') return;
43
+ if (['driver_rate_limited', 'driver_limit'].includes(cause.message)) { phase = 'ready'; error = cause.message === 'driver_limit' ? 'panel.386' : 'panel.385'; }
44
+ else if (['driver_pairing_expired', 'driver_pairing_unavailable', 'invalid_pairing_code', 'driver_pairing_confirmation_required'].includes(cause.message)) { clearPairing(); handoff = null; phase = 'missing'; }
45
+ else { clearTimeout(expiresTimer); clearPairing(); handoff = null; phase = 'approval-unknown'; }
46
+ render();
47
+ }
48
+ });
49
+ initLanguage({ onChange: render });
50
+ render();
51
+ if (handoff) {
52
+ try {
53
+ pairing = await api.lookupBrowserPairing(handoff.ticket);
54
+ phase = 'ready'; render();
55
+ expiresTimer = setTimeout(() => { if (phase !== 'ready') return; clearPairing(); handoff = null; phase = 'missing'; render(); }, Math.max(0, pairing.expiresAt - Date.now()));
56
+ } catch (cause) {
57
+ if (cause.message !== 'authentication_required') { clearPairing(); handoff = null; phase = 'missing'; render(); }
58
+ }
59
+ }
60
+ window.addEventListener('pagehide', () => { clearTimeout(timer); clearTimeout(expiresTimer); });
@@ -0,0 +1,30 @@
1
+ const KEY = 'device-center-pairing-v1';
2
+ const safeStorage = () => { try { return window.sessionStorage; } catch { return null; } };
3
+ const valid = (value, now) => value && /^[a-f0-9]{64}$/.test(value.ticket ?? '') && Number.isSafeInteger(value.expiresAt) && value.expiresAt > now && value.expiresAt <= now + 330_000;
4
+
5
+ export function pendingPairing({ storage = safeStorage(), now = Date.now() } = {}) {
6
+ try {
7
+ const value = JSON.parse(storage.getItem(KEY));
8
+ if (valid(value, now)) return value;
9
+ storage.removeItem(KEY);
10
+ } catch { /* No token in error messages. */ }
11
+ return null;
12
+ }
13
+ export function clearPairing({ storage = safeStorage() } = {}) { try { storage.removeItem(KEY); } catch { /* memory is cleared by the caller */ } }
14
+
15
+ export function capturePairing({ location = window.location, history = window.history, storage = safeStorage(), now = Date.now() } = {}) {
16
+ if (!location.hash) return pendingPairing({ storage, now });
17
+ const fragment = new URLSearchParams(location.hash.slice(1));
18
+ // Strip even malformed input before any network requests, login navigation or language changes.
19
+ history.replaceState(null, '', location.pathname + location.search);
20
+ clearPairing({ storage });
21
+ if ([...fragment.keys()].sort().join(',') !== 'expires,ticket') return null;
22
+ const value = { ticket: fragment.get('ticket'), expiresAt: Number(fragment.get('expires')) };
23
+ if (!valid(value, now)) return null;
24
+ try { storage.setItem(KEY, JSON.stringify(value)); } catch { /* A signed-in user can continue in memory. */ }
25
+ return value;
26
+ }
27
+
28
+ export function loginDestination(locale) {
29
+ return `${pendingPairing() ? '/pair' : '/'}?lang=${encodeURIComponent(locale)}`;
30
+ }
package/src/start.css CHANGED
@@ -1,47 +1,267 @@
1
- :root { color-scheme: light; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #25392d; background: #f5f7f2; }
1
+ :root { color-scheme: dark; font-family: "Helvetica Neue", "Arial", "PingFang SC", "Hiragino Sans GB", sans-serif; color: #eeece8; background: #0c0c0e; font-synthesis: none; --accent: #ff8254; --line: #2b2b2e; --muted: #9a999f; --mono: "SFMono-Regular", Consolas, "Liberation Mono", monospace; }
2
2
  * { box-sizing: border-box; }
3
+ html { scroll-behavior: smooth; scroll-padding-top: 108px; }
3
4
  body { margin: 0; }
4
- a { color: #2f6d4c; text-underline-offset: 3px; }
5
- header, main, footer { width: min(860px, calc(100% - 40px)); margin: 0 auto; }
6
- header { display: flex; justify-content: space-between; align-items: center; padding: 24px 0; gap: 16px; font-size: 13px; }
7
- .brand { display: flex; align-items: center; gap: 10px; font-weight: 700; text-decoration: none; }
8
- .brand span { color: #25392d; }
9
- .hero { padding: 48px 0 38px; }
10
- .eyebrow { font-size: 12px; color: #70816d; letter-spacing: .8px; }
11
- h1 { margin: 18px 0 20px; font-size: clamp(34px, 6vw, 54px); line-height: 1.2; letter-spacing: -1.8px; }
12
- .lead { max-width: 630px; font-size: 17px; line-height: 1.8; color: #62705e; }
13
- .platform { font-size: 12px; color: #70816d; }
14
- section:not(.hero) { padding: 26px 0; border-top: 1px solid #dae3d6; }
15
- h2 { margin: 0 0 22px; font-size: 19px; line-height: 1.6; }
16
- .step { display: inline-grid; place-items: center; width: 28px; height: 28px; margin-right: 7px; border-radius: 50%; background: #e3eddd; font-size: 13px; }
17
- .choices, .checks { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
18
- article { display: flex; flex-direction: column; padding: 22px; border: 1px solid #dce5d7; border-radius: 12px; background: #fff; }
19
- h3 { margin: 0 0 10px; font-size: 16px; }
20
- p, li { font-size: 14px; line-height: 1.85; color: #62705e; }
21
- article p { flex: 1; margin: 0 0 20px; }
22
- button { min-height: 42px; border: 1px solid #c9d8c4; border-radius: 8px; padding: 10px 14px; color: #2f6d4c; background: #f4f8ef; font: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }
23
- button.primary { color: white; background: #356c4c; border-color: #356c4c; }
24
- button:disabled { opacity: .6; }
25
- button:hover { filter: brightness(.96); }
26
- :focus-visible { outline: 2px solid #5c965d; outline-offset: 3px; }
27
- .hint { font-size: 12px; }
28
- details { margin-top: 18px; font-size: 13px; }
29
- summary { color: #436440; cursor: pointer; line-height: 1.8; }
30
- .custom label { display: block; margin: 14px 0 8px; }
31
- .custom-row { display: flex; gap: 10px; }
32
- input, textarea { width: 100%; min-width: 0; border: 1px solid #cbd7c5; border-radius: 8px; padding: 10px; background: white; color: #35482f; font: inherit; }
33
- textarea { margin-top: 12px; font-family: ui-monospace, monospace; font-size: 12px; line-height: 1.7; resize: vertical; }
34
- .custom-row button { flex-shrink: 0; }
35
- #copy-result { min-height: 24px; margin: 12px 0; font-size: 13px; }
36
- .success { color: #2f6d4c; }
37
- .error { color: #9e3f32; }
38
- ol { padding-left: 24px; }
39
- li { padding: 0 0 12px 5px; }
40
- .checks div { padding: 10px 0; }
41
- .checks p { margin: 0; }
42
- code { overflow-wrap: anywhere; font-size: 12px; }
43
- aside { margin: 10px 0 32px; padding: 20px 22px; border: 1px solid #dce5d7; border-radius: 10px; background: #edf2e8; font-size: 14px; }
44
- aside p { margin: 10px 0 0; font-size: 12px; }
45
- footer { display: flex; flex-wrap: wrap; gap: 14px 22px; padding: 14px 0 32px; font-size: 12px; }
46
- footer span { width: 100%; color: #7b8875; }
47
- @media (max-width: 560px) { .choices, .checks { grid-template-columns: 1fr; gap: 12px; } .hero { padding-top: 25px; } .custom-row { flex-direction: column; } header { font-size: 12px; } h2 { font-size: 17px; } }
5
+ [hidden] { display: none !important; }
6
+ ::selection { color: #0c0c0e; background: #ffad89; }
7
+ a { color: #eceae5; text-underline-offset: 5px; }
8
+ header, main, footer { width: min(1200px, calc(100% - 96px)); margin: 0 auto; }
9
+ header { position: sticky; top: 0; z-index: 10; display: flex; justify-content: space-between; align-items: center; min-height: 85px; gap: 24px; border-bottom: 1px solid var(--line); background: #0c0c0ef5; font-size: 13px; }
10
+ .brand { display: inline-flex; align-items: center; gap: 12px; color: #f4f2ee; font-weight: 650; font-size: 18px; letter-spacing: -.6px; text-decoration: none; }
11
+ .brand-mark { display: block; width: 30px; height: 30px; color: var(--accent); }
12
+ .brand-mark svg { width: 100%; height: 100%; fill: none; stroke: currentColor; stroke-width: 2.5; }
13
+ .panel-link { display: inline-flex; align-items: center; justify-content: center; padding: 9px 14px; border: 1px solid #424244; border-radius: 4px; color: #e5e3e0; font-size: 12px; text-decoration: none; transition: border-color .2s, background .2s; }
14
+ .panel-link .icon { width: 13px; height: 13px; margin-left: 8px; transform: rotate(-45deg); }
15
+ .panel-link:hover { border-color: #b2aaa4; background: #1b1a1b; }
16
+ .icon-library { position: absolute; width: 0; height: 0; overflow: hidden; }
17
+ .icon { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
18
+ .hero { position: relative; display: grid; grid-template-columns: minmax(0, .95fr) minmax(0, 1.05fr); align-items: center; gap: 16px; padding: 30px 0 36px; }
19
+ .hero-copy { position: relative; z-index: 1; padding: 42px 0 35px; }
20
+ .eyebrow { display: flex; align-items: center; gap: 17px; margin: 0 0 30px; color: #b3b0ad; font-size: 12px; line-height: 1.5; }
21
+ .section-index { display: inline-block; padding-right: 17px; border-right: 1px solid #505054; color: var(--accent); font: 10px var(--mono); letter-spacing: 1px; }
22
+ h1 { margin: 0 0 26px; font-size: clamp(44px, 5.2vw, 76px); font-weight: 600; line-height: 1.1; letter-spacing: -3px; }
23
+ h1 > span { display: block; }
24
+ h1 > span + span { margin-top: 9px; color: var(--accent); }
25
+ :lang(zh-CN) h1, :lang(zh-TW) h1 { font-size: clamp(44px, 5.9vw, 82px); font-weight: 600; letter-spacing: -4px; line-height: 1.12; }
26
+ :lang(ja) h1, :lang(ko) h1 { font-size: clamp(35px, 4vw, 59px); letter-spacing: -2px; line-height: 1.3; }
27
+ :lang(fr) h1 { font-size: clamp(40px, 4.6vw, 66px); }
28
+ :lang(de) h1, :lang(pt-BR) h1 { font-size: clamp(40px, 4.1vw, 60px); }
29
+ .lead { max-width: 430px; margin: 0 0 28px; color: #aaa7a5; font-size: 17px; line-height: 1.8; }
30
+ p, li { color: #a8a6ab; font-size: 14px; line-height: 1.8; }
31
+ .hero-actions { display: flex; align-items: center; flex-wrap: wrap; gap: 25px; }
32
+ .button, button { display: inline-flex; align-items: center; justify-content: center; gap: 10px; min-height: 42px; border: 1px solid #4a4848; border-radius: 4px; padding: 10px 15px; background: #161517; color: #e9e5df; font: inherit; font-size: 13px; font-weight: 500; line-height: 1.5; text-align: center; text-decoration: none; cursor: pointer; transition: background .2s, color .2s, border-color .2s, transform .2s; }
33
+ button .icon { width: 16px; height: 16px; }
34
+ .primary { color: #211009; background: var(--accent); border-color: var(--accent); font-weight: 600; }
35
+ .hero-cta { min-height: 49px; padding: 12px 21px; }
36
+ .hero-cta .icon { margin-left: 13px; transition: transform .2s; }
37
+ button:hover, .button:hover { background: #282426; border-color: #9e8e86; }
38
+ .primary:hover { background: #ff9a72; color: #211009; border-color: #ff9a72; }
39
+ .hero-cta:hover .icon { transform: translateX(4px); }
40
+ .cta-hint { max-width: 180px; color: #8d8584; font-size: 11px; line-height: 1.8; }
41
+ .returning-user { color: #8d8584; font-size: 11px; }
42
+ button:disabled { opacity: .55; cursor: wait; }
43
+ :focus-visible { outline: 2px solid #ffb28b; outline-offset: 5px; }
44
+ .platform { margin: 21px 0 0; max-width: 430px; color: #8b8788; font-size: 11px; line-height: 1.7; }
45
+ .release-note { display: flex; align-items: center; gap: 8px; margin: 31px 0 0; color: #aaa19b; font-size: 10px; letter-spacing: .3px; }
46
+ .release-dot { width: 4px; height: 4px; border-radius: 50%; background: #c47d5b; }
47
+ .scene { position: relative; min-width: 0; margin: 0; align-self: stretch; display: flex; flex-direction: column; justify-content: center; }
48
+ .scene-caption { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; padding: 0 8px 0 20px; color: #918b88; font-size: 10px; }
49
+ .scene-caption > span { display: inline-flex; align-items: center; gap: 8px; }
50
+ .caption-cross { color: #c07550; font: 16px var(--mono); }
51
+ .scene-stage { position: relative; margin: -10px -14px -9px -10px; transform: perspective(950px) rotateX(var(--scene-rx, 0deg)) rotateY(var(--scene-ry, 0deg)); transition: transform .4s ease-out; }
52
+ .hardware-art { display: block; width: 100%; height: auto; overflow: visible; }
53
+ .graph-label { position: absolute; z-index: 1; display: flex; flex-direction: column; gap: 4px; max-width: 42%; pointer-events: none; }
54
+ .graph-label strong { color: #d9d1cb; font-size: 12px; font-weight: 500; line-height: 1.6; }
55
+ .graph-label > span { color: #9b9290; font-size: 10px; line-height: 1.5; }
56
+ .graph-label-agent { left: 4.7%; top: 48.5%; }
57
+ .graph-label-target { right: 3.5%; top: 5%; text-align: right; }
58
+ .signal { stroke-dasharray: 14 270; animation: wire-signal 5s linear infinite; }
59
+ .signal-two { animation-delay: -2.4s; animation-direction: reverse; }
60
+ @keyframes wire-signal { from { stroke-dashoffset: 284; } to { stroke-dashoffset: 0; } }
61
+ .scene[data-paused="true"] .signal { animation-play-state: paused; }
62
+ .scene[data-paused="true"] .scene-stage { transform: none; }
63
+ .motion-toggle { width: 28px; min-height: 28px; height: 28px; padding: 6px; border-color: transparent; border-radius: 50%; color: #9c9490; background: transparent; }
64
+ .motion-toggle:hover { border-color: #51423b; background: #252021; }
65
+ .motion-toggle .icon { width: 12px; height: 12px; }
66
+ .motion-toggle .play-icon, .scene[data-paused="true"] .pause-icon { display: none; }
67
+ .scene[data-paused="true"] .play-icon { display: block; }
68
+ .scene-query { position: relative; z-index: 1; display: flex; align-items: center; gap: 12px; padding: 15px 4px 15px 16px; margin: 0 7px 0 20px; border-top: 1px solid #353032; border-bottom: 1px solid #353032; background: linear-gradient(90deg, #221b1880, transparent); }
69
+ .query-symbol { flex-shrink: 0; color: var(--accent); font: 16px var(--mono); }
70
+ .scene blockquote { margin: 0; flex: 1; color: #d3c6bd; font-size: 13px; line-height: 1.7; }
71
+ .query-arrow { align-self: flex-end; padding: 0 6px; color: #685e59; }
72
+ .scene-resources { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 25px; padding: 15px 0 0 36px; color: #8a7c74; }
73
+ .scene-resources > span { display: inline-flex; align-items: center; gap: 7px; font-size: 10px; }
74
+ .scene-resources .icon { width: 13px; height: 13px; }
75
+ .scenarios { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
76
+ .scenarios > div { position: relative; display: flex; align-items: center; gap: 13px; padding: 25px 22px; }
77
+ .scenarios > div:first-child { padding-left: 0; }
78
+ .scenarios > div + div { border-left: 1px solid var(--line); }
79
+
80
+ .scenarios > div > .icon { width: 19px; height: 19px; color: #927b6d; }
81
+ .scenarios p { margin: 0; color: #979095; font-size: 12px; line-height: 1.75; }
82
+ .scenarios strong, .scenarios p span { display: block; }
83
+ .scenarios strong { color: #ded7d1; font-size: 14px; font-weight: 500; }
84
+ section.install-section { display: grid; grid-template-columns: minmax(0, .6fr) minmax(0, 1.4fr); gap: 58px; padding: 58px 0 38px; }
85
+ .section-heading .eyebrow { color: var(--accent); font-size: 12px; margin-bottom: 15px; }
86
+ .action-marker { font-size: 22px; line-height: 1; }
87
+ h2 { margin: 0; font-size: 30px; font-weight: 500; line-height: 1.5; letter-spacing: -1px; text-wrap: balance; }
88
+ .section-intro { max-width: 280px; margin: 17px 0 0; color: #aaa0a0; font-size: 14px; line-height: 1.9; }
89
+ .section-note { margin: 13px 0 0; color: #82797b; font-size: 11px; }
90
+ .action-workbench { min-width: 0; }
91
+ .mode-options { min-width: 0; padding: 0; margin: 0; border: 0; }
92
+ .mode-options legend, .action-heading { display: flex; align-items: center; gap: 12px; padding: 0; color: #e8e1db; font-size: 14px; font-weight: 500; line-height: 1.6; }
93
+ .mode-options legend { margin-bottom: 15px; }
94
+ .action-number { flex-shrink: 0; color: #ab8069; font: 11px/1.6 var(--mono); letter-spacing: .3px; }
95
+ .choices { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
96
+ .mode-choice { display: grid; grid-template-columns: 25px minmax(0, 1fr) 16px; align-items: start; gap: 12px; padding: 17px 15px; border: 1px solid #373337; border-radius: 4px; background: #111113; cursor: pointer; transition: border-color .2s, background .2s; }
97
+ .mode-choice:hover { border-color: #a78371; background: #191619; }
98
+ .mode-choice:has(input:checked) { border-color: #b47758; background: #251c18; }
99
+ .mode-choice:has(input:focus-visible) { outline: 2px solid #ffb28b; outline-offset: 4px; }
100
+ .mode-choice input { grid-column: 3; grid-row: 1; width: 16px; height: 16px; padding: 0; margin: 5px 0 0; accent-color: var(--accent); cursor: pointer; }
101
+ .choice-icon { grid-column: 1; grid-row: 1; color: #b89582; }
102
+ .choice-icon .icon { width: 25px; height: 25px; stroke-width: 1.2; }
103
+ .choice-copy { grid-column: 2; grid-row: 1; min-width: 0; }
104
+ .choice-copy strong { display: block; color: #e8e1db; font-size: 15px; font-weight: 500; line-height: 1.7; }
105
+ .choice-copy > span { display: block; margin-top: 5px; color: #a29691; font-size: 12px; line-height: 1.8; }
106
+ h3 { margin: 0; color: #e8e1db; font-size: 16px; font-weight: 500; line-height: 1.6; }
107
+ .custom { margin-top: 12px; }
108
+ .self-hosted-choice { display: flex; align-items: center; gap: 10px; margin-top: 16px; color: #e3d3c9; font-size: 13px; cursor: pointer; }
109
+ .self-hosted-choice input { width: 16px; height: 16px; padding: 0; margin: 0; accent-color: var(--accent); }
110
+ .copy-step { margin-top: 23px; padding-top: 21px; border-top: 1px solid #363034; }
111
+ .copy-step .action-heading { margin-bottom: 13px; }
112
+ .method-options, .pairing-options { min-width: 0; padding: 0; margin: 0; border: 0; }
113
+ .method-switch { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 4px; padding: 4px; margin-bottom: 16px; border: 1px solid #373337; border-radius: 5px; background: #111113; }
114
+ .method-switch label { position: relative; min-width: 0; cursor: pointer; }
115
+ .method-switch input { position: absolute; width: 1px; height: 1px; padding: 0; margin: 0; opacity: 0; }
116
+ .method-switch span { display: block; padding: 9px 12px; border: 1px solid transparent; border-radius: 3px; color: #a79892; font-size: 13px; text-align: center; line-height: 1.6; }
117
+ .method-switch label:hover span { color: #e8e1db; background: #1e191b; }
118
+ .method-switch input:checked + span { color: #ffcfb2; border-color: #74503d; background: #30221b; }
119
+ .method-switch input:focus-visible + span { outline: 2px solid #ffb28b; outline-offset: 3px; }
120
+ .manual-prerequisites { margin: 0 0 15px; line-height: 1.8; }
121
+ .pairing-options { margin-bottom: 17px; }
122
+ .pairing-options legend { margin-bottom: 9px; color: #b9a69b; font-size: 12px; }
123
+ .pairing-choices { display: flex; flex-wrap: wrap; gap: 10px 22px; }
124
+ .pairing-choices label { display: flex; align-items: center; gap: 8px; color: #d6c6bc; font-size: 12px; cursor: pointer; }
125
+ .pairing-choices input { width: 15px; height: 15px; margin: 0; padding: 0; accent-color: var(--accent); }
126
+ .pairing-choices input:focus-visible { outline: 2px solid #ffb28b; outline-offset: 3px; }
127
+ #copy-instructions { width: 100%; min-height: 48px; justify-content: flex-start; padding: 12px 17px; font-size: 14px; }
128
+ .copy-arrow { margin-left: auto; }
129
+ .copy-hint { margin: 11px 0 0; color: #938681; font-size: 11px; line-height: 1.8; }
130
+ #copy-result { margin: 12px 0 0; font-size: 12px; line-height: 1.8; }
131
+ #copy-result:empty { display: none; }
132
+ .action-workbench:has(.next-action[data-copied="true"]) .copy-hint { display: none; }
133
+ #copy-result.success { margin-top: 11px; font-size: 11px; }
134
+ .success { color: #efb68e; }
135
+ .error { color: #f0998b; }
136
+ #instruction-details { margin-top: 12px; }
137
+ .next-action { margin-top: 23px; padding: 16px 20px; border: 1px solid #353136; border-radius: 4px; background: #111113; transition: border-color .2s, background .2s; }
138
+ .next-action .action-number { color: #766662; }
139
+ .next-action > .walkthrough-disclaimer { margin: 6px 0 13px; color: #a0918c; font-size: 11px; line-height: 1.7; }
140
+ .next-action[data-copied="true"] { border-color: #bc7a56; background: #241b16; }
141
+ .next-action[data-copied="true"] .action-heading, .next-action[data-copied="true"] .action-number { color: #ffc29b; }
142
+ .binding-link { display: inline-block; margin: 13px 0 0; color: #f4b991; font-size: 12px; line-height: 1.7; }
143
+ .walkthrough-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 5px; margin: 0 0 16px; padding: 0; list-style: none; }
144
+ .walkthrough-tabs li { min-width: 0; }
145
+ .walkthrough-tabs button { width: 100%; min-height: 38px; display: flex; align-items: center; justify-content: center; gap: 7px; padding: 7px; border: 1px solid #493a33; border-radius: 3px; background: transparent; color: #a9968a; font: inherit; font-size: 12px; line-height: 1.5; cursor: pointer; }
146
+ .walkthrough-tabs button span { color: #86756a; font: 10px var(--mono); }
147
+ .walkthrough-tabs button:hover { border-color: #bc7a56; color: #f4c8ab; }
148
+ .walkthrough-tabs button[aria-current="step"] { background: #3d2a20; border-color: #c48d68; color: #ffcfb1; }
149
+ .walkthrough-tabs button[aria-current="step"] span { color: #ffc29b; }
150
+ .walkthrough-caption { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; margin-bottom: 10px; color: #f4dfd0; font-size: 14px; line-height: 1.7; }
151
+ .walkthrough-caption small { flex-shrink: 0; color: #a48c7c; font: 11px var(--mono); }
152
+ .walkthrough-picture { overflow: hidden; padding: 16px 18px 14px; border: 1px solid #3b302a; border-radius: 5px; background: #121011; }
153
+ .walkthrough-picture svg { width: 22px; height: 22px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
154
+ .picture-transfer { display: flex; align-items: center; justify-content: center; gap: 18px; margin-bottom: 16px; color: #dfb394; font-size: 12px; line-height: 1.7; }
155
+ .picture-transfer > span { display: flex; align-items: center; gap: 8px; min-width: 0; }
156
+ .picture-transfer > svg { width: 28px; color: #b58160; }
157
+ .picture-window { min-width: 0; border: 1px solid #5a4940; border-radius: 7px; background: #1b1818; box-shadow: 0 8px 20px #0003; }
158
+ .picture-titlebar { display: flex; align-items: center; gap: 5px; min-height: 28px; padding: 6px 12px; border-bottom: 1px solid #3e3430; background: #252020; border-radius: 6px 6px 0 0; }
159
+ .picture-titlebar > i { flex-shrink: 0; width: 6px; height: 6px; border: 1px solid #887369; border-radius: 50%; }
160
+ .picture-titlebar > i:first-child { background: #956e5e; }
161
+ .picture-titlebar > span { min-width: 0; margin-left: 9px; color: #baa599; font-size: 10px; line-height: 1.6; }
162
+ .picture-address { display: block; overflow-wrap: anywhere; font: 10px var(--mono); }
163
+ .picture-terminal { padding: 22px 18px 16px; }
164
+ .picture-paste-line { display: flex; align-items: center; gap: 12px; position: relative; color: #ffb98a; font: 12px var(--mono); }
165
+ .picture-paste-line > div { flex: 1; padding: 14px 12px; border: 1px dashed #db9365; border-radius: 3px; background: #35261e; color: #ffcfaf; line-height: 1.8; }
166
+ .picture-caret { display: inline-block; height: 15px; margin-left: 9px; border-left: 2px solid #ffa875; vertical-align: middle; }
167
+ .walkthrough-picture .picture-pointer { position: absolute; right: 6px; bottom: -16px; width: 25px; height: 30px; fill: #f5e9df; stroke: #151112; stroke-width: 1.8; transform: rotate(-9deg); }
168
+ .picture-keyline { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px; margin-top: 26px; color: #c0a596; font-size: 10px; line-height: 1.8; }
169
+ .picture-keyline strong { color: #ffd5b9; font-weight: 400; }
170
+ .walkthrough-picture kbd { display: inline-block; margin-left: 3px; padding: 2px 6px; border: 1px solid #655045; border-bottom-width: 2px; border-radius: 3px; background: #28201d; font: 10px var(--mono); }
171
+ .picture-launch { display: flex; flex-wrap: wrap; gap: 5px 18px; margin-top: 15px; color: #a38c7e; font-size: 10px; line-height: 1.8; }
172
+ .picture-chat { padding: 22px 18px 16px; }
173
+ .picture-chat-message { width: 88%; margin-left: auto; padding: 21px 15px; border: 1px dashed #d08b64; border-radius: 9px 9px 2px 9px; background: #37271e; color: #ffd3b5; font-size: 13px; line-height: 1.8; }
174
+ .picture-send { position: relative; display: flex; align-items: center; justify-content: flex-end; gap: 10px; margin-top: 19px; padding-right: 9px; color: #d8bca9; font-size: 11px; }
175
+ .picture-send b { display: grid; place-items: center; width: 29px; height: 29px; background: #e8b08b; border-radius: 50%; color: #241912; font-size: 19px; font-weight: 400; }
176
+ .picture-send .picture-pointer { bottom: -9px; right: 0; width: 20px; }
177
+ .picture-auto-open { margin-bottom: 14px; color: #c8a88f; text-align: center; font-size: 11px; line-height: 1.7; }
178
+ .picture-pair { padding: 12px 16px; }
179
+ .picture-signin { display: flex; align-items: baseline; gap: 9px; color: #d4b7a4; font-size: 11px; line-height: 1.7; }
180
+ .picture-signin > span { color: #c29170; font: 9px var(--mono); }
181
+ .picture-signin + .picture-signin { margin-top: 8px; }
182
+ .picture-fingerprints { display: grid; grid-template-columns: auto minmax(0, 1fr) 20px; gap: 6px 12px; padding: 10px; margin: 10px 0 13px; border: 1px solid #65503f; border-radius: 3px; background: #29211c; font-size: 10px; }
183
+ .picture-fingerprints > span { color: #bca08b; }
184
+ .picture-fingerprints code { overflow-wrap: anywhere; color: #ffd1a9; font: 10px/1.7 var(--mono); }
185
+ .picture-fingerprints > b { grid-column: 3; grid-row: 1 / span 2; align-self: center; justify-self: center; color: #e9b07d; font-size: 20px; font-weight: 400; line-height: .7; }
186
+ .picture-approval { position: relative; width: fit-content; max-width: 100%; margin: 0 0 5px auto; padding: 8px 12px; border: 1px solid #c58c63; border-radius: 3px; background: #4a3222; color: #ffcfaa; font-size: 11px; line-height: 1.7; }
187
+ .picture-approval .picture-pointer { right: -7px; bottom: -15px; width: 21px; }
188
+ .headless-transfer { gap: 13px; font-size: 11px; }
189
+ .headless-transfer > span:first-child { flex-direction: column; align-items: flex-start; gap: 6px; }
190
+ .headless-transfer code { padding: 4px 7px; border: 1px dashed #a57958; color: #ffd0a9; font: 10px/1.7 var(--mono); }
191
+ .picture-code-entry { margin: 10px 0 12px; color: #b49a89; font-size: 10px; }
192
+ .picture-code-entry > div { position: relative; margin-top: 6px; padding: 9px 12px; border: 1px dashed #b7825f; border-radius: 3px; color: #ffd1b0; background: #30231c; }
193
+ .picture-install { padding: 21px 20px; }
194
+ .picture-install-command { margin-bottom: 19px; color: #e4c5af; font: 13px var(--mono); }
195
+ .picture-install-command > span { color: #ac856a; }
196
+ .picture-install-row { display: flex; align-items: baseline; gap: 12px; margin: 10px 0; color: #cfb5a3; font-size: 12px; }
197
+ .picture-install-row > span { color: #a7866f; font: 10px var(--mono); }
198
+ .picture-install-track { display: flex; gap: 5px; margin-top: 21px; }
199
+ .picture-install-track i { flex: 1; height: 3px; background: #8b644b; }
200
+ .picture-install-track i:nth-child(2) { background: #5e473a; }
201
+ .picture-install-track i:nth-child(3) { background: #392d26; }
202
+ .picture-dashboard { padding: 20px 18px; }
203
+ .picture-device { display: grid; grid-template-columns: 24px minmax(0, 1fr); align-items: center; gap: 4px 12px; color: #d4aa8a; }
204
+ .picture-device svg { grid-row: span 2; }
205
+ .picture-device strong { color: #f1d1b9; font-size: 13px; font-weight: 500; }
206
+ .picture-device > span { color: #a38b7a; font-size: 10px; }
207
+ .picture-metrics { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; margin: 24px 0 20px; }
208
+ .picture-metrics > div { display: grid; gap: 8px; }
209
+ .picture-metrics span { color: #b29c8c; font-size: 11px; }
210
+ .picture-metrics b { color: #ceb39e; font: 14px var(--mono); }
211
+ .picture-metrics i { height: 3px; background: #4b3b30; }
212
+ .picture-report-time { position: relative; padding: 10px 12px; border: 1px dashed #9f7555; color: #ebc3a2; font-size: 11px; line-height: 1.8; }
213
+ .picture-report-time .picture-pointer { right: 3px; bottom: -14px; width: 21px; }
214
+ .walkthrough-description { margin: 12px 0 0; color: #c5ad9d; font-size: 12px; line-height: 1.9; }
215
+ .walkthrough-copy-first { margin: 8px 0 0; color: #aa9382; font-size: 11px; line-height: 1.8; }
216
+ .walkthrough-navigation { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-top: 12px; padding-top: 10px; border-top: 1px solid #49362b; }
217
+ .walkthrough-navigation button { min-height: 30px; padding: 4px 0; border: 0; background: transparent; color: #f3bd94; font: inherit; font-size: 12px; line-height: 1.7; cursor: pointer; }
218
+ .walkthrough-navigation button:hover { color: #ffe0c6; text-decoration: underline; text-underline-offset: 4px; }
219
+ .walkthrough-navigation button:disabled { color: #766155; cursor: default; text-decoration: none; }
220
+ .walkthrough-tabs button:focus-visible, .walkthrough-navigation button:focus-visible, #walkthrough-title:focus-visible { outline: 2px solid #ffc29b; outline-offset: 4px; }
221
+ @media (max-width: 700px) { .walkthrough-tabs { gap: 4px; margin-bottom: 16px; } .walkthrough-tabs button { flex-direction: column; gap: 4px; padding: 9px 5px; font-size: 11px; } .walkthrough-caption { font-size: 13px; gap: 8px; } .walkthrough-picture { padding: 17px 12px 15px; } .picture-terminal { padding: 23px 12px 15px; } .picture-paste-line { gap: 8px; font-size: 11px; } .picture-paste-line > div { padding: 12px 10px; } .picture-transfer { gap: 13px; font-size: 11px; } .picture-titlebar { padding: 8px; } .picture-titlebar > span { margin-left: 5px; } .picture-keyline { gap: 9px; } .picture-pair { padding: 15px 12px; } .picture-fingerprints { gap: 6px 8px; padding: 10px; } .picture-fingerprints code { font-size: 9px; } .picture-dashboard { padding: 18px 12px; } .picture-metrics { gap: 18px; } .walkthrough-description { font-size: 11px; } .headless-transfer { gap: 8px; font-size: 10px; } .headless-transfer code { font-size: 9px; } }
222
+ summary { color: #a2938b; cursor: pointer; font-size: 11px; line-height: 1.8; list-style: none; }
223
+ summary::-webkit-details-marker { display: none; }
224
+ summary::before { content: "+"; display: inline-block; margin-right: 9px; color: #7e716a; font: 14px var(--mono); }
225
+ details[open] > summary::before { content: "−"; }
226
+ summary:hover { color: #f5c3a6; }
227
+ #custom-fields > label { display: block; margin: 15px 0 8px; color: #b9a69b; font-size: 12px; }
228
+ input, textarea { width: 100%; min-width: 0; border: 1px solid #504541; border-radius: 4px; padding: 11px; background: #151315; color: #e3d3c9; font: inherit; font-size: 13px; }
229
+ input[aria-invalid="true"] { border-color: #d47c6d; }
230
+ textarea { margin-top: 13px; font-family: var(--mono); font-size: 12px; line-height: 1.7; resize: vertical; }
231
+ .hint { color: #8f8480; font-size: 12px; }
232
+ .trust-line { display: flex; align-items: center; justify-content: flex-start; gap: 10px; padding: 18px 0; margin: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); color: #b6a094; font-size: 12px; }
233
+ .trust-line .icon { width: 17px; height: 17px; color: #c98e6c; }
234
+ section.more-section { display: grid; grid-template-columns: 1fr 1fr; gap: 0 40px; padding: 28px 0 40px; }
235
+ .more-section details { min-width: 0; border-bottom: 1px solid var(--line); }
236
+ .more-section details[open] { grid-column: 1 / -1; }
237
+ .more-section summary { display: flex; align-items: center; gap: 5px; padding: 17px 0; color: #aaa09c; font-size: 12px; }
238
+ .more-section summary::before { margin-right: 6px; }
239
+ .detail-content { padding: 0 21px 19px; }
240
+ .detail-content p { margin: 8px 0; color: #aaa0a0; font-size: 14px; }
241
+ .detail-content ol { padding-left: 20px; }
242
+ .detail-content li { padding: 0 0 9px 4px; color: #aaa0a0; font-size: 14px; }
243
+ .checks { display: grid; grid-template-columns: 1fr 1fr; gap: 19px 28px; padding-top: 6px; }
244
+ .checks h3 { font-size: 14px; }
245
+ footer { display: flex; flex-wrap: wrap; gap: 16px 27px; padding: 0 0 32px; font-size: 10px; }
246
+ footer a { color: #8b7f79; }
247
+ footer a:hover { color: #e8baa0; }
248
+ footer span { width: 100%; color: #8c807a; }
249
+ /* Keep the shared language control scoped to this public guide. */
250
+ .start-page .language-picker { color: #b8ada6; }
251
+ .start-page .language-picker .language-trigger { border-radius: 4px; font-size: 11px; }
252
+ .start-page .language-trigger > svg { color: #a68d7c; }
253
+ .start-page .language-picker .language-trigger:hover, .start-page .language-trigger[aria-expanded="true"] { background: #262022; border-color: #473d39; }
254
+ .start-page .language-menu { background: #191719; border-color: #51433d; color: #d9cbc1; border-radius: 8px; box-shadow: 0 15px 45px #0008; }
255
+ .start-page .language-menu-title { color: #96857a; }
256
+ .start-page .language-picker .language-option { color: #c5b8b0; border-radius: 4px; }
257
+ .start-page .language-picker .language-option:hover, .start-page .language-picker .language-option:focus-visible { background: #302728; }
258
+ .start-page .language-picker .language-option[aria-checked="true"] { background: #38271f; color: #ffb38b; }
259
+ .start-page .language-option > svg { color: #ff9a65; }
260
+ @media (min-width: 701px) { #install-title { scroll-margin-top: 40px; } }
261
+ @media (min-width: 1440px) { .hero { gap: 35px; padding-top: 38px; } .hero-copy { padding-top: 60px; padding-bottom: 50px; } }
262
+ @media (max-width: 1000px) { header, main, footer { width: calc(100% - 56px); } .hero { gap: 8px; padding-top: 25px; } .hero-copy { padding: 25px 0; } .eyebrow { gap: 10px; font-size: 11px; margin-bottom: 24px; } .section-index { padding-right: 10px; } h1 { font-size: clamp(34px, 4.9vw, 54px); } :lang(zh-CN) h1, :lang(zh-TW) h1 { font-size: clamp(46px, 6vw, 60px); } :lang(ja) h1, :lang(ko) h1 { font-size: clamp(28px, 4.1vw, 41px); } :lang(de) h1, :lang(fr) h1, :lang(pt-BR) h1 { font-size: clamp(32px, 4.4vw, 48px); } .lead { font-size: 15px; max-width: 360px; } .hero-actions { gap: 19px; } .scene-caption { padding-left: 12px; } .graph-label strong { font-size: 11px; } .graph-label > span { font-size: 9px; } .scene-query { margin-left: 12px; padding-left: 10px; gap: 8px; } .scene blockquote { font-size: 11px; } .scene-resources { padding-left: 22px; gap: 15px; } .scenarios > div { padding: 22px 15px; } .scenarios strong { font-size: 13px; } .scenarios p { font-size: 11px; } h2 { font-size: 24px; } }
263
+ @media (max-width: 700px) { html { scroll-padding-top: 104px; } header, main, footer { width: calc(100% - 40px); } header { min-height: 75px; gap: 10px; padding: 14px 0; } .brand { font-size: 16px; gap: 9px; } .brand-mark { width: 25px; height: 25px; } .panel-link { padding: 7px 9px; font-size: 11px; } .start-page .header-actions { gap: 7px; } .start-page .language-picker .language-trigger { padding-left: 5px; padding-right: 5px; font-size: 10px; gap: 5px; } .hero { grid-template-columns: 1fr; gap: 0; padding: 26px 0 25px; } .hero-copy { padding: 0; } .eyebrow { font-size: 11px; margin-bottom: 25px; } h1 { font-size: clamp(46px, 10vw, 64px); letter-spacing: -2.5px; margin-bottom: 24px; } :lang(zh-CN) h1, :lang(zh-TW) h1 { font-size: clamp(50px, 12.5vw, 72px); letter-spacing: -3px; } :lang(ja) h1, :lang(ko) h1 { font-size: clamp(33px, 8vw, 48px); letter-spacing: -1.8px; } :lang(de) h1, :lang(fr) h1, :lang(pt-BR) h1 { font-size: clamp(38px, 9.5vw, 54px); } .lead { max-width: 390px; margin-bottom: 24px; font-size: 15px; line-height: 1.9; } .hero-actions { gap: 24px; } .hero-cta { min-height: 46px; padding: 11px 17px; } .platform { margin-top: 19px; font-size: 10px; } .release-note { margin-top: 17px; font-size: 9px; } .scene { margin-top: 36px; } .scene-stage { margin-top: 0; margin-bottom: -5px; } .graph-label-target { top: 0; max-width: 55%; } .graph-label-target > span { font-size: 9px; } .scene-caption { padding-left: 0; padding-right: 0; } .graph-label strong { font-size: 12px; } .graph-label > span { font-size: 10px; } .scene-query { margin: 0; padding: 12px 7px 12px 12px; gap: 12px; } .scene blockquote { font-size: 12px; } .scene-resources { padding-left: 13px; padding-top: 14px; gap: 22px; } .scenarios { grid-template-columns: 1fr; } .scenarios > div, .scenarios > div:first-child { padding: 15px 0; gap: 17px; } .scenarios > div + div { border-left: 0; border-top: 1px solid #252326; } .scenarios p { display: flex; align-items: baseline; flex-wrap: wrap; gap: 3px 14px; font-size: 12px; } .scenarios strong { min-width: 88px; font-size: 13px; } h2 { font-size: 25px; max-width: 420px; text-wrap: auto; } summary { font-size: 12px; } .trust-line { align-items: flex-start; font-size: 11px; gap: 10px; padding: 18px 0; } .trust-line .icon { margin-top: 3px; } section.more-section { grid-template-columns: 1fr; gap: 0; padding: 18px 0 30px; } .more-section summary { padding: 17px 0; } .checks { grid-template-columns: 1fr; } .detail-content { padding-left: 18px; padding-right: 5px; } footer { gap: 14px 20px; font-size: 10px; } }
264
+ @media (max-width: 700px) { :lang(es) h1 { font-size: clamp(40px, 10vw, 56px); } }
265
+ @media (max-width: 1000px) { section.install-section { gap: 30px; } .choices { grid-template-columns: 1fr; } .mode-choice { padding: 14px; } .section-intro { font-size: 13px; } }
266
+ @media (max-width: 700px) { .returning-user { display: none; } .cta-hint { max-width: 150px; font-size: 11px; } section.install-section { grid-template-columns: 1fr; gap: 25px; padding: 34px 0 30px; } .section-heading .eyebrow { margin-bottom: 10px; } .section-heading h2 { font-size: 26px; } .section-intro { max-width: 380px; margin-top: 11px; font-size: 13px; } .section-note { margin-top: 8px; } .choices { gap: 10px; } .mode-choice { padding: 15px; } .choice-copy strong { font-size: 15px; } .choice-copy > span { margin-top: 2px; font-size: 12px; } .copy-step { margin-top: 20px; padding-top: 20px; } .next-action { padding: 17px 15px; margin-top: 20px; } .action-heading, .mode-options legend { font-size: 14px; gap: 10px; } .copy-hint { font-size: 11px; } }
267
+ @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } *, *::before, *::after { animation: none !important; transition: none !important; } .motion-toggle { display: none; } .scene-stage { transform: none !important; } }