dscord 0.4.0-alpha.3 → 0.4.0-alpha.6

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.
Files changed (67) hide show
  1. package/README.md +10 -10
  2. package/central/README.md +3 -3
  3. package/central/avatars.md +8 -6
  4. package/central/avatars.mjs +5 -2
  5. package/central/service.mjs +8 -5
  6. package/central/store.mjs +17 -1
  7. package/dist/admin/assets/index-BqwnqlFx.css +1 -0
  8. package/dist/admin/assets/index-mXQNxGvo.js +10 -0
  9. package/dist/admin/index.html +2 -2
  10. package/dist/admin/licenses/bundled-dependencies.txt +93 -0
  11. package/dist/central-admin/assets/index-ByNljbpy.css +1 -0
  12. package/dist/central-admin/assets/index-CKu96QBX.js +9 -0
  13. package/dist/central-admin/index.html +2 -2
  14. package/dist/central-admin/licenses/bundled-dependencies.txt +93 -0
  15. package/dist/web/assets/index-493-xdZs.js +55 -0
  16. package/dist/web/assets/index-BHZyzeVq.css +1 -0
  17. package/dist/web/index.html +3 -7
  18. package/dist/web/sw.js +269 -68
  19. package/dsh-connect/KERNEL.md +38 -8
  20. package/dsh-connect/client.js +428 -150
  21. package/dsh-connect/controller.mjs +50 -17
  22. package/dsh-connect/decision-settings.mjs +2 -1
  23. package/dsh-connect/index.mjs +2 -1
  24. package/dsh-connect/native-bridge.mjs +30 -13
  25. package/dsh-connect/native-connections.mjs +5 -3
  26. package/dsh-connect/native-team.mjs +3 -0
  27. package/dsh-connect/work-core.mjs +189 -13
  28. package/dsh-plugin/README.md +2 -2
  29. package/memory/contacts.mjs +8 -2
  30. package/memory/native.mjs +2 -2
  31. package/memory/observation-policy.mjs +6 -3
  32. package/memory/panel.mjs +1 -1
  33. package/memory/service.mjs +16 -6
  34. package/memory/source-query.mjs +10 -7
  35. package/memory/store.mjs +276 -41
  36. package/memory/views.mjs +5 -8
  37. package/memory//345/244/215/347/224/250/344/270/216/350/277/220/350/241/214.md +4 -2
  38. package/memory//345/275/223/345/211/215/344/270/212/344/270/213/346/226/207/344/270/216/345/233/236/351/241/276.md +1 -1
  39. package/package.json +1 -1
  40. package/src/admin.mjs +20 -1
  41. package/src/bot-node.mjs +27 -6
  42. package/src/conversation-view.mjs +103 -0
  43. package/src/core/node-routes.mjs +4 -1
  44. package/src/core/room-routes.mjs +56 -31
  45. package/src/core.mjs +28 -2
  46. package/src/decision-provider.mjs +41 -8
  47. package/src/decision-state.mjs +94 -0
  48. package/src/front-prompt.mjs +348 -0
  49. package/src/kernel-control.mjs +2 -1
  50. package/src/message-context.mjs +0 -40
  51. package/src/node-store.mjs +47 -0
  52. package/src/profile-sync.mjs +7 -3
  53. package/src/push.mjs +51 -14
  54. package/src/room-reader.mjs +55 -0
  55. package/src/server-update-control.mjs +41 -0
  56. package/src/server-update-docker.mjs +70 -0
  57. package/src/server-update-release.mjs +58 -0
  58. package/src/server-updater.mjs +323 -0
  59. package/vendor/ds-bot/UPSTREAM.json +4 -0
  60. package/vendor/ds-bot/src/host/prompt.js +4 -1
  61. package//346/250/241/345/236/213/351/205/215/347/275/256/344/270/216/350/277/201/347/247/273.md +7 -3
  62. package/dist/admin/assets/index-B6oXrmH-.js +0 -10
  63. package/dist/admin/assets/index-C7fyavkW.css +0 -1
  64. package/dist/central-admin/assets/index-C-slX89W.css +0 -1
  65. package/dist/central-admin/assets/index-C06XyvSK.js +0 -9
  66. package/dist/web/assets/index-C5Owa1w-.js +0 -55
  67. package/dist/web/assets/index-Cr0sYP4g.css +0 -1
package/README.md CHANGED
@@ -1,25 +1,25 @@
1
- # DSCord 0.4.0-alpha.3 安装与连接
1
+ # DSCord 0.4.0-alpha.6 安装与连接
2
2
 
3
3
  > [!WARNING]
4
4
  > **实验性项目。** DSCord 目前是实验性质的项目,接口、数据格式和行为随时可能变化,不保证兼容旧版本。项目不考虑安全问题,因此将别人加入到你的服务器当中的时候,请确认你有线下找他喝茶的能力。
5
5
 
6
6
  DSCord 的 npm 包包含 Core、预构建网页/PWA、Matrix 适配器和 DSCord Bot 内核。完整服务器源码发行包另含 Compose 部署文件;同一部署的 Core、客户端和节点使用同一发行版本。
7
7
 
8
- 本指南对应预发布版本 0.4.0-alpha.3,npm registry 和该版本的 GitHub Release 均提供安装包。Node 支持范围由 [package.json](package.json) 声明为 `>=24.19.0 <25`。Bot 宿主固定使用 DSH `0.2.2-rc.1` 的 SDK 契约,见[内核说明](dsh-connect/KERNEL.md)。Docker 服务器无需在宿主另装 Node。
8
+ 本指南对应预发布版本 0.4.0-alpha.6,npm registry 和该版本的 GitHub Release 均提供安装包。Node 支持范围由 [package.json](package.json) 声明为 `>=24.19.0 <25`。Bot 宿主固定使用 DSH `0.2.2-rc.1` 的 SDK 契约,见[内核说明](dsh-connect/KERNEL.md)。Docker 服务器无需在宿主另装 Node。
9
9
 
10
10
  ## 安装
11
11
 
12
- 从发行页下载 `dscord-0.4.0-alpha.3.tgz` 后,可在 DSH 的 `Plugins → Add plugin` 中安装并启用 `dscord`,再登录服务器连接 Bot。发行页同时提供 `SHA256SUMS` 校验文件。[下载发行包](https://github.com/MixGeekers/dscord/releases/tag/ds-group-v0.4.0-alpha.3)
12
+ 从发行页下载 `dscord-0.4.0-alpha.6.tgz` 后,可在 DSH 的 `Plugins → Add plugin` 中安装并启用 `dscord`,再登录服务器连接 Bot。发行页同时提供 `SHA256SUMS` 校验文件。[下载发行包](https://github.com/MixGeekers/dscord/releases/tag/ds-group-v0.4.0-alpha.6)
13
13
 
14
14
  全局安装 CLI 用于服务器与独立节点管理。从 npm registry 安装当前版本,安装后核对 `dscord --version`。
15
15
 
16
16
  ```sh
17
- npm install --global dscord@0.4.0-alpha.3
17
+ npm install --global dscord@0.4.0-alpha.6
18
18
  dscord --help
19
19
  dscord --version
20
20
  ```
21
21
 
22
- Core、CLI 和 DSH 插件使用同一发行版本。已有全局安装可用上述命令升级。当前预发布版本使用 `alpha` 标签,也可执行 `npm install --global dscord@alpha` 升级到最新 Alpha。[npm 包页面](https://www.npmjs.com/package/dscord/v/0.4.0-alpha.3)
22
+ Core、CLI 和 DSH 插件使用同一发行版本。已有全局安装可用上述命令升级。当前预发布版本使用 `alpha` 标签,也可执行 `npm install --global dscord@alpha` 升级到最新 Alpha。[npm 包页面](https://www.npmjs.com/package/dscord/v/0.4.0-alpha.6)
23
23
 
24
24
  从源码构建 npm 包,在 `apps/ds-group/` 中执行:
25
25
 
@@ -28,11 +28,11 @@ npm ci --ignore-scripts
28
28
  npm run npm:pack
29
29
  ```
30
30
 
31
- 该版本默认生成 `.local/npm-release/dscord-0.4.0-alpha.3.tgz`,仍应核对命令实际输出的版本、路径与校验值。构建需要开发依赖,运行已提供的预构建包不需要重新构建界面。
31
+ 该版本默认生成 `.local/npm-release/dscord-0.4.0-alpha.6.tgz`,仍应核对命令实际输出的版本、路径与校验值。构建需要开发依赖,运行已提供的预构建包不需要重新构建界面。
32
32
 
33
33
  ## 部署服务器
34
34
 
35
- 完整服务器采用[Compose 部署指南](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.3/apps/ds-group/deploy/README.md),先配置自己的 Matrix 身份域、公开地址和持久存储。npm CLI 不代替 Synapse 或反向代理。需要 `dscord server`、初始化及节点导入等命令时,在各终端保持同一个绝对数据路径:
35
+ 完整服务器采用[Compose 部署指南](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.6/apps/ds-group/deploy/README.md),先配置自己的 Matrix 身份域、公开地址和持久存储。npm CLI 不代替 Synapse 或反向代理。需要 `dscord server`、初始化及节点导入等命令时,在各终端保持同一个绝对数据路径:
36
36
 
37
37
  ```sh
38
38
  export DS_GROUP_DATA_DIR="$HOME/.local/share/dscord-server"
@@ -62,7 +62,7 @@ Core 提供的 Matrix 公开地址必须能从节点访问。一个连接页使
62
62
 
63
63
  ## 升级和已有 Bot
64
64
 
65
- 先备份服务器数据和各 DSH Bot home,再停止当前工作并升级到同一发行版本。已有 `ds-bot` 插件必须先停用,DSCord 才能在同一目录接管;同时启动两个内核会报冲突。接管会保存迁移快照,保留 Bot 与 Session 身份。[生命周期与迁移](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.3/apps/ds-group/deploy/kernel-runtime.md)
65
+ 先备份服务器数据和各 DSH Bot home,再停止当前工作并升级到同一发行版本。已有 `ds-bot` 插件必须先停用,DSCord 才能在同一目录接管;同时启动两个内核会报冲突。接管会保存迁移快照,保留 Bot 与 Session 身份。[生命周期与迁移](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.6/apps/ds-group/deploy/kernel-runtime.md)
66
66
 
67
67
  旧服务器 Bot 可在连接页绑定已有本机 Bot,保留 Matrix 身份;两套独立 Session 或记忆不会自动合并。已有原生绑定的 Bot 应从所属 DSH 连接,导出其配置不能在另一台机器创建身份副本。
68
68
 
@@ -100,11 +100,11 @@ dscord service down "$BOT_ACCOUNT"
100
100
  dscord dsh stop "$BOT_ACCOUNT"
101
101
  ```
102
102
 
103
- 每个 Bot 同时运行一个节点,每个独立节点使用自己的数据目录和桥接端口。Linux 常驻服务见 [systemd](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.3/apps/ds-group/deploy/systemd/README.md)。
103
+ 每个 Bot 同时运行一个节点,每个独立节点使用自己的数据目录和桥接端口。Linux 常驻服务见 [systemd](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.6/apps/ds-group/deploy/systemd/README.md)。
104
104
 
105
105
  ## 存储与组件
106
106
 
107
- 附件默认使用 Synapse 本地媒体目录,可选 S3-compatible 后端。[存储配置](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.3/apps/ds-group/deploy/storage/README.md)说明远端对象、缓存和备份关系;已有云盘扫描与历史迁移不是自动功能。
107
+ 附件默认使用 Synapse 本地媒体目录,可选 S3-compatible 后端。[存储配置](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.6/apps/ds-group/deploy/storage/README.md)说明远端对象、缓存和备份关系;已有云盘扫描与历史迁移不是自动功能。
108
108
 
109
109
  `dscord server` 运行 Core/PWA,`init` 和 `bootstrap` 初始化服务器,`admin` 管理导出与导入,`node` 运行 Matrix 适配器,`dsh` 管理独立宿主,`service` 管理本机进程,`path` 显示安装目录。
110
110
 
package/central/README.md CHANGED
@@ -39,7 +39,7 @@ npm run central
39
39
 
40
40
  ## 自定义头像
41
41
 
42
- 用户可修改自己和名下 Bot 的头像,跨服务器同步,Bot 离线时也可设置。默认复用中央数据卷;入口、配置、备份和接口见[自定义头像](avatars.md)。
42
+ 用户可修改自己和名下 Bot 的头像和名称,Bot 也能改自己的,跨服务器同步,Bot 离线时也可设置。默认复用中央数据卷;入口、配置、备份和接口见[自定义头像](avatars.md)。
43
43
 
44
44
  ## 账号格式
45
45
 
@@ -61,7 +61,7 @@ npm run central
61
61
  | `GET /.well-known/jwks.json` | 聊天服务器 | 验证身份令牌的公钥 |
62
62
  | `POST /api/register` | 人 | `{username, password, displayName?}` 注册并登录(`registration: open` 时) |
63
63
  | `POST /api/login` / `POST /api/logout` | 人 | 密码登录 / 退出当前会话 |
64
- | `GET /api/me` / `DELETE /api/me` | 人、Bot 节点 / 人 | 查看自己 / 注销(名下还有 Bot 时要先注销 Bot) |
64
+ | `GET /api/me` / `PATCH /api/me` / `DELETE /api/me` | 人、Bot 节点 / 人、Bot 节点 / 人 | 查看自己 / 改自己的显示名称 `{displayName}` / 注销(名下还有 Bot 时要先注销 Bot) |
65
65
  | `POST /api/identity-tokens` | 人、Bot 节点 | `{audience, allowNew?}` 为某台聊天服务器(HTTPS origin)签发 5 分钟有效的身份令牌;第一次去某台服务器时要带 `allowNew: true`,否则返回 `GRANT_REQUIRED` |
66
66
  | `GET /api/grants` / `DELETE /api/grants/:audience` | 人、Bot 节点 | 查看 / 撤销自己授权过的聊天服务器 |
67
67
  | `POST /api/connect-codes` | 人 | 生成 DSH 连接用的一次性代码(10 分钟有效)。聊天服务器网页把它和服务器地址一起编码成连接密钥 |
@@ -132,7 +132,7 @@ npm run central
132
132
 
133
133
  身份令牌是 Ed25519 签名的 JWT(`alg: EdDSA`),声明包括 `iss`、`sub`(账号编号)、`aud`(聊天服务器 origin)、`iat`、`exp`、`jti`、`username`、`gid`(全局身份 ID)、`kind`(`human` 或 `bot`)、`name`(显示名),Bot 另有 `owner`(所属人的全局身份 ID)。聊天服务器用 `central/tokens.mjs` 的 `verifyIdentityToken` 按中央公钥、`issuer` 和自己的 origin 校验。一张令牌只对写明的那台聊天服务器有效。
134
134
 
135
- 撤销授权后,中央不再为那台服务器签发令牌;聊天服务器的本地会话是短期的,续期时要重新拿令牌,因此撤销会在一个会话周期内生效。
135
+ 撤销授权后,中央不再为那台服务器签发新的身份令牌。已经换出的聊天会话由对应 Matrix/Synapse 管理其有效期与撤销;当前网页没有自动刷新聊天令牌的协议,不能把中央撤销授权视为已经结束所有聊天会话。浏览器记住设备的 30 天上限是本地保存期限,详见[登录与恢复](https://github.com/MixGeekers/dscord/blob/ds-group-v0.4.0-alpha.6/apps/ds-group/登录与恢复.md)。
136
136
 
137
137
  ## 聊天服务器怎样接入
138
138
 
@@ -1,10 +1,11 @@
1
- # 自定义头像
1
+ # 自定义头像与显示名称
2
2
 
3
- 用户可以修改自己的头像,以及自己名下 Bot 的头像。头像属于中央账号资料,使用稳定的中央账号 ID;Bot 不需要在线,修改头像不改变 Bot 的运行配置版本、不重启节点或 Session。
3
+ 用户可以修改自己的头像和显示名称,以及自己名下 Bot 的头像和名称;Bot 也能用前台工具 `update_profile` 改自己的名称和头像,和人一样。头像属于中央账号资料,使用稳定的中央账号 ID;Bot 不需要在线,修改头像不改变 Bot 的运行配置版本、不重启节点或 Session。
4
4
 
5
5
  ## 使用
6
6
 
7
- - 自己:左下角账号菜单 → 个人设置 → 账号。自己的成员资料卡也提供更换入口。
7
+ - 自己:左下角账号菜单 → 个人设置 → 账号,头像下方是“显示名称”。自己的成员资料卡也提供更换入口。
8
+ - Bot 自己:前台工具 `update_profile`(`name`、`avatar` 本地图片路径、`clear_avatar`),由连接插件用 Bot 自己的中央会话调用下面的 `/api/me` 接口,不经过所属人。
8
9
  - Bot:Bot 主页 → 设置,或“我的 Bot”展开对应 Bot 的设置。所属 Bot 的成员资料卡也能修改。
9
10
  - 选择 JPG、PNG 或静态 WebP,拖动、缩放并预览,点击“保存头像”。手机支持拖动和双指缩放,键盘可操作缩放及水平/垂直位置滑块。
10
11
  - “恢复默认”恢复名字首字占位。占位颜色由稳定身份决定。
@@ -39,7 +40,7 @@
39
40
 
40
41
  ## 同步与图片处理
41
42
 
42
- Core 每 5 秒按中央账号 ID 批量核对资料版本。读取到更新后保存到本地账号库,并使用该账号的服务凭据将图片同步至本服 Synapse,设置对应的 Matrix `avatar_url`。图片上传成功但资料写入失败时保留 Matrix 媒体回执,后续重试复用该回执。Core 重启后从持久记录继续同步。中央或 Matrix 暂不可用不阻断聊天 API;恢复后继续补齐。
43
+ Core 每 5 秒按中央账号 ID 批量核对资料版本。读取到更新后保存到本地账号库(显示名称写入账号的名字),并使用该账号的服务凭据设置本服 Matrix 的 `displayname`,把图片同步至本服 Synapse 并设置 `avatar_url`。改名和换头像共用同一个资料版本:改名时版本加一,头像不变。图片上传成功但资料写入失败时保留 Matrix 媒体回执,后续重试复用该回执。Core 重启后从持久记录继续同步。中央或 Matrix 暂不可用不阻断聊天 API;恢复后继续补齐。
43
44
 
44
45
  头像保存后,当前页面及同源标签页立即更新;其他设备和服务器通过版本轮询更新。无网络时不能保存,无法加载图片时显示占位。跨服务器同步是最终一致的,不要求所有服务器同时在线。
45
46
 
@@ -47,17 +48,18 @@ Core 每 5 秒按中央账号 ID 批量核对资料版本。读取到更新后
47
48
 
48
49
  服务端使用固定依赖 `sharp@0.35.5`,校验真实图片格式、静态帧、40MP 像素上限与裁剪边界,校正 EXIF 方向,重新编码 WebP 并去除原元数据。安装时保留 npm 的 optional dependencies,Linux amd64/arm64 使用对应平台的预编译 sharp/libvips 包;继续支持 `npm ci --ignore-scripts`。
49
50
 
50
- 头像图片是公开身份资源,通过无账号令牌的内容版本地址访问,可由浏览器缓存。编辑接口只接受本人或 Bot 所属人的中央会话,聊天服务器管理员身份不授予全局头像编辑权。
51
+ 头像图片是公开身份资源,通过无账号令牌的内容版本地址访问,可由浏览器缓存。编辑接口只接受账号本人(人或 Bot 自己的会话)或 Bot 所属人的中央会话,聊天服务器管理员身份不授予全局头像编辑权。
51
52
 
52
53
  ## 接口
53
54
 
54
55
  | 中央接口 | 作用 |
55
56
  | --- | --- |
57
+ | `PATCH /api/me` | 本人(人或 Bot)修改显示名称:`{displayName}`,最多 80 字,空字符串恢复为用户名;名字未变时不增加版本 |
56
58
  | `GET /api/me/avatar` | 读取本人头像、版本及上传上限 |
57
59
  | `PUT /api/me/avatar?version=N&left=X&top=Y&size=S` | 上传原图字节,按校正方向后的像素裁剪;裁剪参数可全部省略,使用居中裁剪 |
58
60
  | `DELETE /api/me/avatar?version=N` | 恢复默认 |
59
61
  | `GET/PUT/DELETE /api/bots/:id/avatar` | 所属人修改对应 Bot,参数同上 |
60
- | `GET /api/profiles?ids=...` | 读取至多 100 个已知中央账号 ID 或全局 ID 的公开头像版本 |
62
+ | `GET /api/profiles?ids=...` | 读取至多 100 个已知中央账号 ID 或全局 ID 的公开资料:显示名称、头像和资料版本 |
61
63
  | `GET /avatars/:assetId/:size.webp` | 读取标准化图片 |
62
64
 
63
65
  写入缺少版本返回 400,版本冲突返回 409;格式不支持返回 415,超限返回 413。客户端调用中央时使用既有 `clientOrigins` 跨域配置。
@@ -15,7 +15,7 @@ export function avatarOptions(input = {}) {
15
15
  return result;
16
16
  }
17
17
 
18
- /** Identity images only. Original uploads and processing directories are never published. */
18
+ /** Public profiles: the display name and identity image. Original uploads and processing directories are never published. */
19
19
  export class Avatars {
20
20
  constructor({ store, dataRoot, issuer, config }) {
21
21
  Object.assign(this, { store, issuer });
@@ -25,10 +25,13 @@ export class Avatars {
25
25
  account_id TEXT PRIMARY KEY REFERENCES accounts(id), version INTEGER NOT NULL,
26
26
  asset_id TEXT, updated_at INTEGER NOT NULL);`);
27
27
  store.profileOf = account => this.profile(account);
28
+ // A name change is a profile change too: the version moves on and the image stays.
29
+ store.profileChanged = account => this.store.db.prepare(`INSERT INTO account_profiles VALUES (?,1,NULL,?) ON CONFLICT(account_id)
30
+ DO UPDATE SET version=version+1,updated_at=excluded.updated_at`).run(account.id, this.store.now());
28
31
  }
29
32
  profile(account) {
30
33
  const row = this.store.db.prepare('SELECT * FROM account_profiles WHERE account_id=?').get(account.id);
31
- return { id: account.id, globalId: this.store.globalId(account.username), profileVersion: row?.version ?? 0,
34
+ return { id: account.id, globalId: this.store.globalId(account.username), displayName: account.display_name, profileVersion: row?.version ?? 0,
32
35
  avatar: row?.asset_id ? { id: row.asset_id, url: `${this.issuer}/avatars/${row.asset_id}/128.webp`,
33
36
  sizes: Object.fromEntries(this.options.sizes.map(size => [size, `${this.issuer}/avatars/${row.asset_id}/${size}.webp`])) } : null };
34
37
  }
@@ -128,9 +128,15 @@ export async function createCentral({ dataRoot = localPath('central'), config, n
128
128
  });
129
129
  router.post('/api/logout', ({ request, send }) => { store.closeSession(signedIn(request).session); send(200, { loggedOut: true }); });
130
130
  router.get('/api/me', ({ request, send }) => send(200, { account: store.view(signedIn(request).account) }));
131
+ // People and Bots edit their own profile the same way; a Bot's owner can also rename it from the Bot's settings.
132
+ router.on('PATCH', '/api/me', async ({ request, body, send }) => {
133
+ const { account } = signedIn(request);
134
+ const input = await body();
135
+ send(200, { account: store.view(input.displayName === undefined ? account : store.rename(account, input.displayName)) });
136
+ });
131
137
  router.delete('/api/me', ({ request, send }) => { store.closeAccount(person(request).account); send(200, { closed: true }); });
132
138
 
133
- // Avatars are public identity images; edits always require the person or the Bot's owner.
139
+ // Avatars are public identity images; edits require the account itself (a person or a Bot) or the Bot's owner.
134
140
  router.get('/api/profiles', ({ url, response, send }) => {
135
141
  response.setHeader('Access-Control-Allow-Origin', '*');
136
142
  const ids = (url.searchParams.get('ids') || '').split(',').filter(Boolean);
@@ -142,10 +148,7 @@ export async function createCentral({ dataRoot = localPath('central'), config, n
142
148
  });
143
149
  router.get(/^\/avatars\/([a-f0-9]{64})\/(\d+)\.webp$/, ({ response }, id, size) => avatars.serve(response, id, size));
144
150
  for (const route of ['/api/me/avatar', /^\/api\/bots\/([^/]+)\/avatar$/]) {
145
- const target = (request, id) => {
146
- const { account } = person(request);
147
- return id ? store.ownedBot(account, id) : account;
148
- };
151
+ const target = (request, id) => id ? store.ownedBot(person(request).account, id) : signedIn(request).account;
149
152
  router.get(route, ({ request, send }, id) => send(200, { profile: avatars.profile(target(request, id)), maxUploadBytes: avatars.options.maxUploadBytes }));
150
153
  router.on('PUT', route, async ({ request, url, send }, id) => {
151
154
  const account = target(request, id);
package/central/store.mjs CHANGED
@@ -181,11 +181,27 @@ export class CentralStore {
181
181
  /** The owner changes a Bot's name or setup; the device that runs it picks the change up the next time it reports. */
182
182
  updateBot(owner, bot, { displayName: name, ...input }, { serverOrigin } = {}) {
183
183
  const checked = this.checkedSettings(owner, input, serverOrigin);
184
- if (name !== undefined) this.db.prepare('UPDATE accounts SET display_name=? WHERE id=?').run(displayName(name, bot.username), bot.id);
184
+ if (name !== undefined) this.rename(bot, name);
185
185
  this.writeSettings(bot, { ...this.settingsOf(bot), ...checked });
186
186
  return this.account(bot.id);
187
187
  }
188
188
 
189
+ /**
190
+ * A new display name, set by the account itself (a person on the web, a Bot with its own tool) or by a Bot's
191
+ * owner. It is part of the public profile, so chat servers pick it up with the next profile version.
192
+ */
193
+ rename(account, name) {
194
+ const value = displayName(name, account.username);
195
+ if (value === this.account(account.id).display_name) return this.account(account.id);
196
+ this.db.exec('BEGIN IMMEDIATE');
197
+ try {
198
+ this.db.prepare('UPDATE accounts SET display_name=? WHERE id=?').run(value, account.id);
199
+ this.profileChanged?.(account);
200
+ this.db.exec('COMMIT');
201
+ } catch (error) { this.db.exec('ROLLBACK'); throw error; }
202
+ return this.account(account.id);
203
+ }
204
+
189
205
  /** A Bot as its owner manages it: the account, its setup, and what its device last reported about it. */
190
206
  managedBot(bot) {
191
207
  const settings = this.settingsOf(bot);
@@ -0,0 +1 @@
1
+ :root{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--font-family-ui:-apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;--font-family-code:ui-monospace, SFMono-Regular, Consolas, monospace;--font-size-xs:.6875rem;--font-size-sm:.75rem;--font-size-md:.875rem;--font-size-lg:1rem;--font-size-xl:1.25rem;--font-size-2xl:1.5rem;--font-weight-regular:400;--font-weight-medium:550;--font-weight-semibold:650;--font-weight-bold:700;--line-height-tight:1.25;--line-height-compact:1.4;--line-height-body:1.55;--text-display:var(--font-weight-bold) var(--font-size-2xl)/var(--line-height-tight) var(--font-family-ui);--text-title:var(--font-weight-semibold) var(--font-size-xl)/var(--line-height-tight) var(--font-family-ui);--text-heading:var(--font-weight-semibold) var(--font-size-lg)/var(--line-height-compact) var(--font-family-ui);--text-message:var(--font-weight-regular) var(--font-size-lg)/var(--line-height-body) var(--font-family-ui);--text-body-strong:var(--font-weight-medium) var(--font-size-md)/var(--line-height-compact) var(--font-family-ui);--text-body:var(--font-weight-regular) var(--font-size-md)/var(--line-height-body) var(--font-family-ui);--text-label:var(--font-weight-semibold) var(--font-size-sm)/var(--line-height-compact) var(--font-family-ui);--text-caption:var(--font-weight-regular) var(--font-size-sm)/var(--line-height-compact) var(--font-family-ui);--text-micro:var(--font-weight-bold) var(--font-size-xs)/var(--line-height-tight) var(--font-family-ui);--text-code:var(--font-weight-regular) var(--font-size-md)/var(--line-height-body) var(--font-family-code);--opacity-disabled:.45;--space-2xs:2px;--space-xs:4px;--space-sm:8px;--space-md:12px;--space-lg:16px;--space-xl:20px;--space-2xl:24px;--space-3xl:32px;--space-4xl:48px;--radius-sm:4px;--radius-md:8px;--radius-lg:12px;--radius-xl:16px;--radius-pill:999px;--radius-round:50%;--size-icon-xs:12px;--size-icon-sm:16px;--size-icon:20px;--size-icon-lg:24px;--size-control-sm:32px;--size-control:38px;--size-check:16px;--size-badge:16px;--size-loading-sm:28px;--size-loading:72px;--size-loading-lg:112px;--height-header:49px;--size-avatar:32px;--size-avatar-message:40px;--width-server-rail:72px;--width-channel-sidebar:240px;--width-member-sidebar:240px;--focus-width:2px;--focus-offset:2px;--focus-ring:calc(var(--focus-width) + var(--focus-offset));--motion-fast:.15s;--motion-panel:.18s;--motion-highlight:3s;--z-message-actions:1;--z-header:2;--z-floating:3;--z-backdrop:5;--z-panel:6;--z-navigation:7;--z-suggestions:12;--z-drop-target:20;--z-toast:50;--z-ui-dialog-backdrop:80;--z-ui-dialog:81;--z-ui-popover:90;--color-bg-canvas:#1e1f22;--color-bg-primary:#313338;--color-bg-secondary:#2b2d31;--color-bg-tertiary:#232428;--color-bg-input:#383a40;--color-bg-hover:#35373c;--color-bg-selected:#404249;--color-bg-message-hover:#2e3035;--color-bg-neutral:#4e5058;--color-bg-neutral-hover:#6d6f78;--color-bg-toast:#111214;--color-text-primary:#f2f3f5;--color-text-normal:#dbdee1;--color-text-secondary:#b5bac1;--color-text-muted:#949ba4;--color-text-on-accent:#fff;--color-document-paper:#fff;--color-icon-muted:#80848e;--color-link:#00a8fc;--color-focus:#00a8fc;--color-border-subtle:#3f4147;--color-border-normal:#45474e;--color-border-strong:#53565f;--color-border-header:#26272c;--color-border-divider:#42444a;--color-border-inset:#25262b;--palette-indigo:#5865f2;--palette-blue:#2563eb;--palette-teal:#0f766e;--palette-rose:#be185d;--palette-amber:#b45309;--color-accent:var(--palette-indigo);--color-accent-hover:#4752c4;--color-status-success:#23a559;--color-status-warning:#f0b232;--color-status-danger:#f23f43;--color-danger-action:#da373c;--color-text-danger:#fa777c;--color-text-danger-soft:#f2a9ab;--color-bg-danger:#5b3335;--color-border-danger:#b74348;--color-text-bot:#82c7b7;--color-code-keyword:#ff7b72;--color-code-string:#a5d6ff;--color-code-comment:#8b949e;--color-code-number:#79c0ff;--color-code-title:#d2a8ff;--color-code-attribute:#ffa657;--color-code-added:#7ee787;--color-code-deleted:#ffa198;--color-quote-border:var(--color-bg-neutral);--color-mention-bg:#5865f24d;--color-mention-text:#c9cdfb;--color-mention-message-bg:#f0b2320c;--color-mention-message-hover:#f0b23215;--color-message-highlight:#5865f259;--color-drop-background:#5865f2ed;--color-drop-border:#b8bfff;--color-file-pdf-border:#da686e;--color-file-image:#95baff;--color-file-text:#8ed8b3;--color-file-text-border:#438b68;--color-avatar-human:#9255a5;--color-avatar-bot:#3b8d7b;--color-avatar-alternate:#5865f2;--color-role-default:#99aab5;--color-scrollbar-thumb:#1a1b1e;--color-overlay:#0007;--color-dialog-overlay:#000a;--shadow-header:0 1px 3px #0002;--shadow-actions:0 2px 5px #0004;--shadow-float:0 3px 10px #0003;--shadow-suggestions:0 6px 24px #0006;--shadow-panel:-5px 0 20px #0004;--shadow-navigation:4px 0 20px #0004;--shadow-dialog:0 12px 48px #0007;--shadow-toast:0 3px 20px #0006;--app-top:0px;--app-bottom:0px;--safe-top:env(safe-area-inset-top,0px);--safe-bottom:env(safe-area-inset-bottom,0px);--app-safe-height:calc(100dvh - var(--app-top) - var(--app-bottom) - var(--safe-top) - var(--safe-bottom));font-family:var(--font-family-ui);color:var(--color-text-normal);background:var(--color-bg-canvas);font-synthesis:none}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--color-bg-canvas:#e3e5e8;--color-bg-primary:#fff;--color-bg-secondary:#f2f3f5;--color-bg-tertiary:#e7e9ec;--color-bg-input:#ebedef;--color-bg-hover:#e7e9ec;--color-bg-selected:#dce0e5;--color-bg-message-hover:#f2f3f5;--color-bg-neutral:#d4d7dc;--color-bg-neutral-hover:#c7ccd4;--color-bg-toast:#313338;--color-text-primary:#060607;--color-text-normal:#313338;--color-text-secondary:#4e5058;--color-text-muted:#5c6069;--color-icon-muted:#6d6f78;--color-link:#0068a8;--color-focus:#0068a8;--color-border-subtle:#e0e2e6;--color-border-normal:#cdd1d7;--color-border-strong:#a7adb8;--color-border-header:#d7dbe0;--color-border-divider:#dce0e5;--color-border-inset:#d7dbe0;--color-status-success:#008642;--color-status-warning:#997000;--color-status-danger:#d72f36;--color-text-danger:#c92d35;--color-text-danger-soft:#9c2026;--color-bg-danger:#fde7e8;--color-border-danger:#c7474e;--color-text-bot:#1d7562;--color-code-keyword:#cf222e;--color-code-string:#0a3069;--color-code-comment:#6e7781;--color-code-number:#0550ae;--color-code-title:#8250df;--color-code-attribute:#953800;--color-code-added:#116329;--color-code-deleted:#82071e;--color-mention-bg:#5865f224;--color-mention-text:#3b48b4;--color-mention-message-bg:#f0b2321a;--color-mention-message-hover:#f0b2322b;--color-message-highlight:#5865f233;--color-file-pdf-border:#b13036;--color-file-image:#3457ab;--color-file-text:#24754d;--color-file-text-border:#438b68;--color-role-default:#6d6f78;--color-scrollbar-thumb:#c4c9d1;--color-overlay:#0003;--color-dialog-overlay:#0006;--shadow-header:0 1px 3px #00000015;--shadow-actions:0 2px 5px #0002;--shadow-float:0 3px 10px #0002;--shadow-suggestions:0 6px 24px #00000026;--shadow-panel:-5px 0 20px #0002;--shadow-navigation:4px 0 20px #0002;--shadow-dialog:0 12px 48px #0003;--shadow-toast:0 3px 20px #0003}:root[data-accent=blue]{--color-accent:var(--palette-blue)}:root[data-accent=teal]{--color-accent:var(--palette-teal)}:root[data-accent=rose]{--color-accent:var(--palette-rose)}:root[data-accent=amber]{--color-accent:var(--palette-amber)}:root[data-accent]:not([data-accent=indigo]){--color-accent-hover:color-mix(in srgb, var(--color-accent) 80%, black);--color-mention-bg:color-mix(in srgb, var(--color-accent) 30%, transparent);--color-mention-text:color-mix(in srgb, var(--color-accent) 30%, white);--color-message-highlight:color-mix(in srgb, var(--color-accent) 35%, transparent);--color-drop-background:color-mix(in srgb, var(--color-accent) 93%, transparent);--color-drop-border:color-mix(in srgb, var(--color-accent) 35%, white);--color-avatar-alternate:var(--color-accent)}:root[data-theme=light][data-accent]:not([data-accent=indigo]){--color-mention-bg:color-mix(in srgb, var(--color-accent) 14%, transparent);--color-mention-text:color-mix(in srgb, var(--color-accent) 80%, black);--color-message-highlight:color-mix(in srgb, var(--color-accent) 20%, transparent)}@media (prefers-reduced-motion:reduce){:root{--motion-fast:0s;--motion-panel:0s;--motion-highlight:0s}}:root[data-keyboard]{--safe-bottom:0px}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:block))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--spacing:.25rem;--font-weight-semibold:600;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer utilities{.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.relative{position:relative}.static{position:static}.sticky{position:sticky}.\!container{width:100%!important}@media (min-width:40rem){.\!container{max-width:40rem!important}}@media (min-width:48rem){.\!container{max-width:48rem!important}}@media (min-width:64rem){.\!container{max-width:64rem!important}}@media (min-width:80rem){.\!container{max-width:80rem!important}}@media (min-width:96rem){.\!container{max-width:96rem!important}}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.block{display:block}.contents{display:contents}.hidden{display:none}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.w-full{width:100%}.min-w-0{min-width:0}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x, ) var(--tw-rotate-y, ) var(--tw-rotate-z, ) var(--tw-skew-x, ) var(--tw-skew-y, )}.resize{resize:both}.items-center{align-items:center}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing) * 2)}.border{border-style:var(--tw-border-style);border-width:1px}.whitespace-nowrap{white-space:nowrap}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.filter{filter:var(--tw-blur, ) var(--tw-brightness, ) var(--tw-contrast, ) var(--tw-grayscale, ) var(--tw-hue-rotate, ) var(--tw-invert, ) var(--tw-saturate, ) var(--tw-sepia, ) var(--tw-drop-shadow, )}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.select-none{-webkit-user-select:none;user-select:none}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.\[\&_svg\]\:shrink-0 svg{flex-shrink:0}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}*{box-sizing:border-box}body{min-width:320px;font:var(--text-body);color:var(--color-text-primary);background:var(--color-bg-primary);margin:0}button,input,textarea{font:inherit}code,pre{font-family:var(--font-family-code);overflow-wrap:anywhere}h1,h2,h3,p,dl{margin:0}:focus-visible{outline:var(--focus-width) solid var(--color-focus);outline-offset:var(--focus-offset)}.admin-layout{background:var(--color-bg-primary);grid-template-columns:248px minmax(0,1fr);min-height:100dvh;display:grid}.admin-sidebar{min-width:0;height:100dvh;padding:var(--space-xl) var(--space-lg) var(--space-lg);border-right:1px solid var(--color-border-subtle);background:var(--color-bg-secondary);flex-direction:column;display:flex;position:sticky;top:0}.admin-brand{align-items:center;gap:var(--space-md);min-width:0;display:flex}.admin-brand-icon,.admin-login-mark{background:var(--color-text-secondary);display:block;-webkit-mask:url(/assets/mark-128-C4ed4p0U.png) 50%/contain no-repeat;mask:url(/assets/mark-128-C4ed4p0U.png) 50%/contain no-repeat}.admin-brand-icon{flex:0 0 36px;width:36px;height:36px}.admin-brand strong,.admin-brand small{line-height:var(--line-height-compact);display:block}.admin-brand strong{font:var(--text-body-strong)}.admin-brand small{font:var(--text-caption);color:var(--color-text-secondary)}.admin-server-name{font:var(--text-caption);min-height:20px;margin:var(--space-lg) var(--space-md) var(--space-xl);color:var(--color-text-muted);overflow-wrap:anywhere;display:block}.admin-sidebar nav{align-content:start;gap:var(--space-xs);min-height:0;display:grid;overflow-y:auto}.admin-nav-button{font:var(--text-body-strong);align-items:center;gap:var(--space-md);width:100%;min-height:38px;padding:0 var(--space-md);border-radius:var(--radius-md);color:var(--color-text-secondary);cursor:pointer;text-align:left;white-space:nowrap;background:0 0;border:0;display:flex}.admin-nav-button svg{flex:none}.admin-nav-button:hover{background:var(--color-bg-hover);color:var(--color-text-primary)}.admin-nav-button[aria-current=page]{background:var(--color-bg-selected);color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.admin-nav-button[aria-current=page] svg{color:var(--color-accent)}.admin-account{font:var(--text-body);gap:var(--space-xs);min-width:0;padding:var(--space-lg) var(--space-sm) 0;border-top:1px solid var(--color-border-subtle);margin-top:auto;display:grid}.admin-account span{overflow-wrap:anywhere;min-width:0;color:var(--color-text-secondary)}.admin-account .ds-button{justify-self:start}.admin-main{width:min(100%,1440px);min-width:0;padding:var(--space-2xl) var(--space-3xl) var(--space-3xl);background:var(--color-bg-primary);margin:0 auto}.admin-header{justify-content:space-between;align-items:flex-start;gap:var(--space-xl);margin-bottom:var(--space-xl);display:flex}.admin-page-title{min-width:0}.admin-page-title h1{font:var(--text-display);margin:0;line-height:1.3}.admin-preferences{align-items:flex-end;gap:var(--space-sm);flex-wrap:wrap;display:flex}.admin-preferences .admin-field{width:136px}.admin-field{gap:var(--space-sm);min-width:0;display:grid}.admin-field>label{font:var(--text-body-strong);color:var(--color-text-secondary)}.admin-field .ds-input{width:100%}.admin-actions{align-items:center;gap:var(--space-sm);flex-wrap:wrap;display:flex}.admin-policy-bar,.admin-pending-bar{justify-content:space-between;align-items:center;gap:var(--space-lg);min-width:0;margin-bottom:var(--space-xl);padding:var(--space-md) var(--space-lg);border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);background:var(--color-bg-secondary);display:flex}.admin-policy-summary{align-items:center;gap:var(--space-sm) var(--space-lg);flex-wrap:wrap;min-width:0;display:flex}.admin-state{font:var(--text-body-strong);align-items:center;gap:var(--space-sm);color:var(--color-text-secondary);font-weight:var(--font-weight-semibold);white-space:nowrap;display:inline-flex}.admin-state:before,.admin-status-note:before{content:"";border-radius:var(--radius-round);background:currentColor;flex:0 0 6px;width:6px;height:6px;display:inline-block}.admin-state.is-good,.admin-status-note.is-good{color:var(--color-status-success)}.admin-state.is-warning,.admin-status-note.is-warning{color:var(--color-status-warning)}.admin-state.is-muted{color:var(--color-text-muted)}.admin-status-note{font:var(--text-body);align-items:center;gap:var(--space-sm);color:var(--color-text-secondary);white-space:nowrap;display:inline-flex}.admin-policy-bar .admin-actions{justify-content:flex-end}.admin-pending-bar{border-color:var(--color-status-warning)}.admin-pending-bar>span{align-items:center;gap:var(--space-sm);color:var(--color-status-warning);font-weight:var(--font-weight-semibold);display:flex}.admin-readonly-note{font:var(--text-body);margin:calc(-1 * var(--space-md)) 0 var(--space-lg);color:var(--color-text-secondary)}.admin-error,.admin-notice{font:var(--text-body);align-items:flex-start;gap:var(--space-md);margin-bottom:var(--space-lg);padding:var(--space-md) var(--space-lg);border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);background:var(--color-bg-secondary);overflow-wrap:anywhere;display:flex}.admin-error{border-color:var(--color-border-danger);color:var(--color-status-danger)}.admin-error strong{margin-bottom:var(--space-2xs);display:block}.admin-notice{color:var(--color-status-success)}.admin-error svg,.admin-notice svg{flex:none;margin-top:1px}.admin-panel{border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);background:0 0;min-width:0;overflow:hidden}.admin-panel-heading{justify-content:space-between;align-items:flex-start;gap:var(--space-lg);min-width:0;padding:var(--space-lg) var(--space-xl);border-bottom:1px solid var(--color-border-subtle);display:flex}.admin-panel-heading>div{min-width:0}.admin-panel-heading h2{font:var(--text-heading);margin:0;line-height:1.35}.admin-panel-heading p{font:var(--text-body);max-width:70ch;margin-top:var(--space-sm);color:var(--color-text-secondary);line-height:var(--line-height-compact)}.admin-panel-heading>svg{color:var(--color-text-muted);flex:none}.admin-count{font:var(--text-body-strong);min-width:28px;height:28px;padding-inline:var(--space-sm);border-radius:var(--radius-md);background:var(--color-bg-tertiary);color:var(--color-text-secondary);font-weight:var(--font-weight-semibold);flex:none;place-items:center;display:grid}.admin-empty{font:var(--text-body);padding:var(--space-2xl) var(--space-xl);color:var(--color-text-muted)}.admin-section-layout{align-items:start;gap:var(--space-lg);grid-template-columns:minmax(0,1fr) minmax(340px,390px);display:grid}.admin-section-layout.is-single{grid-template-columns:minmax(0,1fr)}.admin-list{min-width:0}.admin-list-row{min-width:0;padding:var(--space-lg) var(--space-xl);border-bottom:1px solid var(--color-border-subtle)}.admin-list-row:last-child{border-bottom:0}.admin-row-top{justify-content:space-between;align-items:flex-start;gap:var(--space-lg);min-width:0;display:flex}.admin-row-identity{gap:var(--space-xs);min-width:0;display:grid}.admin-row-identity strong{font:var(--text-body-strong);font-weight:var(--font-weight-semibold);overflow-wrap:anywhere}.admin-row-identity code,.admin-row-identity span{font:var(--text-code);color:var(--color-text-secondary);overflow-wrap:anywhere;word-break:break-word}.admin-row-top>.admin-state{margin-top:var(--space-2xs);flex:none}.admin-detail-list{gap:var(--space-sm);margin-top:var(--space-md);display:grid}.admin-detail-list>div{font:var(--text-body);gap:var(--space-md);grid-template-columns:minmax(90px,32%) minmax(0,1fr);min-width:0;display:grid}.admin-detail-list dt{color:var(--color-text-muted)}.admin-detail-list dd{overflow-wrap:anywhere;white-space:pre-wrap;min-width:0;margin:0}.admin-row-actions{align-items:center;gap:var(--space-sm);margin-top:var(--space-md);flex-wrap:wrap;display:flex}.admin-remove-trigger{color:var(--color-text-danger)}.admin-remove-trigger:hover:not(:disabled){color:var(--color-text-danger);background:var(--color-bg-danger)}.admin-confirm{font:var(--text-body);gap:var(--space-md);min-width:0;margin-top:var(--space-md);padding:var(--space-md);border:1px solid var(--color-border-danger);border-radius:var(--radius-md);background:var(--color-bg-danger);color:var(--color-text-danger);display:grid}.admin-confirm p{overflow-wrap:anywhere}.admin-confirm code{margin-top:var(--space-xs);color:var(--color-text-primary);display:block}.admin-invite{align-items:end;gap:var(--space-md);margin-top:var(--space-lg);padding-top:var(--space-lg);border-top:1px solid var(--color-border-subtle);grid-template-columns:minmax(0,1fr) auto;display:grid}.admin-invite .admin-field{min-width:0}.admin-editor{flex-direction:column;display:flex}.admin-form-fields{gap:var(--space-lg);padding:var(--space-xl);display:grid}.admin-form-actions{align-items:center;gap:var(--space-sm);padding:var(--space-lg) var(--space-xl);border-top:1px solid var(--color-border-subtle);flex-wrap:wrap;display:flex}.admin-checkbox{font:var(--text-body);align-items:center;gap:var(--space-sm);width:-moz-fit-content;width:fit-content;color:var(--color-text-primary);cursor:pointer;display:flex}.admin-tools{gap:var(--space-md);min-width:0;padding:var(--space-lg) 0 0;border:0;border-top:1px solid var(--color-border-subtle);grid-template-columns:repeat(2,minmax(0,1fr));margin:0;display:grid}.admin-tools legend{font:var(--text-body-strong);color:var(--color-text-secondary);padding:0}.admin-tools label{font:var(--text-body);align-items:center;gap:var(--space-sm);cursor:pointer;min-width:0;display:flex}.admin-hint{font:var(--text-body);color:var(--color-text-secondary)}.admin-metrics{margin-bottom:var(--space-lg);grid-template-columns:repeat(4,minmax(0,1fr));display:grid}.admin-metric{gap:var(--space-sm);min-width:0;padding:var(--space-xl);border-right:1px solid var(--color-border-subtle);display:grid}.admin-metric:last-child{border-right:0}.admin-metric span{font:var(--text-body);color:var(--color-text-secondary)}.admin-metric strong{font:var(--text-display);line-height:1.2}.admin-overview-grid{align-items:start;gap:var(--space-lg);grid-template-columns:minmax(0,1fr) minmax(0,2fr);display:grid}.admin-primary-value{font:var(--text-body-strong);padding:var(--space-xl);font-weight:var(--font-weight-semibold);overflow-wrap:anywhere;display:block}.admin-application-summary{font:var(--text-body);justify-content:space-between;align-items:center;gap:var(--space-md);padding:var(--space-lg) var(--space-xl);flex-wrap:wrap;display:flex}.admin-inline-error{font:var(--text-body);align-items:flex-start;gap:var(--space-sm);margin:0 var(--space-xl) var(--space-lg);color:var(--color-status-danger);overflow-wrap:anywhere;display:flex}.admin-inline-error svg{flex:none}.admin-application-row{gap:var(--space-sm);padding:var(--space-lg) var(--space-xl);border-top:1px solid var(--color-border-subtle);display:grid}.admin-application-row>.admin-inline-error{margin:0}.admin-storage-grid{align-items:start;gap:var(--space-lg);grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.admin-storage-grid .admin-primary-value{padding-bottom:0}.admin-detail-list-wide{padding:var(--space-lg) var(--space-xl)}.admin-supporting-text{font:var(--text-body);padding:var(--space-lg) var(--space-xl);color:var(--color-text-secondary)}.admin-record-detail{font:var(--text-body);justify-content:space-between;gap:var(--space-md);min-width:0;padding:var(--space-md) var(--space-xl);border-bottom:1px solid var(--color-border-subtle);flex-wrap:wrap;display:flex}.admin-record-detail:last-child{border-bottom:0}.admin-record-detail code{min-width:0}.admin-backup-panel{grid-column:1/-1}.admin-backup-panel .ds-button{margin:0 var(--space-xl) var(--space-xl)}.admin-deployment-panel .admin-panel-heading p{max-width:90ch}.admin-settings-grid{grid-template-columns:repeat(2,minmax(0,1fr));display:grid}.admin-settings-group{align-content:start;gap:var(--space-lg);min-width:0;padding:var(--space-xl);display:grid}.admin-settings-group+.admin-settings-group{border-left:1px solid var(--color-border-subtle)}.admin-settings-group h3{font:var(--text-body-strong);color:var(--color-text-primary);font-weight:var(--font-weight-semibold)}.admin-settings-group .ds-input[readonly]{color:var(--color-text-secondary);background:var(--color-bg-tertiary)}.admin-settings-group form{gap:var(--space-lg);display:grid}.admin-muted{color:var(--color-text-secondary);overflow-wrap:anywhere}.admin-listing-status{white-space:normal;align-items:baseline}.admin-server-number{gap:var(--space-xs);color:var(--color-text-secondary);display:grid}.admin-server-number strong,.admin-invite-code{font:var(--text-display);color:var(--color-text-primary);font-family:var(--font-family-code);letter-spacing:.08em}.admin-deployment-panel+.admin-list-panel{margin-top:var(--space-xl)}.admin-audit .admin-list-row{gap:var(--space-sm);display:grid}.admin-audit time,.admin-audit-meta{font:var(--text-body);color:var(--color-text-secondary)}.admin-audit pre{font:var(--text-code);color:var(--color-text-secondary);white-space:pre-wrap;margin:0}.admin-audit>.ds-button{margin:var(--space-lg) var(--space-xl)}.admin-login{min-height:100dvh;padding:var(--space-2xl);place-items:center;display:grid}.admin-login-panel{width:min(100%,440px);padding:var(--space-2xl);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg);background:var(--color-bg-primary)}.admin-login-mark{width:40px;height:40px}.admin-login-panel h1{font:var(--text-title);margin:var(--space-md) 0 var(--space-xs)}.admin-login-panel>p{font:var(--text-body);margin-bottom:var(--space-xl);color:var(--color-text-secondary)}.admin-login-panel>.admin-error{margin-top:var(--space-lg);color:var(--color-status-danger)}.admin-login-panel form{gap:var(--space-lg);display:grid}.admin-login-panel form .ds-button{justify-self:start}.admin-login-panel .admin-preferences{margin-top:var(--space-2xl);padding-top:var(--space-lg);border-top:1px solid var(--color-border-subtle)}@media (max-width:1200px){.admin-section-layout{grid-template-columns:minmax(0,1fr)}.admin-overview-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (max-width:920px){.admin-layout{grid-template-columns:minmax(0,1fr)}.admin-sidebar{gap:var(--space-sm);height:auto;padding:var(--space-md) var(--space-lg) 0;border-right:0;border-bottom:1px solid var(--color-border-subtle);grid-template-columns:minmax(0,1fr) auto;display:grid;position:static}.admin-brand{grid-area:1/1}.admin-account{border:0;grid-area:1/2;align-items:center;margin:0;padding:0;display:flex}.admin-account span{display:none}.admin-server-name{min-height:0;margin:0 var(--space-sm);grid-area:2/1/auto/-1}.admin-sidebar nav{gap:var(--space-xs);scrollbar-width:thin;grid-area:3/1/auto/-1;max-width:100%;display:flex;overflow:auto hidden}.admin-nav-button{flex:none;width:auto}.admin-main{padding:var(--space-xl) var(--space-lg) var(--space-3xl)}.admin-policy-bar{flex-direction:column;align-items:flex-start}.admin-policy-bar .admin-actions{justify-content:flex-start}}@media (max-width:640px){.admin-header{gap:var(--space-lg);flex-direction:column}.admin-preferences{width:100%}.admin-preferences .admin-field{width:min(42vw,160px)}.admin-overview-grid,.admin-storage-grid,.admin-settings-grid{grid-template-columns:minmax(0,1fr)}.admin-settings-group+.admin-settings-group{border-left:0;border-top:1px solid var(--color-border-subtle)}.admin-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.admin-metric:nth-child(2){border-right:0}.admin-metric:nth-child(-n+2){border-bottom:1px solid var(--color-border-subtle)}.admin-pending-bar{flex-direction:column;align-items:flex-start}}@media (max-width:480px){.admin-main{padding:var(--space-lg) var(--space-md) var(--space-2xl)}.admin-policy-bar,.admin-pending-bar{padding:var(--space-md)}.admin-panel-heading,.admin-list-row,.admin-form-fields,.admin-form-actions,.admin-settings-group{padding:var(--space-lg)}.admin-invite{grid-template-columns:minmax(0,1fr)}.admin-invite .ds-button{justify-self:start}.admin-row-top{gap:var(--space-sm)}.admin-detail-list>div{gap:var(--space-sm);grid-template-columns:minmax(72px,30%) minmax(0,1fr)}}.admin-update-panels{gap:var(--space-lg);min-width:0;display:grid}.admin-update-panels .admin-panel{overflow-wrap:anywhere;min-width:0;padding:var(--space-xl)}.admin-update-panels form{gap:var(--space-lg);display:grid}.admin-update-panels .admin-settings-grid{gap:var(--space-lg)}.admin-update-panels form>button{justify-self:start}.admin-update-release,.admin-update-progress{margin-block:var(--space-lg);padding:var(--space-lg);border:1px solid var(--color-border-subtle);border-radius:var(--radius-lg)}.admin-update-progress{gap:var(--space-sm);display:grid}.admin-update-history{gap:var(--space-lg);padding-inline-start:var(--space-2xl);display:grid}.ds-text-display{font:var(--text-display);color:var(--color-text-primary);margin:0}.ds-text-title{font:var(--text-title);color:var(--color-text-primary);margin:0}.ds-text-heading{font:var(--text-heading);color:var(--color-text-primary);margin:0}.ds-text-body-strong{font:var(--text-body-strong);color:var(--color-text-primary);margin:0}.ds-text-body{font:var(--text-body);color:var(--color-text-normal);margin:0}.ds-text-label{font:var(--text-label);color:var(--color-text-secondary);margin:0}.ds-text-caption{font:var(--text-caption);color:var(--color-text-muted);margin:0}.ds-text-code{font:var(--text-code);color:var(--color-text-normal)}.ds-button{min-height:var(--size-control);padding:0 var(--space-lg);border-radius:var(--radius-md);font:var(--text-body-strong);cursor:pointer;border:1px solid #0000}.ds-button-size-sm{min-height:var(--size-control-sm);padding-inline:var(--space-md)}.ds-button-size-icon{width:var(--size-control);min-height:var(--size-control);padding:0}.ds-button svg{width:var(--size-icon-sm);height:var(--size-icon-sm);stroke-width:2px}.ds-button-size-icon svg{width:var(--size-icon);height:var(--size-icon)}.ds-button-default{background:var(--color-accent);color:var(--color-text-on-accent)}.ds-button-default:hover:not(:disabled){background:var(--color-accent-hover)}.ds-button-secondary{background:var(--color-bg-neutral);color:var(--color-text-primary)}.ds-button-secondary:hover:not(:disabled){background:var(--color-bg-neutral-hover)}.ds-button-ghost{color:var(--color-text-secondary);background:0 0}.ds-button-ghost:hover:not(:disabled){background:var(--color-bg-hover);color:var(--color-text-primary)}.ds-button-destructive{background:var(--color-bg-danger);color:var(--color-text-danger)}.ds-button-destructive:hover:not(:disabled){background:var(--color-border-danger);color:var(--color-text-on-accent)}.ds-button:focus-visible,.ds-input:focus-visible,.ds-select-trigger:focus-visible,.ds-chip:focus-visible,.ds-popover-content:focus-visible,.ds-dialog-content:focus-visible,.ds-checkbox:focus-visible,.ds-radio:focus-visible,.ds-color-input:focus-visible{outline:var(--focus-width) solid var(--color-focus);outline-offset:var(--focus-offset)}.ds-button:disabled,.ds-input:disabled{cursor:default;opacity:var(--opacity-disabled)}.ds-input{min-height:var(--size-control);padding:var(--space-sm) var(--space-md);border:1px solid var(--color-border-normal);border-radius:var(--radius-md);background:var(--color-bg-input);color:var(--color-text-primary);font:var(--text-body)}.ds-input::placeholder{color:var(--color-text-muted)}:is(.ds-input[aria-invalid=true],.ds-input:user-invalid){border-color:var(--color-border-danger)}.ds-textarea{min-height:calc(var(--size-control) * 2.5);resize:vertical}.ds-select-trigger{justify-content:space-between;align-items:center;gap:var(--space-sm);width:100%;min-width:0;min-height:var(--size-control);padding:0 var(--space-sm) 0 var(--space-md);border:1px solid var(--color-border-normal);border-radius:var(--radius-md);background:var(--color-bg-input);color:var(--color-text-primary);font:var(--text-body);text-align:left;cursor:pointer;-webkit-user-select:none;user-select:none;margin:0;display:flex}.ds-select-trigger:hover:not([data-disabled]){border-color:var(--color-border-strong)}.ds-select-trigger[data-popup-open]{border-color:var(--color-accent)}.ds-select-trigger[data-disabled]{cursor:default;opacity:var(--opacity-disabled)}.ds-select-trigger[aria-invalid=true]{border-color:var(--color-border-danger)}.ds-select-value{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.ds-select-value[data-placeholder]{color:var(--color-text-muted)}.ds-select-icon{color:var(--color-text-secondary);transition:transform var(--motion-fast);flex:none;place-items:center;display:grid}.ds-select-icon svg{width:var(--size-icon-sm);height:var(--size-icon-sm)}.ds-select-trigger[data-popup-open] .ds-select-icon{transform:rotate(180deg)}.ds-select-positioner{z-index:var(--z-ui-popover);outline:none}.ds-select-popup{min-width:var(--anchor-width);max-width:calc(100vw - var(--space-lg));max-height:min(var(--available-height), calc(var(--size-control) * 8));overscroll-behavior:contain;padding:var(--space-xs);border:1px solid var(--color-border-normal);border-radius:var(--radius-md);background:var(--color-bg-primary);color:var(--color-text-normal);box-shadow:var(--shadow-suggestions);outline:none;overflow-y:auto}.ds-select-item{grid-template-columns:minmax(0, 1fr) var(--size-icon-sm);align-items:center;gap:var(--space-sm);min-height:var(--size-control-sm);padding:var(--space-xs) var(--space-sm);border-radius:var(--radius-sm);font:var(--text-body);cursor:pointer;-webkit-user-select:none;user-select:none;outline:none;display:grid}.ds-select-item[data-highlighted]{background:var(--color-bg-hover);color:var(--color-text-primary)}.ds-select-item[data-selected]{color:var(--color-text-primary)}.ds-select-item[data-disabled]{cursor:default;opacity:var(--opacity-disabled)}.ds-select-item-text{min-width:0;display:grid}.ds-select-group+.ds-select-group,.ds-select-item+.ds-select-group{margin-top:var(--space-xs);padding-top:var(--space-xs);border-top:1px solid var(--color-border-subtle)}.ds-select-group-label{padding:var(--space-xs) var(--space-sm);font:var(--text-label);color:var(--color-text-muted)}.ds-select-item-description{font:var(--text-caption);color:var(--color-text-muted)}.ds-select-item-indicator{color:var(--color-accent);place-items:center;display:grid}.ds-select-item-indicator svg{width:var(--size-icon-sm);height:var(--size-icon-sm)}.ds-checkbox,.ds-radio{flex:0 0 var(--size-check);width:var(--size-check);height:var(--size-check);border:1px solid var(--color-border-strong);background:var(--color-bg-input);cursor:pointer;vertical-align:middle;place-items:center;margin:0;padding:0;display:inline-grid}.ds-checkbox{border-radius:var(--radius-sm)}.ds-radio{border-radius:var(--radius-round)}.ds-checkbox[data-checked]{border-color:var(--color-accent);background:var(--color-accent)}.ds-checkbox-indicator{color:var(--color-text-on-accent);place-items:center;display:grid}.ds-checkbox-indicator svg{width:var(--size-icon-xs);height:var(--size-icon-xs)}.ds-radio[data-checked]{border-color:var(--color-accent)}.ds-radio-indicator{width:var(--space-sm);height:var(--space-sm);border-radius:var(--radius-round);background:var(--color-accent)}.ds-checkbox[data-disabled],.ds-radio[data-disabled]{opacity:var(--opacity-disabled);cursor:default}.ds-color-input{width:calc(var(--size-control) * 1.4);min-height:var(--size-control);padding:var(--space-xs);border:1px solid var(--color-border-normal);border-radius:var(--radius-md);background:var(--color-bg-input);cursor:pointer}.ds-color-input:disabled{opacity:var(--opacity-disabled);cursor:default}.ds-field{gap:var(--space-sm);border:0;min-width:0;margin:0;padding:0;display:grid}.ds-field>legend{margin-bottom:var(--space-sm);padding:0}.ds-field-label{align-items:baseline;gap:var(--space-xs);font:var(--text-label);color:var(--color-text-secondary);margin:0;display:flex}.ds-field-optional{font:var(--text-caption);color:var(--color-text-muted)}.ds-field-description{font:var(--text-caption);color:var(--color-text-muted);margin:0}.ds-field-error{font:var(--text-caption);color:var(--color-text-danger);margin:0}.ds-field-group{gap:var(--space-xl);min-width:0;display:grid}.ds-field-row{grid-template-columns:repeat(auto-fit, minmax(calc(var(--size-control) * 5), 1fr));gap:var(--space-lg);display:grid}.ds-choice{grid-template-columns:var(--size-check) minmax(0, 1fr);align-items:start;column-gap:var(--space-md);row-gap:var(--space-2xs);cursor:pointer;display:grid}.ds-choice>.ds-checkbox,.ds-choice>.ds-radio{margin-top:var(--space-2xs)}.ds-choice-label{font:var(--text-body-strong);color:var(--color-text-primary)}.ds-choice-description{font:var(--text-caption);color:var(--color-text-muted);grid-column:2;margin:0}.ds-section{gap:var(--space-lg);min-width:0;display:grid}.ds-section+.ds-section{padding-top:var(--space-2xl);border-top:1px solid var(--color-border-divider)}.ds-section-header{justify-content:space-between;align-items:flex-start;gap:var(--space-sm) var(--space-lg);flex-wrap:wrap;display:flex}.ds-section-heading{gap:var(--space-xs);min-width:0;display:grid}.ds-section-title{font:var(--text-heading);color:var(--color-text-primary);margin:0}.ds-section-description{font:var(--text-caption);color:var(--color-text-muted);margin:0}.ds-section-actions{align-items:center;gap:var(--space-sm);flex-wrap:wrap;display:flex}.ds-section-body{gap:var(--space-lg);min-width:0;display:grid}.ds-info-list{border:1px solid var(--color-border-subtle);border-radius:var(--radius-md);background:var(--color-bg-secondary);grid-template-columns:minmax(0,max-content) minmax(0,1fr);margin:0;display:grid}.ds-info-row{display:contents}.ds-info-label,.ds-info-value{padding:var(--space-md) var(--space-lg);margin:0}.ds-info-row+.ds-info-row>.ds-info-label,.ds-info-row+.ds-info-row>.ds-info-value{border-top:1px solid var(--color-border-subtle)}.ds-info-label{font:var(--text-label);color:var(--color-text-secondary);white-space:nowrap}.ds-info-value{align-items:center;gap:var(--space-sm);min-width:0;font:var(--text-body);color:var(--color-text-normal);display:flex}.ds-info-value-text{overflow-wrap:anywhere;flex:auto;min-width:0}.ds-info-value-code{font:var(--text-code)}.ds-info-value .ds-button{flex:none}@media (max-width:520px){.ds-info-list{grid-template-columns:minmax(0,1fr)}.ds-info-label{padding-bottom:0}.ds-info-value{padding-top:var(--space-xs)}.ds-info-row+.ds-info-row>.ds-info-value{border-top:0}}.ds-chip-group{gap:var(--space-sm);flex-wrap:wrap;min-width:0;display:flex}.ds-chip{align-items:center;gap:var(--space-sm);min-height:var(--size-control-sm);padding:0 var(--space-md);border:1px solid var(--color-border-normal);border-radius:var(--radius-pill);color:var(--color-text-secondary);font:var(--text-body-strong);white-space:nowrap;cursor:pointer;transition:background-color var(--motion-fast), color var(--motion-fast), border-color var(--motion-fast);background:0 0;margin:0;display:inline-flex}.ds-chip:hover:not([data-disabled]){background:var(--color-bg-hover);color:var(--color-text-primary)}.ds-chip[data-pressed]{border-color:var(--color-accent);background:var(--color-accent);color:var(--color-text-on-accent)}.ds-chip[data-disabled]{cursor:default;opacity:var(--opacity-disabled)}.ds-chip-count{min-width:var(--size-badge);padding:0 var(--space-xs);border-radius:var(--radius-pill);background:var(--color-bg-neutral);color:var(--color-text-primary);font:var(--text-micro);line-height:var(--size-badge);text-align:center}.ds-chip[data-pressed] .ds-chip-count{background:var(--color-text-on-accent);color:var(--color-accent)}.ds-chip .ds-avatar-slot{margin-left:calc(-1 * var(--space-xs));place-items:center;display:inline-grid}.ds-empty{justify-items:center;gap:var(--space-sm);padding:var(--space-3xl) var(--space-lg);text-align:center;display:grid}.ds-empty-compact{padding:var(--space-xl) var(--space-lg)}.ds-empty-icon{width:calc(var(--size-icon-lg) * 2);height:calc(var(--size-icon-lg) * 2);margin-bottom:var(--space-xs);border-radius:var(--radius-round);background:var(--color-bg-secondary);color:var(--color-text-muted);place-items:center;display:grid}.ds-empty-icon svg{width:var(--size-icon-lg);height:var(--size-icon-lg)}.ds-empty-title{font:var(--text-body-strong);color:var(--color-text-primary);margin:0}.ds-empty-description{max-width:60ch;font:var(--text-caption);color:var(--color-text-muted);margin:0}.ds-empty-action{margin-top:var(--space-sm)}.ds-scroll-area{overscroll-behavior:contain;min-width:0;min-height:0;padding:var(--space-md);scroll-padding-block:var(--space-md);scrollbar-gutter:stable;overflow:hidden auto}.ds-scroll-area::-webkit-scrollbar{width:var(--space-sm);height:var(--space-sm)}.ds-select-popup::-webkit-scrollbar{width:var(--space-sm);height:var(--space-sm)}.ds-scroll-area::-webkit-scrollbar-track{background:0 0}.ds-select-popup::-webkit-scrollbar-track{background:0 0}.ds-scroll-area::-webkit-scrollbar-thumb{border:var(--space-2xs) solid transparent;border-radius:var(--radius-pill);background:var(--color-scrollbar-thumb);background-clip:padding-box}.ds-select-popup::-webkit-scrollbar-thumb{border:var(--space-2xs) solid transparent;border-radius:var(--radius-pill);background:var(--color-scrollbar-thumb);background-clip:padding-box}.ds-scroll-area::-webkit-scrollbar-thumb:hover{background-color:var(--color-bg-neutral-hover)}.ds-select-popup::-webkit-scrollbar-thumb:hover{background-color:var(--color-bg-neutral-hover)}.ds-scroll-area::-webkit-scrollbar-button{display:none}.ds-select-popup::-webkit-scrollbar-button{display:none}.ds-scroll-area::-webkit-scrollbar-corner{background:0 0}.ds-select-popup::-webkit-scrollbar-corner{background:0 0}.ds-popover-positioner{z-index:var(--z-ui-popover);max-width:calc(100vw - var(--space-lg))}.ds-popover-content{max-height:calc(var(--app-safe-height) - var(--space-2xl));overscroll-behavior:contain;border:1px solid var(--color-border-normal);border-radius:var(--radius-lg);background:var(--color-bg-primary);color:var(--color-text-normal);box-shadow:var(--shadow-suggestions);overflow-y:auto}.ds-dialog-backdrop{z-index:var(--z-ui-dialog-backdrop);background:var(--color-dialog-overlay);position:fixed;inset:0}.ds-dialog-content{top:calc(var(--app-top) + var(--safe-top) + var(--app-safe-height) / 2);z-index:var(--z-ui-dialog);max-height:calc(var(--app-safe-height) - var(--space-3xl));overscroll-behavior:contain;border:1px solid var(--color-border-normal);border-radius:var(--radius-lg);background:var(--color-bg-primary);color:var(--color-text-normal);box-shadow:var(--shadow-dialog);position:fixed;left:50%;overflow:hidden;transform:translate(-50%,-50%)}.ds-dialog-content[data-open]{flex-direction:column;display:flex}@media (max-width:480px){.ds-dialog-content{width:calc(100vw - var(--space-lg));max-height:calc(var(--app-safe-height) - var(--space-lg))}}.ds-loading{padding:var(--space-lg);color:var(--color-accent);justify-content:center;align-items:center;display:flex}.ds-loading-sm{vertical-align:middle;padding:0;display:inline-flex}.ds-loading-lg{padding:var(--space-3xl)}.ds-loading-stage{width:var(--size-loading);height:var(--size-loading);animation:.32s cubic-bezier(.34,1.56,.64,1) both ds-loading-in;display:block;position:relative}.ds-loading-sm .ds-loading-stage{width:var(--size-loading-sm);height:var(--size-loading-sm)}.ds-loading-lg .ds-loading-stage{width:var(--size-loading-lg);height:var(--size-loading-lg)}.ds-loading-mark{position:absolute;inset:23%}.ds-loading-whale,.ds-loading-human,.ds-loading-body,.ds-loading-eyes{position:absolute;inset:0}.ds-loading-whale{z-index:1;transform-origin:45.07% 37.97%;animation:3.2s linear infinite ds-loading-whale}.ds-loading-human{transform-origin:65.27% 72.43%;animation:3.2s linear infinite ds-loading-human}.ds-loading-body,.ds-loading-eyes{-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:contain;mask-size:contain;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat}.ds-loading-body{background:currentColor}.ds-loading-eyes{background:var(--color-text-on-accent)}.ds-loading-whale .ds-loading-eyes{transform-origin:48.4% 32.6%;animation:3.2s linear infinite ds-loading-whale-eyes}.ds-loading-human .ds-loading-eyes{transform-origin:66.8% 67.8%;animation:3.2s linear infinite ds-loading-human-eyes}.ds-loading-label{clip:rect(0, 0, 0, 0);white-space:nowrap;width:1px;height:1px;margin:-1px;position:absolute;overflow:hidden}@keyframes ds-loading-in{0%{opacity:0;transform:scale(.4)}to{opacity:1;transform:scale(1)}}@keyframes ds-loading-whale{0%{transform:translate(0%)rotate(0)scale(1)}3.25%{transform:translate(-.05%,-.08%)rotate(.05deg)scale(1)}3.75%{transform:translate(-.39%,-.67%)rotate(.39deg)scale(.98)}4.5%{transform:translate(-1.43%,-2.43%)rotate(1.29deg)scale(.92)}5.25%{transform:translate(-2.89%,-4.93%)rotate(2.11deg)scale(.84)}6%{transform:translate(-4.57%,-7.79%)rotate(2.17deg)scale(.75)}6.5%{transform:translate(-5.7%,-9.72%)rotate(1.57deg)scale(.69)}7%{transform:translate(-6.76%,-11.54%)rotate(.42deg)scale(.63)}7.75%{transform:translate(-8.11%,-13.83%)rotate(-2.11deg)scale(.38,.56)}8.25%{transform:translate(-8.74%,-14.91%)rotate(-4.03deg)scale(.2,.52)}8.75%{transform:translate(-9.08%,-15.5%)rotate(-5.82deg)scale(.04,.5)}9.25%{transform:translate(-9.06%,-15.62%)rotate(-7.2deg)scale(-.12,.5)}9.75%{transform:translate(-8.54%,-15.92%)rotate(-8.2deg)scale(-.27,.5)}10.25%{transform:translate(-7.51%,-16.49%)rotate(-8.81deg)scale(-.42,.5)}10.75%{transform:translate(-5.98%,-17.29%)rotate(-9deg)scale(-.5,.5)}11.25%{transform:translate(-3.96%,-18.27%)rotate(-8.75deg)scale(-.5,.5)}11.75%{transform:translate(-1.46%,-19.34%)rotate(-8.08deg)scale(-.5,.5)}12.5%{transform:translate(3.2%,-20.99%)rotate(-6.36deg)scale(-.5,.5)}13.25%{transform:translate(8.89%,-22.43%)rotate(-3.96deg)scale(-.5,.5)}14%{transform:translate(15.52%,-23.35%)rotate(-1.13deg)scale(-.5,.5)}14.75%{transform:translate(22.95%,-23.47%)rotate(1.83deg)scale(-.5,.5)}15.25%{transform:translate(28.23%,-22.98%)rotate(3.7deg)scale(-.5,.5)}15.75%{transform:translate(33.7%,-21.95%)rotate(5.4deg)scale(-.5,.5)}16.25%{transform:translate(39.25%,-20.32%)rotate(6.84deg)scale(-.5,.5)}16.75%{transform:translate(44.78%,-18.07%)rotate(7.95deg)scale(-.5,.5)}17.25%{transform:translate(50.19%,-15.18%)rotate(8.68deg)scale(-.5,.5)}17.75%{transform:translate(55.37%,-11.66%)rotate(8.99deg)scale(-.5,.5)}18.25%{transform:translate(60.22%,-7.52%)rotate(8.87deg)scale(-.5,.5)}18.75%{transform:translate(64.63%,-2.83%)rotate(8.31deg)scale(-.5,.5)}19.25%{transform:translate(68.51%,2.37%)rotate(7.36deg)scale(-.5,.5)}19.75%{transform:translate(71.8%,7.98%)rotate(6.06deg)scale(-.5,.5)}20.25%{transform:translate(74.44%,13.91%)rotate(4.46deg)scale(-.5,.5)}20.75%{transform:translate(76.38%,20.05%)rotate(2.65deg)scale(-.35,.5)}21.25%{transform:translate(77.62%,26.3%)rotate(.71deg)scale(-.19,.5)}21.75%{transform:translate(78.17%,32.54%)rotate(-1.27deg)scale(-.04,.5)}22.25%{transform:translate(78.05%,38.68%)rotate(-3.18deg)scale(.12,.5)}22.75%{transform:translate(77.31%,44.6%)rotate(-4.94deg)scale(.27,.5)}23.25%{transform:translate(76.01%,50.24%)rotate(-6.46deg)scale(.42,.5)}24%{transform:translate(73.21%,57.99%)rotate(-8.14deg)scale(.5)}24.75%{transform:translate(69.65%,64.76%)rotate(-8.95deg)scale(.5)}25.25%{transform:translate(67.03%,68.68%)rotate(-8.95deg)scale(.5)}25.75%{transform:translate(64.31%,72.12%)rotate(-8.51deg)scale(.5)}26.25%{transform:translate(61.6%,75.08%)rotate(-7.67deg)scale(.5)}27%{transform:translate(57.75%,78.66%)rotate(-5.74deg)scale(.5)}27.75%{transform:translate(54.4%,81.3%)rotate(-3.18deg)scale(.5)}28.5%{transform:translate(51.79%,83.1%)rotate(-.28deg)scale(.5)}29.25%{transform:translate(50.12%,84.15%)rotate(2.65deg)scale(.5)}29.75%{transform:translate(49.6%,84.46%)rotate(4.46deg)scale(.5)}30.25%{transform:translate(49.49%,84.42%)rotate(6.04deg)scale(.5)}30.75%{transform:translate(49.15%,83.85%)rotate(7.22deg)scale(.51)}31.25%{transform:translate(48.52%,82.77%)rotate(7.88deg)scale(.52)}31.75%{transform:translate(47.63%,81.26%)rotate(7.99deg)scale(.53)}32.25%{transform:translate(46.53%,79.37%)rotate(7.59deg)scale(.55)}33%{transform:translate(44.55%,75.99%)rotate(6.2deg)scale(.58)}34.25%{transform:translate(40.72%,69.46%)rotate(2.93deg)scale(.64)}35.25%{transform:translate(37.53%,64.03%)rotate(.69deg)scale(.69)}36%{transform:translate(35.29%,60.2%)rotate(-.29deg)scale(.72)}36.75%{transform:translate(33.3%,56.82%)rotate(-.62deg)scale(.75)}37.5%{transform:translate(31.72%,54.12%)rotate(-.47deg)scale(.78)}38.25%{transform:translate(30.68%,52.34%)rotate(-.16deg)scale(.79)}38.75%{transform:translate(30.34%,51.76%)rotate(-.02deg)scale(.8)}53.25%{transform:translate(30.3%,51.68%)rotate(-.05deg)scale(.8)}54.25%{transform:translate(30.19%,51.5%)rotate(-.96deg)scale(.77)}55.25%{transform:translate(29.99%,51.17%)rotate(-2.11deg)scale(.71)}56%{transform:translate(29.82%,50.86%)rotate(-2.17deg)scale(.65)}56.5%{transform:translate(29.7%,50.66%)rotate(-1.57deg)scale(.61)}57%{transform:translate(29.58%,50.47%)rotate(-.42deg)scale(.58)}57.75%{transform:translate(29.44%,50.22%)rotate(2.11deg)scale(.53)}58.75%{transform:translate(29.34%,50.05%)rotate(5.82deg)scale(.5)}59.5%{transform:translate(29.07%,50.19%)rotate(7.75deg)scale(.5)}60%{transform:translate(28.29%,50.63%)rotate(8.56deg)scale(.5)}60.5%{transform:translate(27.01%,51.33%)rotate(8.96deg)scale(.5)}61%{transform:translate(25.24%,52.22%)rotate(8.93deg)scale(.5)}61.75%{transform:translate(21.66%,53.8%)rotate(8.08deg)scale(.5)}62.5%{transform:translate(17%,55.45%)rotate(6.36deg)scale(.5)}63.25%{transform:translate(11.31%,56.89%)rotate(3.96deg)scale(.5)}64%{transform:translate(4.68%,57.81%)rotate(1.13deg)scale(.5)}64.75%{transform:translate(-2.75%,57.93%)rotate(-1.83deg)scale(.5)}65.25%{transform:translate(-8.03%,57.44%)rotate(-3.7deg)scale(.5)}65.75%{transform:translate(-13.5%,56.41%)rotate(-5.4deg)scale(.5)}66.25%{transform:translate(-19.05%,54.78%)rotate(-6.84deg)scale(.5)}66.75%{transform:translate(-24.58%,52.53%)rotate(-7.95deg)scale(.5)}67.25%{transform:translate(-29.99%,49.64%)rotate(-8.68deg)scale(.5)}67.75%{transform:translate(-35.17%,46.12%)rotate(-8.99deg)scale(.5)}68.25%{transform:translate(-40.02%,41.98%)rotate(-8.87deg)scale(.5)}68.75%{transform:translate(-44.43%,37.29%)rotate(-8.31deg)scale(.5)}69.25%{transform:translate(-48.31%,32.09%)rotate(-7.36deg)scale(.5)}69.75%{transform:translate(-51.6%,26.48%)rotate(-6.06deg)scale(.5)}70.25%{transform:translate(-54.24%,20.55%)rotate(-4.46deg)scale(.5)}70.75%{transform:translate(-56.18%,14.41%)rotate(-2.65deg)scale(.35,.5)}71.25%{transform:translate(-57.42%,8.16%)rotate(-.71deg)scale(.19,.5)}71.75%{transform:translate(-57.97%,1.92%)rotate(1.27deg)scale(.04,.5)}72.25%{transform:translate(-57.85%,-4.22%)rotate(3.18deg)scale(-.12,.5)}72.75%{transform:translate(-57.11%,-10.14%)rotate(4.94deg)scale(-.27,.5)}73.25%{transform:translate(-55.81%,-15.78%)rotate(6.46deg)scale(-.42,.5)}74%{transform:translate(-53.01%,-23.53%)rotate(8.14deg)scale(-.5,.5)}74.75%{transform:translate(-49.45%,-30.3%)rotate(8.95deg)scale(-.5,.5)}75.25%{transform:translate(-46.83%,-34.22%)rotate(8.95deg)scale(-.5,.5)}75.75%{transform:translate(-44.11%,-37.66%)rotate(8.51deg)scale(-.5,.5)}76.25%{transform:translate(-41.4%,-40.62%)rotate(7.67deg)scale(-.5,.5)}77%{transform:translate(-37.55%,-44.2%)rotate(5.74deg)scale(-.35,.5)}77.75%{transform:translate(-34.2%,-46.84%)rotate(3.18deg)scale(-.12,.5)}78.5%{transform:translate(-31.59%,-48.64%)rotate(.28deg)scale(.12,.5)}79.25%{transform:translate(-29.92%,-49.69%)rotate(-2.65deg)scale(.35,.5)}79.75%{transform:translate(-29.4%,-50%)rotate(-4.46deg)scale(.5)}80.25%{transform:translate(-29.26%,-49.92%)rotate(-6.04deg)scale(.5)}80.75%{transform:translate(-28.75%,-49.05%)rotate(-7.22deg)scale(.51)}81.25%{transform:translate(-27.79%,-47.41%)rotate(-7.88deg)scale(.53)}81.75%{transform:translate(-26.44%,-45.1%)rotate(-7.99deg)scale(.55)}82.25%{transform:translate(-24.75%,-42.22%)rotate(-7.59deg)scale(.58)}83%{transform:translate(-21.73%,-37.06%)rotate(-6.2deg)scale(.63)}84%{transform:translate(-17.1%,-29.17%)rotate(-3.59deg)scale(.71)}85%{transform:translate(-12.23%,-20.87%)rotate(-1.16deg)scale(.79)}85.75%{transform:translate(-8.71%,-14.86%)rotate(.04deg)scale(.85)}86.5%{transform:translate(-5.53%,-9.44%)rotate(.58deg)scale(.91)}87%{transform:translate(-3.7%,-6.31%)rotate(.61deg)scale(.94)}87.5%{transform:translate(-2.17%,-3.71%)rotate(.47deg)scale(.96)}88%{transform:translate(-1.01%,-1.72%)rotate(.26deg)scale(.98)}88.5%{transform:translate(-.26%,-.45%)rotate(.08deg)scale(1)}89%{transform:translate(0%)rotate(0)scale(1)}to{transform:translate(0%)rotate(0)scale(1)}}@keyframes ds-loading-whale-eyes{0%{transform:translate(2.53%,4.31%)scale(1)}3.75%{transform:translate(2.58%,4.41%)scale(1)}5.5%{transform:translate(3.11%,5.31%)scale(1)}6.25%{transform:translate(3.52%,6%)scale(1)}8.75%{transform:translate(3.54%,6.04%)scale(1)}9%{transform:translate(-3.54%,6.04%)scale(1)}11%{transform:translate(-3.05%,6.3%)scale(1)}13%{transform:translate(-1.61%,6.81%)scale(1)}15.25%{transform:translate(.97%,6.93%)scale(1)}17%{transform:translate(3.3%,6.18%)scale(1)}18.75%{transform:translate(5.36%,4.5%)scale(1)}20.25%{transform:translate(6.55%,2.48%)scale(1)}21.75%{transform:translate(7%,.23%)scale(1)}22%{transform:translate(-7%,-.14%)scale(1)}23.75%{transform:translate(-6.52%,-2.54%)scale(1)}25.75%{transform:translate(-5.32%,-4.55%)scale(1)}27.75%{transform:translate(-4.13%,-5.65%)scale(1)}29.75%{transform:translate(-3.55%,-6.03%)scale(1)}36%{transform:translate(-3.5%,-5.97%)scale(1)}38.75%{transform:translate(-3.16%,-5.4%)scale(1)}45%{transform:translate(-3.16%,-5.39%)scale(1)}46%{transform:translate(-3.16%,-5.39%)scaleY(.14)}46.75%{transform:translate(-3.16%,-5.39%)scaleY(.27)}47.5%{transform:translate(-3.16%,-5.39%)scale(1)}53.75%{transform:translate(-3.21%,-5.48%)scale(1)}55.5%{transform:translate(-3.54%,-6.04%)scale(1)}60%{transform:translate(-3.41%,-6.11%)scale(1)}61.75%{transform:translate(-2.61%,-6.49%)scale(1)}63.75%{transform:translate(-.84%,-6.95%)scale(1)}65.75%{transform:translate(1.63%,-6.81%)scale(1)}67.5%{transform:translate(3.94%,-5.79%)scale(1)}69.25%{transform:translate(5.83%,-3.87%)scale(1)}70.75%{transform:translate(6.78%,-1.74%)scale(1)}71.75%{transform:translate(7%,-.23%)scale(1)}72%{transform:translate(-7%,.14%)scale(1)}73.75%{transform:translate(-6.52%,2.54%)scale(1)}75.75%{transform:translate(-5.32%,4.55%)scale(1)}77.75%{transform:translate(-4.13%,5.65%)scale(1)}78%{transform:translate(-4.01%,5.74%)scale(1)}78.25%{transform:translate(3.91%,5.81%)scale(1)}80.25%{transform:translate(3.54%,6.04%)scale(1)}84.25%{transform:translate(3.47%,5.92%)scale(1)}86.5%{transform:translate(2.79%,4.76%)scale(1)}89%{transform:translate(2.53%,4.31%)scale(1)}95%{transform:translate(2.53%,4.31%)scale(1)}96%{transform:translate(2.53%,4.31%)scaleY(.14)}96.75%{transform:translate(2.53%,4.31%)scaleY(.27)}97.5%{transform:translate(2.53%,4.31%)scale(1)}to{transform:translate(2.53%,4.31%)scale(1)}}@keyframes ds-loading-human{0%{opacity:1;transform:translate(0%)scale(1)}25.75%{opacity:1;transform:translate(0%)scale(1.1,.89)}28%{opacity:1;transform:translate(0%)scale(1)}29.75%{opacity:.13;transform:translate(0%)scale(.13)}30%{opacity:0;transform:translate(-20.2%,-34.46%)scale(0)}30.5%{opacity:1;transform:translate(-20.2%,-34.46%)scale(.42)}31.75%{opacity:1;transform:translate(-20.2%,-34.46%)scale(1.12)}48%{opacity:1;transform:translate(-20.2%,-34.46%)scale(1)}75.75%{opacity:1;transform:translate(-20.2%,-34.46%)scale(1.1,.89)}78%{opacity:1;transform:translate(-20.2%,-34.46%)scale(1)}79.75%{opacity:.13;transform:translate(-20.2%,-34.46%)scale(.13)}80%{opacity:0;transform:translate(0%)scale(0)}80.5%{opacity:1;transform:translate(0%)scale(.42)}81.5%{opacity:1;transform:translate(0%)scale(1.06)}84.25%{opacity:1;transform:translate(0%)scale(.99)}to{opacity:1;transform:translate(0%)scale(1)}}@keyframes ds-loading-human-eyes{0%{transform:translate(-2.53%,-4.31%)scale(1)}10%{transform:translate(-2.44%,-4.36%)scale(1)}12.25%{transform:translate(-1.61%,-4.73%)scale(1)}14.75%{transform:translate(.24%,-4.99%)scale(1)}16.75%{transform:translate(2.12%,-4.53%)scale(1)}18.75%{transform:translate(3.83%,-3.21%)scale(1)}20.75%{transform:translate(4.84%,-1.24%)scale(1)}22.5%{transform:translate(4.96%,.62%)scale(1)}25%{transform:translate(4.15%,2.79%)scale(1)}27.25%{transform:translate(3.14%,3.9%)scale(1)}29.75%{transform:translate(2.53%,4.31%)scale(1)}38%{transform:translate(2.53%,4.31%)scale(1)}39%{transform:translate(2.53%,4.31%)scaleY(.14)}39.75%{transform:translate(2.53%,4.31%)scaleY(.27)}40.5%{transform:translate(2.53%,4.31%)scale(1)}60%{transform:translate(2.44%,4.36%)scale(1)}62.25%{transform:translate(1.61%,4.73%)scale(1)}64.75%{transform:translate(-.24%,4.99%)scale(1)}66.75%{transform:translate(-2.12%,4.53%)scale(1)}68.75%{transform:translate(-3.83%,3.21%)scale(1)}70.75%{transform:translate(-4.84%,1.24%)scale(1)}72.5%{transform:translate(-4.96%,-.62%)scale(1)}75%{transform:translate(-4.15%,-2.79%)scale(1)}77.25%{transform:translate(-3.14%,-3.9%)scale(1)}79.75%{transform:translate(-2.53%,-4.31%)scale(1)}88%{transform:translate(-2.53%,-4.31%)scale(1)}89%{transform:translate(-2.53%,-4.31%)scaleY(.14)}89.75%{transform:translate(-2.53%,-4.31%)scaleY(.27)}90.5%{transform:translate(-2.53%,-4.31%)scale(1)}to{transform:translate(-2.53%,-4.31%)scale(1)}}@media (prefers-reduced-motion:reduce){.ds-loading-stage,.ds-loading-whale,.ds-loading-human,.ds-loading-eyes{animation:none}}