@akairos/typeset-studio 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +91 -95
  2. package/dist/assets/index-3Ko1UCBU.css +1 -0
  3. package/dist/assets/index-C9J0f4Lf.js +317 -0
  4. package/dist/index.html +2 -2
  5. package/dist/lib/adapter.js +6 -7734
  6. package/dist/lib/canvas-renderer-cxnar75n.js +23 -0
  7. package/dist/lib/font-loader-j28mt43t.js +11 -0
  8. package/dist/lib/index-0370qz9e.js +303 -0
  9. package/dist/lib/index-37x76zdn.js +4 -0
  10. package/dist/lib/index-6c43c7h4.js +3880 -0
  11. package/dist/lib/index-t9w3p2p9.js +3366 -0
  12. package/dist/lib/index-zdhqg5f0.js +459 -0
  13. package/dist/lib/index.js +26 -7734
  14. package/dist/lib/render-xxd348m1.js +11 -0
  15. package/package.json +3 -7
  16. package/src/adapter.test.ts +117 -0
  17. package/src/adapter.ts +190 -281
  18. package/src/core/canvas-draw.ts +150 -47
  19. package/src/core/canvas-renderer.test.ts +58 -0
  20. package/src/core/canvas-renderer.ts +2 -1
  21. package/src/core/data-dir.ts +13 -0
  22. package/src/core/engine.ts +86 -56
  23. package/src/core/font-loader.test.ts +20 -0
  24. package/src/core/font-loader.ts +6 -2
  25. package/src/core/image-layout.ts +4 -0
  26. package/src/core/inline-text.ts +6 -4
  27. package/src/core/obstacle-flow.ts +29 -15
  28. package/src/core/page-images.test.ts +90 -0
  29. package/src/core/pretext.ts +4 -10
  30. package/src/core/template.test.ts +38 -1
  31. package/src/core/template.ts +53 -41
  32. package/src/core/types.ts +46 -0
  33. package/src/core/typography.ts +0 -14
  34. package/src/index.ts +6 -3
  35. package/src/server/auth.ts +18 -13
  36. package/src/server/index.ts +11 -16
  37. package/src/server/routes/fonts.ts +1 -1
  38. package/src/server/routes/render.ts +60 -17
  39. package/src/server/routes/slots.test.ts +96 -0
  40. package/src/server/routes/templates.ts +37 -48
  41. package/src/server/routes/upload.ts +2 -1
  42. package/src/web/App.tsx +15 -6
  43. package/src/web/components/CanvasViewport.tsx +56 -42
  44. package/src/web/components/EditorSidebar.tsx +59 -48
  45. package/src/web/components/Header.tsx +1 -1
  46. package/src/web/components/TemplateModal.tsx +25 -28
  47. package/src/web/hooks/image-layout.ts +31 -0
  48. package/src/web/hooks/useLocalFonts.ts +10 -1
  49. package/src/web/hooks/useTypeset.ts +139 -100
  50. package/data/templates/.gitkeep +0 -0
  51. package/data/templates/tpl_1791681833354_iofsk.json +0 -46
  52. package/data/templates/tpl_1791682603199_875ny.json +0 -66
  53. package/data/templates/tpl_1791682780929_qnuiy.json +0 -66
  54. package/data/templates/tpl_longform_paper_sage.json +0 -47
  55. package/data/templates/tpl_longform_vintage_news.json +0 -47
  56. package/data/templates/tpl_newspic_poster_cover.json +0 -64
  57. package/data/templates/tpl_square_quote_card.json +0 -46
  58. package/data/templates/tpl_wechat_cover_editorial.json +0 -64
  59. package/data/templates/tpl_wechat_cover_minimal.json +0 -64
  60. package/data/templates/tpl_wide_tech_banner.json +0 -46
  61. package/dist/assets/index-CZ352D7X.css +0 -1
  62. package/dist/assets/index-Dpnz-F2g.js +0 -317
package/README.md CHANGED
@@ -10,7 +10,7 @@
10
10
  <a href="#-运行环境与依赖要求"><img src="https://img.shields.io/badge/Runtime-Bun%20%E2%89%A5%201.2-000000.svg?style=flat-square&logo=bun" alt="Bun Runtime"></a>
11
11
  <a href="#-运行环境与依赖要求"><img src="https://img.shields.io/badge/Language-TypeScript%205.8-3178C6.svg?style=flat-square&logo=typescript" alt="TypeScript"></a>
12
12
  <a href="#-技术栈"><img src="https://img.shields.io/badge/UI-React%2019%20%2B%20Tailwind-61DAFB.svg?style=flat-square&logo=react" alt="React 19"></a>
13
- <a href="#-开发与测试"><img src="https://img.shields.io/badge/Tests-26%20Passing%20(100%25)-brightgreen.svg?style=flat-square" alt="Tests Passing"></a>
13
+ <a href="#-开发与测试"><img src="https://img.shields.io/badge/Tests-Bun-brightgreen.svg?style=flat-square" alt="Tests Passing"></a>
14
14
  <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=flat-square" alt="MIT License"></a>
15
15
  </p>
16
16
 
@@ -80,7 +80,6 @@
80
80
  | **`react` / `react-dom`** | `^19.0.0` | 现代化声明式组件与状态管理 |
81
81
  | **`tailwindcss`** | `^3.4.17` | 高性能极简黑白美学原子化样式 |
82
82
  | **`lucide-react`** | `^1.16.0` | 精致图标库 |
83
- | **`html-to-image`** | `^1.11.11` | 前端画布超清 2x 导出 |
84
83
 
85
84
  ---
86
85
 
@@ -104,13 +103,12 @@ bun install
104
103
  bun run server
105
104
  ```
106
105
 
107
- 服务启动后,控制台将输出服务端口与管理员密钥:
106
+ 服务启动后输出端口信息,管理员密钥保存在环境变量或 `.env` 中:
108
107
  ```text
109
108
  ┌────────────────────────────────────────────────────────┐
110
109
  │ ⚡ Typeset Studio Server │
111
110
  ├────────────────────────────────────────────────────────┤
112
111
  │ Web URL: http://localhost:4890 │
113
- │ Admin Key: tsk_xxxxxxxxxxxxxxxx │
114
112
  │ Protected: X-Admin-Key header required │
115
113
  └────────────────────────────────────────────────────────┘
116
114
  ```
@@ -131,103 +129,29 @@ bun run dev:all
131
129
 
132
130
  Typeset Studio 专为现代发布管线(如 `zzhub-pipeline`)设计,可直接作为 `imageRenderer` 插件无缝替换原有的 Chrome 截屏机制。
133
131
 
134
- ### 1. 适配器实现示例 (`pipeline-adapter.ts`)
135
-
136
- 在您的流水线项目(或 `typeset-studio/src/`)中定义适配器:
137
-
138
- ```typescript
139
- import { join } from "node:path";
140
- import { mkdir, writeFile } from "node:fs/promises";
141
- import type {
142
- ImageRenderPlugin,
143
- ImageRenderInput,
144
- ImageRenderOutput,
145
- } from "zzhub-pipeline/src/adapter-types";
146
-
147
- const STUDIO_URL = process.env.TYPESET_STUDIO_URL || "http://localhost:4890";
148
-
149
- export const typesetStudioAdapter: ImageRenderPlugin = {
150
- name: "typeset-studio",
151
- version: "0.1.0",
152
-
153
- doctor: async () => {
154
- try {
155
- const res = await fetch(`${STUDIO_URL}/api/health`);
156
- return [{ name: "typeset-studio", ok: res.ok, message: "Typeset Studio 在线" }];
157
- } catch {
158
- return [{ name: "typeset-studio", ok: false, message: "无法连接 Typeset Studio (4890)" }];
159
- }
160
- },
161
-
162
- render: async (input: ImageRenderInput): Promise<ImageRenderOutput> => {
163
- const { route, bodyText, title, outputDir, state, bodyImages } = input;
164
- await mkdir(outputDir, { recursive: true });
165
- const account = state.route.account || "default";
166
-
167
- // 微信图文封面 (2.35:1)
168
- if (route === "wechat-article") {
169
- const resp = await fetch(`${STUDIO_URL}/api/render`, {
170
- method: "POST",
171
- headers: { "Content-Type": "application/json" },
172
- body: JSON.stringify({
173
- slot: "article_cover",
174
- account,
175
- title,
176
- markdown: `# ${title}`,
177
- }),
178
- }).then((r) => r.json());
179
-
180
- const coverPath = join(outputDir, "cover.png");
181
- // 写入图片资源...
182
- return {
183
- assets: [{ kind: "cover", route, path: coverPath }],
184
- pageCount: 1,
185
- pages: [{ page: 1, imageCount: 0, imageSources: [] }],
186
- };
187
- }
188
-
189
- // 贴图长图文 (3:4 多页)
190
- if (route === "wechat-newspic") {
191
- const resp = await fetch(`${STUDIO_URL}/api/render`, {
192
- method: "POST",
193
- headers: { "Content-Type": "application/json" },
194
- body: JSON.stringify({
195
- slot: "newspic_content",
196
- account,
197
- title,
198
- markdown: bodyText,
199
- images: bodyImages?.map((img, i) => ({ id: `img_${i}`, src: img.path, alt: img.marker })),
200
- }),
201
- }).then((r) => r.json());
202
-
203
- // 写入各个 page-01.png, page-02.png...
204
- return {
205
- assets: [...],
206
- pageCount: resp.result.pageCount,
207
- pages: resp.result.pages.map(p => ({ page: p.pageNumber, imageCount: p.images.length, imageSources: [] })),
208
- };
209
- }
210
-
211
- throw new Error(`Unsupported route: ${route}`);
212
- },
213
- };
214
-
215
- export default typesetStudioAdapter;
216
- ```
132
+ ### 1. 使用内置适配器
133
+
134
+ 在流水线项目安装 npm 包:
217
135
 
218
- ### 2. 在 Pipeline 中配置启用
136
+ ```bash
137
+ npm install @akairos/typeset-studio
138
+ ```
219
139
 
220
- 在 `zzhub-pipeline` 的 `config.json` 中配置指定适配器路径:
140
+ 在 `zzhub-pipeline` 的 `config.json` 中启用内置适配器:
221
141
 
222
142
  ```json
223
143
  {
224
144
  "plugins": {
225
- "imageRenderer": "/path/to/typeset-studio/src/pipeline-adapter.ts"
145
+ "imageRenderer": "@akairos/typeset-studio/adapter"
226
146
  }
227
147
  }
228
148
  ```
229
149
 
230
- 配置后,执行 `zzp render --state ...` 或 `zzp run` 将自动切换为 Typeset Studio 驱动!
150
+ 源码联调也可将 `imageRenderer` 指向本项目的 `src/adapter.ts` 或构建后的 `dist/lib/adapter.js`。
151
+
152
+ 默认使用本地原生 Canvas。需要独立渲染服务时,在流水线进程设置 `TYPESET_STUDIO_URL` 和 `TYPESET_ADMIN_KEY`,适配器会传输本地图片并保存返回的 PNG。
153
+
154
+ 配置后执行 `zzp render --state ...` 或 `zzp run` 即可使用该插件。
231
155
 
232
156
  ---
233
157
 
@@ -238,7 +162,7 @@ export default typesetStudioAdapter;
238
162
  ### 1. 服务健康检查 (`GET /api/health`)
239
163
  ```bash
240
164
  curl http://localhost:4890/api/health
241
- # {"status":"ok","name":"Typeset Studio Server","version":"0.1.0"}
165
+ # {"status":"ok","name":"Typeset Studio Server","version":"0.2.2"}
242
166
  ```
243
167
 
244
168
  ### 2. 槽位解析与排版 (`POST /api/render`)
@@ -260,10 +184,11 @@ curl -X POST http://localhost:4890/api/render \
260
184
  ```bash
261
185
  curl -X POST http://localhost:4890/api/render/png \
262
186
  -H "Content-Type: application/json" \
187
+ -H "X-Admin-Key: <ADMIN_KEY>" \
263
188
  -d '{
264
189
  "markdown": "# 纯算法排版实战\n无需设计师介入,自动排版对齐",
265
- "ratio": "2.35:1",
266
- "themeName": "editorial-warm"
190
+ "slot": "article_cover",
191
+ "templateKey": "tpl_wechat_cover_editorial"
267
192
  }' \
268
193
  --output cover.png
269
194
  ```
@@ -273,9 +198,11 @@ curl -X POST http://localhost:4890/api/render/png \
273
198
  ```bash
274
199
  curl -X POST http://localhost:4890/api/render/export \
275
200
  -H "Content-Type: application/json" \
201
+ -H "X-Admin-Key: <ADMIN_KEY>" \
276
202
  -d '{
277
203
  "markdown": "# 页面一\n这是第一页\n\n---\n\n# 页面二\n这是第二页",
278
- "ratio": "3:4"
204
+ "slot": "newspic_content",
205
+ "config": { "aspectRatio": "longform-3-4" }
279
206
  }'
280
207
  # 返回 {"ok": true, "pageCount": 2, "pages": [{ "pageNumber": 1, "dataUrl": "..." }, ...]}
281
208
  ```
@@ -364,3 +291,72 @@ bun run build
364
291
  ## 📄 开源协议
365
292
 
366
293
  本项目基于 [MIT 协议](./LICENSE) 开源。
294
+
295
+ ### 鉴权与 pipeline 参数
296
+
297
+ 业务 API(模板、上传、排版)均要求 `X-Admin-Key`。本机编辑器通过真实回环连接自动领取密钥;远程编辑器需在设置中填写密钥。密钥来自 `TYPESET_ADMIN_KEY` 或服务目录的 `.env`,跨域网页不能自动领取。反向代理应使用业务域名,并保留原始 Host。
298
+
299
+ 远程插件设置 `TYPESET_STUDIO_URL` 与 `TYPESET_ADMIN_KEY`。插件会传输本地图片字节;`/uploads/` 路径表示远程服务的上传文件。`TYPESET_DATA_DIR` 统一控制模板、默认配置和上传文件目录。
300
+
301
+ 插件的模板选择顺序为:`studioTemplates.cover/content` → 旧调用中的具体 `template` 名称 → 当前账号的用途槽位默认模板。`wechat-cover-split`、`poster-3-4`、`longform-3-4` 只表示 pipeline 渲染模式,不作为 Studio 模板名查询。未知模板会报错。
302
+
303
+ 插件以有效的 Studio 模板样式为准,pipeline 的 `theme`、`coverTheme`、账号配色、字体、署名和图标不会覆盖已选模板。直接调用渲染 API 时,默认仍允许显式 `config` 覆盖;传 `preserveTemplateStyle: true` 可保留模板样式。页数和逐页填充率约束仍然生效。
304
+
305
+ `minPages`/`maxPages`(0 表示不设置业务上限)和 `pageSpecs.targetFillRatio` 参与正文分页。填充率决定文字可用高度,少页约束尝试将正文分配到更多页面;无法满足时明确报错,不截断文字。引擎最多生成 50 页以避免失控。手动分页段落溢出时继续分页,段落图片保留在该段首页,最终页码连续。`onProgress` 在排版开始、封面和正文生成后报告阶段及实际页数。
306
+
307
+ 服务默认绑定 `127.0.0.1`;需要局域网或远程访问时设置 `TYPESET_HOST=0.0.0.0`。反向代理请求不能自动取钥。
308
+
309
+ ### 固定模板素材
310
+
311
+ 在编辑器上传图片后,在图片卡片开启“固定模板素材”,填写同一模板内唯一的标识(如 `background`、`logo`,仅字母、数字、下划线和短横线)。自由图层模式下,勾选“设为整页背景”可将图片放到文字下方并铺满当前画布;取消勾选恢复勾选前的布局。点击“重置布局”恢复本次上传或应用模板时的尺寸、位置和图层设置,图片来源、说明和素材标识不变。保存模板后,新内容会沿用这些设置;文字按实际内容重新换行、分页。
312
+
313
+ 固定素材默认按最终输出页码放置,未指定页码视为第 1 页。“每页重复”仅允许自由图层开启,切回 Flow 模式自动关闭;接口同样拒绝 Flow 素材重复。开启后会出现在该模板的全部输出页,包括自动溢出的续页。重复素材不独立制造页面,也不占普通配图数量。封面和正文分别使用各自的模板,正文模板的第 1 页从正文开始计算。
314
+
315
+ 普通输入图片只匹配模板的普通图片位置;固定素材默认保留模板中的来源。重新应用或切换模板会替换之前的固定素材。未标记的旧模板图片保留原替换规则,无需迁移。
316
+
317
+ 渲染 API 可按标识替换当前请求的素材,布局仍使用模板设置,不回写模板文件:
318
+
319
+ ```json
320
+ {
321
+ "template": "tpl_my_cover",
322
+ "markdown": "# 新标题",
323
+ "preserveTemplateStyle": true,
324
+ "templateImageOverrides": {
325
+ "background": "/absolute/path/new-background.png"
326
+ }
327
+ }
328
+ ```
329
+
330
+ Pipeline 插件输入新增以下可选字段:
331
+
332
+ ```ts
333
+ studioTemplates: { cover: "tpl_my_cover", content: "tpl_my_content" },
334
+ templateImageOverrides: {
335
+ cover: { background: "/absolute/path/new-background.png" },
336
+ content: { logo: "https://example.com/logo.png" },
337
+ },
338
+ ```
339
+
340
+ 对应的 pipeline 命令(需使用支持这些参数的 pipeline 源码或本地安装):
341
+
342
+ ```bash
343
+ zzp render --state /absolute/path/workflow-state.json \
344
+ --studio-cover-template tpl_my_cover \
345
+ --studio-content-template tpl_my_content \
346
+ --template-images-file /absolute/path/template-images.json
347
+ ```
348
+
349
+ `template-images.json` 内容:
350
+
351
+ ```json
352
+ {
353
+ "cover": { "background": "./new-background.png" },
354
+ "content": { "logo": "https://example.com/logo.png" }
355
+ }
356
+ ```
357
+
358
+ 选择和映射保存在任务的 `intent.studio_render`,重跑继续使用。模板参数传 `""` 清除任务选择、恢复槽位默认;映射文件整体替换任务原映射,传 `{}` 清除全部替换。相对路径按映射文件所在目录解析。固定素材独立于 `bodyImages`,不进入正文配图等待、标记过滤或“每页必须有正文配图”的计数。
359
+
360
+ 模板只保存文件或 URL 引用,不嵌入、不自动打包迁移。`/uploads/...` 由实际渲染方的 `TYPESET_DATA_DIR` 解析,本地插件和编辑器应使用同一目录;远程模式使用服务端目录。模板中的绝对路径必须在渲染机器存在,HTTP URL 必须可访问。远程调用会传输任务替换中本地文件的字节,但不会复制原模板素材。缺失固定素材、未知替换标识或重复标识会明确报错,终止出图。
361
+
362
+ 本功能尚未随 npm 发布。本地联调时,将 pipeline 的 `plugins.imageRenderer` 指向本仓库 `src/adapter.ts` 或构建后的 `dist/lib/adapter.js`,避免继续加载已安装的旧版本。
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,PingFang SC,Hiragino Sans GB,Microsoft YaHei,sans-serif;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:GeistMono,JetBrains Mono,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}:root{--bg-page: #fafafa;--bg-surface: #ffffff;--bg-surface-elevated: #f5f5f5;--border-subtle: #eaeaea;--border-strong: #d4d4d4;--text-primary: #171717;--text-secondary: #666666;--text-tertiary: #999999;--grid-line: rgba(0, 0, 0, .05);--scrollbar-thumb: #e0e0e0;--scrollbar-thumb-hover: #c0c0c0}.dark{--bg-page: #000000;--bg-surface: #0a0a0a;--bg-surface-elevated: #141414;--border-subtle: #222222;--border-strong: #333333;--text-primary: #ededed;--text-secondary: #888888;--text-tertiary: #555555;--grid-line: rgba(255, 255, 255, .03);--scrollbar-thumb: #262626;--scrollbar-thumb-hover: #3a3a3a}body{background-color:var(--bg-page);color:var(--text-primary);font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,sans-serif}.visible{visibility:visible}.static{position:static}.\!fixed{position:fixed!important}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.left-0{left:0}.top-0{top:0}.z-20{z-index:20}.z-30{z-index:30}.z-50{z-index:50}.mx-0\.5{margin-left:.125rem;margin-right:.125rem}.mb-1{margin-bottom:.25rem}.mb-1\.5{margin-bottom:.375rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.block{display:block}.inline{display:inline}.flex{display:flex}.table{display:table}.grid{display:grid}.list-item{display:list-item}.hidden{display:none}.h-1\.5{height:.375rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-2\.5{height:.625rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-screen{height:100vh}.max-h-\[75vh\]{max-height:75vh}.min-h-0{min-height:0px}.w-1\.5{width:.375rem}.w-11{width:2.75rem}.w-12{width:3rem}.w-2\.5{width:.625rem}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-80{width:20rem}.w-\[1px\]{width:1px}.w-full{width:100%}.w-screen{width:100vw}.min-w-0{min-width:0px}.min-w-fit{min-width:-moz-fit-content;min-width:fit-content}.max-w-\[120px\]{max-width:120px}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(360deg)}}.animate-spin{animation:spin 1s linear infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize-none{resize:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-0\.5{gap:.125rem}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-2{gap:.5rem}.gap-2\.5{gap:.625rem}.gap-3{gap:.75rem}.gap-3\.5{gap:.875rem}.gap-4{gap:1rem}.gap-7{gap:1.75rem}.self-end{align-self:flex-end}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-l{border-left-width:1px}.border-l-2{border-left-width:2px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-dashed{border-style:dashed}.border-\[\#444\]{--tw-border-opacity: 1;border-color:rgb(68 68 68 / var(--tw-border-opacity, 1))}.border-\[\#bbf7d0\]{--tw-border-opacity: 1;border-color:rgb(187 247 208 / var(--tw-border-opacity, 1))}.border-\[\#ccc\]{--tw-border-opacity: 1;border-color:rgb(204 204 204 / var(--tw-border-opacity, 1))}.border-\[\#d4d4d4\]{--tw-border-opacity: 1;border-color:rgb(212 212 212 / var(--tw-border-opacity, 1))}.border-\[\#dfdfdf\]{--tw-border-opacity: 1;border-color:rgb(223 223 223 / var(--tw-border-opacity, 1))}.border-\[\#e0e0e0\]{--tw-border-opacity: 1;border-color:rgb(224 224 224 / var(--tw-border-opacity, 1))}.border-\[\#e5e5e5\]{--tw-border-opacity: 1;border-color:rgb(229 229 229 / var(--tw-border-opacity, 1))}.border-\[\#eaeaea\]{--tw-border-opacity: 1;border-color:rgb(234 234 234 / var(--tw-border-opacity, 1))}.border-\[\#eeeeee\]{--tw-border-opacity: 1;border-color:rgb(238 238 238 / var(--tw-border-opacity, 1))}.border-\[\#f0f0f0\]{--tw-border-opacity: 1;border-color:rgb(240 240 240 / var(--tw-border-opacity, 1))}.border-\[\#f5f5f5\]{--tw-border-opacity: 1;border-color:rgb(245 245 245 / var(--tw-border-opacity, 1))}.border-amber-200{--tw-border-opacity: 1;border-color:rgb(253 230 138 / var(--tw-border-opacity, 1))}.border-amber-500\/20{border-color:#f59e0b33}.border-amber-500\/50{border-color:#f59e0b80}.border-blue-500{--tw-border-opacity: 1;border-color:rgb(59 130 246 / var(--tw-border-opacity, 1))}.border-blue-500\/20{border-color:#3b82f633}.border-emerald-200{--tw-border-opacity: 1;border-color:rgb(167 243 208 / var(--tw-border-opacity, 1))}.border-emerald-500{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity, 1))}.border-emerald-500\/20{border-color:#10b98133}.border-transparent{border-color:transparent}.bg-\[\#09090b\]{--tw-bg-opacity: 1;background-color:rgb(9 9 11 / var(--tw-bg-opacity, 1))}.bg-\[\#0a0a0a\]{--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.bg-\[\#e0e0e0\]{--tw-bg-opacity: 1;background-color:rgb(224 224 224 / var(--tw-bg-opacity, 1))}.bg-\[\#e5e5e5\]{--tw-bg-opacity: 1;background-color:rgb(229 229 229 / var(--tw-bg-opacity, 1))}.bg-\[\#eaeaea\]{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.bg-\[\#eeeeee\]{--tw-bg-opacity: 1;background-color:rgb(238 238 238 / var(--tw-bg-opacity, 1))}.bg-\[\#f0fdf4\]{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity, 1))}.bg-\[\#f5f5f5\]{--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1))}.bg-\[\#f9f9f9\]{--tw-bg-opacity: 1;background-color:rgb(249 249 249 / var(--tw-bg-opacity, 1))}.bg-\[\#f9fcfa\]{--tw-bg-opacity: 1;background-color:rgb(249 252 250 / var(--tw-bg-opacity, 1))}.bg-\[\#fafafa\]{--tw-bg-opacity: 1;background-color:rgb(250 250 250 / var(--tw-bg-opacity, 1))}.bg-\[\#ffffff\]{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-amber-50{--tw-bg-opacity: 1;background-color:rgb(255 251 235 / var(--tw-bg-opacity, 1))}.bg-amber-500\/10{background-color:#f59e0b1a}.bg-black{--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.bg-black\/50{background-color:#00000080}.bg-blue-500\/10{background-color:#3b82f61a}.bg-emerald-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.bg-emerald-500{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity, 1))}.bg-emerald-500\/10{background-color:#10b9811a}.bg-transparent{background-color:transparent}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-white\/20{background-color:#fff3}.bg-white\/80{background-color:#fffc}.bg-white\/90{background-color:#ffffffe6}.fill-amber-500{fill:#f59e0b}.fill-emerald-500{fill:#10b981}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0\.5{padding:.125rem}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-3\.5{padding:.875rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-1\.5{padding-left:.375rem;padding-right:.375rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-3\.5{padding-left:.875rem;padding-right:.875rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-1\.5{padding-top:.375rem;padding-bottom:.375rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-8{padding-top:2rem;padding-bottom:2rem}.pb-1{padding-bottom:.25rem}.pb-32{padding-bottom:8rem}.pl-2{padding-left:.5rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.text-center{text-align:center}.font-mono{font-family:GeistMono,JetBrains Mono,SFMono-Regular,Menlo,Monaco,Consolas,monospace}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-black{font-weight:900}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.italic{font-style:italic}.leading-relaxed{line-height:1.625}.tracking-tight{letter-spacing:-.025em}.text-\[\#171717\]{--tw-text-opacity: 1;color:rgb(23 23 23 / var(--tw-text-opacity, 1))}.text-\[\#333333\]{--tw-text-opacity: 1;color:rgb(51 51 51 / var(--tw-text-opacity, 1))}.text-\[\#444444\]{--tw-text-opacity: 1;color:rgb(68 68 68 / var(--tw-text-opacity, 1))}.text-\[\#555555\]{--tw-text-opacity: 1;color:rgb(85 85 85 / var(--tw-text-opacity, 1))}.text-\[\#666666\]{--tw-text-opacity: 1;color:rgb(102 102 102 / var(--tw-text-opacity, 1))}.text-\[\#777777\]{--tw-text-opacity: 1;color:rgb(119 119 119 / var(--tw-text-opacity, 1))}.text-\[\#888888\]{--tw-text-opacity: 1;color:rgb(136 136 136 / var(--tw-text-opacity, 1))}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-amber-500{--tw-text-opacity: 1;color:rgb(245 158 11 / var(--tw-text-opacity, 1))}.text-amber-600{--tw-text-opacity: 1;color:rgb(217 119 6 / var(--tw-text-opacity, 1))}.text-amber-700{--tw-text-opacity: 1;color:rgb(180 83 9 / var(--tw-text-opacity, 1))}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-blue-600{--tw-text-opacity: 1;color:rgb(37 99 235 / var(--tw-text-opacity, 1))}.text-emerald-500{--tw-text-opacity: 1;color:rgb(16 185 129 / var(--tw-text-opacity, 1))}.text-emerald-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity, 1))}.text-emerald-700{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.text-indigo-500{--tw-text-opacity: 1;color:rgb(99 102 241 / var(--tw-text-opacity, 1))}.text-purple-600{--tw-text-opacity: 1;color:rgb(147 51 234 / var(--tw-text-opacity, 1))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.underline{text-decoration-line:underline}.underline-offset-2{text-underline-offset:2px}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.placeholder-\[\#888888\]::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(136 136 136 / var(--tw-placeholder-opacity, 1))}.placeholder-\[\#888888\]::placeholder{--tw-placeholder-opacity: 1;color:rgb(136 136 136 / var(--tw-placeholder-opacity, 1))}.placeholder-\[\#999999\]::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(153 153 153 / var(--tw-placeholder-opacity, 1))}.placeholder-\[\#999999\]::placeholder{--tw-placeholder-opacity: 1;color:rgb(153 153 153 / var(--tw-placeholder-opacity, 1))}.accent-emerald-500{accent-color:#10b981}.shadow-2xl{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-inner{--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / .05);--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),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)}.backdrop-blur{--tw-backdrop-blur: blur(8px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-md{--tw-backdrop-blur: blur(12px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur: blur(4px);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-shadow{transition-property:box-shadow;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-150{transition-duration:.15s}.duration-300{transition-duration:.3s}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--scrollbar-thumb);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--scrollbar-thumb-hover)}.bg-grid{background-size:32px 32px;background-image:linear-gradient(to right,var(--grid-line) 1px,transparent 1px),linear-gradient(to bottom,var(--grid-line) 1px,transparent 1px)}input[type=range]{accent-color:currentColor}.btn-vercel{transition:all .15s ease}.btn-vercel:active{transform:scale(.98)}.selection\:bg-\[\#eaeaea\] *::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.selection\:bg-\[\#eaeaea\] *::selection{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.selection\:text-\[\#000000\] *::-moz-selection{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.selection\:text-\[\#000000\] *::selection{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.selection\:bg-\[\#eaeaea\]::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.selection\:bg-\[\#eaeaea\]::selection{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.selection\:text-\[\#000000\]::-moz-selection{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.selection\:text-\[\#000000\]::selection{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.hover\:scale-105:hover{--tw-scale-x: 1.05;--tw-scale-y: 1.05;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.hover\:border-\[\#cccccc\]:hover{--tw-border-opacity: 1;border-color:rgb(204 204 204 / var(--tw-border-opacity, 1))}.hover\:border-black:hover{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity, 1))}.hover\:border-emerald-500\/40:hover{border-color:#10b98166}.hover\:bg-\[\#262626\]:hover{--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#e8e8e8\]:hover{--tw-bg-opacity: 1;background-color:rgb(232 232 232 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#eaeaea\]:hover{--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#eeeeee\]:hover{--tw-bg-opacity: 1;background-color:rgb(238 238 238 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#f0f0f0\]:hover{--tw-bg-opacity: 1;background-color:rgb(240 240 240 / var(--tw-bg-opacity, 1))}.hover\:bg-\[\#f5f5f5\]:hover{--tw-bg-opacity: 1;background-color:rgb(245 245 245 / var(--tw-bg-opacity, 1))}.hover\:bg-blue-500\/20:hover{background-color:#3b82f633}.hover\:bg-emerald-50:hover{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity, 1))}.hover\:text-\[\#171717\]:hover{--tw-text-opacity: 1;color:rgb(23 23 23 / var(--tw-text-opacity, 1))}.hover\:text-black:hover{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.hover\:text-blue-700:hover{--tw-text-opacity: 1;color:rgb(29 78 216 / var(--tw-text-opacity, 1))}.hover\:text-emerald-700:hover{--tw-text-opacity: 1;color:rgb(4 120 87 / var(--tw-text-opacity, 1))}.hover\:text-red-500:hover{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-2xl:hover{--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.focus\:border-black:focus{--tw-border-opacity: 1;border-color:rgb(0 0 0 / var(--tw-border-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.disabled\:opacity-20:disabled{opacity:.2}.disabled\:opacity-50:disabled{opacity:.5}.dark\:border-\[\#1a1a1a\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(26 26 26 / var(--tw-border-opacity, 1))}.dark\:border-\[\#1c1c1c\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(28 28 28 / var(--tw-border-opacity, 1))}.dark\:border-\[\#222222\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(34 34 34 / var(--tw-border-opacity, 1))}.dark\:border-\[\#262626\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(38 38 38 / var(--tw-border-opacity, 1))}.dark\:border-\[\#2a2a2a\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(42 42 42 / var(--tw-border-opacity, 1))}.dark\:border-\[\#333333\]:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 51 51 / var(--tw-border-opacity, 1))}.dark\:border-amber-500\/20:is(.dark *){border-color:#f59e0b33}.dark\:border-emerald-500\/20:is(.dark *){border-color:#10b98133}.dark\:bg-\[\#000000\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#000000\]\/80:is(.dark *){background-color:#000c}.dark\:bg-\[\#00e676\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 230 118 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#050505\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(5 5 5 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#0a0a0a\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(10 10 10 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#0d0d0d\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(13 13 13 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#0f0f0f\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(15 15 15 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#111111\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(17 17 17 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#111111\]\/90:is(.dark *){background-color:#111111e6}.dark\:bg-\[\#141414\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(20 20 20 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#161616\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(22 22 22 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#181818\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(24 24 24 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#181818\]\/90:is(.dark *){background-color:#181818e6}.dark\:bg-\[\#1a1a1a\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#1f1f1f\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(31 31 31 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#222222\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#242424\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(36 36 36 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#262626\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(38 38 38 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#2b2b2b\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(43 43 43 / var(--tw-bg-opacity, 1))}.dark\:bg-\[\#333333\]:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:bg-amber-500\/10:is(.dark *){background-color:#f59e0b1a}.dark\:bg-black:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(0 0 0 / var(--tw-bg-opacity, 1))}.dark\:bg-black\/70:is(.dark *){background-color:#000000b3}.dark\:bg-emerald-500\/10:is(.dark *){background-color:#10b9811a}.dark\:bg-white:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.dark\:text-\[\#00e676\]:is(.dark *){--tw-text-opacity: 1;color:rgb(0 230 118 / var(--tw-text-opacity, 1))}.dark\:text-\[\#555555\]:is(.dark *){--tw-text-opacity: 1;color:rgb(85 85 85 / var(--tw-text-opacity, 1))}.dark\:text-\[\#666666\]:is(.dark *){--tw-text-opacity: 1;color:rgb(102 102 102 / var(--tw-text-opacity, 1))}.dark\:text-\[\#888888\]:is(.dark *){--tw-text-opacity: 1;color:rgb(136 136 136 / var(--tw-text-opacity, 1))}.dark\:text-\[\#aaaaaa\]:is(.dark *){--tw-text-opacity: 1;color:rgb(170 170 170 / var(--tw-text-opacity, 1))}.dark\:text-\[\#cccccc\]:is(.dark *){--tw-text-opacity: 1;color:rgb(204 204 204 / var(--tw-text-opacity, 1))}.dark\:text-\[\#ededed\]:is(.dark *){--tw-text-opacity: 1;color:rgb(237 237 237 / var(--tw-text-opacity, 1))}.dark\:text-amber-400:is(.dark *){--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.dark\:text-black:is(.dark *){--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity, 1))}.dark\:text-blue-400:is(.dark *){--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.dark\:text-emerald-400:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:text-indigo-400:is(.dark *){--tw-text-opacity: 1;color:rgb(129 140 248 / var(--tw-text-opacity, 1))}.dark\:text-purple-400:is(.dark *){--tw-text-opacity: 1;color:rgb(192 132 252 / var(--tw-text-opacity, 1))}.dark\:text-white:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:placeholder-\[\#444444\]:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(68 68 68 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-\[\#444444\]:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(68 68 68 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-\[\#555555\]:is(.dark *)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(85 85 85 / var(--tw-placeholder-opacity, 1))}.dark\:placeholder-\[\#555555\]:is(.dark *)::placeholder{--tw-placeholder-opacity: 1;color:rgb(85 85 85 / var(--tw-placeholder-opacity, 1))}.dark\:shadow-2xl:is(.dark *){--tw-shadow: 0 25px 50px -12px rgb(0 0 0 / .25);--tw-shadow-colored: 0 25px 50px -12px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.dark\:selection\:bg-\[\#333333\] *:is(.dark *)::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:selection\:bg-\[\#333333\] *:is(.dark *)::selection{--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:selection\:text-\[\#ffffff\] *:is(.dark *)::-moz-selection{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:selection\:text-\[\#ffffff\] *:is(.dark *)::selection{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:selection\:bg-\[\#333333\]:is(.dark *)::-moz-selection{--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:selection\:bg-\[\#333333\]:is(.dark *)::selection{--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:selection\:text-\[\#ffffff\]:is(.dark *)::-moz-selection{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:selection\:text-\[\#ffffff\]:is(.dark *)::selection{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:hover\:border-\[\#333333\]:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(51 51 51 / var(--tw-border-opacity, 1))}.dark\:hover\:border-\[\#555555\]:hover:is(.dark *){--tw-border-opacity: 1;border-color:rgb(85 85 85 / var(--tw-border-opacity, 1))}.dark\:hover\:bg-\[\#141414\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(20 20 20 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#161616\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(22 22 22 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#1a1a1a\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(26 26 26 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#1c1c1c\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(28 28 28 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#1f1f1f\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(31 31 31 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#202020\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(32 32 32 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#222222\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(34 34 34 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#333333\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(51 51 51 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-\[\#eaeaea\]:hover:is(.dark *){--tw-bg-opacity: 1;background-color:rgb(234 234 234 / var(--tw-bg-opacity, 1))}.dark\:hover\:bg-emerald-500\/20:hover:is(.dark *){background-color:#10b98133}.dark\:hover\:text-\[\#cccccc\]:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(204 204 204 / var(--tw-text-opacity, 1))}.dark\:hover\:text-blue-300:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(147 197 253 / var(--tw-text-opacity, 1))}.dark\:hover\:text-emerald-400:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity, 1))}.dark\:hover\:text-white:hover:is(.dark *){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.dark\:focus\:border-\[\#444444\]:focus:is(.dark *){--tw-border-opacity: 1;border-color:rgb(68 68 68 / var(--tw-border-opacity, 1))}@media(min-width:640px){.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:self-auto{align-self:auto}}@media(min-width:768px){.md\:inline{display:inline}}