commit 8cdbfee08ffc67bdd689e6738c0fe0ae9ea9e582 Author: 谢宇宁 Date: Tue Oct 27 16:23:48 2026 +0700 Initial commit Co-Authored-By: Claude Opus 5.5 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..51c4c8f --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +# Legacy development-only .env.local example. Prefer config/development.local.env. +# Development uses this configured backend; browsers call the current site's /api/*. +# Test uses this same backend; production uses http://172.104.56.28:26125. +UPSTREAM_API_URL=http://172.104.181.99:21022 +# API_BASE_URL remains supported for existing local overrides. +# Optional channel code passed when creating a guest account. +API_CHANNEL= +# Public configuration/navigation: 60s; homepage: 30s. Optional cap in milliseconds; 0 disables. +# PUBLIC_DATA_CACHE_TTL_MS=60000 + +# Analytics application ID. Must match this site's reportSdkConfig and upstream app ID. +NEXT_PUBLIC_TRACKER_APP_ID=1047 +# Optional same-project encrypted config fallback when config/info omits reportSdkConfig. +NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f170b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +node_modules/ +.next/ +.next-dev/ +.next-test/ +out/ +dist/ +dist.zip +dist.zip.sha256 +web_91dj_h5.tar.gz +web_91dj_h5.tar.gz.sha256 +web_91dj_h5.md5 +*.tsbuildinfo +.DS_Store +.env* +!.env.example +config/*.local.env +.openai/.local/ diff --git a/.openai/hosting.json b/.openai/hosting.json new file mode 100644 index 0000000..b3f3c99 --- /dev/null +++ b/.openai/hosting.json @@ -0,0 +1,6 @@ +{ + "project_id": "appgprj_6ad2273b8598819198e0c9f1959d5cfa", + "static": { + "directory": "out" + } +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..af3b7c8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,9 @@ + + +## This is NOT the Next.js you know + +This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices. + +This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..6b45d1c --- /dev/null +++ b/README.md @@ -0,0 +1,281 @@ +# 91短剧 · Next.js + +Next.js App Router、React、TypeScript。保留 HTML 原稿布局和移动端导航,业务数据通过 Swagger API 动态获取。 + +## 运行 + +已使用 Node.js **22.17.1** 验证;最低要求 20.9。 + +```sh +nvm use 22.17.1 +npm install +npm run dev +``` + +开发、测试和生产启动的默认地址均为 http://localhost:21021,端口或环境变量修改后需要重启 Next.js。 + +```sh +npm run typecheck +npm run build +npm test +npm start +``` + +现在需要运行 Next.js 服务以提供 API 代理与动态首页,不再导出静态 `out/`。旧的静态预览进程需要停止后用新命令启动。 + +## 环境配置 + +本项目使用 `config/*.env` 区分业务环境,启动脚本在运行 Next.js 前加载配置。 + +| 环境 | 配置文件 | 构建命令 | 启动命令 | 构建目录 | +| --- | --- | --- | --- | --- | +| 开发 | `config/development.env` | `npm run build:dev` | `npm run dev`;构建后可用 `npm run start:dev` | `.next-dev/` | +| 测试 | `config/test.env` | `npm run build:test` | `npm run start:test` | `.next-test/` | +| 生产 | `config/production.env` | `npm run build` | `npm start` | `.next/` | + +测试环境按生产模式构建、启动,使用 `APP_ENV=test` 和 `NODE_ENV=production`;`npm run dev:test` 则使用测试配置启动开发服务器。业务环境通过 `APP_ENV` 区分,不把测试部署误设为单元测试使用的 `NODE_ENV=test`。 + +浏览器始终访问当前站点的相对地址 `/api/*`,Next.js 服务端通过 `UPSTREAM_API_URL` 请求 Go 的 `/api/app/*`。开发、测试环境使用 `http://172.104.181.99:21022`,生产环境使用 `http://172.104.56.28:26125`。上游地址仅由服务端读取。默认应用端口均为 `21021`。公开接口缓存和 SEO 快照按解析后的上游域名隔离。修改服务器配置时,在对应目录新增 `config/test.local.env` 或 `config/production.local.env`;这些本地文件已忽略提交。优先级为:进程环境变量 > 对应 `*.local.env` > 对应基础配置。开发环境兼容旧 `.env.local`,其优先级低于 `config/development.local.env`;测试和生产不从旧 `.env.local` 加载本站业务配置。 + +原有 `API_BASE_URL` 仍作为兼容别名。在同一配置层内 `UPSTREAM_API_URL` 优先;较高配置层的旧变量仍可覆盖较低层的新变量,启动后统一为同一个上游地址。上游地址仅在服务端读取,不写入浏览器代码。 + +例如需要测试服务同时运行在另一端口,可在 `config/test.local.env` 写入: + +```dotenv +PORT=21022 +``` + +也可以临时运行 `npm run start:test -- --port 21022`。不同环境使用各自的构建产物及 SEO 快照缓存;先执行对应环境的构建命令,再执行其启动命令。`UPSTREAM_API_URL` 等服务端配置在启动时读取,修改后重启即可;`NEXT_PUBLIC_*` 在构建时写入浏览器代码,修改后应重新构建。 + +开发环境默认关闭公共接口缓存,测试和生产保留原有缓存上限;可用 `PUBLIC_DATA_CACHE_TTL_MS` 覆盖。`npm run check` 会先完成生产构建(含类型检查),再运行完整本地回归测试。 + +## 打包与部署 + +使用 Node.js 22.17.1;生产跨平台打包需要 npm >=10.9.2。两份脚本均先构建、运行回归测试,再整理 Next.js standalone、静态资源和对应环境配置。 + +```sh +# 预览流程,不构建、不连接服务器 +bash deploy_test.sh --dry-run +bash deploy_prod.sh --dry-run + +# 只生成本地压缩包,不上传 +bash deploy_test.sh --package-only +bash deploy_prod.sh --package-only +``` + +测试产物为根目录的 `web_91dj_h5.tar.gz` 和 `web_91dj_h5.tar.gz.sha256`,上传相同文件及 `up_next_h5.sh`;同时刷新同名 `web_91dj_h5.md5` 供本地兼容校验。包内直接包含 `start.mjs`、`server.js`、`.next-test/`、`public/`、`config/test.env` 和 lockfile。测试包不带本机 `node_modules`,使用 `tar -xzf web_91dj_h5.tar.gz` 解压后,在 Linux 执行 `npm ci --omit=dev --no-audit --no-fund`,再执行 `node start.mjs`;默认端口为 21021。可通过 `DEPLOY_PACKAGE_NAME=自定义名称.tar.gz` 覆盖包名,校验文件随之生成。测试和生产使用同名压缩包,根目录文件对应最后一次执行的环境。 + +生产产物为 `web_91dj_h5.tar.gz` 和纯哈希格式的 `web_91dj_h5.md5`,包含 `.next/` 和 standalone 裁剪依赖,默认适配 Linux x64/glibc,可直接执行 `node start.mjs`。可通过 `DEPLOY_TARGET_ARCH=arm64`、`DEPLOY_TARGET_LIBC=musl` 选择目标架构和 libc。打包时仅从 lockfile 安装对应 Linux Sharp/libvips,并校验 ELF 架构,不携带 macOS 原生模块。 + +两种包仅包含对应的基础 `config/*.env`,不打包 `.env*`、`*.local.env` 和 SEO 缓存。服务器的私有覆盖配置放在发布目录下对应的 `config/test.local.env` / `config/production.local.env`;修改 `NEXT_PUBLIC_*` 必须重新构建。本站媒体密钥由上游接口代理提供,无需额外的本地 `m3u8.key` 文件。 + +部署默认值由两份脚本开头的变量定义,并支持环境变量覆盖;当前上传配置如下。本项目包名和 Supervisor 名使用 `91dj`: + +| 环境 | SSH 目标 | 上传目录 | SCP / SSH 端口 | +| --- | --- | --- | --- | +| 测试 | `root@172.104.181.99` | `/server/web_91dj/h5` | `22889 / 22889` | +| 生产 | `server@34.92.204.175` | `/home/server/web_91dj/h5` | `22889 / 22889` | + +直接执行脚本会使用上述地址;仅打包继续使用 `--package-only`。需要其他目标时可覆盖: + +```sh +DEPLOY_HOST=user@test-host DEPLOY_DIR=/srv/91dj/h5 DEPLOY_RUN_DIR=/srv/91dj/h5 bash deploy_test.sh +DEPLOY_JUMP_HOST=user@jump-host DEPLOY_JUMP_DIR=/srv/91dj/h5 bash deploy_prod.sh +``` + +测试当前 SSH 端口为 22889,可通过 `DEPLOY_SSH_PORT` 修改;它与 `config/test.env` 中的应用监听端口 `PORT` 分别配置。`deploy_test.sh` 上传成功后执行 `ssh -p 22889 root@172.104.181.99 "cd /server/web_91dj/h5 && bash run.sh"`,由服务器现有的 `run.sh` 负责后续更新。上传目录和执行目录当前均为 `/server/web_91dj/h5`,分别可用 `DEPLOY_DIR`、`DEPLOY_RUN_DIR` 覆盖;脚本不上传或覆盖远端 `run.sh`。部署测试从脚本帮助信息读取当前默认执行目录,并单独验证目录覆盖及上传、执行失败时的退出行为,修改服务器目录无需同步修改测试断言。上传失败不会执行远端更新,远端命令失败会立即退出;`--package-only` 和 `--dry-run` 均不执行 SSH。可通过 `DEPLOY_CHECK_URL` 在远端命令成功后增加公网健康检查。 + +随包上传的 `up_next_h5.sh` 保留供服务器按需调用:该脚本校验 SHA-256、检查并解压 tar.gz、安装运行依赖、保留本地覆盖与 SEO 缓存,然后切换版本、启动服务并检查 `/healthz` 与首页,失败自动恢复旧版本。默认 Supervisor 程序为 `91dj_next_h5`,在服务器运行该脚本时可通过 `DEPLOY_PROGRAM` 覆盖;使用自定义包名时需设置对应的 `DEPLOY_PACKAGE_NAME`。服务器 `run.sh` 的解包、服务管理及包名规则以该脚本自身实现为准。 + +生产脚本将 `web_91dj_h5.tar.gz` 和 `web_91dj_h5.md5` 上传到 `server@34.92.204.175:/home/server/web_91dj/h5/`,SCP 与 SSH 默认端口均为 `22889`。上传成功后执行 `ssh -p 22889 server@34.92.204.175 "cd /home/server/web_91dj/h5 && bash rsupdate.sh"`。服务器现有的 `rsupdate.sh` 负责解包、保留生产私有配置和重启服务;本项目不上传或覆盖该脚本。上传失败不会触发远端更新,SSH 失败或返回失败状态会停止发布。可通过 `DEPLOY_JUMP_HOST`、`DEPLOY_JUMP_DIR`、`DEPLOY_SCP_PORT`、`DEPLOY_SSH_PORT` 覆盖目标,执行目录同步跟随上传目录。 + +生产 SSH 的远端命令文本与上述手动命令保持一致:通过路径校验后,`cd` 的目录不额外添加单引号,以兼容按原始命令文本匹配的 SSH 白名单。dry-run 与实际执行共用同一个命令,运行时会打印命令并保留远端日志。远端即使打印“发布成功”,只要此前输出含 `ERROR`(如 `curl` 报 HTTP 401)、`FATAL` 等失败状态,本地仍会报错;HTTP 401 的具体检查地址需核对服务器现有 `rsupdate.sh`,应用自身提供无需登录的 `/healthz`。 + +`npm run bundle:test` / `npm run bundle` 只整理已有构建;`npm run bundle:linux -- x64 glibc` 为生产产物准备 Linux 原生依赖。同一仓库的测试、生产脚本共用打包锁,压缩包及 `dist/` 已忽略提交。`/healthz` 是不触发游客登录、禁止缓存和索引的部署探针,不依赖上游接口。 + +## 页面路由 + +首页使用站点根地址,例如 `http://127.0.0.1:21021`,也支持 `http://127.0.0.1:21021/`;两者都直接显示首页。浏览器把纯域名的请求路径解析为 `/`,不需要跳转。短剧列表单独使用 `/shorts/`。 + +其他页面统一使用以 `/` 结尾的路径路由,可直接打开、刷新、在新标签页打开,并使用浏览器前进和后退。链接生成时就包含尾斜杠,不通过跳转或地址替换补齐。 + +- 首页 `/`;短剧 `/shorts/`;专题 `/topics/`;热播榜 `/rank/hot/`、新剧榜 `/rank/new/`、追剧榜 `/rank/follow/`;应用 `/apps/`;回家的路 `/home-road/`。榜单 tab 使用页面链接,数据类型由路径决定;原 `/rank/` 仍可直接访问热播榜,不重定向。 +- 官方入口 `/navigation/`:独立展示线路、官方渠道与 App 下载,不加载首页导航、播放器或账户交互;首页和业务页面不再弹出入口导航页。 +- 专题、题材、标签、栏目:`/topics/:id/`、`/theme/:id/`、`/tag/:id/`、`/config/:id/`。 +- 详情 `/detail/:id/`;章节播放 `/play/:id/:chapterId/`。 +- 内容路由优先使用接口返回的英文或拼音 `slug`(字母、数字、连字符、下划线),中文或无效 slug 回退到 ID,例如 `/detail/city-story/`、`/play/city-story/90001/`、`/topics/urban/`、`/tag/urban/`、`/config/editors-choice/`。章节段仍是章节 ID。卡片、轮播、选集、评论/列表分页、解锁后跳转和 SEO 地址使用同一规则;原 ID 地址继续直接访问。有配置的 `moreUrl` 优先级不变。 +- 搜索 `/search/关键词/`;分页 `/search/关键词/page/2/`。关键词通过 `encodeURIComponent` 作为单个路径段编码,支持中文和特殊字符。 +- 独立导航页 `/navigation/` 从 `config/info` 读取站名、介绍、`navPageList`、永久入口、TG/X 和 App 下载地址。线路按 `line` 排序;旧配置兼容 `dhNewUrlAddr1/2/3` 与最新地址字段。未配置或无效的链接不展示,不使用演示账号或首页链接兜底。 +- 个人中心 `/member/center/`,收藏、历史、点赞、已购和设置分别为 `/member/favorites/`、`/member/history/`、`/member/likes/`、`/member/purchased/`、`/member/settings/`。 +- 帮助与平台信息 `/info/:slug/`;充值 `/info/deposit/`;VIP `/info/deposit/vip/`。 +- 所有分页使用独立路径,例如 `/shorts/page/2/`、`/rank/new/page/2/`、`/topics/31/page/2/`、`/member/history/page/2/`、`/info/deposit/vip/page/2/`;详情和播放页的 `/page/2/` 对应评论第 2 页。页码、上一页和下一页均为真实链接,可直接访问、刷新和前进后退。 +- 排序通过 `?sort=2` 保留;评论排序使用 `?commentSort=1`。切换排序时链接指向第一页,翻页时保留当前条件;第一页链接使用原页面路径,不执行自动跳转或地址替换。 + +页面直接根据 URL 路径加载,不解析或转换旧 hash 路由。站内导航、动态卡片、播放选集、搜索和会员入口均使用路径地址;无效路径返回 404。 + +页面地址统一使用尾斜杠。`src/proxy.ts` 对有效页面的无尾斜杠地址返回 `308` 永久重定向,例如 `/shorts` → `/shorts/`,分页、搜索关键词和查询参数完整保留;首页 `/` 直接响应。Next.js 保留 `trailingSlash: true` 和 `skipTrailingSlashRedirect: true`,由 Proxy 仅处理页面,API、播放列表、密钥、静态资源地址不受影响;无效路径直接返回 404。 + +## 接口配置 + +顶部、侧栏和移动导航由 `navigation/header.categoryList` 在服务端渲染,按接口顺序显示名称、地址和二级导航。URL 导航与二级导航显示在主导航,底部类型不混入;成功返回空列表时不注入固定菜单。`/hot` 直接生成本站 `/rank/hot/` 链接;已知系统入口的空地址按本站路由生成。导航公共数据缓存 60 秒,后台更新在缓存过期后的页面请求生效;页面高亮仍按当前路由判断。接口图标优先,缺失时沿用本站图标;外链添加 nofollow 等属性。 + +2026-10-21 已按当前 91av Swagger 重新核对各页面,具体映射和接口缺项见 [页面接口对照](docs/api-contract.md)。短剧列表使用 `playlet/all/list`;专题采用顶层详情字段;首页未配置 `moreUrl` 的栏目使用首页真实数据兜底。按用户要求移除独立剧集入口及 `/series/`、`/video/:id/` 路由,旧地址直接返回 404。 + +页面 head 由本项目(`JHA-1047`)的 `src/lib/seo-server.ts` 和 `src/lib/seo.ts` 生成:全站名称与首页 TDK 取 `config/info.seoInfo`,栏目 TDK 优先使用 `translation/category/list` 的导航配置,具体专题、剧集使用对应接口信息。兼容字符串形式的 SEO 配置及 `keyworld` 等旧字段。`/navigation/` 使用 `seoNavInfo`。 + +canonical 的域名优先取 `config.foreverUrlAddr`,其次取 `newAddressInfo.foreverUrlAddr`,只使用配置中的永久域名,不回退到请求 Host、API 或 CDN 域名。配置沿用 60 秒公共缓存,成功取得有效永久域名后,将公开 SEO 信息保存到 `.next/cache/seo/`,按 API 地址隔离;接口超时、报错、配置或永久域名暂缺时沿用最近一次有效值,服务重启也可读取已有快照。快照不包含 SDK 配置、账号或凭证;部署时需保留该缓存目录,完全没有有效配置/快照时不输出猜测的 canonical 和站点 JSON-LD。后台更新为有效永久域名后,缓存过期后的新页面请求采用新配置;同一浏览器文档沿用服务端给出的 canonical、site_name 和发布者身份。 + +canonical 不带查询参数,分页各自生成规范地址和有边界的 `prev/next`,第 1 页使用栏目地址。搜索、账户、充值和工具页按参考项目输出 `noindex`。服务端将 TDK、robots、Open Graph、Twitter 和图标信息直接写入 ``,每次页面请求都返回对应页面的完整 head。本次未新增统计脚本。 + +`/robots.txt` 由 `src/app/robots.ts` 提供纯文本响应,不依赖账户会话或请求 Host。生产环境(`APP_ENV=production`)允许页面和 JS/CSS、图片、播放资源抓取,仅限制 `/api/` 和 `/healthz`;开发、测试环境禁止全站抓取。搜索、账户、充值、工具和重复页面继续使用页面级 `noindex`,保留爬虫读取这些标记的权限。生产环境声明 `Sitemap: https://永久域名/sitemap.xml`,域名与 canonical 共用配置及最近一次有效快照;完全没有有效域名时仅省略 Sitemap 行,抓取规则继续返回。开发、测试环境不声明 Sitemap。本项只添加声明,XML 文件仍由后续 sitemap 服务提供。 + +公开正文与 SEO 使用同一次服务端取数:短剧、三个榜单、专题列表/详情、题材/标签、首页更多栏目、短剧详情、播放页及各自分页直接输出当前页的标题、简介、内容卡片、集数和已有评论,分页与免费集数保留原生链接。搜索结果也直接输出首屏正文,继续保持 `noindex`。详情/播放和栏目模板在服务端与站内导航间共用,沿用现有 HTML 样式。 + +请求与渲染方式:服务端先完成当前页面取数,再返回页头和完整正文;福利应用、回家的路、反馈、个人中心、收藏/历史/点赞/已购、设置及充值/VIP 页面也直接输出内容。链接和搜索使用浏览器原生页面请求,旧页面保留到新响应到达,不清空正文或插入页面加载占位。浏览器复用首屏快照绑定轮播、播放、表单和支付等交互,不再重复获取整页数据;提交成功后重新请求当前页面。 + +公共配置、导航、分类、页脚和标签选项使用 60 秒进程内缓存,首页使用 30 秒;同一上游地址及查询参数的并发请求合并,返回独立数据副本,缓存有数量上限。配置及首页刷新失败可沿用已成功取得的数据;错误和无效响应不写入缓存。账户、余额、套餐及付费观看权限不使用公共缓存,HTML 继续保持私有响应。可用 `PUBLIC_DATA_CACHE_TTL_MS` 缩短公共缓存时间,设为 `0` 可关闭;多实例各自维护缓存。 + +详情、章节、选集、评论和推荐按 URL 中的 ID 并行请求。浏览器复用首屏中的公开配置和页脚数据,下载与广告交互不再等待重复取数;SDK 配置仍单独读取,不写入 HTML。支持浏览器前进后退缓存时,公开页保留 DOM、滚动位置和交互,并在后台恢复当前会话和页面埋点;账户、播放及打开弹窗的页面仍重新请求,防止恢复已处置的支付/权限状态。 + +有效业务页面在 Proxy 中恢复账号或建立游客会话,将同一身份的 Cookie 传给本次服务端渲染并写回浏览器。客户端复用已准备的用户信息,避免首屏再次初始化游客。正式账号过期显示登录入口,接口异常直接返回重试状态;账户页面保持 `private, no-store` 和 `noindex`。首屏账号、账户列表、套餐、流水和播放器数据均按字段白名单传递,未授权付费视频源、服务端账号凭证及 SDK 私密配置不写入 HTML;点击金币集数仍读取实时权限、价格并由用户确认解锁。当前 Next.js 动态参数在路由入口只解码一次,中文和含空格、斜杠、百分号的搜索词与浏览器保持一致。 + +JSON-LD 参考 `huangguo.com` 的品牌节点及参考项目 `seoser/jsonld.go`:收录页输出 `Organization`、`WebSite` 和对应 `WebPage` / `CollectionPage`,列表输出实际内容的 `ItemList`,详情使用 `TVSeries`,播放页关联 `TVEpisode` 与具备真实封面、发布时间的 `VideoObject`。关于页使用 `AboutPage`,常见问题直接从页面可见问答生成 `FAQPage`。URL、站名与分页序号沿用现有动态 SEO 配置;不使用参考站的品牌、域名、伪造日期或评分。每份页面响应包含本页的原生 JSON-LD script,不收录页不输出旧页面的数据。测试覆盖脚本安全转义和重复节点清理。 + +- 文档:https://91av-wz.80sygtm.com/swagger/index.html +- `UPSTREAM_API_URL`:开发、测试环境为 `http://172.104.181.99:21022`,生产环境为 `http://172.104.56.28:26125`;兼容旧 `API_BASE_URL`。 +- `API_CHANNEL`:可选游客、登录和绑定的默认渠道码;已有首次渠道时优先保留。 +- 项目编号:`JHA-1047`。接口读取与业务处理位于 `src/lib/api/server.ts`、`src/lib/client/config.ts`、`src/lib/client/channel.ts`、`src/lib/client/auth.ts` 和 `src/lib/client/account.ts`。 + +浏览器访问同源 `/api/*`,由 Next.js 按白名单代理到配置上游的 `/api/app/*`。Nginx 已有的 `/api/app/* → Go` 规则可以保留,其他 `/api/*` 交给 Next.js;不能将整个 `/api/` 都交给 Go。旧 Next.js `/api/app/*` 处理器仍保留供本地兼容访问。首页由服务端渲染,公开首页数据缓存 30 秒。公共接口不携带用户 token;用户接口使用 HttpOnly、SameSite Cookie。token 和密码不会写入浏览器本地存储或返回给前端脚本。 + +若显式配置为 `current`,则使用当前请求域名(默认配置不使用此模式)。该模式依赖 Nginx 将 `/api/app/*` 及 `/h5/*`、`/enkey`、`/newenkey` 等后端媒体路径转发到 Go,本站其他页面和 `/api/*` 由 Next.js 处理。上游请求若被再次转回 Next.js,会立即返回 `502` 和转发配置提示,避免递归请求。修改接口模式后重启服务即可,无须将当前域名写入前端构建产物。 + +请求处理参考 `jha-1045-hgdjnet-web`:Next.js 接口代理不根据 `Origin`、`Host` 或 `Sec-Fetch-Site` 拦截 POST,避免 CloudFront / Nginx 转发后域名或协议变化导致正常浏览器请求返回 403。浏览器继续请求当前站点的 `/api/*`,代理负责接口白名单、参数处理和会话传递,业务权限由上游校验。 + +## 登录与游客绑定 + +登录流程由本项目(`JHA-1047`)的 `src/lib/client/auth.ts` 和 `src/app/api/app/[...path]/route.ts` 实现,保留本站登录弹窗。 + +- 首次访问自动建立游客会话;已有 Cookie 时先读取 `auth/getInfo` 恢复身份、昵称和余额,接口未返回新 token 时继续使用原 token。 +- 游客、登录和绑定使用同一持久设备标识,发送 `devID` 和渠道;首次渠道取 `dc/ch/pc`,后续访问保持不变。绑定支持 `inviteCode/invite/code` 邀请参数。服务端按参考项目发送设备头,并透传有效的客户端 IP。 +- 绑定携带当前游客凭证以保留其数据;前端填写账号、密码和确认密码并同意协议,无需邮箱。上游要求的缺省邮箱由服务端生成为 `账号@91dj.local`;实际已绑定邮箱仍用于找回密码。 +- 绑定账号为 6–20 位字母或数字,密码为 8–64 位且包含字母和数字;登录兼容旧账号的 4 位密码及绑定允许的较长账号、密码,支持邮箱登录。 +- 登录失败保留原身份;成功后更新账户并重新读取当前页面。自动游客、登录和退出响应串行处理,旧请求的失效结果不会清除客户端刚登录的身份。 +- 游客请求明确返回会话失效时重建游客并重试一次;正式账号失效会清理会话并提示登录。登录、注册、找回密码自身的错误不触发游客恢复。 +- 退出清理会话,并跳过紧接着一次页面初始化的自动游客登录。当前页面保持未登录,主动登录或绑定可恢复使用。 +- 表单提交期间禁用重复提交;支持显示/隐藏密码,关闭和成功后均清除密码值。仅设备与渠道写本地存储,凭证保留在 HttpOnly Cookie。 + +## 已接入 + +- 首页轮播、公告、后台配置栏目、广告、题材及视频区块。 +- 短剧运营分类、剧集视频列表、榜单、专题、题材/标签、分页和更多列表。 +- 搜索、搜索联想、热搜、猜你喜欢;搜索记录单独保存在当前浏览器。 +- 短剧详情、章节列表、章节权限和实际播放地址;HLS 播放、媒体代理和播放上报。 +- 游客会话、登录、注册/游客绑定、找回密码、资料与头像更新。 +- 个人中心统计、收藏、历史、点赞、已购列表;点赞、收藏、评论、回复、评论点赞。 +- 金币/VIP 套餐、支付方式、订单创建、余额查询和逐集解锁。 +- 应用列表、下载配置、固定平台说明页、反馈提交。 + +首页“推荐 / 最新 / 热门 / 随机”筛选项保持移除。栏目排序、列表数据、总数与观看权限由接口决定,失败时显示错误或空状态。 + +首页动态栏目沿用 HTML 的模块间距(桌面 48px、移动端 32px)与卡片网格,首个栏目不再复用原稿图文混排区域的选择器。首页封面统一为 3:4,图片居中裁切,避免接口方图、竖图混排时卡片高度不齐,并在图片解密加载前预留封面高度。 + +全站以 `91短剧-2组 (3).html` 的页面结构和样式为基准:短剧、剧集、专题、题材/标签、更多列表和搜索页沿用原稿网格、标题、筛选栏和封面框;排行榜使用原稿数字角标、五列布局及窄屏断点,排名按接口分页顺序连续计算,榜单 tab 保持独立路由。搜索关键词只在已转义的标题中高亮。 + +专题列表与专题详情按 HTML 直接展示卡片和分页,不增加“最新专题 / 热门专题”或“编辑推荐 / 最新 / 最热”等分类选项。 + +搜索结果由本项目(`JHA-1047`)的 `src/lib/client/search.ts` 和 `src/lib/seo-server.ts` 实现:服务端准备会话后调用 `playlet/search`,传入 `keyword/pageNum/pageSize`,默认每页 25 条;展示接口总数、关键词和实际 `pageSize`,剧名优先使用 `playName`,不再本地筛选接口结果。无结果优先展示 `everyoneWatching`,缺少推荐时请求 `playlet/hot/list`;推荐不计入搜索数量。`/search/` 可直接进入,空关键词跳过搜索接口,翻页使用 `/search/{关键词}/page/{页码}/`,搜索框、热搜、搜索历史和联想词入口均直接生成关键词路径,不通过重定向转换。标题高亮沿用 HTML 的 NFKC、大小写和空白处理;无结果/失败状态使用原稿插图,接口错误保留重试入口,不伪装为零结果。 + +个人中心、收藏、历史、点赞与已购页共用原稿的头像/金币余额卡片、资料入口、书架导航与卡片列表;个人中心默认展示真实观看历史,分页链接进入历史页。设置页恢复头像编辑、昵称保存和账号安全卡片,资料、头像、密码仍通过原有接口保存;登录/绑定弹窗恢复原稿输入框、眼睛图标与按钮层级。页脚与桌面导航采用新版 HTML 的排版。 + +“关于我们、用户协议、隐私政策、常见问题、2257、DMCA”六页固定使用 `src/lib/info-pages.ts` 中的原 HTML 文案与结构,不再请求富文本接口,底部入口也保持固定。每次访问均由服务端输出正文、标题和对应样式标记,无须等待首页或富文本接口。保留分节间距、折叠问答、编号段落和 `mailto:` 邮箱链接,反馈链接直接使用 `/info/feedback/`。 + +“关于我们”的“联系我们”沿用 HTML 分节和段落样式:商务合作读取 `config.telegram`(兼容 `telegramAdd`),用户反馈读取 `config.tgGroup`,空值再取 `newAddressInfo`;不使用邮箱或在线客服地址代替 TG。服务时间等附加说明读取 `config/footer/info` 的 `footerInfo.contactList`。这些内容随正文一起由服务端输出,未配置的项目不展示。 + +反馈页恢复原稿的问题选项、环境/设备面板、描述计数、上传区域和提交区。实际提交通过 `feedback/add`,环境信息随描述保存;图片通过现有 `upload/image` 上传,支持最多 3 张、每张 5MB 以内的 JPG/PNG/WebP,不展示接口尚未支持的视频上传。上传失败不清空表单,提交时阻止重复写入,离开页面后隔离迟到的结果。 + +详情页结构按原 HTML 调整:封面与资料卡、热度图标和标签、播放/下载与互动操作栏、三行简介展开/收起、选集横向分段和金币角标、官方链接、评论及嵌套回复。移动端复用原稿的封面、四列操作栏与五列选集布局;无下载配置时只显示播放入口,无推荐数据时不留空侧栏。内容和章节 ID 继续由接口提供,评论分页保持独立路由。 + +支付和解锁均由用户在页面确认后发起;创建订单不会直接把本地余额改为“支付成功”,余额通过用户信息接口刷新。 + +充值页按 `91短剧-2组 (3).html#info/deposit` 展示余额与金币用途卡片、套餐面板、推荐角标、选中标记、独立支付按钮和交易明细。先选择接口套餐,再点击“立即支付”打开确认弹窗;价格、赠送金币与流水收支读取真实字段,不使用原稿的模拟金额和交易记录。桌面端四列、移动端三列套餐,交易明细继续使用独立分页路由。 + +充值确认弹窗按原 HTML 展示订单摘要、赠送/合计金币、金额徽标、支付渠道图标与选中圆点、“去支付”按钮和确认说明。金币数读取 `baseCoin/giftCoin/totalCoin`,价格按接口分单位换算;仅展示套餐配置的支付方式。切换样式按钮会同步实际下单参数,提交期间禁止重复下单。金币和 VIP 下单成功后自动在新标签页打开支付地址;原页保留“打开支付页面”供再次打开同一订单,以及“我已完成支付,刷新余额”。点击去支付时先预留标签页以避免异步弹窗拦截,下单失败或离开页面时关闭等待页,浏览器禁止新窗口时自动在当前页继续支付。创建订单后仍需用户完成真实支付。 + +详情和播放页点击金币集数,直接打开原 HTML 样式的解锁弹窗,读取所选章节的最新权限、余额和价格;价格优先使用 `costCoins`,其次 `singleCoins`。确认后提交该集的 `chapterId/playletId`,成功后刷新余额并进入对应播放页;余额不足进入充值,会员章节进入会员套餐。读取期间禁止支付,支付期间禁止重复提交或切换目标;关闭弹窗、切换集数或离开页面会隔离迟到的读取结果。已允许观看的章节仍使用真实播放链接。 + +App 下载入口(顶部、悬浮图标、首页底部、详情页和 `/navigation/`)统一校验配置。配置返回前隐藏入口;没有有效的 HTTP(S) 下载链接时不展示。电脑端直接点击链接下载,已移除悬浮二维码面板和触发脚本;移动端保留点击下载弹窗,二维码只在实际点击时生成。 + +图片解密由本项目(`JHA-1047`)的 `src/lib/client/images.ts` 实现:使用相同密钥对前 100 字节做 XOR,识别 JPEG、PNG、GIF、WebP,并兼容未加密图片。封面、轮播、题材、广告、应用图标、用户/评论头像和视频 poster 统一处理;接口原图放在 `data-src` / `data-poster`,完成解密后展示 Blob URL。本地素材和下载二维码保持直接加载。移动端并发 2、桌面端并发 4,首屏主图和弹窗优先;共享请求与缓存、视口懒加载、12 秒超时及占位图回退,页面离开时释放 Blob URL。 + +播放逻辑由本项目(`JHA-1047`)的 `src/lib/client/player.ts`、`src/lib/client/playback.ts` 和 `src/lib/api/media.ts` 实现:绝对地址直接播放,相对地址走 `/h5/m3u8/`;Safari 优先原生 HLS,其余浏览器优先 hls.js,无 MSE 时再尝试原生 HLS。保留移动端内联属性,HLS 致命错误最多恢复 3 次,重试会重建播放器;切换预告、正片或离开路由时销毁旧实例。锁定章节可看配置的免费预告,正片按 `canWatch` 和参考项目的旧字段兼容规则判定;金币解锁需用户确认,成功后重新获取账户与章节,预告不提交正片观看上报。上一集/下一集使用接口的真实章节 ID。 + +H.264/AVC 与 H.265/HEVC 使用 `hls.js` 1.7.3 完整版解析,保留 HEVC MPEG-2 TS 支持(参考项目为 1.6.13 完整版)。多编码清单按设备能力保留 H.264、`hvc1`、`hev1`,不强制改写编码或限制为 H.264。编码不兼容时在 hls.js 与原生 HLS 之间各尝试一次,保留签名与播放位置;普通网络/密钥错误仍走原有恢复逻辑。无可用解码方式时显示编码不支持提示。H.265 的实际解码依赖浏览器、系统及设备能力,前端不提供转码或软件解码器,参见 [hls.js 官方说明](https://github.com/video-dev/hls.js/blob/v1.7.3/README.md)。 + +媒体和 `/enkey`、`/newenkey` 使用 Route Handler 读取运行时 `UPSTREAM_API_URL`,保留完整签名查询参数,不转发应用登录 Cookie 或 Authorization。密钥上游先请求无尾斜杠路径,兼容不同服务的尾斜杠规则:只允许同一域名、同一密钥路径增加或删除尾斜杠的一次跳转,签名查询、IP 和 UA 保持不变,返回值必须是 16 字节 AES 密钥。媒体支持 Range,清单和密钥均不缓存,业务错误响应不会作为视频内容返回。 + +## 年龄确认与广告弹窗 + +弹窗逻辑由本项目(`JHA-1047`)的 `src/lib/client/startup.ts` 实现,保留本项目的弹窗样式。 + +- 年龄确认使用会话记录 `hg_age_ok`,当前标签页会话首次进入时展示;确认后刷新、切换业务页面不再重复展示。存储不可用时仍可正常确认并继续;退出行为与参考项目一致,打开 Google。 +- 大图广告使用配置接口 `advitise.dialogList`,按接口顺序逐条展示;先通过年龄确认,再显示已加载完成的广告,配置晚于确认返回时也能继续展示。 +- 点击关闭或按 Esc 进入下一条;点击广告在新标签页打开配置链接。全部关闭后,同一会话内不再展示相同广告队列;刷新时从当前广告恢复。广告队列签名优先使用 `advertiseCode`,队列变化后重新展示。 +- 图片加载失败、解码失败或 12 秒超时会跳过当前广告;全部失败时不记为已完成,后续页面可以重试。 +- 大图广告结束后展示 `advitise.nineGridList` 宫格弹窗,保留 HTML 的四列图标、名称、间距、模糊遮罩和底部关闭按钮;没有大图广告时,年龄确认后直接展示宫格。按配置顺序渲染,不补齐模拟数据,图片复用解密逻辑,失败项不展示;没有有效图片时不弹出。点击应用打开配置链接并关闭弹窗,关闭按钮和 Esc 均可关闭;`gridAdDismissed` 独立记录当前宫格配置,同一会话不重复展示,配置变化后可重新展示。 +- 宫格弹窗最大高度为视口的 80%,预留底部关闭按钮空间,超出的广告在宫格区域内纵向滚动;每次展示从顶部开始。 + +`/navigation/` 保持独立入口页,不加载年龄确认和广告队列。 + +## 验证 + +`npm test` 使用独立本地模拟上游和生产构建验证代理与 SSR,不写入真实业务数据。 + +启动服务后可执行真实接口检查: + +```sh +PREVIEW_URL=http://127.0.0.1:21021 npm run smoke:api +``` + +若游客登录服务暂时超时,可仅验证公开内容: + +```sh +SMOKE_PUBLIC_ONLY=1 PREVIEW_URL=http://127.0.0.1:21021 npm run smoke:api +``` + +2026-10-21 的类型检查、生产构建和全部 258 项测试通过;真实接口检查通过游客会话、公开与个人只读接口及 29 个页面。页面检查确认账户、充值、搜索和工具页的首个响应包含完整正文。真实支付、付费解锁及浏览器视觉验收不在此次验证范围内,详情见 [验证记录](docs/verification.md)。 + +## 目录 + +- `src/app/api/app/[...path]/route.ts`:业务代理、会话 Cookie 和接口白名单。 +- `src/app/[...path]/page.tsx` 与 `src/lib/routing.ts`:页面路径入口、校验与解析。 +- `src/app/navigation/page.tsx`:独立入口导航页,服务端读取线路、渠道和下载配置。 +- `src/lib/api/`:接口类型、响应兼容和服务端请求。 +- `src/lib/seo-server.ts`:服务端页面取数、正文模板和 SEO。 +- `src/lib/api/page-session.ts`:页面会话准备及同一次请求的 Cookie 传递。 +- `src/lib/api/public-cache.ts`:有上限的公共数据缓存、过期刷新及并发合并。 +- `src/lib/site-shared-data.ts`:首屏公开配置和页脚数据白名单。 +- `src/lib/client/`:共享页面模板及轮播、播放、登录、支付、下载等浏览器交互。 +- `public/scripts/site-runtime.js`:原稿布局、移动端菜单和播放器控件。 +- `public/assets/`:本地品牌、图标和空状态素材。 +- `tests/api-integration.test.mjs`:SSR、鉴权隔离和代理边界测试。 +- `tests/routing.test.mjs`:路径解析、搜索关键词编码、hash 地址不转换与原生页面导航测试。 +- `src/lib/client/startup.ts` 与 `tests/startup.test.mjs`:年龄确认、广告队列和会话进度,以及控制器与浏览器事件适配测试。 + +## 埋点 + +按照 [Web SDK v1.2.0 文档](https://doc2.adcenteryyds.cc/web/getting-started.html) 、[飞书埋点规范](https://djprsdgo4i4n.jp.larksuite.com/wiki/X8sjwbzs2ib01Ak2TWzj9Y94pFd)(已通过 Google Chrome 读取,包含 2026-10-17 更新)为本项目(`JHA-1047`)接入,官方 SDK 固定保存在 `public/scripts/web-sdk-v1.2.0.js`。应用 ID 默认 `1047`,可通过 `NEXT_PUBLIC_TRACKER_APP_ID` 配置,必须与当前站点密文和上游应用一致;初始化优先读取 `config/info.reportSdkConfig`,可选 `NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG` 作为同项目的密文备用配置。缺少配置或 SDK 加载失败时,业务页面继续工作。 + +- SDK 自动采集生命周期、网络和会话;关闭自动 PV/点击,由接入层按真实路径上报,避免重复。支持首页、榜单、分页、专题、搜索、账户、信息页和独立导航页;登录、游客恢复和退出后同步 UID,渠道沿用首次归因。 +- 内容曝光以可见元素为准,关闭的弹窗、被模态弹窗遮挡的页面、后台标签页和轮播副本不计入;关闭弹窗或恢复标签页后重新观察尚未曝光的元素。短剧、专题和普通福利应用均有曝光、点击;内容无父级时 `parent_item_id` 使用 `0`,满足 SDK 的必填非空字符串校验。 +- 广告保留接口广告代码和广告位字段,上报 `adImpression/adClick`;页面广告、大图弹窗、宫格弹窗按实际展示位置分别去重,避免同一广告出现在宫格时被首页曝光记录吞掉。宫格仅统计滚动区域内可见的项目。两种弹窗另按参考项目协议上报 `advertising` 的 `show/click/close` 行为;只关闭实际曝光过的项目,同一展示只记一次关闭,关闭不算广告点击。福利应用有 `advitiseId` 时使用该广告 ID 上报,没有广告关联时按普通内容统计。 +- 搜索成功后上报关键词与实际总数,结果点击使用含页码的全局位置;未接推荐引擎的追踪字段保留空串。顶部、侧栏、独立入口页渠道和 App 下载入口记录导航点击。 +- 短剧使用 `dramaEvent/dramaStatusChange`,记录展示、播放、暂停、快进/快退、完成、分享、切集和成功解锁;独立视频使用 `videoEvent`。终止播放失败分别上报 `ep_failed/video_failed`(播放失败),自动恢复中的中间错误和预告失败不作为正片失败上报。短剧完播同时补报 `ep_play`,进度固定 100%,保留最后一段播放时长。播放时长按实际播放区间累计,排除缓冲和免费预告,进度为百分比,倍速乘以 100。 +- 所有同源业务 API 请求携带 SDK 原始公共字段 `X-SDK-Common`,保留 SDK 返回的完整公共字段,包括应用、渠道、UID、数值时间戳、会话、设备、指纹和版本等;注册、登录、充值及 VIP 下单都使用同一份字段格式。字段来自 `WebSDK.getCommonFields()`,不重新生成指纹或会话 ID,等待最多 1.5 秒。 +- 点赞、收藏、评论、购买由上游服务端上报,前端传入 `X-Tracker-Request-Id/X-Tracker-Event-Name/X-DC-Request-Context`,包含完整公共字段和目标剧集上下文。Next.js 代理校验应用 ID、事件对应接口和字段结构后透传;游客重试复用原始快照和请求 ID,不额外从客户端发送业务成功事件。 + +SDK 未初始化时最多缓存 1000 条事件,配置到达后按顺序补发;配置接口的首次请求不等待 SDK 加载,避免互相等待,首次 SDK 脚本失败后在取得配置时再尝试加载。SDK 返回字段校验失败、事件不在允许列表、地址不可用或异常时,仅触发脱敏的 `tracker:diagnostic` 事件,不向浏览器控制台输出。自带 SDK 仅移除了三处控制台警告,保留字段校验、HTTPS 地址限制和配置解密失败兜底;网络重试和持久队列由官方 SDK 负责。 + +埋点回归使用 SDK 替身及项目自带官方 SDK 的真实字段校验器;浏览器验证使用同一 SDK 的事件地址参数,将签名后的测试记录发送到本机接收端,不向真实统计后台发送测试事件。生产环境还需在广告中心为当前应用的 `reportSdkConfig` 配置业务域名反向代理地址(SDK 的 `batchUrls`),由部署层转发到文档指定的生产批量上报接口,不能让客户端直连生产上报域名。代理需保留请求体、真实来源 IP 和 SDK 签名;目前未变更线上域名或 Nginx 配置。 + +“我的点赞”入口位于头像菜单和个人中心书架导航,路由为 `/member/likes/`,分页使用 `/member/likes/page/N/`。服务端通过当前账号请求 `GET /api/app/playlet/like/list?pageNum=N&pageSize=24`,列表不进入公开缓存或搜索索引;空列表显示原 HTML 的空状态和发现入口,接口失败显示重试状态。 diff --git a/config/development.env b/config/development.env new file mode 100644 index 0000000..846f819 --- /dev/null +++ b/config/development.env @@ -0,0 +1,9 @@ +# Local development. Personal overrides belong in development.local.env. +APP_ENV=development +PORT=21021 +# Server-only Go API origin. Browser requests use the current site's /api/*. +UPSTREAM_API_URL=http://172.104.181.99:21022 +API_CHANNEL= +PUBLIC_DATA_CACHE_TTL_MS=0 +NEXT_PUBLIC_TRACKER_APP_ID=1047 +NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG= diff --git a/config/production.env b/config/production.env new file mode 100644 index 0000000..7dfbb6d --- /dev/null +++ b/config/production.env @@ -0,0 +1,9 @@ +# Production defaults. Server overrides belong in production.local.env. +APP_ENV=production +PORT=21021 +# Server-only Go API origin. Browser requests use the current site's /api/*. +UPSTREAM_API_URL=http://172.104.56.28:26125 +API_CHANNEL= +PUBLIC_DATA_CACHE_TTL_MS=60000 +NEXT_PUBLIC_TRACKER_APP_ID=1047 +NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG= diff --git a/config/test.env b/config/test.env new file mode 100644 index 0000000..060cce5 --- /dev/null +++ b/config/test.env @@ -0,0 +1,9 @@ +# Test deployment. Server overrides belong in test.local.env. +APP_ENV=test +PORT=21021 +# Server-only Go API origin. Browser requests use the current site's /api/*. +UPSTREAM_API_URL=http://172.104.181.99:21022 +API_CHANNEL= +PUBLIC_DATA_CACHE_TTL_MS=60000 +NEXT_PUBLIC_TRACKER_APP_ID=1047 +NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG= diff --git a/deploy/up_next_h5.sh b/deploy/up_next_h5.sh new file mode 100755 index 0000000..4b5e891 --- /dev/null +++ b/deploy/up_next_h5.sh @@ -0,0 +1,93 @@ +#!/bin/bash +# Linux 测试服 tar.gz 更新入口;需预先配置 Supervisor 程序并手动调用。 +set -Eeuo pipefail +DIR=$(cd "$(dirname "$0")" && pwd) +PROGRAM="${DEPLOY_PROGRAM:-91dj_next_h5}" +PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_91dj_h5.tar.gz}" +CHECKSUM_NAME="$PACKAGE_NAME.sha256" +[[ "$PROGRAM" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_-]*$ ]] || { echo 'DEPLOY_PROGRAM 格式无效' >&2; exit 1; } +[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || { echo 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' >&2; exit 1; } +CURRENT="$DIR/next" +LOCK="$DIR/.$PROGRAM.lock" +STAGING=''; UPLOAD=''; PREVIOUS=''; FAILED='' +MOVED_OLD=0; INSTALLED=0; STOPPED=0; WAS_RUNNING=0 +fail() { printf '错误:%s\n' "$*" >&2; exit 1; } +for tool in node npm sha256sum tar supervisorctl mktemp; do command -v "$tool" >/dev/null || fail "缺少命令 $tool"; done +node -e 'const [a,b]=process.versions.node.split(".").map(Number);if(a<20||a===20&&b<9)process.exit(1)' || fail '需要 Node.js >=20.9' +mkdir "$LOCK" 2>/dev/null || fail "已有更新进程或残留锁:$LOCK" +finish() { + local result=$? + trap - EXIT INT TERM + if [ "$result" != 0 ] && { [ "$STOPPED" = 1 ] || [ "$INSTALLED" = 1 ] || [ "$MOVED_OLD" = 1 ]; }; then + printf '更新失败,恢复上一版本。\n' >&2 + supervisorctl stop "$PROGRAM" >/dev/null 2>&1 || true + if [ "$INSTALLED" = 1 ]; then + FAILED="$DIR/next.failed.$(date +%Y%m%d%H%M%S).$$" + mv "$CURRENT" "$FAILED" || { echo "无法保存失败版本:$CURRENT" >&2; result=1; } + fi + if [ "$MOVED_OLD" = 1 ]; then + mv "$PREVIOUS" "$CURRENT" || { echo "需要手动恢复:$PREVIOUS → $CURRENT" >&2; result=1; } + fi + if [ "$WAS_RUNNING" = 1 ]; then supervisorctl start "$PROGRAM" || result=1; fi + fi + if [ -n "$STAGING" ] && [ -d "$STAGING" ]; then rm -rf "$STAGING"; fi + if [ -n "$UPLOAD" ] && [ -d "$UPLOAD" ]; then rm -rf "$UPLOAD"; fi + rmdir "$LOCK" 2>/dev/null || true + exit "$result" +} +trap finish EXIT +trap 'exit 130' INT +trap 'exit 143' TERM +[ ! -L "$CURRENT" ] && { [ ! -e "$CURRENT" ] || [ -d "$CURRENT" ]; } || fail 'next 必须是普通目录' +STATUS=$(supervisorctl status "$PROGRAM" 2>/dev/null || true) +[[ "$STATUS" == "$PROGRAM "* ]] && [[ "$STATUS" != *'ERROR'* ]] || fail "请先配置 Supervisor 程序 $PROGRAM,command 指向 node $CURRENT/start.mjs" +if [[ "$STATUS" == *'RUNNING'* ]]; then WAS_RUNNING=1; fi +[ -s "$DIR/$PACKAGE_NAME" ] && [ -s "$DIR/$CHECKSUM_NAME" ] || fail "缺少 $PACKAGE_NAME 或 SHA-256" +# Freeze the uploaded pair before validation/extraction; concurrent uploads cannot replace this copy. +UPLOAD=$(mktemp -d "$DIR/.91dj-upload.XXXXXX") +cp "$DIR/$PACKAGE_NAME" "$DIR/$CHECKSUM_NAME" "$UPLOAD/" +(cd "$UPLOAD" && sha256sum -c "$CHECKSUM_NAME") || fail 'SHA-256 校验失败' +LC_ALL=C tar -tzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/members" || fail 'tar.gz 压缩包损坏' +while IFS= read -r entry; do + entry="${entry#./}" + case "$entry" in + /*|../*|*/../*|*/..|node_modules|node_modules/*|*/node_modules/*|*\\*) fail "非法 tar.gz 路径:$entry" ;; + esac +done < "$UPLOAD/members" +# Reject links and special entries before extraction, including hard links to outside paths. +LC_ALL=C tar -tvzf "$UPLOAD/$PACKAGE_NAME" > "$UPLOAD/types" || fail '无法检查 tar.gz 文件类型' +while IFS= read -r entry; do + case "$entry" in + -?*|d?*) ;; + *) fail '测试包只允许普通文件和目录,不允许链接或特殊文件' ;; + esac +done < "$UPLOAD/types" +STAGING=$(mktemp -d "$DIR/.91dj-release.XXXXXX") +tar -xzf "$UPLOAD/$PACKAGE_NAME" -C "$STAGING" --no-same-owner --no-same-permissions +[ -z "$(find "$STAGING" -type l -print -quit)" ] || fail '测试包不允许符号链接' +for file in start.mjs server.js .next-test/BUILD_ID config/test.env package.json package-lock.json scripts/next-env.mjs scripts/check-health.mjs; do + [ -s "$STAGING/$file" ] || fail "包内缺少 $file" +done +[ -d "$STAGING/.next-test/static" ] && [ -d "$STAGING/public" ] || fail '包内缺少静态资源' +# Install for the server's real OS/CPU, rather than shipping this Mac's node_modules. +(cd "$STAGING" && npm ci --omit=dev --no-audit --no-fund) +if [ -f "$CURRENT/config/test.local.env" ] && [ ! -L "$CURRENT/config/test.local.env" ]; then + cp "$CURRENT/config/test.local.env" "$STAGING/config/test.local.env" + chmod 600 "$STAGING/config/test.local.env" +fi +if [ -d "$CURRENT/.next-test/cache/seo" ] && [ ! -L "$CURRENT/.next-test/cache/seo" ]; then + mkdir -p "$STAGING/.next-test/cache" + cp -R "$CURRENT/.next-test/cache/seo" "$STAGING/.next-test/cache/seo" +fi +APP_PORT=$(cd "$STAGING" && node --input-type=module -e 'import{nextCommand}from"./scripts/next-env.mjs";console.log(nextCommand("test","start",[],{root:process.cwd()}).env.PORT)') +# Stop only after checksum, installation and configuration validation have succeeded. +if [ "$WAS_RUNNING" = 1 ]; then supervisorctl stop "$PROGRAM"; STOPPED=1; fi +if [ -d "$CURRENT" ]; then + PREVIOUS="$DIR/next.prev.$(date +%Y%m%d%H%M%S).$$" + mv "$CURRENT" "$PREVIOUS"; MOVED_OLD=1 +fi +mv "$STAGING" "$CURRENT"; STAGING=''; INSTALLED=1 +supervisorctl start "$PROGRAM" +node "$CURRENT/scripts/check-health.mjs" "http://127.0.0.1:$APP_PORT" test +supervisorctl status "$PROGRAM" +printf '测试服更新完成:%s;上一版本:%s\n' "$CURRENT" "${PREVIOUS:-无}" diff --git a/deploy_prod.sh b/deploy_prod.sh new file mode 100755 index 0000000..841b642 --- /dev/null +++ b/deploy_prod.sh @@ -0,0 +1,91 @@ +#!/bin/bash +# 91短剧正式服:构建 → 测试 → Linux standalone + MD5 → 可选触发 rsupdate.sh。 +set -euo pipefail +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source "$SCRIPT_DIR/scripts/deploy-common.sh" +JUMP_HOST="${DEPLOY_JUMP_HOST:-server@34.92.204.175}" +# 压缩包上传完成后,在同一目录执行服务器现有的 rsupdate.sh。 +JUMP_DIR="${DEPLOY_JUMP_DIR:-/home/server/web_91dj/h5}" +# valid_path 限制目录为安全字符;命令文本与手动执行一致,兼容 SSH 命令白名单。 +REMOTE_UPDATE_COMMAND="cd ${JUMP_DIR%/} && bash rsupdate.sh" +SCP_PORT="${DEPLOY_SCP_PORT:-22889}" +SSH_PORT="${DEPLOY_SSH_PORT:-22889}" +TARGET_ARCH="${DEPLOY_TARGET_ARCH:-x64}" +TARGET_LIBC="${DEPLOY_TARGET_LIBC:-glibc}" +PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_91dj_h5.tar.gz}" +MD5_NAME="${PACKAGE_NAME%.tar.gz}.md5" +RELEASE="$SCRIPT_DIR/dist/production-linux-$TARGET_ARCH-$TARGET_LIBC" +PACKAGE_ONLY=0 +for argument in "$@"; do + case "$argument" in + --dry-run) DEPLOY_DRY_RUN=1 ;; + --package-only) PACKAGE_ONLY=1 ;; + -h|--help) + printf '用法:%s [--dry-run] [--package-only]\n' "$0" + printf '仅打包:--package-only。仅预览:--dry-run(不构建、不联网)。\n' + printf '上传目标:%s:%s/;上传成功后在该目录执行 bash rsupdate.sh。\n' "$JUMP_HOST" "${JUMP_DIR%/}" + printf '可覆盖 DEPLOY_JUMP_HOST、DEPLOY_JUMP_DIR、DEPLOY_SCP_PORT(%s)、DEPLOY_SSH_PORT(%s)、DEPLOY_PACKAGE_NAME(%s)、DEPLOY_TARGET_ARCH(x64/arm64)、DEPLOY_TARGET_LIBC(glibc/musl)。\n' "$SCP_PORT" "$SSH_PORT" "$PACKAGE_NAME" + exit 0 ;; + *) fail "不支持的参数 $argument" ;; + esac +done +case "$TARGET_ARCH" in x64|arm64) ;; *) fail 'DEPLOY_TARGET_ARCH 仅支持 x64 或 arm64' ;; esac +case "$TARGET_LIBC" in glibc|musl) ;; *) fail 'DEPLOY_TARGET_LIBC 仅支持 glibc 或 musl' ;; esac +[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || fail 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' +if [ "$PACKAGE_ONLY" = 0 ] && [ "${DEPLOY_DRY_RUN:-0}" != 1 ]; then + valid_host "$JUMP_HOST" DEPLOY_JUMP_HOST; valid_path "$JUMP_DIR" DEPLOY_JUMP_DIR + valid_port "$SCP_PORT" DEPLOY_SCP_PORT; valid_port "$SSH_PORT" DEPLOY_SSH_PORT + need ssh; need scp; need tee +fi +check_local production +if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then + printf 'DRY RUN:正式环境;Linux %s/%s;不构建、不连接服务器。\n' "$TARGET_ARCH" "$TARGET_LIBC"; public_profile production + printf ' npm run check → npm run bundle → npm run bundle:linux -- %s %s\n' "$TARGET_ARCH" "$TARGET_LIBC" + printf ' 保留 standalone 裁剪依赖,仅安装 lockfile 中目标 Linux 的 Sharp/libvips。\n' + printf ' 生成 %s + %s(纯 MD5 hash)。\n' "$PACKAGE_NAME" "$MD5_NAME" + if [ "$PACKAGE_ONLY" = 1 ]; then printf ' 仅生成本地包。\n' + else + printf ' scp -P %s %s %s %s:%s/\n' "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST" "${JUMP_DIR%/}" + printf ' ssh -p %s %s "%s"\n' "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND" + fi + exit 0 +fi +need tar +command -v md5sum >/dev/null || command -v md5 >/dev/null || fail '缺少 md5sum 或 macOS md5' +lock_build +step '构建正式环境并运行回归测试' +npm run check +step '整理正式环境 standalone 和目标 Linux 原生依赖' +npm run bundle +npm run bundle:linux -- "$TARGET_ARCH" "$TARGET_LIBC" +for file in start.mjs server.js .next/BUILD_ID config/production.env runtime-target.json; do + [ -s "$RELEASE/$file" ] || fail "正式包缺少 $file" +done +for directory in node_modules .next/static public; do [ -d "$RELEASE/$directory" ] || fail "正式包缺少 $directory"; done +step "生成 $PACKAGE_NAME" +TEMP_PACKAGE=$(mktemp "$SCRIPT_DIR/dist/.production-archive.XXXXXX") +if ! COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar --no-xattrs \ + --exclude='._*' --exclude='.DS_Store' --exclude='.env*' --exclude='*.local.env' --exclude='*.log' \ + -czf "$TEMP_PACKAGE" -C "$RELEASE" .; then + rm -f "$TEMP_PACKAGE"; fail '压缩失败' +fi +tar -tzf "$TEMP_PACKAGE" >/dev/null +mv -f "$TEMP_PACKAGE" "$PACKAGE_NAME" +chmod 600 "$PACKAGE_NAME" +if command -v md5sum >/dev/null; then md5sum "$PACKAGE_NAME" | awk '{print $1}' > "$MD5_NAME" +else md5 -q "$PACKAGE_NAME" > "$MD5_NAME"; fi +printf '正式包:%s/%s;校验:%s/%s\n' "$SCRIPT_DIR" "$PACKAGE_NAME" "$SCRIPT_DIR" "$MD5_NAME" +if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi +step "上传到 $JUMP_HOST:$JUMP_DIR/" +scp -P "$SCP_PORT" "$PACKAGE_NAME" "$MD5_NAME" "$JUMP_HOST:${JUMP_DIR%/}/" +step '执行正式服 rsupdate.sh' +printf '远端命令:%s\n' "$REMOTE_UPDATE_COMMAND" +RUN_LOG=$(mktemp "$SCRIPT_DIR/dist/production-rsupdate.XXXXXX") +if ! ssh -p "$SSH_PORT" "$JUMP_HOST" "$REMOTE_UPDATE_COMMAND" 2>&1 | tee "$RUN_LOG"; then fail "rsupdate.sh 执行失败,日志:$RUN_LOG"; fi +if ! node - "$RUN_LOG" <<'NODE' +const output=require('node:fs').readFileSync(process.argv[2],'utf8').replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,''); +if(/\b(?:BACKOFF|FATAL|ERROR)\b|\bspawn\s+error\b/i.test(output))process.exit(1); +NODE +then fail "远端输出包含失败状态,日志:$RUN_LOG"; fi +printf '正式服 rsupdate.sh 执行完成;请核对远端服务状态。日志:%s\n' "$RUN_LOG" diff --git a/deploy_test.sh b/deploy_test.sh new file mode 100755 index 0000000..b4dc7f3 --- /dev/null +++ b/deploy_test.sh @@ -0,0 +1,94 @@ +#!/bin/bash +# 91短剧测试服:构建 → 测试 → tar.gz + SHA-256 → 可选上传并执行 run.sh。 +set -euo pipefail +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) +cd "$SCRIPT_DIR" +source "$SCRIPT_DIR/scripts/deploy-common.sh" +HOST="${DEPLOY_HOST:-root@172.104.181.99}" +SSH_PORT="${DEPLOY_SSH_PORT:-22889}" +# 保留 91dj-web 已约定的上传位置;项目包和 Supervisor 标识使用 91dj。 +REMOTE_DIR="${DEPLOY_DIR:-/server/web_91dj/h5}" +# 远端启动脚本目录独立于上传目录,按服务器约定执行。 +RUN_DIR="${DEPLOY_RUN_DIR:-/server/web_91dj/h5}" +CHECK_URL="${DEPLOY_CHECK_URL:-}" +PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_91dj_h5.tar.gz}" +CHECKSUM_NAME="$PACKAGE_NAME.sha256" +PACKAGE_ONLY=0 +for argument in "$@"; do + case "$argument" in + --dry-run) DEPLOY_DRY_RUN=1 ;; + --package-only) PACKAGE_ONLY=1 ;; + -h|--help) + printf '用法:%s [--dry-run] [--package-only]\n' "$0" + printf '仅打包:--package-only。仅预览:--dry-run(不构建、不联网)。\n' + printf '上传目标:%s:%s %s/。\n' "$HOST" "$SSH_PORT" "${REMOTE_DIR%/}" + printf '上传成功后:ssh -p %s %s "cd %s && bash run.sh"\n' "$SSH_PORT" "$HOST" "$RUN_DIR" + printf '可覆盖 DEPLOY_HOST、DEPLOY_DIR、DEPLOY_RUN_DIR(%s)、DEPLOY_SSH_PORT、DEPLOY_PACKAGE_NAME(%s)、DEPLOY_CHECK_URL。\n' "$RUN_DIR" "$PACKAGE_NAME" + exit 0 ;; + *) fail "不支持的参数 $argument" ;; + esac +done +[[ "$PACKAGE_NAME" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.-]*\.tar\.gz$ ]] || fail 'DEPLOY_PACKAGE_NAME 必须是 .tar.gz 文件名' +if [ "$PACKAGE_ONLY" = 0 ] && [ "${DEPLOY_DRY_RUN:-0}" != 1 ]; then + valid_host "$HOST" DEPLOY_HOST; valid_path "$REMOTE_DIR" DEPLOY_DIR; valid_path "$RUN_DIR" DEPLOY_RUN_DIR; valid_port "$SSH_PORT" DEPLOY_SSH_PORT + need ssh; need scp +fi +check_local test +if [ -n "$CHECK_URL" ]; then + node --input-type=module -e 'const u=new URL(process.argv[1]);if(!["http:","https:"].includes(u.protocol)||u.username||u.password||u.search||u.hash||u.pathname!=="/")process.exit(1)' "$CHECK_URL" || fail 'DEPLOY_CHECK_URL 必须是 HTTP(S) 站点根地址' +fi +if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then + printf 'DRY RUN:测试环境;不构建、不连接服务器。\n'; public_profile test + printf ' npm run build:test → npm test → npm run bundle:test → %s + %s\n' "$PACKAGE_NAME" "$CHECKSUM_NAME" + printf ' 包内不含 node_modules、本地环境覆盖和缓存;服务端 npm ci --omit=dev 后 node start.mjs。\n' + if [ "$PACKAGE_ONLY" = 1 ]; then printf ' 仅生成本地包。\n' + else + printf ' 发布目标:%s:%s(SSH %s)。\n' "$HOST" "$REMOTE_DIR" "$SSH_PORT" + printf ' 上传 %s、%s 和 up_next_h5.sh。\n' "$PACKAGE_NAME" "$CHECKSUM_NAME" + printf ' 上传成功后:ssh -p %s %s "cd %s && bash run.sh"\n' "$SSH_PORT" "$HOST" "$RUN_DIR" + fi + exit 0 +fi +need tar +[ -s deploy/up_next_h5.sh ] || fail '缺少 deploy/up_next_h5.sh' +bash -n deploy/up_next_h5.sh +lock_build +step '构建测试环境并运行回归测试' +npm run build:test +TEST_APP_ENV=test npm test +step '整理 standalone 测试包' +npm run bundle:test +for file in start.mjs server.js .next-test/BUILD_ID config/test.env; do + [ -s "dist/test/$file" ] || fail "测试包缺少 $file" +done +[ -d dist/test/.next-test/static ] && [ -d dist/test/public ] || fail '测试包缺少静态资源' +step "生成 $PACKAGE_NAME 和 SHA-256" +TEMP_PACKAGE=$(mktemp "$SCRIPT_DIR/dist/.test-archive.XXXXXX") +if ! COPYFILE_DISABLE=1 COPY_EXTENDED_ATTRIBUTES_DISABLE=1 tar --no-xattrs \ + --exclude='node_modules' --exclude='*/node_modules' --exclude='._*' --exclude='.DS_Store' \ + --exclude='*.local.env' --exclude='.env*' --exclude='*.log' \ + -czf "$TEMP_PACKAGE" -C dist/test .; then + rm -f "$TEMP_PACKAGE"; fail '压缩失败' +fi +tar -tzf "$TEMP_PACKAGE" >/dev/null +mv -f "$TEMP_PACKAGE" "$PACKAGE_NAME" +chmod 600 "$PACKAGE_NAME" +node --input-type=module - "$PACKAGE_NAME" "$CHECKSUM_NAME" <<'NODE' +import {createHash} from 'node:crypto'; +import {readFileSync,writeFileSync} from 'node:fs'; +const [archive,checksum]=process.argv.slice(2); +const bytes=readFileSync(archive); +writeFileSync(checksum,createHash('sha256').update(bytes).digest('hex')+' '+archive+'\n'); +// The test and production commands share a package name; refresh its companion checksum too. +writeFileSync(archive.replace(/\.tar\.gz$/,'.md5'),createHash('md5').update(bytes).digest('hex')+'\n'); +NODE +printf '测试包:%s/%s;校验:%s/%s\n' "$SCRIPT_DIR" "$PACKAGE_NAME" "$SCRIPT_DIR" "$CHECKSUM_NAME" +if [ "$PACKAGE_ONLY" = 1 ]; then exit 0; fi +step "上传到 $HOST:$REMOTE_DIR/" +ssh -p "$SSH_PORT" "$HOST" "mkdir -p '$REMOTE_DIR'" +scp -P "$SSH_PORT" "$PACKAGE_NAME" "$CHECKSUM_NAME" deploy/up_next_h5.sh "$HOST:$REMOTE_DIR/" +printf '测试包上传完成:%s:%s/%s\n' "$HOST" "${REMOTE_DIR%/}" "$PACKAGE_NAME" +step "执行测试服 $RUN_DIR/run.sh" +ssh -p "$SSH_PORT" "$HOST" "cd '$RUN_DIR' && bash run.sh" +if [ -n "$CHECK_URL" ]; then node scripts/check-health.mjs "$CHECK_URL" test; fi +printf '测试服 run.sh 执行完成:%s:%s/run.sh\n' "$HOST" "${RUN_DIR%/}" diff --git a/docs/api-contract.md b/docs/api-contract.md new file mode 100644 index 0000000..e0210b2 --- /dev/null +++ b/docs/api-contract.md @@ -0,0 +1,53 @@ +# 91av 页面接口对照 + +核对日期:2026-10-21。来源:[当前 Swagger](https://91av-wz.80sygtm.com/swagger/index.html)。上游地址通过 `API_BASE_URL` 配置,默认 `https://91av-wz.80sygtm.com`;下表省略 `/api/app/` 前缀。 + +| 页面 | 接口及关键参数 | +| --- | --- | +| 顶部、侧栏和移动导航 | `navigation/header`;使用 `categoryList` 的顺序、`name,urlValue,icon,categoryType,translationList`。1 为 URL、2 为二级导航,3 不混入主导航;不生成固定菜单 | +| 首页 | `playlet/home`;轮播 `homeBannerData`,栏目 `homeConfigData`,广告 `advitise` | +| 短剧列表 | `playlet/all/list`;`pageNum,pageSize,sortType`,排序 1 最新、2 热播、3 独家、4 随机 | +| 三个排行榜 | `playlet/hot/rank/list`;`rankType` 分别为 1 热播、2 新剧、3 追剧,分页使用 `pageNum,pageSize` | +| 专题列表 | `playlet/topic/list`;`pageNum,pageSize`,没有专题分类和排序参数 | +| 专题详情、题材页 | `playlet/topic/detail`;`id,pageNum,pageSize`。当前服务的题材和专题使用同一 ID;不再向全量列表传会被忽略的 `themeId` | +| 标签页 | `playlet/tag/list`;`tagSlug` 或 `tagId`,以及 `pageNum,pageSize,sortType` | +| 首页“更多” | 优先使用 `moreUrl`,继续视为站内业务入口。未配置时使用 `/config/{slug或id}/`,从 `playlet/home.homeConfigData` 按 slug 或 ID 提取对应已启用栏目;只展示实际返回的条目,不伪造完整目录或后续分页 | +| 搜索结果 | `playlet/search`;`keyword,pageNum,pageSize`。无结果优先展示 `everyoneWatching`,缺少推荐时读取 `playlet/hot/list` | +| 搜索联想 | `playlet/search/suggest`;`keyword`,返回短剧对象,点击进入实际详情页;热搜词继续进入搜索页 | +| 搜索推荐换一批 | `playlet/all/list`;`sortType=4` 随机获取,避免缺少必填的短剧 ID | +| 短剧详情 | `playlet/detail` 传 `slug` 或 `id`;`playlet/chapter/list` 传 `playletSlug` 或 `playletId` | +| 详情推荐 | `playlet/guess-like/list`;必须传当前 `playletId`,不依赖详情中未定义的推荐字段 | +| 章节播放 | `playlet/chapter/detail`;`chapterId` 加 `playletSlug` 或 `playletId`,使用实时观看权限、费用、余额及视频地址。章节 SEO 优先于整剧 SEO | +| 评论 | `comment/list`;`objectId,objectType,pageNum,pageSize,sortOrder`;整剧类型 10、章节类型 11 | +| 收藏、历史、已购 | `playlet/collect/list`、`playlet/history/list`、`playlet/bought/list`;`pageNum,pageSize`;服务端使用 `auth/getInfo` 恢复当前账号,直接输出列表 | +| 账号设置 | `auth/profile`;头像上传 `upload/image`,使用返回的 `sourceNameFs` | +| 充值、VIP | `recharge/package/list`;读取真实套餐金额(分)、金币及支付渠道;余额明细 `coin/trans/list` 使用 `page,pageSize,type` | +| 福利应用 | `applist/list?categoryId=…`;保留分类锚点及空分类隐藏逻辑 | +| 回家的路、独立导航 | `config/info` 的永久地址、备用线路、TG、App 链接;没有 App 地址时隐藏下载入口 | +| 关于等六个信息页 | 按既有要求保留 HTML 静态正文;联系我们使用配置中的商务 TG 与 TG 群,服务时间可取 `config/footer/info` | + +## 写接口 + +2026-10-22 本项目(`JHA-1047`)调整内容地址:链接优先使用非空 slug,缺少时使用 ID,保留尾斜杠和独立分页。专题详情接口仍必填 ID,slug 路由先分页查询专题目录解析 ID,再读取详情;目录故障返回服务错误,不伪装成专题不存在。详情评论和推荐等只支持数字 ID 的接口在详情返回后使用真实 ID;支持 slug 的章节请求继续并行。canonical、面包屑、选集和 ItemList 使用对应主地址,旧 ID 地址不新增转向 slug 的重定向。 + +登录、绑定和找回密码使用 `auth/login`、`auth/register`、`auth/resetPassword`;凭证保存在 HttpOnly Cookie。整剧点赞/收藏传 `id` 和布尔状态,章节点赞/收藏只传 `chapterId` 和布尔状态。解锁传 `playletId,chapterId`,仍在用户确认后提交。评论发送、点赞、观看报告的字段按各自请求模型发送。 + +金币下单 `coin/buy`、VIP 下单 `vip/pay` 均使用 `packageId,payMode`,支付渠道只取套餐配置。没有在真实服务上创建订单、付费解锁、注册账号、发送评论或提交反馈。 + +反馈按 `proto.FeedbackReq` 模型:1 建议、2 Bug、3 体验、4 账号、5 其他;操作描述仍写旧的四类,与模型不一致,本次以请求模型为准。问题正文与附加环境信息合计最多 500 字,优先完整保留用户正文。 + +## 已移除的旧接口依赖 + +`playlet/operate/list`、`playlet/home/config/detail/list`、`video/all/list` 在实际游客身份下返回 HTTP 404,且不在当前文档内。其余旧 `video/*`、`playlet/rank`、`playlet/today-update` 也不再开放代理。 + +当前文档没有独立视频列表和详情接口。按用户要求移除“剧集”的桌面、侧栏、移动导航入口及 `/series/`、`/video/{id}/` 路由(含分页);旧地址直接返回 404,不转换或重定向。首页和结构化数据不再输出旧类型的独立视频栏目。短剧详情、选集和章节播放继续保留。 + +## 验证方式 + +页面请求采用服务端渲染:服务端先准备会话与当前页数据,再直接返回完整正文;列表、搜索、详情/播放、工具、账户和充值均不使用浏览器整页取数及加载占位。原生链接请求下一页,浏览器只绑定首屏数据对应的交互。搜索联想、用户主动操作、实时播放权限和显式余额刷新仍按需调用 API。 + +公共配置、页脚、导航、分类和标签选项缓存 60 秒,首页缓存 30 秒,按上游和查询参数隔离并合并并发读取。详情、章节、选集、评论及推荐并行准备;账户、余额、套餐和观看权限保持实时。公开配置及页脚以字段白名单随 HTML 传递,SDK 配置继续单独读取。可用 `PUBLIC_DATA_CACHE_TTL_MS=0` 关闭公共缓存用于排查;页面和鉴权响应继续禁止共享缓存。 + +`tests/fixtures/91av-app-contract.json` 保存本次核对的端点、方法、查询参数及请求字段,不包含配置或凭证。合同测试检查页面请求参数及代理白名单;本地模拟服务另验证 SSR、分页、权限、登录、解锁及支付交互。 + +`PREVIEW_URL=http://127.0.0.1:3107 npm run smoke:api` 检查真实只读接口、游客握手及页面响应,凭证仅保存在进程内存。浏览器交互与真实扣费不包含在该脚本的验证范围内。 diff --git a/docs/desktop-preview.jpg b/docs/desktop-preview.jpg new file mode 100644 index 0000000..ccbaef4 Binary files /dev/null and b/docs/desktop-preview.jpg differ diff --git a/docs/indexation-policy.md b/docs/indexation-policy.md new file mode 100644 index 0000000..4fb8c20 --- /dev/null +++ b/docs/indexation-policy.md @@ -0,0 +1,42 @@ +# 页面索引策略 + +调整日期:2026-10-22。规则由 `src/lib/seo.ts` 统一生成,服务端初始 HTML 与客户端更新共用。这里只控制允许索引的信号,不保证搜索引擎实际收录。 + +## 允许索引 + +- 首页、有效且非空的短剧列表、三个榜单、专题列表与详情、标签列表、作品详情。 +- 普通内容列表的有效分页继续自引用 canonical,不统一归并到第一页。 +- 关于我们、常见问题、用户协议、隐私政策、2257、DMCA。 +- `/config/{id}/` 必须有栏目自己的标题、正文简介和有效内容卡片。只有 SEO 标题/描述或空卡片不能满足条件;此规则不依赖额外接口请求,也不代表已自动判定其内容与全站其他栏目语义唯一。 +- `/play/{id}/{chapterId}/` 必须有作品名、真实正整数集数,以及当前访问身份有权观看的有效正片地址。共享预告、继承的作品简介、章节 SEO 字段不能单独使纯解锁/不可播放页开放索引。收费类型本身不作为排除条件,有权观看的金币/VIP 章节仍可索引。当前模板没有独立可见的章节正文,若以后增加此类内容,可再评估受限章节的索引范围。 + +## 不允许索引 + +| 范围 | 设置 | +| --- | --- | +| 内部搜索、导航入口、回家的路、福利应用 | `noindex,follow` | +| 个人中心、收藏、历史、已购、设置、充值/VIP、反馈 | `noindex,nofollow` | +| 非默认有效排序变体、非默认评论排序 | `noindex,follow`,沿用原有例外规则 | +| 详情和播放页的评论分页,页码大于 1 | `noindex,follow` | +| 接口成功确认 `totalCount=0` 的公开列表第一页 | `noindex,follow`,继续返回 200 | +| 缺少独立栏目信息的更多列表、纯解锁或无有效正片的播放页 | `noindex,follow`,继续提供原页面 | + +noindex 页面不输出 canonical、head 中的 prev/next 或内容 JSON-LD;正文的选集、评论、分页链接继续提供。作品详情没有评论时仍可索引,评论数为 0 不等于作品为空。 + +## 去重和结构化数据 + +- 同 ID 的 `/theme/{id}/` 与 `/topics/{id}/` 当前读取相同资源,以 `/topics/{id}/` 为 canonical。两条路由都可直接访问,不新增重定向。 +- 自动生成的专题卡片与 ItemList 链接使用 `/topics/{id}/`;后台明确配置的 `moreUrl` 保留原值。 +- 数字资源 ID 的前导零在 canonical 与播放实体 URL 中归一;普通分页仍保留自己的页码。搜索关键词不进行数字归一。 +- canonical 仍仅取配置的永久域名,去除跟踪参数;`/rank/`、`/page/1/` 继续使用既有主地址。 +- 2026-10-22 起内容主地址优先使用接口的非空 slug,缺少时使用 ID。ID 地址仍直接提供内容,其 canonical 与结构化数据指向该内容的 slug 主地址;评论与操作请求继续使用真实数字 ID。 +- 可索引的付费播放页标记 `isAccessibleForFree:false` 与 `hasPart.cssSelector:.play-paywalled-content`,选择器对应真实播放器容器,不改变样式。 +- VideoObject 只在真实媒体源、有效日期与封面均存在时输出。无权观看的媒体地址不进入 SEO 数据;不编造视频日期或媒体源。 + +## 状态码与验证 + +资源缺失和已证明越界的分页继续返回 404;主内容临时故障继续返回 500。noindex 不用来替代错误状态码。本次未修改 sitemap、robots.txt 或接口访问权限。 + +`npm run build` 及全部 274 项 `npm test` 通过。验证覆盖函数级索引矩阵、客户端 head 清理、真实 Next.js 生产服务的初始 HTML、Googlebot/YandexBot 请求、普通分页、评论分页、空列表、别名与前导零、更多栏目、付费章节权限和 JSON-LD。模拟接口不提交真实业务写入;线上响应头和实际收录结果仍以部署后的验证为准。 + +规则参考:[Google canonical](https://developers.google.com/search/docs/crawling-indexing/consolidate-duplicate-urls)、[Google 分页](https://developers.google.com/search/docs/specialty/ecommerce/pagination-and-incremental-page-loading)、[Google 付费内容结构化数据](https://developers.google.com/search/docs/appearance/structured-data/paywalled-content)。 diff --git a/docs/mobile-preview.jpg b/docs/mobile-preview.jpg new file mode 100644 index 0000000..07ba574 Binary files /dev/null and b/docs/mobile-preview.jpg differ diff --git a/docs/sitemap-backend-contract.md b/docs/sitemap-backend-contract.md new file mode 100644 index 0000000..611033d --- /dev/null +++ b/docs/sitemap-backend-contract.md @@ -0,0 +1,222 @@ +# 后端 Sitemap 生成对接规则 + +核对时间:2026-10-22 22:53(Asia/Shanghai)。 + +本文根据当前工作区的前端代码(包含尚未提交的修改)整理固定页面、URL、canonical 和 noindex 规则,供后端实现 sitemap 生成。它描述现有页面行为及生成约定,不表示 sitemap、存储或 Nginx 已完成接入。 + +当前内容主地址仅优先使用符合英文/拼音字符规则的 slug;中文、已编码中文或其他无效 slug 回退到 ID。搜索关键词仍支持中文和特殊字符,但所有搜索地址均排除在 sitemap 外。 + +## 1. 域名与通用约定 + +- `D` 表示正式站点的 origin,即协议、域名和可选端口,不带末尾 `/`。 +- 域名优先取 `config.config.foreverUrlAddr`;无有效值时取 `config.newAddressInfo.foreverUrlAddr`。移除配置中的路径、查询参数和锚点,与前端 canonical 保持一致。 +- 不使用 API 域名、CDN 域名、请求 Host、临时访问域名或本地地址作为页面域名。 +- 生成任务没有有效正式域名时,不猜测域名;暂停本次发布并保留上一份成功结果。 +- 页面 URL 以 `/` 结尾;sitemap 中使用绝对地址 `D + 规范路径`。 +- sitemap 只提交正常、允许索引的页面及其 canonical 地址;别名、参数变体和重复地址不单独提交。 +- API、媒体源、静态资源和健康检查不属于本文的页面 sitemap 范围。 + +## 2. 固定页面清单 + +固定地址候选共 12 个。满足条件时,其 canonical 均为 `D + URL`。列表为空或页面异常时,按后续排除规则处理。 + +| 页面 | URL | 加入 sitemap 的条件 | +| --- | --- | --- | +| 首页 | `/` | 页面正常 | +| 短剧列表 | `/shorts/` | 页面正常且列表非空 | +| 专题列表 | `/topics/` | 页面正常且列表非空 | +| 热播榜 | `/rank/hot/` | 页面正常且列表非空 | +| 新剧榜 | `/rank/new/` | 页面正常且列表非空 | +| 追剧榜 | `/rank/follow/` | 页面正常且列表非空 | +| 关于我们 | `/info/about-us/` | 页面正常 | +| 用户协议 | `/info/user-agreement/` | 页面正常 | +| 隐私政策 | `/info/privacy-policy/` | 页面正常 | +| 常见问题 | `/info/help-center/` | 页面正常 | +| 2257 合规声明 | `/info/2257/` | 页面正常 | +| DMCA 版权声明 | `/info/dmca/` | 页面正常 | + +这些是页面地址,不是需要前端预建的 sitemap XML 文件。 + +## 3. 动态页面 URL 规则 + +| 类型 | 规范 URL | 加入 sitemap 的条件 | +| --- | --- | --- | +| 作品详情 | `/detail/{key}/` | 作品存在,页面正常且未被标记为需登录或禁止索引 | +| 专题详情 | `/topics/{key}/` | 专题存在,页面正常且列表非空 | +| 标签列表 | `/tag/{key}/` | 标签存在,页面正常且列表非空 | +| 更多栏目 | `/config/{key}/` | 首页栏目存在、已启用且类型为 `1` 或 `3`,并具有自己的标题、正文简介和有效内容卡片 | +| 单集播放 | `/play/{作品key}/{chapterId}/` | 满足下述播放页条件 | +| 内容列表分页 | `{列表基础路径}page/{n}/` | `n >= 2`,分页真实存在且允许索引 | + +### 3.1 内容标识 + +- `{key}` 优先使用内容接口返回的有效英文/拼音 `slug`;无有效 slug 时使用正整数 ID。前端不会自动把中文转成拼音。 +- slug 去掉首尾空格后,必须满足 `/^[a-z\d][a-z\d_-]*$/i`:以 ASCII 字母或数字开头,其余只能包含 ASCII 字母、数字、连字符 `-`、下划线 `_`。 +- 使用接口返回的规范大小写,作为一个路径段按 `encodeURIComponent` 的语义编码一次,避免重复编码;不得把编码后的中文当作英文 slug。 +- 中文、中文英文混合、内含空格、斜杠、点号、百分号、问号等不符合正则的 slug 均回退到 ID。`-city` 和 `_city` 也不符合开头规则。 +- 历史中文 slug 地址在能够匹配到真实内容时仍可读取,但 canonical 和新生成链接使用 ID;后端不应继续向 sitemap 写入这些兼容地址。 +- 纯数字路径按 ID 查询,不按 slug 查询。当前生成正则本身未单独排除纯数字 slug,因此后端应避免分配此类 slug;存量数据须核对身份,不能直接当作 slug 发布。 +- 使用数字 ID 时去除前导零;ID 必须是正整数,且处于 JavaScript 安全整数范围内。 +- `{chapterId}` 是真实章节 ID,不是集数,也不支持章节 slug。章节必须属于 URL 中的作品。 + +以作品 `id=9` 为例: + +| 接口返回的 slug | 生成的详情路径 | +| --- | --- | +| `city-story` | `/detail/city-story/` | +| `city_story-2026` | `/detail/city_story-2026/` | +| `dushiaiqing` | `/detail/dushiaiqing/` | +| `City-Story` | `/detail/City-Story/` | +| `都市爱情` | `/detail/9/` | +| `city-爱情` | `/detail/9/` | +| `%E9%83%BD%E5%B8%82` | `/detail/9/` | +| `city story`、`city/story`、`city.story`、空值 | `/detail/9/` | + +以上规则同时用于作品、专题、标签和更多栏目;播放页的作品段使用同一规则,章节段仍使用章节 ID。 + +### 3.2 播放页条件 + +播放页同时满足以下条件时才可进入公共 sitemap: + +1. 作品及章节真实存在,且两者对应。 +2. 有作品名和真实的正整数集数。 +3. 公开抓取身份有权观看当前章节。 +4. 存在有效的当前章节正片地址。 + +共享预告、继承的作品简介、章节 SEO 标题或纯解锁提示不能单独满足条件。收费类型本身不是排除依据,但不能使用管理员、VIP 或已购用户的个人权限生成公共 sitemap。 + +sitemap 提交页面 URL,不写入用户凭证或受限媒体源。 + +### 3.3 列表分页 + +- 普通内容目录当前每页 24 条,后端根据有效总数计算真实页数。 +- 更多栏目当前使用有限首页栏目快照:类型 `1` 取实际作品列表,类型 `3` 取实际专题列表,以实际条目数为 `totalCount`。SEO 层取 `pageSize = max(24, totalCount)`,因此当前只发布栏目基础地址,不生成 `/config/{key}/page/2/` 等额外分页。 +- 第一页只提交基础地址,不额外提交 `/page/1/`。 +- 第 2 页及以后保留自己的 canonical,例如 `/shorts/page/2/`,不统一归并到第一页。 +- 作品详情和播放页的 `/page/{n}/` 是评论分页;`n > 1` 时不进入 sitemap。 +- 搜索、收藏、历史、已购和充值即使有分页,也不进入 sitemap。 + +## 4. Canonical 规则 + +| 场景 | 规范化规则 | +| --- | --- | +| 正式域名 | 统一使用 `D`,与前端页面 canonical 的域名一致 | +| 尾斜杠 | 页面路径以 `/` 结尾 | +| 查询参数和锚点 | canonical 不含查询参数和锚点;sitemap 同样不提交这些变体 | +| 第一页 | `/shorts/page/1/` 规范化为 `/shorts/`;其他支持分页的页面同理 | +| 有效列表分页 | 第 2 页及以后自引用,保留 `/page/{n}/` | +| 默认榜单 | `/rank/` 规范化为 `/rank/hot/`;`/rank/page/2/` 规范化为 `/rank/hot/page/2/` | +| 专题别名 | `/theme/{key}/` 规范化为 `/topics/{规范key}/`,保留有效分页 | +| ID 与 slug | 有符合英文/拼音字符正则的 slug 时,ID 地址指向 slug 主地址;中文或其他无效 slug 回退到规范 ID | +| 历史中文 slug 地址 | 能匹配到资源时保留兼容读取,canonical 使用该资源的规范 ID 地址 | +| 数字 ID 前导零 | 无 slug 时,`/detail/009/` 规范化为 `/detail/9/`;其他数字内容 ID 同理 | +| noindex 页面 | 当前前端不输出 canonical,不进入 sitemap | + +例如作品 `id=9`、`slug=city-story`,章节 ID 为 `90`: + +```text +/detail/9/ -> D/detail/city-story/ +/detail/city-story/ -> D/detail/city-story/ +/play/9/90/ -> D/play/city-story/90/ +/play/city-story/90/ -> D/play/city-story/90/ +``` + +例如专题 `id=7`、`slug=city-collection`: + +```text +/theme/7/ -> D/topics/city-collection/ +/topics/7/ -> D/topics/city-collection/ +/theme/7/page/2/ -> D/topics/city-collection/page/2/ +``` + +例如作品 `id=9`、`slug=都市爱情`,章节 ID 为 `90`,中文仅用于展示或兼容读取: + +```text +/detail/9/ -> D/detail/9/ +/detail/都市爱情/ -> D/detail/9/ +/play/9/90/ -> D/play/9/90/ +/play/都市爱情/90/ -> D/play/9/90/ +``` + +上述中文路径按可读形式展示,实际请求可使用百分号编码;sitemap 只写入右侧 ID 主地址。 + +别名页面仍可允许索引,canonical 归并不等于 noindex,也不表示已有重定向。sitemap 只提交最终 canonical 地址,并按完整地址去重。 + +`/home`、`/playlet`、`/list`、`/topic`、`/application` 等值只用于后台导航值转换,不是额外公开页面,不应据此生成 sitemap 地址。 + +## 5. 固定 Noindex 页面 + +| 页面范围 | URL | 当前 robots | +| --- | --- | --- | +| 搜索和搜索分页 | `/search/`、`/search/{关键词}/`、`/search/{关键词}/page/{n}/`、`/search/?q={关键词}`、`/search/page/{n}/?q={关键词}` | `noindex,follow` | +| 独立入口 | `/navigation/` | `noindex,follow` | +| 回家的路 | `/home-road/` | `noindex,follow` | +| 福利应用 | `/apps/` | `noindex,follow` | +| 个人中心 | `/member/center/`、`/info/user-center/` | `noindex,nofollow` | +| 收藏 | `/member/favorites/`,包括分页 | `noindex,nofollow` | +| 历史 | `/member/history/`,包括分页 | `noindex,nofollow` | +| 已购 | `/member/purchased/`,包括分页 | `noindex,nofollow` | +| 账号设置 | `/member/settings/`、`/info/settings/` | `noindex,nofollow` | +| 充值及 VIP | `/info/deposit/`、`/info/deposit/vip/`,包括支持的分页 | `noindex,nofollow` | +| 意见反馈 | `/info/feedback/` | `noindex,nofollow` | + +以上页面及其参数变体均不进入 sitemap。 + +### 5.1 当前搜索地址的排除规则 + +- 新生成的搜索入口统一使用 `/search/{关键词}/`;关键词先去掉首尾空格,再通过 `encodeURIComponent` 整体编码为一个路径段。 +- 关键词可以包含中文、空格、斜杠、问号、百分号等字符。搜索关键词不受内容 slug 的 ASCII 正则限制,也不进行数字 ID 归一。 +- `/search/?q={关键词}` 和 `/search/page/{n}/?q={关键词}` 仍可解析;后续分页链接生成路径形式并移除 `q` 参数,不依赖重定向完成转换。 +- 路径本身已有关键词时优先使用路径关键词;只有路径没有关键词时才从 `q` 读取。 +- 不论入口形式、搜索结果数量或关键词字符,搜索页始终 `noindex,follow`,不输出 canonical,也不进入 sitemap。 +- 无关键词的 `/search/` 可以访问;无关键词且页码大于 1 时返回 404。 + +## 6. 条件性 Noindex 与异常处理 + +| 情况 | 当前前端行为 | 后端生成处理 | +| --- | --- | --- | +| 空公开列表 | 接口成功确认 `totalCount=0` 时,第一页 `noindex,follow`,仍返回 200 | 不提交 | +| 详情或播放页评论分页 | `/detail/{key}/page/{n}/`、`/play/{key}/{chapterId}/page/{n}/`,`n>1` 时 `noindex,follow` | 不提交 | +| 非默认列表排序 | 除榜单和更多栏目外,公开列表 `sort != 1` 时 noindex | 只生成无参数的默认地址 | +| 非法列表排序 | 公开列表的 `sort` 不属于 `1、2、3` 时 noindex;未传时默认 `1` | 不生成非法变体 | +| 非默认评论排序 | 存在 `commentSort` 且值不等于字符串 `2` 时 noindex | 不生成参数变体 | +| 更多栏目内容不足或无效 | 已存在栏目缺少自己的标题、正文简介或有效卡片时 `noindex,follow`;找不到启用的类型 `1/3` 栏目时按资源缺失处理 | 不提交 | +| 播放页条件不足 | 预告、纯解锁提示、无有效正片或无观看权限时 `noindex,follow` | 不提交 | +| 明确禁止索引或需要登录 | `indexable=false` 或 `loginRequired=true` 时 noindex | 不提交 | +| 无效路由或不存在的资源 | 返回 404,且无 canonical | 不提交 | +| 已确认越界的分页 | 返回 404,且无 canonical | 不提交 | +| 临时取数故障 | `failed` 或 `unavailable` 时返回 500,且无 canonical | 不将故障误判为删除;保留上次成功结果,稍后重试 | + +补充约定: + +- 榜单和更多栏目可以接受 `sort=2/3`,但 canonical 仍不含参数;sitemap 不重复提交这些参数地址。 +- 作品详情的评论数为 0,不等于作品内容为空,不能按空列表规则排除作品详情。 +- 不存在的内容与临时故障必须区分;无法可靠判断一批内容时,不发布因取数失败而缺项的新结果。 + +## 7. 建议的生成与发布约定 + +1. 合并本文的固定地址与后端数据库枚举的动态内容。 +2. 按公开访问时的页面状态判断是否允许索引。 +3. 按英文/拼音 slug 白名单和 ID 回退规则生成最终 canonical URL,不输出参数、锚点、别名、中文内容 slug 或非页面资源。 +4. 按完整 canonical URL 去重,只发布正常、有效的页面地址。 +5. `lastmod` 有可靠数据时使用页面内容实际更新时间;没有时省略,不统一伪造为生成时间。 +6. 生成文件保存到前端发布目录之外的持久化存储,不写入前端 `public` 或 `.next`。 +7. 完成分片生成和可访问性校验后,再切换 sitemap 索引;失败时保留上一份成功版本。 +8. 建议固定入口为 `/sitemap.xml`,分片通过 `/sitemaps/` 下的通用路径提供访问。现有入口不同时,优先保留已提交的地址。 +9. `robots.txt` 中声明正式站点的 sitemap 索引地址;前后端 URL 或索引规则变更时同步更新生成逻辑。 + +首次接入需要部署访问配置;后续内容和分片变化不应要求前端预留文件、重新构建或重启 H5。 + +## 8. 代码依据 + +- [路由解析、内容路径和分页支持](../src/lib/routing.ts) +- [域名、canonical、noindex 和页面状态判断](../src/lib/seo.ts) +- [服务端资源核对与 SEO 元数据输出](../src/lib/seo-server.ts) +- [专题解析、首页栏目快照和列表分页](../src/lib/client/channel.ts) +- [章节权限与正片地址判断](../src/lib/client/playback.ts) +- [固定信息页](../src/lib/info-pages.ts) +- [现有页面索引策略](indexation-policy.md)(其“非空 slug 优先”描述需结合当前代码的英文/拼音字符白名单理解) +- [SEO 规则回归用例](../tests/seo.test.mjs) +- [Slug 路由与 canonical 回归用例](../tests/slug-routing.test.mjs) +- [路由与搜索参数兼容回归用例](../tests/routing.test.mjs) +- [分页与搜索路径回归用例](../tests/pagination.test.mjs) diff --git a/docs/verification.md b/docs/verification.md new file mode 100644 index 0000000..11b8ff9 --- /dev/null +++ b/docs/verification.md @@ -0,0 +1,173 @@ +# API 对接验证 + +日期:2026-10-17。 + +## 2026-10-22 测试与生产环境配置 + +本项目增加 `config/test.env`、`config/production.env`,保留独立的开发配置。进程变量优先,其次为对应 `*.local.env`,最后为基础配置;本地覆盖文件忽略提交。接口均沿用 `https://91av-wz.80sygtm.com`,默认端口均为 21021。测试部署使用 `APP_ENV=test` 和 `NODE_ENV=production`,构建目录为 `.next-test/`;生产目录为 `.next/`。开发目录为 `.next-dev/`。SEO 快照随对应目录隔离,同 API 的两个环境也不会共用快照。 + +`npm run build:test`、`npm run check` 通过,全部 290 项测试通过。新增测试覆盖配置优先级、环境隔离、CLI 参数、错误配置、真实子进程退出状态,以及同上游的 SEO 快照隔离。另通过启动脚本分别启动两套实际 Next.js 服务,使用临时端口和本地模拟上游验证静态资源及 API 代理均返回 200,验证结束后关闭临时服务。未执行远程部署。 + +## 2026-10-22 slug 路由 + +本项目(`JHA-1047`)的内容链接、轮播、专题/标签/栏目、搜索、书架卡片、选集、评论分页及解锁后跳转统一优先 slug,缺少时用 ID。支持 slug 地址直接访问与刷新,保留原 ID 地址直接访问;业务写入及仅接受 ID 的读取继续使用真实数字 ID。专题详情按当前文档先从分页专题目录解析 ID,不向接口发送未定义的 slug 参数。 + +生产构建及全部 283 项测试通过。新增回归覆盖 URL 编码、中文及特殊字符、分页目录解析、目录异常与缺失资源、canonical/JSON-LD 一致性、客户端选集与解锁跳转,以及严格 Swagger 参数约束下的 Next.js 生产响应。当前 Swagger 的 slug 查询参数已只读核对;测试使用本地模拟接口,未提交真实付费解锁、订单或评论。 + +## 2026-10-21 跳转性能优化 + +公共数据服务增加短期进程内缓存和并发合并:配置、页脚、导航、分类及标签选项 60 秒,首页 30 秒;按上游及查询参数隔离,返回独立副本并限制条目数量。无效数据、错误响应、认证和私人业务读取不缓存,配置/首页可在刷新失败时使用最近一次有效数据。详情、章节、选集、评论和推荐在响应详情之前并行请求,仍在服务端输出完整 HTML。 + +浏览器直接复用首屏公开配置及页脚,SDK 配置不序列化。公开页从浏览器前进后退缓存恢复时保留文档,后台按当前 Cookie 恢复身份并补报页面访问;账户、播放、支付及打开弹窗的页面保留重新验证。类型检查、生产构建及全部 258 项测试通过,包括跨请求缓存、并发合并、过期、错误恢复、副本隔离、缓存边界、两个账号隔离、首屏配置白名单、并行请求屏障、后退恢复与埋点。 + +真实接口及 29 个页面检查通过。本机短期采样中,公共缓存预热后列表约 312ms、专题约 287ms、回家的路约 143ms(首字节中位数,各 3 次);首次游客会话曾超时,另一次交替对比也出现显著上游波动,不能将上述采样当作稳定延迟承诺。未提交真实订单、付费解锁、评论或反馈,未执行浏览器视觉验收。 + +## 2026-10-21 服务端直接渲染 + +请求与渲染方式:页面数据在服务端准备,页头、公开列表、搜索、详情/播放、应用目录、回家的路、反馈、个人中心及设置、充值/VIP 的正文随初始 HTML 返回。站内链接、分页和搜索使用原生文档请求,保留当前页面直到新响应到达;不再清空正文显示页面加载占位。浏览器复用首屏快照绑定交互,避免启动时重复读取整页数据。提交成功后重新请求当前页面,轮播、图片解密、实时权限、解锁和支付确认保留。 + +Proxy 为有效业务页面准备身份,并在本次请求中传递新 Cookie,供服务端读取账户内容;同一 Cookie 同时写回浏览器。客户端复用服务端用户快照,避免再次初始化游客;正式账号失效显示登录入口,服务异常直接提供重试。外部传入的内部身份头先清除,用户、列表、套餐和流水使用明确字段白名单,凭证不写入 HTML;账户响应继续禁止缓存和索引。独立 `/navigation/`、资源、无效地址和尾斜杠重定向不触发该会话流程。 + +类型检查、生产构建及全部 249 项测试通过,覆盖完整首屏正文、原生跳页保留旧文档、快照绑定不重复取数、两个账号隔离、伪造身份头、正式账号过期与退出、Cookie 传递、套餐及流水、播放器与广告/轮播清理。真实只读接口及 29 个页面检查通过,另确认真实免费与金币章节页面输出完整 HTML。没有创建真实订单、付费解锁、提交评论或反馈;未执行浏览器视觉验收。 + +## 2026-10-21 动态导航 + +顶部与侧栏共用 `navigation/header` 的服务端数据,移动抽屉继承同一菜单,底栏按同源数据生成。保留接口名称、顺序、图标和二级条目;过滤底部类型、不安全地址及已移除的独立剧集地址。首屏 HTML 已包含真实链接,原脚本不再插入固定顶部导航或覆盖接口名称。已知 API 的 `/hot` 与空系统入口直接生成本站路径,不新增跳转。 + +路由高亮由同一纯函数处理首屏及客户端导航:首页精确匹配,列表分页保持选中,排行榜覆盖三个榜单,具体专题及带查询条件的入口按对应地址匹配,顶部不借用详情页的列表高亮。接口空列表不回填静态菜单;异常只提示导航暂不可用,正文仍能加载。构建、类型检查和全部 248 项测试通过,覆盖动态顺序/名称、二级条目、外链、空列表、异常、旧剧集过滤和高亮规则。 + +## 2026-10-21 移除独立剧集 + +按后续确认移除桌面顶部、侧栏、移动端的“剧集”导航,以及独立剧集列表、视频详情和对应分页路由;旧地址无论是否带尾斜杠均直接返回 404,不请求内容接口,也不重定向。首页与 JSON-LD 不再展示旧类型的视频栏目。短剧详情、选集、金币解锁和章节播放保留。构建、类型检查与全部 242 项测试通过,包括入口清理、旧地址 404 和原有播放/解锁回归。 + +## 2026-10-21 当前 Swagger 对接 + +按 `91av-wz.80sygtm.com` 当前文档与实际只读响应核对页面接口,端点和参数映射见 [页面接口对照](api-contract.md)。修正短剧列表、专题顶层字段、题材筛选、搜索联想与空结果推荐、详情推荐及章节 SEO/统计字段;删除旧接口代理依赖。首页“更多”保留已确认的 moreUrl 优先规则,未配置时只展示真实首页栏目的数据。按用户后续要求移除独立剧集导航和路由,旧地址及分页直接返回 404;短剧章节播放保留。 + +构建、类型检查及全部 240 项接口合同与回归测试通过。真实服务验证游客身份、列表/三个榜单、专题、标签、配置、应用、套餐、个人列表和余额记录;另核对 29 个原生页面响应与实际有内容的免费/金币章节(`/play/10923/12723/`、`/play/10923/12726/`)。没有创建真实订单、付费解锁、注册账号或提交评论、反馈。未执行新的浏览器视觉验收。 + +## 2026-10-20 充值确认弹窗 + +恢复原 HTML 的支付标题/副标题、订单卡片、基础/赠送/合计金币、金额徽标、渠道图标按钮及选中圆点、确认按钮和底部说明,并复用原稿的尺寸、间距、圆角及移动布局。支付宝、微信、USDT 图标使用原始本地素材;渠道仅来自套餐配置,其他渠道保留名称和通用图标。使用隐藏表单值连接样式按钮和原下单参数,金币充值与 VIP 接口保持不变。 + +`tests/payment.test.mjs` 验证实际金币字段及金额换算、不注入演示赠送或未配置渠道、方法切换后的订单参数、重复提交拦截、VIP 接口、无效支付地址、无支付方式以及旧表单响应隔离。类型检查、生产构建和全部 152 项测试通过,仅使用本地模拟上游,没有创建真实订单或发起真实支付。 + +## 2026-10-20 详情选集解锁 + +对照参考项目 `playletser/chapter.go` 的列表权限规则和 `pages/play.js` 的解锁处理,金币集数使用按钮直接打开 `play-episode-lock`,保留原 HTML 的金币图标、余额/消耗摘要、确认和充值入口样式。弹窗先获取目标章节详情,再展示实际价格及余额;仅确认后提交解锁,成功刷新余额并跳转所选真实章节。已可观看和会员章节按最新权限处理,余额不足和登录过期分别进入充值或登录。 + +新增测试覆盖:列表权限优先级、详情页弹窗不提前跳转/扣费、最新价格覆盖列表旧价格、正确章节 ID、重复点击及确认防重、支付中禁止切换、迟到响应隔离、关闭/离开页面、余额不足、已可观看/VIP、章节信息不匹配、登录失效、失败重试,以及实际 Route Handler 的凭据和解锁参数转发。类型检查、生产构建与全部 147 项测试通过;测试仅连接本地模拟上游,未提交真实金币解锁。 + +## 2026-10-20 详情页样式调整 + +区块留白按原稿保留:横幅上下桌面端 26px、手机端 22px,官方链接至评论区 28px。无横幅数据时,选集与官方链接之间仍保留对应间距,不因条件渲染移除广告区而贴合。 + +对照原 HTML 的 `overviewMarkup`、操作栏、选集和评论 DOM 恢复结构,复用已有桌面/移动样式及原始 SVG 素材;修复 API 按钮属性与原样式选择器不一致的问题。简介支持三行截断与展开/收起,尺寸变化及离开页面时更新和清理;选集分段只横向调整滚动,保持桌面/手机切换前的章节位置。评论使用原稿的头像、正文、时间/操作行和嵌套回复容器,保留接口和独立分页路由。 + +`tests/detail.test.mjs` 验证动态字段与文本转义、图片解密入口、真实章节链接、配置官方链接和简介展开/尺寸变化/清理;播放测试补充详情页点赞接口、评论回复结构及移动端集数分段。类型检查、生产构建和全部 137 项测试通过。浏览器视觉复核仍受访问工具策略限制,未生成新的浏览器截图。 + +## 2026-10-20 登录逻辑调整 + +游客登录 403 当时通过匹配实际 HTTP Host 和协议修复了本地监听地址误判。2026-10-26 正式站点复核发现:空参数登录请求不带 Origin 时返回 400,携带正式域名 Origin 时却被本地检查拦截为 403。因此现已按 `jha-1045-hgdjnet-web` 移除代理层 POST 来源拦截,并以 CDN 域名、内部 Host 和协议变化的真实 HTTP 集成测试替代原来源校验单元测试;上游权限错误仍正常返回。 + +修复后对用户正在运行的 `http://localhost:3000/api/app/auth/guest` 进行一次正常游客握手,携带浏览器 Origin 和设备标识,真实响应为 HTTP 200 / code 200,返回用户信息及会话 Cookie。未记录或展示登录凭证,未提交正式账号注册、密码或其他业务写操作。 + +对照参考项目的 `common/auth.js`、登录/绑定弹窗及 `authser` 调整游客初始化、账号切换、绑定、失效恢复和退出。`tests/auth.test.mjs` 验证账号/邮箱和密码校验、密码确认、协议、设备持久化、首次渠道与邀请码、存储不可用、游客请求合并、登录响应顺序、失败保留原身份、旧请求隔离、正式账号过期只提示一次、手动退出,以及实际弹窗事件的防重复提交和密码清理。 + +HTTP 集成测试另验证真实 Route Handler 的登录与绑定参数、游客 Authorization、可选邮箱补全、设备头与客户端 IP、getInfo 不返回 token 时续用原凭证、正式账号初始化过期不降级、游客 GET/POST 失效最多重试一次、HTTP 401、缺少 token 的异常登录响应、Cookie 清理和退出后一次自动登录跳过。类型检查、生产构建和全部 127 项测试通过。测试只连接本地模拟上游,没有提交真实账号登录、绑定或密码重置。 + +## 真实接口 + +通过本地 Next.js 代理验证首页、配置、底部信息、短剧运营列表、热榜、专题、热搜、应用列表、充值套餐和搜索接口,均返回业务码 200。首页使用服务端请求的实际内容,首页筛选项保持移除。 + +上游 `POST /api/app/auth/guest` 在直接请求和代理请求中均出现超时(最长等待 25 秒)。无有效 token 的章节列表返回业务码 1000。真实登录态下的播放、用户资料、评论、收藏、订单和金币解锁尚未完成端到端联调;没有向真实接口提交评论、支付订单或解锁请求。 + +## 本地集成测试 + +`npm test` 启动独立模拟上游和 Next.js 生产服务,检查: + +- 服务端渲染使用上游标题和栏目;API 文本转义;首页筛选项未恢复。 +- 首页广告列表显式返回 `null`、轮播/栏目/公告为非数组、数组夹有空项时仍返回 200;有效内容继续显示,空内容显示空状态。 +- 纯站点地址和带 `/` 的根地址均直接返回首页及 200,无 Location 响应头;首页入口指向根路径,短剧入口单独指向 `/shorts/`。 +- 游客 token 仅写 HttpOnly Cookie,不返回到前端脚本;设备头按参考格式发送。 +- 公共请求隔离过期 token;兼容二次编码的 JSON data。 +- 正式账号会话失效后,写请求不会自动降级为游客重试。 +- 接口白名单、请求方法、跨站写入拒绝。 +- 保留真实章节 ID、观看权限和单集金币数。 +- 退出登录清理会话 Cookie,不调用上游业务写接口。 +- 30 个页面路径以 `/` 结尾,直接返回 200 且没有 Location 响应头;不带尾斜杠的地址也直接返回 200;`/shorts/` 刷新返回 200;无效路径返回可见的 404;首页和业务页面链接直接包含尾斜杠。 +- `/navigation/` 直接访问与刷新返回 200,独立渲染入口线路、渠道和 App 下载链接,不加载业务页面运行脚本;首页及其余 30 个路径均不包含入口导航弹窗或隐藏页面的等待状态。 +- 顶部、悬浮工具和首页底部的下载入口在配置未知时保持隐藏;页面不包含悬浮二维码面板。独立入口页在下载地址为空或无效时不渲染下载按钮。 + +`tests/routing.test.mjs` 另检查所有路由类型、中文和含斜线的搜索词、hash 地址不转换或替换、站内链接识别,以及 History 导航的事件通知、重复地址和前进后退记录。 + +全部分页使用 `原页面路径/page/页码/`,第一页链接使用原路径;页码、上一页和下一页均为真实链接。`tests/pagination.test.mjs` 覆盖短剧、剧集、专题/题材/标签/栏目、榜单、搜索、个人收藏/历史/已购、充值和 VIP 余额明细,以及详情/播放页的评论分页,检查页码解析、首尾页、无效页码、中文搜索词、排序保留和会员接口参数。播放器测试检查评论页码与排序、真实分页链接;HTTP 集成测试检查 20 类路径的两种尾斜杠形式均直接返回 200、刷新不重定向,以及非法分页路径返回 404。本次生产构建及全部 109 项测试通过。 + +排行榜独立使用 `/rank/hot/`、`/rank/new/`、`/rank/follow/`,对应接口 `rankType=1/2/3`。`tests/rank-pages.test.mjs` 检查真实页面路由逻辑、链接与当前页高亮、标题、刷新和翻页后的榜单类型,以及加载中和请求失败时保留导航;HTTP 集成测试检查三条路径两种尾斜杠形式均直接返回 200、独立标题且没有 Location 响应头。原 `/rank/` 仍直接展示热播榜,不重定向。此次生产构建和全部 100 项测试通过。 + +`tests/download.test.mjs` 检查下载地址优先级和有效性、各下载入口随配置显示/隐藏、动态插入入口,以及桌面端直接打开链接、移动端点击弹窗与组合键保留浏览器默认操作。 + +`tests/render.test.mjs` 检查广告、首页列表及嵌套栏目中的空值和类型异常,保留有效内容顺序及 HTML 转义;弹窗测试另覆盖广告队列中的空项和非对象项。 + +`tests/images.test.mjs` 使用参考算法的固定密文验证逐字节解密、100 字节边界、MIME 识别和普通图片兼容,并检查重复请求合并与缓存、并发和优先级、失败重试、超时取消、异步切图、Blob 释放、初始/动态节点和视口懒加载。渲染与 SSR 测试另确认接口图片不直接写入 `src`,视频封面使用 `data-poster`。 + +2026-10-19 只读抽取真实首页接口的 3 张图片验证,均返回 HTTP 200,按参考密钥解密后识别为 WebP;图片 CDN 返回 `Access-Control-Allow-Origin: *`。未保存原始图片,也未提交业务写请求。 + +`tests/playback.test.mjs` 覆盖播放地址及签名保留、观看权限优先级与旧字段、正片/预告切换、Safari/Chromium/移动壳浏览器选择、HLS 恢复上限、重试重建、切换路由与异步导入竞态、拒绝自动播放后的重试、预告不计入正片记录、真实章节 ID、解锁确认与防重复提交,以及实际控件脚本的播放/重试/解锁事件。HTTP 集成测试覆盖媒体清单、密钥端点两种尾斜杠形式、原始 AES 字节、凭据隔离、Range 和错误状态。 + +H.264/H.265 兼容测试另覆盖 MSE/原生 HLS 编码错误回退、签名与播放位置保留、重复错误及重试次数限制、网络错误区别处理、MP4 不误走 HLS,以及回退过程中的路由取消。`tests/codec-manifest.test.mjs` 运行真实 hls.js 1.7.3 完整版的清单解析和 LevelController,在模拟设备能力下验证 H.264 回退、`hvc1`/`hev1` 保留、HEVC 单编码清单接受/拒绝;测试不访问网络,不代表真实浏览器或硬件的解码实播结果。 + +2026-10-19 只读验证真实免费预告:清单返回 200,保留 `id/t/ticket` 参数后密钥代理返回 200 和 16 字节 AES 密钥,首片成功通过 AES-128-CBC 解密,输出首字节为 MPEG-TS 同步字节 `0x47`。不带签名参数的密钥请求会被上游拒绝。本次未保存密钥/媒体,也未提交真实观看上报、解锁或订单请求;真实账号付费章节和浏览器实播仍需联调。 + +`tests/startup.test.mjs` 对照参考项目的实际脚本规则验证:会话内首次年龄确认、确认早于配置返回、完整广告队列、刷新恢复进度、相同队列不重复弹出、广告码变化后重置、无效图片与失败图片跳过、全部失败后可重试、存储不可用、过期异步结果隔离,以及图片加载超时和取消。DOM 事件适配测试覆盖年龄弹窗禁止 Esc 关闭、广告 Esc/按钮切换下一条、广告链接与展示/点击事件,以及退出行为。 + +浏览器访问工具受 URL 策略限制,本次未进行浏览器视觉复核。目录中的桌面、手机截图属于原 HTML 迁移阶段的布局基线,不是当前 API 数据截图。 + +## 2026-10-22 部署脚本与本地发布包 + +- `bash deploy_test.sh --package-only` 完成测试环境构建、294 项回归测试和 zip/SHA-256 生成;`bash deploy_prod.sh --package-only` 完成生产构建、299 项回归测试和 Linux x64/glibc 包/MD5 生成。构建包含 TypeScript 检查。 +- 两份脚本的 `--dry-run` 通过命令替身验证,不执行构建、SSH 或 SCP;缺失/非法部署目标在构建前拒绝。测试和生产共用构建锁。 +- 发布包测试覆盖基础配置隔离、本地覆盖/缓存/旧 dist 排除、符号链接目标保护。Next.js 自动追踪漏带的 Turbopack Route Handler 运行文件已显式包含。 +- 最终 `dist.zip`(29.87 MiB)解压到临时目录,使用本机裁剪依赖启动 `node start.mjs`;本地模拟 API 下首页、`/healthz`、Next 静态资源和 public 文件均返回 200,健康检查未触发上游请求。测试 zip 按约定不含依赖,真实 Linux 服务器需执行 `npm ci --omit=dev`。 +- 最终 `web_91dj_h5.tar.gz`(44.94 MiB)通过内容、MD5 和目标平台元信息检查;目标 Sharp/libvips 使用 lockfile 固定版本,并通过 Linux x64 ELF 校验,不含 macOS 原生模块。本机未运行 Linux 生产服务。 +- `deploy/up_next_h5.sh` 用临时目录和命令替身验证校验失败、安装失败不停止旧服务;健康检查失败恢复旧版本;成功更新保留测试私有覆盖和公开 SEO 缓存。新增部署测试共 8 项通过,已包含在最新生产环境的 299 项回归中。 +- 本次仅本地构建和打包,未执行任何真实 SSH/SCP、Supervisor 更新或远端 `running.sh`。部署前需配置本站目标地址,测试 Supervisor 程序及生产跳板机脚本由运维预先配置。 + +部署配置统一采用本站标识:测试 `root@172.104.181.99:22889` → `/server/web_91dj/h5`;生产 `server@116.212.120.2` → `/home/server/web_91dj/h5/nuxt`,SCP 22889 / SSH 22。包名 `web_91dj_h5.tar.gz`、校验文件和 Supervisor `91dj_next_h5` 使用本站标识;生产仍只通过完整路径调用原有 `running.sh`。部署测试验证当前配置、环境变量覆盖、无效目标提前失败及 dry-run 不连接远端;本次未执行实际上传或部署。 + +### 部署配置变化后的测试修复 + +已复现 `tests/deploy.test.mjs:72` 的目录断言失败:测试仍固定期望旧项目的上传目录,实际测试脚本已配置 `/server/web_91dj/h5`。修复后不再将主机、路径、端口或 API 域名作为测试不变量,保留对参数覆盖、命令与帮助输出一致、非法目标提前失败、dry-run 不构建/不连接远端的验证。测试服更新的模拟 Supervisor 使用固定测试服务,避免调用者的 `DEPLOY_PROGRAM` 污染测试。两份脚本的帮助内容统一从当前变量生成,修复当时测试 SSH 配置 21021、帮助文案却写 22889 的不一致;保留修复时的上传路径和端口。 + +`bash deploy_test.sh --package-only` 已完成测试环境构建、全部 299 项测试和 `dist.zip` 打包;压缩内容及 SHA-256 通过校验。另在设置自定义 `DEPLOY_PROGRAM`、`DEPLOY_DIR`、`DEPLOY_SSH_PORT` 后运行部署专项测试,8 项通过。当前 SSH 端口和应用监听端口分别配置,未连接真实服务器验证 SSH 端口或执行上传/部署。 + +### 测试部署改用 tar.gz + +`deploy_test.sh` 现在生成并上传 `web_91dj_h5.tar.gz`、`web_91dj_h5.tar.gz.sha256` 和 `up_next_h5.sh`,包名可通过 `DEPLOY_PACKAGE_NAME` 覆盖;同时刷新对应 MD5 文件供本地兼容校验。测试包继续排除依赖、本地覆盖、缓存和 macOS 附加文件,包内使用 `.next-test` 与 `config/test.env`。保留当前上传目录和端口,以及用户注释的远端自动更新命令;实际脚本上传完成后结束。 + +服务器辅助脚本同步使用 tar.gz:先校验 SHA-256、检查成员路径与类型,再解压、安装依赖和切换版本;损坏包、路径越界、链接及特殊文件在停止旧服务前拒绝。测试覆盖真实 tar.gz 上传参数、校验值、自定义包名、自动更新保持禁用,以及安装/健康检查失败的回滚。 + +完整本地构建和 304 项测试通过;测试部署专项 13 项通过。生成的测试 tar.gz 为 29.78 MiB、414 个文件,内容、SHA-256 和 MD5 均通过校验。解压后使用本机裁剪依赖和本地模拟 API 验证启动、首页、健康检查、Next 静态资源与 public 文件均返回 200。未执行真实上传或服务器更新。 + +## 2026-10-22 埋点检查与补全 + +通过项目自带的官方 SDK v1.2.0 复现了内容事件因空 `parent_item_id` 被拒收的问题;此前测试替身未校验字段,所以没有发现。现在回归测试调用真实 SDK 的具名事件方法,仅使用其初始化前缓存和字段校验,不向外发送测试数据。只读获取当前业务接口配置并在本机解析后,确认页面、内容、搜索、广告、弹窗行为、播放事件均在事件允许列表内;未修改服务端配置或打印密文、签名密钥。 + +| 范围 | 事件与检查结果 | +| --- | --- | +| 页面、分页、后退恢复 | `appPageView/pageClick`,保留真实路径及原有访问去重规则 | +| 导航与下载 | `navigation`,补齐独立入口页渠道、App 下载入口选择器 | +| 首页及列表内容 | `appPageitemExposure/appPageitemClick`,无父级用字符串 `0`,通过真实 SDK 校验 | +| 专题、福利应用 | 补齐专题卡片内容元数据;关联 `advitiseId` 的应用按广告上报,其他应用按内容并携带分类父级 | +| 大图广告 | `adImpression/adClick`,保留原展示、点击触发;独立展示位置及延迟 SDK 队列防止漏报 | +| 宫格广告 | 与首页分开去重,滚动后补报可见项;关闭、遮挡或后台标签页不记曝光,恢复后重新观察 | +| 弹窗行为 | 补齐参考项目的 `advertising`:show/click/close,使用真实广告 ID;关闭不记 adClick,重复关闭不重报 | +| 搜索 | `keywordSearch/keywordClick`,保留真实总数、首轮搜索及分页后的全局位置 | +| 播放、切集、分享、失败 | `dramaEvent/dramaStatusChange`,全部现有行为的载荷通过真实 SDK 校验,预告不作为正片统计 | +| 登录、游客、注册、充值、VIP | 沿用 SDK 原始公共字段透传及 UID 同步,成功业务事件由服务端负责 | +| 点赞、收藏、评论、购买解锁 | 保留 BFF 校验后的事件上下文和请求 ID 透传,前端不重复发送服务端成功事件 | +| 加载、排队、错误 | 配置首读跳过 SDK 等待;脚本失败重试加载;扩大初始化前队列并报告溢出;SDK 拒收和发送失败有脱敏诊断 | + +浏览器使用真实页面及未修改的官方 SDK,测试专用包装通过 SDK 支持的单次事件地址参数把请求送入本机接收端,避开官方域名解析器对配置地址的 HTTPS 要求。已验证大图广告展示/点击、宫格展示/点击、页面访问、内容曝光,以及原生页面跳转前的内容点击均实际发出带 `event_sig` 的网络记录。UI 点击和弹窗滚动经浏览器执行,未向正式统计后台发测试事件。未验证正式部署的反向代理和统计后台入库结果。 + +最终验证:测试环境构建(含 TypeScript)及全部 323 项回归测试通过。最后一轮本机浏览器验证收到 78 条带签名的 SDK 事件,其中 `advertising` 为 show 21、click 2、close 21;大图广告的三种行为均到达,宫格关闭只覆盖实际曝光的广告,关闭未增加 `ad_click`。另外一轮已确认原生页面跳转前的 `app_pageitem_click` 实际到达,内容父级字段为字符串 `0`。所有网络验证仅使用本地模拟业务数据和本地测试签名配置。 diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..ce4e94a --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,7 @@ +/// +/// +import "./.next/types/routes.d.ts"; +import "./.next/types/root-params.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/next.config.ts b/next.config.ts new file mode 100644 index 0000000..ffb1fb4 --- /dev/null +++ b/next.config.ts @@ -0,0 +1,36 @@ +import type { NextConfig } from "next"; + +const appEnvironment=process.env.APP_ENV||(process.env.NODE_ENV==='development'?'development':'production'); +if(!['development','test','production'].includes(appEnvironment))throw new Error('Invalid APP_ENV'); + +const nextConfig: NextConfig = { + output: 'standalone', + outputFileTracingExcludes: { + '/*': ['./dist/**/*', './tests/**/*', './.next/cache/**/*', './.next-test/cache/**/*', './.next-dev/cache/**/*'], + }, + // Turbopack's route runtime is external; keep it in relocatable standalone releases. + outputFileTracingIncludes: { + '/*': ['./node_modules/next/dist/compiled/next-server/*runtime.prod.js'], + }, + distDir: appEnvironment==='development'?'.next-dev':appEnvironment==='test'?'.next-test':'.next', + trailingSlash: true, + // Page redirects live in src/proxy.ts; API and media URLs keep their exact paths. + skipTrailingSlashRedirect: true, + // Serve the complete page metadata in to browsers and crawlers alike. + htmlLimitedBots: /.*/, + images: { unoptimized: true }, + poweredByHeader: false, + turbopack: { root: process.cwd() }, + async headers() { + const xml=[{key:'Content-Type',value:'application/xml; charset=utf-8'}] + const xsl=[{key:'Content-Type',value:'text/xsl; charset=utf-8'}] + return [ + {source:'/sitemap.xml',headers:xml}, + {source:'/sitemaps/:file',headers:xml}, + {source:'/static/sitemapindex.xsl',headers:xsl}, + {source:'/static/sitemap.xsl',headers:xsl}, + ] + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..44c47f8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1356 @@ +{ + "name": "91-drama-next", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "91-drama-next", + "version": "1.0.0", + "dependencies": { + "dotenv": "17.2.3", + "hls.js": "1.7.3", + "next": "^16.0.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "typescript": "^5.9.0" + }, + "engines": { + "node": ">=20.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.2.tgz", + "integrity": "sha512-nTUTFH7G5/J/fmpkjVLegOkqyKOP0CEsJcMsyO4UCYeN1su+YMW/MfaSErnJskL7XM+UDK6aTvslxsRCyya/sA==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.2.tgz", + "integrity": "sha512-ONkhFvZNFWlsEHOM/81AtxdyuJP2KRYKYR0JxPwvcu1AuPMMMgcL+wW2luldJvMtZwKtUxoSkCychW5wIeoYSw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.2.tgz", + "integrity": "sha512-luPCMbOsCoaF+kXJEUjmjmOZg1lC3BflhmUu+ZmW4tBu2vW/4AXak8JBFvePizGkxVmNiAw/r9ra+6UviCWtWQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.2.tgz", + "integrity": "sha512-xhwFTAncJyaCNy5KqKKuKaIIdCy1/1ZV1s5S1G0hhT7wGMkDTlfJ6aQbfRFnIG/s3/kDrxQ8aycVe+c5QnP8ow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.2.tgz", + "integrity": "sha512-T1PNmfbPcxr5ndtWUL/3wQtLXsz808lq6sc2GrNT5k2oquF7UyqZddHSuAewHVsWsGXe6nsl9HoN1YNriVjuSg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.2.tgz", + "integrity": "sha512-N9cDUOCGtkJG5PNKDFZUF3xXndrJJcpLp121uAGjIGvO/U2p9tfdNOM9htj7+5f72l0m8rMw/cvYacUJBQoudg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.2.tgz", + "integrity": "sha512-yY2uh1YhX6ufuYy2FCbw1dqblbhI57GLYAJfCkqD9auTv6pLrH67Uccjc3QMKBeCYAG6lkgu08ExmX1bbjejjQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.2.tgz", + "integrity": "sha512-j/ArWRIHpMMB6wYLMhr2puTguBt70lNU/Cmjl9p6dTDE8DGknYJMP9PdwPk2dqpCNL/KIX9yfngDvIesxJPmsg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.2.tgz", + "integrity": "sha512-AA4edyWHDko3NCSo0x6GomsdgFb0oR1J3MXU1b5xt6FRFg4AucrMPW3S0hCe/9onU0O1BbpGQBsqo1r0Gneu1g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "22.20.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.5.tgz", + "integrity": "sha512-U2+DNr+wSjpsTS/wZGYHq7GcwfuSmKiKvoPvK22zwTlRhU91yOniN4qRR5KhIjvif7ysw/dz/hKmfDH0Ris4aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/qrcode": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz", + "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.29.tgz", + "integrity": "sha512-YeesxhUmR4h5y5V8KF8iBCqJb8YFrBJ++XBTeGcWIaQOYXGN19JM0FoiOjAhwuDoln8owMgXcWBmgHtYSkky2w==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001816", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001816.tgz", + "integrity": "sha512-+8Yx7BXCoqk6IF8yTicjfUR6D/ldJzdlsodlEOiOSPmhK844WIgk67/zIQaGAOxNkZyyvBlrRFnLvqKIoqbAFw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "17.2.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", + "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/hls.js": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.7.3.tgz", + "integrity": "sha512-MsPlx6yVW4Qv4C7mEVou4/gk/5cN2dVxOTnrNjPSmyH2f0Ln+/UkG10Ax1PE6OvrmTVPKgQYozTtBaPOuCzq7A==", + "license": "Apache-2.0" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.2.tgz", + "integrity": "sha512-r+oBJojfefUjOiWkBhXgLS+igxX/7BWyK59uHnyOyplMSLtBZ+QLP3okrdYTbrfrbrmNTVWLBPaHDX9KRaIt3w==", + "license": "MIT", + "dependencies": { + "@next/env": "16.4.2", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.4.2", + "@next/swc-darwin-x64": "16.4.2", + "@next/swc-linux-arm64-gnu": "16.4.2", + "@next/swc-linux-arm64-musl": "16.4.2", + "@next/swc-linux-x64-gnu": "16.4.2", + "@next/swc-linux-x64-musl": "16.4.2", + "@next/swc-win32-arm64-msvc": "16.4.2", + "@next/swc-win32-x64-msvc": "16.4.2", + "sharp": "^0.35.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/qrcode": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", + "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.1" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..047cd8f --- /dev/null +++ b/package.json @@ -0,0 +1,42 @@ +{ + "name": "91-drama-next", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "node scripts/next-env.mjs development dev", + "dev:test": "node scripts/next-env.mjs test dev", + "build": "node scripts/next-env.mjs production build", + "build:dev": "node scripts/next-env.mjs development build", + "build:test": "node scripts/next-env.mjs test build", + "start": "node scripts/next-env.mjs production start", + "start:dev": "node scripts/next-env.mjs development start", + "start:test": "node scripts/next-env.mjs test start", + "typecheck": "tsc --noEmit", + "test": "node --test tests/*.test.mjs", + "check": "npm run build && npm test", + "bundle": "node scripts/package-standalone.mjs production", + "bundle:test": "node scripts/package-standalone.mjs test", + "bundle:linux": "node scripts/prepare-production-runtime.mjs", + "deploy:test": "bash deploy_test.sh", + "deploy:prod": "bash deploy_prod.sh", + "smoke:api": "node scripts/smoke-api.mjs" + }, + "dependencies": { + "dotenv": "17.2.3", + "hls.js": "1.7.3", + "next": "^16.0.0", + "qrcode": "^1.5.4", + "react": "^19.2.0", + "react-dom": "^19.2.0" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "@types/qrcode": "^1.5.6", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "typescript": "^5.9.0" + }, + "engines": { + "node": ">=20.9.0" + } +} diff --git a/project.md b/project.md new file mode 100644 index 0000000..e92e75d --- /dev/null +++ b/project.md @@ -0,0 +1,215 @@ +--- +项目名称: 91短剧web +一级部门: 北斗 +二级部门: 瑞升 + +DMP编码: JHA-1047 + +项目类型: PC + # 可选值: +# H5 / APP / PC / 服务 / 组件 / 工具 / AI / 中间件 + +开发语言: Node + # 可选值: +# GO / Java / PHP / Node + +项目负责人: DN1425 # 员工编号, 多个用逗号隔开,这里的负责是这套代码的负责人 + +运维负责人: DN9460, DN9651, DN4792 # 负责部署、监控、K8S、发布、证书等 + +产品负责人: YY1166 # 项目产品经理 + +项目成员: DN7208, DN2185, DN8431 + +项目状态: 维护中 + # 可选值: + # 开发中 / 维护中 / 已停止 / 已归档 + +项目级别: 中 + # 可选值: + # 高 / 中 / 低 , 北斗体系 s和a为 高 b为中 c d为低 + +核心分支: main + # 即部署时候使用的分支,可选值: + # master / main / dev / release + +代码仓库: + - ssh://git@bd-git-sh.jsyyds.com:8082/ruisheng/jha-1021-91jhs/frontend/jha-1047-91dj-web.git + +最后审计人: + +最后审计时间: + +告警群: + +备注: Next.js 网站与 API 代理服务;业务数据由独立 Go 后端提供。 + +--- + +# 1. 项目说明 + +91短剧 Web 项目,DMP 编码 **JHA-1047**,应用及埋点 App ID 为 **1047**。采用 Next.js App Router、React、TypeScript,实现 PC 和移动端响应式页面,布局依据提供的 HTML 原稿还原。 + +本文按当前仓库代码整理,更新日期为 **2026-10-26**;该日期不代表安全审计或线上验收时间。人员信息沿用原登记信息,部署目标以当前脚本默认值为准。 + +## 1.1 功能范围 + +- 首页动态栏目、轮播、顶部及侧栏导航、广告与宫格广告弹窗。 +- 短剧列表、排行榜、专题、题材/标签、栏目更多页、搜索及独立分页。 +- 短剧详情、章节播放、HLS 播放、图片解密、点赞、收藏、评论与分享。 +- 游客/账号登录、个人中心、观看历史、我的收藏、我的点赞、已购剧集、账号设置。 +- 金币充值、VIP 套餐、章节解锁、反馈、App 下载、福利应用、回家的路和独立导航页。 +- 服务端 SEO 元数据、JSON-LD、robots.txt 与广告/页面/播放等埋点。 + +本仓库负责网站页面、浏览器交互和服务端 API 代理,不包含 Go 业务服务、管理后台或数据库部署代码。独立“剧集”入口及 `/series/`、`/video/*` 页面已移除。 + +## 1.2 主要目录与路由 + +| 位置 | 职责 | +| --- | --- | +| `src/app/` | App Router 页面、API/媒体代理、robots 和健康检查 | +| `src/components/site/` | 首页、详情、播放、导航等页面组件 | +| `src/lib/api/` | 上游配置、接口白名单、会话、公共缓存和媒体请求 | +| `src/lib/client/` | 登录、账户、播放、解锁、广告、图片及埋点交互 | +| `src/lib/routing.ts` | 路由解析、slug/ID、分页地址与导航匹配 | +| `src/lib/seo*.ts`、`src/lib/structured-data.ts` | TDK、canonical、索引策略、SEO 快照和 JSON-LD | +| `src/proxy.ts` | 页面尾斜杠规范化和首屏会话准备 | +| `config/`、`scripts/`、`deploy/` | 环境配置、启动/打包脚本及辅助更新脚本 | +| `public/`、`tests/`、`docs/` | 静态素材、回归测试、接口与收录规则说明 | + +| 页面 | 主要路径 | +| --- | --- | +| 首页 / 短剧 | `/`、`/shorts/` | +| 三个排行榜 | `/rank/hot/`、`/rank/new/`、`/rank/follow/` | +| 专题 / 标签 / 更多栏目 | `/topics/`、`/topics/{key}/`、`/tag/{key}/`、`/config/{key}/` | +| 详情 / 播放 | `/detail/{key}/`、`/play/{key}/{chapterId}/` | +| 搜索 | `/search/{keyword}/` | +| 个人中心及书架 | `/member/center/`、`/member/history/`、`/member/favorites/`、`/member/likes/`、`/member/purchased/` | +| 充值 / VIP | `/info/deposit/`、`/info/deposit/vip/` | +| 官方入口 / 回家的路 / 福利应用 | `/navigation/`、`/home-road/`、`/apps/` | + +内容地址优先使用有效英文/拼音 slug,无效或缺失时使用 ID;播放页章节段使用真实章节 ID。搜索词作为单个路径段编码。支持分页的页面使用 `/page/{N}/`,第一页使用基础路径;详情和播放页的分页对应评论。有效页面无尾斜杠访问返回 308,API、媒体和静态资源保留原路径。确认不存在的资源或越界分页返回 404,上游业务服务异常与资源不存在分别处理。 + +# 2. 基础设施与中间件 + +## 2.1 运行依赖与请求链路 + +- Node.js 最低 **20.9.0**,推荐 **22.17.1**;生产跨平台打包要求 **npm >=10.9.2**。依赖版本以 [package-lock.json](package-lock.json) 为准,使用 `npm ci` 安装。 +- Next.js 16、React 19、TypeScript 5.9;主要运行库包括 `hls.js`、`qrcode`、`dotenv`。 +- 网站需要运行 Node.js 服务,使用 Next.js `standalone` 产物,不是静态 `out/` 部署。 +- 页面在服务端取数并输出当前页面正文与 head;浏览器复用首屏数据绑定交互,页面链接和分页使用原生路径访问。 +- 浏览器请求本站 `/api/*`,Next.js 按白名单转发到 Go 后端的 `/api/app/*`。`UPSTREAM_API_URL` 仅服务端使用,`API_BASE_URL` 是兼容别名。 +- `/h5/m3u8/*`、`/enkey`、`/newenkey` 由媒体代理处理,保留签名查询、客户端相关请求信息;媒体和密钥不缓存,不转发应用登录 Cookie 或 Authorization。 + +登录会话通过 HttpOnly Cookie 传递;SSR 启动数据按字段白名单输出,不向浏览器暴露后端登录凭证。公共接口和账户、余额、订单、付费权限数据采用不同缓存策略。 + +本仓库不直接连接 MongoDB、Redis 或 Elasticsearch。Go 后端是否使用这些中间件及其拓扑,由后端/运维文档确认;本仓库没有 Telegram Bot 部署通知实现。 + +## 2.2 环境配置 + +| 环境 | 配置文件 | Go API 上游 | 网站端口 | 构建目录 | +| --- | --- | --- | --- | --- | +| 开发 | `config/development.env` | `http://172.104.181.99:21022` | `21021` | `.next-dev/` | +| 测试 | `config/test.env` | `http://172.104.181.99:21022` | `21021` | `.next-test/` | +| 生产 | `config/production.env` | `http://172.104.56.28:26125` | `21021` | `.next/` | + +配置由 [scripts/next-env.mjs](scripts/next-env.mjs) 加载,优先级为:进程环境变量 > `config/{环境}.local.env` > `config/{环境}.env`。开发环境兼容旧 `.env.local`,优先级低于 `config/development.local.env`;测试和生产不从旧 `.env.local` 加载本站业务配置。 + +`APP_ENV` 区分业务环境;测试构建/启动仍使用 `NODE_ENV=production`,`dev` 命令使用 `NODE_ENV=development`。服务端配置修改后需重启,`NEXT_PUBLIC_*` 修改后需重新构建。私有覆盖文件已忽略提交和打包。 + +## 2.3 缓存、SEO 与外部配置 + +- 公共配置、页脚、导航、分类和标签选项默认缓存 60 秒,首页缓存 30 秒,进程内最多 128 条并合并并发请求;开发配置关闭缓存。`PUBLIC_DATA_CACHE_TTL_MS=0` 可关闭,正数仅缩短既有缓存上限。 +- 公共缓存按上游地址隔离,各实例独立;配置和首页刷新失败可沿用已成功的数据。账户及付费权限不进入公共缓存。 +- canonical 和站点身份取后台配置的永久域名,优先 `config.foreverUrlAddr`,其次 `newAddressInfo.foreverUrlAddr`。有效 SEO 配置保存在对应构建目录的 `cache/seo/`,异常时复用最近一次有效快照;不以 API IP 或访问 Host 作为正式域名。 +- 生产 `/robots.txt` 允许抓取公开资源,限制 `/api/` 和 `/healthz`;开发、测试禁止全站抓取。账户、充值、搜索、工具等页面另有页面级 `noindex` 规则。 +- 生产 robots 在取得有效永久域名后声明 `https://{永久域名}/sitemap.xml` 形式的绝对 URL。**本仓库没有 sitemap XML 生成或读取路由**;现有 XSL 和响应头配置不等于已提供 XML,需由后端/反向代理另行接入。规则见 [后端 Sitemap 对接约定](docs/sitemap-backend-contract.md)。 +- 导航、官方入口、商务 TG、用户反馈 TG 群、广告和下载地址由接口动态配置;未配置有效 App 下载链接时隐藏入口。 +- 埋点使用 App ID `1047`,优先读取接口 `reportSdkConfig`,构建配置 `NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG` 为兜底;配置及上报链路需与后端保持一致。 + +# 3. 运维部署 + +## 3.1 开发、验证和启动 + +```sh +nvm use 22.17.1 +npm ci +npm run dev # 开发配置,默认 http://localhost:21021 +npm run typecheck # 类型检查 +npm test # 本地回归测试 +npm run check # 生产构建 + 回归测试 +``` + +| 环境 | 构建 | 启动 | +| --- | --- | --- | +| 开发 | `npm run build:dev` | `npm run dev`;已有构建用 `npm run start:dev` | +| 测试 | `npm run build:test` | `npm run start:test`;开发调试可用 `npm run dev:test` | +| 生产 | `npm run build` | `npm start` | + +启动前确认 `21021` 未被本项目的旧进程或其他服务占用。不同环境须使用对应构建产物,不能互换构建目录。 + +## 3.2 打包与发布目标 + +```sh +bash deploy_test.sh --dry-run # 仅预览,不构建、不联网 +bash deploy_prod.sh --dry-run +bash deploy_test.sh --package-only # 仅本地构建、测试、打包 +bash deploy_prod.sh --package-only +``` + +| 环境 | 默认 SSH 目标 | 上传及执行目录 | SCP / SSH 端口 | 上传后执行 | +| --- | --- | --- | --- | --- | +| 测试 | `root@172.104.181.99` | `/server/web_91dj/h5` | `22889 / 22889` | `bash run.sh` | +| 生产 | `server@34.92.204.175` | `/home/server/web_91dj/h5` | `22889 / 22889` | `bash rsupdate.sh` | + +直接执行 `bash deploy_test.sh` 或 `bash deploy_prod.sh` 会构建、测试、打包、上传并执行远端脚本。生产上传主机与生产 API 主机不同,不要混用。 + +- **测试脚本**:[deploy_test.sh](deploy_test.sh)。执行 `build:test`、测试、`bundle:test`,生成 `web_91dj_h5.tar.gz` 和 `web_91dj_h5.tar.gz.sha256`,上传这两份文件及 `deploy/up_next_h5.sh`;同时刷新本地兼容 MD5 文件。测试包不含 `node_modules`,Linux 解包后需 `npm ci --omit=dev --no-audit --no-fund`,再运行 `node start.mjs`。 +- **生产脚本**:[deploy_prod.sh](deploy_prod.sh)。执行 `check`、`bundle`、`bundle:linux`,生成 `web_91dj_h5.tar.gz` 和纯哈希格式 `web_91dj_h5.md5`,两份文件一并上传。包内含 standalone 裁剪依赖,默认 Linux x64/glibc,可运行 `node start.mjs`;打包时安装并校验目标平台 Sharp/libvips。 +- 两种包只带对应环境的基础配置,不包含私有 `*.local.env`、`.env*` 和 SEO 缓存。服务器覆盖配置放在实际运行目录的 `config/` 下。 +- 测试与生产使用**同名压缩包**,后一次打包会覆盖根目录旧产物;发布前核对环境和校验文件。`dist/`、压缩包和校验文件均已忽略提交,两份脚本共用 `dist/.deploy.lock` 防止并发打包。 + +测试目标可通过 `DEPLOY_HOST`、`DEPLOY_DIR`、`DEPLOY_RUN_DIR`、`DEPLOY_SSH_PORT` 覆盖;上传目录和执行目录分别配置。生产使用 `DEPLOY_JUMP_HOST`、`DEPLOY_JUMP_DIR`、`DEPLOY_SCP_PORT`、`DEPLOY_SSH_PORT`,执行目录跟随上传目录。两者支持 `DEPLOY_PACKAGE_NAME`;生产另支持 `DEPLOY_TARGET_ARCH=x64|arm64`、`DEPLOY_TARGET_LIBC=glibc|musl`。 + +上传失败不会执行远端更新;远端命令失败会使发布退出。生产脚本还检查远端输出中的失败状态,并将执行日志写入 `dist/production-rsupdate.*`。 + +## 3.3 服务管理、检查和回退 + +远端现有 `run.sh`、`rsupdate.sh` 不在本仓库,发布脚本不会覆盖它们。服务启动方式、私有配置保留、日志位置和线上回退步骤,需由运维结合服务器脚本确认。 + +仓库提供的 [deploy/up_next_h5.sh](deploy/up_next_h5.sh) 是测试环境辅助更新脚本,默认 Supervisor 程序为 `91dj_next_h5`,可通过 `DEPLOY_PROGRAM` 覆盖。它支持 SHA-256 校验、解包、安装依赖、保留本地配置和 SEO 缓存、切换版本、检查和失败回退;是否由服务器 `run.sh` 调用,应核对远端实现。 + +`/healthz` 返回进程状态、应用名和环境,不请求 Go 后端。发布后可执行: + +```sh +node scripts/check-health.mjs http://127.0.0.1:21021 production +# 测试环境将最后一个参数改为 test +PREVIEW_URL=http://127.0.0.1:21021 npm run smoke:api +``` + +健康检查同时检查 `/healthz` 和首页;测试发布可设置 `DEPLOY_CHECK_URL` 自动执行。API 冒烟检查可能创建临时游客会话,使用 `SMOKE_PUBLIC_ONLY=1` 可仅检查公共部分。支付、解锁、媒体播放及埋点仍需对应业务验收,不能仅以进程存活判定全部功能正常。 + +# 4. 风险说明 + +| 风险点 | 影响及处理要求 | +| --- | --- | +| 上游及反向代理配置错误 | SSR、登录或媒体可能失败;区分网站 `21021`、API `21022/26125` 和 SSH `22889`。本站 `/api/*` 应进入 Next.js,避免上游回环;如启用可选 `current` 模式,必须先确认 `/api/app/*` 转发到 Go。 | +| 播放签名、IP、UA 或密钥异常 | 可能出现播放 502;保留签名查询和客户端信息,检查真实上游响应,AES 密钥必须为 16 字节。H.265 解码受设备及浏览器限制,前端不提供转码。 | +| 个人数据误入共享缓存 | 可能造成身份或权限混用;新增接口需延续公开数据与账户、订单、付费权限隔离,首屏输出遵守字段白名单。 | +| 发布包平台或环境不匹配 | 生产需匹配 Linux 架构/libc,测试包需安装运行依赖;同名产物发布前核对配置和校验值。 | +| SEO 配置或 sitemap 未接入 | 测试环境禁止抓取;生产需核对 `APP_ENV`、永久域名、robots、页面索引标记及实际 sitemap 响应,不能只确认 Sitemap 声明。 | +| 服务器更新脚本未交接 | 本地脚本成功不等于业务验收通过;运维应提供远端脚本、服务管理、日志、监控告警及回退说明。 | + +# 5. 历史事故(可选) + +仓库内未提供经确认的生产事故台账。负责人可按“时间、环境、影响范围、原因、处置及预防措施”补充;开发调试错误不直接登记为生产事故。 + +# 6. 交接说明(可选) + +- **应用标识**:DMP 为 `JHA-1047`;三套环境的 `NEXT_PUBLIC_TRACKER_APP_ID` 均为 `1047`,对接后台与 SDK 时保持一致。 +- **新增页面**:复用集中路由、slug/ID、分页和 SEO 规则,补充有效/缺失/异常状态处理。导航与下载入口优先消费动态配置,不写死演示链接。 +- **个人列表**:我的点赞入口为 `/member/likes/`,上游接口为 `GET /api/app/playlet/like/list`,与收藏、历史、已购共同保持私有响应及禁止索引。 +- **配置交接**:确认实际运行目录的私有覆盖、上游连通性、正式永久域名、TG 联系方式、广告、下载及埋点配置。新版本切换需保留服务器私有配置和对应 SEO 快照。 +- **发布交接**:确认 SSH 权限、远端 `run.sh`/`rsupdate.sh`、服务管理名称、日志与回退方法;本文记录的是仓库默认值,不替代服务器实际配置验收。 +- **待补登记**:最后审计人、最后审计时间、告警群,以及运维确认后的监控和事故台账;不将本次文档整理视为已完成审计。 +- **参考资料**:[运行与部署说明](README.md)、[页面接口对照](docs/api-contract.md)、[索引策略](docs/indexation-policy.md)、[Sitemap 对接约定](docs/sitemap-backend-contract.md)。历史核对记录以其日期为界,当前配置和行为以代码为准。 diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000..db10c5b Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/assets/01a1c7fc200c39f2.jpg b/public/assets/01a1c7fc200c39f2.jpg new file mode 100644 index 0000000..6865d00 Binary files /dev/null and b/public/assets/01a1c7fc200c39f2.jpg differ diff --git a/public/assets/01c359ce005dc785.webp b/public/assets/01c359ce005dc785.webp new file mode 100644 index 0000000..5c43898 Binary files /dev/null and b/public/assets/01c359ce005dc785.webp differ diff --git a/public/assets/026444e7f0debb8a.png b/public/assets/026444e7f0debb8a.png new file mode 100644 index 0000000..00528a1 Binary files /dev/null and b/public/assets/026444e7f0debb8a.png differ diff --git a/public/assets/09bbacc482165f7f.jpg b/public/assets/09bbacc482165f7f.jpg new file mode 100644 index 0000000..b820d2b Binary files /dev/null and b/public/assets/09bbacc482165f7f.jpg differ diff --git a/public/assets/0a24767770663c9e.png b/public/assets/0a24767770663c9e.png new file mode 100644 index 0000000..7477c9e Binary files /dev/null and b/public/assets/0a24767770663c9e.png differ diff --git a/public/assets/0c829f3186dd2aa8.jpg b/public/assets/0c829f3186dd2aa8.jpg new file mode 100644 index 0000000..8a20d77 Binary files /dev/null and b/public/assets/0c829f3186dd2aa8.jpg differ diff --git a/public/assets/0dcb9a25191e606e.webp b/public/assets/0dcb9a25191e606e.webp new file mode 100644 index 0000000..1ad6e93 Binary files /dev/null and b/public/assets/0dcb9a25191e606e.webp differ diff --git a/public/assets/0e23b0d87a4b6e04.webp b/public/assets/0e23b0d87a4b6e04.webp new file mode 100644 index 0000000..dcad676 Binary files /dev/null and b/public/assets/0e23b0d87a4b6e04.webp differ diff --git a/public/assets/0ee5776b9c09c740.webp b/public/assets/0ee5776b9c09c740.webp new file mode 100644 index 0000000..9f2d02c Binary files /dev/null and b/public/assets/0ee5776b9c09c740.webp differ diff --git a/public/assets/0f0ef061135a13f0.svg b/public/assets/0f0ef061135a13f0.svg new file mode 100644 index 0000000..b080a31 --- /dev/null +++ b/public/assets/0f0ef061135a13f0.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/11bae9823678ebdc.svg b/public/assets/11bae9823678ebdc.svg new file mode 100644 index 0000000..1d298e0 --- /dev/null +++ b/public/assets/11bae9823678ebdc.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/13536e3a41e2f59f.png b/public/assets/13536e3a41e2f59f.png new file mode 100644 index 0000000..fbe5962 Binary files /dev/null and b/public/assets/13536e3a41e2f59f.png differ diff --git a/public/assets/14372da0677bc8a2.png b/public/assets/14372da0677bc8a2.png new file mode 100644 index 0000000..8bc93cd Binary files /dev/null and b/public/assets/14372da0677bc8a2.png differ diff --git a/public/assets/144f33adfb6673f6.svg b/public/assets/144f33adfb6673f6.svg new file mode 100644 index 0000000..a8857f5 --- /dev/null +++ b/public/assets/144f33adfb6673f6.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/160ae2de19037355.svg b/public/assets/160ae2de19037355.svg new file mode 100644 index 0000000..fe19a8f --- /dev/null +++ b/public/assets/160ae2de19037355.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/16bd37861fd6246c.svg b/public/assets/16bd37861fd6246c.svg new file mode 100644 index 0000000..d05fdc8 --- /dev/null +++ b/public/assets/16bd37861fd6246c.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/180fca29d93502d0.webp b/public/assets/180fca29d93502d0.webp new file mode 100644 index 0000000..244c2ad Binary files /dev/null and b/public/assets/180fca29d93502d0.webp differ diff --git a/public/assets/189d7323288bd44d.svg b/public/assets/189d7323288bd44d.svg new file mode 100644 index 0000000..69ccf12 --- /dev/null +++ b/public/assets/189d7323288bd44d.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/18da69acb7776e92.svg b/public/assets/18da69acb7776e92.svg new file mode 100644 index 0000000..1df0b7c --- /dev/null +++ b/public/assets/18da69acb7776e92.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/19ed41602046c136.svg b/public/assets/19ed41602046c136.svg new file mode 100644 index 0000000..291e503 --- /dev/null +++ b/public/assets/19ed41602046c136.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/1e03fc1be2cc2dc4.png b/public/assets/1e03fc1be2cc2dc4.png new file mode 100644 index 0000000..0b98570 Binary files /dev/null and b/public/assets/1e03fc1be2cc2dc4.png differ diff --git a/public/assets/1ed1c03f29c9aecc.png b/public/assets/1ed1c03f29c9aecc.png new file mode 100644 index 0000000..ffd7db5 Binary files /dev/null and b/public/assets/1ed1c03f29c9aecc.png differ diff --git a/public/assets/1f0d06c643b6660a.png b/public/assets/1f0d06c643b6660a.png new file mode 100644 index 0000000..fcd8724 Binary files /dev/null and b/public/assets/1f0d06c643b6660a.png differ diff --git a/public/assets/200f9c8479491fb3.png b/public/assets/200f9c8479491fb3.png new file mode 100644 index 0000000..cbcd688 Binary files /dev/null and b/public/assets/200f9c8479491fb3.png differ diff --git a/public/assets/226b263bb72d177e.png b/public/assets/226b263bb72d177e.png new file mode 100644 index 0000000..4018ea4 Binary files /dev/null and b/public/assets/226b263bb72d177e.png differ diff --git a/public/assets/228a0af1662081ac.svg b/public/assets/228a0af1662081ac.svg new file mode 100644 index 0000000..0abe8d6 --- /dev/null +++ b/public/assets/228a0af1662081ac.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/22ec0b5db3ff7cd5.webp b/public/assets/22ec0b5db3ff7cd5.webp new file mode 100644 index 0000000..4e70826 Binary files /dev/null and b/public/assets/22ec0b5db3ff7cd5.webp differ diff --git a/public/assets/2326b439717b8919.png b/public/assets/2326b439717b8919.png new file mode 100644 index 0000000..81e7bea Binary files /dev/null and b/public/assets/2326b439717b8919.png differ diff --git a/public/assets/2419ebe0d21ada3d.webp b/public/assets/2419ebe0d21ada3d.webp new file mode 100644 index 0000000..5315599 Binary files /dev/null and b/public/assets/2419ebe0d21ada3d.webp differ diff --git a/public/assets/255fc2eddadf06d2.webp b/public/assets/255fc2eddadf06d2.webp new file mode 100644 index 0000000..2c306b5 Binary files /dev/null and b/public/assets/255fc2eddadf06d2.webp differ diff --git a/public/assets/2699fa102e73d8c8.svg b/public/assets/2699fa102e73d8c8.svg new file mode 100644 index 0000000..b71a877 --- /dev/null +++ b/public/assets/2699fa102e73d8c8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/26bbd61b3e089f86.png b/public/assets/26bbd61b3e089f86.png new file mode 100644 index 0000000..c0c1063 Binary files /dev/null and b/public/assets/26bbd61b3e089f86.png differ diff --git a/public/assets/27160b85d8347743.webp b/public/assets/27160b85d8347743.webp new file mode 100644 index 0000000..5fe10bb Binary files /dev/null and b/public/assets/27160b85d8347743.webp differ diff --git a/public/assets/27877a11ee8315b3.webp b/public/assets/27877a11ee8315b3.webp new file mode 100644 index 0000000..463cada Binary files /dev/null and b/public/assets/27877a11ee8315b3.webp differ diff --git a/public/assets/2864103ed160195a.webp b/public/assets/2864103ed160195a.webp new file mode 100644 index 0000000..5b52bdb Binary files /dev/null and b/public/assets/2864103ed160195a.webp differ diff --git a/public/assets/28b276f54dfea226.webp b/public/assets/28b276f54dfea226.webp new file mode 100644 index 0000000..c4fb628 Binary files /dev/null and b/public/assets/28b276f54dfea226.webp differ diff --git a/public/assets/29b976d622f7c5d1.svg b/public/assets/29b976d622f7c5d1.svg new file mode 100644 index 0000000..6adc929 --- /dev/null +++ b/public/assets/29b976d622f7c5d1.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/assets/2a8580c42090f36c.svg b/public/assets/2a8580c42090f36c.svg new file mode 100644 index 0000000..b041985 --- /dev/null +++ b/public/assets/2a8580c42090f36c.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/2a92e7ab0cae2c3a.webp b/public/assets/2a92e7ab0cae2c3a.webp new file mode 100644 index 0000000..db99fbe Binary files /dev/null and b/public/assets/2a92e7ab0cae2c3a.webp differ diff --git a/public/assets/2bad2bb0d4ffcbe9.jpg b/public/assets/2bad2bb0d4ffcbe9.jpg new file mode 100644 index 0000000..dff98a5 Binary files /dev/null and b/public/assets/2bad2bb0d4ffcbe9.jpg differ diff --git a/public/assets/2bead9ea5c435c98.svg b/public/assets/2bead9ea5c435c98.svg new file mode 100644 index 0000000..48ca28f --- /dev/null +++ b/public/assets/2bead9ea5c435c98.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/public/assets/2c77547c257b8f70.webp b/public/assets/2c77547c257b8f70.webp new file mode 100644 index 0000000..5d2bfe8 Binary files /dev/null and b/public/assets/2c77547c257b8f70.webp differ diff --git a/public/assets/2d4b4cd3fe1e8795.svg b/public/assets/2d4b4cd3fe1e8795.svg new file mode 100644 index 0000000..aed1b44 --- /dev/null +++ b/public/assets/2d4b4cd3fe1e8795.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/2d8f1898b668e45e.png b/public/assets/2d8f1898b668e45e.png new file mode 100644 index 0000000..bf4a6dc Binary files /dev/null and b/public/assets/2d8f1898b668e45e.png differ diff --git a/public/assets/2fdedce06a90a6a1.svg b/public/assets/2fdedce06a90a6a1.svg new file mode 100644 index 0000000..5155d55 --- /dev/null +++ b/public/assets/2fdedce06a90a6a1.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/2ff6d2000e27356c.webp b/public/assets/2ff6d2000e27356c.webp new file mode 100644 index 0000000..64d70ff Binary files /dev/null and b/public/assets/2ff6d2000e27356c.webp differ diff --git a/public/assets/33256436588298b0.png b/public/assets/33256436588298b0.png new file mode 100644 index 0000000..f69ff0a Binary files /dev/null and b/public/assets/33256436588298b0.png differ diff --git a/public/assets/3482d6856aee853d.svg b/public/assets/3482d6856aee853d.svg new file mode 100644 index 0000000..b190509 --- /dev/null +++ b/public/assets/3482d6856aee853d.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/348a414d41099579.png b/public/assets/348a414d41099579.png new file mode 100644 index 0000000..4796886 Binary files /dev/null and b/public/assets/348a414d41099579.png differ diff --git a/public/assets/350ab2f3118693a8.svg b/public/assets/350ab2f3118693a8.svg new file mode 100644 index 0000000..f581c91 --- /dev/null +++ b/public/assets/350ab2f3118693a8.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/35b4e9820e5272f1.webp b/public/assets/35b4e9820e5272f1.webp new file mode 100644 index 0000000..c2a9f94 Binary files /dev/null and b/public/assets/35b4e9820e5272f1.webp differ diff --git a/public/assets/3651a2618990bf95.svg b/public/assets/3651a2618990bf95.svg new file mode 100644 index 0000000..284c4ad --- /dev/null +++ b/public/assets/3651a2618990bf95.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/366eac80d0309a2e.webp b/public/assets/366eac80d0309a2e.webp new file mode 100644 index 0000000..f234fff Binary files /dev/null and b/public/assets/366eac80d0309a2e.webp differ diff --git a/public/assets/388ac2896091bdb8.png b/public/assets/388ac2896091bdb8.png new file mode 100644 index 0000000..9fa3162 Binary files /dev/null and b/public/assets/388ac2896091bdb8.png differ diff --git a/public/assets/3f0f8131360bc200.webp b/public/assets/3f0f8131360bc200.webp new file mode 100644 index 0000000..868c723 Binary files /dev/null and b/public/assets/3f0f8131360bc200.webp differ diff --git a/public/assets/43059b3dd536fae4.webp b/public/assets/43059b3dd536fae4.webp new file mode 100644 index 0000000..be770ab Binary files /dev/null and b/public/assets/43059b3dd536fae4.webp differ diff --git a/public/assets/4532d28d6a61c426.svg b/public/assets/4532d28d6a61c426.svg new file mode 100644 index 0000000..62d049c --- /dev/null +++ b/public/assets/4532d28d6a61c426.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/47632de987649cda.png b/public/assets/47632de987649cda.png new file mode 100644 index 0000000..4eb97e8 Binary files /dev/null and b/public/assets/47632de987649cda.png differ diff --git a/public/assets/48deec4cd52f5397.png b/public/assets/48deec4cd52f5397.png new file mode 100644 index 0000000..9ccf0c2 Binary files /dev/null and b/public/assets/48deec4cd52f5397.png differ diff --git a/public/assets/49cffe02110af446.svg b/public/assets/49cffe02110af446.svg new file mode 100644 index 0000000..f6bd93e --- /dev/null +++ b/public/assets/49cffe02110af446.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/4b2300563a47e13e.webp b/public/assets/4b2300563a47e13e.webp new file mode 100644 index 0000000..3fcce1b Binary files /dev/null and b/public/assets/4b2300563a47e13e.webp differ diff --git a/public/assets/5178e628105b82b4.svg b/public/assets/5178e628105b82b4.svg new file mode 100644 index 0000000..781cdfa --- /dev/null +++ b/public/assets/5178e628105b82b4.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/51e809aac4f1e40a.png b/public/assets/51e809aac4f1e40a.png new file mode 100644 index 0000000..7b30358 Binary files /dev/null and b/public/assets/51e809aac4f1e40a.png differ diff --git a/public/assets/537b2be75c1688b3.webp b/public/assets/537b2be75c1688b3.webp new file mode 100644 index 0000000..48dd90d Binary files /dev/null and b/public/assets/537b2be75c1688b3.webp differ diff --git a/public/assets/55f4c0c888a3cee3.png b/public/assets/55f4c0c888a3cee3.png new file mode 100644 index 0000000..4a9f939 Binary files /dev/null and b/public/assets/55f4c0c888a3cee3.png differ diff --git a/public/assets/5652fcead17188e8.svg b/public/assets/5652fcead17188e8.svg new file mode 100644 index 0000000..9e0a1d3 --- /dev/null +++ b/public/assets/5652fcead17188e8.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/56bfd436158003be.png b/public/assets/56bfd436158003be.png new file mode 100644 index 0000000..0b5abe3 Binary files /dev/null and b/public/assets/56bfd436158003be.png differ diff --git a/public/assets/56f0435f9c47fec3.png b/public/assets/56f0435f9c47fec3.png new file mode 100644 index 0000000..b5a17c8 Binary files /dev/null and b/public/assets/56f0435f9c47fec3.png differ diff --git a/public/assets/573d7e27b3f6b69b.webp b/public/assets/573d7e27b3f6b69b.webp new file mode 100644 index 0000000..3079841 Binary files /dev/null and b/public/assets/573d7e27b3f6b69b.webp differ diff --git a/public/assets/5d32f20e4a4c4b95.jpg b/public/assets/5d32f20e4a4c4b95.jpg new file mode 100644 index 0000000..d28131a Binary files /dev/null and b/public/assets/5d32f20e4a4c4b95.jpg differ diff --git a/public/assets/5dc6629665de4078.webp b/public/assets/5dc6629665de4078.webp new file mode 100644 index 0000000..edee98d Binary files /dev/null and b/public/assets/5dc6629665de4078.webp differ diff --git a/public/assets/63867cb14798be9f.png b/public/assets/63867cb14798be9f.png new file mode 100644 index 0000000..8163280 Binary files /dev/null and b/public/assets/63867cb14798be9f.png differ diff --git a/public/assets/6472cc642da82b83.webp b/public/assets/6472cc642da82b83.webp new file mode 100644 index 0000000..46a7590 Binary files /dev/null and b/public/assets/6472cc642da82b83.webp differ diff --git a/public/assets/6594cfd4c90de958.png b/public/assets/6594cfd4c90de958.png new file mode 100644 index 0000000..ed547f2 Binary files /dev/null and b/public/assets/6594cfd4c90de958.png differ diff --git a/public/assets/6600cde1d7a82529.webp b/public/assets/6600cde1d7a82529.webp new file mode 100644 index 0000000..eebd3bb Binary files /dev/null and b/public/assets/6600cde1d7a82529.webp differ diff --git a/public/assets/67b33e5481de36bf.jpg b/public/assets/67b33e5481de36bf.jpg new file mode 100644 index 0000000..19a3372 Binary files /dev/null and b/public/assets/67b33e5481de36bf.jpg differ diff --git a/public/assets/6822ddc74c30d987.webp b/public/assets/6822ddc74c30d987.webp new file mode 100644 index 0000000..d53cc53 Binary files /dev/null and b/public/assets/6822ddc74c30d987.webp differ diff --git a/public/assets/69304455ce1e30ae.svg b/public/assets/69304455ce1e30ae.svg new file mode 100644 index 0000000..36c6a01 --- /dev/null +++ b/public/assets/69304455ce1e30ae.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/6af0eb8975b2a879.webp b/public/assets/6af0eb8975b2a879.webp new file mode 100644 index 0000000..21a00f3 Binary files /dev/null and b/public/assets/6af0eb8975b2a879.webp differ diff --git a/public/assets/6b84a86651a478fe.svg b/public/assets/6b84a86651a478fe.svg new file mode 100644 index 0000000..c64008b --- /dev/null +++ b/public/assets/6b84a86651a478fe.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/6dd2c76631816aa2.svg b/public/assets/6dd2c76631816aa2.svg new file mode 100644 index 0000000..4d0e338 --- /dev/null +++ b/public/assets/6dd2c76631816aa2.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +APP 下载 \ No newline at end of file diff --git a/public/assets/6fa3b8cccc07ed57.svg b/public/assets/6fa3b8cccc07ed57.svg new file mode 100644 index 0000000..6fc5021 --- /dev/null +++ b/public/assets/6fa3b8cccc07ed57.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/assets/71ec6a19ef0e1b01.png b/public/assets/71ec6a19ef0e1b01.png new file mode 100644 index 0000000..723fe8b Binary files /dev/null and b/public/assets/71ec6a19ef0e1b01.png differ diff --git a/public/assets/720b7aa5a05841ef.jpg b/public/assets/720b7aa5a05841ef.jpg new file mode 100644 index 0000000..ce0a9e3 Binary files /dev/null and b/public/assets/720b7aa5a05841ef.jpg differ diff --git a/public/assets/723fbfaeabe6ca96.jpg b/public/assets/723fbfaeabe6ca96.jpg new file mode 100644 index 0000000..2b9f249 Binary files /dev/null and b/public/assets/723fbfaeabe6ca96.jpg differ diff --git a/public/assets/72f702ca215f597d.svg b/public/assets/72f702ca215f597d.svg new file mode 100644 index 0000000..cb10f46 --- /dev/null +++ b/public/assets/72f702ca215f597d.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/7848816d05aa8591.svg b/public/assets/7848816d05aa8591.svg new file mode 100644 index 0000000..71f1491 --- /dev/null +++ b/public/assets/7848816d05aa8591.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/assets/7ac5104cd3d3e49c.png b/public/assets/7ac5104cd3d3e49c.png new file mode 100644 index 0000000..0255d27 Binary files /dev/null and b/public/assets/7ac5104cd3d3e49c.png differ diff --git a/public/assets/7ae5ed16a625703c.webp b/public/assets/7ae5ed16a625703c.webp new file mode 100644 index 0000000..185a5d4 Binary files /dev/null and b/public/assets/7ae5ed16a625703c.webp differ diff --git a/public/assets/7c8623882a4c28b7.png b/public/assets/7c8623882a4c28b7.png new file mode 100644 index 0000000..afadf24 Binary files /dev/null and b/public/assets/7c8623882a4c28b7.png differ diff --git a/public/assets/7d05c412943babef.png b/public/assets/7d05c412943babef.png new file mode 100644 index 0000000..1932054 Binary files /dev/null and b/public/assets/7d05c412943babef.png differ diff --git a/public/assets/7f137587edc271d0.png b/public/assets/7f137587edc271d0.png new file mode 100644 index 0000000..7693f0c Binary files /dev/null and b/public/assets/7f137587edc271d0.png differ diff --git a/public/assets/812c622409947995.png b/public/assets/812c622409947995.png new file mode 100644 index 0000000..75e74dc Binary files /dev/null and b/public/assets/812c622409947995.png differ diff --git a/public/assets/813abcae48078ac7.svg b/public/assets/813abcae48078ac7.svg new file mode 100644 index 0000000..49fca29 --- /dev/null +++ b/public/assets/813abcae48078ac7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/81a3adb37f20adbd.webp b/public/assets/81a3adb37f20adbd.webp new file mode 100644 index 0000000..8535dae Binary files /dev/null and b/public/assets/81a3adb37f20adbd.webp differ diff --git a/public/assets/8292db6251d1e129.png b/public/assets/8292db6251d1e129.png new file mode 100644 index 0000000..b4238ac Binary files /dev/null and b/public/assets/8292db6251d1e129.png differ diff --git a/public/assets/8466240296112fb5.jpg b/public/assets/8466240296112fb5.jpg new file mode 100644 index 0000000..3fa128f Binary files /dev/null and b/public/assets/8466240296112fb5.jpg differ diff --git a/public/assets/867ae921f79dbe99.webp b/public/assets/867ae921f79dbe99.webp new file mode 100644 index 0000000..090509b Binary files /dev/null and b/public/assets/867ae921f79dbe99.webp differ diff --git a/public/assets/8837c3e58159bd48.png b/public/assets/8837c3e58159bd48.png new file mode 100644 index 0000000..b7ac55c Binary files /dev/null and b/public/assets/8837c3e58159bd48.png differ diff --git a/public/assets/8b1153736cc5dca1.svg b/public/assets/8b1153736cc5dca1.svg new file mode 100644 index 0000000..8469d48 --- /dev/null +++ b/public/assets/8b1153736cc5dca1.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/8b9bfab6cb2e72b9.png b/public/assets/8b9bfab6cb2e72b9.png new file mode 100644 index 0000000..f1e1a19 Binary files /dev/null and b/public/assets/8b9bfab6cb2e72b9.png differ diff --git a/public/assets/8c1c285ac6bd3b66.svg b/public/assets/8c1c285ac6bd3b66.svg new file mode 100644 index 0000000..92602a0 --- /dev/null +++ b/public/assets/8c1c285ac6bd3b66.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/8eb3c37a26ca792b.svg b/public/assets/8eb3c37a26ca792b.svg new file mode 100644 index 0000000..c49d321 --- /dev/null +++ b/public/assets/8eb3c37a26ca792b.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/90e5136c083143d0.svg b/public/assets/90e5136c083143d0.svg new file mode 100644 index 0000000..79e26d1 --- /dev/null +++ b/public/assets/90e5136c083143d0.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/assets/929f93aa7c28c5c7.svg b/public/assets/929f93aa7c28c5c7.svg new file mode 100644 index 0000000..166d8cb --- /dev/null +++ b/public/assets/929f93aa7c28c5c7.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/92a6e44715e728e2.jpg b/public/assets/92a6e44715e728e2.jpg new file mode 100644 index 0000000..9d03635 Binary files /dev/null and b/public/assets/92a6e44715e728e2.jpg differ diff --git a/public/assets/92fc7fda98f7db04.svg b/public/assets/92fc7fda98f7db04.svg new file mode 100644 index 0000000..03255ed --- /dev/null +++ b/public/assets/92fc7fda98f7db04.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/93b149e40991d773.png b/public/assets/93b149e40991d773.png new file mode 100644 index 0000000..595b2f9 Binary files /dev/null and b/public/assets/93b149e40991d773.png differ diff --git a/public/assets/93d679135d26da60.webp b/public/assets/93d679135d26da60.webp new file mode 100644 index 0000000..e901b57 Binary files /dev/null and b/public/assets/93d679135d26da60.webp differ diff --git a/public/assets/96c24a25fd78210c.webp b/public/assets/96c24a25fd78210c.webp new file mode 100644 index 0000000..388dc70 Binary files /dev/null and b/public/assets/96c24a25fd78210c.webp differ diff --git a/public/assets/98454e9d9e2ef0a1.jpg b/public/assets/98454e9d9e2ef0a1.jpg new file mode 100644 index 0000000..4746a33 Binary files /dev/null and b/public/assets/98454e9d9e2ef0a1.jpg differ diff --git a/public/assets/98ddead2e56f2360.webp b/public/assets/98ddead2e56f2360.webp new file mode 100644 index 0000000..8f9f372 Binary files /dev/null and b/public/assets/98ddead2e56f2360.webp differ diff --git a/public/assets/9903882b2da0df62.svg b/public/assets/9903882b2da0df62.svg new file mode 100644 index 0000000..3431826 --- /dev/null +++ b/public/assets/9903882b2da0df62.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/9a5b6e9db9de76b8.svg b/public/assets/9a5b6e9db9de76b8.svg new file mode 100644 index 0000000..a7da62e --- /dev/null +++ b/public/assets/9a5b6e9db9de76b8.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/assets/9bb14f2dd28ab426.png b/public/assets/9bb14f2dd28ab426.png new file mode 100644 index 0000000..d53beea Binary files /dev/null and b/public/assets/9bb14f2dd28ab426.png differ diff --git a/public/assets/9df0afe903e8d542.svg b/public/assets/9df0afe903e8d542.svg new file mode 100644 index 0000000..cfadbaf --- /dev/null +++ b/public/assets/9df0afe903e8d542.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/assets/a0c6451fadcb7738.svg b/public/assets/a0c6451fadcb7738.svg new file mode 100644 index 0000000..5116bb4 --- /dev/null +++ b/public/assets/a0c6451fadcb7738.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/a0cb8cb4c545a0bc.png b/public/assets/a0cb8cb4c545a0bc.png new file mode 100644 index 0000000..82d610d Binary files /dev/null and b/public/assets/a0cb8cb4c545a0bc.png differ diff --git a/public/assets/a0f163d35a28d0c6.webp b/public/assets/a0f163d35a28d0c6.webp new file mode 100644 index 0000000..dfbe9dc Binary files /dev/null and b/public/assets/a0f163d35a28d0c6.webp differ diff --git a/public/assets/a6ba4b9749786513.png b/public/assets/a6ba4b9749786513.png new file mode 100644 index 0000000..7d4c318 Binary files /dev/null and b/public/assets/a6ba4b9749786513.png differ diff --git a/public/assets/a8076c86caaa5bd7.png b/public/assets/a8076c86caaa5bd7.png new file mode 100644 index 0000000..c467792 Binary files /dev/null and b/public/assets/a8076c86caaa5bd7.png differ diff --git a/public/assets/a8b9176037760d37.webp b/public/assets/a8b9176037760d37.webp new file mode 100644 index 0000000..ae91211 Binary files /dev/null and b/public/assets/a8b9176037760d37.webp differ diff --git a/public/assets/a95a5642fd778796.webp b/public/assets/a95a5642fd778796.webp new file mode 100644 index 0000000..89984a0 Binary files /dev/null and b/public/assets/a95a5642fd778796.webp differ diff --git a/public/assets/abe9b9f85f00e37d.jpg b/public/assets/abe9b9f85f00e37d.jpg new file mode 100644 index 0000000..019b8cf Binary files /dev/null and b/public/assets/abe9b9f85f00e37d.jpg differ diff --git a/public/assets/addb3b51206a8978.png b/public/assets/addb3b51206a8978.png new file mode 100644 index 0000000..85dd094 Binary files /dev/null and b/public/assets/addb3b51206a8978.png differ diff --git a/public/assets/af4370e9c87f0f75.webp b/public/assets/af4370e9c87f0f75.webp new file mode 100644 index 0000000..2c40fb4 Binary files /dev/null and b/public/assets/af4370e9c87f0f75.webp differ diff --git a/public/assets/afc7eccbc7ad2064.webp b/public/assets/afc7eccbc7ad2064.webp new file mode 100644 index 0000000..237f667 Binary files /dev/null and b/public/assets/afc7eccbc7ad2064.webp differ diff --git a/public/assets/b572feb468a57fe7.jpg b/public/assets/b572feb468a57fe7.jpg new file mode 100644 index 0000000..444227d Binary files /dev/null and b/public/assets/b572feb468a57fe7.jpg differ diff --git a/public/assets/b57b58bd55aeee91.png b/public/assets/b57b58bd55aeee91.png new file mode 100644 index 0000000..2813a74 Binary files /dev/null and b/public/assets/b57b58bd55aeee91.png differ diff --git a/public/assets/b651ac584424e2f2.jpg b/public/assets/b651ac584424e2f2.jpg new file mode 100644 index 0000000..19181e0 Binary files /dev/null and b/public/assets/b651ac584424e2f2.jpg differ diff --git a/public/assets/b71aedc5cfcc6bd5.svg b/public/assets/b71aedc5cfcc6bd5.svg new file mode 100644 index 0000000..a6db8b2 --- /dev/null +++ b/public/assets/b71aedc5cfcc6bd5.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/assets/b73eaf71d4ec80f9.webp b/public/assets/b73eaf71d4ec80f9.webp new file mode 100644 index 0000000..4711468 Binary files /dev/null and b/public/assets/b73eaf71d4ec80f9.webp differ diff --git a/public/assets/b90a9ab6918d47c7.svg b/public/assets/b90a9ab6918d47c7.svg new file mode 100644 index 0000000..55d5b35 --- /dev/null +++ b/public/assets/b90a9ab6918d47c7.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/assets/ba17d56edfb047e2.svg b/public/assets/ba17d56edfb047e2.svg new file mode 100644 index 0000000..785348f --- /dev/null +++ b/public/assets/ba17d56edfb047e2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/bcf06f98cbfa83ee.jpg b/public/assets/bcf06f98cbfa83ee.jpg new file mode 100644 index 0000000..7c4dfec Binary files /dev/null and b/public/assets/bcf06f98cbfa83ee.jpg differ diff --git a/public/assets/bd44ab8fafaca01f.webp b/public/assets/bd44ab8fafaca01f.webp new file mode 100644 index 0000000..7889474 Binary files /dev/null and b/public/assets/bd44ab8fafaca01f.webp differ diff --git a/public/assets/bd69a2c88e8d17cc.png b/public/assets/bd69a2c88e8d17cc.png new file mode 100644 index 0000000..5fb3c95 Binary files /dev/null and b/public/assets/bd69a2c88e8d17cc.png differ diff --git a/public/assets/bd8f7fd04bc0b709.png b/public/assets/bd8f7fd04bc0b709.png new file mode 100644 index 0000000..1b8750b Binary files /dev/null and b/public/assets/bd8f7fd04bc0b709.png differ diff --git a/public/assets/bf82634dbd0f4907.svg b/public/assets/bf82634dbd0f4907.svg new file mode 100644 index 0000000..4e6d0e6 --- /dev/null +++ b/public/assets/bf82634dbd0f4907.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/assets/c1cb918e48d35bd3.webp b/public/assets/c1cb918e48d35bd3.webp new file mode 100644 index 0000000..19cbac3 Binary files /dev/null and b/public/assets/c1cb918e48d35bd3.webp differ diff --git a/public/assets/c468611c26ac6aa5.webp b/public/assets/c468611c26ac6aa5.webp new file mode 100644 index 0000000..2e4085e Binary files /dev/null and b/public/assets/c468611c26ac6aa5.webp differ diff --git a/public/assets/c568d83ba6e559ac.png b/public/assets/c568d83ba6e559ac.png new file mode 100644 index 0000000..38b1707 Binary files /dev/null and b/public/assets/c568d83ba6e559ac.png differ diff --git a/public/assets/c612eedfded8baed.webp b/public/assets/c612eedfded8baed.webp new file mode 100644 index 0000000..10bce49 Binary files /dev/null and b/public/assets/c612eedfded8baed.webp differ diff --git a/public/assets/c792d0645e28b789.webp b/public/assets/c792d0645e28b789.webp new file mode 100644 index 0000000..04ec12d Binary files /dev/null and b/public/assets/c792d0645e28b789.webp differ diff --git a/public/assets/c80f072eec53a3ca.svg b/public/assets/c80f072eec53a3ca.svg new file mode 100644 index 0000000..260fdc6 --- /dev/null +++ b/public/assets/c80f072eec53a3ca.svg @@ -0,0 +1 @@ +热搜榜 \ No newline at end of file diff --git a/public/assets/c8cb1f1f87814ea6.jpg b/public/assets/c8cb1f1f87814ea6.jpg new file mode 100644 index 0000000..f516a33 Binary files /dev/null and b/public/assets/c8cb1f1f87814ea6.jpg differ diff --git a/public/assets/c9e57990dff7b688.png b/public/assets/c9e57990dff7b688.png new file mode 100644 index 0000000..410522a Binary files /dev/null and b/public/assets/c9e57990dff7b688.png differ diff --git a/public/assets/cb1ba9ad10c4d7e0.png b/public/assets/cb1ba9ad10c4d7e0.png new file mode 100644 index 0000000..61f9fb1 Binary files /dev/null and b/public/assets/cb1ba9ad10c4d7e0.png differ diff --git a/public/assets/cd2c7b933f47bf37.webp b/public/assets/cd2c7b933f47bf37.webp new file mode 100644 index 0000000..e6cb1e1 Binary files /dev/null and b/public/assets/cd2c7b933f47bf37.webp differ diff --git a/public/assets/cd8b6ba1e9378a10.png b/public/assets/cd8b6ba1e9378a10.png new file mode 100644 index 0000000..e1003e7 Binary files /dev/null and b/public/assets/cd8b6ba1e9378a10.png differ diff --git a/public/assets/ce2f84f155d32b0b.png b/public/assets/ce2f84f155d32b0b.png new file mode 100644 index 0000000..5334d67 Binary files /dev/null and b/public/assets/ce2f84f155d32b0b.png differ diff --git a/public/assets/cea8731b980a14a0.png b/public/assets/cea8731b980a14a0.png new file mode 100644 index 0000000..06b190a Binary files /dev/null and b/public/assets/cea8731b980a14a0.png differ diff --git a/public/assets/cf6f51dd8344bdcf.png b/public/assets/cf6f51dd8344bdcf.png new file mode 100644 index 0000000..6df14b0 Binary files /dev/null and b/public/assets/cf6f51dd8344bdcf.png differ diff --git a/public/assets/d001d1cc6c2f0ce6.svg b/public/assets/d001d1cc6c2f0ce6.svg new file mode 100644 index 0000000..75a9613 --- /dev/null +++ b/public/assets/d001d1cc6c2f0ce6.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/assets/d11925d521c7a7a7.webp b/public/assets/d11925d521c7a7a7.webp new file mode 100644 index 0000000..da1625b Binary files /dev/null and b/public/assets/d11925d521c7a7a7.webp differ diff --git a/public/assets/d2182785de825f85.svg b/public/assets/d2182785de825f85.svg new file mode 100644 index 0000000..44434b5 --- /dev/null +++ b/public/assets/d2182785de825f85.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/d4b3692a7af5718a.webp b/public/assets/d4b3692a7af5718a.webp new file mode 100644 index 0000000..3f5b1b1 Binary files /dev/null and b/public/assets/d4b3692a7af5718a.webp differ diff --git a/public/assets/d4e6ff3babd3ac8c.jpg b/public/assets/d4e6ff3babd3ac8c.jpg new file mode 100644 index 0000000..8f1892e Binary files /dev/null and b/public/assets/d4e6ff3babd3ac8c.jpg differ diff --git a/public/assets/d4ebee2aa88ba9a2.svg b/public/assets/d4ebee2aa88ba9a2.svg new file mode 100644 index 0000000..e5f1b7d --- /dev/null +++ b/public/assets/d4ebee2aa88ba9a2.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/d4f324df023454a6.png b/public/assets/d4f324df023454a6.png new file mode 100644 index 0000000..0bd841a Binary files /dev/null and b/public/assets/d4f324df023454a6.png differ diff --git a/public/assets/d59d2d7390d88f08.svg b/public/assets/d59d2d7390d88f08.svg new file mode 100644 index 0000000..fd6d63f --- /dev/null +++ b/public/assets/d59d2d7390d88f08.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/assets/d66d6df7a026c4e6.webp b/public/assets/d66d6df7a026c4e6.webp new file mode 100644 index 0000000..4c679c5 Binary files /dev/null and b/public/assets/d66d6df7a026c4e6.webp differ diff --git a/public/assets/d88cd6f4785506cf.jpg b/public/assets/d88cd6f4785506cf.jpg new file mode 100644 index 0000000..9ac7c23 Binary files /dev/null and b/public/assets/d88cd6f4785506cf.jpg differ diff --git a/public/assets/dc0366b46a5478f2.webp b/public/assets/dc0366b46a5478f2.webp new file mode 100644 index 0000000..f03fd74 Binary files /dev/null and b/public/assets/dc0366b46a5478f2.webp differ diff --git a/public/assets/dc5a4f514353d4c3.png b/public/assets/dc5a4f514353d4c3.png new file mode 100644 index 0000000..6bd0220 Binary files /dev/null and b/public/assets/dc5a4f514353d4c3.png differ diff --git a/public/assets/de7a7edd54482f6e.webp b/public/assets/de7a7edd54482f6e.webp new file mode 100644 index 0000000..7ffdbe5 Binary files /dev/null and b/public/assets/de7a7edd54482f6e.webp differ diff --git a/public/assets/dfda4dcfd6a1c3a3.png b/public/assets/dfda4dcfd6a1c3a3.png new file mode 100644 index 0000000..c8397ff Binary files /dev/null and b/public/assets/dfda4dcfd6a1c3a3.png differ diff --git a/public/assets/dff7559d1b382481.webp b/public/assets/dff7559d1b382481.webp new file mode 100644 index 0000000..e1a208c Binary files /dev/null and b/public/assets/dff7559d1b382481.webp differ diff --git a/public/assets/e01d276237418eec.webp b/public/assets/e01d276237418eec.webp new file mode 100644 index 0000000..ccf1bd7 Binary files /dev/null and b/public/assets/e01d276237418eec.webp differ diff --git a/public/assets/e26ed8036e427ca3.webp b/public/assets/e26ed8036e427ca3.webp new file mode 100644 index 0000000..fa11642 Binary files /dev/null and b/public/assets/e26ed8036e427ca3.webp differ diff --git a/public/assets/e2e6cfa02b692824.jpg b/public/assets/e2e6cfa02b692824.jpg new file mode 100644 index 0000000..dc7a43f Binary files /dev/null and b/public/assets/e2e6cfa02b692824.jpg differ diff --git a/public/assets/e41e657a9f3adc5c.png b/public/assets/e41e657a9f3adc5c.png new file mode 100644 index 0000000..3b93b3b Binary files /dev/null and b/public/assets/e41e657a9f3adc5c.png differ diff --git a/public/assets/e4baa97000c6f0fe.png b/public/assets/e4baa97000c6f0fe.png new file mode 100644 index 0000000..fcddde3 Binary files /dev/null and b/public/assets/e4baa97000c6f0fe.png differ diff --git a/public/assets/eb003aa14e68556b.webp b/public/assets/eb003aa14e68556b.webp new file mode 100644 index 0000000..2ba0523 Binary files /dev/null and b/public/assets/eb003aa14e68556b.webp differ diff --git a/public/assets/eb4bee364040817e.png b/public/assets/eb4bee364040817e.png new file mode 100644 index 0000000..1dbc6b0 Binary files /dev/null and b/public/assets/eb4bee364040817e.png differ diff --git a/public/assets/ee7917333f5f4d49.webp b/public/assets/ee7917333f5f4d49.webp new file mode 100644 index 0000000..fa3e863 Binary files /dev/null and b/public/assets/ee7917333f5f4d49.webp differ diff --git a/public/assets/f0e3cca7f186c715.png b/public/assets/f0e3cca7f186c715.png new file mode 100644 index 0000000..0bf7485 Binary files /dev/null and b/public/assets/f0e3cca7f186c715.png differ diff --git a/public/assets/f19227c0a214f999.png b/public/assets/f19227c0a214f999.png new file mode 100644 index 0000000..f934bfb Binary files /dev/null and b/public/assets/f19227c0a214f999.png differ diff --git a/public/assets/f2f13d8c5b693bac.webp b/public/assets/f2f13d8c5b693bac.webp new file mode 100644 index 0000000..29e4127 Binary files /dev/null and b/public/assets/f2f13d8c5b693bac.webp differ diff --git a/public/assets/f3d55d5dd5a321d3.png b/public/assets/f3d55d5dd5a321d3.png new file mode 100644 index 0000000..00ef994 Binary files /dev/null and b/public/assets/f3d55d5dd5a321d3.png differ diff --git a/public/assets/f4a2543ba4fb506b.jpg b/public/assets/f4a2543ba4fb506b.jpg new file mode 100644 index 0000000..60fa963 Binary files /dev/null and b/public/assets/f4a2543ba4fb506b.jpg differ diff --git a/public/assets/f6fd1c865977e25d.png b/public/assets/f6fd1c865977e25d.png new file mode 100644 index 0000000..f84c53b Binary files /dev/null and b/public/assets/f6fd1c865977e25d.png differ diff --git a/public/assets/f73e1cf1db670dc9.webp b/public/assets/f73e1cf1db670dc9.webp new file mode 100644 index 0000000..b50e8a6 Binary files /dev/null and b/public/assets/f73e1cf1db670dc9.webp differ diff --git a/public/assets/f7534a32a91cf37b.png b/public/assets/f7534a32a91cf37b.png new file mode 100644 index 0000000..d8bc4e5 Binary files /dev/null and b/public/assets/f7534a32a91cf37b.png differ diff --git a/public/assets/f90a5717b5768f43.webp b/public/assets/f90a5717b5768f43.webp new file mode 100644 index 0000000..91cd7da Binary files /dev/null and b/public/assets/f90a5717b5768f43.webp differ diff --git a/public/assets/f92464cf99366ea2.webp b/public/assets/f92464cf99366ea2.webp new file mode 100644 index 0000000..7aae016 Binary files /dev/null and b/public/assets/f92464cf99366ea2.webp differ diff --git a/public/assets/fcbe47a58d6fd9e7.svg b/public/assets/fcbe47a58d6fd9e7.svg new file mode 100644 index 0000000..dfbc026 --- /dev/null +++ b/public/assets/fcbe47a58d6fd9e7.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/assets/fd1d767246850d19.webp b/public/assets/fd1d767246850d19.webp new file mode 100644 index 0000000..4e7d964 Binary files /dev/null and b/public/assets/fd1d767246850d19.webp differ diff --git a/public/assets/fd58cd99b50a8dbe.png b/public/assets/fd58cd99b50a8dbe.png new file mode 100644 index 0000000..a5e1ae4 Binary files /dev/null and b/public/assets/fd58cd99b50a8dbe.png differ diff --git a/public/assets/fdd6fe924244b1f4.jpg b/public/assets/fdd6fe924244b1f4.jpg new file mode 100644 index 0000000..9ee8be0 Binary files /dev/null and b/public/assets/fdd6fe924244b1f4.jpg differ diff --git a/public/assets/febda32da630fa68.webp b/public/assets/febda32da630fa68.webp new file mode 100644 index 0000000..cca45ba Binary files /dev/null and b/public/assets/febda32da630fa68.webp differ diff --git a/public/assets/poster-placeholder.svg b/public/assets/poster-placeholder.svg new file mode 100644 index 0000000..ae3ad39 --- /dev/null +++ b/public/assets/poster-placeholder.svg @@ -0,0 +1 @@ +91 短剧 diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..ef90914 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/og-default.png b/public/og-default.png new file mode 100644 index 0000000..3a7e2b8 Binary files /dev/null and b/public/og-default.png differ diff --git a/public/og-default.svg b/public/og-default.svg new file mode 100644 index 0000000..c06453c --- /dev/null +++ b/public/og-default.svg @@ -0,0 +1 @@ + diff --git a/public/scripts/site-runtime.js b/public/scripts/site-runtime.js new file mode 100644 index 0000000..214bc45 --- /dev/null +++ b/public/scripts/site-runtime.js @@ -0,0 +1,134 @@ +// Layout and player controls retained from the HTML reference. Business data is loaded by the Next.js application. + +/* ---- unified-empty-states-script ---- */ + + +const emptyStateImages={"write":"/assets/ce2f84f155d32b0b.png","planet":"/assets/a6ba4b9749786513.png","records":"/assets/51e809aac4f1e40a.png","error":"/assets/2d8f1898b668e45e.png","purchased":"/assets/b57b58bd55aeee91.png","profile":"/assets/1f0d06c643b6660a.png","content":"/assets/55f4c0c888a3cee3.png","data":"/assets/a0cb8cb4c545a0bc.png","search":"/assets/c568d83ba6e559ac.png","network":"/assets/47632de987649cda.png","messages":"/assets/9bb14f2dd28ab426.png","calendar":"/assets/eb4bee364040817e.png"}; +function emptyStateMarkup(kind,options={}){ + const defaults={search:['搜索无结果','换个关键词试试,也许下一部好剧就在这里。','查看全部短剧'],data:['暂无数据','这里还没有记录,去发现一些喜欢的短剧吧。','去发现好剧'],error:['加载失败','内容暂时未能加载,请稍后重试。','重新加载'],content:['暂无内容','精彩内容正在准备中,先去看看其他好剧吧。','随便逛逛']}; + const config=defaults[kind]||defaults.content,escape=value=>String(value).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); + const title=options.title||config[0],description=options.description||config[1],label=options.action===false?'':(options.label||config[2]); + const action=label?(kind==='error'?'':''+escape(label)+''):''; + return '

'+escape(title)+'

'+escape(description)+'

'+action+'
'; +} + +; + + +/* ---- sidebar-initial-state ---- */ + +try{if(localStorage.getItem('huangguo-sidebar-collapsed')==='true')document.documentElement.classList.add('sidebar-collapsed')}catch{} +; + + +/* ---- collapsible-sidebar-script ---- */ + + +(() => { + const root=document.documentElement; + const toggle=document.getElementById('sidebar-toggle'); + const key='huangguo-sidebar-collapsed'; + function update(){const collapsed=root.classList.contains('sidebar-collapsed');toggle.setAttribute('aria-expanded',String(!collapsed));toggle.setAttribute('aria-label',collapsed?'展开侧边栏':'收起侧边栏');toggle.title=collapsed?'展开侧边栏':'收起侧边栏'} + const header=toggle.closest('.sidebar-header'); + toggle.addEventListener('click',()=>{const collapsed=root.classList.toggle('sidebar-collapsed');header.classList.toggle('just-collapsed',collapsed);try{localStorage.setItem(key,String(collapsed))}catch{}update()}); + header.addEventListener('mouseleave',()=>header.classList.remove('just-collapsed')); + header.addEventListener('focusout',()=>header.classList.remove('just-collapsed')); + toggle.addEventListener('keydown',()=>header.classList.remove('just-collapsed')); + update(); +})(); + +; + + +/* ---- custom-player-controls-script ---- */ + + +function installPlayerControls(screen,currentEpisode=1,actions={}){ + const video=screen.querySelector('video'),bar=screen.querySelector('.play-screen-controls'),overlay=screen.querySelector('.play-screen-overlay'),status=screen.querySelector('.play-source-status'); + const paths={play:'',pause:'',next:'',volume:'',mute:'',full:'',exit:''}; + Object.assign(paths,{"play": "", "centerPlay": "", "previous": "\n", "next": "\n", "pause": "\n"}); + const icon=name=>''; + const last=bar.querySelector('[data-play-action="next"]')?.disabled; + bar.innerHTML=`
00:00 / 00:00
`; + screen.tabIndex=0;screen.setAttribute('aria-label','视频播放器'); + const seek=bar.querySelector('.player-seek'),volume=bar.querySelector('.player-volume input'),toggle=bar.querySelector('[data-media="toggle"]'),mute=bar.querySelector('[data-media="mute"]'),full=bar.querySelector('[data-media="fullscreen"]'); + const center=overlay.querySelector('button');center.innerHTML=icon('centerPlay');center.removeAttribute('data-play-action');center.dataset.media='toggle'; + const time=t=>{t=Number.isFinite(t)?Math.max(0,Math.floor(t)):0;return (t>=3600?Math.floor(t/3600)+':':'')+String(Math.floor(t/60)%60).padStart(2,'0')+':'+String(t%60).padStart(2,'0')}; + function sync(){const duration=Number.isFinite(video.duration)?video.duration:0;seek.disabled=!duration;seek.max=duration||100;seek.value=video.currentTime||0;seek.style.setProperty('--fill',(duration?video.currentTime/duration*100:0)+'%');seek.setAttribute('aria-valuetext',time(video.currentTime)+' / '+time(duration));bar.querySelector('.player-time').textContent=time(video.currentTime)+' / '+time(duration);toggle.innerHTML=icon(video.paused?'play':'pause');toggle.setAttribute('aria-label',video.paused?'播放':'暂停');overlay.hidden=!video.paused;volume.value=video.muted?0:video.volume;volume.style.setProperty('--fill',Number(volume.value)*100+'%');mute.innerHTML=icon(video.muted||!video.volume?'mute':'volume');mute.setAttribute('aria-label',video.muted||!video.volume?'取消静音':'静音')} + async function play(){if(actions.toggle){try{await actions.toggle()}catch{status.textContent='播放失败,请重试';overlay.hidden=false}return}if(!video.currentSrc&&!video.getAttribute('src')&&!video.querySelector('source[src]')){status.textContent='本剧尚未提供视频源,接入视频后即可播放';return}if(video.paused){try{await video.play()}catch{status.textContent='暂时无法播放,请检查视频源后重试';overlay.hidden=false}}else video.pause()} + async function retry(){overlay.querySelector('.ui-empty-state')?.remove();center.hidden=false;status.hidden=false;status.textContent='正在重新加载…';try{if(actions.retry)await actions.retry();else{video.load();await play()}}catch{status.textContent='视频加载失败,请重试';overlay.hidden=false}} + async function fullscreen(){try{if(document.fullscreenElement)await document.exitFullscreen();else if(screen.requestFullscreen)await screen.requestFullscreen();else if(video.webkitEnterFullscreen)video.webkitEnterFullscreen();else throw Error()}catch{status.textContent='当前浏览器暂不支持全屏';overlay.hidden=false}} + function closeMenus(){bar.querySelectorAll('[data-options]').forEach(e=>e.hidden=true);bar.querySelectorAll('[data-menu]').forEach(e=>e.setAttribute('aria-expanded','false'))} + screen.addEventListener('click',e=>{const b=e.target.closest('button');if(!b){if(e.target===video){closeMenus();play()}return}if(['previous','next'].includes(b.dataset.playAction)||b.hasAttribute('data-show-unlock'))return;e.stopPropagation();if(b.dataset.media==='retry')void retry();if(b.dataset.media==='toggle')play();if(b.dataset.media==='mute'){if(video.muted||!video.volume){video.muted=false;if(!video.volume)video.volume=.5}else video.muted=true;sync()}if(b.dataset.media==='fullscreen')fullscreen();if(b.dataset.menu){const menu=bar.querySelector('[data-options="'+b.dataset.menu+'"]'),open=menu.hidden;closeMenus();menu.hidden=!open;b.setAttribute('aria-expanded',String(open))}else if(b.dataset.speed){video.playbackRate=Number(b.dataset.speed);bar.querySelector('[data-menu="speed"]').textContent=b.dataset.speed==='1'?'倍速':b.dataset.speed+'×';bar.querySelectorAll('[data-speed]').forEach(e=>e.setAttribute('aria-checked',String(e===b)));closeMenus()}else closeMenus()}); + seek.addEventListener('input',()=>{if(Number.isFinite(video.duration)&&video.duration>0){video.currentTime=Math.min(video.duration,Math.max(0,Number(seek.value)));sync()}}); + volume.addEventListener('input',()=>{video.volume=Number(volume.value);video.muted=video.volume===0;sync()}); + screen.addEventListener('keydown',e=>{if(e.target.matches('input,button'))return;if(e.code==='Space'){e.preventDefault();play()}else if(e.key.toLowerCase()==='m'){mute.click()}else if(e.key.toLowerCase()==='f')fullscreen();else if(['ArrowLeft','ArrowRight'].includes(e.key)&&Number.isFinite(video.duration)){e.preventDefault();video.currentTime=Math.min(video.duration,Math.max(0,video.currentTime+(e.key==='ArrowRight'?5:-5)));sync()}else if(e.key==='Escape')closeMenus()}); + screen.addEventListener('focusout',e=>{if(!screen.contains(e.relatedTarget))closeMenus()}); + screen.addEventListener('fullscreenchange',()=>{const active=document.fullscreenElement===screen;full.innerHTML=icon(active?'exit':'full');full.setAttribute('aria-label',active?'退出全屏':'进入全屏')}); + ['loadedmetadata','durationchange','timeupdate','play','pause','ended','volumechange','emptied'].forEach(event=>video.addEventListener(event,sync));function failed(event){if(event.type==='error')status.textContent='视频加载失败,请重试';overlay.querySelector('.ui-empty-state')?.remove();center.hidden=true;status.hidden=true;overlay.insertAdjacentHTML('beforeend',emptyStateMarkup('error',{description:status.textContent}));overlay.hidden=false}video.addEventListener('error',failed);screen.addEventListener('playback:error',failed); + screen._disposePlayer=()=>video.pause();sync(); +} + +; + + +/* ---- sticky-search-header-script ---- */ + + +(()=>{ + const siteHeader=document.getElementById('global-site-header');if(!siteHeader)return; + let scheduled=false; + function update(){siteHeader.classList.toggle('is-scrolled',window.scrollY>8);scheduled=false} + function measure(){document.documentElement.style.setProperty('--global-header-height',siteHeader.getBoundingClientRect().height+'px')} + new ResizeObserver(measure).observe(siteHeader);measure(); + window.addEventListener('scroll',()=>{if(!scheduled){scheduled=true;requestAnimationFrame(update)}},{passive:true}); + window.addEventListener('site:route',update);update(); +})(); + +; + + +/* ---- mobile-slide-navigation-script ---- */ + + +(()=>{ + const header=document.querySelector('#global-site-header .top'),sidebar=document.getElementById('site-sidebar');if(!header||!sidebar)return; + const mobile=matchMedia('(max-width:760px)'),trigger=document.createElement('button');trigger.type='button';trigger.className='mobile-menu-trigger';trigger.setAttribute('aria-label','打开导航菜单');trigger.setAttribute('aria-controls','mobile-navigation-drawer');trigger.setAttribute('aria-expanded','false');trigger.innerHTML='';header.prepend(trigger); + const drawer=document.createElement('dialog');drawer.id='mobile-navigation-drawer';drawer.setAttribute('aria-label','网站导航'); + drawer.innerHTML='
'; + const brand=sidebar.querySelector('.sidebar-brand').cloneNode(true);brand.removeAttribute('class');drawer.querySelector('.mobile-drawer-brand').append(brand); + const nav=drawer.querySelector('.mobile-drawer-links'); + const sourceNavigation=sidebar.querySelector('[data-site-navigation="sidebar"]'); + if(sourceNavigation)nav.innerHTML=sourceNavigation.innerHTML; + nav.querySelectorAll('a[data-page]').forEach(link=>{link.dataset.mobileRoute=link.dataset.page;link.dataset.siteNav='sidebar';}); + const pcInfo=sidebar.querySelector('.side-info');if(pcInfo)drawer.querySelector('.mobile-drawer-info').innerHTML=pcInfo.innerHTML; + const footerLogo=drawer.querySelector('.mobile-drawer-info .side-info-wordmark');if(footerLogo){const brandText=document.createElement('span');brandText.className='mobile-drawer-brand-text';brandText.textContent='91短剧';footerLogo.replaceWith(brandText);} + sidebar.querySelectorAll('.sidebar-social a').forEach(link=>drawer.querySelector('.mobile-drawer-social').append(link.cloneNode(true))); + document.body.append(drawer);let timer=null,start=null; + function finishClose(){clearTimeout(timer);timer=null;drawer.classList.remove('is-closing');if(drawer.open)drawer.close();document.body.classList.remove('mobile-drawer-open');trigger.setAttribute('aria-expanded','false')} + function close(animated=true){if(!drawer.open)return;if(!animated||matchMedia('(prefers-reduced-motion:reduce)').matches){finishClose();return}if(timer)return;drawer.classList.add('is-closing');timer=setTimeout(finishClose,180)} + trigger.addEventListener('click',()=>{if(!mobile.matches)return;clearTimeout(timer);timer=null;drawer.classList.remove('is-closing');drawer.showModal();document.body.classList.add('mobile-drawer-open');trigger.setAttribute('aria-expanded','true')}); + drawer.querySelector('.mobile-drawer-close').addEventListener('click',()=>close()); + drawer.addEventListener('cancel',e=>{e.preventDefault();close()});drawer.addEventListener('close',finishClose); + drawer.addEventListener('click',e=>{if(e.target.closest('a')){close();return}if(e.target!==drawer)return;const r=drawer.getBoundingClientRect();if(e.clientXr.right||e.clientYr.bottom)close()}); + drawer.addEventListener('touchstart',e=>{start=e.touches.length===1?{x:e.touches[0].clientX,y:e.touches[0].clientY}:null},{passive:true}); + drawer.addEventListener('touchend',e=>{if(!start)return;const t=e.changedTouches[0],dx=t.clientX-start.x,dy=t.clientY-start.y;start=null;if(dx < -60&&Math.abs(dx)>Math.abs(dy)*1.5)close()},{passive:true}); + drawer.addEventListener('touchcancel',()=>{start=null},{passive:true}); + window.addEventListener('site:route',()=>close());mobile.addEventListener('change',()=>{if(!mobile.matches)close(false)}); +})(); + +; + + +/* ---- huangguo-figma-refresh-script ---- */ + +(()=>{ + const header=document.querySelector('#global-site-header .top'); + if(!header)return; + const query=document.getElementById('query');if(query)query.placeholder='搜索你感兴趣的内容'; + const searchButton=header.querySelector('.search button[type=submit]');if(searchButton)searchButton.innerHTML="\"搜索图标\""; + const coin=header.querySelector('.coin-symbol img');if(coin)coin.src="/assets/8eb3c37a26ca792b.svg"; + const account=header.querySelector('.account-avatar .guest-avatar-image');if(account)account.src="/assets/b90a9ab6918d47c7.svg"; + +})(); +; diff --git a/public/scripts/web-sdk-v1.2.0.js b/public/scripts/web-sdk-v1.2.0.js new file mode 100644 index 0000000..f2268b8 --- /dev/null +++ b/public/scripts/web-sdk-v1.2.0.js @@ -0,0 +1 @@ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).WebSDK=t()}(this,function(){"use strict";function e(e){var t,n,r,o,i,a,s,c,u,l,d,_,p,f,h,g,m,y,v,w,k,b,C,S,P=function(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}((null==e?"":e)+""),E=8*P.length;for(P.push(128);P.length%64!=56;)P.push(0);for(t=0;8>t;t++)P.push(E>>>8*t&255);for(n=function(e,t){return e<>>32-t},r=function(e,t){return((e>>>0)+(t>>>0)&4294967295)>>>0},o=function(e,t,n,o){return r(r(e,t),r(n,o))},i=[7,12,17,22,7,12,17,22,7,12,17,22,7,12,17,22,5,9,14,20,5,9,14,20,5,9,14,20,5,9,14,20,4,11,16,23,4,11,16,23,4,11,16,23,4,11,16,23,6,10,15,21,6,10,15,21,6,10,15,21,6,10,15,21],a=Array(64),s=0;64>s;s++)a[s]=Math.floor(4294967296*Math.abs(Math.sin(s+1)))>>>0;for(c=1732584193,u=4023233417,l=2562383102,d=271733878,_=0;P.length>_;_+=64){for(p=Array(16),f=0;16>f;f++)p[f]=(P[h=_+4*f]|P[h+1]<<8|P[h+2]<<16|P[h+3]<<24)>>>0;for(g=c,m=u,y=l,v=d,w=0;64>w;w++)16>w?(k=m&y|~m&v,b=w):32>w?(k=v&m|~v&y,b=(5*w+1)%16):48>w?(k=m^y^v,b=(3*w+5)%16):(k=y^(m|~v),b=7*w%16),C=v,v=y,y=m,m=r(m,n(o(g,k,a[w],p[b]),i[w])),g=C;c=r(c,g),u=r(u,m),l=r(l,y),d=r(d,v)}return S=function(e){for(var t="",n=0;4>n;n++)t+=(256+(e>>>8*n&255)).toString(16).slice(1);return t},S(c)+S(u)+S(l)+S(d)}function t(e,t){return(e>>>t|e<<32-t)>>>0}function n(e,t,n){return(e&t^~e&n)>>>0}function r(e,t,n){return(e&t^e&n^t&n)>>>0}function o(e){return(t(e,2)^t(e,13)^t(e,22))>>>0}function i(e){return(t(e,6)^t(e,11)^t(e,25))>>>0}function a(e){return(t(e,7)^t(e,18)^e>>>3)>>>0}function s(e){return(t(e,17)^t(e,19)^e>>>10)>>>0}function c(e){for(var t=(e>>>0).toString(16);8>t.length;)t="0"+t;return t}function u(e,t){return((255&e[t])<<24|(255&e[t+1])<<16|(255&e[t+2])<<8|255&e[t+3])>>>0}function l(e){for(var t=function(e){var t,c,l,d,_,p,f,h,g,m,y,v,w,k,b,C,S,P,E,T,x,D=e.slice(),M=0,I=8*D.length>>>0;for(D.length>=536870912&&(M=Math.floor(D.length/536870912),0==(I=D.length%536870912*8>>>0)&&D.length>0&&M--),D.push(128);D.length%64!=56;)D.push(0);for(D.push(M>>>24&255,M>>>16&255,M>>>8&255,255&M,I>>>24&255,I>>>16&255,I>>>8&255,255&I),t=1779033703,c=3144134277,l=1013904242,d=2773480762,_=1359893119,p=2600822924,f=528734635,h=1541459225,g=0;D.length>g;g+=64){for(y=Array(64),m=0;16>m;m++)y[m]=u(D,g+4*m);for(m=16;64>m;m++)y[m]=s(y[m-2])+y[m-7]+a(y[m-15])+y[m-16]>>>0;for(v=t,w=c,k=l,b=d,C=_,S=p,P=f,E=h,m=0;64>m;m++)T=E+i(C)+n(C,S,P)+zt[m]+y[m]>>>0,x=o(v)+r(v,w,k)>>>0,E=P,P=S,S=C,C=b+T>>>0,b=k,k=w,w=v,v=T+x>>>0;t=t+v>>>0,c=c+w>>>0,l=l+k>>>0,d=d+b>>>0,_=_+C>>>0,p=p+S>>>0,f=f+P>>>0,h=h+E>>>0}return[t,c,l,d,_,p,f,h]}(e||[]),l="",d=0;t.length>d;d++)l+=c(t[d]);return l}function d(e){return l(function(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}((null==e?"":e)+""))}function _(e){var t,n,r,o,i,a;for(t=(null==e?"":e)+"",n=[],r=0;t.length>r;r++)128>(o=t.charCodeAt(r))?n.push(o):2048>o?n.push(192|o>>6,128|63&o):55296>o||o>56319||r+1>=t.length||56320>(i=t.charCodeAt(r+1))||i>57343?n.push(224|o>>12,128|o>>6&63,128|63&o):(n.push(240|(a=i-56320+(o-55296<<10)+65536)>>18,128|a>>12&63,128|a>>6&63,128|63&a),r++);return n}function p(e){for(var t=(null==e?"":e)+"",n=[],r=0;t.length>r;r+=2)n.push(parseInt(t.substr(r,2),16));return n}function f(e,t){var n,r=Array(e.length+t.length);for(n=0;e.length>n;n++)r[n]=e[n];for(n=0;t.length>n;n++)r[e.length+n]=t[n];return r}function h(e,t){var n,r;return null===e?"__t:null__":"string"==typeof e?"__t:str__:"+e:"number"==typeof e&&isFinite(e)?"__t:num__:"+e:"boolean"==typeof e?"__t:bool__:"+(e?"1":"0"):Array.isArray(e)?-1!==t.indexOf(e)?"__t:circular__":(t.push(e),n="["+e.map(function(e){return h(e,t)}).join(",")+"]",t.splice(t.indexOf(e),1),n):null!==e&&"object"==typeof e?-1!==t.indexOf(e)?"__t:circular__":(t.push(e),r="{"+Object.keys(e).sort().map(function(n){return n+":"+h(e[n],t)}).join(",")+"}",t.splice(t.indexOf(e),1),r):"__t:any__:"+e}function g(e){return h(e,[])}function m(t){var n,r=t||{},o=Object.keys(r).filter(function(e){return"event_id"!==e}).sort();return o.length?(n=o.map(function(t){return e(g(r[t]))}).join(""),e(n)):e("")}function y(){if("undefined"==typeof navigator)return"PC";var e=navigator.userAgent||"";return/iPhone|iPad|iPod/i.test(e)?"iOS":function(e){return/HarmonyOS|OpenHarmony/i.test((null==e?"":e)+"")}(e)||/Android/i.test(e)?"Android":"PC"}function v(e){var t=((null==e?"":e)+"").trim();return t&&/^\d+\.\d+\.\d+$/.test(t)?t:"1.0.0"}function w(e,t){return null==e||""===e?"":"int"===t?parseInt(e,10)+"":(e+"").replace(/^\s+|\s+$/g,"").toLowerCase()}function k(e,t){try{return e()}catch(e){return t}}function b(t){return e((null==t?"":t)+"").substr(0,32)}function C(e){return b((e||[]).slice().sort().join("|"))}function S(){return k(function(){var e,t,n,r,o,i,a,s="undefined"!=typeof OfflineAudioContext?OfflineAudioContext:"undefined"!=typeof webkitOfflineAudioContext?webkitOfflineAudioContext:null;if(!s)return"";if((t=(e=new s(1,44100,44100)).createOscillator()).type="triangle",t.frequency.value=1e4,n=e.createDynamicsCompressor(),t.connect(n),n.connect(e.destination),t.start(0),(r=e.startRendering())&&"function"==typeof r.then)return"";if(!r||!r.getChannelData)return"";for(o=r.getChannelData(0),i=0,a=4500;5e3>a&&o.length>a;a++)i+=Math.abs(o[a]);return b(i+"")},"")}function P(){return k(function(){function e(e){var t,n=document.createElement("span");return n.style.cssText="position:absolute;left:-9999px;top:-9999px;visibility:hidden;font-size:72px;font-family:"+e+",monospace;",n.textContent="mmmmmmmmmmlli",document.body.appendChild(n),t=n.offsetWidth,document.body.removeChild(n),t}var t,n,r,o;if("undefined"==typeof document||!document.body)return{hash:"",installed:[],count:-1};for(t=e("monospace"),n=[],r=0;$t.length>r;r++)e("'"+(o=$t[r])+"'")!==t&&n.push(o);return n.sort(),{hash:C(n.map(function(e){return e.toLowerCase()})),installed:n,count:n.length}},{hash:"",installed:[],count:-1})}function E(){return k(function(){var e,t,n,r,o,i,a;if("undefined"==typeof document)return{hash:"",supported:[]};for(e=document.createElement("video"),t=document.createElement("audio"),n=[],r=0;Zt.length>r;r++){i="video"===(o=Zt[r]).kind?e:t,a="";try{a=i.canPlayType?(i.canPlayType(o.type)||"")+"":""}catch(e){}a&&"no"!==a&&n.push(o.type+":"+a)}return n.sort(),{hash:C(n.map(function(e){return e.toLowerCase()})),supported:n}},{hash:"",supported:[]})}function T(e){var t;try{return"undefined"==typeof window?0:(t="session"===e?window.sessionStorage:window.localStorage)?(t.setItem("__sdk_probe__","1"),t.removeItem("__sdk_probe__"),1):0}catch(e){return 0}}function x(){try{return"undefined"!=typeof indexedDB?1:0}catch(e){return 0}}function D(){try{return"undefined"!=typeof navigator&&navigator.serviceWorker?1:0}catch(e){return 0}}function M(e){return/Trident|MSIE/i.test(e=(e||"")+"")?"trident":/Edge\/\d/i.test(e)&&!/Edg\//i.test(e)?"edgehtml":/Edg\//i.test(e)||/Chrome\//i.test(e)&&!/Edg\//i.test(e)?"chromium":/Firefox\//i.test(e)?"gecko":/Safari\//i.test(e)&&!/Chrome\//i.test(e)?"webkit":"unknown"}function I(e,t){return t=(t||"")+"",/HarmonyOS|OpenHarmony/i.test(e=(e||"")+"")?"harmonyos":/iPhone|iPad|iPod/i.test(e)?"ios":/Android/i.test(e)?"android":/Mac OS X|Macintosh/i.test(e)||/^Mac/i.test(t)?"macos":/Windows/i.test(e)||/^Win/i.test(t)?"windows":/Linux/i.test(e)||/Linux/i.test(t)?"linux":"unknown"}function A(e){return/iPad|Tablet/i.test(e=(e||"")+"")?"tablet":/Mobile|iPhone|Android/i.test(e)?"phone":"desktop"}function O(e){var t,n,r;for(e=(e||"")+"",t=[[/EdgiOS\/([\d.]+)/i,"Edge"],[/CriOS\/([\d.]+)/i,"Chrome"],[/FxiOS\/([\d.]+)/i,"Firefox"],[/Edg\/([\d.]+)/i,"Edge"],[/OPR\/([\d.]+)/i,"Opera"],[/Chrome\/([\d.]+)/i,"Chrome"],[/Firefox\/([\d.]+)/i,"Firefox"],[/Version\/([\d.]+).*Safari/i,"Safari"]],n=0;t.length>n;n++)if(r=e.match(t[n][0]))return{browser_name:t[n][1],browser_version:r[1]||"unknown"};return{browser_name:"unknown",browser_version:"unknown"}}function L(){var e=k(function(){var e,t="undefined"!=typeof screen?screen:null;if(t&&t.orientation&&t.orientation.type)return t.orientation.type+"";if("undefined"!=typeof window&&"number"==typeof window.orientation){if(0===(e=window.orientation)||180===e)return"portrait-primary";if(90===e||-90===e)return"landscape-primary"}return t&&null!=t.width&&null!=t.height?t.width>t.height?"landscape-primary":"portrait-primary":"unknown"},"unknown");if(-1!==e.indexOf("landscape"))return"landscape";if(-1!==e.indexOf("portrait"))return"portrait";try{if("undefined"!=typeof screen&&null!=screen.width&&null!=screen.height){if(screen.width>screen.height)return"landscape";if(screen.widthu;u++){l=s[u];try{null!=(d=t[l])&&(c[l]=t.getParameter(d)+"")}catch(e){}}for(_=Object.keys(c).sort(),p=[],f=0;_.length>f;f++)p.push((h=_[f])+"="+c[h]);return g=b(p.join("|")),m=b([n,r,a,g].join("|")),{vendor:n,renderer:r,extensions_hash:a,params_hash:g,webgl_fp:m}},{vendor:"",renderer:"",extensions_hash:"",params_hash:"",webgl_fp:""}),H=F.webgl_fp||"",U=S(),X=k(function(){var e,t,n;if("undefined"==typeof navigator||!navigator.plugins)return"";for(e=[],t=0;t0?Math.round(t):-1},-1),J=(R.platform||"")+"",Q=0;try{Q=R.plugins?R.plugins.length:0}catch(R){}e=G.count,t=H?"available":"unavailable",n=!0===R.webdriver?1:0,r=0,o=0;try{K&&K.documentElement&&(r=K.documentElement.clientWidth||B.innerWidth||0,o=K.documentElement.clientHeight||B.innerHeight||0)}catch(R){}i=0,(1===n||"available"!==t&&0===Q&&e>=0&&2>=e&&(0>=r||0>=o))&&(i=1),a="";try{"undefined"!=typeof Intl&&Intl.DateTimeFormat&&(a=Intl.DateTimeFormat().resolvedOptions().timeZone||"")}catch(R){}s=function(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(R){return""}}(),c="",u="",l="";try{"undefined"!=typeof location&&(c=location.hostname||"",u=location.pathname||"",location.search&&(l=b(location.search)))}catch(R){}_=function(e){var t,n=((e||"")+"").trim();if(!n)return{referer_domain:"",referer_path:""};try{return{referer_domain:(t=new URL(n)).hostname||"",referer_path:t.pathname||""}}catch(e){return{referer_domain:"",referer_path:""}}}(d=K&&K.referrer?K.referrer+"":""),p=function(e){var t,n,r,o;if(t=(e=e||{}).userAgent,/iPhone|iPad|iPod/i.test(t=(t||"")+"")&&!/CriOS\/|FxiOS\/|EdgiOS\/|OPiOS\//i.test(t)&&/Version\/[\d.]+.*Safari/i.test(t))return O(e.userAgent);try{if((n=e.userAgentData&&e.userAgentData.brands)&&n.length)for(r=0;n.length>r;r++)if((o=(n[r].brand||"")+"")&&"Chromium"!==o&&0!==o.indexOf("Not")&&"Not.A/Brand"!==o)return{browser_name:o,browser_version:(n[r].version||"unknown")+""}}catch(e){}return O(e.userAgent)}(R),f=z.supported?z.supported.length:-1,g=Math.round(96*(h=B.devicePixelRatio||1)),m=-1;try{null!=R.hardwareConcurrency&&R.hardwareConcurrency>0&&(m=R.hardwareConcurrency)}catch(R){}y=m,v=-1;try{null!=R.deviceMemory&&R.deviceMemory>0&&(v=Math.round(10*R.deviceMemory)/10)}catch(R){}return w=j&&j.type?j.type+"":"unknown",C=R.maxTouchPoints||0,{platform:"web",os_family:I(V,J),device_type:A(V),browser_kernel:M(V),browser_name:p.browser_name,browser_version:p.browser_version,browser_platform:J,display_mode:k(function(){return"undefined"==typeof window||"function"!=typeof window.matchMedia?"unknown":window.matchMedia("(display-mode: standalone)").matches?"standalone":window.matchMedia("(display-mode: fullscreen)").matches?"fullscreen":window.matchMedia("(display-mode: minimal-ui)").matches?"minimal-ui":"browser"},"unknown"),visibility_state:k(function(){if("undefined"==typeof document)return"unknown";var e=document.visibilityState;return"visible"===e||"hidden"===e?e:"unknown"},"unknown"),screen_width:q.width||0,screen_height:q.height||0,screen_dpi:g,device_pixel_ratio:Math.round(100*h),orientation:L(),viewport_width:r>0?Math.round(r):-1,viewport_height:o>0?Math.round(o):-1,color_depth:q.colorDepth||0,max_touch_points:C,touch_support:C>0?1:0,hardware_concurrency:m,cpu_cores:y,device_memory_gb:v,is_debug:0,language:R.language||"",timezone:a,cookie_enabled:R.cookieEnabled?1:0,local_storage_enabled:T("local"),session_storage_enabled:T("session"),indexeddb_enabled:x(),service_worker_enabled:D(),network_type:"unknown",connection_type:w,effective_type:j&&j.effectiveType?j.effectiveType+"":"unknown",downlink:j&&null!=j.downlink?Math.round(1e3*j.downlink):-1,rtt:j&&null!=j.rtt?j.rtt:-1,canvas_fp:N,webgl_fp:H,webgl_vendor:F.vendor||"",webgl_renderer:F.renderer||"",webgl_extensions_hash:F.extensions_hash||"",webgl_params_hash:F.params_hash||"",audio_fp:U,audio_sample_rate:W>0?W:44100,fonts:G.installed||[],fonts_hash:G.hash,plugins:Y,plugins_hash:X,mimes:z.supported||[],mimes_hash:z.hash,mime_count:f,plugin_count:Q,font_count:e,webgl_status:t,has_webdriver:n,is_headless:i,headless_reason:i?n?"webdriver":"composite":"",current_url:s,current_domain:c,current_path:u,current_query_hash:l,referer:d,referer_domain:_.referer_domain,referer_path:_.referer_path}}function q(e){return null==e?"":e+""}function B(e){var t=e||{},n={app_id:(t.appId||"")+"",uid:null!=t.uid?t.uid:0,channel:(t.channel||"")+"",sid:(t.sid||"")+"",client_ts:Math.floor(Date.now()/1e3),device:(t.deviceType||"PC")+"",device_id:(t.deviceId||"")+"",device_brand:(t.deviceBrand||"")+"",device_model:(t.deviceModel||"")+"",device_fingerprint:(t.deviceFingerprint||"")+"",fp_version:(t.fpVersion||"")+"",user_agent:"undefined"!=typeof navigator&&navigator.userAgent||"",sdk_version:(t.sdkVersion||"")+"",app_version:(t.appVersion||"1.0.0")+""};return null!=t.deviceEnv&&(n.device_env=t.deviceEnv),n}function K(){var e,t,n,r=new Uint8Array(16);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(r);else for(e=0;16>e;e++)r[e]=Math.floor(256*Math.random());for(t="",n=0;r.length>n;n++)t+=("0"+r[n].toString(16)).slice(-2);return t}function V(){try{return!("undefined"==typeof window||!window.sessionStorage||(window.sessionStorage.setItem("__sdk_ss_ping__","1"),window.sessionStorage.removeItem("__sdk_ss_ping__"),0))}catch(e){return!1}}function j(){if(!un){if(un=!0,function(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem("__web_sdk_ping__","1"),window.localStorage.removeItem("__web_sdk_ping__"),0))}catch(e){return!1}}())try{window.localStorage.removeItem(rn)}catch(e){}if("undefined"!=typeof document&&"string"==typeof document.cookie)try{var t="path=/;max-age=0;SameSite=Lax";"undefined"!=typeof location&&"https:"===location.protocol&&(t+=";Secure"),document.cookie=encodeURIComponent(on)+"=;"+t}catch(e){}}}function N(t){j();var n=function(e){return JSON.stringify({sid:e.sid,sessionStartTs:e.sessionStartTs,lastValidBehaviorAt:e.lastValidBehaviorAt})}(t);if(V())try{window.sessionStorage.setItem(tn,t.sid),window.sessionStorage.setItem(nn,n)}catch(e){}cn=t}function F(){if(cn)return cn;j();var t=null;if(V())try{t=window.sessionStorage.getItem(nn)}catch(e){}return cn=t?function(t){try{var n=JSON.parse(t);return n&&n.sid?{sid:n.sid+"",sessionStartTs:"number"==typeof n.sessionStartTs?n.sessionStartTs:Date.now(),lastValidBehaviorAt:"number"==typeof n.lastValidBehaviorAt?n.lastValidBehaviorAt:Date.now()}:null}catch(e){return null}}(t):null,cn}function H(e){return{sid:K(),sessionStartTs:e,lastValidBehaviorAt:e}}function U(e){return"number"==typeof e&&isFinite(e)?1e12>e?1e3*e:e:null}function X(e,t,n){var r=U(e.sessionStartTs)||n,o=U(e.lastValidBehaviorAt)||r,i=n;return"idle"===t?i=Math.min(i=Math.max(r,o+an),n):"forced"===t&&(i=Math.min(r+sn,n)),Math.max(r,i)}function Y(e,t,n){var r,o;return n=n||Date.now(),r=U(e.sessionStartTs)||n,o=X(e,t,n),{sid:e.sid,sessionStartTs:r,lastValidBehaviorAt:U(e.lastValidBehaviorAt)||r,sessionEndTs:o,endReason:t}}function G(e){var t,n,r,o,i;return e=e||Date.now(),(t=F())?(r=e-t.sessionStartTs>sn,e-t.lastValidBehaviorAt>an||r?(o=Y(t,r?"forced":"idle",e),N(i=H(e)),{sid:i.sid,isNew:!0,endedSession:o}):{sid:t.sid,isNew:!1}):(N(n=H(e)),{sid:n.sid,isNew:!0})}function z(){var e=F();return e?e.sid:""}function W(){cn=null}function J(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem(dn,"1"),window.localStorage.removeItem(dn),0))}catch(e){return!1}}function Q(){var t,n;if(!J())return Promise.resolve(_n||"NULL");try{return(t=window.localStorage.getItem(ln))&&/^[0-9a-f]{32}$/.test(t)?Promise.resolve(t):(n=function(){var e,t,n,r=new Uint8Array(16);if("undefined"!=typeof crypto&&crypto.getRandomValues)crypto.getRandomValues(r);else for(e=0;16>e;e++)r[e]=Math.floor(256*Math.random());for(t="",n=0;r.length>n;n++)t+=("0"+r[n].toString(16)).slice(-2);return t}(),window.localStorage.setItem(ln,n),_n=n,Promise.resolve(n))}catch(e){return Promise.resolve("NULL")}}function $(t,n){n&&(t.audio_fp=n),fn=t;var r=function(){if(!J())return"";try{var t=window.localStorage.getItem(pn);if(t&&/^[0-9a-f]{16,64}$/i.test(t))return t.toLowerCase()}catch(e){}return""}();return r?hn=r:(hn=function(e){var t,n,r,o,i,a;for(t=e||{},n={screen_width:1,screen_height:1,screen_dpi:1,device_pixel_ratio:1,color_depth:1,max_touch_points:1,cpu_cores:1,cookie_enabled:1,local_storage_enabled:1,session_storage_enabled:1,indexeddb_enabled:1,service_worker_enabled:1},r={canvas_fp:1,webgl_fp:1,audio_fp:1,fonts_hash:1,plugins_hash:1,mimes_hash:1},o=[],i=0;Qt.length>i;i++)o.push((a=Qt[i])+"="+w(t[a],n[a]?"int":r[a]?"hash":"string"));return d("fp_v3|fingerprint_platform=web|"+o.join("|"))}(t),function(t){if(t&&J())try{window.localStorage.setItem(pn,t)}catch(e){}}(hn)),{snapshot:t,fingerprint:hn}}function Z(){return fn&&hn?Promise.resolve({snapshot:fn,fingerprint:hn}):gn||(gn=new Promise(function(e){k(function(){var t,n,r,o,i="undefined"!=typeof OfflineAudioContext?OfflineAudioContext:"undefined"!=typeof webkitOfflineAudioContext?webkitOfflineAudioContext:null;i?((n=(t=new i(1,44100,44100)).createOscillator()).type="triangle",n.frequency.value=1e4,r=t.createDynamicsCompressor(),n.connect(r),r.connect(t.destination),n.start(0),(o=t.startRendering())&&"function"==typeof o.then?o.then(function(t){for(var n=t.getChannelData(0),r=0,o=4500;5e3>o&&n.length>o;o++)r+=Math.abs(n[o]);e(b(r+""))}).catch(function(){e("")}):e(S())):e("")},e(""))}).then(function(e){return $(R(),e)}).catch(function(){return $(R(),"")}),gn)}function ee(){return hn}function te(){return Wt}function ne(){return mn.digest()}function re(){try{if("undefined"!=typeof screen&&screen.orientation&&screen.orientation.type){var t=screen.orientation.type+"";if(-1!==t.indexOf("landscape"))return"landscape";if(-1!==t.indexOf("portrait"))return"portrait"}if("undefined"!=typeof screen&&null!=screen.width&&null!=screen.height){if(screen.width>screen.height)return"landscape";if(screen.width0?Math.round(n):-1}catch(e){return-1}}function ie(e,t){var n=t.accumulated_ms||0;return null!=t.resume_at_ms&&(n+=Math.max(0,e-t.resume_at_ms)),n}function ae(){return xn.pageSeqCounter++,xn.currentPageSeq=xn.pageSeqCounter,xn.currentPageSeq}function se(){try{return!("undefined"==typeof window||!window.sessionStorage||(window.sessionStorage.setItem("__sdk_metrics_ping__","1"),window.sessionStorage.removeItem("__sdk_metrics_ping__"),0))}catch(e){return!1}}function ce(e){var t,n=e||xn;return{eventCountTotal:n.eventCountTotal||0,businessEventCount:n.businessEventCount||0,pageViewCount:n.pageViewCount||0,pageClickCount:n.pageClickCount||0,networkChangeCount:n.networkChangeCount||0,backgroundCount:n.backgroundCount||0,pageKeys:le({},n.pageKeys||{}),pageStayList:(n.pageStayList||[]).slice(),pageSeqCounter:n.pageSeqCounter||0,currentPageSeq:n.currentPageSeq||0,lastEventName:n.lastEventName||"",scrollTotalCount:n.scrollTotalCount||0,scrollTotalDistance:n.scrollTotalDistance||0,scrollUpCount:n.scrollUpCount||0,scrollDownCount:n.scrollDownCount||0,scrollDirectionChangeCount:n.scrollDirectionChangeCount||0,lastScrollDirection:n.lastScrollDirection||0,firstScrollDelayMs:null!=n.firstScrollDelayMs?n.firstScrollDelayMs:-1,hasScrolled:!!n.hasScrolled,maxScrollDepthPercent:n.maxScrollDepthPercent||0,isReachBottom:n.isReachBottom||0,keydownTotalCount:n.keydownTotalCount||0,inputCount:n.inputCount||0,focusTotalCount:n.focusTotalCount||0,firstInteractionDelayMs:null!=n.firstInteractionDelayMs?n.firstInteractionDelayMs:-1,firstInteractionRecorded:!!n.firstInteractionRecorded,lastOrientation:n.lastOrientation||"unknown",touchTotalCount:n.touchTotalCount||0,swipeTotalCount:n.swipeTotalCount||0,longPressTotalCount:n.longPressTotalCount||0,pinchTotalCount:n.pinchTotalCount||0,currentPage:(t=n.currentPage,t&&"object"==typeof t?{page_key:(t.page_key||"")+"",page_name:(t.page_name||"")+"",enter_time:null!=t.enter_time?t.enter_time:Date.now(),enter_time_ms:null!=t.enter_time_ms?t.enter_time_ms:t.enter_time,page_seq_in_sid:t.page_seq_in_sid||0,has_interaction:!!t.has_interaction,accumulated_ms:t.accumulated_ms||0,resume_at_ms:null!=t.resume_at_ms?t.resume_at_ms:null,hidden_paused:!!t.hidden_paused,stay_duration_ms:t.stay_duration_ms}:null)}}function ue(e,t){return null==e||0>e||t>0&&e>t?-1:e}function le(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function de(e){return kn+":"+(e||"")}function _e(t){var n,r;if(!t)return null;if(n=null,se())try{n=window.sessionStorage.getItem(de(t))}catch(e){}if(!n&&ye())try{n=window.localStorage.getItem(de(t))}catch(e){}if(!n)return null;try{return(r=JSON.parse(n))&&r.sid===t?r:null}catch(e){return null}}function pe(t,n){if(t){var r=JSON.stringify(n||{});if(se())try{window.sessionStorage.setItem(de(t),r)}catch(e){}if(ye())try{window.localStorage.setItem(de(t),r)}catch(e){}}}function fe(e){if(!e)return null;var t=_e(e);return t&&t.metrics?t.metrics:null}function he(){var e,t=z();t&&(Cn?Sn||(Sn=t):(Cn=!0,(e=_e(t))&&e.metrics?(xn=ce(e.metrics),e.sessionStartedAt&&(Dn=e.sessionStartedAt),Sn=t):Sn=t))}function ge(e){var t=_e(e);return t&&t.behaviorHash?t.behaviorHash:""}function me(){var e=z();e&&(Sn&&Sn!==e||(Sn||(Sn=e),function(e){e&&pe(e,{sid:e,savedAt:Date.now(),sessionStartedAt:Dn,behaviorHash:ne(),metrics:ce(xn)})}(e)))}function ye(){try{return!("undefined"==typeof window||!window.localStorage||(window.localStorage.setItem("__sdk_metrics_ping__","1"),window.localStorage.removeItem("__sdk_metrics_ping__"),0))}catch(e){return!1}}function ve(){var t,n;if(!ye())return[];try{return(t=window.localStorage.getItem(wn))?(n=JSON.parse(t),Array.isArray(n)?n:[]):[]}catch(e){return[]}}function we(t){if(ye())try{window.localStorage.setItem(wn,JSON.stringify(t||[]))}catch(e){}}function ke(e){we(ve().filter(function(t){return t&&t.sid!==e}))}function be(e){for(var t,n=e.slice(0,yn);n.length>0;){if(t=JSON.stringify(n),vn>=t.length)return n;n.pop()}return n}function Ce(e){var t,n=xn.currentPage;n&&(n.stay_duration_ms=ie(e,n),t={page_key:n.page_key,page_name:n.page_name||"",stay_duration_ms:n.stay_duration_ms,enter_time:Math.floor(n.enter_time/1e3),has_interaction:n.has_interaction?1:0},n.page_seq_in_sid&&(t.page_seq_in_sid=n.page_seq_in_sid),xn.pageStayList.push(t),xn.pageStayList=be(xn.pageStayList),xn.currentPage=null)}function Se(){xn.firstInteractionRecorded||(xn.firstInteractionRecorded=!0,xn.firstInteractionDelayMs=Math.max(0,Date.now()-Dn))}function Pe(){xn.lastOrientation=re()}function Ee(e){e&&xn.currentPage&&xn.currentPage.page_key===e&&(xn.currentPage.has_interaction=!0)}function Te(e,t,n,r){var o=Date.now();Ce(o),xn.currentPage={page_key:(e||"")+"",page_name:(t||"")+"",enter_time:null!=n?1e3*n:o,enter_time_ms:o,page_seq_in_sid:r||xn.currentPageSeq||0,has_interaction:!1,accumulated_ms:0,resume_at_ms:o,hidden_paused:!1}}function xe(){Ce(Date.now())}function De(t){var n,r=t;if(null==r)return null;if("string"==typeof r)try{r=JSON.parse(r)}catch(e){return null}return r&&"object"==typeof r?(n={page_key:(r.page_key||"")+"",page_name:(r.page_name||"")+"",stay_duration_ms:null!=r.stay_duration_ms?r.stay_duration_ms:0,enter_time:null!=r.enter_time?r.enter_time:0,has_interaction:r.has_interaction?1:0},null!=r.page_seq_in_sid&&(n.page_seq_in_sid=r.page_seq_in_sid),n):null}function Me(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f=e||{},h=f.sid||z(),g=h&&h===z();for(n?r=ce(n):g?(xe(),me(),r=ce(xn)):r=function(e){var t,n;return he(),!(t=e||z())||t!==z()||Sn&&Sn!==t?(n=fe(t))?ce(n):Sn&&Sn!==t?{eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null}:ce(xn):ce(xn)}(h),o=function(e,t){var n=Date.now(),r=e||{},o=U(r.sessionStartTs)||n,i=null!=r.sessionEndTs?U(r.sessionEndTs):X(r,t||"idle",n);return{startTs:o,endTs:i=Math.max(o,i||n)}}(f,t),s=Math.max(0,(a=o.endTs)-(i=o.startTs)),c=le({},r.pageKeys||{}),u=function(e){var t,n,r=[];for(t=0;(e||[]).length>t;t++)(n=De(e[t]))&&r.push(n);return r}(function(e){var t,n,r,o={},i=[];for(t=0;e.length>t;t++)null!=(r=(n=e[t])&&n.page_seq_in_sid)&&o[r]||(null!=r&&(o[r]=!0),i.push(n));return i}((r.pageStayList||[]).slice()).sort(function(e,t){return(e.page_seq_in_sid||0)-(t.page_seq_in_sid||0)})),u=function(e,t,n){var r,o,i,a=Math.floor((t||0)/1e3),s=Math.floor((n||Date.now())/1e3),c=Math.max(0,a-5),u=[];for(r=0;(e||[]).length>r;r++)(o=e[r])&&((i=null!=o.enter_time?+o.enter_time:0)>0&&c>i||i>s+5||u.push(o));return u}(u,i,a),l=0;u.length>l;l++)(d=u[l]&&u[l].page_key?u[l].page_key+"":"")&&(c[d]=1);return _=Object.keys(c),p=n?ge(h)||ne():g?ne():ge(h)||ne(),{session_start_ts:Math.floor(i/1e3),session_end_ts:Math.floor(a/1e3),session_duration_ms:s,event_count_total:r.eventCountTotal||0,business_event_count:r.businessEventCount||0,page_count:_.length,page_view_count:r.pageViewCount||0,page_click_count:r.pageClickCount||0,session_end_reason:(t||"idle")+"",last_event_name:r.lastEventName||"",page_stay_list:u,behavior_sequence_hash:p,network_change_count:r.networkChangeCount||0,scroll_total_count:r.scrollTotalCount||0,scroll_total_distance_px:Math.round(r.scrollTotalDistance||0),scroll_up_count:r.scrollUpCount||0,scroll_down_count:r.scrollDownCount||0,scroll_direction_change_count:r.scrollDirectionChangeCount||0,first_scroll_delay_ms:ue(null!=r.firstScrollDelayMs?r.firstScrollDelayMs:-1,s),max_scroll_depth_percent:r.maxScrollDepthPercent||0,is_reach_bottom:r.isReachBottom||0,content_height:oe(),keydown_total_count:r.keydownTotalCount||0,input_count:r.inputCount||0,focus_total_count:r.focusTotalCount||0,first_interaction_delay_ms:ue(null!=r.firstInteractionDelayMs?r.firstInteractionDelayMs:-1,s),orientation:r.lastOrientation||re(),touch_total_count:r.touchTotalCount||0,swipe_total_count:r.swipeTotalCount||0,long_press_total_count:r.longPressTotalCount||0,pinch_total_count:r.pinchTotalCount||0}}function Ie(){On=!0,setTimeout(function(){On=!1},2e3)}function Ae(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function Oe(t){return e((null==t?"":t)+"").substr(0,32)}function Le(e){return null!=e&&""!==(e+"").trim()}function Re(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function qe(e){var t=(null==e?"":e)+"";return(t=(t=(t=t.replace(/\.html?$/i,"")).replace(/[^a-zA-Z0-9_]+/g,"_")).replace(/_+/g,"_").replace(/^_|_$/g,""))||"index"}function Be(){try{return qe(("undefined"!=typeof location&&location.pathname?location.pathname:"/").replace(/^\/+|\/+$/g,"").replace(/\//g,"_")||"index")}catch(e){return"index"}}function Ke(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}function Ve(){try{var t=Ke();return t?{scroll_top:Math.max(0,Math.round(t.scrollTop||0)),scroll_left:Math.max(0,Math.round(t.scrollLeft||0))}:{scroll_top:-1,scroll_left:-1}}catch(e){return{scroll_top:-1,scroll_left:-1}}}function je(e,t){if(null==e||!isFinite(e))return-1;var n=Math.round(+e);return 0>n&&(n=0),null!=t&&t>0&&n>t&&(n=t),n}function Ne(){var t,n,r;try{return(t=document.documentElement||document.body)?(r=t.scrollHeight||0,(n=t.scrollWidth||0)>0&&r>0?{page_width:Math.round(n),page_height:Math.round(r)}:{page_width:-1,page_height:-1}):{page_width:-1,page_height:-1}}catch(e){return{page_width:-1,page_height:-1}}}function Fe(){var t,n;try{return"undefined"==typeof window?{viewport_width:-1,viewport_height:-1}:(t=window.innerWidth||0,n=window.innerHeight||0,t>0&&n>0?{viewport_width:Math.round(t),viewport_height:Math.round(n)}:{viewport_width:-1,viewport_height:-1})}catch(e){return{viewport_width:-1,viewport_height:-1}}}function He(){var t,n;try{return"undefined"==typeof screen?{screen_width:-1,screen_height:-1}:(t=screen.width||0,n=screen.height||0,t>0&&n>0?{screen_width:Math.round(t),screen_height:Math.round(n)}:{screen_width:-1,screen_height:-1})}catch(e){return{screen_width:-1,screen_height:-1}}}function Ue(){var t,n,r;try{if("undefined"!=typeof performance&&performance.timing&&(t=performance.timing).loadEventEnd&&t.navigationStart&&t.loadEventEnd>0)return Math.max(0,t.loadEventEnd-t.navigationStart);if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(n=performance.getEntriesByType("navigation"))&&n.length>0&&(r=n[0]).loadEventEnd>0&&r.fetchStart>=0)return Math.max(0,Math.round(r.loadEventEnd-r.fetchStart))}catch(e){}return 0}function Xe(e,t){return t>0?Math.min(100,Math.max(0,Math.round(e/t*100))):0}function Ye(e){return e?{clientX:null!=e.clientX?e.clientX:null,clientY:null!=e.clientY?e.clientY:null,pageX:null!=e.pageX?e.pageX:null,pageY:null!=e.pageY?e.pageY:null}:{}}function Ge(e){var t,n,r,o,i,a,s,c,u=e||{},l=Fe(),d=Ne(),_=Ve(),p=function(e){return!!e&&(null!=e.clientX||null!=e.clientY||null!=e.pageX||null!=e.pageY)}(u),f=d.page_width>0&&d.page_height>0,h=He(),g=f&&_.scroll_top>=0?_.scroll_top:0,m=f&&_.scroll_left>=0?_.scroll_left:0,y=l.viewport_width>0?l.viewport_width:-1,v=l.viewport_height>0?l.viewport_height:-1;return p?(n=null!=u.clientY?+u.clientY:null,r=null!=u.pageX?+u.pageX:null,o=null!=u.pageY?+u.pageY:null,null!=(t=null!=u.clientX?+u.clientX:null)||null!=n?(i=null!=t&&isFinite(t)?t:0,a=null!=n&&isFinite(n)?n:0,s=f?null!=r&&isFinite(r)?r:i+m:-1,c=f?null!=o&&isFinite(o)?o:a+g:-1):(s=f&&null!=r&&isFinite(r)?r:-1,c=f&&null!=o&&isFinite(o)?o:-1,i=null!=r&&isFinite(r)?r-m:0,a=null!=o&&isFinite(o)?o-g:0),i=je(i,y),a=je(a,v),f&&(s=je(s,d.page_width),c=je(c,d.page_height)),Ae({click_viewport_x:i,click_viewport_y:a,click_document_x:f?s:-1,click_document_y:f?c:-1,click_page_x:i,click_page_y:a,click_x_percent:y>0?Xe(i,y):-1,click_y_percent:v>0?Xe(a,v):-1,screen_width:h.screen_width,screen_height:h.screen_height},l,f?d:{page_width:-1,page_height:-1},f?_:{scroll_top:-1,scroll_left:-1})):{click_viewport_x:-1,click_viewport_y:-1,click_document_x:-1,click_document_y:-1,click_page_x:-1,click_page_y:-1,click_x_percent:-1,click_y_percent:-1,screen_width:h.screen_width,screen_height:h.screen_height,viewport_width:l.viewport_width,viewport_height:l.viewport_height,page_width:f?d.page_width:-1,page_height:f?d.page_height:-1,scroll_top:f?_.scroll_top:-1,scroll_left:f?_.scroll_left:-1}}function ze(t){var n=t||{},r=qe(n.pageKey||Be()),o=n.pageName||"undefined"!=typeof document&&document.title||r,i=function(){try{return"undefined"==typeof location?{}:{page_url:Re(),page_domain:(location.hostname||"")+"",page_path:(location.pathname||"")+"",page_query_hash:location.search?Oe(location.search):""}}catch(e){return{}}}(),a=Fe();return{user_type:"",page_key:r,page_name:o,referrer_page_key:(n.previousPageKey||"")+"",referrer_page_name:(n.previousPageName||"")+"",current_page_key:r,current_page_name:o,recommend_trace_id:"",page_url:i.page_url,viewport_width:a.viewport_width,viewport_height:a.viewport_height,is_sdk_auto_collected:1}}function We(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function Je(e,t){var n,r;return"app_session_summary"===t?We({},e.summaryFields||{},e.payload||{}):(n=function(e){var t=We({},e||{});return delete t._metricsRecorded,delete t._pageSeqPrealloc,delete t._clickSnap,t}(e.payload||{}),r={event:1,payload:1,event_id:1,summaryFields:1,_pageSeqInSid:1,_metricsRecorded:1,_sdkAutoClick:1,_pageSeqPrealloc:1},Object.keys(e).forEach(function(t){r[t]||(n[t]=e[t])}),n)}function Qe(e,t,n,r,o){var i,a,s,c,u,d,h,g=e||{},y=((n.event||"")+"").trim(),v=n.lifecycle_event_key||n.payload&&n.payload.lifecycle_event_key||"";return function(e,t){var n,r=(e||"")+"";return"app_page_view"===r||"page_click"===r||("app_lifecycle"===r?"cold_start"==(n=(t||"")+"")||"web_load"===n||"warm_start"===n||"foreground"===n:"sdk_init"!==r&&"network_state_change"!==r&&"app_session_summary"!==r&&"background"!==r)}(y,v)&&(c=Date.now(),(u=F())?(u.lastValidBehaviorAt=c,N(u)):N(H(c))),i="",n.payload&&null!=n.payload.page_key?i=n.payload.page_key+"":null!=n.page_key&&(i=n.page_key+""),function(e,t,n){"app_lifecycle"===e&&t&&en[t]?mn.append(t,n):("app_page_view"===e||"page_click"===e||e&&!Tn[e])&&mn.append(e,n)}(y,v,i),a=function(e,t){return B({appId:e.appId,channel:e.channel,uid:e.uid,sid:t,deviceId:(n=e.deviceId,((null==n?"":n)+"").trim()||"NULL"),deviceType:e.deviceType||"PC",deviceBrand:e.deviceBrand||"",deviceModel:e.deviceModel||"",deviceFingerprint:ee(),fpVersion:te(),sdkVersion:e.sdkVersion,appVersion:e.appVersion});var n}(g,t),function(e,t){var n,r;if(he(),(n=(e||"")+"")&&"app_session_summary"!==n){if("app_page_view"===n&&t&&t._metricsRecorded)return xn.lastEventName=n,void me();xn.eventCountTotal++,xn.lastEventName=n,Tn[n]||xn.businessEventCount++,r=t&&null!=t.page_key?t.page_key+"":"","app_page_view"===n?(xn.pageViewCount++,r&&(xn.pageKeys[r]=1)):"page_click"===n?(xn.pageClickCount++,Ee(r)):"network_state_change"===n?xn.networkChangeCount++:r&&Ee(r),me()}}(y,n.payload||{}),s=function(e,t){return function(e,t){for(var n=t||{},r={},o=Object.keys(e||{}),i=0;o.length>i;i++)r[o[i]]=e[o[i]];return r.event_sig_version=(n.eventSigVersion||Jt)+"",r.event_sig=n.hmacKey?function(e,t){return t?function(e,t){var n,r,o,i,a=_((null==t?"":t)+"");for(a.length>64&&(a=p(l(a)));64>a.length;)a.push(0);for(n=[],r=[],o=0;64>o;o++)n.push(54^a[o]),r.push(92^a[o]);return i=p(l(f(n,_((null==e?"":e)+"")))),l(f(r,i))}(function(e){var t=e||{};return[q(t.event),q(t.event_id),q(t.app_id),q(t.client_ts),q(t.device_fingerprint)].join("|")}(e),t+""):""}(e,n.hmacKey):"",r}(e,{hmacKey:t&&t.hmacKey?t.hmacKey:"",eventSigVersion:Jt})}(function(e,t,n){return function(e,t){var n=function(){var e,t,n,r,o;for(e={},t=0;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)e[r[o]]=n[r[o]];return e}(e,t);return n.event_id||(n.event_id=m(n)),n}(e,{event:n,payload:Je(t,n)})}(a,n,y),r),"app_page_view"===y&&n.payload&&n.payload.page_key&&(n.payload._metricsRecorded||n._metricsRecorded||Te(n.payload.page_key,n.payload.page_name,n.payload.page_enter_ts,null!=n._pageSeqInSid?n._pageSeqInSid:n.payload.page_seq_in_sid)),"page_click"===y&&n.payload&&!n._sdkAutoClick&&An.push({x:null!=(d=null!=n.payload.click_viewport_x?n.payload.click_viewport_x:n.payload.click_page_x)?d:0,y:null!=(h=null!=n.payload.click_viewport_y?n.payload.click_viewport_y:n.payload.click_page_y)?h:0,t:Date.now()}),s._sessionInfo=o||null,s}function $e(t,n,r){var o=We({},n||{}),i=((o.event||"")+"").trim();return Bn[i]&&o.payload&&1!==o.payload.is_sdk_auto_collected&&(o.payload=function(e,t,n){var r,o=e||{},i=1===o.is_sdk_auto_collected,a=((n||"")+"").trim(),s="app_page_view"===a||null!=o.page_key&&(null!=o.current_page_key||null!=o.user_type),c="page_click"===a,u={}||{},l=qe(o.page_key||Be());return r=s?ze(Ae(u,{pageKey:l,pageName:o.page_name})):c?Ae({page_key:l,page_name:o.page_name||"undefined"!=typeof document&&document.title||l,recommend_trace_id:"",referrer_page_key:u.previousPageKey||""},function(e){var t=e||{},n=Fe(),r=He(),o=Ne(),i=Ve(),a=o.page_width>0&&o.page_height>0,s=a&&i.scroll_top>=0?i.scroll_top:0,c=a&&i.scroll_left>=0?i.scroll_left:0,u=n.viewport_width>0?n.viewport_width:-1,l=n.viewport_height>0?n.viewport_height:-1,d=null!=t.click_page_x?+t.click_page_x:null,_=null!=t.click_page_y?+t.click_page_y:null,p=-1,f=-1,h=-1,g=-1;return null!=d&&isFinite(d)&&null!=_&&isFinite(_)?(p=d,f=_,a&&(h=d+c,g=_+s)):null!=t.click_x_percent&&null!=t.click_y_percent&&u>0&&l>0&&(p=+t.click_x_percent/100*u,f=+t.click_y_percent/100*l,a&&(h=p+c,g=f+s)),-1!==p&&(p=je(p,u)),-1!==f&&(f=je(f,l)),a&&-1!==h&&(h=je(h,o.page_width)),a&&-1!==g&&(g=je(g,o.page_height)),{click_viewport_x:p,click_viewport_y:f,click_document_x:a?h:-1,click_document_y:a?g:-1,screen_width:r.screen_width,screen_height:r.screen_height,viewport_width:n.viewport_width,viewport_height:n.viewport_height,page_width:a?o.page_width:-1,page_height:a?o.page_height:-1,scroll_top:a?i.scroll_top:-1,scroll_left:a?i.scroll_left:-1}}(o)):{page_key:l,page_name:o.page_name||"undefined"!=typeof document&&document.title||l,recommend_trace_id:"",referrer_page_key:u.previousPageKey||""},i||s?i&&(r.is_sdk_auto_collected=1):delete r.is_sdk_auto_collected,function(e,t){var n,r,o=Ae({},e||{}),i=Object.keys(t||{});for(n=0;i.length>n;n++)Le(o[r=i[n]])||(o[r]=t[r]);return o}(o,r)}(o.payload,0,i)),Z().then(function(){var n,a=G();if(a.endedSession&&qn)try{qn(a.endedSession)}catch(e){}return n=a.sid||z(),Bn[i]&&o.payload&&function(t,n){var r,o,i,a=((t.event||"")+"").trim();Bn[a]&&t.payload&&("app_page_view"===a?1===t.payload.is_sdk_auto_collected?(r=t.payload._pageSeqPrealloc||ae(),o=t.payload._metricsRecorded,null!=t.payload._pageSeqPrealloc&&delete t.payload._pageSeqPrealloc,t.payload=function(e,t,n){var r=e||{},o=Fe(),i=Math.floor(Date.now()/1e3),a=Oe((t||"")+"|"+(n||0)+"|"+i),s=Ue(),c=qe(r.page_key||""),u=(r.page_name||"")+"";return{user_type:(null!=r.user_type?r.user_type:"")+"",page_key:c,page_name:u,referrer_page_key:(null!=r.referrer_page_key?r.referrer_page_key:"")+"",referrer_page_name:(null!=r.referrer_page_name?r.referrer_page_name:"")+"",current_page_key:(null!=r.current_page_key?r.current_page_key:c)+"",current_page_name:(null!=r.current_page_name?r.current_page_name:u)+"",recommend_trace_id:(null!=r.recommend_trace_id?r.recommend_trace_id:"")+"",page_id:a,page_enter_ts:i,page_url:r.page_url||Re(),page_load_time:null!=s?s:0,viewport_width:o.viewport_width,viewport_height:o.viewport_height}}(t.payload,n,r),t._pageSeqInSid=r,o&&(t._metricsRecorded=1)):(i=function(t,n){var r,o=t||{},i=null!=o.page_enter_ts?o.page_enter_ts:Math.floor(Date.now()/1e3),a=o.page_seq_in_sid||ae();return{page_seq_in_sid:a,page_id:o.page_id||e((null==(r=(n||"")+"|"+a+"|"+i)?"":r)+"").substr(0,32),page_enter_ts:i}}(t.payload,n),t.payload=We(t.payload,i)):"page_click"===a&&1===t.payload.is_sdk_auto_collected&&(t.payload=function(e){var t=e||{},n=Ge(t._clickSnap||t);return{page_key:qe(t.page_key||""),page_name:(t.page_name||"")+"",recommend_trace_id:(null!=t.recommend_trace_id?t.recommend_trace_id:"")+"",referrer_page_key:(null!=t.referrer_page_key?t.referrer_page_key:"")+"",click_viewport_x:n.click_viewport_x,click_viewport_y:n.click_viewport_y,click_document_x:n.click_document_x,click_document_y:n.click_document_y,click_page_x:n.click_page_x,click_page_y:n.click_page_y,click_x_percent:n.click_x_percent,click_y_percent:n.click_y_percent,screen_width:n.screen_width,screen_height:n.screen_height,viewport_width:n.viewport_width,viewport_height:n.viewport_height,page_width:n.page_width,page_height:n.page_height,scroll_top:n.scroll_top,scroll_left:n.scroll_left}}(t.payload),t._sdkAutoClick=1,delete t.payload.is_sdk_auto_collected,delete t.payload._clickSnap))}(o,n),Qe(t,n,o,r,a)})}function Ze(){try{return"undefined"==typeof location?"":(location.protocol||"")+"//"+(location.host||"")+(location.pathname||"")}catch(e){return""}}function et(){var t,n;try{return(t=function(){try{return document.scrollingElement||document.documentElement||document.body||null}catch(e){return null}}())&&(n=Math.max(0,(t.scrollHeight||0)-(t.clientHeight||0)))>0?Math.min(100,Math.max(0,Math.round((t.scrollTop||0)/n*100))):0}catch(e){return 0}}function tt(){if(On)return"navigation";try{if("hidden"===document.visibilityState)return"visibility_hidden"}catch(e){}return"unknown"}function nt(){return function(){if(xn.currentPage&&xn.currentPage.page_key)return xn.currentPage.page_key;var e=xn.pageStayList||[];return e.length>0&&e[e.length-1].page_key||""}()||Be()}function rt(){var t,n="unknown";try{n="visible"===document.visibilityState||"hidden"===document.visibilityState?document.visibilityState:"unknown"}catch(e){}return{page_url:Ze(),unload_reason:tt(),scroll_depth_percent_at_unload:et(),web_visibility_state:n,page_stay_duration_ms:(t=xn.currentPage,t?ie(Date.now(),t):0),last_active_page_key:nt()}}function ot(e,t,n,r){var o,i,a=n||{};return a.lifecycle_event_key=t,t===Kn&&(a=at((o=Fe(),{page_url:Ze(),page_load_time_ms:Ue(),viewport_width:o.viewport_width,viewport_height:o.viewport_height}),a)),t===Nn&&(a=at(a,rt())),t===jn&&((i=xn.currentPage)&&!i.hidden_paused&&(i.accumulated_ms=ie(Date.now(),i),i.resume_at_ms=null,i.hidden_paused=!0),a.last_active_page_key||(a.last_active_page_key=nt())),t===Vn&&function(){var e=xn.currentPage;e&&e.hidden_paused&&(e.resume_at_ms=Date.now(),e.hidden_paused=!1)}(),$e(e,{event:"app_lifecycle",payload:a},r)}function it(e,t,n,r){return function(e,t,n,r){var o=We({},n||{});return Z().then(function(){return Qe(e,t,o,r,null)})}(e,r||z(),{event:"app_session_summary",summaryFields:t||{}},n)}function at(e){var t,n,r,o,i=e||{};for(t=1;arguments.length>t;t++)if(n=arguments[t])for(r=Object.keys(n),o=0;r.length>o;o++)i[r[o]]=n[r[o]];return i}function st(e){var t,n;return!e||2>e.length?0:Math.sqrt((t=e[0].clientX-e[1].clientX)*t+(n=e[0].clientY-e[1].clientY)*n)}function ct(){Gn&&(clearTimeout(Gn),Gn=null)}function ut(t,n,r){var o,i,a,s,c,u;if(r=r||{},!t)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"no_endpoint"});if(!Array.isArray(n)||0===n.length)return Promise.resolve({sent:!0,ok:!0,status:200,reason:"empty_batch"});if(o=JSON.stringify(n),i=!0===r.useBeacon,a=!1!==r.requireHttps,c=isFinite(s=+r.timeoutMs)&&s>0?Math.floor(s):Qn,!function(e,t){if(!t)return!0;var n=((e||"")+"").match(/^(https?):\/\/([^/:?#]+)/i);return!(!n||"https"!==n[1].toLowerCase()&&("http"!==n[1].toLowerCase()||!function(e){var t=((e||"")+"").toLowerCase();return"localhost"===t||"127.0.0.1"===t||"::1"===t}(n[2])))}(t,a))return Promise.resolve({sent:!1,ok:!1,status:0,reason:"insecure_endpoint"});if(i&&"undefined"!=typeof navigator&&"function"==typeof navigator.sendBeacon)try{if(u=new Blob([o],{type:"application/json"}),navigator.sendBeacon(t,u))return Promise.resolve({sent:!0,ok:!0,status:200,transport:"beacon",uncertain:!0})}catch(e){}return function(e,t,n){var r,o;return"function"!=typeof fetch?Promise.resolve(null):(r="undefined"!=typeof AbortController?new AbortController:null,o=r&&n>0?setTimeout(function(){r.abort()},n):null,fetch(e,{method:"POST",headers:{"Content-Type":"application/json"},body:t,keepalive:!0,signal:r?r.signal:void 0}).then(function(e){return o&&clearTimeout(o),{sent:!0,ok:e.ok,status:+(e.status||0),transport:"fetch"}}).catch(function(e){return o&&clearTimeout(o),e&&"object"==typeof e&&"AbortError"===e.name?{sent:!1,ok:!1,status:0,reason:"timeout",transport:"fetch"}:null}))}(t,o,c).then(function(n){return null!==n?n:function(t,n,r){return new Promise(function(o){try{if("undefined"==typeof XMLHttpRequest)return o({sent:!1,ok:!1,status:0,reason:"no_xhr"});var i=new XMLHttpRequest;i.open("POST",t,!0),i.timeout=r,i.setRequestHeader("Content-Type","application/json"),i.onreadystatechange=function(){4===i.readyState&&o({sent:!0,ok:i.status>=200&&300>i.status,status:i.status,transport:"xhr"})},i.onerror=function(){o({sent:!1,ok:!1,status:0,reason:"xhr_error"})},i.ontimeout=function(){o({sent:!1,ok:!1,status:0,reason:"timeout",transport:"xhr"})},i.send(n)}catch(e){o({sent:!1,ok:!1,status:0,reason:"xhr_exception"})}})}(t,o,c)})}function lt(e){e=e||{},this.selectedBatchEndpoint="",this.remoteEndpoints=[],this.remoteProbePromise=null,this.remoteProbeKey="",this.shouldPersistOnFail=!1,this.hasBatchProbeStarted=!1,this.initialized=!1,this.initPromise=null,this.endpointReadyListeners=[],this.timeoutMs=+e.pingTimeoutMs||1500}function dt(e){return 255&(e<<1^(128&e?27:0))}function _t(e){return(Mt[e>>>24&255]<<24|Mt[e>>>16&255]<<16|Mt[e>>>8&255]<<8|Mt[255&e])>>>0}function pt(e,t){var n,r,o,i,a,s,c,u,l,d,_,p,f=e.slice(0);for(o=[],i=0;16>i;i++)f[i]=255&(f[i]^t[i>>2]>>>24-8*(3&i));for(a=1;14>=a;a++){for(n=[],i=0;16>i;i++)n[i]=Mt[f[i]];for(r=[],c=0;4>c;c++)for(u=0;4>u;u++)r[c+4*u]=n[c+4*(u+c&3)];if(14>a){for(o=[],s=0;4>s;s++)d=r[4*s+1],_=r[4*s+2],p=r[4*s+3],o[4*s]=255&(dt(l=r[4*s])^dt(d)^d^_^p),o[4*s+1]=255&(l^dt(d)^dt(_)^_^p),o[4*s+2]=255&(l^d^dt(_)^dt(p)^p),o[4*s+3]=255&(dt(l)^l^d^_^dt(p));for(f=o,o=[],i=0;16>i;i++)f[i]=255&(f[i]^t[4*a+(i>>2)]>>>24-8*(3&i))}else for(f=r,i=0;16>i;i++)f[i]=255&(f[i]^t[4*a+(i>>2)]>>>24-8*(3&i))}return f}function ft(e,t){var n,r,o,i=[],a=t.slice(0);for(n=0;16>n;n++)i[n]=0;for(n=0;128>n;n++){if(e[n>>3]&128>>(7&n))for(r=0;16>r;r++)i[r]^=a[r];for(o=1&a[15],r=15;r>0;r--)a[r]=255&(a[r]>>>1|a[r-1]<<7);a[0]=a[0]>>>1&255,o&&(a[0]^=225)}return i}function ht(e){var t,n=e.slice(0);for(t=15;t>=12&&(n[t]=n[t]+1&255,!n[t]);t--);return n}function gt(e){this.encryptedConfigText=(e=e||{}).encryptedConfig||e.configCipher||"",this.cachedConfigPromise=null,this._secondGcmKeyBase64="en1BNo0VBrN/zi+mI2LO7E9W40ehCBYwC+frBn8s3rQ="}function mt(e,t){var n=((e&&e.event_id||"")+"").trim();return n?n+"|||"+(t||""):null}function yt(e){var t,n={},r=Object.keys(e);for(t=0;r.length>t;t++)n[r[t]]=e[r[t]];return n}function vt(){this.maxSize=tr}function wt(e){var t,n,r=e||{},o=r.im_behavior_key,i=qt[o];if(!i)throw(t=Error("[sdk][web] 未知 im_behavior_key:"+o)).name="EventValidationError",t.eventMethod="imEvent",t.typeErrorFields=["im_behavior_key(unknown)"],t;return n={},Object.keys(r).forEach(function(e){"im_behavior_name"!==e&&(n[e]=r[e])}),n.im_behavior_name=i,n}function kt(e,t){var n,r,o,i,a,s=Lt[e];if(!s)throw Error("[sdk][web] 未知事件方法:"+e);if(n=t||{},r={},o=[],i=[],Object.keys(s).forEach(function(e){var t,a=s[e],c=1&a,u=a>>1&3,l=!!(8&a),d=n[e];l&&null===d&&(d=""),!c||l||null!=d&&""!==d?c&&l&&void 0===d?o.push(e):(c||null!=d)&&(t=1===u?function(e){return"number"==typeof e&&isFinite(e)&&e===Math.floor(e)}(d):2===u?function(e){return"number"==typeof e&&isFinite(e)}(d):function(e){return"string"==typeof e}(d),t?r[e]=d:i.push(e+"(expected "+(1===u?"int":2===u?"number":"string")+")")):o.push(e)}),o.length||i.length)throw(a=Error("[sdk][web] 事件字段校验失败:"+e)).name="EventValidationError",a.eventMethod=e,a.missingFields=o.slice(),a.typeErrorFields=i.slice(),a;return r}function bt(){var e,t,n,r,o={};for(e=0;arguments.length>e;e++)if(t=arguments[e])for(n=Object.keys(t),r=0;n.length>r;r++)o[n[r]]=t[n[r]];return o}function Ct(e){var t,n,r={};if(!e||!Array.isArray(e))return r;for(t=0;e.length>t;t++)(n=((null==e[t]?"":e[t])+"").trim())&&(Vt[n]?r[n]=1:Kt('[sdk][web] disableAutoEvents 含未知事件 "'+n+'",已忽略'));return r}function St(e){var t,n=[],r=Object.keys(e||{});for(t=0;r.length>t;t++)n.push(r[t]);return n}function Pt(e,t){this.opts=e,this.resolver=t,this.queue=[],this.timer=null,this.flushTail=Promise.resolve(),this.replaying=!1,this.replayTimer=null,this.hasPersistentItems=!0,this.persistentQueue=new vt,this._resolveEndpoint=null,this._getEventCodes=null,this.startReplayTimer()}function Et(e){var t,n;if("undefined"==typeof window)return Kt("[web-sdk] 非浏览器环境(SSR/Node.js),SDK 仅支持浏览器,初始化已跳过。"),this._ssrSkipped=!0,this.options=e,this.configMgr=null,this.resolver=null,void(this.batcher=null);if(!Object.prototype.hasOwnProperty.call(e,"channel"))throw Error('channel 为必传字段(可传空字符串 "")');this._ssrSkipped=!1,(t=bt(e)).channel=null==e.channel?"":e.channel+"",this._disableAutoEvents=Ct(t.disableAutoEvents),t.disableAutoEvents=St(this._disableAutoEvents),this.options=t,this.configMgr=new gt(e),this.resolver=new lt({pingTimeoutMs:nr}),this.batcher=new Pt(this.options,this.resolver),n=this,this.batcher._resolveEndpoint=function(e){return n.resolver.resolveEndpoint(function(){return n.configMgr.getBatchEndpoints()},e)},this.batcher._getEventCodes=function(){return n.configMgr.getEventCodes()},this.batcher._getSigningConfig=function(){return n.configMgr.getSigningConfig()},this._unsubReady=null,this._refreshTimer=null,this._autoLifecycleTeardown=null,this._webLoadTeardown=null,this._autoNetworkTeardown=null,this._pageTrackerTeardown=null,this._webInteractionTeardown=null,this._sessionPollTimer=null,this._autoEventsStarted=!1,this._init({}),this._startAutoEvents()}function Tt(e){if(!Ht)throw Error("请先调用 init() 初始化(参数须包含 channel)");return Ht}function xt(e,t,n){var r,o,i;if(n>=e.length)return Promise.resolve();if(!Ht||Ht!==t){for(o=(r=e.slice(n)).length-1;o>=0;o--)Ut.unshift(r[o]);return Promise.resolve()}return t.track((i=e[n]).event,i.endpoint).catch(function(){}).then(function(){return xt(e,t,n+1)})}function Dt(e,t){var n;return Ht?(n=Ht,(Xt||Promise.resolve()).then(function(){return n.track(e,t)})):(Ut.push({event:e,endpoint:t}),Ut.length>500&&Ut.shift(),Promise.resolve({sent:!1,ok:!0,status:0,reason:"pre_init_cached"}))}var Mt,It,At,Ot,Lt,Rt,qt,Bt,Kt,Vt,jt,Nt,Ft,Ht,Ut,Xt,Yt,Gt,zt=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],Wt="3.0.1",Jt="sig_v2",Qt=["os_family","browser_kernel","browser_platform","screen_width","screen_height","screen_dpi","device_pixel_ratio","color_depth","max_touch_points","cpu_cores","cookie_enabled","local_storage_enabled","session_storage_enabled","indexeddb_enabled","service_worker_enabled","canvas_fp","webgl_fp","audio_fp","fonts_hash","plugins_hash","mimes_hash"],$t=["Arial","Arial Black","Comic Sans MS","Courier New","Georgia","Impact","Times New Roman","Trebuchet MS","Verdana","Microsoft YaHei","SimHei","SimSun","PingFang SC","Helvetica","Roboto","Monaco","Menlo","Consolas","Palatino Linotype","Tahoma","Lucida Console","Candara","Futura","Geneva"],Zt=[{kind:"video",type:'video/mp4; codecs="avc1.42E01E"'},{kind:"video",type:'video/webm; codecs="vp8, vorbis"'},{kind:"video",type:'video/ogg; codecs="theora"'},{kind:"video",type:"video/mp4"},{kind:"video",type:"video/webm"},{kind:"audio",type:"audio/mpeg"},{kind:"audio",type:'audio/ogg; codecs="vorbis"'},{kind:"audio",type:'audio/wav; codecs="1"'},{kind:"audio",type:"audio/mp4"},{kind:"audio",type:"audio/aac"}],en={cold_start:1,web_load:1,warm_start:1,foreground:1,background:1,web_unload:1,terminate:1,unknown:1},tn="__web_sdk_sid__",nn="__web_sdk_sid_meta__",rn="__web_sdk_sid__",on="__web_sdk_sid_v1",an=18e5,sn=216e5,cn=null,un=!1,ln="__web_sdk_device_id__",dn="__web_sdk_ping__",_n=null,pn="__web_sdk_device_fp__",fn=null,hn="",gn=null,mn=function(){var e="";return{append:function(t,n){var r=((t||"")+"").trim();r&&(e+=r+"|"+(null!=n?n+"":"")+";")},digest:function(){return d(e)},reset:function(){e=""}}}(),yn=50,vn=16384,wn="__web_sdk_pending_session_summaries__",kn="__web_sdk_session_metrics_v1__",bn="__web_sdk_summary_claimed__:",Cn=!1,Sn="",Pn={},En={},Tn={sdk_init:1,app_lifecycle:1,app_session_summary:1,network_state_change:1},xn={eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},Dn=Date.now(),Mn=0,In=0,An=[],On=!1,Ln="",Rn="",qn=null,Bn={app_page_view:1,page_click:1},Kn="web_load",Vn="foreground",jn="background",Nn="web_unload",Fn=null,Hn=0,Un=0,Xn=0,Yn=0,Gn=null,zn=!1,Wn=null,Jn=!1,Qn=1e4,$n="1.2.0",Zn=5e3,er=3e4,tr=2e3,nr=1500,rr=!1;if(lt.prototype.normalizeBatchEndpoints=function(e){return(Array.isArray(e)?e:[]).map(function(e){return((e||"")+"").trim()}).filter(Boolean).filter(function(e){return/^https:\/\//i.test(e)}).map(function(e){var t=e.match(/^(https?:\/\/[^/?#]+)/i);return(t?t[1]:e.replace(/\/+$/,""))+"/api/eventTracking/batchReport.json"})},lt.prototype._withTimeout=function(e,t){return new Promise(function(n,r){var o=setTimeout(function(){r(Error("timeout"))},t);e.then(function(e){clearTimeout(o),n(e)}).catch(function(e){clearTimeout(o),r(e)})})},lt.prototype._xhrRace=function(t,n){return t.length?new Promise(function(r){function o(){for(var t=0;c.length>t;t++)try{c[t].abort()}catch(e){}}var i,a=t.length,s=!1,c=[];for(i=0;t.length>i;i++)(function(t){var i,u,l;try{function d(e){l||(l=!0,u&&(clearTimeout(u),u=null),e&&!s?(s=!0,o(),r(t)):e||(a--,s||0!==a||r("")))}i=new XMLHttpRequest,u=null,l=!1,c.push(i),u=setTimeout(function(){d(!1)},n),i.open("POST",t,!0),i.setRequestHeader("Content-Type","application/json"),i.onreadystatechange=function(){4===i.readyState&&d(i.status>0)},i.onerror=function(){d(!1)},i.send("[]")}catch(e){a--,s||0!==a||r("")}})(t[i])}):Promise.resolve("")},lt.prototype._pickFastest=function(t,n){function r(){if(o)for(var t=0;i.length>t;t++)try{i[t].abort()}catch(e){}}var o,i,a=this;return"undefined"==typeof fetch?a._xhrRace(t,n):t.length?(o="undefined"!=typeof AbortController,i=[],new Promise(function(e){var s,c=t.length,u=!1;for(s=0;t.length>s;s++)(function(t){var s,l=o?new AbortController:null;l&&i.push(l),s={method:"POST",mode:"cors",headers:{"Content-Type":"application/json"},body:"[]",cache:"no-store"},l&&(s.signal=l.signal),a._withTimeout(fetch(t,s),n).then(function(n){n&&n.status>0?u||(u=!0,r(),e(t)):(c--,u||0!==c||e(""))}).catch(function(){c--,u||0!==c||e("")})})(t[s])})):Promise.resolve("")},lt.prototype._notifyReady=function(){var t=this.selectedBatchEndpoint;this.endpointReadyListeners.forEach(function(n){try{n(t)}catch(e){}})},lt.prototype._setSelectedEndpoint=function(e){var t=(e||"")+"";t&&t!==this.selectedBatchEndpoint&&(this.selectedBatchEndpoint=t,this._notifyReady())},lt.prototype.startRemoteProbe=function(e){var t,n,r=e.join("|");return e.length?this.remoteProbePromise&&this.remoteProbeKey===r?this.remoteProbePromise:(this.remoteProbeKey=r,this.shouldPersistOnFail=!1,t=this,n=this._pickFastest(e,this.timeoutMs).then(function(r){if(t.remoteProbePromise!==n)return t.selectedBatchEndpoint;if(t.remoteProbePromise=null,r)t._setSelectedEndpoint(r);else if(!t.selectedBatchEndpoint&&e.length){t.shouldPersistOnFail=!0;var o=e[Math.floor(Math.random()*e.length)];t._setSelectedEndpoint(o)}return t.selectedBatchEndpoint}).catch(function(){return t.remoteProbePromise===n&&(t.remoteProbePromise=null),t.selectedBatchEndpoint}),this.remoteProbePromise=n,n):Promise.resolve("")},lt.prototype.refreshDomainConfig=function(e){var t=this;return e().then(function(e){var n=t.normalizeBatchEndpoints(e);return t.remoteEndpoints=n,n.length?(t.hasBatchProbeStarted=!0,t.startRemoteProbe(n)):t.selectedBatchEndpoint})},lt.prototype.init=function(e){return this.initialized?Promise.resolve():(this.initPromise||(this.initialized=!0,this.initPromise=this.refreshDomainConfig(e).catch(function(){})),this.initPromise)},lt.prototype.resolveEndpoint=function(e,t){if(t)return Promise.resolve(t);var n=this;return this.init(e).then(function(){return n.selectedBatchEndpoint||""})},lt.prototype.onEndpointReady=function(e){if("function"!=typeof e)return function(){};this.endpointReadyListeners.push(e);var t=this;return function(){var n=t.endpointReadyListeners.indexOf(e);0>n||t.endpointReadyListeners.splice(n,1)}},lt.prototype.consumeProbeFailurePersistSignal=function(){return!!this.shouldPersistOnFail&&(this.shouldPersistOnFail=!1,!0)},lt.prototype.getEndpointState=function(){return this.selectedBatchEndpoint?"ready":this.remoteEndpoints.length>0?"probing":"unavailable"},lt.prototype.getSelectedBatchEndpoint=function(){return this.selectedBatchEndpoint},Mt=function(){var e,t=[];for(e=0;1272>e;e+=2)t.push(parseInt("637c777bf26b6fc53001672bed2a28a248888da76f3248a3043813899432c6b326fb8d0d37e84760dad3574759e0b764532a380602b1fc71a9e7f97d3b6415c21d02b5612440a4500bf9c043d33e085f45c20e0b65124603dfa8cd7a76b65c11b829e69a4e7f7872c6c7150b6a0f0801d163ae27d4d6cd5d19e77a9b1b48aaf2428594d3752f1d67250bab67676203b9b40df3d7cdf7eebf4b1bd5b6e16416be39a4c4a56e644726087d013b8ea78a6750d8b0339b871ee1b6a9ab66c8032cc1ac2462c695459dc034deae0678ae1779d9e7abce45d167e719b7776c73222f3c7e3cc11ae6da5cd659b79ce60ca82ec97f6059f674c029a68db0853dc22bb6518048ed4f66256d430b74de2f1a95f0589c341befce783e7c6d9dc7dd8e21cb643ee710b3c11329260195e0c0a956f14f970a9e61e594c663f68266a11045baf84a11e051ea5b6a42067253b2d37392c3516e1fc226a5cd4ab8990c475651a4698910219b94e2eb7d2b7129f38c94e3b1246a3298fef32dcae306137c39e1f0e32fe97d11b986c41a55bd5351319f7f078010fa81eb86b8a0556f880000e3630c6a0b136307027bf70e1b5722632937a93b53bf2d240e3949fa5a6e4e6a2a6f4d5b43a39d051df60bc98f25ff3130c31fd85b6e4167b276aed3276b70c3e527f2f2963b60554543e32e1f0c1754766b610c20b0ffe75d3fb88f6fa49605d6a3d6ca3022ffc62f5c8d297b65c778da96c8941ac9c55845e094f1c4a020bff5bb01a0cb04f5a8ac42b1cb41c38d27c1e1358256f3e99e3295a6e3b8c9c0481ed49af7d43f580a8571a0b13162171ebe8666ee5365e9ce6bef0a682080268b3bb67a1076a5776a996c26c2c8c12c7490c356348d10351b5279fa09851ebf".substr(e,2),16));return t}(),It=[1,2,4,8,16,32,64,128,27,54],At="__web_sdk_remote_config_cache__",gt.prototype.setEncryptedConfig=function(e){this.encryptedConfigText!==e&&(this.encryptedConfigText=e,this.cachedConfigPromise=null)},gt.prototype._tryParse=function(t){try{return JSON.parse(t)}catch(e){return t}},gt.prototype._unwrap=function(e,t){var n,r,o;for(t=t||3,n=e,r=0;t>r&&"string"==typeof n&&(o=this._tryParse(n))!==n;r++)n=o;return n},gt.prototype._toStringArray=function(e){var t,n=this._unwrap(e);return Array.isArray(n)?n.map(function(e){return((e||"")+"").trim()}).filter(Boolean):"string"==typeof n&&n.trim()?(t=this._tryParse(n),Array.isArray(t)?t.map(function(e){return((e||"")+"").trim()}).filter(Boolean):[n.trim()]):[]},gt.prototype._getStorage=function(){try{return"undefined"!=typeof window&&window.localStorage?window.localStorage:null}catch(e){return null}},gt.prototype._readPersisted=function(){var t,n,r;try{return(t=this._getStorage())&&(n=t.getItem(At))?(r=JSON.parse(n),Date.now()-(r.updatedAt||0)>24e5?{}:this._mapPayload(r)):{}}catch(e){return{}}},gt.prototype._mapPayload=function(e){if(!e||"object"!=typeof e)return{};var t=e.data&&"object"==typeof e.data?e.data:e;return{batchUrls:this._toStringArray(null!=t.batchUrls?t.batchUrls:t.domainList),eventCodes:this._toStringArray(null!=t.eventCodes?t.eventCodes:t.eventList),signKey:(t.signKey||t.hmacKey||t.eventSignKey||"")+"",securityKeyVersion:(t.securityKeyVersion||t.signKeyVersion||"")+""}},gt.prototype._persist=function(t){try{var n=this._getStorage();if(!n)return;n.setItem(At,JSON.stringify({batchUrls:this._toStringArray(t.batchUrls),eventCodes:this._toStringArray(t.eventCodes),signKey:(t.signKey||"")+"",securityKeyVersion:(t.securityKeyVersion||"")+"",updatedAt:Date.now()}))}catch(e){}},gt.prototype._base64ToBytes=function(e){var t,n,r,o,i=((e||"")+"").trim().replace(/-/g,"+").replace(/_/g,"/"),a=i.length%4?4-i.length%4:0;for(t=0;a>t;t++)i+="=";if("undefined"!=typeof atob&&"undefined"!=typeof Uint8Array){for(n=atob(i),r=new Uint8Array(n.length),o=0;n.length>o;o++)r[o]=n.charCodeAt(o);return r}return new Uint8Array(0)},gt.prototype._decryptAes256Gcm=function(t,n){var r,o,i,a,s,c,u;try{if(28>(r=this._base64ToBytes(t)).length)return Promise.reject(Error("密文过短"));if(o=this._base64ToBytes(n),i=r.slice(0,12),a=r.slice(12),"undefined"!=typeof crypto&&crypto.subtle)return crypto.subtle.importKey("raw",o,{name:"AES-GCM"},!1,["decrypt"]).then(function(e){return crypto.subtle.decrypt({name:"AES-GCM",iv:i},e,a)}).then(function(e){return new Uint8Array(e)});if(!(s=function(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f,h,g,m=[],y=[],v=[],w=[];for(r=0;32>r;r++)m[r]=e[r]||0;for(r=0;12>r;r++)y[r]=t[r]||0;if(0>(i=n.length-16))return null;for(r=0;i>r;r++)v[r]=n[r];for(r=0;16>r;r++)w[r]=n[i+r];for(a=function(e){var t,n,r=[];for(t=0;8>t;t++)r[t]=(e[4*t]<<24|e[4*t+1]<<16|e[4*t+2]<<8|e[4*t+3])>>>0;for(t=8;60>t;t++)n=r[t-1],t%8?t%8==4&&(n=_t(n)):n=(_t((n<<8|n>>>24)>>>0)^It[t/8-1]<<24)>>>0,r[t]=(r[t-8]^n)>>>0;return r}(m),s=[],r=0;16>r;r++)s[r]=0;for(c=pt(s,a),u=y.slice(0),r=12;15>r;r++)u[r]=0;for(u[15]=1,l=pt(u,a),d=ht(u),_=[],r=0;i>r;r+=16)for(p=pt(d,a),d=ht(d),o=0;16>o&&i>r+o;o++)_[r+o]=v[r+o]^p[o];for(f=v.slice(0);15&f.length;)f.push(0);for(r=0;8>r;r++)f.push(0);for(h=8*i,f.push(0,0,0,0),f.push(h>>>24&255,h>>>16&255,h>>>8&255,255&h),g=function(e,t){var n,r,o=[];for(n=0;16>n;n++)o[n]=0;for(n=0;t.length>n;n+=16){for(r=0;16>r;r++)o[r]^=t[n+r]||0;o=ft(o,e)}return o}(c,f),r=0;16>r;r++)if((l[r]^g[r])!==w[r])return null;return _}(o,i,a)))return Promise.reject(Error("AES-GCM 认证标签校验失败"));if("undefined"!=typeof Uint8Array)for(c=new Uint8Array(s.length),u=0;s.length>u;u++)c[u]=s[u];else c=s;return Promise.resolve(c)}catch(e){return Promise.reject(e)}},gt.prototype.fetchAndDecryptConfig=function(){var e,t,n;return this.cachedConfigPromise?this.cachedConfigPromise:(e=this,t=this._readPersisted(),this.encryptedConfigText?(n=this._decryptAes256Gcm(this.encryptedConfigText,this._secondGcmKeyBase64).then(function(t){var n,r,o,i;if("undefined"!=typeof TextDecoder)n=(new TextDecoder).decode(t);else{for(r=[],o=0;t.length>o;o++)r[o]=t[o];n=String.fromCharCode.apply(null,r)}return i=e._mapPayload(JSON.parse(n)),e._persist(i),i}).catch(function(e){return t}),this.cachedConfigPromise=n,n):(this.cachedConfigPromise=Promise.resolve(t),this.cachedConfigPromise))},gt.prototype.getBatchEndpoints=function(){return this.fetchAndDecryptConfig().then(function(e){return e.batchUrls||[]})},gt.prototype.getEventCodes=function(){return this.fetchAndDecryptConfig().then(function(e){return Array.isArray(e.eventCodes)&&e.eventCodes.length>0?e.eventCodes:[]})},gt.prototype.getSigningConfig=function(){return this.fetchAndDecryptConfig().then(function(e){return{hmacKey:(e.signKey||"")+"",securityKeyVersion:(e.securityKeyVersion||"")+""}})},gt.prototype.prewarm=function(){this.fetchAndDecryptConfig().catch(function(){})},gt.prototype.forceRefresh=function(){this.cachedConfigPromise=null},Ot="events",vt.prototype.isAvailable=function(){return"undefined"!=typeof indexedDB&&"undefined"!=typeof window&&null!=window.indexedDB},vt.prototype.openDb=function(){var t=this;return new Promise(function(n,r){if(!t.isAvailable())return n(null);var o=indexedDB.open("web_sdk_event_queue",3);o.onerror=function(){r(o.error||Error("open indexedDB failed"))},o.onupgradeneeded=function(t){var n,r,i,a,s=o.result,c=t.oldVersion,u=t.target.transaction;if(!s.objectStoreNames.contains(Ot))return(n=s.createObjectStore(Ot,{keyPath:"id",autoIncrement:!0})).createIndex("createdAt","createdAt",{unique:!1}),void n.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0});2>c&&u&&((r=u.objectStore(Ot)).indexNames.contains("pendingDedupeKey")||r.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!1})),3>c&&u&&((i=u.objectStore(Ot)).indexNames.contains("pendingDedupeKey")&&i.deleteIndex("pendingDedupeKey"),(a=i.getAll()).onsuccess=function(){var t,n,r,o,s,c,u,l=a.result||[],d={};for(t=0;l.length>t;t++)(r=(n=l[t]).pendingDedupeKey||mt(n.record,n.endpointOverride))&&(d[r]||(d[r]=[]),d[r].push(n.id));for(o=Object.keys(d),s=0;o.length>s;s++)if((c=d[o[s]]).length>=2)for(c.sort(function(e,t){return e-t}),u=1;c.length>u;u++)i.delete(c[u]);try{i.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(e){}},a.onerror=function(){try{i.createIndex("pendingDedupeKey","pendingDedupeKey",{unique:!0})}catch(e){}})},o.onsuccess=function(){n(o.result)}})},vt.prototype._txDone=function(e){return new Promise(function(t,n){e.oncomplete=function(){t()},e.onerror=function(){n(e.error||Error("tx failed"))},e.onabort=function(){n(e.error||Error("tx aborted"))}})},vt.prototype._trimOverflow=function(e){var t=this,n=Ot,r=e.transaction(n,"readonly");return new Promise(function(e,t){var o=r.objectStore(n).count();o.onsuccess=function(){e(+(o.result||0))},o.onerror=function(){t(o.error)}}).then(function(e){return t._txDone(r).then(function(){return e})}).then(function(r){var o,i,a,s;if(r>t.maxSize)return o=r-t.maxSize,i=e.transaction(n,"readwrite"),a=i.objectStore(n).index("createdAt"),s=0,new Promise(function(e,t){var r=a.openCursor();r.onerror=function(){t(r.error)},r.onsuccess=function(){var t=r.result;if(!t||s>=o)return e();i.objectStore(n).delete(t.primaryKey),s++,t.continue()}}).then(function(){return t._txDone(i)})})},vt.prototype.enqueueMany=function(e){var t=this;return e&&e.length?this.openDb().then(function(n){var r,o;if(n)return r=Date.now(),o=e.map(function(e,t){var n=e&&"object"==typeof e&&"record"in e?e:{record:e},o=mt(n.record,n.endpointOverride||""),i={createdAt:r+t,record:n.record,endpointOverride:n.endpointOverride||"",retryCount:0,nextRetryAt:0};return o&&(i.pendingDedupeKey=o),i}),new Promise(function(e,t){var r,i=n.transaction(Ot,"readwrite"),a=i.objectStore(Ot);i.oncomplete=function(){e()},i.onerror=function(){t(i.error)},i.onabort=function(){t(i.error)},r=0,function e(){var n,i;o.length>r&&(n=o[r++],(i=a.add(n)).onsuccess=function(){e()},i.onerror=function(n){var r=i.error;if(r&&"ConstraintError"===r.name)return n.preventDefault(),void e();t(r||Error("add failed"))})}()}).then(function(){return t._trimOverflow(n)}).then(function(){n.close()}).catch(function(e){throw n.close(),e})}):Promise.resolve()},vt.prototype.readOldest=function(e){return e=e||20,this.openDb().then(function(t){var n,r;return t?(n=Date.now(),r=[],new Promise(function(o,i){var a=t.transaction(Ot,"readonly").objectStore(Ot).index("createdAt").openCursor();a.onerror=function(){i(a.error)},a.onsuccess=function(){var t,i,s,c,u,l=a.result;if(!l||r.length>=e)return o();(s=isFinite(i=+(t=l.value).nextRetryAt)?Math.floor(i):0)>n||((c=yt(t)).endpointOverride=t.endpointOverride||"",c.retryCount=isFinite(u=+t.retryCount)?Math.floor(u):0,c.nextRetryAt=s,r.push(c)),l.continue()}}).then(function(){return t.close(),r}).catch(function(e){throw t.close(),e})):[]})},vt.prototype.removeByIds=function(e){var t=this;return e&&e.length?this.openDb().then(function(n){var r,o,i;if(n){for(o=(r=n.transaction(Ot,"readwrite")).objectStore(Ot),i=0;e.length>i;i++)o.delete(e[i]);return t._txDone(r).then(function(){n.close()}).catch(function(e){throw n.close(),e})}}):Promise.resolve()},vt.prototype.updateRetryMeta=function(e,t,n){var r=this;return e&&e.length?this.openDb().then(function(o){var i,a,s,c,u,l,d,_,p,f,h,g,m,y,v;if(!o)return{dropped:0,kept:0};for(i=Date.now(),s=isFinite(a=+t)?Math.max(0,Math.floor(a)):0,u=isFinite(c=+n)?Math.max(0,Math.floor(c)):0,l=0,d=0,p=(_=o.transaction(Ot,"readwrite")).objectStore(Ot),f=0;e.length>f;f++)u<(m=(isFinite(g=+(h=e[f]).retryCount)?Math.floor(g):0)+1)?(l++,p.delete(h.id)):(d++,y=h.pendingDedupeKey||mt(h.record,h.endpointOverride||""),v=yt(h),y&&(v.pendingDedupeKey=y),v.retryCount=m,v.nextRetryAt=i+s,p.put(v));return r._txDone(_).then(function(){return o.close(),{dropped:l,kept:d}}).catch(function(e){throw o.close(),e})}):Promise.resolve({dropped:0,kept:0})},vt.prototype.count=function(){var e=this;return this.openDb().then(function(t){if(!t)return 0;var n=t.transaction(Ot,"readonly");return new Promise(function(e,t){var r=n.objectStore(Ot).count();r.onsuccess=function(){e(+(r.result||0))},r.onerror=function(){t(r.error)}}).then(function(r){return e._txDone(n).then(function(){return t.close(),r})}).catch(function(e){throw t.close(),e})})},Lt={navigation:{navigation_key:1,navigation_name:1},appPageView:{user_type:1,page_key:1,page_name:1,referrer_page_key:9,referrer_page_name:9,current_page_key:1,current_page_name:1,page_load_time:3,recommend_trace_id:0},appInstall:{trace_id:1,attribution_flag:2},pageClick:{page_key:1,page_name:1,click_page_x:3,click_page_y:3,click_x_percent:3,click_y_percent:3,screen_width:3,screen_height:3,recommend_trace_id:0},recommendListView:{page_key:1,page_name:1,recommend_content_type:1,recommend_trace_id:0,recommend_id:1,recommend_trace_info:0,client_version:0},recommendListClick:{page_key:1,page_name:1,recommend_content_type:1,recommend_trace_id:0,recommend_id:1,recommend_trace_info:0,client_version:0},advertising:{event_type:1,advertising_key:1,advertising_name:1,advertising_id:1},videoEvent:{media_id:0,video_id:1,video_title:1,video_type_id:1,video_type_name:1,video_content_type:9,recommend_trace_id:0,video_tag_key:9,video_tag_name:9,video_duration:3,play_duration:3,play_progress:3,play_speed:3,video_behavior_key:1,video_behavior_name:1,post_id:0},novelEvent:{media_id:0,novel_id:1,novel_title:1,chapter_id:0,chapter_name:0,novel_type_id:1,novel_type_name:1,recommend_trace_id:0,novel_tag_key:9,novel_tag_name:9,read_progress:3,page_no:3,novel_behavior_key:1,novel_behavior_name:1},comicEvent:{media_id:0,comic_id:1,comic_title:1,chapter_id:0,chapter_name:0,comic_type_id:1,comic_type_name:1,recommend_trace_id:0,comic_tag_key:9,comic_tag_name:9,read_progress:3,page_no:3,comic_behavior_key:1,comic_behavior_name:1},keywordSearch:{keyword:1,search_result_count:3,search_content_type:0,search_trace_id:0,search_id:0},keywordClick:{keyword:1,click_item_id:1,click_item_type_key:1,click_item_type_name:1,click_position:3,search_trace_id:0},adImpression:{page_key:1,page_name:1,ad_slot_key:1,ad_slot_name:1,ad_id:1,creative_id:0,ad_type:0},adClick:{page_key:1,page_name:1,ad_slot_key:1,ad_slot_name:1,ad_id:1,creative_id:0,ad_type:0},appPageitemExposure:{item_id:1,item_type:1,item_name:1,parent_item_id:1,page_key:1,page_name:1,referrer_page_key:9,referrer_page_name:9,recommend_trace_id:9,recommend_id:9,recommend_trace_info:9},appPageitemClick:{item_id:1,item_type:1,item_name:1,parent_item_id:1,position:3,page_key:1,page_name:1,recommend_trace_id:9,recommend_id:9,recommend_trace_info:9},adRequest:{request_id:1,ad_slot_key:1},adFill:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,fill_count:3,is_skippable:3,is_closable:3},adNoFill:{request_id:1,ad_slot_key:1,ad_type:1,reason:1},adLoadSuccess:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,load_duration:3},adImpressionSdk:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,material_size:1,is_skippable:3,is_closable:3},adClickSdk:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1},adClose:{trace_id:1,request_id:1,ad_slot_key:1,ad_id:1,ad_type:1,close_type:1},adError:{trace_id:1,request_id:0,ad_slot_key:1,ad_type:1,ad_id:9,error_code:1,error_msg:1,error_stage:1},imEvent:{conversation_id:1,conversation_type:1,current_uid:1,target_uid:1,target_role_key:1,target_role_name:1,message_id:9,message_type:9,im_behavior_key:1,im_behavior_name:1},imConversationView:{current_uid:1,conversation_count:3,total_unread_count:3,normal_conversation_count:3,archived_count:3,pinned_count:3},imConnectionSuccess:{current_uid:9,im_sdk_version:1},imMessageContent:{conversation_id:1,conversation_type:1,current_uid:1,target_uid:1,sender_uid:1,message_id:1,message_type:1,message:9,message_content_length:3,content_hash:9},postEvent:{media_id:0,post_id:1,post_title:1,post_create_time:3,post_user_id:1,post_user_nick:1,post_type_id:1,post_type_name:1,post_content_type:1,recommend_trace_id:9,post_tag_key:9,post_tag_name:9,post_behavior_key:1,post_behavior_name:1},dramaEvent:{media_id:0,drama_id:1,drama_title:1,episode:3,total_episode:3,video_id:1,drama_type_id:1,drama_type_name:1,drama_content_type:1,recommend_trace_id:9,drama_tag_key:9,drama_tag_name:9,drama_duration:3,play_duration:3,play_progress:3,play_speed:3,play_behavior_key:1,play_behavior_name:1},dramaStatusChange:{media_id:0,drama_id:1,drama_title:1,episode:3,video_id:1,pre_episode:3,drama_behavior_key:1,drama_behavior_name:1}},qt={message_send:"发送消息",message_receive:"收到消息",message_read:"消息已读",message_recall:"消息撤回",conversation_pin:"置顶会话",conversation_unpin:"取消置顶",conversation_archive:"归档会话",conversation_unarchive:"取消归档"},Bt=Object.keys(Rt={navigation:"navigation",appPageView:"app_page_view",appInstall:"app_install",pageClick:"page_click",recommendListView:"recommend_list_view",recommendListClick:"recommend_list_click",advertising:"advertising",videoEvent:"video_event",novelEvent:"novel_event",comicEvent:"comic_event",keywordSearch:"keyword_search",keywordClick:"keyword_click",adImpression:"ad_impression",adClick:"ad_click",appPageitemExposure:"app_pageitem_exposure",appPageitemClick:"app_pageitem_click",adRequest:"ad_request",adFill:"ad_fill",adNoFill:"ad_no_fill",adLoadSuccess:"ad_load_success",adImpressionSdk:"ad_impression_sdk",adClickSdk:"ad_click_sdk",adClose:"ad_close",adError:"ad_error",imEvent:"im_event",imConversationView:"im_conversation_view",imConnectionSuccess:"im_connection_success",imMessageContent:"im_message_content",postEvent:"post_event",dramaEvent:"drama_event",dramaStatusChange:"drama_status_change"}).reduce(function(e,t){var n=Rt[t];return e[t]="imEvent"===t?function(e){return{event:n,payload:kt(t,wt(e))}}:function(e){return{event:n,payload:kt(t,e||{})}},e},{}),Kt=function(){},Vt={sdk_init:1,app_lifecycle:1,network_state_change:1,app_session_summary:1,app_page_view:1,page_click:1},jt=6e4,Nt="__web_sdk_recent_ok_event_ids__",Ft=function(){function t(e){var t,n=Object.keys(r);for(t=0;n.length>t;t++)e-r[n[t]]>jt&&delete r[n[t]]}function n(){try{"undefined"!=typeof window&&window.localStorage&&window.localStorage.setItem(Nt,JSON.stringify(r))}catch(e){}}var r={};if(function(){var t,n,o,i,a;try{if("undefined"!=typeof window&&window.localStorage&&(t=window.localStorage.getItem(Nt)))for(n=JSON.parse(t),o=Date.now(),i=Object.keys(n),a=0;i.length>a;a++)/^[0-9a-f]{32}$/.test(i[a])&&(o-n[i[a]]>jt||(r[i[a]]=n[i[a]]))}catch(e){}}(),"undefined"!=typeof window)try{window.addEventListener("storage",function(e){var t,n,o,i,a;try{if(e.key!==Nt||!e.newValue)return;for(t=JSON.parse(e.newValue),n=Date.now(),o=Object.keys(t),i=0;o.length>i;i++)a=t[o[i]],/^[0-9a-f]{32}$/.test(o[i])&&(n-a>jt||(r[o[i]]=a))}catch(s){}})}catch(e){}return{has:function(e){var n,o;return!!e&&(t(n=Date.now()),void 0!==(o=r[e])&&jt>=n-o)},set:function(e){var o,i,a,s,c,u=Date.now();for(t(u),o=0;e.length>o;o++)e[o]&&(r[e[o]]=u);if((i=Object.keys(r)).length>500){for(a="",s=1/0,c=0;i.length>c;c++)s>r[i[c]]&&(s=r[i[c]],a=i[c]);a&&delete r[a]}n()},reset:function(){r={},n()}}}(),Pt.prototype.startReplayTimer=function(){var e=this;this.replayTimer=setInterval(function(){e.replayPersistent()},Zn)},Pt.prototype._stopTimer=function(){this.timer&&(clearTimeout(this.timer),this.timer=null)},Pt.prototype._settleItem=function(e,t){e.settled||(e.settled=!0,e.timeoutTimer&&(clearTimeout(e.timeoutTimer),e.timeoutTimer=null),e.resolve(t))},Pt.prototype._purgeSettled=function(){this.queue.length&&(this.queue=this.queue.filter(function(e){return!e.settled}))},Pt.prototype._toPersistentEntries=function(e){return e.map(function(e){return{record:e.record,endpointOverride:e.endpointOverride||"",retryCount:0,nextRetryAt:0}})},Pt.prototype._getRetryDelay=function(e){return Math.min(6e4,Math.max(Zn,Math.floor(Zn*Math.pow(2,Math.min(10,Math.max(0,e))))))},Pt.prototype.schedule=function(){var e=this;this._purgeSettled(),!this.timer&&this.queue.length&&(this.timer=setTimeout(function(){e.timer=null,e.flush().catch(function(){})},5e3))},Pt.prototype.flush=function(){var e=this,t=this.flushTail.then(function(){return e.flushOnce()});return this.flushTail=t.catch(function(){}),t},Pt.prototype._processFlushGroup=function(e,t,n){var r,o,i,a,s,c,u,l,d,_,p,f=this,h="__default__"===e?"":e,g=[],m=[];for(r=0;t.length>r;r++)((i=(((o=t[r]).record&&o.record.event_id||"")+"").trim())&&Ft.has(i)?m:g).push(o);for(a=0;m.length>a;a++)f._settleItem(m[a],{sent:!1,ok:!1,status:0,reason:"cross_batch_recent_success_deduped"});if(!g.length)return Promise.resolve();if(!f._resolveEndpoint){for(Kt("[sdk][web] _resolveEndpoint 未注入,事件无法发送"),s=0;g.length>s;s++)f._settleItem(g[s],{sent:!1,ok:!1,status:0,reason:"no_endpoint_resolver"});return Promise.resolve()}for(c={},u=[],l={},d=0;g.length>d;d++)c[p=(((_=g[d]).record&&_.record.event_id||"")+"").trim()||"__empty__:"+d]||(c[p]=!0,u.push(_.record),l[d]=!0);return f.resolver.consumeProbeFailurePersistSignal()?f.persistentQueue.enqueueMany(f._toPersistentEntries(g)).then(function(){f.hasPersistentItems=!0;for(var e=0;g.length>e;e++)f._settleItem(g[e],{sent:!1,ok:!1,status:0,reason:"probe_all_failed"})}).catch(function(){}):f._resolveEndpoint(h).then(function(e){var t,r,o,i,a,s;if(e){for(r=[],o=0;u.length>o;o++)if((i=((u[o]&&u[o].event_id||"")+"").trim())&&Ft.has(i)){for(a=0;g.length>a;a++)if(l[a]&&((g[a].record&&g[a].record.event_id||"")+"").trim()===i){f._settleItem(g[a],{sent:!1,ok:!1,status:0,reason:"cross_batch_recent_success_deduped"}),l[a]=!1;break}}else r.push(u[o]);if(!r.length){for(s=0;g.length>s;s++)l[s]||f._settleItem(g[s],{sent:!1,ok:!1,status:0,reason:"batch_deduped_duplicate"});return Promise.resolve()}return ut(e,r,{useBeacon:rr}).then(function(e){var t,n,o,i,a,s=Promise.resolve();if(e&&e.ok){for(t=[],n=0;r.length>n;n++)(o=((r[n]&&r[n].event_id||"")+"").trim())&&t.push(o);Ft.set(t)}else{for(i=[],a=0;g.length>a;a++)l[a]&&i.push(g[a]);s=f.persistentQueue.enqueueMany(f._toPersistentEntries(i)).then(function(){f.hasPersistentItems=!0}).catch(function(){})}return s.then(function(){for(var t=0;g.length>t;t++)f._settleItem(g[t],l[t]?e:{sent:!1,ok:!1,status:0,reason:"batch_deduped_duplicate"})})})}if("unavailable"===f.resolver.getEndpointState())return f.persistentQueue.enqueueMany(f._toPersistentEntries(g)).then(function(){f.hasPersistentItems=!0;for(var e=0;g.length>e;e++)f._settleItem(g[e],{sent:!1,ok:!1,status:0,reason:"endpoint_unavailable"})}).catch(function(){});for(t=0;g.length>t;t++)n.push(g[t])})},Pt.prototype.flushOnce=function(){var e,t,n,r,o,i,a,s,c,u=this;if(this._purgeSettled(),!this.queue.length)return Promise.resolve();for(this._stopTimer(),e=this.queue.splice(0,10),t={},n=0;e.length>n;n++)(r=e[n]).settled||(t[o=r.endpointOverride||"__default__"]||(t[o]=[]),t[o].push(r));for(i=[],a=Object.keys(t),s=Promise.resolve(),c=0;a.length>c;c++)(function(e){s=s.then(function(){return u._processFlushGroup(e,t[e],i)})})(a[c]);return s.then(function(){var e,t,n,r,o,a,s;if(i.length){for(e=Date.now(),t=[],n=[],r=0;i.length>r;r++)(i[r].timeoutAt>e?n:t).push(i[r]);for(o=0;n.length>o;o++)u.queue.push(n[o]);for(a=Promise.resolve(),s=0;t.length>s;s++)(function(e){a=a.then(function(){return u.persistentQueue.enqueueMany(u._toPersistentEntries([e])).catch(function(){}).then(function(){u.hasPersistentItems=!0,u._settleItem(e,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})})})(t[s]);return a}}).then(function(){u.queue.length&&u.schedule()},function(e){throw u.queue.length&&u.schedule(),e})},Pt.prototype._processReplayGroup=function(e,t){var n=this;return n._resolveEndpoint?n._resolveEndpoint("__default__"===e?"":e).then(function(e){var r,o,i,a,s,c,u,l,d;if(e){for(r={},o=[],i=[],a=[],s=0;t.length>s;s++)u=(((c=t[s]).record&&c.record.event_id||"")+"").trim()||"__empty__:"+c.id,(l=((c.record&&c.record.event_id||"")+"").trim())&&Ft.has(l)||r[u]?a.push(c.id):(r[u]=!0,o.push(c.record),i.push(c.id));return d=Promise.resolve(),a.length&&(d=d.then(function(){return n.persistentQueue.removeByIds(a)})),o.length?d.then(function(){return ut(e,o,{useBeacon:rr})}).then(function(e){var r,s,c,u,l,d,_,p,f,h;if(e&&e.ok){for(r=[],s=0;o.length>s;s++)(c=((o[s]&&o[s].event_id||"")+"").trim())&&r.push(c);for(Ft.set(r),u=[],l=0;i.length>l;l++)u.push(i[l]);for(d=0;a.length>d;d++)u.push(a[d]);return n.persistentQueue.removeByIds(u)}for(_=0,p=0;t.length>p;p++)(f=t[p].retryCount||0)>_&&(_=f);return h=n._getRetryDelay(_),n.persistentQueue.updateRetryMeta(t,h,5)}):d}}):(Kt("[sdk][web] _resolveEndpoint 未注入,持久队列重试跳过"),Promise.resolve())},Pt.prototype.replayPersistent=function(){var e=this;return!this.hasPersistentItems||this.replaying?Promise.resolve():(this.replaying=!0,this.persistentQueue.readOldest(10).then(function(t){var n,r,o,i,a,s,c;if(!t.length)return e.persistentQueue.count().then(function(t){0===t&&(e.hasPersistentItems=!1)});for(n={},r=0;t.length>r;r++)n[i=(o=t[r]).endpointOverride||"__default__"]||(n[i]=[]),n[i].push(o);for(a=Object.keys(n),s=Promise.resolve(),c=0;a.length>c;c++)(function(t){s=s.then(function(){return e._processReplayGroup(t,n[t])})})(a[c]);return s}).catch(function(){}).then(function(){e.replaying=!1},function(){e.replaying=!1}))},Pt.prototype.enqueueAssembled=function(e,t){var n=this;return(n._getEventCodes?n._getEventCodes():Promise.resolve([])).then(function(r){return r&&0===r.length||r&&-1===r.indexOf(e.event)?{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}:new Promise(function(r){var o={record:e,endpointOverride:t||"",resolve:r,reject:r,settled:!1,timeoutAt:Date.now()+er,timeoutTimer:null};o.timeoutTimer=setTimeout(function(){o.settled||n.persistentQueue.enqueueMany(n._toPersistentEntries([o])).catch(function(){}).then(function(){n.hasPersistentItems=!0,n._settleItem(o,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})},er),n.queue.push(o),10>n.queue.length?n.schedule():n.flush().catch(function(){})})})},Pt.prototype.track=function(e,t){var n=this;return Q().then(function(r){var o={appId:n.opts.appId,channel:n.opts.channel,uid:n.opts.uid||"",deviceId:n.opts.deviceId||r,deviceType:y(),sdkVersion:$n,appVersion:v(n.opts.appVersion)};return(n._getSigningConfig?n._getSigningConfig():Promise.resolve({hmacKey:"",securityKeyVersion:""})).then(function(t){return $e(o,e,t)}).then(function(e){return delete e._sessionInfo,(n._getEventCodes?n._getEventCodes():Promise.resolve([])).then(function(r){return r&&0===r.length?(Kt("[sdk][web] 当前没有事件允许上报,请检查服务端配置(eventCodes 为空)"),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):r&&-1===r.indexOf(e.event)?(Kt('[sdk][web] 事件 "'+e.event+'" 不在服务端允许上报的列表中,已忽略'),{sent:!1,ok:!1,status:0,reason:"event_code_not_allowed",event:e.event}):new Promise(function(r,o){var i={record:e,endpointOverride:t||"",resolve:r,reject:o,settled:!1,timeoutAt:Date.now()+er,timeoutTimer:null};i.timeoutTimer=setTimeout(function(){i.settled||n.persistentQueue.enqueueMany(n._toPersistentEntries([i])).catch(function(){}).then(function(){n.hasPersistentItems=!0,n._settleItem(i,{sent:!1,ok:!1,status:0,reason:"track_timeout",transport:"queue"})})},er),n.queue.push(i),10>n.queue.length?n.schedule():n.flush().catch(function(){})})})})})},Pt.prototype.stop=function(){var e,t;this._stopTimer(),this.replayTimer&&(clearInterval(this.replayTimer),this.replayTimer=null),e={sent:!1,ok:!1,status:0,reason:"stopped"},t=this,this.queue.splice(0).forEach(function(n){t._settleItem(n,e)})},Pt.prototype.getConfig=function(){return{batchMaxSize:10,batchIntervalMs:5e3,replayBatchSize:10,replayIntervalMs:Zn}},Et.prototype._sdkOpts=function(){return{appId:this.options.appId,channel:this.options.channel,uid:this.options.uid||"",deviceId:this.options.deviceId||"",deviceType:y(),sdkVersion:$n,appVersion:v(this.options.appVersion)}},Et.prototype._isAutoDisabled=function(e){return!(!this._disableAutoEvents||!this._disableAutoEvents[e])},Et.prototype._handleEndedSession=function(t){var n,r,o,i,a,s,c;return t&&t.sid&&!this._ssrSkipped?this._isAutoDisabled("app_session_summary")?Promise.resolve():function(t){if(!t)return!1;if(Pn[t]||En[t])return!1;if(se())try{if(window.sessionStorage.getItem(bn+(t+"")))return Pn[t]=1,!1}catch(e){}return En[t]=1,!0}(t.sid)?(n=this,o=t.sid,a=(null!=t.sessionEndTs?1e12>t.sessionEndTs?1e3*t.sessionEndTs:t.sessionEndTs:Date.now())||Date.now(),s=fe(o),c=!Sn||Sn===o||!s&&(xn.eventCountTotal>0||xn.currentPage||(xn.pageStayList||[]).length>0),s&&Sn&&Sn!==o&&(c=!1),s&&c&&(s.eventCountTotal||0)>(xn.eventCountTotal||0)&&(c=!1),c?(xe(),i=ce(xn)):i=s?function(e,t){var n,r,o,i=e&&e.currentPage;return i?(n=t||Date.now(),r=i.accumulated_ms||0,null!=i.resume_at_ms&&(r+=Math.max(0,n-i.resume_at_ms)),o={page_key:i.page_key,page_name:i.page_name||"",stay_duration_ms:r,enter_time:Math.floor((i.enter_time||n)/1e3),has_interaction:i.has_interaction?1:0},i.page_seq_in_sid&&(o.page_seq_in_sid=i.page_seq_in_sid),e.pageStayList=be((e.pageStayList||[]).concat([o])),e.currentPage=null,e):e}(ce(s),a):{eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},pe(o,{sid:o,savedAt:Date.now(),sessionStartedAt:Dn,behaviorHash:ne(),metrics:i}),r=i,Q().then(function(e){var o=bt(n._sdkOpts(),{deviceId:e});return n.configMgr.getSigningConfig().then(function(e){var n=Me(t,t.endReason,r);return it(o,n,e,t.sid)})}).then(function(e){return e}).then(function(e){return delete e._sessionInfo,n.batcher.enqueueAssembled(e,"")}).then(function(){var n,r;!function(t){if(t&&(delete En[t],Pn[t]=1,se()))try{window.sessionStorage.setItem(bn+(t+""),"1")}catch(e){}}(t.sid),ke(t.sid),n={pageKey:Ln||"",pageName:Rn||""},r=function(e){if(!e||null==e.sessionStartTs)return Date.now();var t=e.sessionStartTs;return 1e12>t?1e3*t:t}(F()),function(t,n){var r,o;t&&function(t){if(t){if(se())try{window.sessionStorage.removeItem(de(t))}catch(e){}if(ye())try{window.localStorage.removeItem(de(t))}catch(e){}}}(t),xn={eventCountTotal:0,businessEventCount:0,pageViewCount:0,pageClickCount:0,networkChangeCount:0,backgroundCount:0,pageKeys:{},pageStayList:[],pageSeqCounter:0,currentPageSeq:0,lastEventName:"",scrollTotalCount:0,scrollTotalDistance:0,scrollUpCount:0,scrollDownCount:0,scrollDirectionChangeCount:0,lastScrollDirection:0,firstScrollDelayMs:-1,hasScrolled:!1,maxScrollDepthPercent:0,isReachBottom:0,keydownTotalCount:0,inputCount:0,focusTotalCount:0,firstInteractionDelayMs:-1,firstInteractionRecorded:!1,lastOrientation:"unknown",touchTotalCount:0,swipeTotalCount:0,longPressTotalCount:0,pinchTotalCount:0,currentPage:null},Dn=r=n&&"number"==typeof n.sessionStartMs&&isFinite(n.sessionStartMs)?n.sessionStartMs:Date.now(),xn.lastOrientation=re(),mn.reset(),Cn=!0,Sn=z()||"",(o=n&&n.carryOver)&&o.pageKey?function(t,n,r){var o,i,a,s,c=((t||"")+"").trim();if(!c)return!1;o="number"==typeof r&&isFinite(r)?r:Date.now(),i=Date.now(),a=!1;try{a="undefined"!=typeof document&&"hidden"===document.visibilityState}catch(e){}xn.pageKeys[c]=1,s=ae(),xn.currentPage={page_key:c,page_name:(n||"")+"",enter_time:o,enter_time_ms:o,page_seq_in_sid:s,has_interaction:!1,accumulated_ms:0,resume_at_ms:a?null:i,hidden_paused:a},me()}(o.pageKey,o.pageName,r):me()}(t.sid,{sessionStartMs:r,carryOver:n.pageKey?{pageKey:n.pageKey,pageName:n.pageName}:null})}).catch(function(){var e;(e=t.sid)&&delete En[e],function(e){if(e&&e.sid){var t=ve().filter(function(t){return t&&t.sid!==e.sid});t.push(e),we(t)}}({sid:t.sid,sessionStartTs:t.sessionStartTs,sessionEndTs:t.sessionEndTs,endReason:t.endReason,fields:Me(t,t.endReason,r)})})):Promise.resolve():Promise.resolve()},Et.prototype._replayPendingSummaries=function(e,t,n){var r,o,i=ve();if(!i.length)return Promise.resolve();for(r=Promise.resolve(),o=0;i.length>o;o++)(function(o){r=r.then(function(){return n(it(e,o.fields,t,o.sid)).then(function(){return ke(o.sid),null}).catch(function(){})})})(i[o]);return r},Et.prototype._startAutoEvents=function(){if(!this._ssrSkipped&&!this._autoEventsStarted){var t=this;this._autoEventsStarted=!0,qn=function(e){t._handleEndedSession(e).catch(function(){})},Z().then(function(){return Q()}).then(function(n){var r=bt(t._sdkOpts(),{deviceId:n});return t.configMgr.getSigningConfig().then(function(n){return t.configMgr.fetchAndDecryptConfig().then(function(o){function i(e){return!s||-1!==s.indexOf(e)}function a(e){return i(e)&&!t._isAutoDisabled(e)}var s=o.eventCodes&&o.eventCodes.length>0?o.eventCodes:null,c=function(e){return e.then(function(e){return delete e._sessionInfo,t.batcher.enqueueAssembled(e,"")})};return t._replayPendingSummaries(r,n,c).then(function(){function o(e){if(!s&&(s=!0,a("app_session_summary"))){var n=function(e){var t,n,r=Date.now()||Date.now(),o=F();return o?(t=Y(o,e||"web_visibility_hidden",r),"web_visibility_hidden"!==e&&"app_exit"!==e||(t.sessionEndTs=r),N(n=H(r)),{endedSession:t,sid:n.sid}):null}(e);n&&n.endedSession&&t._handleEndedSession(n.endedSession).catch(function(){})}}var s,u,l,d,_,p,f,h,g,m,y,v,w,k,b;a("sdk_init")&&c(function(e,t){var n,r=fn||{},o={},i=Object.keys(r);for(n=0;i.length>n;n++)o[i[n]]=r[i[n]];return $e(e,{event:"sdk_init",payload:o},t)}(r,n)).catch(function(){}),a("app_lifecycle")&&(t._webLoadTeardown=function(t,n,r){function o(){a||(a=!0,s&&(window.removeEventListener("load",s,!1),s=null),t(ot(n,Kn,{},r)).catch(function(){}))}function i(){(function(){var t,n,r;try{if("undefined"!=typeof performance&&performance.timing&&(t=performance.timing).loadEventEnd&&t.navigationStart&&t.loadEventEnd>0)return!0;if("undefined"!=typeof performance&&"function"==typeof performance.getEntriesByType&&(n=performance.getEntriesByType("navigation"))&&n.length>0&&(r=n[0]).loadEventEnd>0&&r.fetchStart>=0)return!0}catch(e){}return!1})()&&o()}var a,s;return"undefined"==typeof window?(t(ot(n,Kn,{},r)).catch(function(){}),function(){}):(a=!1,s=null,"complete"===document.readyState?(i(),a||o(),function(){}):(s=function(){i(),a||o()},window.addEventListener("load",s,!1),setTimeout(function(){a||o()},1e4),function(){a=!0,s&&(window.removeEventListener("load",s,!1),s=null)}))}(c,r,n)),s=!1,t._autoLifecycleTeardown=function(e,t){var n,r,o,i,a;return"undefined"==typeof window||"undefined"==typeof document?function(){}:(r=!0===(n=t||{}).webMode,o=function(){"visible"===document.visibilityState?r?n.onVisible&&n.onVisible():e(Vn,{}):"hidden"===document.visibilityState&&(r?n.onVisibleHidden&&n.onVisibleHidden():e(jn,{}),n.onHidden&&n.onHidden())},i=function(){e(Nn,rt()),n.onPageUnload&&n.onPageUnload()},a=function(e){e&&e.persisted||n.onPageHide&&n.onPageHide()},window.addEventListener("visibilitychange",o,!1),window.addEventListener("beforeunload",i,!1),window.addEventListener("pagehide",a,!1),function(){window.removeEventListener("visibilitychange",o,!1),window.removeEventListener("beforeunload",i,!1),window.removeEventListener("pagehide",a,!1)})}(function(e,t){return a("app_lifecycle")?c(ot(r,e,t||{},n)):Promise.resolve()},{onHidden:function(){t.batcher.flush().catch(function(){})},onPageUnload:function(){t.batcher.flush().catch(function(){}),o("app_exit")},onPageHide:function(){t.batcher.flush().catch(function(){}),o("web_visibility_hidden")}}),t._autoNetworkTeardown=(l=function(e){return a("network_state_change")?c(function(e,t,n){return $e(e,{event:"network_state_change",payload:t||{}},n)}(r,e,n)):Promise.resolve()},"undefined"==typeof navigator?function(){}:(d=navigator.connection||navigator.mozConnection||navigator.webkitConnection)?(_="unknown",p=d.type?d.type+"":"",f="boolean"==typeof navigator.onLine?navigator.onLine:null,d.onchange=function(){var e,t,n,r,o,i,a=d.type?d.type+"":"",s="boolean"==typeof navigator.onLine?navigator.onLine:null;(a!==p||s!==f)&&(p=a,f=s,r="boolean"==typeof(t=s)?t?1:0:-1,"none"==(n=(a||"")+"")&&1===r?n="unknown":n&&"none"!==n&&"unknown"!==n&&0===r&&(r=1),l(at({network_type:_,from_network_type:_,to_network_type:_,is_online:(e={connection_type:n,is_online:r}).is_online,change_count_in_sid:(xn.networkChangeCount||0)+1},(i={},(o=e.connection_type)?i.connection_type=o+"":d.type&&(i.connection_type=d.type+""),d.effectiveType&&(i.effective_type=d.effectiveType+""),null!=d.downlink&&(i.downlink=Math.round(1e3*d.downlink)),null!=d.rtt&&(i.rtt=d.rtt),i))))},function(){d.onchange=null}):function(){}),t._webInteractionTeardown="undefined"==typeof window||"undefined"==typeof document?function(){}:(h=function(e){!e||e.repeat||e.isComposing||(Se(),he(),xn.keydownTotalCount++,me())},g=function(e){var t,n=e&&e.target;(function(t){if(!t||1!==t.nodeType)return!1;var n=((t.tagName||"")+"").toLowerCase();if("input"===n||"textarea"===n)return!0;try{if(t.isContentEditable)return!0}catch(e){}return!1})(n)&&(t=Date.now(),Fn===n&&50>t-Hn||(Fn=n,Hn=t,Se(),he(),xn.inputCount++,me()))},m=function(e){(function(t){if(!t||1!==t.nodeType)return!1;var n=((t.tagName||"")+"").toLowerCase();if("input"===n||"textarea"===n||"select"===n)return!0;try{if(t.isContentEditable)return!0}catch(e){}return!1})(e&&e.target)&&(Se(),he(),xn.focusTotalCount++,me())},y=function(){Pe()},v=function(e){e&&e.changedTouches&&e.changedTouches.length&&(Se(),he(),xn.touchTotalCount++,me(),e.touches&&1===e.touches.length?(Un=e.touches[0].clientX,Xn=e.touches[0].clientY,Yn=Date.now(),ct(),zn=!1,Gn=setTimeout(function(){zn=!0,he(),xn.longPressTotalCount++,me()},500)):(Yn=0,ct()),e.touches&&2===e.touches.length&&(Wn=st(e.touches),Jn=!1))},w=function(e){var t,n,r;e&&e.touches&&(1===e.touches.length&&Yn>0&&Math.sqrt((t=e.touches[0].clientX-Un)*t+(n=e.touches[0].clientY-Xn)*n)>8&&ct(),2===e.touches.length&&(r=st(e.touches),null==Wn||Jn||12>Math.abs(r-Wn)||(he(),xn.pinchTotalCount++,me(),Jn=!0),Wn=r))},k=function(e){var t,n;ct(),Yn>0&&e.changedTouches&&e.changedTouches.length&&!zn&&(30>Math.sqrt((t=e.changedTouches[0].clientX-Un)*t+(n=e.changedTouches[0].clientY-Xn)*n)||(he(),xn.swipeTotalCount++,me())),Yn=0,zn=!1,e.touches&&e.touches.length>=2||(Wn=null,Jn=!1)},b=function(){ct(),Yn=0,zn=!1,Wn=null,Jn=!1},document.addEventListener("keydown",h,!1),document.addEventListener("input",g,!1),document.addEventListener("focusin",m,!1),window.addEventListener("orientationchange",y,!1),document.addEventListener("touchstart",v,!1),document.addEventListener("touchmove",w,!1),document.addEventListener("touchend",k,!1),document.addEventListener("touchcancel",b,!1),Pe(),function(){document.removeEventListener("keydown",h,!1),document.removeEventListener("input",g,!1),document.removeEventListener("focusin",m,!1),window.removeEventListener("orientationchange",y,!1),document.removeEventListener("touchstart",v,!1),document.removeEventListener("touchmove",w,!1),document.removeEventListener("touchend",k,!1),document.removeEventListener("touchcancel",b,!1),ct()}),(i("app_page_view")||i("page_click"))&&(u=a("app_page_view"),t._pageTrackerTeardown=function(t){function n(){if(u.resolvePageKey)try{var t=u.resolvePageKey();if(t)return qe(t+"")}catch(e){}return Be()}function r(){var e,t=n(),r=ze({previousPageKey:d,previousPageName:_,pageKey:t});d=t,_=r.page_name,Ln=t,Rn=r.page_name||"",l&&(e=ae(),function(e,t,n,r){he(),xn.eventCountTotal++,xn.lastEventName="app_page_view",xn.pageViewCount++;var o=(e||"")+"";o&&(xn.pageKeys[o]=1),Te(o,t,n,r),me()}(t,r.page_name,Math.floor(Date.now()/1e3),e),r._pageSeqPrealloc=e,r._metricsRecorded=1,u.onPageView&&u.onPageView(r))}function o(){var t,n,r,o,i=Date.now();200>i-Mn||(Mn=i,(t=Ke())&&(r=(n=t.scrollTop||0)-In,In=n,o=function(){var t,n,r;try{return(t=Ke())?(n=Math.max(0,(t.scrollHeight||0)-(t.clientHeight||0)))>0?{depthPercent:Math.min(100,Math.max(0,Math.round((r=t.scrollTop||0)/n*100))),atBottom:r>=n-8}:{depthPercent:0,atBottom:!0}:{depthPercent:0,atBottom:!1}}catch(e){return{depthPercent:0,atBottom:!1}}}(),u.onScroll&&u.onScroll(r,o.depthPercent,o.atBottom)))}function i(e){var t,r,o,i;e&&0>=e.button&&((t=e.target)&&"function"==typeof t.closest&&t.closest("[data-sdk-no-auto-click]")||(r=null!=e.clientX?e.clientX:0,o=null!=e.clientY?e.clientY:0,i=Ye(e),setTimeout(function(){if(!function(e,t){var n,r;for(function(e){for(var t=An.length-1;t>=0;t--)e-An[t].t>300&&An.splice(t,1)}(Date.now()),n=0;An.length>n;n++)if(10>=Math.abs((r=An[n]).x-e)&&10>=Math.abs(r.y-t))return!0;return!1}(r,o)){Se();var e=function(e,t){var n=e||{},r=qe(n.pageKey||Be()),o=Ye(t);return Ae({page_key:r,page_name:n.pageName||"undefined"!=typeof document&&document.title||r,recommend_trace_id:"",referrer_page_key:n.previousPageKey||"",is_sdk_auto_collected:1,_clickSnap:o},Ge(o))}({pageKey:d||n(),pageName:_,previousPageKey:d},i);u.onPageClick&&u.onPageClick(e)}},50)))}function a(){Ie(),r()}function s(e){e&&e.persisted&&r()}function c(){var e,t,n=Ke();In=n&&n.scrollTop||0,r(),!p&&window.history&&(p=!0,e=history.pushState,t=history.replaceState,history.pushState=function(){var t=e.apply(history,arguments);return Ie(),setTimeout(r,0),t},history.replaceState=function(){var e=t.apply(history,arguments);return Ie(),setTimeout(r,0),e},window.addEventListener("popstate",a,!1))}var u,l,d,_,p;return"undefined"==typeof window||"undefined"==typeof document?function(){}:(l=!1!==(u=t||{}).enableAutoPageView,d="",_="",p=!1,"loading"===document.readyState?document.addEventListener("DOMContentLoaded",c,!1):c(),window.addEventListener("pageshow",s,!1),window.addEventListener("scroll",o,!1),document.addEventListener("click",i,!0),function(){document.removeEventListener("click",i,!0),window.removeEventListener("scroll",o,!1),window.removeEventListener("pageshow",s,!1),window.removeEventListener("popstate",a,!1),document.removeEventListener("DOMContentLoaded",c,!1)})}({resolvePageKey:t.options.pageKeyResolver,enableAutoPageView:u,onPageView:function(e){u&&t.batcher.track({event:"app_page_view",payload:e},"").catch(function(){})},onPageClick:function(e){a("page_click")&&t.batcher.track({event:"page_click",payload:e},"").catch(function(){})},onScroll:function(e,t,n){!function(e,t,n){var r,o;he(),8>(r=Math.abs(e))||(xn.hasScrolled||(xn.hasScrolled=!0,xn.firstScrollDelayMs=Math.max(0,Date.now()-Dn)),xn.scrollTotalCount++,xn.scrollTotalDistance+=r,o=e>0?1:-1,0!==xn.lastScrollDirection&&o!==xn.lastScrollDirection&&xn.scrollDirectionChangeCount++,xn.lastScrollDirection=o,0>e?xn.scrollUpCount++:e>0&&xn.scrollDownCount++,null!=t&&(xn.maxScrollDepthPercent=Math.max(xn.maxScrollDepthPercent,Math.min(100,Math.max(0,Math.round(t))))),n&&(xn.isReachBottom=1),me())}(e,t,n)}})),t._sessionPollTimer&&clearInterval(t._sessionPollTimer),t._sessionPollTimer=setInterval(function(){var e=G();e.endedSession&&t._handleEndedSession(e.endedSession).catch(function(){})},6e4)})})})}).catch(function(){})}},Et.prototype._init=function(e){var t,n,r;if(this._ssrSkipped)return this._buildInitReturn();for(t=Object.keys(e||{}),n=0;t.length>n;n++)this.options[t[n]]=e[t[n]];return null==this.options.channel&&(this.options.channel=""),this._disableAutoEvents=Ct(this.options.disableAutoEvents),this.options.disableAutoEvents=St(this._disableAutoEvents),this.configMgr.setEncryptedConfig(this.options.encryptedConfig||this.options.configCipher||""),this.configMgr.prewarm(),r=this,this.resolver.init(function(){return r.configMgr.getBatchEndpoints()}),this._refreshTimer&&clearInterval(this._refreshTimer),this._refreshTimer=setInterval(function(){r.refreshConfig()},12e5),this._unsubReady&&this._unsubReady(),this._unsubReady=this.resolver.onEndpointReady(function(){r.batcher.flush().catch(function(){})}),this._buildInitReturn()},Et.prototype.refreshConfig=function(){var e=this;return this.configMgr.forceRefresh(),this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).catch(function(){})},Et.prototype.track=function(e,t){return this._ssrSkipped?Promise.resolve({sent:!1,ok:!0,status:0,reason:"ssr_skipped"}):this.batcher.track(e,t)},Et.prototype.setContext=function(e){if(this._ssrSkipped)return this.getConfig();var t=e&&"object"==typeof e?e:{};return Object.prototype.hasOwnProperty.call(t,"uid")&&(this.options.uid=null==t.uid?"":t.uid+""),Object.prototype.hasOwnProperty.call(t,"channel")&&(this.options.channel=null==t.channel?"":t.channel+""),this.batcher.opts=bt(this.options),this.getConfig()},Et.prototype.setUid=function(e){return this.setContext({uid:e})},Et.prototype.getSelectedHost=function(){var e,t;return this._ssrSkipped||!this.resolver?"":(e=this.resolver.getSelectedBatchEndpoint())&&(t=/^(https?:\/\/[^/]+)/.exec(e))?t[1]:""},Et.prototype._buildInitReturn=function(){return this._ssrSkipped||!this.batcher?{selectedHost:"",version:$n}:bt(this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),version:$n})},Et.prototype.getConfig=function(){if(this._ssrSkipped||!this.batcher)return bt(this.options,{selectedHost:"",selectedBatchEndpoint:"",version:$n});var e=bt(this.options,this.batcher.getConfig(),{selectedHost:this.getSelectedHost(),selectedBatchEndpoint:this.resolver.getSelectedBatchEndpoint(),version:$n});return delete e.encryptedConfig,delete e.configCipher,e},Et.prototype.flush=function(){return this._ssrSkipped?Promise.resolve():this.batcher.flush()},Et.prototype.refreshDomainConfig=function(){if(this._ssrSkipped)return Promise.resolve({selectedBatchEndpoint:"",selectedHost:"",version:$n});var e=this;return this.resolver.refreshDomainConfig(function(){return e.configMgr.getBatchEndpoints()}).then(function(t){return{selectedBatchEndpoint:t,selectedHost:e.getSelectedHost(),version:$n}})},Et.prototype.stop=function(){this._ssrSkipped||(this._sessionPollTimer&&(clearInterval(this._sessionPollTimer),this._sessionPollTimer=null),this._pageTrackerTeardown&&(this._pageTrackerTeardown(),this._pageTrackerTeardown=null),this._webInteractionTeardown&&(this._webInteractionTeardown(),this._webInteractionTeardown=null),this._webLoadTeardown&&(this._webLoadTeardown(),this._webLoadTeardown=null),this._autoLifecycleTeardown&&(this._autoLifecycleTeardown(),this._autoLifecycleTeardown=null),this._autoNetworkTeardown&&(this._autoNetworkTeardown(),this._autoNetworkTeardown=null),this._unsubReady&&(this._unsubReady(),this._unsubReady=null),this._refreshTimer&&(clearInterval(this._refreshTimer),this._refreshTimer=null),this.batcher.stop())},Ht=null,Ut=[],Xt=null,(Yt=Dt).setContext=function(e){return Tt().setContext(e)},Yt.setUid=function(e){return Tt().setUid(e)},Yt.setChannel=function(e){return Tt().setContext({channel:e})},Yt.notifyPageLoad=function(){!function(){if(cn=null,V())try{window.sessionStorage.removeItem(tn),window.sessionStorage.removeItem(nn)}catch(e){}}(),G(Date.now())},Yt.debugInvalidateSessionCache=function(){W()},Yt.debugForceEnsureSession=function(){var e,t;return W(),e=G(),t=Tt(),e.endedSession&&t._handleEndedSession(e.endedSession).catch(function(){}),e},Yt.getConfig=function(){return Tt().getConfig()},Yt.flush=function(){return Tt().flush()},Yt.refreshDomainConfig=function(){return Tt().refreshDomainConfig()},Yt.stop=function(){Ht?(Ht.stop(),Ht=null):Ut.length=0},Yt.version=$n,Yt.getCommonFields=function(e){var t=Ht?Ht.getConfig():{};return Z().then(function(){return Q().then(function(n){var r,o,i,a,s=B({appId:t.appId,channel:t.channel,uid:t.uid,deviceId:t.deviceId||n,sid:(a=z(),a||G().sid),deviceType:y(),deviceBrand:"",deviceModel:"",deviceFingerprint:ee(),fpVersion:te(),sdkVersion:$n,appVersion:v(t.appVersion)});if(!Array.isArray(e)||!e.length)return s;for(r={},o=0;e.length>o;o++)(i=((e[o]||"")+"").trim())&&(r[i]=Object.prototype.hasOwnProperty.call(s,i)?s[i]:"");return r})})},Yt.getCommonField=function(e){var t=((e||"")+"").trim();return t?Yt.getCommonFields([t]).then(function(e){return e[t]}):Promise.resolve("")},Yt.init=function(e){var t,n,r,o;return Ht&&(t=Ht,Ht=null,t.flush().catch(function(){}).then(function(){t.stop()})),n=new Et(e),Ht=n,o=xt(Ut.splice(0),n,0).then(function(){Xt===r&&(Xt=null)},function(){Xt===r&&(Xt=null)}),Xt=r=o,bt(n._buildInitReturn(),{track:Yt.track,setContext:Yt.setContext,setUid:Yt.setUid,setChannel:Yt.setChannel,notifyPageLoad:Yt.notifyPageLoad,getCommonFields:Yt.getCommonFields,getCommonField:Yt.getCommonField})},Gt=function(){return Kt("[sdk][web] 非法上报:禁止直接调用 WebSDK.track({...}),请使用 WebSDK.track.videoEvent({...}) 等具名方法"),Promise.resolve({sent:!1,ok:!1,status:0,reason:"direct_track_forbidden"})},Object.keys(Bt).forEach(function(t){var n=Bt[t];Gt[t]=function(r,o){try{return Dt(n(r),o)}catch(e){if(e&&"EventValidationError"===e.name)return Promise.resolve({sent:!1,ok:!1,status:0,reason:"event_validation_failed",event:e.eventMethod||t,missingFields:e.missingFields||[],typeErrorFields:e.typeErrorFields||[]});throw e}}}),Yt.track="undefined"!=typeof Proxy?new Proxy(Gt,{get:function(e,t){return"string"!=typeof t||t in e||!/^[a-z]/.test(t)?e[t]:function(){return Kt('[sdk][web] 未知上报事件 "'+t+'",该方法不存在,事件未上报'),Promise.resolve({sent:!1,ok:!1,status:0,reason:"unknown_event_method",method:t})}}}):Gt,"undefined"!=typeof globalThis)try{globalThis.WebSDK=Yt}catch(e){}else"undefined"!=typeof window&&(window.WebSDK=Yt);return Yt}); diff --git a/public/static/sitemap.xsl b/public/static/sitemap.xsl new file mode 100644 index 0000000..d31c603 --- /dev/null +++ b/public/static/sitemap.xsl @@ -0,0 +1,45 @@ + + + + + + + + + Sitemap + + + +

Sitemap

+ + + + + + + + + + + +
loclastmod
+ + + + +
+ + +
+
diff --git a/public/static/sitemapindex.xsl b/public/static/sitemapindex.xsl new file mode 100644 index 0000000..41afa64 --- /dev/null +++ b/public/static/sitemapindex.xsl @@ -0,0 +1,45 @@ + + + + + + + + + Sitemap Index + + + +

Sitemap Index

+ + + + + + + + + + + +
loclastmod
+ + + + +
+ + +
+
diff --git a/scripts/check-health.mjs b/scripts/check-health.mjs new file mode 100644 index 0000000..9fa4075 --- /dev/null +++ b/scripts/check-health.mjs @@ -0,0 +1,28 @@ +const [origin,environment]=process.argv.slice(2); +if(!origin||!['test','production'].includes(environment))throw new Error('Usage: node scripts/check-health.mjs test|production'); +async function read(path) { + const response=await fetch(new URL(path,origin),{signal:AbortSignal.timeout(5000),redirect:'error',cache:'no-store'}); + if(response.status!==200)throw new Error(`${path} HTTP ${response.status}`); + let text='',size=0; + const decoder=new TextDecoder(); + for await(const part of response.body) { + size+=part.length;if(size>1048576)throw new Error(`${path} 响应超过检查上限`); + text+=decoder.decode(part,{stream:true}); + if(path==='/'&&text.includes('/_next/'))break; + } + return {text,type:response.headers.get('content-type')||''}; +} +let healthy=false; +for(let attempt=1;attempt<=5;attempt++) { + try { + const health=await read('/healthz'),status=JSON.parse(health.text); + if(status.status!=='ok'||status.environment!==environment||status.application!=='91-drama-next')throw new Error('/healthz 未命中本站对应环境'); + const home=await read('/'); + if(!/^text\/html(?:;|$)/i.test(home.type)||!home.text.includes('/_next/'))throw new Error('首页不是 Next HTML'); + healthy=true;console.log('/healthz 与 Next 首页检查通过');break; + }catch(error) { + console.error(`健康检查 ${attempt}/5 失败:${error.message}`); + if(attempt<5)await new Promise(resolve=>setTimeout(resolve,1000)); + } +} +process.exitCode=healthy?0:1; diff --git a/scripts/deploy-common.sh b/scripts/deploy-common.sh new file mode 100644 index 0000000..e0dcce3 --- /dev/null +++ b/scripts/deploy-common.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# Shared by deploy_test.sh / deploy_prod.sh; compatible with macOS Bash 3.2. +fail() { printf '错误:%s\n' "$*" >&2; exit 1; } +step() { printf '\n▸ %s\n' "$*"; } +need() { command -v "$1" >/dev/null || fail "缺少命令 $1"; } +valid_host() { [[ "$1" =~ ^[a-zA-Z0-9_][a-zA-Z0-9_.@-]*$ ]] || fail "$2 格式无效或未配置"; } +valid_path() { [[ "$1" =~ ^/[a-zA-Z0-9_-]+(/[a-zA-Z0-9_.-]+)*/?$ ]] && [[ "$1/" != *'/../'* ]] && [[ "$1/" != *'/./'* ]] || fail "$2 必须是普通绝对路径"; } +valid_port() { [[ "$1" =~ ^[0-9]+$ ]] && [ "$1" -ge 1 ] && [ "$1" -le 65535 ] || fail "$2 必须在 1–65535 内"; } +check_local() { + need node; need npm + node -e 'const [a,b]=process.versions.node.split(".").map(Number);if(a<20||a===20&&b<9)process.exit(1)' || fail '需要 Node.js >=20.9,推荐 22.17.1' + [ -d node_modules/next ] && [ -d node_modules/dotenv ] || fail '请先执行 npm ci 安装本地依赖' + [ -s "config/$1.env" ] && [ -s package-lock.json ] || fail "缺少 config/$1.env 或 package-lock.json" +} +public_profile() { + node --input-type=module -e 'import{readFileSync}from"node:fs";import{parse}from"dotenv";const c=parse(readFileSync(`config/${process.argv[1]}.env`));console.log(` API=${c.UPSTREAM_API_URL||c.API_BASE_URL} PORT=${c.PORT}`)' "$1" +} +lock_build() { + [ ! -L dist ] || fail 'dist 不允许为符号链接' + mkdir -p dist + DEPLOY_LOCK="$SCRIPT_DIR/dist/.deploy.lock" + mkdir "$DEPLOY_LOCK" 2>/dev/null || fail "已有构建/发布进程或残留锁:$DEPLOY_LOCK" + trap 'rmdir "$DEPLOY_LOCK" 2>/dev/null || true' EXIT + trap 'exit 130' INT + trap 'exit 143' TERM +} diff --git a/scripts/next-env.mjs b/scripts/next-env.mjs new file mode 100644 index 0000000..0283f0d --- /dev/null +++ b/scripts/next-env.mjs @@ -0,0 +1,68 @@ +import {existsSync, readFileSync} from 'node:fs'; +import {resolve} from 'node:path'; +import {fileURLToPath} from 'node:url'; +import {spawn} from 'node:child_process'; +import {parse} from 'dotenv'; + +const projectRoot=fileURLToPath(new URL('../',import.meta.url)); +const directories={development:'.next-dev',test:'.next-test',production:'.next'}; +const commands=new Set(['dev','build','start']); +const readConfig=file=>existsSync(file)?parse(readFileSync(file)):{}; +// Normalize each layer before merging so a legacy process override still wins over a profile. +const apiConfig=values=>({...values,...(!Object.hasOwn(values,'UPSTREAM_API_URL')&&Object.hasOwn(values,'API_BASE_URL')?{UPSTREAM_API_URL:values.API_BASE_URL}:{})}); + +/** APP_ENV selects the deployment; NODE_ENV keeps Next's development/production semantics. */ +export function nextCommand(environment='development',command='dev',args=[],{root=projectRoot,variables=process.env}={}) { + if(!Object.hasOwn(directories,environment)||!commands.has(command))throw new Error('Usage: node scripts/next-env.mjs development|test|production dev|build|start'); + const base=resolve(root,'config',environment+'.env'); + if(!existsSync(base))throw new Error(`缺少环境配置:config/${environment}.env`); + const env={ + ...apiConfig(readConfig(base)), + // Preserve the old local-development setup without leaking it into test/production. + ...(environment==='development'?apiConfig(readConfig(resolve(root,'.env.local'))):{}), + ...apiConfig(readConfig(resolve(root,'config',environment+'.local.env'))), + ...apiConfig(Object.fromEntries(Object.entries(variables).filter(([,value])=>value!==undefined))), + APP_ENV:environment, + APP_DIST_DIR:directories[environment], + NODE_ENV:command==='dev'?'development':'production', + }; + try { + const value=(env.UPSTREAM_API_URL||'').trim(); + if(value==='current')env.UPSTREAM_API_URL=value; + else{ + const api=new URL(value); + if(!['http:','https:'].includes(api.protocol)||api.username||api.password||api.search||api.hash)throw new Error(); + env.UPSTREAM_API_URL=api.href.replace(/\/+$/,''); + } + env.API_BASE_URL=env.UPSTREAM_API_URL; + }catch{throw new Error(`config/${environment}.env 的 UPSTREAM_API_URL(兼容 API_BASE_URL)必须为 current 或有效的 HTTP(S) 接口地址`);} + const port=String(env.PORT||'21021'); + if(!/^\d+$/.test(port)||Number(port)<1||Number(port)>65535)throw new Error('PORT 必须在 1–65535 之间'); + env.PORT=port; + const hasOption=names=>args.some(arg=>names.some(name=>arg===name||arg.startsWith(name+'='))); + const options=command==='build'?[]:[ + ...(hasOption(['--hostname','-H'])?[]:['--hostname','0.0.0.0']), + ...(hasOption(['--port','-p'])?[]:['--port',port]), + ]; + return {executable:process.execPath,args:[resolve(root,'node_modules/next/dist/bin/next'),command,...options,...args],cwd:root,env}; +} + +export async function runNext(environment,command,args,options) { + const spec=nextCommand(environment,command,args,options); + const child=spawn(spec.executable,spec.args,{cwd:spec.cwd,env:spec.env,stdio:'inherit'}); + const interrupt=()=>child.kill('SIGINT'),terminate=()=>child.kill('SIGTERM'); + process.on('SIGINT',interrupt);process.on('SIGTERM',terminate); + try { + return await new Promise((resolve,reject)=>{ + child.once('error',reject); + child.once('exit',(code,signal)=>resolve(code??(signal==='SIGINT'?130:143))); + }); + }finally{process.off('SIGINT',interrupt);process.off('SIGTERM',terminate);} +} + +if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)){ + try { + const [environment='development',command='dev',...args]=process.argv.slice(2); + process.exitCode=await runNext(environment,command,args); + }catch(error){console.error(error instanceof Error?error.message:'启动 Next.js 失败');process.exitCode=1;} +} diff --git a/scripts/package-standalone.mjs b/scripts/package-standalone.mjs new file mode 100644 index 0000000..614c9c9 --- /dev/null +++ b/scripts/package-standalone.mjs @@ -0,0 +1,61 @@ +import {cp, lstat, mkdir, readFile, realpath, rm, writeFile} from 'node:fs/promises'; +import {basename, join, relative, resolve, sep} from 'node:path'; +import {fileURLToPath} from 'node:url'; + +const projectRoot=fileURLToPath(new URL('../',import.meta.url)); + +/** Assemble a relocatable release; only the selected public environment profile is shipped. */ +export async function packageStandalone(environment,{root=projectRoot}={}) { + if(!['test','production'].includes(environment))throw new Error('Usage: node scripts/package-standalone.mjs test|production'); + root=await realpath(root); + const build=environment==='test'?'.next-test':'.next'; + const dist=join(root,'dist'),destination=join(dist,environment),source=join(root,build,'standalone'); + async function directory(path,optional=false) { + const info=await lstat(path).catch(error=>{if(optional&&error.code==='ENOENT')return null;throw error;}); + if(info&&(!info.isDirectory()||info.isSymbolicLink()||await realpath(path)!==path))throw new Error(`Unsafe release directory: ${path}`); + } + for(const path of [join(root,build),source])await directory(path); + for(const file of [join(source,'server.js'),join(source,build,'BUILD_ID'),join(root,'config',environment+'.env')]) { + if(!(await lstat(file)).isFile()||!(await readFile(file)).length)throw new Error(`Missing release file: ${file}`); + } + await directory(dist,true);await mkdir(dist,{recursive:true});await directory(destination,true); + // A fixed dist/ is the only location removed by this command. + await rm(destination,{recursive:true,force:true}); + const filter=async path=>{ + const name=basename(path); + if(name.startsWith('.env')||name.endsWith('.local.env')||name.startsWith('._')||name==='.DS_Store'||name.endsWith('.log'))return false; + // Dynamic filesystem reads can make Next trace sibling build/cache/test directories. + // Keep this project's runtime roots, never recursively include earlier dist releases. + if(path.startsWith(source+sep)&&!['server.js','package.json','package-lock.json',build,'node_modules','src'].includes(relative(source,path).split(sep)[0]))return false; + if(path===join(source,build,'cache'))return false; + if((await lstat(path)).isSymbolicLink())throw new Error(`Release source contains a symbolic link: ${path}`); + return true; + }; + await cp(source,destination,{recursive:true,filter}); + for(const [from,to] of [[join(root,'public'),'public'],[join(root,build,'static'),build+'/static'], + [join(root,'node_modules/dotenv'),'node_modules/dotenv']]) { + await cp(from,join(destination,to),{recursive:true,filter}); + } + await mkdir(join(destination,'config'),{recursive:true});await mkdir(join(destination,'scripts'),{recursive:true}); + for(const file of ['package.json','package-lock.json','scripts/next-env.mjs','scripts/check-health.mjs','config/'+environment+'.env']) { + await cp(join(root,file),join(destination,file)); + } + await writeFile(join(destination,'start.mjs'),`import {fileURLToPath} from 'node:url'; +import {nextCommand} from './scripts/next-env.mjs'; +const root=fileURLToPath(new URL('./',import.meta.url)); +process.chdir(root); +const {env}=nextCommand(${JSON.stringify(environment)},'start',[],{root}); +Object.assign(process.env,env); +process.env.HOSTNAME=process.env.HOSTNAME||'0.0.0.0'; +await import('./server.js'); +`); + console.log(`Standalone release ready: ${destination}`); + return destination; +} + +if(process.argv[1]&&resolve(process.argv[1])===fileURLToPath(import.meta.url)) { + try { + if(process.argv.length!==3)throw new Error('Usage: node scripts/package-standalone.mjs test|production'); + await packageStandalone(process.argv[2]); + }catch(error){console.error(error.message);process.exitCode=1;} +} diff --git a/scripts/prepare-production-runtime.mjs b/scripts/prepare-production-runtime.mjs new file mode 100644 index 0000000..ff2eebb --- /dev/null +++ b/scripts/prepare-production-runtime.mjs @@ -0,0 +1,163 @@ +import { cp, lstat, mkdtemp, open, readFile, readdir, realpath, rm, writeFile } from 'node:fs/promises'; +import { basename, dirname, join, relative, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +const [arch = 'x64', libc = 'glibc', ...extra] = process.argv.slice(2); +if (!['x64', 'arm64'].includes(arch) || !['glibc', 'musl'].includes(libc) || extra.length) { + throw new Error('Usage: node scripts/prepare-production-runtime.mjs [x64|arm64] [glibc|musl]'); +} +const root = await realpath(resolve(dirname(fileURLToPath(import.meta.url)), '..')); +const dist = join(root, 'dist'); +const source = join(dist, 'production'); +const target = join(dist, `production-linux-${arch}-${libc}`); +if (dirname(target) !== dist || !/^production-linux-(x64|arm64)-(glibc|musl)$/.test(basename(target))) { + throw new Error('Runtime destination must be a fixed repository dist directory'); +} +for (const directory of [dist, source]) { + const info = await lstat(directory); + if (info.isSymbolicLink() || !info.isDirectory() || await realpath(directory) !== directory) { + throw new Error(`Unsafe build directory: ${directory}`); + } +} +const oldTarget = await lstat(target).catch((error) => { + if (error.code !== 'ENOENT') + throw error; + return null; +}); +if (oldTarget?.isSymbolicLink()) + throw new Error('Runtime destination cannot be a symbolic link'); +async function requireFile(base, name) { + const path = join(base, name); + const info = await lstat(path); + if (!info.isFile() || info.size === 0 || !(await realpath(path)).startsWith(base + '/')) { + throw new Error(`Missing, empty or unsafe runtime file: ${name}`); + } +} +const required = ['start.mjs', 'server.js', '.next/BUILD_ID', 'config/production.env']; +for (const file of required) + await requireFile(source, file); +for (const directory of ['.next/static', 'public']) { + if (!(await lstat(join(source, directory))).isDirectory()) + throw new Error(`Missing ${directory}`); +} +for (const file of ['package.json', 'package-lock.json']) + await requireFile(root, file); +const originalLock = JSON.parse(await readFile(join(root, 'package-lock.json'), 'utf8')); +for (const file of await matchingFiles(join(source, 'node_modules'), /^package\.json$/)) { + const locked = originalLock.packages[relative(source, dirname(file))]; + if (locked && JSON.parse(await readFile(file, 'utf8')).version !== locked.version) { + throw new Error(`Traced dependency does not match the root lockfile: ${file}`); + } +} +const npmVersion = spawnSync('npm', ['--version'], { cwd: root, encoding: 'utf8' }); +if (npmVersion.error || npmVersion.status !== 0) + throw new Error('Cannot read the current npm version'); +const version = npmVersion.stdout.trim(); +const match = /^(\d+)\.(\d+)\.(\d+)(?:$|[-+])/.exec(version); +const [major = 0, minor = 0, patch = 0] = match ? match.slice(1).map(Number) : []; +if (major < 10 || major === 10 && (minor < 9 || minor === 9 && patch < 2)) { + throw new Error(`npm >=10.9.2 is required for cross-platform installation; found ${version}`); +} +// Only this validated, fixed dist destination can be removed; source and host dependencies stay intact. +const sharpPlatform = `${libc === 'musl' ? 'linuxmusl' : 'linux'}-${arch}`; +const packages = [ + [`@img/sharp-${sharpPlatform}`, /\.node$/], + [`@img/sharp-libvips-${sharpPlatform}`, /libvips.*\.so(?:\.\d+)*$/], +]; +await rm(target, { recursive: true, force: true }); +await cp(source, target, { + recursive: true, + filter: async (path) => { + const name = basename(path); + if (name.startsWith('.env') || name.endsWith('.local.env') || + name.startsWith('._') || name === '.DS_Store' || name.endsWith('.log')) + return false; + const scope = basename(dirname(path)); + if (scope === '@next' && name.startsWith('swc-')) + return false; + if (scope === '@img' && name.startsWith('sharp-') && name !== 'sharp-wasm32' && + !packages.some(([packageName]) => packageName === `@img/${name}`)) + return false; + if ((await lstat(path)).isSymbolicLink()) + throw new Error(`Runtime source contains a symbolic link: ${path}`); + return true; + }, +}); +for (const file of ['package.json', 'package-lock.json']) + await cp(join(root, file), join(target, file)); +// Preserve Next's traced JS tree. Install only the two platform packages, never the full app dependency graph. +const nativeDir = await mkdtemp(join(dist, '.production-native-')); +try { + const optionalDependencies = {}, records = {}; + for (const [name] of packages) { + const entry = originalLock.packages[`node_modules/${name}`]; + if (!entry?.version || !entry.resolved || !entry.integrity) + throw new Error(`Missing locked native package: ${name}`); + optionalDependencies[name] = entry.version; + const record = { ...entry, optional: true }; + delete record.dev; + delete record.devOptional; + records[`node_modules/${name}`] = record; + } + const manifest = { name: '91dj-native-runtime', version: '1.0.0', private: true, optionalDependencies }; + await writeFile(join(nativeDir, 'package.json'), JSON.stringify(manifest)); + await writeFile(join(nativeDir, 'package-lock.json'), JSON.stringify({ name: manifest.name, + version: manifest.version, lockfileVersion: 3, requires: true, packages: { '': manifest, ...records } })); + console.log(`Installing two locked Linux ${arch}/${libc} native packages with npm ${version}`); + const install = spawnSync('npm', ['ci', '--omit=dev', '--include=optional', '--ignore-scripts', + '--no-audit', '--no-fund', '--os=linux', `--cpu=${arch}`, `--libc=${libc}`], { cwd: nativeDir, stdio: 'inherit' }); + if (install.error || install.status !== 0) + throw new Error(`Native npm ci failed (${install.status ?? install.error?.message})`); + for (const [name, pattern] of packages) { + const directory = join(nativeDir, 'node_modules', name); + const metadata = JSON.parse(await readFile(join(directory, 'package.json'), 'utf8')); + if (metadata.name !== name || metadata.version !== optionalDependencies[name] || + !metadata.os?.includes('linux') || !metadata.cpu?.includes(arch)) + throw new Error(`Incorrect native package: ${name}`); + const binaries = await matchingFiles(directory, pattern); + if (!binaries.length) + throw new Error(`Native runtime binary is missing: ${name}`); + for (const binary of binaries) + await verifyELF(binary); + await cp(directory, join(target, 'node_modules', name), { recursive: true }); + } +} +finally { + await rm(nativeDir, { recursive: true, force: true }); +} +for (const file of [...required, 'node_modules/dotenv/package.json', 'node_modules/sharp/package.json', + 'node_modules/detect-libc/package.json', 'node_modules/semver/package.json', 'node_modules/@img/colour/package.json']) { + await requireFile(target, file); +} +const runtimeRequire = createRequire(join(target, 'package.json')); +if (typeof runtimeRequire('dotenv').parse !== 'function') + throw new Error('Runtime dotenv is unavailable'); +async function matchingFiles(directory, pattern) { + const found = []; + for (const entry of await readdir(directory, { withFileTypes: true })) { + const path = join(directory, entry.name); + if (entry.isDirectory()) + found.push(...await matchingFiles(path, pattern)); + else if (entry.isFile() && pattern.test(entry.name)) + found.push(path); + } + return found; +} +async function verifyELF(path) { + const file = await open(path, 'r'); + const header = Buffer.alloc(64); + try { + await file.read(header, 0, header.length, 0); + } + finally { + await file.close(); + } + if (!header.subarray(0, 4).equals(Buffer.from([0x7f, 0x45, 0x4c, 0x46])) || + header[4] !== 2 || header[5] !== 1 || header.readUInt16LE(18) !== (arch === 'x64' ? 62 : 183)) { + throw new Error(`Native runtime is not a Linux ${arch} ELF binary: ${path}`); + } +} +// These public target details describe the package; environment values and credentials are never copied here. +await writeFile(join(target, 'runtime-target.json'), JSON.stringify({ platform: 'linux', arch, libc, packaging: 'standalone' }, null, 2) + '\n'); +console.log(`Linux runtime ready: ${target}`); diff --git a/scripts/smoke-api.mjs b/scripts/smoke-api.mjs new file mode 100644 index 0000000..e2c4cd1 --- /dev/null +++ b/scripts/smoke-api.mjs @@ -0,0 +1,88 @@ +// Read-only business smoke checks; POSTs only establish a guest or validate empty login input. +// Cookies stay in memory and are never printed or written to disk. +import assert from "node:assert/strict"; + +const base = process.env.PREVIEW_URL || "http://127.0.0.1:21021"; +const publicOnly = process.env.SMOKE_PUBLIC_ONLY === "1"; +const cookies = new Map(); +const cookieHeader=()=>[...cookies].map(([key,value])=>`${key}=${value}`).join('; '); +async function request(path, body) { + const response = await fetch(`${base}/api/${path}`, { + method: body === undefined ? "GET" : "POST", + headers: {Cookie: cookieHeader(), ...(body === undefined ? {} : {"Content-Type":"application/json"})}, + body: body === undefined ? undefined : JSON.stringify(body), + }); + for (const item of response.headers.getSetCookie()) { + const [pair] = item.split(";"); const index = pair.indexOf("="); + cookies.set(pair.slice(0,index), pair.slice(index+1)); + } + const envelope = await response.json(); + assert.equal(response.status,200,`${path}: HTTP ${response.status}`); + assert.equal(envelope.code,200,`${path}: ${envelope.msg || envelope.tip}`); + return envelope.data; +} + +const home = await request("playlet/home"); +const response = await fetch(base); +const html = await response.text(); +assert.equal(response.status,200); +assert.ok(!html.includes("data-must-watch-sort"),"首页筛选项应保持移除"); +if (!publicOnly) { + const guest = await request("auth/guest",{}); + assert.ok(guest.userId,"游客登录应返回用户 ID"); + assert.equal(guest.token,undefined,"token 不应暴露给浏览器脚本"); + assert.ok(cookies.has("drama_session"),"应通过 Cookie 保持会话"); + console.log("PASS: HttpOnly guest session"); +} +console.log("PASS: SSR homepage"); + +const checks = ["config/info", "config/footer/info", "navigation/header", "translation/category/list", "theme/list", "playlet/tag/options", "playlet/all/list?sortType=1&pageNum=1&pageSize=4", "playlet/hot/list?pageNum=1&pageSize=4", ...[1,2,3].map(rank=>`playlet/hot/rank/list?rankType=${rank}&pageNum=1&pageSize=4`), "playlet/topic/list?pageNum=1&pageSize=4", "applist/list?categoryId=0", "recharge/package/list", "playlet/hot-like", ...(!publicOnly?["auth/getInfo", "user/center/data", "playlet/history/list?pageNum=1&pageSize=4", "playlet/collect/list?pageNum=1&pageSize=4&watchStatus=0", "playlet/bought/list?pageNum=1&pageSize=4", "coin/trans/list?page=1&pageSize=10&type=0"]:[])]; +const dataByPath=new Map(); +for(let offset=0;offset{const data=await request(path);assert.ok(data,`${path}: missing data`);dataByPath.set(path.split('?')[0],data);console.log(`PASS: ${path.split("?")[0]}`);})); +const pages=['/shorts/','/shorts/page/2/','/rank/hot/','/rank/new/','/rank/follow/','/topics/','/apps/','/home-road/','/navigation/','/info/about-us/','/info/user-agreement/','/info/privacy-policy/','/info/help-center/','/info/2257/','/info/dmca/','/info/feedback/']; +const topic=dataByPath.get('playlet/topic/list')?.list?.[0]; +if(topic){ + const detail=await request(`playlet/topic/detail?id=${topic.id}&pageNum=1&pageSize=4`); + assert.equal(detail.id,topic.id);assert.equal(typeof detail.name,'string');assert.ok(Array.isArray(detail.list)); + pages.push(`/topics/${topic.id}/`,`/theme/${topic.id}/`); +} +const tag=dataByPath.get('playlet/tag/options')?.list?.[0]; +if(tag)pages.push(`/tag/${tag.id}/`); +const block=home.homeConfigData?.find(item=>item.status&&!item.moreUrl); +if(block)pages.push(`/config/${block.id}/`); +const first = home.homeBannerData?.[0] || home.homeConfigData?.find(block=>block.list?.length)?.list[0]; +if (first) { + const search=await request("playlet/search?"+new URLSearchParams({keyword:first.playName||first.title,pageNum:"1",pageSize:"4"})); + assert.ok(Array.isArray(search.list)); + pages.push('/search/'+encodeURIComponent(first.playName||first.title)+'/'); + const suggestions=await request('playlet/search/suggest?'+new URLSearchParams({keyword:first.playName||first.title}));assert.ok(Array.isArray(suggestions.list)); + if (!publicOnly) { + const detail=await request(`playlet/detail?id=${first.id}`); + assert.equal(detail.id,first.id); + const chapters=await request(`playlet/chapter/list?playletId=${first.id}`); + const related=await request(`playlet/guess-like/list?playletId=${first.id}&pageNum=1&pageSize=6`);assert.ok(Array.isArray(related.list)); + pages.push(`/detail/${first.id}/`); + if (chapters.list?.length) { + const chapter=await request(`playlet/chapter/detail?playletId=${first.id}&chapterId=${chapters.list[0].id}`); + assert.equal(chapter.id,chapters.list[0].id); + assert.equal(typeof chapter.canWatch,"boolean"); + pages.push(`/play/${first.id}/${chapter.id}/`); + } + console.log("PASS: drama detail and chapter permissions"); + } + console.log("PASS: search"); +} +if(!publicOnly)pages.push('/member/center/','/member/favorites/','/member/history/','/member/purchased/','/member/settings/','/info/deposit/','/info/deposit/vip/'); +for(let offset=0;offset{ + const response=await fetch(base+path,{headers:{Cookie:cookieHeader()},redirect:'manual'}),html=await response.text(); + assert.equal(response.status,200,path);assert.equal(response.headers.get('location'),null,path);assert.ok(!html.includes('内容暂不可用,请稍后重试'),path);assert.ok(!html.includes('class="api-loading"'),path); + const marker=path.startsWith('/member/settings/')?'member-settings':path.startsWith('/member/')?'member-center':path.startsWith('/info/deposit/')?'wallet-page':path==='/info/feedback/'?'id="feedback-form"':path==='/home-road/'?'return-home-directory':path.startsWith('/search/')?'data-search-results':''; + if(marker)assert.ok(html.includes(marker),path+' must contain the complete server-rendered page'); + console.log(`PASS: page ${path}`); +})); +const rejected=await fetch(`${base}/api/not-allowed`); +assert.equal(rejected.status,404); +const invalidLogin=await fetch(`${base}/api/auth/login`,{method:"POST",headers:{Origin:new URL(base).origin,"Sec-Fetch-Site":"same-origin","Content-Type":"application/json"},body:"{}"}); +assert.equal(invalidLogin.status,400); +assert.equal((await invalidLogin.json()).code,400); +console.log("PASS: proxy endpoint allowlist and browser login input validation"); diff --git a/src/app/[...path]/page.tsx b/src/app/[...path]/page.tsx new file mode 100644 index 0000000..d109a54 --- /dev/null +++ b/src/app/[...path]/page.tsx @@ -0,0 +1,35 @@ +import { notFound } from 'next/navigation'; +import type { Metadata } from 'next'; +import { routeFromSegments } from '@/lib/routing'; +import { getPageSeo, metadataFromHead, queryString } from '@/lib/seo-server'; +import { SiteFrame } from '@/components/site/SiteFrame'; +import { empty } from '@/lib/client/render'; +import type { PageView } from '@/lib/page-data'; +import { assertPageContent } from '@/lib/page-response'; + +export const dynamic = 'force-dynamic'; + +type PageProps={params:Promise<{path:string[]}>;searchParams:Promise>}; +const pathname=(path:string[])=>'/'+path.map(encodeURIComponent).join('/')+'/'; + +export async function generateMetadata({params,searchParams}:PageProps):Promise { + const {path}=await params; + if(!routeFromSegments(path))notFound(); + const result=await getPageSeo(pathname(path),queryString(await searchParams)); + assertPageContent(pathname(path),result.bootstrap.content); + return metadataFromHead(result.head); +} + +export default async function Page({params,searchParams}:PageProps) { + // Next 16.4 getDynamicParam encodes page props; generateMetadata receives decoded params. + // Decode at this boundary so both calls share the same URL, data and SEO cache entry. + const {path:encodedPath} = await params; + const path=encodedPath.map(decodeURIComponent); + const route=routeFromSegments(path); + if (!route) notFound(); + const {bootstrap,view,session,shared}=await getPageSeo(pathname(path),queryString(await searchParams)); + assertPageContent(pathname(path),bootstrap.content); + const title=bootstrap.content.title||({'apps':'福利应用','home-road':'回家的路',center:'个人中心',favorites:'我的收藏',history:'观看历史',likes:'我的点赞',purchased:'已购剧集',settings:'账号设置',deposit:'金币充值',feedback:'意见反馈'} as Record)[route.key]||'内容列表'; + const fallback:PageView={panel:'channel',title,html:empty(bootstrap.content.failed?'内容暂不可用,请稍后重试':'暂无内容',true)}; + return ; +} diff --git a/src/app/api/[...path]/route.ts b/src/app/api/[...path]/route.ts new file mode 100644 index 0000000..f08803e --- /dev/null +++ b/src/app/api/[...path]/route.ts @@ -0,0 +1,4 @@ +// The browser BFF stays separate from /api/app/*, which Nginx sends directly to Go. +export { GET, POST } from '../app/[...path]/route'; +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; diff --git a/src/app/api/app/[...path]/route.ts b/src/app/api/app/[...path]/route.ts new file mode 100644 index 0000000..55aa474 --- /dev/null +++ b/src/app/api/app/[...path]/route.ts @@ -0,0 +1,158 @@ +import { NextRequest, NextResponse } from "next/server"; +import { randomUUID } from "node:crypto"; +import { isIP } from "node:net"; +import { allowedRequest, PUBLIC_PATHS, upstream } from "@/lib/api/server"; +import { UPSTREAM_HOP_HEADER } from '@/lib/api/config'; +import { ApiError, EXPIRED_CODES } from "@/lib/api/protocol"; +import { trackingHeaders } from "@/lib/api/tracking-headers"; +import type { Envelope, User } from "@/lib/api/types"; + +export const runtime = "nodejs"; +export const dynamic = "force-dynamic"; +const AUTH = "drama_session"; +const DEVICE = "drama_device"; +const REGISTERED = "drama_registered"; +const CHANNEL = "drama_channel"; +const SKIP_AUTO = "drama_skip_auto"; +const CREDENTIALS = new Set(['auth/login','auth/register','auth/resetPassword']); +const text = (value: unknown) => typeof value === 'string' ? value.trim() : ''; +const deviceId = (value: unknown) => /^[a-zA-Z0-9_-]{1,100}$/.test(text(value)) ? text(value) : ''; +const expired = (code: number) => EXPIRED_CODES.has(code) || code === 401; + +function sessionUser(value: unknown, token = '', registered = false): User { + if (!value || typeof value !== 'object' || !Number.isSafeInteger((value as User).userId) || (value as User).userId <= 0) throw new ApiError('登录响应缺少用户信息,请稍后重试',502); + const user = value as User; + const sessionToken = text(user.token) || token; + if (!sessionToken) throw new ApiError('登录响应缺少凭证,请稍后重试',502); + return {...user, token:sessionToken, isRegister:user.isRegister || user.type === 1 || (user.isRegister === undefined && user.type === undefined && registered)}; +} + +async function handler(request: NextRequest, context: {params: Promise<{path: string[]}>}) { + const path = (await context.params).path.join('/'); + const method = request.method; + const headers = {'Cache-Control':'no-store'}; + if (request.headers.has(UPSTREAM_HOP_HEADER)) return NextResponse.json({code:502,msg:'上游请求返回本站,请检查 Nginx 的 /api/app/ 后端转发配置',data:null},{status:502,headers:{...headers,[UPSTREAM_HOP_HEADER]:'1'}}); + if (!allowedRequest(path,method) && path !== 'auth/logout') return NextResponse.json({code:404,msg:'接口不存在',data:null},{status:404,headers}); + const secure = request.nextUrl.protocol === 'https:'; + const cookieOptions = {httpOnly:true,sameSite:'lax' as const,secure,path:'/',maxAge:7*86400}; + const clearSession = (response: NextResponse) => { + response.cookies.set(AUTH,'',{...cookieOptions,maxAge:0}); + response.cookies.set(REGISTERED,'',{...cookieOptions,maxAge:0}); + }; + if (path === 'auth/logout') { + if (method !== 'POST') return NextResponse.json({code:405,data:null},{status:405,headers}); + const response = NextResponse.json({code:200,data:null},{headers}); + clearSession(response); + response.cookies.set(SKIP_AUTO,'1',{httpOnly:true,sameSite:'lax',secure,path:'/'}); + return response; + } + let token = PUBLIC_PATHS.has(path) ? '' : request.cookies.get(AUTH)?.value || ''; + const registered = request.cookies.get(REGISTERED)?.value === '1'; + let device = deviceId(request.cookies.get(DEVICE)?.value) || randomUUID(); + let channel = text(request.cookies.get(CHANNEL)?.value).slice(0,100); + const establishesDevice = ['auth/guest','auth/login','auth/register'].includes(path); + const userAgent = request.headers.get('user-agent') || ''; + const forwardedIp = (request.headers.get('x-real-ip') || request.headers.get('x-forwarded-for')?.split(',')[0] || '').trim(); + const clientIp = isIP(forwardedIp) ? forwardedIp : undefined; + const tracking=trackingHeaders(request.headers,path,method); + const identity=trackingHeaders(request.headers,'auth/guest','POST'); + const setContextCookies = (response: NextResponse) => { + if (establishesDevice) { + response.cookies.set(DEVICE,device,{...cookieOptions,httpOnly:false,maxAge:365*86400}); + if (channel) response.cookies.set(CHANNEL,channel,{...cookieOptions,httpOnly:false,maxAge:365*86400}); + } + }; + const guest = async () => { + const result = await upstream('auth/guest',{request,method:'POST',body:{devID:device,deviceId:device,channel,mac:''},device,userAgent,clientIp,tracking:identity}); + if (result.code !== 200) throw new ApiError(result.tip || result.msg || '游客登录失败',result.code); + return sessionUser(result.data); + }; + // Normalize HTTP 401 into the same recovery path as an expired business envelope. + const call = async (callPath: string, options: Parameters[1]): Promise> => { + try { return await upstream(callPath,{...options,request,clientIp,tracking:callPath.split('?')[0]===path?tracking:identity}); } + catch (error) { + if (error instanceof ApiError && error.code === 401) return {code:401,msg:error.message,data:null}; + throw error; + } + }; + try { + let body: unknown; + if (method === 'POST') { + const size = Number(request.headers.get('content-length') || 0); + if (size > 6*1024*1024) throw new ApiError('文件过大,请上传小于 5MB 的图片',413); + body = request.headers.get('content-type')?.includes('multipart/form-data') ? await request.formData() : await request.json().catch(()=>({})); + } + const input = body && typeof body === 'object' && !Array.isArray(body) && !(body instanceof FormData) ? body as Record : {}; + if (establishesDevice) { + device = deviceId(request.cookies.get(DEVICE)?.value) || deviceId(input.devID) || device; + channel ||= text(input.channel).slice(0,100) || text(process.env.API_CHANNEL).slice(0,100); + } + let session: User | undefined; + let result: Envelope; + if (path === 'auth/guest') { + if (!token && input.automatic === true && request.cookies.get(SKIP_AUTO)?.value === '1') { + const response = NextResponse.json({code:200,data:null},{headers}); + response.cookies.set(SKIP_AUTO,'',{...cookieOptions,maxAge:0}); + setContextCookies(response); + return response; + } + if (token) { + const info = await call('auth/getInfo',{token,device,userAgent}); + if (info.code === 200) session = sessionUser(info.data,token,registered); + else if (!expired(info.code) || registered) throw new ApiError(info.tip || info.msg || '登录已过期,请重新登录',info.code); + } + session ||= await guest(); + result = {code:200,data:session}; + } else { + if (path === 'auth/login' || path === 'auth/register') { + const account = text(input[path === 'auth/login' ? 'account' : 'accountNo']); + const password = typeof input.password === 'string' ? input.password : ''; + if (!account || !password) throw new ApiError('请填写账号和密码',400); + body = path === 'auth/login' + ? {account,password,devID:device,channel} + : {accountNo:account,password,email:text(input.email) || account+'@91dj.local',devID:device,channel,...(text(input.agentInviteCode) ? {agentInviteCode:text(input.agentInviteCode).slice(0,100)} : {})}; + // Only guest binding inherits the old identity; account switching does not. + if (path === 'auth/login' || registered) token = ''; + } else if (path === 'auth/resetPassword') { + body = {accountNo:text(input.accountNo),email:text(input.email),newPassword:input.newPassword}; + token = ''; + } + result = await call(path+request.nextUrl.search,{method,body,token,device,userAgent}); + // Retry only an explicitly rejected guest request, once; never downgrade registered actions. + if (expired(result.code) && !PUBLIC_PATHS.has(path) && !CREDENTIALS.has(path) && !registered) { + session = await guest(); + token = session.token!; + result = await call(path+request.nextUrl.search,{method,body,token,device,userAgent}); + } + if (result.code === 200 && ['auth/login','auth/register','auth/getInfo'].includes(path)) { + session = sessionUser(result.data,path === 'auth/getInfo' ? token : '',registered || CREDENTIALS.has(path)); + result = {...result,data:session}; + } + } + const data = result.data && typeof result.data === 'object' && 'token' in result.data ? {...result.data,token:undefined} : result.data; + const isExpired = expired(result.code) && !CREDENTIALS.has(path); + const response = NextResponse.json({...result,data},{status:isExpired ? 401 : 200,headers}); + if (isExpired) { + clearSession(response); + if (registered) response.headers.set('X-Auth-State','login-required'); + } else if (session?.token) { + response.cookies.set(AUTH,session.token,cookieOptions); + response.cookies.set(REGISTERED,session.isRegister || session.type === 1 ? '1' : '0',cookieOptions); + response.cookies.set(SKIP_AUTO,'',{...cookieOptions,maxAge:0}); + } + setContextCookies(response); + return response; + } catch (error) { + const code = error instanceof ApiError ? error.code : 502; + const isExpired = expired(code) && !CREDENTIALS.has(path); + const response = NextResponse.json({code,msg:error instanceof Error ? error.message : '请求失败',data:null},{status:isExpired ? 401 : code >= 400 && code <= 599 ? code : 502,headers}); + if (isExpired) { + clearSession(response); + if (registered) response.headers.set('X-Auth-State','login-required'); + } + setContextCookies(response); + return response; + } +} +export const GET = handler; +export const POST = handler; diff --git a/src/app/enkey/route.ts b/src/app/enkey/route.ts new file mode 100644 index 0000000..fb8fe80 --- /dev/null +++ b/src/app/enkey/route.ts @@ -0,0 +1,5 @@ +import { proxyMedia } from '@/lib/api/media'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; +export function GET(request: Request) {return proxyMedia(request,'/enkey',true);} diff --git a/src/app/error.tsx b/src/app/error.tsx new file mode 100644 index 0000000..7db3bab --- /dev/null +++ b/src/app/error.tsx @@ -0,0 +1,12 @@ +'use client'; + +export default function PageError({retry}:{retry:()=>void}) { + return
+ 页面暂不可用 - 91短剧 + +

页面暂不可用

+

内容服务暂时异常,请稍后重试

+ + 返回首页 +
; +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..63e1708 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,3160 @@ +/* Original reference styles, in their original cascade order. */ + +:root{--font-ui:"PingFang SC", "Microsoft YaHei", sans-serif} + +:root{--bg:#131414;--panel:#1d1e1e;--line:#ffffff13;--text:#f3f0e9;--muted:#92958f;--accent:#FFC000}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.5 var(--font-ui)}button,input{font:inherit}button,a{-webkit-tap-highlight-color:transparent}button{cursor:pointer;color:inherit;border:0;background:none}a{color:inherit;text-decoration:none}button:focus-visible,a:focus-visible,input:focus-visible{outline:2px solid var(--accent);outline-offset:5px}button:hover{color:var(--accent)}.side{width:108px;height:100vh;position:fixed;left:0;top:0;border-right:1px solid var(--line);display:flex;align-items:center;flex-direction:column;padding:30px 0;z-index:4;background:var(--bg)}.mark{height:46px;width:46px;background:var(--accent);color:#191919;display:grid;place-items:center;border-radius:14px;font-weight:950;font-size:24px;letter-spacing:-2px;transform:rotate(-7deg)}.side nav{display:grid;gap:26px;margin-top:65px}.side a{text-align:center;display:grid;gap:7px;color:#8f928e;font-size:12px}.side a svg{width:24px;height:24px;margin:auto}.side a.active{color:var(--accent)}.side-bottom{margin-top:auto;writing-mode:vertical-rl;letter-spacing:5px;color:#656965;font-size:12px}.shell{margin-left:108px;padding:0 44px;max-width:1920px}.top{height:106px;display:flex;align-items:center;gap:30px}.brand{font-size:26px;font-weight:900;letter-spacing:-1px;white-space:nowrap}.brand em{color:var(--accent);font-style:normal;margin-right:6px}.edition{font-size:12px;letter-spacing:3px;color:#92958f;border-left:1px solid #484a45;padding-left:20px;line-height:1.8}.search{margin-left:auto;display:flex;align-items:center;background:#202222;border:1px solid #33352f;border-radius:100px;width:310px;padding:10px 15px;gap:10px}.search input{background:none;border:0;outline:none;color:var(--text);width:100%;min-width:0;font-size:14px}.search svg{width:18px;height:18px}.saved{white-space:nowrap;color:#c0c2b9;font-size:14px}.avatar{width:38px;height:38px;background:#303329;border-radius:50%;display:grid;place-items:center;color:#d5d7ca}.nav{display:flex;align-items:center;gap:29px;border-bottom:1px solid var(--line);height:53px;margin-bottom:27px}.nav button{height:100%;position:relative;color:#a3a69f;font-size:14px}.nav button.active{color:var(--text);font-weight:700}.nav button.active:after{content:'';position:absolute;bottom:-1px;height:3px;background:var(--accent);left:0;right:0}.nav small{margin-left:auto;font-size:12px;letter-spacing:2px;color:#92958f}.hero-grid{display:grid;grid-template-columns:minmax(0,1fr) 290px;gap:24px}.hero{min-height:432px;position:relative;overflow:hidden;border-radius:8px;background:#283433}.art{background-image:url(/assets/fdd6fe924244b1f4.jpg);background-size:300% 200%;background-position:var(--pos)}.hero-art{position:absolute;right:0;top:0;width:66%;height:100%;background-size:300% 200%;opacity:.9}.hero:after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,#102127 0%,#102127ec 28%,#10212750 60%,#10212700 100%),linear-gradient(0deg,#0a141b77,transparent 50%);pointer-events:none}.hero-content{position:relative;z-index:1;padding:34px 38px;max-width:64%}.eyebrow{color:#cfdbd4;display:flex;align-items:center;gap:10px;font-size:12px;letter-spacing:2px}.eyebrow b{background:var(--accent);color:#1c1b19;letter-spacing:1px;padding:3px 7px;font-size:10px}.hero h2{font-family:"Songti SC","STSong",serif;font-size:clamp(44px,4.5vw,76px);font-weight:600;letter-spacing:6px;margin:37px 0 10px;line-height:1.2}.hero .english{letter-spacing:6px;color:#b5c3c5;font-size:12px}.meta{display:flex;flex-wrap:wrap;gap:12px;font-size:12px;color:#b5c2be;margin:19px 0 12px}.meta span:first-child{color:#FFCF40}.hero p{max-width:290px;line-height:1.9;font-size:14px;color:#c3cec8}.actions{display:flex;gap:10px;margin-top:22px}.primary{background:var(--accent);color:#1a1b17;border-radius:4px;font-weight:750;padding:12px 21px;display:inline-flex;align-items:center;justify-content:center;gap:9px}.primary:hover{background:#ff6829;color:#171713}.circle{background:#ffffff16;border:1px solid #ffffff26;width:44px;height:44px;border-radius:4px;font-size:26px}.hero-bottom{position:absolute;bottom:27px;right:28px;z-index:2;display:flex;align-items:center;gap:12px}.hero-bottom button{width:32px;height:32px;border:1px solid #ffffff55;border-radius:50%}.hero-bottom span{font-size:12px;font-variant-numeric:tabular-nums}.dots{display:flex;gap:5px;margin-right:9px}.dots i{width:5px;height:5px;background:#ffffff55;border-radius:10px}.dots i.on{width:23px;background:var(--accent)}.chart{border-top:2px solid var(--accent);padding:17px 0 0}.chart-head{display:flex;align-items:center;justify-content:space-between}.chart-head h2{font-size:20px;margin:0}.chart-head small{font-size:10px;letter-spacing:2px;color:var(--accent)}.chart-sub{color:var(--muted);font-size:12px;margin:5px 0 20px}.rank{display:flex;width:100%;align-items:center;gap:13px;padding:11px 0;text-align:left;border-bottom:1px solid var(--line)}.ranknum{font-family:Georgia,serif;font-style:italic;font-size:26px;color:#72766b;width:28px}.rank:first-child .ranknum{color:var(--accent)}.thumb{width:48px;height:61px;border-radius:3px;flex-shrink:0}.rank strong{font-size:14px;font-weight:600;display:block}.rank p{font-size:12px;color:var(--muted);margin:4px 0 0}.rank .rise{margin-left:auto;color:var(--accent);font-size:12px}.bar{display:flex;align-items:center;gap:15px;padding:27px 0;border-bottom:1px solid var(--line);flex-wrap:wrap}.bar-title{font-size:12px;color:var(--muted);margin-right:10px}.chip{padding:6px 15px;border:1px solid #34372f;border-radius:30px;color:#b5b8ad;font-size:12px}.chip.active{color:#191c15;background:#e4e6d9;border-color:#e4e6d9}.bar .total{margin-left:auto;font-size:12px;color:var(--muted)}.section-head{display:flex;align-items:end;justify-content:space-between;margin:31px 0 19px}.section-head h2{font-size:24px;letter-spacing:1px;margin:0}.section-head h2 small{font-size:10px;font-weight:400;letter-spacing:2px;color:var(--muted);margin-left:15px}.section-head p{font-size:12px;color:var(--muted);margin:6px 0 0}.section-head button{font-size:12px;color:#b6b9af}.cards{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:18px}.card{text-align:left;padding:0;min-width:0}.cover{aspect-ratio:3/4;position:relative;border-radius:5px;overflow:hidden;transition:transform .3s}.cover:after{content:'';position:absolute;inset:40% 0 0;background:linear-gradient(transparent,#000b)}.card:hover .cover{transform:translateY(-5px)}.badge{position:absolute;top:10px;left:10px;font-size:10px;padding:3px 6px;background:#1519149e;backdrop-filter:blur(9px);border:1px solid #ffffff40;z-index:1}.poster-name{position:absolute;bottom:42px;left:15px;right:10px;font-family:"Songti SC",serif;font-size:28px;line-height:1.2;letter-spacing:3px;z-index:1;text-shadow:0 2px 8px #000}.cover-foot{position:absolute;left:12px;right:12px;bottom:12px;display:flex;justify-content:space-between;z-index:1;font-size:10px;color:#e9e8de}.cover-foot b{color:#FFCF40;font-weight:400}.card :is(h2,h3){font-size:14px;font-weight:600;margin:11px 0 4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.card>p{color:#8f948a;font-size:12px;margin:0}.themes{display:grid;grid-template-columns:1.2fr 1fr;gap:22px;margin:36px 0}.theme{position:relative;min-height:225px;background:#dce0cf;color:#22281f;overflow:hidden;border-radius:5px;text-align:left;padding:28px 32px}.theme small{font-size:10px;letter-spacing:3px}.theme h2{font-size:32px;line-height:1.3;margin:18px 0 10px;font-family:"Songti SC",serif;position:relative;z-index:1}.theme p{font-size:12px;position:relative;z-index:1}.theme .art{position:absolute;right:-20px;top:-10px;width:240px;height:260px;transform:rotate(12deg);opacity:.92;mask-image:linear-gradient(90deg,transparent,#000 45%)}.theme.dark{background:#302820;color:#ecd4b8}.theme:hover{color:inherit}.empty{padding:45px;border:1px dashed #4b5046;color:var(--muted);grid-column:1/-1;text-align:center}.footer{display:flex;justify-content:space-between;padding:26px 0 35px;border-top:1px solid var(--line);color:#71796d;font-size:12px}.footer strong{color:#b2b9a9;font-size:14px}dialog{background:#1b201e;color:var(--text);border:1px solid #494d41;border-radius:12px;width:min(760px,92vw);padding:0}dialog::backdrop{background:#000b;backdrop-filter:blur(10px)}.dialog-grid{display:grid;grid-template-columns:40% 60%}.dialog-art{min-height:400px}.dialog-body{padding:32px}.dialog-body h2{font:36px "Songti SC",serif}.dialog-body p{color:#a9b4a7;font-size:14px;line-height:1.9}.close{position:absolute;right:12px;top:8px;font-size:26px}.episodes{display:flex;gap:7px;margin:20px 0;flex-wrap:wrap}.episodes button{border:1px solid #4a4f42;border-radius:4px;width:35px;height:33px}.episodes button.active{background:var(--accent);color:#111}.preview-note{font-size:12px!important}.toast{position:fixed;bottom:30px;left:50%;transform:translateX(-50%);padding:13px 24px;background:#e4e6d9;color:#171b15;border-radius:5px;z-index:20;box-shadow:0 6px 40px #0006}.hidden{display:none}svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}.fade{animation:fade .45s ease}@keyframes fade{from{opacity:.5;transform:translateY(5px)}to{opacity:1;transform:none}}@media(min-width:1600px){.hero{min-height:500px}.hero-content{padding:45px}.hero h2{margin-top:60px}.chart .rank{padding:15px 0}.thumb{height:70px;width:55px}}@media(max-width:1180px){.shell{padding:0 26px}.side{width:78px}.shell{margin-left:78px}.edition{display:none}.hero-grid{grid-template-columns:minmax(0,1fr) 230px}.hero-content{padding:30px;max-width:78%}.cards{grid-template-columns:repeat(3,1fr)}.cover{aspect-ratio:3/4}.chart-head h2{font-size:18px}.themes .art{opacity:.5}}@media(max-width:760px){.side{display:none}.shell{margin:0;padding:0 19px}.top{height:82px;gap:15px}.brand{font-size:24px}.saved,.avatar{display:none}.search{width:160px;padding:8px}.nav{gap:25px;margin-bottom:19px;overflow:auto;white-space:nowrap}.nav small{display:none}.hero-grid{display:block}.hero{min-height:415px}.hero-art{width:90%;opacity:.65}.hero-content{padding:26px;max-width:95%}.hero h2{font-size:52px;margin-top:40px}.chart{margin-top:25px}.chart-sub{margin-bottom:10px}.rank{padding:10px 0}.rank:nth-child(n+4){display:none}.bar{gap:8px}.bar-title,.total{display:none}.chip{padding:6px 13px}.cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 12px}.cover{aspect-ratio:3/4}.themes{grid-template-columns:1fr}.section-head h2 small{display:none}.section-head h2{font-size:22px}.theme{min-height:210px}.footer{gap:20px}.dialog-grid{grid-template-columns:1fr}.dialog-art{min-height:190px;max-height:190px}.dialog-body{padding:22px}.dialog-body h2{margin:10px 0}.dialog-body p{margin:10px 0}.hero-bottom{bottom:18px;right:18px}}@media(prefers-reduced-motion:reduce){*{scroll-behavior:auto!important;animation:none!important;transition:none!important}} + + + +.top{border-bottom:1px solid var(--line)} +.top .nav{align-self:stretch;height:auto;margin:0 0 0 18px;border:0;flex:0 0 auto} +.top .nav button{height:100%;white-space:nowrap} +.top .nav button.active:after{bottom:0} +@media(max-width:1180px){.top{gap:18px}.top .nav{gap:18px;margin-left:4px}.top .search{width:260px}} +@media(max-width:760px){.top{height:auto;min-height:0;padding:18px 0 0;flex-wrap:wrap;gap:14px}.top .brand{order:1}.top .search{order:2;margin-left:auto}.top .nav{order:3;width:100%;height:48px;margin:0;overflow-x:auto;gap:26px}.top .nav button{flex:0 0 auto}.top .nav::-webkit-scrollbar{display:none}} + +/* Two cover formats: portrait lists and landscape collections. */ +:root{--portrait:3/4;--landscape:16/9} +.cover{aspect-ratio:var(--portrait)} +.cover .art,.media .art{position:absolute;inset:auto;width:auto;height:100%;aspect-ratio:1;left:50%;top:0;transform:translateX(-50%);background-size:300% 200%;opacity:1;mask-image:none} +.media{position:relative;display:block;overflow:hidden;border-radius:8px;isolation:isolate} +.media.portrait{aspect-ratio:var(--portrait)} +.media.landscape{aspect-ratio:var(--landscape)} +.media.landscape .art{width:100%;height:auto;left:0;top:50%;transform:translateY(-40%)} +.section-head{align-items:center;margin:32px 0 20px} +.section-head h2{font-size:24px;line-height:1.35;letter-spacing:.5px} +.section-head button{display:flex;gap:12px;align-items:center;padding:6px 0;font-size:12px} +.themes{display:flex;flex-direction:column;gap:46px;margin:42px 0 48px} +.topic-section{min-width:0} +.topic-section .section-head{margin:0 0 20px} +.collection-pair{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:26px} +.collection-cover{padding:0;width:100%;display:block;text-align:left} +.collection-cover .media:after{content:'';position:absolute;inset:0;background:linear-gradient(0deg,#07100ced 0,transparent 75%);z-index:1} +.collection-cover .media{border:1px solid #ffffff15} +.collection-cover .art{transition:filter .3s} +.collection-cover:hover .art{filter:brightness(1.14)} +.collection-name{position:absolute;left:28px;bottom:24px;z-index:2;color:#fff;display:grid;gap:12px;pointer-events:none} +.collection-name strong{font:500 clamp(24px,2.8vw,42px)/1.2 "Songti SC","STSong",serif;letter-spacing:2px} +.collection-name small{font:12px/1.4 var(--font-ui);color:#ddd;letter-spacing:1px} +.collection-arrow{position:absolute;right:24px;bottom:24px;z-index:2;width:38px;height:38px;display:grid;place-items:center;border:1px solid #ffffff60;border-radius:50%;color:#fff;font-size:20px} +.topic-meta{display:flex;gap:14px;align-items:center;color:#92958f;font-size:12px} +.topic-meta .kind{border:1px solid #ffffff20;padding:3px 9px;border-radius:20px;color:#c8c8be} +.ranked-shelf{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:25px;padding:27px 28px 23px;background:#1a1d1e;border:1px solid #ffffff0e;border-radius:12px} +.ranked-card{display:block;min-width:0;text-align:left;padding:0} +.ranked-card .media{border-radius:6px} +.ranked-card .media:after{content:'';position:absolute;inset:50% 0 0;background:linear-gradient(transparent,#070c12d9);z-index:1} +.ranked-card .badge{left:12px;top:12px;z-index:2} +.ranked-card .cover-foot{z-index:2;font-size:12px} +.rank-caption{display:flex;gap:13px;align-items:center;margin-top:16px} +.rank-caption>strong{font:italic 36px/.95 Georgia,serif;color:#858c92;letter-spacing:-2px} +.ranked-card:first-child .rank-caption>strong{color:var(--accent)} +.rank-caption h3{font-size:18px;margin:0 0 4px;font-weight:600} +.rank-caption small{font-size:12px;color:var(--muted)} +.ranked-card:hover h3{color:var(--accent)} +.media-play{position:absolute;z-index:3;left:50%;top:50%;width:48px;height:48px;display:grid;place-items:center;border-radius:50%;background:#111a;color:white;border:1px solid #fff8;transform:translate(-50%,-40%);opacity:0;transition:opacity .2s,transform .2s} +.ranked-card:hover .media-play,.ranked-card:focus-visible .media-play{opacity:1;transform:translate(-50%,-50%)} +.romance-showcase{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(240px,1fr);gap:0;background:#262022;border:1px solid #ffffff13;border-radius:10px;overflow:hidden} +.romance-feature{min-width:0;padding:0;align-self:start;display:block;text-align:left;position:relative} +.romance-feature .media{border-radius:0} +.romance-feature .media:after{content:'';position:absolute;inset:0;background:linear-gradient(0deg,#211618cb,transparent 65%);z-index:1} +.romance-feature-name{position:absolute;left:30px;bottom:26px;color:#fff1e7;z-index:2;font:500 clamp(24px,2.6vw,38px)/1.3 "Songti SC","STSong",serif;letter-spacing:2px} +.romance-feature-play{position:absolute;right:28px;bottom:26px;z-index:2;border:1px solid #ffffff80;width:40px;height:40px;border-radius:50%;display:grid;place-items:center;color:#fff} +.romance-menu{padding:clamp(18px,2.5vw,38px);display:flex;flex-direction:column;min-width:0} +.romance-choices{display:flex;flex-direction:column;flex:1;justify-content:center} +.romance-choice{padding:22px 0;display:grid;grid-template-columns:1fr auto;gap:7px 16px;align-items:center;text-align:left;border-bottom:1px solid #ffffff13;transition:color .2s;color:#938b8b} +.romance-choice:last-child{border-bottom:0} +.romance-choice .choice-genre{font-size:10px;letter-spacing:2px;color:inherit;grid-column:1} +.romance-choice strong{font:500 clamp(22px,2vw,30px)/1.3 "Songti SC","STSong",serif;color:inherit;grid-column:1} +.romance-choice .choice-dot{grid-column:2;grid-row:1/3;width:8px;height:8px;border:1px solid #948889;border-radius:50%;transition:background .2s} +.romance-choice[aria-pressed="true"]{color:#f9c5de} +.romance-choice[aria-pressed="true"] .choice-dot{background:#FFCF40;border-color:#FFCF40;box-shadow:0 0 0 5px #ff9a7011} +.romance-choice:hover{color:#f9c5de} +.romance-visit{display:flex;align-items:center;justify-content:space-between;gap:12px;border-top:1px solid #ffffff25;margin-top:16px;padding:18px 0 0;color:#f9c5de;font-size:12px} +.romance-visit span{font-size:20px} +@media(max-width:900px){.romance-menu{padding:18px 24px}.romance-choice{padding:14px 0}.romance-choice strong{font-size:22px}.romance-visit{margin-top:10px;padding-top:12px}} +@media(max-width:760px){.romance-showcase{grid-template-columns:1fr}.romance-menu{padding:0 22px 20px}.romance-choices{flex-direction:row;gap:24px}.romance-choice{flex:1;border-bottom:0;padding:22px 0 16px;gap:6px 10px}.romance-choice strong{font-size:22px}.romance-choice[aria-pressed="true"]{border-bottom:2px solid #FFCF40}.romance-visit{margin-top:4px}.romance-feature-name{left:22px;bottom:20px}.romance-feature-play{right:22px;bottom:20px}} +.curated-pairs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:26px} +.curated-card{padding:20px;display:grid;grid-template-columns:minmax(0,1fr) minmax(0,.9fr);gap:25px;align-items:center;text-align:left;background:#20251f;border:1px solid #ffffff15;border-radius:10px} +.curated-card:nth-child(2){background:#25211f} +.curated-card .media{width:100%;min-width:0;border-radius:5px} +.curated-copy{display:flex;flex-direction:column;align-items:flex-start;color:#eeeade;min-width:0} +.curated-copy .pick{font-size:12px;letter-spacing:2px;color:#b5bfa4;border-bottom:1px solid #b5bfa44a;padding-bottom:10px;margin-bottom:27px} +.curated-copy h3{font:500 30px/1.35 "Songti SC","STSong",serif;margin:0 0 16px;letter-spacing:1px} +.tags{display:flex;flex-wrap:wrap;gap:7px}.tags span{border:1px solid #ffffff1f;border-radius:3px;font-size:10px;padding:3px 7px;color:#b2b7a9} +.curated-copy .heat{font-size:12px;color:#FFCF40;margin:20px 0 5px} +.curated-copy .episode-count{font-size:12px;color:#979c91} +.curated-copy .watch-link{font-size:12px;margin-top:27px;display:flex;gap:18px;align-items:center;color:#f0eddf} +.curated-card:hover .watch-link{color:var(--accent)} +.card>p{display:flex;gap:7px;margin-top:7px} +.card>p span{font-size:10px;border-radius:3px;background:#ffffff08;border:1px solid #ffffff0c;padding:2px 6px} +.collection-dialog{padding:30px} +.collection-dialog .section-head{margin:0 0 24px} +.collection-dialog .cards{grid-template-columns:repeat(3,minmax(0,1fr))} +.collection-dialog .poster-name{font-size:26px} +.thumb{height:auto!important;aspect-ratio:var(--portrait)} +@media(max-width:1180px){.ranked-shelf{gap:16px;padding:20px}.rank-caption{gap:9px}.rank-caption h3{font-size:14px}.curated-card{gap:17px;padding:16px}.curated-copy h3{font-size:24px}.curated-copy .pick{margin-bottom:17px}.curated-copy .watch-link{margin-top:18px}.collection-name{left:20px;bottom:20px}} +@media(max-width:760px){.themes{gap:36px;margin-top:34px}.section-head h2{font-size:22px}.collection-pair,.romance-collections,.curated-pairs{grid-template-columns:1fr;gap:24px}.topic-meta .kind{display:none}.ranked-shelf{grid-template-columns:repeat(2,minmax(0,1fr));padding:14px;gap:23px 13px}.rank-caption>strong{font-size:28px}.rank-caption h3{font-size:14px}.rank-caption small{font-size:10px}.collection-name strong{font-size:30px}.romance-caption{padding:17px 20px}.curated-card{padding:16px;gap:22px}.curated-copy h3{font-size:26px}.curated-copy .pick{margin-bottom:20px}.collection-dialog{padding:24px 18px}.collection-dialog .cards{grid-template-columns:repeat(2,minmax(0,1fr))}.footer{flex-wrap:wrap}} +.curated-pairs{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px} +.curated-card{grid-template-columns:190px minmax(0,1fr);gap:28px;padding:20px} +.curated-card:nth-child(3){background:#1c2221} +.curated-card:nth-child(4){background:#211f24} +.curated-copy{display:grid;grid-template-columns:minmax(0,1fr) auto;grid-template-areas:"pick heat" "title episodes" "tags action";grid-template-rows:auto auto 1fr;gap:20px 30px;align-self:stretch;padding:24px 20px 24px 0;align-items:start} +.curated-copy .pick{grid-area:pick;margin:0;width:max-content;padding-bottom:9px} +.curated-copy h3{grid-area:title;font-size:36px;margin:0} +.curated-copy .tags{grid-area:tags} +.curated-copy .heat{grid-area:heat;margin:0;font-size:18px;text-align:right} +.curated-copy .episode-count{grid-area:episodes;text-align:right;align-self:center;font-size:14px} +.curated-copy .watch-link{grid-area:action;align-self:end;margin:0;border:1px solid #ffffff30;border-radius:4px;padding:12px 20px;gap:28px} +@media(max-width:1000px){.curated-card{grid-template-columns:150px minmax(0,1fr);gap:20px}.curated-copy{padding:10px 4px 10px 0;gap:13px}.curated-copy h3{font-size:26px}.curated-copy .heat{font-size:12px}.curated-copy .watch-link{padding:9px 12px;gap:14px}} +@media(max-width:760px){.curated-pairs{grid-template-columns:1fr}.curated-card{grid-template-columns:36% minmax(0,1fr);gap:20px;padding:16px}.curated-copy{display:flex;padding:0;gap:0;align-self:center}.curated-copy .pick{margin-bottom:14px;font-size:10px}.curated-copy h3{font-size:24px;margin-bottom:12px}.curated-copy .heat{font-size:12px;margin-top:14px}.curated-copy .episode-count{align-self:auto;text-align:left;font-size:12px;margin-top:3px}.curated-copy .watch-link{align-self:auto;margin-top:17px;padding:0;border:0;font-size:12px;gap:15px}} +.romance-editorial{overflow:hidden}.romance-editorial>.section-head{margin-bottom:6px}.editorial-row{display:grid;grid-template-columns:minmax(0,3fr) minmax(260px,1fr);gap:54px;align-items:center;padding:38px 0 46px;border-bottom:1px solid #ffffff30}.editorial-row.reverse{grid-template-columns:minmax(260px,1fr) minmax(0,3fr)}.editorial-row.reverse .editorial-posters{order:2}.editorial-row.reverse .editorial-copy{order:1}.editorial-posters{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:22px}.editorial-card{text-align:left;min-width:0}.editorial-card .media{border-radius:2px;overflow:hidden;transition:transform .25s,filter .25s}.editorial-card:hover .media{transform:translateY(-7px);filter:brightness(1.08)}.editorial-card strong{display:block;font-size:14px;margin:12px 0 2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.editorial-card small{display:block;color:#737872;font-size:10px}.editorial-copy{position:relative;min-height:270px;display:flex;flex-direction:column;justify-content:center;align-items:flex-start;padding:24px 8px}.editorial-copy .editorial-no{position:absolute;right:0;top:18px;font:italic 32px/1 Georgia,serif;color:#b7bab2}.editorial-copy>p{margin:0 0 15px;color:var(--accent);font-size:10px;letter-spacing:3px}.editorial-copy h3{font:500 clamp(32px,3vw,52px)/1.05 "Songti SC","STSong",serif;letter-spacing:2px;margin:0 0 16px}.editorial-copy>small{color:#858a83;font-size:12px}.editorial-copy button{margin-top:auto;border-bottom:2px solid var(--accent);padding:12px 0 8px;font-size:12px;letter-spacing:1px}.editorial-copy button span{margin-left:14px}.editorial-copy i{position:absolute;right:0;top:50px;width:42px;height:2px;background:var(--accent)}.editorial-row.reverse .editorial-copy .editorial-no,.editorial-row.reverse .editorial-copy i{right:auto;left:0}.editorial-row.reverse .editorial-copy{padding-left:0}.editorial-row.reverse .editorial-copy>p,.editorial-row.reverse .editorial-copy h3,.editorial-row.reverse .editorial-copy>small,.editorial-row.reverse .editorial-copy button{margin-left:56px} +@media(max-width:1000px){.editorial-row,.editorial-row.reverse{grid-template-columns:1fr;gap:20px}.editorial-row.reverse .editorial-posters,.editorial-row.reverse .editorial-copy{order:initial}.editorial-copy{min-height:160px;padding:10px 0 15px}.editorial-copy h3{font-size:36px}.editorial-copy button{margin-top:28px}.editorial-row.reverse .editorial-copy>p,.editorial-row.reverse .editorial-copy h3,.editorial-row.reverse .editorial-copy>small,.editorial-row.reverse .editorial-copy button{margin-left:0}} +@media(max-width:760px){.editorial-row{padding:24px 0 32px}.editorial-posters{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}.editorial-card strong{font-size:12px}.editorial-copy{min-height:150px}.editorial-copy h3{font-size:32px}.editorial-copy i{display:none}} +.cinematic-love{min-height:520px;position:relative;overflow:hidden;border-radius:12px;background:#d8ded5;color:#161914}.cinematic-love>.art{position:absolute;inset:0;background-size:300% 200%;background-position:var(--pos);filter:saturate(.88) contrast(1.03)}.cinematic-love:before{content:'';position:absolute;z-index:1;inset:0;background:linear-gradient(90deg,#090b0bf2 0%,#101312db 28%,#1013124d 58%,#e7ebe2d9 100%),linear-gradient(0deg,#1118,transparent 55%)}.cinematic-copy{position:relative;z-index:2;width:42%;min-height:520px;padding:62px 52px;display:flex;flex-direction:column;align-items:flex-start}.cinematic-copy .cinema-kicker{font-size:10px;letter-spacing:4px;color:#FFCF40}.cinematic-copy h3{font:500 clamp(44px,5vw,74px)/1.08 "Songti SC","STSong",serif;letter-spacing:5px;margin:24px 0 16px;color:#fff7ed}.cinema-rating{display:flex;align-items:center;gap:14px;color:#FFCF40;font-size:18px}.cinema-rating small{font-size:12px;color:#d3d4ce;letter-spacing:1px}.cinematic-copy>p{max-width:390px;margin:22px 0 0;color:#b8bdb6;font-size:14px;line-height:1.9}.cinema-actions{display:flex;align-items:center;gap:13px;margin-top:auto}.cinema-play{min-width:148px;background:var(--accent);color:#171916;border-radius:30px;padding:13px 25px;font-weight:700}.cinema-play:hover{background:#ff6829;color:#111}.cinema-save{width:42px;height:42px;border:1px solid #ffffff80;border-radius:50%;color:#fff;font-size:24px}.cinema-scenes{position:absolute;z-index:3;right:38px;top:50%;transform:translateY(-50%);display:grid;gap:14px;width:190px}.cinema-scene{position:relative;display:block;width:100%;border:2px solid #ffffff80;border-radius:14px;overflow:hidden;box-shadow:0 8px 25px #0005}.cinema-scene .media{border-radius:0}.cinema-scene:after{content:'▶';position:absolute;inset:0;display:grid;place-items:center;color:white;background:#0001;opacity:0;transition:opacity .2s}.cinema-scene:hover:after{opacity:1}.cinema-scene:hover{border-color:var(--accent)} +@media(max-width:1000px){.cinematic-love,.cinematic-copy{min-height:450px}.cinematic-copy{width:56%;padding:45px 36px}.cinema-scenes{width:145px;right:24px}.cinematic-copy h3{font-size:52px}} +@media(max-width:760px){.cinematic-love{min-height:620px}.cinematic-love:before{background:linear-gradient(0deg,#090b0bf8 0%,#101312d8 50%,#10131233 100%)}.cinematic-copy{width:100%;min-height:620px;padding:245px 24px 24px;justify-content:flex-end}.cinematic-copy h3{font-size:40px;margin:14px 0 8px}.cinematic-copy>p{font-size:12px;margin-top:12px;line-height:1.65}.cinema-actions{margin-top:20px}.cinema-scenes{right:18px;left:18px;top:18px;transform:none;width:auto;grid-template-columns:repeat(3,1fr);gap:8px}.cinema-scene{border-radius:8px}} +.hero-grid{grid-template-columns:1fr}.hero{min-height:610px;border-radius:24px}.hero-art{width:100%;background-size:300% 200%}.hero:after{background:linear-gradient(90deg,#071317f2 0%,#071317c9 26%,#07131738 58%,transparent 78%),linear-gradient(0deg,#061012b8 0%,transparent 45%)}.hero-content{padding:45px 48px;max-width:44%}.hero-content h2{font-size:clamp(58px,6vw,92px);margin:54px 0 8px}.hero-content>p{margin-top:22px}.hero-rating{margin-top:20px}.hero-rating span:first-child{font-size:18px;letter-spacing:2px;color:#FFCF40}.hero-add{width:44px;height:44px;border:1px solid #ffffff88;border-radius:50%;font-size:24px;color:#fff}.hero-previews{position:absolute;z-index:3;right:115px;bottom:24px;width:min(560px,47%);display:grid;grid-template-columns:repeat(5,1fr);gap:9px}.hero-preview{aspect-ratio:1.7;border-radius:7px;overflow:hidden;border:2px solid transparent;opacity:.62;transition:opacity .2s,transform .2s,border-color .2s}.hero-preview .art{display:block;width:100%;height:100%;background-size:300% 200%;background-position:var(--pos)}.hero-preview:hover{opacity:1;transform:translateY(-4px)}.hero-preview.active{opacity:1;border-color:#fff;box-shadow:none}.hero-bottom{bottom:31px}.chart{display:none} +@media(max-width:1000px){.hero{min-height:530px}.hero-content{max-width:58%;padding:38px}.hero-content h2{font-size:60px;margin-top:35px}.hero-previews{right:90px;width:48%}} +@media(max-width:760px){.hero{min-height:600px}.hero-content{max-width:100%;padding:28px}.hero-content h2{font-size:50px;margin-top:34px}.hero-previews{left:20px;right:20px;bottom:70px;width:auto;gap:6px}.hero-bottom{bottom:22px}.hero-preview:nth-child(n+5){display:none}} +.cinematic-love,.cinematic-copy{min-height:650px}.hot-image{position:absolute;inset:0;display:block;background-image:var(--image);background-size:cover;background-position:center}.hot-main{z-index:0}.cinema-scene .hot-image{background-size:cover;background-position:center}.cinematic-copy{padding-top:78px;padding-bottom:62px}.cinema-scenes{width:210px}.cinema-scene .media{aspect-ratio:1.8} +@media(max-width:1000px){.cinematic-love,.cinematic-copy{min-height:560px}} +@media(max-width:760px){.cinematic-love,.cinematic-copy{min-height:650px}.cinematic-copy{padding-top:255px}} + + + +/* Floating sidebar: 16px outer spacing, 20px corners. */ +@media (min-width:761px){ + .side{ + left:16px; + top:16px; + bottom:16px; + height:calc(100vh - 32px); + height:calc(100dvh - 32px); + border:1px solid var(--line); + border-radius:20px; + background:var(--panel); + box-shadow:0 8px 24px rgba(0,0,0,.18); + padding:24px 0; + overflow-y:auto; + scrollbar-width:thin; + } + .side .mark{flex-shrink:0} + .side nav{width:100%;gap:12px;margin-top:42px;padding:0 10px} + .side nav a{position:relative;border-radius:12px;padding:12px 2px;transition:background .2s,color .2s} + .side nav a:hover{background:#ffffff06;color:var(--text)} + .side nav a.active{background:#FFC00010;color:var(--accent)} + .side nav a.active:before{content:'';position:absolute;left:0;top:50%;width:3px;height:22px;border-radius:3px;background:var(--accent);transform:translateY(-50%)} + .side-bottom{padding-top:28px;flex-shrink:0} + .shell{margin-left:140px;padding-left:28px;padding-right:28px} +} +@media (min-width:761px) and (max-width:1180px){ + .shell{margin-left:110px;padding-left:26px;padding-right:26px} + .side nav{padding:0 6px} + .side nav a{font-size:12px} +} + + + +/* Platform information and rule pages. */ +[hidden]{display:none!important} +.side-info{margin-top:auto;width:100%;padding:24px 20px 0;flex-shrink:0;color:var(--muted)} +.side-info>strong{display:block;border-top:1px solid var(--line);padding-top:20px;color:var(--text);font-size:18px;letter-spacing:-.5px} +.side-info>strong span{color:var(--accent)} +.side-info p{font-size:12px;line-height:1.9;margin:10px 0 16px} +.policy-links{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px 12px} +.side .policy-links a,.policy-links a{display:block;text-align:left;font-size:12px;color:#a8aaa4;line-height:1.6;border-radius:4px} +.policy-links a:hover,.policy-links a[aria-current="page"]{color:var(--accent)} +.side-info>small{display:block;font-size:10px;color:#737871;line-height:1.8;margin-top:20px} +.footer{align-items:center;gap:24px}.footer p{margin:7px 0}.footer .policy-links{grid-template-columns:repeat(3,auto);gap:12px 24px} +.info-shell{max-width:1920px;min-height:100vh;padding-top:32px!important;padding-bottom:40px!important} +.info-topbar{display:flex;justify-content:space-between;align-items:center;gap:16px;border-bottom:1px solid var(--line);padding:0 0 25px} +.info-topbar a{font-size:14px;color:#b4b7b0}.info-topbar a:hover{color:var(--accent)} +.info-topbar span{font-size:12px;letter-spacing:2px;color:var(--muted)} +.info-heading{padding:48px 0 32px}.info-heading>small{color:var(--accent);letter-spacing:3px;font-size:10px}.info-heading h1{font-size:38px;letter-spacing:1px;margin:10px 0}.info-heading p{color:var(--muted);margin:0;font-size:14px} +.info-layout{display:grid;grid-template-columns:180px minmax(0,860px);gap:28px;align-items:start} +.info-nav{display:grid;gap:8px;position:sticky;top:24px}.info-nav a{padding:13px 16px;border-radius:10px;color:#9b9e98;font-size:14px}.info-nav a[aria-current="page"]{background:#FFC00010;color:var(--accent)} +.info-card{background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:36px;min-width:0}.info-card h2{font-size:20px;margin:30px 0 12px}.info-card h2:first-child{margin-top:0}.info-card p,.info-card li{font-size:14px;line-height:1.95;color:#b6b9b2}.info-card ul{padding-left:22px}.info-card a{color:var(--accent)}.info-card .note{border-left:3px solid var(--accent);background:#FFC00008;padding:12px 16px;color:#c3b3a5;font-size:12px;margin:0 0 26px}.info-card details{border-bottom:1px solid var(--line);padding:18px 0}.info-card summary{cursor:pointer;font-size:16px;color:var(--text)}.info-card details p{margin-bottom:0}.info-card .return-home{display:inline-block;border:1px solid #ffffff20;border-radius:8px;padding:10px 16px;margin-top:18px;font-size:14px} +.feedback-form{display:grid;gap:22px}.feedback-form label,.feedback-form legend{display:block;font-size:14px;color:#dedfd8}.feedback-form input,.feedback-form textarea,.feedback-form select{display:block;width:100%;margin-top:9px;border:1px solid #ffffff20;background:#151717;color:var(--text);border-radius:8px;padding:12px;font:inherit;box-sizing:border-box;color-scheme:dark}.feedback-form textarea{min-height:150px;resize:vertical}.feedback-form :is(input,select,textarea):focus-visible{outline:2px solid var(--accent);outline-offset:3px}.feedback-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}.feedback-form .field-note{font-size:12px;color:var(--muted);margin:6px 0 0}.feedback-actions{display:flex;align-items:center;gap:16px;flex-wrap:wrap}.feedback-actions .primary{border-radius:8px}.feedback-status{font-size:12px;color:#c4cbb8;line-height:1.8}.info-shell>.footer{margin-top:48px} +@media(min-width:761px){.side{width:216px;align-items:stretch}.side>.mark{margin-left:20px}.side>nav{margin-top:30px;gap:8px;padding:0 12px}.side>nav>a{display:flex;align-items:center;gap:12px;text-align:left;padding:13px 14px;font-size:14px}.side>nav>a svg{margin:0;width:20px;height:20px;flex-shrink:0}.shell{margin-left:248px;padding-left:24px;padding-right:32px}} +@media(min-width:761px) and (max-width:1180px){.side{width:188px}.shell{margin-left:220px;padding-left:20px;padding-right:24px}.side-info{padding-left:16px;padding-right:16px}.top{flex-wrap:wrap;height:auto;min-height:106px;padding:18px 0;gap:14px}.top .nav{height:44px}.top .search{width:220px;flex:1 1 190px}.hero-content{max-width:78%}.info-layout{grid-template-columns:1fr}.info-nav{position:static;display:flex;flex-wrap:wrap;gap:6px}.info-nav a{padding:10px 14px}} +@media(max-width:760px){.info-shell{padding-top:22px!important}.info-topbar span{letter-spacing:0}.info-heading{padding:30px 0 24px}.info-heading h1{font-size:30px}.info-layout{grid-template-columns:1fr;gap:20px}.info-nav{position:static;display:flex;flex-wrap:wrap;gap:6px}.info-nav a{padding:9px 12px;font-size:12px}.info-card{padding:24px 20px}.feedback-grid{grid-template-columns:1fr}.footer{align-items:flex-start}.footer .policy-links{gap:10px 18px}} + + + +.mobile-pages{display:none}.channel-shell{padding-top:32px!important;padding-bottom:36px!important;min-height:100vh}.channel-shell>.footer{margin-top:48px}.channel-tabs{display:flex;gap:10px;flex-wrap:wrap;padding-bottom:22px;border-bottom:1px solid var(--line);margin-bottom:24px}.channel-tabs button{padding:10px 18px;border-radius:8px;background:#ffffff05;color:var(--muted);font-size:14px}.channel-tabs button[aria-pressed=true]{background:#FFC00018;color:var(--accent)}.channel-filter{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:26px}.channel-summary{display:flex;justify-content:space-between;gap:12px;color:var(--muted);font-size:12px;margin-bottom:20px}.channel-shell .cards{grid-template-columns:repeat(4,minmax(0,1fr));gap:26px 20px}.channel-topics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:28px}.channel-topic{display:block}.channel-topic h2{font-size:20px;margin:14px 0 6px}.channel-topic p{color:var(--muted);font-size:12px;margin:0}.channel-topic:hover h2{color:var(--accent)}.channel-topic .media:after{content:'';position:absolute;inset:0;background:linear-gradient(transparent,#0008)}.channel-topic .collection-name strong{font-size:30px}.channel-ranks{display:grid;gap:14px}.channel-rank{display:grid;grid-template-columns:44px 82px minmax(0,1fr) auto;gap:22px;align-items:center;border:1px solid var(--line);background:var(--panel);border-radius:14px;padding:20px;text-align:left;width:100%}.channel-rank>.number{font:italic 30px Georgia,serif;color:#858a83}.channel-rank:nth-child(-n+3)>.number{color:var(--accent)}.channel-rank h2{font-size:18px;margin:0 0 8px}.channel-rank p{color:var(--muted);font-size:12px;margin:0;line-height:1.8}.channel-rank .score{color:#FFCF40;font-size:18px;white-space:nowrap}.channel-rank .score small{display:block;text-align:right;font-size:10px;color:var(--muted);margin-top:5px}.road-banner{border:1px solid #FFC00030;border-radius:16px;background:linear-gradient(120deg,#35271f,var(--panel));padding:32px;margin-bottom:28px}.road-banner h2{font-size:26px;margin:0 0 12px}.road-banner p{color:#b9b5ab;font-size:14px;line-height:1.8}.road-banner .primary{margin-top:10px;border-radius:8px}.road-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}.road-card{padding:24px;background:var(--panel);border:1px solid var(--line);border-radius:12px;min-width:0}.road-card h3{font-size:16px;margin:0 0 14px}.road-card p{font-size:12px;color:var(--muted);line-height:1.8}.road-card input{width:100%;min-width:0;background:#141616;color:#aeb3a9;border:1px solid var(--line);padding:10px;border-radius:6px;font:12px var(--font-ui)}.road-card button{color:var(--accent);padding:12px 0 0;font-size:12px}.road-card .pending{color:#888d84;font-size:12px}.road-guide{margin-top:28px;max-width:none}.app-empty{padding:64px 24px;text-align:center;border:1px dashed #ffffff25;border-radius:16px;background:#1b1d1b}.app-empty .app-symbol{margin:auto;width:58px;height:58px;border-radius:16px;background:#FFC00014;display:grid;place-items:center;color:var(--accent);font-size:28px}.app-empty h2{font-size:22px;margin:22px 0 10px}.app-empty p{font-size:14px;line-height:1.9;color:var(--muted)}.app-empty a{display:inline-block;color:var(--accent);font-size:14px;margin-top:10px}.side>nav{flex-shrink:0}.side>nav>a{min-height:44px}.channel-back{display:inline-block;margin-bottom:22px;color:var(--accent);font-size:14px} +@media(min-width:761px){.side>nav{gap:3px;margin-top:24px}.side>nav>a{padding-top:10px;padding-bottom:10px}.side-info{padding-top:20px}.side-info>strong{padding-top:16px}.side-info>small{margin-top:14px}} +@media(max-width:1180px){.channel-shell .cards{grid-template-columns:repeat(3,minmax(0,1fr))}.road-grid{grid-template-columns:1fr}.channel-rank{gap:14px}} +@media(max-width:760px){.mobile-pages{display:flex;gap:6px;overflow-x:auto;padding:12px 19px;border-bottom:1px solid var(--line);background:var(--bg)}.mobile-pages a{flex-shrink:0;font-size:12px;padding:8px 11px;border-radius:7px;color:var(--muted)}.mobile-pages a[aria-current=page]{color:var(--accent);background:#FFC00014}.channel-shell{padding-top:22px!important}.channel-shell .cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}.channel-topics{grid-template-columns:1fr}.channel-rank{grid-template-columns:24px 60px minmax(0,1fr);gap:12px;padding:16px}.channel-rank .score{grid-column:3;font-size:14px}.channel-rank .score small{display:inline;margin-left:8px}.channel-rank h2{font-size:16px}.channel-rank p.rank-desc{display:none}.channel-rank>.number{font-size:24px}.road-banner{padding:24px}.road-banner h2{font-size:24px}} + + + +.benefits-directory{max-width:1400px}.benefits-directory>.channel-tabs{position:sticky;top:0;z-index:3;background:var(--bg);padding:14px 0;gap:12px;margin-bottom:16px}.benefits-directory>.channel-tabs button{border:1px solid transparent;padding:12px 20px;font-size:14px}.benefits-directory>.channel-tabs button[aria-pressed=true]{border-color:#FFC0002c}.benefits-notice{font-size:12px;color:#80867d;margin:0 0 36px}.benefit-section{scroll-margin-top:96px;padding:0 0 34px;margin:0 0 32px;border-bottom:1px solid var(--line)}.benefit-heading{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:24px}.benefit-heading h2{font-size:24px;letter-spacing:.3px;margin:0 0 8px}.benefit-heading p{font-size:14px;line-height:1.8;color:var(--muted);margin:0}.benefit-heading>span{flex-shrink:0;font-size:12px;color:#93998e;border:1px solid var(--line);padding:5px 10px;border-radius:20px}.benefit-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}.benefit-grid>div{min-width:0}.benefit-card{display:flex;align-items:center;gap:16px;text-align:left;width:100%;height:116px;padding:20px;background:#1c1e1d;border:1px solid #ffffff0a;border-radius:14px;transition:background .2s,border-color .2s,transform .2s}.benefit-card:hover{background:#242622;border-color:#FFC0004a;transform:translateY(-2px)}.benefit-icon{position:relative;display:grid;place-items:center;width:64px;height:64px;flex-shrink:0;border-radius:17px;background:linear-gradient(140deg,var(--app-color),#1b1e23);color:#fff5e7;overflow:hidden;box-shadow:inset 0 1px 0 #ffffff38,0 5px 14px #0003}.benefit-icon>span{position:relative;z-index:1;font:600 30px "Songti SC",serif}.benefit-icon:after{content:'';position:absolute;right:-10px;bottom:-14px;width:52px;height:52px;border:1px solid #ffffff3a;border-radius:50%}.benefit-icon i{position:absolute;width:40px;height:40px;border:1px solid #ffffff26;border-radius:50%;left:-14px;top:-9px}.benefit-copy{min-width:0;display:grid;gap:7px}.benefit-copy strong{font-size:16px;font-weight:650;color:var(--text)}.benefit-copy small{font-size:12px;color:#989e92;line-height:1.7}.benefit-arrow{margin-left:auto;color:#808878;font-size:18px}.benefit-card:hover .benefit-arrow{color:var(--accent)}.benefit-expand{display:block;margin:22px auto 0;padding:10px 28px;border:1px solid #ffffff18;border-radius:8px;color:#b5bbad;font-size:12px}.benefit-expand:hover{border-color:#FFC00066}.benefit-pending{display:flex;align-items:center;gap:18px;border:1px dashed #ffffff20;border-radius:14px;padding:28px;background:#ffffff02}.benefit-pending>span{width:48px;height:48px;display:grid;place-items:center;border:1px solid var(--line);border-radius:12px;font-size:22px;color:#8c9385}.benefit-pending strong{font-size:14px;color:#a8ae9f;font-weight:500}.benefit-pending p{font-size:12px;color:#767d71;margin:7px 0 0}.benefit-detail{padding:44px;text-align:center}.benefit-detail>.benefit-icon{margin:0 auto 22px;width:80px;height:80px;border-radius:22px}.benefit-detail-label{font-size:12px;color:var(--accent);letter-spacing:2px}.benefit-detail h2{font-size:28px;margin:10px 0}.benefit-detail>p:not(.benefit-detail-label){font-size:14px;color:var(--muted)}.benefit-detail-note{padding:18px;background:#ffffff04;border-radius:10px;font-size:12px;line-height:1.9;color:#a1a899;margin:26px 0 20px}.benefit-disabled{background:#ffffff08;color:#747b6c;padding:12px 22px;border-radius:8px;cursor:not-allowed} + +@media(max-width:1180px){.benefit-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.benefit-card{gap:13px;padding:16px}.benefits-directory>.channel-tabs{gap:6px}.benefits-directory>.channel-tabs button{font-size:12px;padding:10px 12px}} +@media(max-width:760px){.benefits-directory>.channel-tabs{flex-wrap:nowrap;overflow-x:auto;margin-left:-19px;margin-right:-19px;padding-left:19px;padding-right:19px}.benefits-directory>.channel-tabs button{flex-shrink:0}.benefit-section{scroll-margin-top:82px}.benefit-heading{align-items:flex-start}.benefit-heading h2{font-size:20px}.benefit-heading p{font-size:12px}.benefit-heading>span{display:none}.benefit-grid{gap:12px}.benefit-card{height:158px;flex-direction:column;justify-content:flex-start;gap:10px;text-align:center;padding:16px 10px}.benefit-icon{width:54px;height:54px;border-radius:15px}.benefit-icon>span{font-size:26px}.benefit-copy{gap:4px}.benefit-copy strong{font-size:14px}.benefit-copy small{font-size:10px}.benefit-arrow{display:none}.benefit-detail{padding:36px 22px}.benefits-notice{line-height:1.8;margin-bottom:28px}} + + + +/* Placeholder cover source: https://hongguoduanju.com/ */ +.art{background-image:var(--cover);background-color:#29302b;background-size:cover!important;background-position:center top!important} +.cover .art,.media .art,.media.landscape .art{inset:0;width:100%;height:100%;aspect-ratio:auto;transform:none;background-size:cover!important;background-position:center top!important} +.hero-art{left:auto;right:0;width:62%;background-size:cover!important;background-position:center 22%!important} +.hero-preview .art{background-size:cover!important;background-position:center 20%!important} +.dialog-art{background-size:cover!important;background-position:center top!important} +.hot-image{background-position:center 22%}.cinema-scene .hot-image{background-position:center 20%} +.poster-name{display:none} +@media(max-width:760px){.hero-art{width:100%;opacity:.65}.dialog-art{background-position:center 24%!important}} + + +.side>nav>a .qq-nav-icon{display:block;width:24px;height:24px;flex:0 0 24px;background-color:currentColor;-webkit-mask-image:var(--qq-icon);mask-image:var(--qq-icon);-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-position:center;mask-position:center;-webkit-mask-size:contain;mask-size:contain}.side>nav>a.active .qq-nav-icon{-webkit-mask-image:var(--qq-icon-selected);mask-image:var(--qq-icon-selected)}.side>nav>a[data-page="rank"] .qq-nav-icon{-webkit-mask-size:32px 32px;mask-size:32px 32px}.side>nav>a[data-page="home"]{--qq-icon:url("/assets/19ed41602046c136.svg");--qq-icon-selected:url("/assets/813abcae48078ac7.svg")}.side>nav>a[data-page="shorts"]{--qq-icon:url("/assets/f3d55d5dd5a321d3.png");--qq-icon-selected:url("/assets/7d05c412943babef.png")}.side>nav>a[data-page="series"]{--qq-icon:url("/assets/8c1c285ac6bd3b66.svg");--qq-icon-selected:url("/assets/49cffe02110af446.svg")}.side>nav>a[data-page="topics"]{--qq-icon:url("/assets/9903882b2da0df62.svg");--qq-icon-selected:url("/assets/bf82634dbd0f4907.svg")}.side>nav>a[data-page="rank"]{--qq-icon:url("/assets/c80f072eec53a3ca.svg");--qq-icon-selected:url("/assets/c80f072eec53a3ca.svg")}.side>nav>a[data-page="home-road"]{--qq-icon:url("/assets/350ab2f3118693a8.svg");--qq-icon-selected:url("/assets/350ab2f3118693a8.svg")}.side>nav>a[data-page="apps"]{--qq-icon:url("/assets/16bd37861fd6246c.svg");--qq-icon-selected:url("/assets/16bd37861fd6246c.svg")} + + +/* All portrait poster frames use 3:4. Landscape modules retain their sizing. */ +.cover,.media.portrait{aspect-ratio:3/4;height:auto;align-self:start} +.thumb{aspect-ratio:3/4} +@media(min-width:761px){ + .dialog-art{width:100%;height:auto;min-height:0;max-height:none;aspect-ratio:3/4;align-self:start} +} + + + +/* Keep the entire source poster inside each 3:4 portrait frame. */ +.cover .art,.media.portrait .art,.thumb.art{ + background-size:contain!important; + background-position:center center!important; + background-repeat:no-repeat; + background-color:#191b19; +} +@media(min-width:761px){ + .dialog-art{ + background-size:contain!important; + background-position:center center!important; + background-repeat:no-repeat; + background-color:#191b19; + } +} + + + +/* Short-drama information hierarchy: status, likes, episodes, synopsis, tags, heat. */ +.drama-data-card{color:var(--text)}.drama-data-card>.cover{display:block}.drama-label,.drama-likes,.drama-episodes{position:absolute;z-index:2;font-size:10px;line-height:1.5;pointer-events:none} +.drama-label{top:10px;left:10px;border-radius:4px;padding:3px 7px;font-weight:650;background:#f37941;color:#fff}.drama-label--new{background:#537d67}.drama-label--free{background:#414849e8} +.drama-likes{right:10px;top:10px;display:flex;align-items:center;gap:4px;color:#fff;background:#101411bd;border:1px solid #ffffff22;padding:3px 7px;border-radius:20px;font-variant-numeric:tabular-nums}.drama-likes svg{width:12px;height:12px;stroke-width:1.6}.drama-episodes{right:10px;bottom:10px;padding:3px 7px;border-radius:4px;background:#101411cc;color:#fff} +.drama-data-card :is(h2,h3),.editorial-card>.drama-title{font-size:16px;line-height:1.5;margin:12px 0 7px;font-weight:650;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.drama-description{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;font-size:12px;line-height:1.8;color:#8e968c;text-align:left;min-height:3.6em} +.drama-meta{display:flex;align-items:center;justify-content:space-between;gap:8px;flex-wrap:wrap;margin-top:12px}.drama-tags{display:flex;gap:5px;flex-wrap:wrap;min-width:0}.drama-tags>span{display:inline-block;font-size:10px;line-height:1.5;padding:3px 6px;border:1px solid #ffffff0d;background:#ffffff05;border-radius:4px;color:#a5ad9e} +.drama-heat{display:flex;align-items:center;gap:4px;font-size:12px;color:#FFCF40;white-space:nowrap}.drama-heat svg{width:13px;height:13px;stroke-width:1.5}.drama-heat b{font-size:12px;font-weight:550;font-variant-numeric:tabular-nums}.drama-heat small,.editorial-card .drama-heat small{display:inline;font-size:10px;color:#828b7e} +.editorial-card{text-align:left;padding:0}.editorial-card .drama-tags>span{font-size:10px;padding:2px 5px}.ranked-card .rank-caption{margin-top:14px;margin-bottom:8px;min-height:36px}.ranked-card .rank-caption h3{margin:0}.ranked-card .rank-caption>span{min-width:0}.ranked-card .drama-meta{margin-top:12px}.curated-copy{display:flex;flex-direction:column;gap:0;padding:8px 0;align-self:center}.curated-copy .pick{margin-bottom:15px}.curated-copy h3{margin:0 0 12px;font:500 28px/1.35 "Songti SC",serif;white-space:normal}.curated-copy .drama-description{min-height:0}.curated-copy .drama-meta{width:100%;gap:12px;margin-top:16px}.curated-copy .watch-link{margin-top:20px;align-self:flex-start;font-size:12px}.channel-rank .drama-tags{margin-top:9px}.drama-data-card:hover :is(h2,h3),.editorial-card:hover>.drama-title{color:var(--accent)} +@media(max-width:1180px){.drama-label,.drama-likes{top:8px}.drama-label{left:8px}.drama-likes{right:8px}.curated-card{grid-template-columns:minmax(100px,.8fr) minmax(0,1fr)}.curated-copy h3{font-size:24px}} +@media(max-width:760px){.drama-data-card :is(h2,h3),.editorial-card>.drama-title{font-size:14px}.drama-description{font-size:12px}.drama-label,.drama-likes,.drama-episodes{font-size:10px}.drama-likes{gap:3px;padding:3px 5px}.drama-meta{margin-top:9px;gap:6px}.curated-copy h3{font-size:22px}.curated-copy .watch-link{margin-top:14px}.curated-copy .drama-meta{margin-top:10px}} + + + +/* Category-card layout based on hongguoduanju.com/category/ai-drama. */ +.drama-data-card>.cover,.drama-data-card>.media.portrait{border-radius:12px;overflow:hidden} +.drama-data-card>.cover:after,.drama-data-card>.media.portrait:after{content:'';position:absolute;inset:auto 0 0;height:64px;background:linear-gradient(180deg,transparent,rgba(0,0,0,.3));pointer-events:none;z-index:1} +.drama-data-card .drama-episodes{right:12px;bottom:12px;padding:0;border-radius:0;background:none;color:#fff;font-size:14px;font-weight:500;line-height:1;text-shadow:0 1px 4px #0009} +.drama-data-card>:is(h2,h3),.editorial-card>.drama-title{font-size:18px;font-weight:500;line-height:1.4;margin:14px 0 9px;color:#ffffffe6;letter-spacing:0} +.drama-data-card .drama-meta{display:block;margin-top:0} +.drama-data-card .drama-tags{display:flex;flex-wrap:nowrap;gap:0;overflow:hidden;line-height:1.5} +.drama-data-card .drama-tags>span{position:relative;flex-shrink:0;border:0;border-radius:0;background:none;font-size:12px;font-weight:400;color:#8b9189;padding:0;line-height:1.5} +.drama-data-card .drama-tags>span+span{padding-left:10px;margin-left:10px} +.drama-data-card .drama-tags>span+span:before{content:'';position:absolute;left:0;top:4px;bottom:4px;width:1px;background:#ffffff20} +.ranked-card .rank-caption{margin:14px 0 9px}.ranked-card .rank-caption h3{font-size:18px;font-weight:500}.ranked-card .drama-meta{margin:0} +.curated-copy .drama-meta{margin-top:0}.curated-copy h3{margin-bottom:12px}.curated-copy .watch-link{margin-top:24px} +/* Keep the complete poster visible during hover as well. */ +.drama-data-card:hover .cover,.editorial-card:hover .media{transform:none;filter:brightness(1.06)} +@media(max-width:760px){.drama-data-card>:is(h2,h3),.editorial-card>.drama-title,.ranked-card .rank-caption h3{font-size:14px;margin-top:11px}.drama-data-card .drama-tags>span{font-size:12px}.drama-data-card .drama-episodes{font-size:12px;bottom:10px;right:10px}.drama-data-card>.cover,.drama-data-card>.media.portrait{border-radius:9px}.curated-copy h3{font-size:22px}} + + + +/* Portrait frames follow the native image dimensions without cropping or side bars. */ +.cover,.media.portrait{aspect-ratio:auto;height:auto;align-self:start;background:transparent} +.poster-source{display:block;width:100%;height:auto;max-width:100%;border:0} +@media(min-width:761px){.dialog-art{aspect-ratio:var(--source-ratio);min-height:0;max-height:none;height:auto}} + + + +/* Zoom only the poster inside its frame, without moving surrounding content. */ +.poster-source{transform:scale(1);transform-origin:center center;transition:transform .38s cubic-bezier(.2,.65,.3,1)} +@media(hover:hover) and (pointer:fine){ + .drama-data-card:hover .poster-source,.channel-rank:hover .poster-source{transform:scale(1.06)} + .drama-data-card:hover .cover,.editorial-card:hover .media{transform:none;filter:none} +} +.drama-data-card:focus-visible .poster-source,.channel-rank:focus-visible .poster-source{transform:scale(1.06)} +@media(prefers-reduced-motion:reduce){.poster-source{transition:none!important;transform:none!important}} + + + +/* Stable title colors and a single-line synopsis beneath each card title. */ +.drama-data-card>:is(h2,h3),.drama-data-card:hover>:is(h2,h3), +.editorial-card>.drama-title,.editorial-card:hover>.drama-title{color:#ffffffe6} +.ranked-card .rank-caption h3,.ranked-card:hover .rank-caption h3{color:var(--text)} +.curated-card .curated-copy h3,.curated-card:hover .curated-copy h3{color:#eeeade} +.drama-data-card .drama-description{display:block;width:100%;min-width:0;min-height:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:unset;font-size:12px;line-height:1.8;color:#8e968c;margin:0 0 9px;text-align:left} +.curated-copy .drama-description{flex-shrink:0} +@media(max-width:760px){.drama-data-card .drama-description{font-size:12px;margin-bottom:7px}} + + + +/* Reference corner-badge size, placement, and colors from huangguo.tv. */ +.cover-corner-badge{position:absolute;top:8px;right:8px;z-index:2;display:inline-flex;align-items:center;justify-content:center;padding:5px 10px;min-height:30px;border-radius:6px;color:#fff;font-size:14px;line-height:20px;font-weight:400;pointer-events:none} +.cover-corner-badge--hot{background:#ff4d60} +.cover-corner-badge--new{background:#7065ff} +.cover-corner-badge--free{background:#0bbba9} +@media(max-width:767px){.cover-corner-badge{padding:4px 8px;min-height:26px;font-size:12px;line-height:18px}} + + + +/* User-provided logo, embedded at its original SVG resolution. */ +.side>.mark{display:flex;align-items:center;justify-content:flex-start;gap:10px;width:auto;height:46px;margin-right:16px;flex-shrink:0;background:none;transform:none;border-radius:0;color:var(--text);font-size:18px;font-weight:750;letter-spacing:0;white-space:nowrap} +.brand-logo{display:block;flex-shrink:0;object-fit:contain;border-radius:11px} +.side>.mark .brand-logo{width:46px;height:46px} +.brand{display:inline-flex;align-items:center;gap:10px;font-size:24px;letter-spacing:0} +.brand .brand-logo{width:34px;height:34px;border-radius:9px} +@media(min-width:761px) and (max-width:1180px){.side>.mark{gap:8px;font-size:16px}.side>.mark .brand-logo{width:38px;height:38px}} +@media(max-width:760px){.brand{font-size:22px;gap:8px}.brand .brand-logo{width:30px;height:30px}} + + + +/* Supplied title artwork keeps its original 63:15 aspect ratio. */ +.brand-wordmark{display:block;object-fit:contain;flex-shrink:0;height:auto} +.side>.mark .brand-wordmark{width:100px;height:auto} +.brand .brand-wordmark{width:118px;height:auto} +@media(min-width:761px) and (max-width:1180px){.side>.mark .brand-wordmark{width:96px}} +@media(max-width:760px){.brand .brand-wordmark{width:101px}} + + + +/* Expanded navigation and compact icon-only navigation. */ +.sidebar-toggle{display:flex;align-items:center;gap:10px;flex-shrink:0;margin:18px 12px 0;padding:9px 14px;border-radius:9px;border:1px solid var(--line);color:#92998e;font-size:12px;text-align:left;line-height:20px;min-height:40px} +.sidebar-toggle:hover{background:#ffffff06;color:var(--text)}.sidebar-toggle svg{width:20px;height:20px;flex-shrink:0}.collapsed-help{display:none!important}.side-nav-label{white-space:nowrap} +@media(min-width:761px){ + .side{transition:width .24s ease}.shell{transition:margin-left .24s ease}.side>nav{margin-top:14px} + .sidebar-collapsed .side{width:80px;padding-top:20px;padding-bottom:16px} + .sidebar-collapsed .side>.mark{margin:0 auto;width:44px;height:44px;gap:0} + .sidebar-collapsed .side>.mark .brand-logo{width:44px;height:44px} + .sidebar-collapsed .side .brand-wordmark,.sidebar-collapsed .side-nav-label,.sidebar-collapsed .sidebar-toggle-label,.sidebar-collapsed .side-info{display:none} + .sidebar-collapsed .sidebar-toggle{margin:16px auto 0;width:44px;height:40px;padding:0;justify-content:center} + .sidebar-collapsed .sidebar-toggle svg{transform:rotate(180deg)} + .sidebar-collapsed .side>nav{padding:0 10px;gap:6px} + .sidebar-collapsed .side>nav>a{justify-content:center;padding:12px 0;gap:0;min-height:48px} + .sidebar-collapsed .side>nav>a .qq-nav-icon{margin:0} + .sidebar-collapsed .shell{margin-left:112px} + .sidebar-collapsed .collapsed-help{display:grid!important;place-items:center;margin: auto auto 0;padding:12px;width:46px;min-height:46px;border-radius:10px;color:#949b8e} + .sidebar-collapsed .collapsed-help:hover{background:#ffffff08;color:var(--accent)} +} +@media(prefers-reduced-motion:reduce){.side,.shell{transition:none!important}} + + + +.ui-empty-state{grid-column:1/-1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;width:100%;min-height:320px;padding:38px 20px;box-sizing:border-box} +.ui-empty-state>img{display:block;width:210px;height:210px;object-fit:contain;margin:0 0 8px;opacity:1} +.ui-empty-state>:is(h2,h3){margin:0 0 10px;color:#d5d5da;font-size:18px;font-weight:500;line-height:1.5} +.ui-empty-state>p{margin:0;max-width:360px;color:#7e7e88;font-size:14px;line-height:1.8} +.ui-empty-state .ui-empty-action{display:inline-flex;align-items:center;justify-content:center;margin-top:22px;min-width:116px;padding:10px 22px;border:1px solid #ffffff25;border-radius:999px;background:#ffffff06;color:#bfc0c8!important;font-size:14px;text-decoration:none;cursor:pointer} +.ui-empty-state .ui-empty-action:hover{border-color:#FFC000;color:#fff!important;background:#FFC00016} +.ui-empty-state .ui-empty-action:focus-visible{outline:2px solid #FFC000;outline-offset:4px} +.play-comment-list .ui-empty-state{min-height:270px;padding:20px}.play-comment-list .ui-empty-state>img{width:180px;height:180px} +#drama-play-page .play-screen-overlay .ui-empty-state{min-height:0;padding:12px}#drama-play-page .play-screen-overlay .ui-empty-state>img{width:160px;height:160px}#drama-play-page .play-screen-overlay .ui-empty-action{pointer-events:auto;width:auto;height:auto;border-radius:999px;font-size:14px} +@media(max-width:600px){.ui-empty-state{min-height:260px;padding:26px 16px}.ui-empty-state>img{width:176px;height:176px}} + + + +/* Compact header toggle aligned with the brand, matching the supplied reference. */ +.sidebar-header{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 12px;flex-shrink:0;min-height:40px} +.side .sidebar-header>.mark{display:flex;align-items:center;gap:8px;margin:0;width:auto;height:40px;min-width:0;background:none;border-radius:0;transform:none;letter-spacing:0;flex-shrink:1} +.side .sidebar-header>.mark .brand-logo{width:36px;height:36px;border-radius:10px} +.side .sidebar-header>.mark .brand-wordmark{width:96px;height:auto} +.side .sidebar-header>.sidebar-toggle{display:grid;place-items:center;flex-shrink:0;width:28px;height:32px;min-height:32px;margin:0;padding:0;gap:0;border:0;border-radius:6px;background:none;color:#9b9e95} +.side .sidebar-header>.sidebar-toggle:hover{background:#ffffff0a;color:var(--text)} +.side .sidebar-header>.sidebar-toggle svg{width:20px;height:20px;stroke-width:1.5;transform:none} +@media(min-width:761px){.side>nav{margin-top:24px}} +@media(min-width:761px) and (max-width:1180px){.sidebar-header{padding:0 10px;gap:6px}.side .sidebar-header>.mark{gap:6px}.side .sidebar-header>.mark .brand-logo{width:32px;height:32px}.side .sidebar-header>.mark .brand-wordmark{width:82px}} +@media(min-width:761px){ + .sidebar-collapsed .sidebar-header{padding:0 10px;gap:4px;min-height:36px} + .sidebar-collapsed .side .sidebar-header>.mark{width:28px;height:32px;margin:0;gap:0;flex-shrink:0} + .sidebar-collapsed .side .sidebar-header>.mark .brand-logo{width:28px;height:28px;border-radius:8px} + .sidebar-collapsed .side .sidebar-header>.sidebar-toggle{width:24px;height:32px;min-height:32px;margin:0;padding:0} + .sidebar-collapsed .side .sidebar-header>.sidebar-toggle svg{width:19px;height:19px;transform:none} + .sidebar-collapsed .side .sidebar-header>.sidebar-toggle svg path{transform:translateX(6px)} +} + + + +/* Sidebar toggle is the first navigation-list item. */ +.side>nav>.sidebar-toggle{display:flex;align-items:center;justify-content:flex-start;gap:12px;width:100%;height:44px;min-height:44px;margin:0 0 6px;padding:10px 14px;border:0;border-radius:12px;background:none;color:#8f928e;font-size:14px;text-align:left} +.side>nav>.sidebar-toggle:hover{background:#ffffff06;color:var(--text)} +.side>nav>.sidebar-toggle svg{width:24px;height:24px;flex:0 0 24px;stroke-width:1.5;transform:none} +.sidebar-header{justify-content:flex-start} +.side .sidebar-header>.mark .brand-wordmark{width:100px} +@media(min-width:761px){ + .sidebar-collapsed .sidebar-header{justify-content:center} + .sidebar-collapsed .side .sidebar-header>.mark{width:40px;height:40px} + .sidebar-collapsed .side .sidebar-header>.mark .brand-logo{width:40px;height:40px;border-radius:11px} + .sidebar-collapsed .side>nav>.sidebar-toggle{justify-content:center;width:100%;height:48px;min-height:48px;padding:12px 0;margin:0 0 6px;gap:0} + .sidebar-collapsed .side>nav>.sidebar-toggle svg{transform:none} + .sidebar-collapsed .side>nav>.sidebar-toggle svg path{transform:translateX(6px)} +} + + + +/* IYF landscape artwork and its matching portrait preview are separate assets. */ +.hero-art{left:0;right:0;width:100%;height:100%;object-fit:cover;object-position:center;opacity:1} +.hero-content{padding-bottom:170px} +.hero-previews{right:28px;bottom:76px;width:min(450px,46%);grid-template-columns:repeat(6,minmax(0,1fr));gap:9px} +.hero-preview{aspect-ratio:5/7;padding:0;display:block;min-width:0;background:#202222} +.hero-preview img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .35s ease} +.hero-preview:hover img{transform:scale(1.06)} +.hero-preview:nth-child(n+5){display:block} +@media(max-width:1000px){.hero-previews{right:24px;bottom:76px;width:48%;gap:7px}} +@media(max-width:760px){.hero-art{width:100%;object-position:65% center;opacity:.75}.hero-content{padding-bottom:210px}.hero-previews{left:20px;right:20px;bottom:70px;width:auto;max-width:420px;margin-left:auto;gap:7px}.hero-preview{max-height:112px}} +@media(prefers-reduced-motion:reduce){.hero-preview,.hero-preview img{transition:none}.hero-preview:hover,.hero-preview:hover img{transform:none}} + + + +.home-content{--module-gap:48px;--module-inset:28px;display:flex;flex-direction:column;gap:var(--module-gap);margin-bottom:var(--module-gap);min-width:0} +.home-content>.hero-grid,.home-content>#library,.home-content>.themes,.home-content>.romance-editorial{margin:0;min-width:0} +.home-content>.themes{gap:var(--module-gap)} +.home-content .collection-pair{column-gap:26px;row-gap:var(--module-gap)} +.home-content .topic-section>.section-head{margin:0 0 24px} +.home-content>#library>.bar{padding-top:0} +.home-content>.romance-editorial{padding:0;border:0;border-radius:0;background:transparent} +.home-content .romance-editorial>.section-head{margin:0 0 28px} +.home-content #romance-rows{display:flex;flex-direction:column;gap:32px} +.home-content .editorial-row{padding:0;border:0;gap:36px} +.home-content .editorial-row+.editorial-row{padding-top:32px;border-top:1px solid #ffffff12} +@media(max-width:1000px){.home-content .editorial-row{gap:20px}} +@media(max-width:760px){.home-content{--module-gap:32px;--module-inset:18px}.home-content .topic-section>.section-head{margin-bottom:20px}.home-content>.romance-editorial{border-radius:0}.home-content #romance-rows{gap:24px}.home-content .editorial-row+.editorial-row{padding-top:24px}} + + + +.editorial-copy .editorial-no{position:static;display:block;font:italic 64px/1 Georgia,serif;color:#b7bab2;margin:0 0 18px;order:-1} +.editorial-copy i{display:none} +.editorial-row.reverse .editorial-copy>p,.editorial-row.reverse .editorial-copy h3,.editorial-row.reverse .editorial-copy>small,.editorial-row.reverse .editorial-copy button{margin-left:0} + + +.home-content>#library>.section-head{margin-top:0} + + +.home-content .section-head h2{position:relative;padding-left:18px} +.home-content .section-head h2::before{content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);width:6px;height:1.1em;border-radius:999px;background:#FFC000} + + + +button,button:hover,button:focus-visible,button:active,button.active,button[aria-pressed="true"], +a.primary,a.primary:hover,a.primary:focus-visible,[role="button"],[role="button"]:hover{color:#fff!important} + + + +/* Slide copy must never change the carousel's outer dimensions. */ +.home-content .hero{height:610px;min-height:0;container-type:inline-size} +.home-content .hero-content{position:absolute;inset:0 auto auto 0;width:60%;max-width:none;padding:45px 48px 0} +.home-content .hero-content h2{font-size:clamp(28px,6cqw,72px);line-height:1.2;letter-spacing:3px;margin:40px 0 12px;white-space:nowrap} +.home-content .hero-content .english{letter-spacing:3px;font-size:10px;line-height:1.6} +.home-content .hero-content>p{max-width:320px;margin:22px 0 0;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden} +@media(max-width:1000px){.home-content .hero{height:530px}.home-content .hero-content{width:65%;padding:32px 32px 0}.home-content .hero-content h2{margin-top:30px}} +@media(max-width:760px){.home-content .hero{height:600px}.home-content .hero-content{width:100%;padding:28px 24px 0}.home-content .hero-content h2{font-size:clamp(28px,8cqw,48px);margin-top:28px;letter-spacing:2px}.home-content .hero-content>p{margin-top:16px}.home-content .hero-rating{margin-top:16px}.home-content .hero-content .actions{margin-top:18px}} + + + +.side .sidebar-header{justify-content:center;padding:0 12px} +.side .sidebar-header>.sidebar-logo-toggle{display:grid;place-items:center;width:48px;height:48px;min-height:48px;padding:4px;margin:0;border:0;border-radius:12px;background:none;flex-shrink:0} +.side .sidebar-header>.sidebar-logo-toggle .brand-logo{width:40px;height:40px;border-radius:11px} +.side .sidebar-header>.sidebar-logo-toggle:hover{background:#ffffff08} + + + +html:not(.sidebar-collapsed) .side .sidebar-header{justify-content:flex-start} +html:not(.sidebar-collapsed) .side>nav>a .qq-nav-icon{width:28px;height:28px;flex-basis:28px} +html:not(.sidebar-collapsed) .side>nav>a[data-page="rank"] .qq-nav-icon{-webkit-mask-size:37px 37px;mask-size:37px 37px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle{width:60px;height:60px;min-height:60px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-logo{width:52px;height:52px;border-radius:14px} + + + +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle{display:flex;justify-content:flex-start;align-items:center;width:100%;height:60px;gap:10px;padding:4px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-logo{flex:0 0 52px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-wordmark{display:block;width:100px;max-width:calc(100% - 62px);height:auto} +.sidebar-collapsed .side .sidebar-header>.sidebar-logo-toggle .brand-wordmark{display:none} + + + +.side .sidebar-header .brand-copy{display:flex;flex:1;min-width:0;flex-direction:column;align-items:flex-start;gap:7px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-wordmark{width:100%;max-width:100px;height:auto} +.side .sidebar-header .brand-tagline{display:block;color:#fff;font-size:12px;font-weight:400;line-height:1.2;letter-spacing:1.2px;white-space:nowrap} +.sidebar-collapsed .side .sidebar-header .brand-copy{display:none} +@media(min-width:761px) and (max-width:1180px){.side .sidebar-header .brand-tagline{font-size:10px;letter-spacing:.7px}} + + + +.top{height:96px;min-height:96px;padding:20px 0;flex-wrap:nowrap;gap:20px;border-bottom:0} +.top .search{order:0;margin:0;width:min(420px,100%);flex:0 1 420px} +.top .saved{margin-left:auto} +@media(max-width:760px){.top{height:82px;min-height:82px;padding:16px 0}.top .search{width:100%;flex:1 1 auto}} + + + +.top{height:112px;min-height:112px;padding:24px 0} +.top .search{width:min(960px,100%);flex:0 1 960px;min-width:0;height:60px;padding:6px 10px 6px 24px;gap:16px;border:1px solid #ffffff1c;border-radius:999px;background:#27272c;box-shadow:0 0 34px 8px #ffffff05,0 8px 28px #0002;transition:border-color .25s,box-shadow .25s,background .25s} +.top .search:hover{background:#2b2b30;border-color:#ffffff32;box-shadow:0 0 38px 10px #ffffff08,0 8px 28px #0002} +.top .search:focus-within{border-color:#FFC00080;box-shadow:0 0 0 3px #FFC00012,0 0 34px 6px #FFC00018} +.top .search input{flex:1;min-width:0;font-size:16px;line-height:24px;color:#fff;background:transparent;outline:none;box-shadow:none} +.top .search input::placeholder{color:#929297;opacity:1} +.top .search button{display:grid;place-items:center;flex:0 0 44px;width:44px;height:44px;padding:10px;border-radius:50%;background:transparent;transition:background .2s,transform .2s} +.top .search button:hover{background:#ffffff0c;transform:scale(1.05)} +.top .search button svg{width:24px;height:24px;stroke-width:1.8} +@media(max-width:760px){.top{height:94px;min-height:94px;padding:20px 0}.top .search{height:54px;flex:1 1 auto;width:100%;padding-left:18px;gap:8px}.top .search input{font-size:14px}.top .search button{width:40px;height:40px;flex-basis:40px}.top .search button svg{width:22px;height:22px}} + + + +.home-content .hero-content{inset:auto auto 38px 0;width:52%;padding:0 48px;max-width:620px} +.home-content .hero-content h2{font-size:clamp(28px,4cqw,48px);line-height:1.2;letter-spacing:2px;margin:20px 0 10px} +.home-content .hero-content>p{max-width:360px;margin-top:18px} +.home-content .hero-content .actions{margin-top:20px} +@media(max-width:1000px){.home-content .hero-content{bottom:32px;width:52%;padding:0 28px}.home-content .hero-content h2{font-size:clamp(26px,4cqw,40px);margin-top:16px}.home-content .hero-content .english{letter-spacing:2px}} +@media(max-width:760px){.home-content .hero-content{inset:auto 0 208px 0;width:100%;max-width:none;padding:0 24px}.home-content .hero-content h2{font-size:clamp(28px,7cqw,36px);margin-top:16px}.home-content .hero-content>p{margin-top:14px}.home-content .hero-content .actions{margin-top:16px}} + + + +.top .search{flex:1 1 600px;width:auto;max-width:960px} +.header-actions{display:flex;align-items:center;gap:24px;margin-left:auto;flex-shrink:0} +.header-action{display:flex;align-items:center;justify-content:center;gap:9px;padding:8px 0;white-space:nowrap;font-size:14px;border-radius:8px} +.header-action:hover{opacity:.8}.header-action>svg{width:22px;height:22px} +.coin-entry{gap:8px}.coin-symbol{display:grid;place-items:center;width:32px;height:32px;border:2px solid #ffe88c;border-radius:50%;background:linear-gradient(145deg,#fff495,#ffbf39);box-shadow:inset 0 0 0 3px #ffd563;color:#d68b13;font-size:20px;text-shadow:0 1px #fff5b0}.coin-value{font-size:18px;font-variant-numeric:tabular-nums}.coin-label{font-size:12px;color:var(--muted)} +.account-divider{height:24px;width:1px;background:#ffffff20}.account-avatar{width:38px;height:38px;display:grid;place-items:center;border-radius:50%;background:#343439;overflow:hidden}.account-avatar svg{width:24px;height:24px;color:#b8b8c0}.account-entry.is-logged-in .account-avatar{background:#FFC00025}.account-entry.is-logged-in .account-avatar svg{color:#FFCF40}.account-copy{text-align:left;display:grid;gap:2px}.account-copy strong{font-size:14px;font-weight:500}.account-copy small{font-size:12px;color:#96969e} +.top .header-saved-proxy{display:none!important} +#account-dialog{width:min(420px,calc(100vw - 32px));padding:32px;border-radius:20px;background:#202024;border:1px solid #ffffff18} +#account-dialog h2{font-size:24px;margin:0 0 12px}#account-dialog p{font-size:14px;line-height:1.8;color:#a8a8b0;margin:12px 0 24px}#account-dialog .primary{width:100%;border-radius:10px}#account-dialog .account-secondary{display:block;width:100%;padding:12px;margin-top:10px;border-radius:10px;background:#ffffff08}#account-dialog .balance{display:flex;align-items:center;gap:12px;margin:24px 0;font-size:36px}#account-dialog .balance small{font-size:14px;color:#a8a8b0}#account-dialog .unavailable{padding:12px;border-radius:10px;background:#ffffff08;text-align:center;color:#9898a0;font-size:14px} +@media(max-width:1180px){.header-actions{gap:16px}.top{gap:18px}.coin-label{display:none}} +@media(max-width:760px){.top{height:auto;min-height:0;flex-wrap:wrap;padding:18px 0 22px;gap:14px}.top .search{flex:1 0 100%;max-width:none}.header-actions{width:100%;justify-content:flex-end;gap:20px}.download-entry{margin-right:auto}.header-action{padding:2px 0}.account-avatar{width:34px;height:34px}} + + + +.top{height:96px;min-height:96px;gap:32px;padding:20px 0} +.top .search{height:46px;max-width:720px;flex:1 1 480px;padding:3px 6px 3px 22px;gap:10px;background:#ffffff09;border-color:#ffffff10;box-shadow:inset 0 1px 0 #ffffff03} +.top .search:hover{background:#ffffff0d;border-color:#ffffff24;box-shadow:none} +.top .search:focus-within{border-color:#FFC00080;box-shadow:0 0 0 3px #FFC00010} +.top .search input{font-size:14px}.top .search button{width:36px;height:36px;flex-basis:36px;padding:8px}.top .search button svg{width:21px;height:21px} +.header-actions{gap:26px;margin-left:auto;align-items:center} +.header-action{flex-direction:column;gap:5px;min-width:44px;padding:2px 0;font-size:12px;line-height:1.3;opacity:.8;transition:opacity .2s,transform .2s} +.header-action:hover{opacity:1;transform:translateY(-1px)} +.header-action>svg{width:24px;height:24px;stroke-width:1.6} +.coin-symbol{width:25px;height:25px;font-size:14px;box-shadow:inset 0 0 0 2px #ffd563} +.coin-caption{font-size:12px}.coin-value{font-size:12px} +.account-entry{min-width:46px;gap:4px}.account-avatar{width:34px;height:34px;background:linear-gradient(145deg,#56565e,#303036);border:1px solid #ffffff18}.account-avatar svg{width:23px;height:23px} +.account-copy{display:block;text-align:center}.account-copy strong{font-size:12px;font-weight:400}.account-copy small{display:none} +.account-entry.is-logged-in .account-avatar{border-color:#FFC00055} +.account-history-list{display:grid;gap:10px;margin-top:20px;max-height:50vh;overflow:auto} +.account-history-item{display:flex;align-items:center;gap:12px;text-align:left;padding:10px;border-radius:10px;background:#ffffff05;width:100%}.account-history-item:hover{background:#ffffff0a}.account-history-item img{width:42px;height:58px;object-fit:cover;border-radius:5px}.account-history-item strong{display:block;font-size:14px;font-weight:500}.account-history-item small{display:block;font-size:12px;color:#94949c;margin-top:5px} +@media(max-width:1180px){.top{gap:20px}.header-actions{gap:16px}.top .search{flex-basis:300px}} +@media(max-width:760px){.top{height:auto;min-height:0;flex-wrap:wrap;padding:18px 0;gap:16px}.top .search{flex:1 0 100%;max-width:none;height:46px}.header-actions{justify-content:flex-end;gap:24px;width:100%}.download-entry{margin-right:auto}} + + + +.top{height:106px;min-height:106px;gap:28px} +.top .search{height:54px;max-width:none;flex:1 1 620px;padding:5px 12px 5px 24px;background:#ffffff15;border:1px solid #ffffff25;backdrop-filter:blur(18px);-webkit-backdrop-filter:blur(18px);box-shadow:inset 0 1px 0 #ffffff08} +.top .search input{font-size:16px}.top .search input::placeholder{color:#a9a9b0}.top .search button svg{width:25px;height:25px;color:#c6c6ce} +.top .search:hover{background:#ffffff1c;border-color:#ffffff35} +.header-actions{gap:24px} +.header-actions .download-entry{flex-direction:row;gap:8px;height:50px;padding:0 18px;border-radius:999px;border:1px solid #ffffff1c;background:#ffffff09;font-size:14px;opacity:1;backdrop-filter:blur(12px)} +.header-actions .download-entry:hover{background:#ffffff14;transform:none} +.header-actions .coin-entry{flex-direction:row;height:50px;padding:0 16px;gap:8px;background:#d6aa4610;border:1px solid #d6aa4630;border-radius:999px;opacity:1} +.header-actions .coin-caption,.header-actions .coin-value{font-size:14px;color:#e6bf68}.header-actions .coin-symbol{width:25px;height:25px} +.header-actions .history-entry{gap:4px;min-width:38px;font-size:12px}.header-actions .history-entry>svg{width:28px;height:28px;color:#bfc0c7} +.header-actions .account-entry{padding:0;min-width:50px;opacity:1}.header-actions .account-avatar{width:50px;height:50px;background:linear-gradient(145deg,#777780,#39393f);border:1px solid #ffffff28;box-shadow:inset 0 1px 0 #ffffff15}.header-actions .account-avatar svg{width:30px;height:30px;color:#ececf0}.header-actions .account-copy{display:none}.header-actions .account-entry.is-logged-in .account-avatar{background:linear-gradient(145deg,#ad3570,#4a2538);border-color:#FFC00080} +@media(max-width:1180px){.top{gap:18px}.header-actions{gap:14px}.header-actions .download-entry{padding:0 14px}.header-actions .coin-entry{padding:0 12px}.top .search{flex-basis:300px}} +@media(max-width:760px){.top{height:auto;min-height:0;gap:16px}.top .search{flex:1 0 100%;height:50px;padding-left:18px}.top .search input{font-size:14px}.header-actions{justify-content:flex-end;gap:16px}.header-actions .download-entry{margin-right:0;height:42px;font-size:12px;padding:0 12px}.header-actions .coin-entry{height:42px;margin-right:auto}.header-actions .account-entry{min-width:42px}.header-actions .account-avatar{width:42px;height:42px}} + + + +.page-tools{position:fixed;right:24px;bottom:28px;z-index:8;display:flex;flex-direction:column;gap:10px;opacity:0;visibility:hidden;transform:translateY(12px);pointer-events:none;transition:opacity .2s,transform .2s,visibility .2s} +.page-tools.is-visible{opacity:1;visibility:visible;transform:none;pointer-events:auto} +.page-tool{display:grid;place-items:center;width:48px;height:48px;padding:12px;border:1px solid #ffffff1c;border-radius:14px;background:#252529ed;box-shadow:0 5px 20px #0003;backdrop-filter:blur(12px)} +.page-tool:hover,.page-tool[aria-expanded="true"]{background:#FFC000;border-color:#FFC000}.page-tool svg{width:24px;height:24px} +.page-tool-download{position:relative}.download-qr-panel{position:absolute;right:100%;bottom:0;margin-right:14px;width:190px;padding:18px;border:1px solid #ffffff1c;border-radius:16px;background:#242429;box-shadow:0 12px 40px #0006;text-align:center;opacity:0;visibility:hidden;transform:translateX(6px);transition:opacity .18s,transform .18s,visibility .18s} +.download-qr-panel::after{content:'';position:absolute;left:100%;bottom:0;width:16px;height:100%} +.page-tool-download:hover .download-qr-panel,.page-tool-download:focus-within .download-qr-panel,.page-tool-download.is-open .download-qr-panel{opacity:1;visibility:visible;transform:none} +.download-qr-panel strong{display:block;font-size:14px;margin-bottom:12px}.download-qr-panel p{color:#a7a7ae;font-size:12px;margin:12px 0 0;line-height:1.6} +.download-qr-placeholder{display:grid;place-items:center;align-content:center;gap:10px;width:152px;height:152px;border-radius:9px;background:#f3f3f5;color:#777780;font-size:12px}.download-qr-placeholder svg{width:34px;height:34px;color:#b0b0b8} +@media(max-width:760px){.page-tools{right:14px;bottom:20px;gap:8px}.page-tool{width:44px;height:44px;padding:10px;border-radius:12px}.download-qr-panel{margin-right:10px}} + + +.download-qr-placeholder{background:#fff;padding:12px}.download-qr-placeholder img{display:block;width:128px;height:128px;image-rendering:pixelated} + + +.header-download-wrap{position:relative} +.header-download-qr{position:absolute;top:100%;right:0;margin-top:14px;z-index:12;width:190px;padding:18px;border:1px solid #ffffff1c;border-radius:16px;background:#242429;box-shadow:0 12px 40px #0006;text-align:center;opacity:0;visibility:hidden;transform:translateY(-6px);transition:opacity .18s,transform .18s,visibility .18s} +.header-download-qr::before{content:'';position:absolute;bottom:100%;left:0;width:100%;height:16px} +.header-download-qr strong{display:block;font-size:14px;margin-bottom:12px}.header-download-qr p{color:#a7a7ae;font-size:12px;margin:12px 0 0;line-height:1.6} +.header-download-wrap:hover .header-download-qr,.header-download-wrap:focus-within .header-download-qr,.header-download-wrap.is-open .header-download-qr{opacity:1;visibility:visible;transform:none} + + + +.header-download-qr,.download-qr-panel{width:320px;padding:32px 22px 28px;border:1px solid #ffffff0a;border-radius:20px;background:#303232;box-shadow:0 16px 44px #0005} +.download-panel-brand{display:flex;justify-content:center;align-items:center;gap:10px;margin:0 0 24px} +.download-panel-brand .brand-logo{display:block;width:34px;height:34px;object-fit:contain;flex:0 0 34px} +.download-panel-brand .brand-wordmark{display:block;width:126px;height:30px;object-fit:contain} +.header-download-qr .download-qr-placeholder,.download-qr-panel .download-qr-placeholder{width:180px;height:180px;margin:0 auto;padding:12px;background:#fff;border-radius:12px} +.header-download-qr .download-qr-placeholder img,.download-qr-panel .download-qr-placeholder img{width:156px;height:156px} +.download-panel-copy{margin-top:22px} +.header-download-qr .download-panel-copy p,.download-qr-panel .download-panel-copy p{margin:0;color:#a5a7a7;font-size:14px;line-height:1.9;white-space:nowrap} +@media(max-width:760px){.header-download-qr,.download-qr-panel{width:270px;padding:26px 14px 24px}.header-download-qr{right:-46px}.header-download-qr .download-panel-copy p,.download-qr-panel .download-panel-copy p{font-size:12px}.download-panel-brand{margin-bottom:20px}} + + + +.top .search{position:relative} +.search-discovery{position:absolute;top:calc(100% + 14px);left:0;width:min(760px,calc(100vw - 48px));max-width:max(100%,320px);max-height:min(620px,75vh);overflow:auto;z-index:20;padding:28px;border:1px solid #ffffff12;border-radius:24px;background:#171719;box-shadow:0 16px 60px #0008;text-align:left;animation:search-panel-in .18s ease} +.search-discovery[hidden]{display:none} +.search-discovery-heading{display:flex;justify-content:space-between;align-items:center;gap:16px;margin-bottom:16px}.search-discovery-heading h2{font-size:16px;margin:0;color:#f2f2f4;font-weight:600}.search-history-actions{display:flex;gap:10px;align-items:center} +.top .search .search-discovery button{display:inline-flex;width:auto;height:auto;flex:initial;min-height:30px;padding:5px 8px;border-radius:6px;font-size:12px;transform:none;white-space:normal} +.top .search .search-discovery button:hover{background:#ffffff0a} +.search-history-tags{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:26px}.top .search .search-history-tags button{padding:7px 12px;background:#ffffff08;border-radius:8px;max-width:100%;overflow-wrap:anywhere} +.search-discovery-status{font-size:14px;color:#82828c;margin:0 0 12px}.search-suggestion-list{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px 16px} +.top .search .search-suggestion-list button{display:flex;align-items:center;gap:10px;padding:10px 6px;width:100%;text-align:left;font-size:14px;min-width:0} +.search-suggestion-list .suggestion-number{font-size:18px;font-weight:700;font-style:italic;color:#74747c;flex:0 0 20px}.search-suggestion-list button:nth-child(-n+3) .suggestion-number{color:var(--accent)}.suggestion-copy{min-width:0;display:grid;gap:4px}.suggestion-copy strong{font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:400}.suggestion-copy small{font-size:12px;color:#85858f}.search-discovery mark{background:none;color:#FFCF40} +@keyframes search-panel-in{from{opacity:0;transform:translateY(-5px)}to{opacity:1;transform:none}} +@media(max-width:760px){.search-discovery{width:100%;max-width:none;padding:20px;border-radius:18px;max-height:65vh}.search-suggestion-list{grid-template-columns:1fr}} + + + +.search-discovery{width:760px;max-width:none;padding:36px;border-radius:24px;background:#0d0d0d;border:1px solid #ffffff20;box-shadow:0 22px 65px #0009;scrollbar-width:thin;scrollbar-color:#414141 #0d0d0d;overscroll-behavior:contain} +.search-discovery-heading{margin-bottom:22px}.search-discovery-heading h2{font-size:18px;font-weight:650} +.search-history-section{padding-bottom:28px;border-bottom:1px solid #ffffff12}.search-history-tags{margin:0;min-height:28px}.search-discovery-status{font-size:14px;line-height:1.8;margin:0} +.search-ranking-section{padding:28px 0;border-bottom:1px solid #ffffff12}.search-picks-section{padding-top:28px} +.search-suggestion-list{display:flex;flex-direction:column;gap:4px} +.top .search .search-suggestion-list button{padding:10px 0;min-height:46px;gap:14px;border-radius:8px} +.search-suggestion-list .suggestion-number{display:grid;place-items:center;width:28px;height:28px;flex:0 0 28px;border-radius:50%;background:#282828;color:#969696;font-size:16px;font-weight:400;font-style:normal} +.search-suggestion-list button:nth-child(1) .suggestion-number{background:linear-gradient(135deg,#ffd095,#ed9b3b);color:#fff} +.search-suggestion-list button:nth-child(2) .suggestion-number{background:linear-gradient(135deg,#d3eaf7,#8aaebd);color:#fff} +.search-suggestion-list button:nth-child(3) .suggestion-number{background:linear-gradient(135deg,#ffd0b5,#df9277);color:#fff} +.suggestion-copy{flex:1}.suggestion-copy strong{font-size:16px;color:#c6c6c6}.suggestion-heat{margin-left:auto;white-space:nowrap;font-size:14px;color:#858585} +.top .search .search-discovery #search-clear,.top .search .search-discovery #search-picks-refresh{color:var(--accent)!important;font-size:14px;padding:4px 0} +.top .search .search-discovery #search-panel-close{padding:4px;margin-left:8px}.top .search #search-panel-close svg{width:22px;height:22px} +.search-picks-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px} +.top .search .search-discovery .search-pick{display:block;min-width:0;width:100%;padding:0;border-radius:12px;overflow:hidden;background:#191919;text-align:left} +.search-pick-cover{display:block;position:relative;overflow:hidden}.search-pick-cover>img{display:block;width:100%;height:auto;transition:transform .3s}.search-pick:hover .search-pick-cover>img{transform:scale(1.05)} +.search-pick-body{display:grid;gap:8px;padding:12px 10px}.search-pick-body strong,.search-pick-body small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-pick-body strong{font-size:14px;color:#eee;font-weight:600}.search-pick-body small{font-size:12px;color:#777} +.search-pick .cover-corner-badge{top:6px;right:6px;font-size:10px;line-height:16px;min-height:22px;padding:3px 6px}.search-pick .drama-episodes{font-size:12px} +@media(max-width:760px){.search-discovery{padding:22px;border-radius:20px}.search-discovery-heading h2{font-size:16px}.suggestion-copy strong{font-size:14px}.suggestion-heat{font-size:12px}.search-picks-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.search-history-section{padding-bottom:22px}.search-ranking-section{padding:22px 0}.search-picks-section{padding-top:22px}} + + + +/* Lift the entire header context, not only the popup inside the blurred form. */ +.top{position:relative;z-index:100;overflow:visible} +.top .search{position:relative;z-index:30;overflow:visible} +.top .header-actions{position:relative;z-index:20} +.top .search-discovery{z-index:1000} +.home-content{position:relative;z-index:0;isolation:isolate} + + + +.search-discovery{padding:24px;border-radius:18px} +.search-discovery-heading{margin-bottom:16px}.search-discovery-heading h2{font-size:16px} +.search-history-section{padding-bottom:20px}.search-ranking-section{padding:20px 0}.search-picks-section{padding-top:20px} +.top .search .search-suggestion-list button{min-height:38px;padding:6px 0;gap:10px} +.search-suggestion-list .suggestion-number{width:24px;height:24px;flex-basis:24px;font-size:14px} +.suggestion-copy strong{font-size:14px}.suggestion-heat{font-size:12px}.search-discovery-status{font-size:12px} +.search-picks-grid{gap:10px}.search-pick-body{padding:9px 8px;gap:5px}.search-pick-body strong{font-size:12px}.search-pick-body small{font-size:12px} +@media(max-width:760px){.search-discovery{padding:18px}} + + + +.search-discovery{max-height:none;overflow:visible;transform-origin:top left;transform:scale(var(--search-panel-scale,1));animation:none;padding:20px} +.search-history-section{padding-bottom:16px}.search-ranking-section{padding:16px 0}.search-picks-section{padding-top:16px}.search-discovery-heading{margin-bottom:12px} +.search-suggestion-list{gap:2px}.top .search .search-suggestion-list button{min-height:34px;padding:4px 0} +@media(max-width:760px){.search-discovery{padding:16px;max-height:none}.search-picks-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:7px}.search-pick-body{padding:8px 5px}.search-pick-body small{display:none}.search-pick-body strong{font-size:10px}} + + +.search-picks-grid{grid-template-columns:repeat(3,minmax(0,1fr))} + +.top .search button .search-icon-solid{fill:currentColor;stroke:none;color:#fff} + + +.editorial-copy button{margin-top:36px;transition:color .2s,border-color .2s} +.editorial-copy button:hover,.editorial-copy button:focus-visible{color:var(--accent)!important;border-bottom-color:var(--accent)} + + + +.hero .eyebrow b{color:#fff} +.hero .actions .primary{border-radius:999px;padding-left:26px;padding-right:26px} + + +.hero-rating span:first-child{font-size:12px;letter-spacing:0;color:#b5c2be} + + +@media(min-width:761px){.home-content .hero{--hero-content-bottom:76px}.home-content .hero-content,.home-content .hero-previews{bottom:var(--hero-content-bottom)}} + + + +.home-content .hero-previews{right:72px;width:min(450px,calc(48% - 72px))} +.hero-preview-arrow{position:absolute;top:50%;transform:translateY(-50%);display:grid;place-items:center;width:34px;height:34px;padding:0;border:1px solid #ffffff70;border-radius:50%;background:#13141466;font-size:22px;line-height:1;transition:background .2s,border-color .2s,color .2s} +.hero-preview-arrow.prev{left:-44px}.hero-preview-arrow.next{right:-44px} +.hero-preview-arrow:hover,.hero-preview-arrow:focus-visible{background:var(--accent);border-color:var(--accent);color:#fff!important} +.home-content .hero-bottom{right:72px} +@media(max-width:760px){.home-content .hero-previews{left:58px;right:58px;width:auto;max-width:none;gap:6px}.hero-preview-arrow{width:30px;height:30px;font-size:20px}.hero-preview-arrow.prev{left:-38px}.hero-preview-arrow.next{right:-38px}.home-content .hero-bottom{right:24px}} + + + +.hero-preview-arrow{width:34px;height:48px;border:0;border-radius:0;background:transparent;box-shadow:none} +.hero-preview-arrow svg{width:30px;height:38px;fill:none;stroke:currentColor;stroke-width:4;stroke-linecap:square;stroke-linejoin:miter} +.hero-preview-arrow:hover,.hero-preview-arrow:focus-visible{background:transparent;border:0;color:var(--accent)!important} +@media(max-width:760px){.hero-preview-arrow{width:30px;height:42px}.hero-preview-arrow svg{width:26px;height:32px}} + + + +.home-content .section-head button{transition:color .2s} +.home-content .section-head button>span{display:inline-block;transition:none} +.home-content .section-head button:hover,.home-content .section-head button:focus-visible{color:var(--accent)!important} +.home-content .section-head button:hover>span,.home-content .section-head button:focus-visible>span{transform:none} + + + +.topic-recommendation-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:20px} +.topic-recommendation-card{position:relative;display:block;min-width:0;aspect-ratio:16/9;border-radius:14px;overflow:hidden;isolation:isolate;background:#242424} +.topic-recommendation-card>img{display:block;width:100%;height:100%;object-fit:cover;object-position:center;transition:transform .4s ease,filter .4s ease} +.topic-recommendation-card::after{content:'';position:absolute;inset:0;background:linear-gradient(180deg,#0003 0%,transparent 28%,#0009 100%);z-index:1;pointer-events:none} +.topic-recommendation-card:hover>img{transform:scale(1.045);filter:brightness(1.08)} +.topic-recommendation-brand{position:absolute;z-index:2;left:12px;top:10px;display:flex;align-items:center;gap:5px;color:#fff;font-size:10px;font-weight:600;text-shadow:0 1px 4px #0009}.topic-recommendation-brand img{width:22px;height:22px;object-fit:contain} +.topic-recommendation-copy{position:absolute;z-index:2;left:18px;right:16px;bottom:16px;display:flex;flex-direction:column;align-items:flex-start;gap:7px;color:#fff} +.topic-recommendation-copy small{padding:3px 7px;border-radius:5px;background:#0009;color:var(--accent);font-size:12px;line-height:1.4} +.topic-recommendation-copy :is(strong,h2,h3){margin:0;font-size:18px;line-height:1.3;font-weight:600;text-shadow:0 2px 8px #0008} +.home-content .section-head .topic-more{display:inline-flex;align-items:center;gap:9px;padding:7px 14px;border-radius:999px;border:1px solid #ffffff08;background:#ffffff05;color:#b8b8b8;font-size:12px;transition:color .2s,background .2s}.topic-more span{font-size:20px;line-height:1;transition:none}.home-content .section-head .topic-more:hover,.home-content .section-head .topic-more:focus-visible{color:var(--accent);background:#FFC00012}.topic-more:hover span{transform:none} +@media(max-width:1180px){.topic-recommendation-grid{gap:14px}.topic-recommendation-copy{left:12px;bottom:12px;gap:5px}.topic-recommendation-copy :is(strong,h2,h3){font-size:16px}.topic-recommendation-brand{font-size:10px;top:8px;left:9px}.topic-recommendation-brand img{width:18px;height:18px}} +@media(max-width:760px){.topic-recommendation-grid{grid-template-columns:minmax(0,1fr);gap:12px}.topic-recommendation-card{border-radius:10px}.topic-recommendation-copy :is(strong,h2,h3){font-size:14px}.topic-recommendation-copy small{font-size:10px}.topic-recommendation-brand{display:none}} + + + +.home-content .section-head .topic-more{gap:12px;padding:6px 0;border:0;border-radius:0;background:none;color:#fff;font-size:12px;transition:color .2s} +.home-content .section-head .topic-more span{font-size:inherit;line-height:inherit;transition:none} +.home-content .section-head .topic-more:hover,.home-content .section-head .topic-more:focus-visible{color:var(--accent);background:none} +.home-content .section-head .topic-more:hover span,.home-content .section-head .topic-more:focus-visible span{transform:none} + + + +#twist-cards{grid-template-columns:repeat(6,minmax(0,1fr));gap:18px;background:transparent;border:0;border-radius:0;padding:0} +#twist-cards .rank-caption>strong{color:#858c92} +#twist-cards .ranked-card:nth-child(-n+3) .rank-caption>strong{color:var(--accent)} +@media(max-width:1180px){#twist-cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:20px 16px}} +@media(max-width:760px){#twist-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}} +.side>nav>a[data-page="rank"]{--qq-icon-selected:url("/assets/2699fa102e73d8c8.svg")} +.side>nav>a[data-page="home-road"]{--qq-icon-selected:url("/assets/92fc7fda98f7db04.svg")} +.side>nav>a[data-page="apps"]{--qq-icon-selected:url("/assets/3482d6856aee853d.svg")} +html .side>nav>a[data-page="rank"].active .qq-nav-icon{-webkit-mask-size:contain;mask-size:contain} + + + +#martial-cards .drama-description{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;white-space:normal;overflow:hidden;text-overflow:ellipsis;line-height:1.7;max-height:3.4em} + + + +.home-content{--promotion-gap:24px} +.home-content>.home-promotions{margin-top:calc(24px - var(--module-gap));margin-bottom:calc(var(--promotion-gap) - var(--module-gap))} +.home-promotions{width:100%;max-width:100%;min-width:0;box-sizing:border-box;display:grid;grid-template-columns:minmax(0,1fr);gap:var(--promotion-gap)} +.announcement-bar{width:100%;max-width:100%;min-width:0;box-sizing:border-box;overflow:hidden;display:flex;align-items:center;gap:18px;min-height:44px;padding:10px 16px;border-radius:10px;background:#FFC0000b;border:1px solid #FFC0001c} +.announcement-label{display:flex;align-items:center;gap:8px;flex-shrink:0;color:var(--accent);font-size:14px;font-weight:600}.announcement-label svg{width:20px;height:20px;stroke-width:1.8} +.announcement-viewport{min-width:0;flex:1;overflow:hidden;color:#babac0;font-size:14px;line-height:22px} +.announcement-track{display:flex;width:max-content;animation:announcement-scroll var(--announcement-duration,70s) linear infinite;will-change:transform}.announcement-track>span{display:block;flex-shrink:0;white-space:nowrap} +.announcement-bar:hover .announcement-track,.announcement-bar:focus-within .announcement-track{animation-play-state:paused} +.promo-marquee{width:100%;min-width:0;overflow:hidden;border-radius:12px;position:relative} +.promo-track{display:flex;width:max-content;animation:promo-scroll 65s linear infinite;will-change:transform} +.promo-group{display:flex;flex-shrink:0;gap:18px;padding-right:18px} +.promo-tile{display:block;flex:0 0 132px;width:132px;text-align:center;color:#c7c7cc;padding:2px 0 4px} +.promo-tile-image{display:block;width:100%;aspect-ratio:3/4;overflow:hidden;border-radius:12px;background:#252529} +.promo-tile-image img{display:block;width:100%;height:100%;object-fit:cover;object-position:center top;transition:transform .3s,filter .3s} +.promo-tile strong{display:block;margin-top:10px;font-size:14px;line-height:1.5;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.promo-tile:hover{color:var(--accent)}.promo-tile:hover img{transform:scale(1.04);filter:brightness(1.08)} +.promo-marquee:hover .promo-track,.promo-marquee:focus-within .promo-track{animation-play-state:paused} +@keyframes promo-scroll{to{transform:translateX(-50%)}}@keyframes announcement-scroll{to{transform:translateX(-50%)}} +@media(max-width:760px){.home-content>.home-promotions{margin-top:calc(18px - var(--module-gap))}.home-content{--promotion-gap:18px}.announcement-bar{padding:9px 12px;gap:12px}.announcement-label,.announcement-viewport{font-size:12px}.promo-tile{width:96px;flex-basis:96px}.promo-group{gap:12px;padding-right:12px}.promo-tile strong{font-size:12px}.promo-tile-image{border-radius:10px}} +@media(prefers-reduced-motion:reduce){.promo-track,.announcement-track{animation:none}.promo-marquee,.announcement-viewport{overflow-x:auto}.promo-group[aria-hidden="true"],.announcement-track>span+span{display:none}} + + + +#martial-cards .martial-description{display:block;white-space:normal;line-height:1.7;height:3.4em;max-height:none;min-width:0;width:100%} +#martial-cards .martial-description>span{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.7} + + + +.header-actions .account-entry:not(.is-logged-in) .account-avatar{background:transparent;border:0;box-shadow:none} +.account-avatar .guest-avatar-image{display:block;width:100%;height:100%;object-fit:contain} +.account-entry:not(.is-logged-in) .account-avatar>svg{display:none} +.account-entry.is-logged-in .guest-avatar-image{display:none} + + +.promo-tile-image{aspect-ratio:1/1}.promo-tile-image img{object-fit:contain;object-position:center}.promo-tile:hover img{transform:none} + + +.side>nav>a[data-page="rank"].active .qq-nav-icon, +.side>nav>a[data-page="home-road"].active .qq-nav-icon, +.side>nav>a[data-page="apps"].active .qq-nav-icon{-webkit-mask-image:var(--qq-icon);mask-image:var(--qq-icon)} +html .side>nav>a[data-page="rank"].active .qq-nav-icon{-webkit-mask-size:32px 32px;mask-size:32px 32px} +html:not(.sidebar-collapsed) .side>nav>a[data-page="rank"].active .qq-nav-icon{-webkit-mask-size:37px 37px;mask-size:37px 37px} + + + +.promo-marquee{overflow:visible}.promo-track{display:block;width:100%;animation:none!important;transform:none;will-change:auto} +.promo-group{display:grid;grid-template-columns:repeat(16,minmax(0,1fr));gap:14px;padding:0;width:100%} +.promo-tile{width:auto;min-width:0;padding:0}.promo-tile-image{border-radius:22%;aspect-ratio:1}.promo-tile strong{margin-top:8px;font-size:12px} +.page-breadcrumb{display:flex;align-items:center;flex-wrap:wrap;gap:12px;padding:0;border:0;font-size:16px;line-height:1.5;color:#8d8d95} +.page-breadcrumb a{color:#8d8d95}.page-breadcrumb a:hover{color:var(--accent)}.page-breadcrumb [aria-current="page"]{color:var(--accent)}.page-breadcrumb .breadcrumb-separator{color:#777780} +.channel-shell>.info-heading,.info-shell>.info-heading{padding:30px 0 28px}.channel-shell>.info-heading>small,.info-shell>.info-heading>small{display:none}.channel-shell>.info-heading h1,.info-shell>.info-heading h1{font-size:32px;line-height:1.3;margin:0;font-weight:650;letter-spacing:0} +#channel-page[data-channel="shorts"] #channel-subtitle{display:none} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs{display:flex;gap:44px;padding:0;margin:0 0 28px;border:0;overflow-x:auto} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button{position:relative;flex-shrink:0;padding:12px 0 20px;border:0;border-radius:0;background:none;color:#919198!important;font-size:20px} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button[aria-pressed="true"]{color:var(--accent)!important}@media(hover:hover) and (pointer:fine){#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button:hover{color:var(--accent)!important}} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button[aria-pressed="true"]::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:24px;height:5px;border-radius:99px;background:var(--accent);box-shadow:0 0 12px #FFC00055} +@media(max-width:1180px){.promo-group{grid-template-columns:repeat(8,minmax(0,1fr));gap:18px 14px}} +@media(max-width:760px){.promo-group{grid-template-columns:repeat(4,minmax(0,1fr));gap:18px 14px}.page-breadcrumb{font-size:14px;gap:9px}.channel-shell>.info-heading,.info-shell>.info-heading{padding:24px 0}.channel-shell>.info-heading h1,.info-shell>.info-heading h1{font-size:26px}#channel-page[data-channel="shorts"] #channel-content>.channel-tabs{gap:22px}#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button{font-size:16px}} + + + +.promo-tile-image img{transition:transform .3s ease,filter .3s ease} +.promo-tile:hover .promo-tile-image img,.promo-tile:focus-visible .promo-tile-image img{transform:scale(1.08);filter:brightness(1.06)} +@media(prefers-reduced-motion:reduce){.promo-tile:hover .promo-tile-image img,.promo-tile:focus-visible .promo-tile-image img{transform:none}} + + + +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle{height:48px;min-height:48px;gap:9px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-logo{width:40px;height:40px;flex-basis:40px;border-radius:11px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle .brand-wordmark{width:90px;max-width:100%;height:auto} +.side .sidebar-header .brand-copy{gap:5px}.side .sidebar-header .brand-tagline{font-size:10px;letter-spacing:1px} +.side>nav>#sidebar-toggle{display:grid;place-items:center;width:28px;height:28px;min-height:28px;padding:0;margin:0 12px 10px;border-radius:7px;gap:0} +.side>nav>#sidebar-toggle svg{width:16px;height:16px;flex-basis:16px} +.sidebar-collapsed .side>nav>#sidebar-toggle{margin:0 auto 10px} +.announcement-label .announcement-solid-icon{width:20px;height:20px;fill:currentColor;stroke:none} + + + +.side .sidebar-header{position:relative;gap:8px;min-height:44px} +html:not(.sidebar-collapsed) .side .sidebar-header>.sidebar-logo-toggle{height:44px;min-height:44px;flex:1;overflow:hidden} +.sidebar-brand-window{display:block;width:125px;height:36px;overflow:hidden;flex-shrink:0} +.side .sidebar-header .sidebar-brand{display:block;width:125px;max-width:none;height:36px;object-fit:contain} +.side .sidebar-header>#sidebar-toggle{width:28px;height:32px;margin:0;padding:0;min-height:32px} +.side .sidebar-header>#sidebar-toggle svg{fill:none;stroke:currentColor;width:18px;height:18px} +.sidebar-collapsed .side .sidebar-header{justify-content:center;padding:0;height:44px} +.sidebar-collapsed .side .sidebar-header>.sidebar-logo-toggle{width:36px;height:36px;min-height:36px;margin:0;flex:none} +.sidebar-collapsed .sidebar-brand-window{width:36px} +.sidebar-collapsed .side .sidebar-header>#sidebar-toggle{position:absolute;width:44px;height:44px;inset:0;margin:auto;opacity:0;background:transparent;transition:opacity .16s} +.sidebar-collapsed .sidebar-header:not(.just-collapsed):hover>#sidebar-toggle,.sidebar-collapsed .sidebar-header:not(.just-collapsed):has(> #sidebar-toggle:focus-visible)>#sidebar-toggle{opacity:1} +.sidebar-collapsed .sidebar-header:not(.just-collapsed):hover .sidebar-brand-window,.sidebar-collapsed .sidebar-header:not(.just-collapsed):has(> #sidebar-toggle:focus-visible) .sidebar-brand-window{opacity:0} +.side .sidebar-social{display:flex;justify-content:center;gap:12px;flex-shrink:0;padding:18px 0 0;margin-top:auto} +.side .sidebar-social a{width:38px;height:38px;display:grid;place-items:center;border-radius:50%;background:#ffffff12;color:#fff;transition:background .18s,color .18s} +.side .sidebar-social a:hover{background:var(--accent)} +.side .sidebar-social svg,.side .sidebar-social img{width:20px;height:20px;object-fit:contain;fill:currentColor;stroke:none} +.sidebar-collapsed .side .sidebar-social{flex-direction:column;align-items:center;gap:10px} +.sidebar-collapsed .side .collapsed-help{display:none!important} +#martial-cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:20px} +#martial-cards .curated-card{grid-template-columns:minmax(0,.8fr) minmax(0,1fr);gap:18px;padding:16px} +#martial-cards .curated-copy{min-width:0;padding:0} +#martial-cards .curated-copy h3{font-size:22px;margin-bottom:10px} +#martial-cards .curated-copy .pick{margin-bottom:12px} +#martial-cards .curated-copy .watch-link{padding:10px 12px;gap:16px;margin-top:16px} +.wide-ad-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:20px;width:100%} +.wide-ad{display:block;margin:0;aspect-ratio:840/97;overflow:hidden;border-radius:10px;background:#142655} +.wide-ad img{display:block;width:100%;height:100%;object-fit:cover;object-position:center} +@media(max-width:1180px){#martial-cards{grid-template-columns:repeat(2,minmax(0,1fr))}.sidebar-brand-window{width:112px;height:32.256px}.side .sidebar-header .sidebar-brand{width:112px;height:32.256px;object-fit:contain}.sidebar-collapsed .side .sidebar-header .sidebar-brand{width:125px;height:36px;object-fit:contain}.sidebar-collapsed .sidebar-brand-window{height:36px}} +@media(max-width:760px){#martial-cards{grid-template-columns:1fr}.wide-ad-row{gap:10px}.wide-ad{border-radius:6px}} + + + +.sidebar-collapsed .side .sidebar-header{width:44px;height:44px;min-height:44px;margin:0 auto;flex:none} +.sidebar-collapsed .side .sidebar-header .sidebar-brand-window{width:36px;height:36px;border-radius:50%;overflow:hidden;opacity:1;transition:opacity .15s} +.sidebar-collapsed .side .sidebar-header .sidebar-brand{width:125px;height:36px;max-width:none;object-fit:contain} +.sidebar-collapsed .side .sidebar-header>#sidebar-toggle{display:grid;width:44px;height:44px;min-height:44px;border-radius:12px;opacity:0;cursor:pointer;color:#fff;background:transparent;z-index:2} +.sidebar-collapsed .side .sidebar-header:not(.just-collapsed):hover .sidebar-brand-window,.sidebar-collapsed .side .sidebar-header:not(.just-collapsed):has(> #sidebar-toggle:focus-visible) .sidebar-brand-window{opacity:0} +.sidebar-collapsed .side .sidebar-header:not(.just-collapsed):hover>#sidebar-toggle,.sidebar-collapsed .side .sidebar-header:not(.just-collapsed):has(> #sidebar-toggle:focus-visible)>#sidebar-toggle{opacity:1;background:#ffffff0d} +.sidebar-collapsed .side .sidebar-header>#sidebar-toggle svg{width:22px;height:22px} +@media(prefers-reduced-motion:reduce){.sidebar-collapsed .side .sidebar-header .sidebar-brand-window,.sidebar-collapsed .side .sidebar-header>#sidebar-toggle{transition:none}} + + + +/* Subtle individual tints matched to the original card background intensity. */ +#martial-cards .curated-card[data-open="3"]{background:#20251f} +#martial-cards .curated-card[data-open="0"]{background:#25211f} +#martial-cards .curated-card[data-open="2"]{background:#251f20} +#martial-cards .curated-card[data-open="5"]{background:#211f24} +#martial-cards .curated-card[data-open="1"]{background:#1e2226} +#martial-cards .curated-card[data-open="4"]{background:#252124} + + + +.download-panel-brand .download-brand-complete{display:block;width:184px;max-width:100%;height:auto;aspect-ratio:92/24;object-fit:contain} +.header-actions .account-avatar .member-avatar-image{display:block;width:100%;height:100%;object-fit:cover;border-radius:50%} +.bottom-app-prompt{display:flex;align-items:center;justify-content:space-between;gap:36px;padding:34px 40px;border:1px solid #ffffff12;border-radius:24px;background:linear-gradient(110deg,#28170c 0%,#1b120d 48%,#141416 100%)} +.bottom-app-eyebrow{display:flex;align-items:center;gap:14px;color:#FFD24D;font-size:16px;letter-spacing:2px;margin-bottom:14px} +.bottom-app-eyebrow::before{content:"";width:24px;height:2px;background:currentColor} +.bottom-app-copy h2{margin:0 0 14px;font-size:clamp(20px,2vw,28px);line-height:1.45;color:#f4f1f3} +.bottom-app-copy p{margin:0 0 12px;color:#a9a0a8;font-size:16px} +.bottom-app-copy small{color:#807782;font-size:14px} +.bottom-app-download{display:flex;align-items:center;justify-content:center;gap:12px;flex-shrink:0;min-width:240px;padding:20px 36px;border:0;border-radius:999px;background:linear-gradient(110deg,#FFD24D,#FFC000 55%,#CC9900);font-size:20px;font-weight:600;transition:filter .2s,transform .2s;cursor:pointer} +.bottom-app-download svg{width:26px;height:26px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round;stroke-linejoin:round} +.bottom-app-download:hover{filter:brightness(1.13);transform:translateY(-2px)} +@media(max-width:760px){.bottom-app-prompt{align-items:flex-start;flex-direction:column;padding:26px 24px;gap:24px;border-radius:18px}.bottom-app-download{min-width:0;width:100%;font-size:18px;padding:16px 24px}.bottom-app-copy p{font-size:14px}} +@media(prefers-reduced-motion:reduce){.bottom-app-download{transition:none}.bottom-app-download:hover{transform:none}} + + + +.history-dialog{width:min(440px,calc(100vw - 32px));max-height:80dvh;padding:28px;border:1px solid #ffffff18;border-radius:20px;background:#202124;color:#f4f2f4} +.history-dialog::backdrop{background:#0009;backdrop-filter:blur(4px)} +.history-dialog h2{font-size:22px;margin:0 36px 8px 0}.history-note{color:#96969f;font-size:14px;margin:0}.history-empty{color:#a5a5ae;font-size:14px;line-height:1.8;padding:24px 0} +.history-dialog .account-history-list{margin-top:20px;max-height:50dvh}.history-clear{display:block;margin:20px 0 0 auto;border:0;background:transparent;font-size:14px;color:#aaa;cursor:pointer}.history-clear:hover{color:var(--accent)!important}.history-clear[hidden]{display:none} + + + +.app-download-dialog{width:min(460px,calc(100vw - 32px));padding:36px 30px 26px;max-height:90dvh;overflow:auto;background:#202124;color:#f5f3f5;border:1px solid #ffffff1c;border-radius:24px;text-align:center;box-shadow:0 24px 80px #0008} +.app-download-dialog::backdrop{background:#0009;backdrop-filter:blur(5px)}.app-download-dialog h2{margin:0 22px 10px;font-size:24px}.app-download-intro{margin:0;color:#a6a3ad;font-size:14px} + +.app-download-code{width:220px;height:220px;display:grid;place-items:center;margin:24px auto 0;background:white;padding:14px;border-radius:16px}.app-download-code img{width:192px;height:192px;image-rendering:pixelated}#app-platform-caption{color:#b3afb9;font-size:14px;margin:16px 0 22px} +.app-download-link{display:flex;flex-direction:column;align-items:center;gap:14px}.app-download-link input{width:100%;min-width:0;border:0;background:transparent;color:#9995a0;font-size:12px;padding:8px;text-align:center;outline-offset:2px}.app-download-link button{min-width:220px;border:0;background:linear-gradient(110deg,#FFD24D,#FFC000 55%,#CC9900);color:#fff;padding:14px 32px;border-radius:999px;font-size:16px;font-weight:600;cursor:pointer;transition:filter .2s}.app-download-link button:hover{filter:brightness(1.12)} +.app-copy-status{min-height:20px;font-size:12px;color:#ffb18b;margin:10px 0 0}.app-download-demo{font-size:12px;color:#87838d;margin:6px 0 0} +@media(max-width:480px){.app-download-dialog{padding:32px 20px 24px}} + + + +.channel-shell #channel-content .cards{grid-template-columns:repeat(6,minmax(0,1fr));gap:26px 20px} +.channel-ranked-card{position:relative;min-width:0}.channel-ranked-card>.card{width:100%} +.channel-rank-number{position:absolute;top:10px;left:10px;z-index:2;display:grid;place-items:center;min-width:34px;height:34px;padding:0 6px;border-radius:8px;background:#17171bd9;color:#99999f;font-size:20px;font-weight:700;pointer-events:none} +.channel-rank-number.top-three{color:var(--accent)} +.channel-ranking-cards>.empty{grid-column:1/-1} +@media(max-width:1000px){.channel-shell #channel-content .cards{grid-template-columns:repeat(3,minmax(0,1fr));gap:24px 16px}} +@media(max-width:600px){.channel-shell #channel-content .cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}} + + + +.channel-pagination{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:24px;margin:44px 0 20px;color:#92929b;font-size:14px}.pagination-pages{display:flex;align-items:center;gap:10px}.channel-pagination button{display:grid;place-items:center;min-width:38px;height:38px;border:1px solid #ffffff12;border-radius:9px;padding:0 12px;background:#ffffff04;color:#aaa!important;cursor:pointer}.channel-pagination .pagination-pages button:hover:not(:disabled):not([aria-current="page"]){border-color:var(--accent);color:var(--accent)!important}.channel-pagination button[aria-current="page"]{background:var(--accent);border-color:var(--accent);color:#fff!important}.channel-pagination button:disabled{opacity:.3;cursor:default}.pagination-gap{padding:0 4px}.pagination-jump,.pagination-jump label{display:flex;align-items:center;gap:10px}.pagination-jump input{width:50px;height:38px;border:1px solid #ffffff18;border-radius:8px;background:#ffffff05;color:#eee;text-align:center;padding:4px}.pagination-jump input:focus{outline:none;box-shadow:inset 0 0 0 1px #ffffff35} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs{padding-bottom:14px;margin-bottom:14px} +#channel-page[data-channel="shorts"] #channel-content>.channel-tabs button[aria-pressed="true"]::after{width:24px;height:5px;background:#FFC000;box-shadow:0 0 7px #FFC000cc,0 0 16px #FFC00080,0 0 26px #FFC00045} +@media(max-width:600px){.channel-pagination{gap:16px;margin-top:30px}.pagination-pages{gap:6px}} + + + +#channel-page[data-channel="rank"] #channel-subtitle{display:block;color:#909095;font-size:14px;line-height:1.8;margin-top:18px} +#channel-page[data-channel="rank"] #channel-content>.channel-tabs{gap:16px;margin-bottom:24px;padding:0;border:0} +#channel-page[data-channel="rank"] .channel-tabs button{min-width:106px;padding:13px 24px;font-size:16px;border:1px solid #ffffff0b;border-radius:9px;background:#1d1d20;color:#929299!important} +#channel-page[data-channel="rank"] .channel-tabs button[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:#fff!important} +#channel-page[data-channel="rank"] .channel-tabs button:hover:not([aria-pressed="true"]){border-color:#FFC00055;color:#FFC000!important} +#channel-page[data-channel="rank"] .channel-summary{display:none} +#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(5,minmax(0,1fr));gap:24px 18px} +.rank-poster-card{display:block;min-width:0;width:100%;padding:0;border:0;border-radius:16px;overflow:hidden;background:#1d1e20;text-align:left;cursor:pointer;transition:background .2s,box-shadow .2s} +.rank-poster-cover{display:block;position:relative;overflow:hidden;isolation:isolate} +.rank-poster-cover>img{display:block;width:100%;height:auto;transition:transform .3s} +.rank-poster-card:hover{background:#242427;box-shadow:0 0 0 1px #FFC00045} +.rank-poster-card:hover .rank-poster-cover>img{transform:scale(1.035)} +.rank-poster-cover .drama-episodes{right:10px;bottom:12px;background:#0008;padding:3px 6px;border-radius:5px;font-size:12px} +.rank-poster-number{position:absolute;bottom:0;left:0;display:grid;place-items:center;width:38px;height:34px;border-radius:0 9px 0 0;background:#626268;color:#eee;font-size:18px;font-weight:600;z-index:2} +.rank-poster-number.top-three{background:var(--accent);color:white} +.rank-poster-copy{display:flex;flex-direction:column;gap:9px;padding:12px} +.rank-poster-copy>strong{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:16px;line-height:1.5;font-weight:600;color:#eee} +.rank-poster-description{overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:14px;line-height:1.5;color:#939398} +.rank-poster-tags{display:flex;flex-wrap:wrap;gap:6px}.rank-poster-tags>span{font-size:12px;line-height:1.5;border-radius:7px;padding:3px 7px;background:#FFC00010;border:1px solid #FFC00017;color:#ca7849} +@media(max-width:1000px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(3,minmax(0,1fr))}} +@media(max-width:600px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 12px}#channel-page[data-channel="rank"] #channel-content>.channel-tabs{gap:10px}#channel-page[data-channel="rank"] .channel-tabs button{min-width:0;flex:1;padding:11px 12px;font-size:14px}} +@media(prefers-reduced-motion:reduce){.rank-poster-card,.rank-poster-cover>img{transition:none}.rank-poster-card:hover .rank-poster-cover>img{transform:none}} + + + +.adult-tabs a{position:relative;display:block;flex-shrink:0;padding:12px 0 20px;background:none;border:0;color:#919198;font-size:20px;text-decoration:none;white-space:nowrap} +.adult-tabs a[aria-current="page"]{color:var(--accent)}@media(hover:hover) and (pointer:fine){.adult-tabs a:hover{color:var(--accent)}} +.adult-tabs a[aria-current="page"]::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:24px;height:5px;border-radius:99px;background:#FFC000;box-shadow:0 0 7px #FFC000cc,0 0 16px #FFC00080,0 0 26px #FFC00045} +@media(max-width:760px){#channel-page[data-channel="series"] #channel-content>.adult-tabs{gap:22px}.adult-tabs a{font-size:16px}} + + + +#channel-page .info-heading>#channel-subtitle,#channel-page[data-channel] .info-heading>#channel-subtitle,#info-page .info-heading>#info-subtitle{display:none;margin:0;padding:0} + + + +#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(6,minmax(0,1fr));gap:28px 18px} +.rank-poster-card{background:transparent;border-radius:0;overflow:visible;box-shadow:none;align-self:start} +.rank-poster-card:hover{background:transparent;box-shadow:none} +.rank-poster-cover{border-radius:8px;overflow:hidden} +.rank-poster-copy{position:relative;display:flex;flex-direction:column;gap:7px;padding:24px 0 0} +.rank-poster-number{position:absolute;left:0;top:-24px;width:auto;min-width:0;height:auto;padding:0;border-radius:0;background:none;color:#8e8e97;font:800 46px/1 var(--font-ui);letter-spacing:-2px;text-shadow:-2px -2px 0 #121414,2px -2px 0 #121414,-2px 2px 0 #121414,2px 2px 0 #121414} +.rank-poster-number.top-three{background:none;color:var(--accent)} +.rank-poster-copy>strong{font-size:16px;line-height:1.5;font-weight:600;transition:color .2s} +.rank-poster-card:hover .rank-poster-copy>strong{color:var(--accent)} +.rank-compact-meta{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;color:#909096;font-size:12px;line-height:1.5} +.rank-compact-heat{display:inline-flex;align-items:center;gap:4px;color:#c47b52;white-space:nowrap} +.rank-compact-heat svg{width:13px;height:13px;fill:currentColor;stroke:none} +@media(max-width:1000px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(3,minmax(0,1fr))}} +@media(max-width:600px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px 12px}.rank-poster-number{font-size:40px;top:-21px}.rank-poster-copy{padding-top:22px}} + + + +.channel-pagination{gap:24px;margin:48px 0 28px;font-size:16px}.pagination-pages{gap:14px} +.channel-pagination .pagination-pages button{width:50px;min-width:50px;height:50px;padding:0;border:0;border-radius:50%;background:#28282b;font-size:18px;color:#aaa!important} +.channel-pagination .pagination-pages button[aria-current="page"]{background:var(--accent);color:#fff!important} +.channel-pagination .pagination-pages button:hover:not(:disabled):not([aria-current="page"]){background:#343438} +.channel-pagination .pagination-pages button:disabled{opacity:.4}.pagination-gap{padding:0 8px;color:#94949b} +.pagination-jump,.pagination-jump label{gap:12px}.pagination-jump input{width:76px;height:50px;border:0;border-radius:999px;background:#28282b;font-size:16px;appearance:textfield;-moz-appearance:textfield} +.pagination-jump input::-webkit-inner-spin-button,.pagination-jump input::-webkit-outer-spin-button{-webkit-appearance:none;margin:0} +.channel-pagination .pagination-jump button{height:50px;min-width:102px;border:0;border-radius:999px;background:var(--accent);color:#fff!important;font-size:16px;padding:0 26px}.channel-pagination .pagination-jump button:hover:not(:disabled),.channel-pagination .pagination-jump button:focus-visible{color:#fff!important;background:var(--accent);filter:brightness(1.1)} +@media(max-width:600px){.channel-pagination{gap:18px;font-size:14px}.pagination-pages{gap:8px}.channel-pagination .pagination-pages button{width:40px;min-width:40px;height:40px;font-size:16px}.pagination-jump input,.channel-pagination .pagination-jump button{height:42px}} + + + +#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(5,minmax(0,1fr));gap:26px 20px} +.channel-ranking-cards .cover{position:relative} +.rank-home-badge{position:absolute;left:0;bottom:0;z-index:3;display:grid;place-items:center;min-width:48px;height:40px;padding:0 10px;border-radius:10px 10px 10px 0;background:linear-gradient(120deg,#535454,#797a7b);color:#fff;font-size:24px;line-height:1;font-weight:600;pointer-events:none} +.rank-home-badge[data-rank="1"]{background:linear-gradient(120deg,#ee9641,#ffc17e)} +.rank-home-badge[data-rank="2"]{background:linear-gradient(120deg,#92b4c7,#c0ddec)} +.rank-home-badge[data-rank="3"]{background:linear-gradient(120deg,#da896c,#ffb59b)} +@media(max-width:1000px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(3,minmax(0,1fr))}} +@media(max-width:600px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}.rank-home-badge{min-width:38px;height:32px;font-size:20px;border-radius:8px 8px 8px 0}} + + + +#channel-page[data-channel="apps"] .benefits-directory{max-width:none} +#channel-page[data-channel="apps"] .benefits-directory>.channel-tabs{position:static;gap:14px;padding:0;border:0;margin-bottom:34px;background:transparent} +#channel-page[data-channel="apps"] .channel-tabs button{border:1px solid #ffffff0c;border-radius:9px;background:#202022;padding:12px 22px;font-size:16px;color:#99999f!important} +#channel-page[data-channel="apps"] .channel-tabs button[aria-pressed="true"]{background:var(--accent);border-color:var(--accent);color:white!important} +#channel-page[data-channel="apps"] .benefit-section{padding:0;margin:0 0 42px;border:0;scroll-margin-top:28px} +#channel-page[data-channel="apps"] .benefit-heading{margin-bottom:20px} +#channel-page[data-channel="apps"] .benefit-heading h2{font-size:22px;margin:0 0 9px} +#channel-page[data-channel="apps"] .benefit-heading p{font-size:14px;color:#85858e;margin:0} +#channel-page[data-channel="apps"] .benefit-grid{display:grid;grid-template-columns:repeat(10,minmax(0,1fr));gap:22px;max-width:1440px} +#channel-page[data-channel="apps"] .benefit-grid>[hidden]{display:none} +#channel-page[data-channel="apps"] .benefit-card{display:flex;flex-direction:column;align-items:center;gap:10px;width:100%;height:auto;min-height:0;min-width:0;padding:0;border:0;border-radius:0;background:transparent;box-shadow:none;text-align:center} +#channel-page[data-channel="apps"] .benefit-card:hover{background:transparent;transform:none} +#channel-page[data-channel="apps"] .benefit-icon{width:100%;height:auto;max-width:110px;aspect-ratio:1;flex:none;border-radius:22%;overflow:hidden;background:transparent;box-shadow:none} +.benefit-icon>img{display:block;width:100%;height:100%;object-fit:cover;transition:transform .25s} +.benefit-card:hover .benefit-icon>img{transform:scale(1.08)} +#channel-page[data-channel="apps"] .benefit-copy{display:block;flex:none;min-width:0;width:100%} +#channel-page[data-channel="apps"] .benefit-copy strong{display:block;font-size:14px;font-weight:500;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#ddd} +#channel-page[data-channel="apps"] .benefit-copy small{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:12px;line-height:1.5;margin-top:4px;color:#818188} +#channel-page[data-channel="apps"] .benefit-card:hover strong{color:var(--accent)} +#channel-page[data-channel="apps"] .benefit-expand{display:block;position:static;clear:both;margin:28px auto 0;padding:8px 18px;border:0;border-radius:999px;background:transparent;font-size:14px;color:#9898a0!important} +#channel-page[data-channel="apps"] .benefit-expand:hover{color:var(--accent)!important;background:#ffffff05} +@media(max-width:1100px){#channel-page[data-channel="apps"] .benefit-grid{grid-template-columns:repeat(5,minmax(0,1fr));gap:22px 18px}} +@media(max-width:600px){#channel-page[data-channel="apps"] .benefit-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:20px 14px}#channel-page[data-channel="apps"] .channel-tabs button{padding:10px 14px;font-size:14px}#channel-page[data-channel="apps"] .benefit-copy strong{font-size:12px}#channel-page[data-channel="apps"] .benefit-heading h2{font-size:18px}} +@media(prefers-reduced-motion:reduce){.benefit-icon>img{transition:none}.benefit-card:hover .benefit-icon>img{transform:none}} + + + +@media(max-width:1100px){#channel-page[data-channel="apps"] .benefit-grid{grid-template-columns:repeat(var(--compact-app-columns),minmax(0,1fr))}} +@media(max-width:600px){#channel-page[data-channel="apps"] .benefit-grid{gap:20px 10px}} + + + +.return-home-directory{display:flex;flex-direction:column;gap:36px;padding-bottom:36px}.return-hero{padding:38px 40px;border:1px solid #FFC00025;border-radius:22px;background:radial-gradient(ellipse at top left,#FFC00020,transparent 70%),#19191d}.return-eyebrow{font-size:14px;color:#ff8b5a;letter-spacing:2px}.return-hero h2{font-size:30px;line-height:1.4;margin:15px 0 12px}.return-hero p{font-size:14px;color:#9b98a2;margin:0 0 24px}.return-actions,.return-card-actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.return-actions a,.return-actions button{display:inline-flex;gap:18px;padding:13px 24px;border-radius:999px;border:1px solid #ffffff18;background:#ffffff05;font-size:14px;color:white}.return-actions a{background:var(--accent);border-color:var(--accent)}.return-actions a:hover{filter:brightness(1.1)}.return-actions button:hover{background:#ffffff10} +.return-section-head{display:flex;align-items:center;gap:14px;margin-bottom:18px}.return-section-head h2{font-size:20px;margin:0}.return-section-head>span{font-size:12px;color:#8e8995}.return-address-grid,.return-contact-grid,.return-help-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}.return-address-card{border:1px solid #ffffff10;border-radius:16px;padding:24px;background:#1b1c20}.return-card-label{font-size:12px;color:#9a949f}.return-address-card>strong{display:block;font-size:24px;margin:16px 0 24px;overflow-wrap:anywhere}.return-card-actions a,.return-card-actions button{padding:9px 16px;border:1px solid #ffffff14;border-radius:999px;background:transparent;font-size:14px;color:#c0bac6}.return-card-actions a{color:#ff986e;border-color:#FFC00030;background:#FFC0000e} +.return-permanent{border:1px solid #ffffff10;border-radius:16px;background:#1b1c20;padding:0 24px}.return-permanent-row{display:grid;grid-template-columns:130px 1fr auto;gap:18px;align-items:center;padding:20px 0;border-bottom:1px solid #ffffff09}.return-permanent-row:last-child{border-bottom:0}.return-permanent-row>span{font-size:14px;color:#96909e}.return-permanent-row a{font-size:18px}.return-permanent-row button{padding:9px 17px;background:#ffffff06;border:1px solid #ffffff12;border-radius:999px;font-size:12px}.return-home-directory button{cursor:pointer}.return-card-actions button:hover,.return-permanent-row button:hover{background:#FFC00028;border-color:#FFC00055} +.return-contact-card{display:flex;align-items:center;gap:14px;padding:22px;border:1px solid #ffffff10;border-radius:16px;background:#1b1c20}.return-contact-icon{display:grid;place-items:center;width:44px;height:44px;flex-shrink:0;border-radius:13px;background:#FFC00017;color:#ff8b5a;font-size:20px;font-weight:600}.return-contact-card strong{display:block;font-size:16px}.return-contact-card small{display:block;color:#918a99;font-size:12px;margin-top:6px}.return-contact-arrow{margin-left:auto;color:#999}.return-contact-card:hover{border-color:#FFC00055}.return-help-grid article{padding:24px;border:1px solid #ffffff0e;border-radius:16px;background:#ffffff02}.return-help-grid article>span{font-size:26px;font-weight:700;color:#FFC000;opacity:.75}.return-help-grid h3{font-size:16px;margin:12px 0}.return-help-grid p{font-size:14px;line-height:1.8;color:#99929f;margin:0}.return-faq details{border-bottom:1px solid #ffffff10;padding:18px 0}.return-faq summary{font-size:16px;cursor:pointer}.return-faq details p{font-size:14px;line-height:1.8;color:#9a939f;margin-bottom:0}.return-source-note{margin:0;color:#74717b;font-size:12px}.return-copy-status{position:fixed;left:50%;bottom:30px;transform:translateX(-50%);z-index:30;border-radius:999px;background:#333039;color:#fff;font-size:14px;max-width:90vw;overflow-wrap:anywhere}.return-copy-status:not(:empty){padding:12px 24px;box-shadow:0 8px 24px #0005} +@media(max-width:1000px){.return-address-grid,.return-contact-grid,.return-help-grid{gap:14px}.return-address-card,.return-contact-card,.return-help-grid article{padding:18px}.return-address-card>strong{font-size:20px}} +@media(max-width:760px){.return-address-grid,.return-contact-grid,.return-help-grid{grid-template-columns:1fr}.return-hero{padding:26px 24px}.return-hero h2{font-size:26px}.return-permanent{padding:0 18px}.return-permanent-row{grid-template-columns:1fr auto;gap:10px}.return-permanent-row>span{grid-column:1/-1}.return-permanent-row a{font-size:16px}.return-home-directory{gap:28px}} + + +.return-section-head>.return-section-icon{display:block;width:30px;height:30px;flex:0 0 30px;object-fit:contain} +.return-permanent-row>a{min-width:0;overflow-wrap:anywhere} + + +.return-contact-grid{grid-template-columns:repeat(4,minmax(0,1fr))} +.return-contact-card{text-align:left;color:inherit;min-width:0}.return-contact-card>span:nth-child(2){min-width:0} +.return-contact-icon{background:transparent;border-radius:0;width:30px;height:30px;flex:0 0 30px}.return-contact-icon>img{display:block;width:28px;height:28px;object-fit:contain} +.return-device-heading{display:flex;align-items:center;gap:12px}.return-device-heading>img{display:block;width:28px;height:28px;flex:0 0 28px;object-fit:contain} +@media(max-width:1180px){.return-contact-grid{grid-template-columns:repeat(2,minmax(0,1fr))}} +@media(max-width:600px){.return-contact-grid{grid-template-columns:1fr}} + + + +#info-page[data-info="feedback"] .info-layout{display:block} +#info-page[data-info="feedback"] #info-content{padding:0;border:0;border-radius:0;background:transparent;box-shadow:none}#info-page[data-info="feedback"] .info-nav{display:none} +#feedback-form.feedback-reference{display:flex;flex-direction:column;gap:30px;width:100%;max-width:none;padding:40px 48px;border:1px solid #ffffff0e;border-radius:20px;background:#ffffff04} +#feedback-form .feedback-part{display:block;min-width:0;margin:0;padding:0;border:0}#feedback-form legend{padding:0;margin-bottom:16px;font-size:18px;font-weight:600;color:#eee}#feedback-form legend em{font-style:normal;color:var(--accent)} +#feedback-form .feedback-options{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:12px} +#feedback-form label.feedback-option{display:block;position:relative;margin:0;cursor:pointer} +#feedback-form .feedback-option input{position:absolute;opacity:0;width:1px;height:1px;padding:0;margin:0} +#feedback-form .feedback-option span{display:flex;align-items:center;justify-content:center;min-height:48px;padding:10px 12px;border:1px solid #ffffff12;border-radius:10px;background:#ffffff03;color:#aaa;font-size:14px;transition:background .15s,color .15s,border-color .15s} +#feedback-form .feedback-option:hover span{border-color:var(--accent);color:var(--accent)}#feedback-form .feedback-option input:checked+span{background:var(--accent);border-color:var(--accent);color:#fff}#feedback-form .feedback-option input:focus-visible+span{outline:2px solid #eee;outline-offset:3px} +#feedback-form .feedback-panel{display:flex;flex-direction:column;gap:16px;padding:22px;border:1px solid #ffffff0b;border-radius:12px;background:#ffffff02} +#feedback-form label:not(.feedback-option):not(.feedback-upload-box){display:flex;flex-direction:column;gap:10px;font-size:14px;color:#aaaaaa} +#feedback-form .feedback-address{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}#feedback-form .feedback-field-label{font-size:14px;color:#aaaaaa} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]),#feedback-form select,#feedback-form textarea{box-sizing:border-box;display:block;width:100%;min-width:0;border:1px solid #ffffff14;border-radius:10px;padding:14px 16px;color:#dddddd;background:#1c1c1c;font:inherit;font-size:14px;outline:none;color-scheme:dark} +#feedback-form input:focus,#feedback-form select:focus,#feedback-form textarea:focus{border-color:#ffffff50} +#feedback-form .feedback-description-wrap{position:relative}#feedback-form textarea{min-height:180px;resize:vertical;padding-bottom:40px;line-height:1.8}#feedback-count{position:absolute;bottom:14px;right:16px;font-size:12px;color:#888888} +#feedback-form .feedback-uploads{display:flex;flex-wrap:wrap;gap:16px}#feedback-form .feedback-upload-box{display:flex;position:relative;flex-direction:column;align-items:center;justify-content:center;gap:12px;min-height:160px;width:250px;border:1px dashed #ffffff30;border-radius:12px;background:#ffffff02;cursor:pointer;text-align:center;padding:18px} +#feedback-form .feedback-upload-box input{position:absolute;inset:0;opacity:0;width:100%;height:100%;cursor:pointer}#feedback-form .feedback-upload-box:focus-within{outline:2px solid #eee;outline-offset:3px}#feedback-form .feedback-upload-box svg{width:32px;height:32px;fill:none;stroke:#b5acbe;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}#feedback-form .feedback-upload-box strong{font-size:14px;font-weight:500}#feedback-form .feedback-upload-box small,#feedback-form .feedback-file-note{font-size:12px;color:#8f8898;line-height:1.6}.feedback-file-note{margin:12px 0 0} +#feedback-file-list{display:flex;flex-wrap:wrap;gap:12px}.feedback-attachment{position:relative;width:140px;padding:10px;border-radius:12px;background:#ffffff05}.feedback-attachment img,.feedback-attachment video{display:block;width:120px;height:95px;object-fit:cover;border-radius:7px}.feedback-attachment span{display:block;font-size:12px;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;margin-top:8px}.feedback-attachment button{position:absolute;right:4px;top:4px;width:24px;height:24px;padding:0;background:#252329;border-radius:50%;border:1px solid #ffffff30}#feedback-file-error{font-size:12px;color:#ff956a;margin:8px 0 0} +.feedback-submit-area{text-align:center}.feedback-submit-button{min-width:240px;padding:16px 36px;border:0;border-radius:999px;background:var(--accent);color:white;font-size:16px;cursor:pointer}.feedback-submit-button:hover{filter:brightness(1.1);color:white!important}.feedback-submit-area>p{font-size:12px;color:#888888;line-height:1.7;margin:16px 0 0} +@media(max-width:760px){#feedback-form.feedback-reference{padding:24px 20px;gap:26px}#feedback-form .feedback-options{grid-template-columns:repeat(2,minmax(0,1fr))}#feedback-form .feedback-address{grid-template-columns:1fr}#feedback-form .feedback-panel{padding:16px}#feedback-form .feedback-upload-box{width:100%}.feedback-submit-button{width:100%;min-width:0}} + + + +/* Source: iconfont.cn/collections/detail?cid=52526, 草鱼设计 / 133-常用单色. */ +.side>nav>a .nav-collection-icon{display:block;width:30px;height:30px;flex:0 0 30px;fill:currentColor;stroke:none;stroke-width:0;overflow:visible;transition:color .18s} +.side>nav>a .nav-collection-icon path{fill:currentColor;stroke:none} +.sidebar-collapsed .side>nav>a .nav-collection-icon{width:32px;height:32px;flex-basis:32px;margin:0} + + + +#feedback-form .feedback-select{position:relative;min-width:0;width:100%} +#feedback-form .feedback-select select{position:absolute;width:1px;height:1px;min-height:0;padding:0;opacity:0;pointer-events:none} +#feedback-form .feedback-select-trigger{display:flex;align-items:center;justify-content:space-between;gap:16px;box-sizing:border-box;width:100%;min-height:49px;padding:14px 20px 14px 16px;border:1px solid #ffffff14;border-radius:10px;background:#1c1c1c;color:#dddddd;font:inherit;font-size:14px;text-align:left;cursor:pointer;box-shadow:none} +#feedback-form .feedback-select-trigger[data-placeholder="true"]{color:#999999} +#feedback-form .feedback-select-trigger:hover{background:#242424;border-color:#ffffff30} +#feedback-form .feedback-select-trigger:focus-visible,#feedback-form .feedback-select-trigger[aria-expanded="true"]{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #FFC00012} +#feedback-form .feedback-select-trigger svg{width:16px;height:16px;flex:0 0 16px;fill:none;stroke:#aaaaaa;stroke-width:2;transition:transform .15s} +#feedback-form .feedback-select-trigger[aria-expanded="true"] svg{transform:rotate(180deg)} +#feedback-form .feedback-select-menu{position:absolute;top:calc(100% + 8px);left:0;right:0;z-index:50;margin:0;padding:6px;max-height:280px;overflow-y:auto;overscroll-behavior:contain;list-style:none;border:1px solid #ffffff24;border-radius:12px;background:#242424;color:#dddddd;box-shadow:0 14px 32px #0006;scrollbar-width:thin;scrollbar-color:#5d5d5d #242424} +#feedback-form .feedback-select-menu[hidden]{display:none} +#feedback-form .feedback-select-menu [role="option"]{padding:12px 14px;border-radius:8px;cursor:pointer;font-size:14px;line-height:1.5} +#feedback-form .feedback-select-menu [role="option"]:hover,#feedback-form .feedback-select-menu .is-active{background:#333333;color:#fff} +#feedback-form .feedback-select-menu [aria-selected="true"]{color:#ffad83;background:#FFC00019} + + + +#feedback-form .feedback-upload-box{box-sizing:border-box;width:360px;max-width:100%;aspect-ratio:1;min-height:0;padding:28px 16px;gap:10px;border:1px solid #ffffff12;border-radius:28px;background:#222;transition:background .18s,border-color .18s} +#feedback-form .feedback-upload-box:hover{background:#262626;border-color:#ffffff25} +#feedback-form .feedback-upload-box input{margin:0;padding:0;z-index:1} +#feedback-form .feedback-upload-plus{display:flex;align-items:center;justify-content:center;width:48px;height:48px;margin-bottom:18px;border-radius:18px;background:#383838} +#feedback-form .feedback-upload-plus svg{width:25px;height:25px;stroke:#fff;stroke-width:4;fill:none;stroke-linecap:round} +#feedback-form .feedback-upload-box strong{color:#ccc;font-size:18px;font-weight:400;line-height:1.5} +#feedback-form .feedback-upload-box small{color:#828282;font-size:14px;line-height:1.6} +#feedback-form .feedback-file-note{margin:18px 0 0;color:#828282;font-size:14px;line-height:1.6} +@media(max-width:600px){#feedback-form .feedback-upload-box{width:100%;max-width:360px;border-radius:24px}} + + + +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]), +#feedback-form textarea, +#feedback-form .feedback-select-trigger{box-sizing:border-box;margin:0;border:1px solid #ffffff14;border-radius:10px;background:#1c1c1c;color:#dddddd;font-size:14px;line-height:20px;transition:background .18s,border-color .18s,box-shadow .18s} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]), +#feedback-form .feedback-select-trigger{height:50px;min-height:50px;padding:14px 16px} +#feedback-form .feedback-select-trigger{padding-right:20px} +#feedback-form textarea{padding:14px 16px 40px} +#feedback-form input::placeholder,#feedback-form textarea::placeholder,#feedback-form .feedback-select-trigger[data-placeholder="true"]{color:#999999;opacity:1} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]):hover, +#feedback-form textarea:hover, +#feedback-form .feedback-select-trigger:hover{background:#242424;border-color:#ffffff30;color:#dddddd} +#feedback-form .feedback-select-trigger[data-placeholder="true"]:hover{color:#999999} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]):focus, +#feedback-form textarea:focus, +#feedback-form .feedback-select-trigger:focus-visible, +#feedback-form .feedback-select-trigger[aria-expanded="true"]{outline:none;outline-offset:0;background:#1c1c1c;border-color:#ffffff50;box-shadow:0 0 0 2px #ffffff08} +#feedback-form .feedback-address{align-items:start} + + + +#feedback-form .feedback-option span{background:#1c1c1c;border-color:#2d2d2d;color:#aaa} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]),#feedback-form textarea,#feedback-form .feedback-select-trigger{background:#1c1c1c;border-color:#2d2d2d;color:#ddd} +#feedback-form input::placeholder,#feedback-form textarea::placeholder,#feedback-form .feedback-select-trigger[data-placeholder="true"]{color:#999} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]):hover,#feedback-form textarea:hover,#feedback-form .feedback-select-trigger:hover{background:#222;border-color:#444} +#feedback-form input:not([type="radio"]):not([type="file"]):not([type="hidden"]):focus,#feedback-form textarea:focus,#feedback-form .feedback-select-trigger:focus-visible,#feedback-form .feedback-select-trigger[aria-expanded="true"]{background:#1c1c1c;border-color:#666;box-shadow:0 0 0 2px #ffffff08} +#feedback-form .feedback-option:hover span{border-color:var(--accent);color:var(--accent)} +#feedback-form .feedback-option input:checked+span{background:var(--accent);border-color:var(--accent);color:#fff} + + + +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) .info-layout{display:block} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) .info-nav{display:none} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content{box-sizing:border-box;width:100%;padding:36px;border:1px solid #ffffff1a;border-radius:20px;background:#ffffff0d;box-shadow:none} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) .about-section+.about-section{margin-top:30px} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content h2{margin:0 0 18px;font-size:20px;font-weight:600;line-height:1.5;color:#eee} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content p{margin:0 0 14px;color:#aaa;font-size:16px;line-height:1.9;overflow-wrap:anywhere} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content p:last-child{margin-bottom:0} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content a{color:inherit;text-decoration:none} +#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content a:hover{color:var(--accent)} +@media(max-width:600px){#info-page:is([data-info="about-us"],[data-info="privacy-policy"],[data-info="user-agreement"]) #info-content{padding:24px 20px}} + +#info-content .policy-reference-note{margin-top:32px!important;padding-top:20px;border-top:1px solid #ffffff12;font-size:12px!important;color:#888!important} + + + +#info-page:is([data-info="help-center"],[data-info="2257"],[data-info="dmca"]) .info-nav{display:none} +#info-page:is([data-info="help-center"],[data-info="2257"],[data-info="dmca"]) .info-layout{display:block} +#info-page:is([data-info="help-center"],[data-info="2257"],[data-info="dmca"]) #info-content{box-sizing:border-box;width:100%} + + + +#info-page:is([data-info="2257"],[data-info="dmca"]) #info-content{padding:36px;border:1px solid #ffffff1a;border-radius:20px;background:#ffffff0d;box-shadow:none} +#info-page:is([data-info="2257"],[data-info="dmca"]) .compliance-section{display:grid;grid-template-columns:48px minmax(0,1fr);gap:20px;padding:28px 0;border-bottom:1px solid #ffffff12} +#info-page:is([data-info="2257"],[data-info="dmca"]) .compliance-section:first-child{padding-top:0} +#info-page:is([data-info="2257"],[data-info="dmca"]) .compliance-section:last-child{padding-bottom:0;border-bottom:0} +#info-page:is([data-info="2257"],[data-info="dmca"]) .compliance-number{font-size:26px;line-height:1.4;font-weight:600;color:var(--accent);font-variant-numeric:tabular-nums} +#info-page:is([data-info="2257"],[data-info="dmca"]) #info-content h2{margin:0 0 14px;color:#eee;font-size:20px;line-height:1.5} +#info-page:is([data-info="2257"],[data-info="dmca"]) #info-content p{margin:0;color:#aaa;font-size:16px;line-height:1.9} +#info-page:is([data-info="2257"],[data-info="dmca"]) #info-content a{color:var(--accent)} +@media(max-width:600px){#info-page:is([data-info="2257"],[data-info="dmca"]) #info-content{padding:24px 20px}#info-page:is([data-info="2257"],[data-info="dmca"]) .compliance-section{grid-template-columns:34px minmax(0,1fr);gap:12px}} + +#info-page[data-info="dmca"] #info-content a{overflow-wrap:anywhere} + + + +#detail .detail-play{border-radius:999px;padding:12px 26px;gap:9px;background:var(--accent);color:#fff} +#detail .detail-play:hover{background:#ff6829;color:#fff} +#detail .detail-play svg{display:block;width:18px;height:18px;flex:0 0 18px;fill:currentColor;stroke:none} + + + +#detail .dialog-grid{box-sizing:border-box;grid-template-columns:minmax(0,2fr) minmax(0,3fr);gap:28px;padding:24px;align-items:stretch} +#detail .dialog-art{box-sizing:border-box;width:100%;height:auto;min-height:0;max-height:none;aspect-ratio:var(--source-ratio,640/914);align-self:stretch;border-radius:14px;overflow:hidden;background-size:cover!important;background-position:center!important;background-repeat:no-repeat} +#detail .dialog-body{min-width:0;padding:12px 8px 12px 0;align-self:center} +@media(max-width:760px){#detail .dialog-grid{grid-template-columns:1fr;gap:20px;padding:20px}#detail .dialog-art{width:min(100%,280px);justify-self:center;aspect-ratio:var(--source-ratio,640/914);height:auto;min-height:0;max-height:none}#detail .dialog-body{padding:0}} + + + +#detail:has(.series-preview){width:min(1080px,calc(100vw - 48px));max-width:none;max-height:calc(100dvh - 48px);border:1px solid #ffffff1a;border-radius:24px;background:radial-gradient(ellipse at 15% 0%,#30232a 0%,#1b1b1b 65%);overflow:auto} +#detail .series-preview{padding:40px} +#detail:has(.series-preview)>.close{position:absolute;top:16px;right:18px;width:32px;height:32px;line-height:1;font-size:28px;z-index:2} +#detail .series-overview{display:grid;grid-template-columns:176px minmax(0,1fr);gap:30px;align-items:start} +#detail .series-poster{display:block;width:100%;height:auto;aspect-ratio:640/914;object-fit:cover;border-radius:12px} +#detail .series-info{min-width:0;padding-top:4px} +#detail .series-title-row{display:flex;align-items:baseline;flex-wrap:wrap;gap:10px 18px;padding-right:16px} +#detail .series-title-row h2{margin:0;color:#f5f5f5;font-family:inherit;font-size:26px;font-weight:600;line-height:1.4} +#detail .series-heat{font-size:14px;color:#999;white-space:nowrap} +#detail .series-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px} +#detail .series-tags span{padding:5px 9px;border-radius:5px;background:#ffffff08;color:#aaa;font-size:12px} +#detail .series-tags .series-stat{color:#eead8b;background:#FFC00012} +#detail .series-description{margin:18px 0 24px;font-size:14px;line-height:1.9;color:#aaa} +#detail .series-actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px} +#detail .series-actions button{min-height:46px;font-size:14px} +#detail .series-download{padding:12px 20px;border:1px solid #ffffff0d;border-radius:999px;background:#ffffff08;color:#eee} +#detail .series-download:hover{background:#ffffff14} +#detail .series-episodes{margin-top:28px} +#detail .series-episode-heading{display:flex;align-items:center;gap:12px;margin-bottom:14px} +#detail .series-episode-heading h3{font-size:16px;margin:0;color:#eee} +#detail .series-episode-heading span{font-size:12px;color:#888} +#detail .series-episodes .episodes{display:grid;grid-template-columns:repeat(auto-fill,minmax(48px,1fr));gap:10px;margin:0} +#detail .series-episodes .episodes button{height:48px;min-width:0;padding:0;border:1px solid transparent;border-radius:10px;background:#292929;font-size:16px;color:#ddd} +#detail .series-episodes .episodes button:hover{border-color:var(--accent);background:#332923} +#detail .series-episodes .episodes button.active{background:var(--accent);color:#fff} +#detail .series-episodes .preview-note{margin:16px 0 0;font-size:12px;color:#888;line-height:1.6} +@media(max-width:650px){#detail:has(.series-preview){width:calc(100vw - 24px);max-height:calc(100dvh - 24px);border-radius:18px}#detail .series-preview{padding:48px 20px 24px}#detail .series-overview{grid-template-columns:100px minmax(0,1fr);gap:16px}#detail .series-title-row h2{font-size:22px}#detail .series-tags{margin-top:12px}#detail .series-description{margin:12px 0 18px}#detail .series-actions{gap:8px}#detail .series-actions button{padding:10px 16px}#detail .series-episodes{margin-top:24px}} +@media(max-width:420px){#detail .series-overview{grid-template-columns:1fr}#detail .series-poster{width:120px}#detail .series-title-row{padding:0}} + + + +#detail .series-episodes .episodes{grid-template-columns:repeat(auto-fill,48px);justify-content:start;gap:10px} +#detail .series-episodes .episodes button{box-sizing:border-box;width:48px;height:48px;min-width:48px;min-height:48px;aspect-ratio:1;padding:0;display:grid;place-items:center;line-height:1;border-radius:8px} +#detail .series-tags{gap:8px;margin-top:14px} +#detail .series-tags .series-stat{color:#d7a461;background:rgba(215,164,97,.1);font-size:12px;line-height:18px;font-weight:400;padding:3px 8px;border-radius:4px} +#detail .series-genres{margin:12px 0 0;color:#888;font-size:14px;line-height:20px} +#detail .series-title-row h2{font-size:24px;font-weight:500;color:#eee;line-height:1.5} +#detail .series-heat{color:#888;font-size:14px} +#detail .series-description{margin:10px 0 24px;color:#888;font-size:14px;font-weight:400;line-height:1.8;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden} +@media(max-width:650px){#detail .series-title-row h2{font-size:22px}#detail .series-description{-webkit-line-clamp:4}} + + + +#detail .series-episodes .episodes{max-width:744px;grid-template-columns:repeat(auto-fill,48px);gap:10px} +#detail .series-episodes .episodes button{background:#292929;border-color:transparent;color:#ddd} +#detail .series-episodes .episodes button:hover{background:#383838;border-color:#ffffff25} +#detail .series-episodes .episodes button:focus-visible{outline:2px solid #aaa;outline-offset:2px} +#detail .series-description{-webkit-line-clamp:2;line-clamp:2;min-height:calc(2 * 1.8em)} + + + +#detail:has(.series-preview){box-sizing:border-box;width:min(826px,calc(100vw - 48px))} +#detail .series-description{white-space:normal;overflow-wrap:anywhere;-webkit-line-clamp:2;line-clamp:2;line-height:1.8;min-height:3.6em;max-height:3.6em} +@media(max-width:650px){#detail:has(.series-preview){width:calc(100vw - 24px)}} + + + +#detail .series-heat{display:inline-flex;align-items:center;gap:5px} +#detail .series-heat-icon{display:block;width:16px;height:16px;flex:0 0 16px;object-fit:contain} + + + +#detail .series-tags .series-new{color:#00d6aa;background:rgba(0,214,170,.1);font-size:12px;line-height:18px;font-weight:400;padding:3px 8px;border-radius:4px} + + + +#detail .series-tags .series-badge{font-size:12px;line-height:18px;font-weight:400;padding:3px 8px;border-radius:4px} +#detail .series-tags .series-badge--hot{color:#ff6878;background:#ff4d601a} +#detail .series-tags .series-badge--new{color:#a097ff;background:#7065ff1a} +#detail .series-tags .series-badge--free{color:#12d3bd;background:#0bbba91a} + + + +#account-entry .account-copy small{display:none}#account-entry .account-copy strong{font-size:14px}#account-entry{gap:10px} +.account-chevron{width:14px;height:14px;fill:none;stroke:#888;stroke-width:2;transition:transform .15s}#account-entry[aria-expanded="true"] .account-chevron{transform:rotate(180deg)} +#profile-popover{position:fixed;z-index:1500;width:280px;max-width:calc(100vw - 24px);box-sizing:border-box;padding:18px 8px 8px;border:1px solid #ffffff26;border-radius:18px;background:#141416;color:#eee;box-shadow:0 18px 50px #0007;max-height:calc(100dvh - 100px);overflow:auto} +#profile-popover[hidden]{display:none}.profile-popover-head{display:flex;align-items:center;gap:12px;padding:0 12px 16px}.profile-popover-head img{width:44px;height:44px;border-radius:50%;object-fit:cover}.profile-popover-head strong{display:block;font-size:16px}.profile-popover-head small{display:block;font-size:12px;color:#85858f;margin-top:4px} +#profile-popover button{display:flex;align-items:center;gap:12px;width:100%;min-height:40px;padding:10px 12px;border:0;border-radius:9px;background:transparent;color:#eee;text-align:left;font-size:14px} +#profile-popover button:hover{background:#ffffff09}#profile-popover button:focus-visible{outline:1px solid #888;outline-offset:-1px} +#profile-popover button svg{width:19px;height:19px;flex:0 0 19px;fill:none;stroke:#92929c;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round} +#profile-popover .profile-row-label{flex:1}#profile-popover .profile-count,#profile-popover .profile-next{color:#85858f;font-size:14px}#profile-popover .profile-next{font-size:20px} +#profile-popover .profile-switch{border-top:1px solid #ffffff12;border-radius:0;margin-top:6px;padding-top:14px;color:#aaa!important} + +#profile-popover .profile-avatar-link{display:block;flex:0 0 44px;width:44px;height:44px;min-height:0;padding:0;border-radius:50%;cursor:pointer} +#profile-popover .profile-avatar-link:hover{box-shadow:0 0 0 2px var(--accent)} +#profile-popover .profile-avatar-link:focus-visible{outline:2px solid var(--accent);outline-offset:3px} + + + +#info-page[data-info="user-center"] .info-layout{display:block}#info-page[data-info="user-center"] .info-nav{display:none} +#info-page[data-info="user-center"] #info-content{padding:0;background:transparent;border:0;border-radius:0;box-shadow:none} +.member-center{display:grid;gap:24px}.member-summary{display:flex;align-items:center;justify-content:space-between;gap:32px;padding:36px;border:1px solid #ffffff14;border-radius:20px;background:#ffffff04} +.member-identity{display:flex;align-items:center;gap:22px}.member-identity img{width:88px;height:88px;object-fit:cover;border-radius:50%;border:2px solid #ffffff20} +#info-content .member-identity h2{margin:0 0 8px;font-size:26px;line-height:1.4;color:#eee}#info-content .member-identity p{margin:0;font-size:14px;color:#888} +.member-wallet{display:grid;grid-template-columns:1fr auto;gap:14px 40px;min-width:290px;text-align:left;background:#ffffff05;border:1px solid #ffffff0d;border-radius:16px;padding:22px 26px;color:#ddd}.member-wallet>span:first-child{font-size:14px}.member-wallet small{color:#888}.member-wallet strong{grid-row:2;font-size:32px;color:#e6c46d;display:flex;align-items:center;gap:8px}.member-wallet-link{grid-column:2;grid-row:2;align-self:center;font-size:14px;color:#d5b566} +.member-features{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:20px}.member-features>button{display:flex;flex-direction:column;align-items:flex-start;gap:12px;padding:26px;text-align:left;background:#ffffff07;border:1px solid #ffffff10;border-radius:16px;transition:background .18s,border-color .18s}.member-features>button:hover,.member-wallet:hover{background:#ffffff0c;border-color:#ffffff25}.member-feature-icon{display:grid;place-items:center;width:44px;height:44px;border-radius:12px;background:#FFC00013;color:var(--accent);margin-bottom:6px}.member-feature-icon svg{width:25px;height:25px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}.member-features strong{font-size:18px;font-weight:500}.member-features small{font-size:14px;color:#888;line-height:1.6} +@media(max-width:1000px){.member-features{grid-template-columns:repeat(2,minmax(0,1fr))}.member-summary{padding:28px;gap:24px}.member-wallet{min-width:240px}} +@media(max-width:600px){.member-summary{align-items:stretch;flex-direction:column;padding:24px}.member-identity img{width:68px;height:68px}.member-wallet{min-width:0;width:100%}.member-features{gap:12px}.member-features>button{padding:20px 16px}.member-features strong{font-size:16px}} + +.member-advertisements{position:relative;min-width:0;padding:0;border:0;background:transparent} +.member-ad-label{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap} +.member-ad-grid{display:grid;grid-template-columns:repeat(16,minmax(0,1fr));gap:14px;width:100%} +#info-content .member-ad-tile{display:flex;flex-direction:column;align-items:center;gap:8px;min-width:0;color:#c7c7cc;text-decoration:none;font-size:12px;line-height:1.5} +.member-ad-tile img{display:block;width:100%;height:auto;aspect-ratio:1;object-fit:contain;border-radius:22%;transition:transform .2s} +.member-ad-tile span{max-width:100%;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +#info-content .member-ad-tile:hover{color:var(--accent)}.member-ad-tile:hover img{transform:translateY(-3px)} +.member-ad-tile:focus-visible{outline:2px solid var(--accent);outline-offset:4px;border-radius:8px} +@media(max-width:1180px){.member-ad-grid{grid-template-columns:repeat(8,minmax(0,1fr));gap:18px 14px}} +@media(max-width:760px){.member-ad-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:18px 14px}} +@media(prefers-reduced-motion:reduce){.member-ad-tile img{transition:none}.member-ad-tile:hover img{transform:none}} + + + +#profile-popover button .account-collection-icon{width:20px;height:20px;flex:0 0 20px;fill:currentColor;stroke:none;color:#92929c} +#profile-popover button .account-collection-icon path{fill:currentColor;stroke:none} + + + +.member-library{min-width:0}.member-library-tabs{display:flex;gap:40px;border-bottom:1px solid #ffffff12;overflow-x:auto} +.member-library-tabs button{position:relative;flex-shrink:0;padding:16px 0 20px;background:none;border:0;color:#929299;font-size:16px;font-weight:500;white-space:nowrap;transition:color .18s ease} +.member-library-tabs button[aria-selected="true"]{color:var(--accent)}.member-library-tabs button[aria-selected="true"]::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:26px;height:3px;border-radius:3px;background:var(--accent)} +.member-library-tabs button[aria-selected="false"]:hover{color:var(--accent)}.member-library-tabs button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:6px}.member-library-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;min-height:66px;color:#999;font-size:14px} +.member-library-tools{display:flex;align-items:center;gap:12px}.member-library-tools button{padding:8px 18px;border:1px solid #ffffff20;border-radius:999px;background:#242424;color:#ddd;font-size:14px}.member-library-tools button[data-library-action="delete"]{background:var(--accent);border-color:var(--accent);color:white}.member-library-tools button:disabled{opacity:.35;cursor:not-allowed} +.member-library-cards{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:24px 20px}.member-library-item{position:relative;min-width:0}.member-library-item>.card{display:block;width:100%;text-align:left;background:transparent;padding:0;border:0;opacity:1;animation:none;transform:none} +#info-content .member-library-item h3{font-size:16px;line-height:1.5;color:#eee;margin:10px 0 6px}.member-library-item .cover{display:block;position:relative;aspect-ratio:640/914;overflow:hidden;border-radius:12px}.member-library-item .poster-source{width:100%;height:100%;object-fit:cover} +.member-library-item .drama-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;color:#888;font-size:12px;line-height:1.6}.member-library-item .drama-meta{font-size:12px} +.member-library-item.is-selected .cover{outline:3px solid var(--accent);outline-offset:-3px}.member-library-select{position:absolute;z-index:4;top:10px;left:10px;display:grid;place-items:center;width:28px;height:28px;cursor:pointer} +.member-library-select input{appearance:none;-webkit-appearance:none;display:block;box-sizing:border-box;width:24px;height:24px;margin:0;padding:0;border:0;border-radius:50%;background:transparent url("/assets/d2182785de825f85.svg") center/24px 24px no-repeat;cursor:pointer} +.member-library-select input:checked{background-image:url("/assets/69304455ce1e30ae.svg")} +.member-library-select input:focus-visible{outline:2px solid var(--accent);outline-offset:2px} + + +.member-library-empty{grid-column:1/-1;text-align:center;padding:60px 20px;color:#888}.member-library-empty a{display:inline-block;margin-top:16px;color:var(--accent)} +#member-delete-dialog{width:400px;max-width:calc(100vw - 32px);padding:30px;border:1px solid #ffffff20;border-radius:20px;background:#1b1b1e;color:#eee}#member-delete-dialog::backdrop{background:#0009;backdrop-filter:blur(4px)}#member-delete-dialog h2{font-size:22px;margin:0 0 16px}#member-delete-dialog p{color:#aaa;font-size:14px;line-height:1.8;margin:0 0 26px}.member-delete-actions{display:flex;justify-content:flex-end;gap:12px}.member-delete-actions button{padding:10px 22px;border-radius:999px;background:#303033;color:white;border:0}.member-delete-actions button[value="confirm"]{background:var(--accent)} +@media(max-width:1180px){.member-library-cards{grid-template-columns:repeat(4,minmax(0,1fr))}} +@media(max-width:760px){.member-library-tabs{gap:24px}.member-library-tabs button{font-size:14px}.member-library-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 14px}.member-library-toolbar{align-items:flex-start;padding:14px 0;flex-direction:column}.member-library-tools{flex-wrap:wrap}} + + + +#info-content .member-watch-status{display:block;margin:10px 0 0;color:var(--accent);font-size:14px;line-height:1.5;font-weight:500} +#info-content .member-watch-status.is-complete{color:#9a9a9f} +#info-content .member-library-item h3{margin-top:5px} + + + +.member-name-row{display:flex;align-items:center;flex-wrap:wrap;gap:16px;margin-bottom:8px}#info-content .member-name-row h2{margin:0;overflow-wrap:anywhere} +#info-content .member-settings-link{display:inline-flex;align-items:center;gap:10px;padding:6px 13px;border:1px solid #ffffff20;border-radius:999px;color:#aaa;text-decoration:none;font-size:12px;white-space:nowrap;transition:color .18s,border-color .18s}#info-content .member-settings-link:hover{color:var(--accent);border-color:var(--accent)} +#info-page[data-info="account-settings"] .info-layout{display:block}#info-page[data-info="account-settings"] .info-nav{display:none}#info-page[data-info="account-settings"] #info-content{padding:0;border:0;background:transparent;box-shadow:none} +.member-settings{max-width:900px;display:grid;gap:24px}.member-settings-back{font-size:14px;color:#999!important;text-decoration:none}.member-settings-card{padding:28px;border:1px solid #ffffff14;border-radius:18px;background:#191919}#info-content .member-settings-card h2{font-size:18px;color:#eee;margin:0 0 24px}.member-settings-card label{display:block;margin-bottom:12px;font-size:14px;color:#bbb}.member-nickname-editor{display:flex;gap:16px}.member-nickname-editor input{min-width:0;flex:1;padding:12px 16px;height:46px;border:1px solid #363636;border-radius:10px;background:#222;color:#eee;font:inherit}.member-nickname-editor input:focus{outline:1px solid var(--accent);border-color:var(--accent)}.member-nickname-editor button{padding:0 22px;border-radius:999px;border:0;background:var(--accent);color:white;font-size:14px;white-space:nowrap}#info-content #member-nickname-hint{font-size:12px;color:#777;margin:10px 0 0}#info-content #member-nickname-status{font-size:14px;color:var(--accent);margin:8px 0 0;min-height:20px} +.member-settings-action{width:100%;display:flex;align-items:center;justify-content:space-between;gap:20px;padding:20px 0;border:0;background:transparent;color:#888;text-align:left}.member-settings-action+.member-settings-action{border-top:1px solid #ffffff12}.member-settings-action strong{display:block;color:#ddd;font-size:16px;font-weight:500}.member-settings-action small{display:block;margin-top:8px;color:#777;font-size:14px}.member-settings-action:hover strong{color:var(--accent)}.member-settings-trailing{display:flex;gap:16px;align-items:center;font-size:14px}.member-settings-action>span:last-child{flex-shrink:0} +@media(max-width:600px){.member-name-row{gap:8px}.member-settings-card{padding:20px}.member-nickname-editor{gap:10px}.member-nickname-editor button{padding:0 14px}} + + + +#member-auth-dialog{box-sizing:border-box;width:440px;max-width:calc(100vw - 32px);max-height:calc(100dvh - 40px);overflow:auto;padding:36px;border:1px solid #ffffff20;border-radius:24px;background:#19191c;color:#eee;box-shadow:0 24px 100px #0008}#member-auth-dialog::backdrop{background:#000a;backdrop-filter:blur(6px)} +#member-auth-dialog .auth-close{position:absolute;right:18px;top:14px;width:32px;height:32px;background:none;border:0;color:#888;font-size:28px}#member-auth-dialog h2{font-size:24px;margin:0 0 10px}.auth-subtitle{font-size:14px;line-height:1.7;color:#888;margin:0 0 24px}.auth-tabs{display:flex;gap:28px;border-bottom:1px solid #ffffff12;margin-bottom:24px}.auth-tabs button{position:relative;padding:0 0 13px;border:0;background:none;color:#888;font-size:16px}.auth-tabs button[aria-selected="true"]{color:var(--accent)}.auth-tabs button[aria-selected="true"]::after{content:'';position:absolute;bottom:-1px;left:0;right:0;height:2px;background:var(--accent)} +.auth-field{display:block;font-size:14px;color:#bbb;margin-bottom:10px}.auth-input-row{display:flex;align-items:center;gap:10px;margin-bottom:20px}.auth-input-row input{box-sizing:border-box;min-width:0;width:100%;height:48px;padding:0 14px;border:1px solid #38383d;border-radius:10px;background:#222225;color:#eee;font-size:14px}.auth-input-row input:focus{outline:none;border-color:var(--accent)}.auth-input-row input[aria-invalid="true"]{border-color:#ff597b}.auth-code-button{flex-shrink:0;background:transparent;border:0;color:var(--accent);font-size:14px;padding:10px 0}.auth-code-button:disabled{color:#777;cursor:not-allowed}.auth-note{font-size:12px;color:#89898f;line-height:1.7;margin:-8px 0 20px}.auth-error{font-size:14px;color:#ff718b;min-height:20px;margin:0 0 12px}.auth-submit{width:100%;height:46px;border:0;border-radius:999px;background:var(--accent);color:white;font-size:16px;font-weight:500}.auth-agreement{display:flex;align-items:flex-start;gap:8px;margin:18px 0;font-size:12px;line-height:1.7;color:#888}.auth-agreement input{accent-color:var(--accent);margin:3px 0 0}.auth-agreement a{color:#bbb;text-decoration:none}.auth-footer{text-align:center;font-size:12px;color:#666;margin:18px 0 0}.auth-success{padding-top:16px;text-align:center}.auth-success-icon{display:grid;place-items:center;margin:0 auto 22px;width:64px;height:64px;background:#FFC0001a;color:var(--accent);border-radius:50%;font-size:30px}.auth-success p{font-size:14px;color:#aaa;line-height:1.8;margin:18px 0 28px} +@media(max-width:520px){#member-auth-dialog{padding:30px 24px}} + + + +.auth-password-row{position:relative}.auth-password-row input{padding-right:64px}.auth-password-toggle{position:absolute;right:14px;top:50%;transform:translateY(-50%);padding:6px 0;background:none;border:0;color:#999;font-size:12px}.auth-password-toggle:hover{color:var(--accent)} + + + +.auth-password-toggle{width:28px;height:32px;display:grid;place-items:center;padding:0}.auth-password-toggle svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round} +.auth-password-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:5px} +.auth-remember{display:flex;align-items:center;gap:9px;width:fit-content;margin:0 0 16px;color:#a0a0a8;font-size:14px;cursor:pointer}.auth-remember input{appearance:none;-webkit-appearance:none;position:relative;display:block;width:18px;height:18px;margin:0;border:1px solid #64646b;border-radius:4px;background:#252528;cursor:pointer}.auth-remember input:checked{border-color:var(--accent);background:var(--accent)}.auth-remember input:checked::after{content:'';position:absolute;left:5px;top:2px;width:5px;height:9px;border:solid white;border-width:0 2px 2px 0;transform:rotate(45deg)}.auth-remember input:focus-visible{outline:2px solid var(--accent);outline-offset:3px} +#member-auth-dialog .auth-footer{font-size:12px;line-height:1.8;color:#888;margin-top:20px}#member-auth-dialog .auth-footer a{color:#bbb;text-decoration:none}#member-auth-dialog .auth-footer a:hover{color:var(--accent)} + + + +#member-auth-dialog.auth-bind-mode{width:440px;padding:32px;background:#141416;border-radius:24px} +#member-auth-dialog.auth-bind-mode h2{font-size:26px;margin-bottom:10px}.auth-bind-mode .auth-subtitle{margin-bottom:24px;font-size:14px;line-height:1.7;color:#9999a1} +#member-auth-dialog.auth-bind-mode .auth-close{top:16px;right:16px;width:32px;height:32px;display:grid;place-items:center;padding:0;border-radius:50%;background:#202023;color:#96969e;font-size:24px} +.auth-bind-mode .auth-field{font-size:14px;color:#a3a3ac;margin-bottom:8px}.auth-bind-mode .auth-input-row{margin-bottom:18px;position:relative}.auth-bind-mode .auth-input-row input{height:48px;padding-left:44px;border:1px solid transparent;border-radius:12px;background:#232326;font-size:16px}.auth-bind-mode .auth-input-row input:focus{border-color:var(--accent)}.auth-bind-mode .auth-input-row input[aria-invalid="true"]{border-color:#ff597b}.auth-bind-mode .auth-input-row input::placeholder{color:#7d7d87}.auth-bind-mode .auth-password-row input{padding-right:46px} +.auth-icon-row>svg{position:absolute;left:16px;top:50%;transform:translateY(-50%);width:18px;height:18px;fill:none;stroke:#7d7d87;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;pointer-events:none} +.auth-bind-agreement{display:flex;align-items:flex-start;gap:10px;font-size:14px;line-height:1.7;color:#a0a0aa;margin:0 0 14px}.auth-bind-agreement input{appearance:none;flex:0 0 18px;width:18px;height:18px;margin:2px 0 0;border:1px solid #555;border-radius:4px;position:relative;background:transparent;cursor:pointer}.auth-bind-agreement input:checked{background:var(--accent);border-color:var(--accent)}.auth-bind-agreement input:checked::after{content:'';position:absolute;left:5px;top:2px;width:5px;height:9px;border:solid white;border-width:0 2px 2px 0;transform:rotate(45deg)}.auth-bind-agreement input:focus-visible{outline:2px solid var(--accent);outline-offset:3px}.auth-bind-agreement a{color:inherit;text-decoration:underline;text-underline-offset:3px}.auth-bind-agreement a:hover{color:var(--accent)} +.auth-bind-mode .auth-error:empty{display:none}.auth-bind-mode .auth-submit{height:52px;font-size:16px;background:linear-gradient(110deg,#ff4085 0%,#FFC000 56%,#953c20 100%)}.auth-bind-login{display:flex;align-items:center;justify-content:center;gap:8px;margin:20px 0 0;color:#9b9ba5;font-size:14px}.auth-bind-login button{background:none;border:0;padding:0;color:#ff4085;font-size:14px}.auth-bind-login button:hover{color:#ff9b79} +@media(max-width:480px){#member-auth-dialog.auth-bind-mode{padding:28px 22px}.auth-bind-agreement{font-size:12px}} + + +#member-auth-dialog .auth-password-toggle{color:#a3a3ad;background:transparent}#member-auth-dialog .auth-password-toggle .auth-eye-image{display:block;width:22px;height:22px;max-width:none;object-fit:contain;pointer-events:none}#member-auth-dialog .auth-password-toggle:hover .auth-eye-image{opacity:.8} + + +.member-avatar-editor{display:flex;align-items:center;gap:22px;margin-bottom:28px}.member-avatar-preview{position:relative;width:88px;height:88px;flex:0 0 88px;padding:0;border:1px solid #ffffff20;border-radius:50%;overflow:hidden;background:#252528;cursor:pointer}.member-avatar-preview img{display:block;width:100%;height:100%;object-fit:cover}.member-avatar-preview span{position:absolute;bottom:0;left:0;right:0;padding:5px 0 7px;background:#0009;color:white;font-size:12px}.member-avatar-controls button{padding:7px 14px;border:1px solid #ffffff25;border-radius:999px;color:#ccc;background:transparent;font-size:14px}.member-avatar-controls button:hover{color:var(--accent);border-color:var(--accent)}#info-content .member-avatar-controls p{margin:8px 0 0;font-size:12px;color:#888}.member-avatar-controls [data-avatar-action="save"]{background:var(--accent);color:white;border-color:var(--accent)}#member-avatar-confirm{margin-top:10px}#member-avatar-confirm button+button{margin-left:8px}#info-content #member-avatar-status{color:var(--accent)} +@media(max-width:600px){.member-avatar-editor{gap:16px}.member-avatar-preview{width:76px;height:76px;flex-basis:76px}} + + + +#info-page[data-info="deposit"] .info-layout{display:block}#info-page[data-info="deposit"] .info-nav{display:none}#info-page[data-info="deposit"] #info-content{padding:0;background:transparent;border:0;box-shadow:none}.wallet-page{min-width:0}.wallet-balance{color:#999;font-size:14px;margin:-10px 0 28px}.wallet-balance strong{color:#efca75;font-size:22px;margin:0 5px} +.wallet-hero{padding:30px 32px;border:1px solid #ffffff18;border-radius:18px;background:#ffffff08;margin-bottom:30px}.wallet-hero>span{display:inline-block;color:var(--accent);font-size:12px;background:#FFC00013;border-radius:999px;padding:5px 12px}#info-content .wallet-hero h2{margin:14px 0 10px;font-size:26px;color:#f1f1f1}#info-content .wallet-hero p{font-size:14px;color:#999;margin:0}.wallet-section-title,#info-content .wallet-section-title{font-size:20px;color:#eee;margin:0 0 18px}.wallet-pack-panel{padding:26px;border:1px solid #ffffff18;border-radius:18px;background:#ffffff06}.wallet-pack-heading{display:flex;align-items:center;gap:14px;margin-bottom:22px}.wallet-pack-heading strong{color:#eee;font-size:18px}.wallet-pack-heading>span{color:#888;font-size:14px}.wallet-pack-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px}.wallet-package{position:relative;display:flex;flex-direction:column;align-items:flex-start;gap:12px;min-width:0;padding:24px;border:1px solid #ffffff1c;border-radius:15px;background:#ffffff06;text-align:left;transition:border-color .15s,background .15s}.wallet-package:hover{border-color:#FFC00080;background:#FFC00009}.wallet-package[aria-pressed="true"]{border-color:var(--accent);box-shadow:inset 0 0 0 1px var(--accent);background:#FFC0000d}.wallet-package-price{font-size:30px;font-weight:650;color:#eee}.wallet-package-name{font-size:18px;color:#ddd}.wallet-package-hint{font-size:12px;color:#888}.wallet-package-hint em{font-style:normal;color:#e7b96d}.wallet-package-check{position:absolute;right:14px;top:14px;width:21px;height:21px;display:none;place-items:center;border-radius:50%;background:var(--accent);color:white;font-size:14px}.wallet-package[aria-pressed="true"] .wallet-package-check{display:grid}.wallet-pay-area{text-align:center;margin:28px 0 38px}.wallet-submit{min-width:250px;padding:14px 30px;border:0;border-radius:999px;background:var(--accent);color:white;font-size:16px;font-weight:600}#info-content #wallet-selection{font-size:14px;color:#888;margin:12px 0 0}.wallet-empty{text-align:center;padding:38px 20px;border:1px solid #ffffff12;background:#ffffff03;border-radius:16px;color:#666}.wallet-empty svg{width:32px;height:32px;stroke:currentColor}#info-content .wallet-empty p{font-size:14px;color:#777;margin:10px 0 0}#info-content .wallet-support{text-align:center;font-size:14px;color:#777;margin:22px 0 0}.wallet-support a{color:var(--accent)} +#wallet-payment-dialog{width:440px;max-width:calc(100vw - 32px);box-sizing:border-box;background:#19191c;border:1px solid #ffffff20;border-radius:22px;padding:32px;color:#eee}#wallet-payment-dialog::backdrop{background:#0009;backdrop-filter:blur(5px)}.wallet-dialog-close{position:absolute;right:16px;top:14px;background:none;border:0;color:#999;font-size:26px}.wallet-payment-title{font-size:24px;margin:0 0 25px}.wallet-order{padding:20px;border-radius:14px;background:#ffffff05;display:grid;gap:10px}.wallet-order>span{font-size:14px;color:#999}.wallet-order strong{font-size:24px}.wallet-order small{color:#dfb769;font-size:12px}.wallet-order b{font-size:30px;color:var(--accent);margin-top:4px}.wallet-method-title{font-size:16px;margin:24px 0 14px}.wallet-methods{display:flex;gap:12px}.wallet-methods button{flex:1;display:flex;justify-content:center;align-items:center;gap:8px;padding:14px 8px;border:1px solid #ffffff24;border-radius:12px;background:#242427;color:#bbb}.wallet-methods button[aria-pressed="true"]{border-color:var(--accent);color:#eee;background:#FFC0000c}.wallet-method-symbol{display:grid;place-items:center;width:24px;height:24px;border-radius:6px;background:#1677ff;color:white;font-size:16px}.wallet-method-symbol.wechat{background:#19ad60}.wallet-payment-go{width:100%;margin-top:26px}.wallet-payment-note{font-size:12px;color:#777;line-height:1.8;text-align:center;margin:14px 0 0}.wallet-payment-status{font-size:14px;color:#dca5bf;line-height:1.7}.wallet-payment-status:empty{display:none} +@media(max-width:900px){.wallet-pack-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}@media(max-width:600px){.wallet-hero{padding:24px}.wallet-pack-panel{padding:18px}.wallet-pack-grid{gap:12px}.wallet-package{padding:20px 14px}.wallet-package-price{font-size:26px}.wallet-package-name{font-size:16px}.wallet-package-hint{font-size:12px}.wallet-pack-heading{align-items:flex-start;flex-direction:column;gap:8px}.wallet-submit{width:100%;min-width:0}#wallet-payment-dialog{padding:28px 24px}} + + + +#wallet-payment-dialog{width:440px;max-height:calc(100dvh - 32px);overflow:auto;padding:34px 32px;border:1px solid #ffffff28;border-radius:36px;background:#0e0e0f;box-shadow:0 24px 100px #0005} +#wallet-payment-dialog::backdrop{background:#0008;backdrop-filter:blur(9px)}#wallet-payment-dialog .wallet-dialog-close{font-size:20px;right:16px;top:12px;width:24px;height:24px;padding:0;color:#888} +.wallet-payment-heading{display:flex;align-items:center;gap:12px;margin-bottom:20px}.wallet-payment-heading .wallet-payment-title{font-size:24px;line-height:1.4;margin:0}.wallet-payment-heading>span{font-size:16px;color:#929292} +#wallet-payment-dialog .wallet-order{display:flex;align-items:center;justify-content:space-between;gap:14px;padding:18px 16px;border:1px solid #ffffff0c;border-radius:20px;background:#1a1a1b}.wallet-order-copy{display:grid;gap:12px}.wallet-order-copy strong{font-size:20px;font-weight:400;line-height:1.4}.wallet-order-copy>span{font-size:14px;color:#909090}.wallet-order-copy small{font-size:12px;line-height:1.6;color:#bda578}#wallet-payment-dialog .wallet-order>b{flex-shrink:0;display:grid;place-items:center;min-width:78px;padding:10px 16px;margin:0;background:#FFC00010;border-radius:999px;color:var(--accent);font-size:22px;font-weight:600} +#wallet-payment-dialog .wallet-method-title{font-size:18px;margin:20px 0 18px}#wallet-payment-dialog .wallet-methods{display:flex;flex-direction:column;gap:12px}#wallet-payment-dialog .wallet-methods button{display:flex;align-items:center;justify-content:flex-start;flex:none;gap:12px;width:100%;padding:14px 16px;min-height:66px;border:1px solid #ffffff0d;border-radius:20px;background:#1a1a1b;color:#f5f5f5;text-align:left}#wallet-payment-dialog .wallet-methods button:hover{border-color:#FFC00080}#wallet-payment-dialog .wallet-methods button[aria-pressed="true"]{border-color:var(--accent)}.wallet-method-name{flex:1;font-size:16px;font-weight:500}.wallet-method-check{display:grid;place-items:center;width:20px;height:20px;flex:0 0 20px;border-radius:50%;background:#333;color:white;font-size:14px}.wallet-methods [aria-pressed="true"] .wallet-method-check{background:var(--accent)} +.wallet-pay-logo{width:36px;height:36px;flex:0 0 36px;display:grid;place-items:center;border-radius:50%;color:white}.wallet-pay-logo.alipay{background:#3459ed;font-size:28px;font-family:var(--font-ui)}.wallet-pay-logo.wechat{background:#11bb0d}.wallet-pay-logo.usdt{background:#269f80}.wallet-pay-logo svg{width:29px;height:29px;stroke:none} +#wallet-payment-dialog .wallet-payment-go{min-width:0;width:100%;height:50px;margin-top:18px;padding:0;border-radius:12px;font-size:18px;font-weight:500}#wallet-payment-dialog .wallet-payment-note{font-size:12px;line-height:1.7;margin:18px 0 0;color:#858585} +#wallet-payment-dialog .wallet-dialog-close{display:grid;place-items:center} +#wallet-payment-dialog .wallet-order-copy{min-width:0;overflow-wrap:anywhere} +#wallet-payment-dialog .wallet-methods button:disabled,#wallet-payment-dialog .wallet-payment-go:disabled{opacity:.6;cursor:not-allowed} +#wallet-payment-dialog .wallet-pay-logo--other{background:#39424b;color:#fff} +#wallet-payment-dialog .wallet-pay-logo--other svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:1.8} +#wallet-payment-dialog .wallet-payment-link{display:flex;margin-top:18px;border-radius:12px} +@media(max-width:480px){#wallet-payment-dialog{padding:30px 24px;border-radius:28px}.wallet-payment-heading .wallet-payment-title{font-size:22px}.wallet-payment-heading>span{font-size:14px}} + + +.wallet-pay-area>.wallet-submit{width:420px;max-width:100%;min-width:0}@media(max-width:600px){.wallet-pay-area>.wallet-submit{width:100%}} +.wallet-pay-area>.wallet-submit:disabled{opacity:.5;cursor:not-allowed}.wallet-pack-empty{grid-column:1/-1}.wallet-balance-copy{min-width:0}.wallet-balance-amount{flex-wrap:wrap;overflow-wrap:anywhere}.wallet-transaction-row>div{min-width:0;overflow-wrap:anywhere} + + +.wallet-balance-card{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(300px,1fr);align-items:center;gap:40px;padding:36px 38px;margin-bottom:30px;border:1px solid #ffffff16;border-radius:24px;background:linear-gradient(110deg,#2c1a10 0%,#1b140f 48%,#141416 100%)}.wallet-balance-summary{display:flex;align-items:center;gap:26px;min-width:0}.wallet-large-coin{display:block;flex:0 0 130px;width:130px;height:140px;object-fit:contain}.wallet-balance-copy>span{font-size:14px;color:#8b8391}.wallet-balance-amount{display:flex;align-items:baseline;gap:16px;margin:8px 0}.wallet-balance-amount strong{font-size:48px;line-height:1.2;font-weight:600;color:#ecd079}.wallet-balance-amount b{font-size:18px;color:#eee;font-weight:500}#info-content .wallet-balance-copy p{font-size:14px;color:#8b8391;line-height:1.7;margin:0}#info-content .wallet-usage h2{font-size:14px;font-weight:400;color:#8a8490;margin:0 0 14px}.wallet-usage{min-width:0}.wallet-usage-item{display:flex;align-items:flex-start;gap:10px;margin-top:12px}.wallet-usage-item svg{width:20px;height:20px;flex:0 0 20px;fill:none;stroke:#dec269;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}.wallet-usage-item strong{font-size:14px;font-weight:600;line-height:1.5;color:#ddd}#info-content .wallet-usage-item p{font-size:14px;line-height:1.6;color:#847f8b;margin:3px 0 0} +@media(max-width:1000px){.wallet-balance-card{grid-template-columns:1fr;gap:28px;padding:28px}.wallet-usage{padding-top:22px;border-top:1px solid #ffffff0e}.wallet-large-coin{width:105px;height:115px;flex-basis:105px}} +@media(max-width:600px){.wallet-balance-card{padding:24px 20px;border-radius:20px}.wallet-balance-summary{gap:16px}.wallet-large-coin{width:80px;height:90px;flex-basis:80px}.wallet-balance-amount strong{font-size:38px}.wallet-balance-amount b{font-size:16px}#info-content .wallet-balance-copy p{font-size:12px}} + + + +.wallet-balance-coin{flex:0 0 110px;width:110px;height:110px;box-sizing:border-box;border-width:5px;font-size:68px;box-shadow:inset 0 0 0 7px #ffd563} +@media(max-width:1000px){.wallet-balance-coin{flex-basis:96px;width:96px;height:96px;font-size:58px}} +@media(max-width:600px){.wallet-balance-coin{flex-basis:76px;width:76px;height:76px;border-width:4px;font-size:46px;box-shadow:inset 0 0 0 5px #ffd563}} + + + +.wallet-package-coins{display:flex;align-items:center;gap:12px;max-width:100%;padding-right:12px} +.wallet-package .wallet-package-coin{flex:0 0 32px;width:32px;height:32px;box-sizing:border-box;font-size:20px} +.wallet-package .wallet-package-name{display:flex;align-items:baseline;flex-wrap:wrap;gap:6px;font-size:30px;line-height:1.3;font-weight:650;color:#f0d281} +.wallet-package-name small{font-size:18px;font-weight:500} +.wallet-package .wallet-package-price{font-size:18px;line-height:1.4;font-weight:500;color:#c5c5ca} +@media(max-width:1180px){.wallet-package-coins{gap:8px}.wallet-package .wallet-package-name{font-size:26px}.wallet-package-name small{font-size:16px}} +@media(max-width:600px){.wallet-package .wallet-package-coin{flex-basis:26px;width:26px;height:26px;font-size:16px}.wallet-package .wallet-package-name{font-size:24px}.wallet-package-name small{font-size:14px}.wallet-package .wallet-package-price{font-size:16px}.wallet-package-coins{gap:7px;padding-right:0}} + + +.coin-symbol .coin-soft-star{display:block;width:76%;height:76%;object-fit:contain;pointer-events:none;filter:drop-shadow(0 1px 0 #fff5b0)} + + +.wallet-package{overflow:hidden;padding-bottom:32px}.wallet-package .wallet-package-hint{line-height:1.6}.wallet-package:has(.wallet-package-recommend) .wallet-package-hint{padding-right:62px}.wallet-package .wallet-package-recommend{position:absolute;right:-12px;bottom:-22px;width:96px;height:96px;object-fit:contain;pointer-events:none} +@media(max-width:600px){.wallet-package{padding-bottom:30px}.wallet-package .wallet-package-recommend{width:76px;height:76px;right:-9px;bottom:-18px}.wallet-package:has(.wallet-package-recommend) .wallet-package-hint{padding-right:45px}} + + + +.wallet-transaction-list{padding:0 22px;border:1px solid #ffffff10;border-radius:16px;background:#1a1a1b}.wallet-transaction-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:22px 0}.wallet-transaction-row+.wallet-transaction-row{border-top:1px solid #ffffff0b}.wallet-transaction-row strong{display:block;color:#e5e5e5;font-size:16px;font-weight:400;line-height:1.5}.wallet-transaction-row time{display:block;margin-top:12px;color:#85858b;font-size:14px}.wallet-transaction-amount{font-size:18px;font-weight:500;white-space:nowrap;font-variant-numeric:tabular-nums}.wallet-transaction-amount.income{color:#00c780}.wallet-transaction-amount.expense{color:#ff3d43}#info-content .wallet-demo-records{font-size:12px;color:#707076;margin:12px 0 0} +@media(max-width:600px){.wallet-transaction-list{padding:0 16px}.wallet-transaction-row{padding:18px 0}} + + + +#drama-play-page[hidden]{display:none}#drama-play-page{padding-top:30px}.play-detail-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:28px;margin-top:26px}.play-detail-main{min-width:0}.play-screen{position:relative;width:100%;aspect-ratio:16/9;overflow:hidden;border-radius:16px;background:#050505}.play-screen video{display:block;width:100%;height:100%;object-fit:contain}.play-screen-overlay{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:14px;background:#0008;padding:20px;text-align:center}.play-screen-overlay button{width:66px;height:66px;border:1px solid #ffffff60;border-radius:50%;background:#FFC000dd;color:white;font-size:28px}.play-screen-overlay p{font-size:14px;color:#ccc;margin:0}.play-screen-controls{position:absolute;bottom:0;left:0;right:0;display:flex;align-items:center;gap:16px;padding:14px 18px;background:linear-gradient(transparent,#000d);color:#aaa;font-size:12px}.play-screen-controls button{background:none;border:0;color:#ddd;font-size:14px}.play-screen-controls span{flex:1}.play-advertisements{display:grid;grid-template-columns:repeat(8,minmax(0,1fr));gap:12px;padding:22px 0}.play-advertisements a{display:block;text-align:center;color:#999;font-size:12px;min-width:0;text-decoration:none}.play-advertisements img{width:100%;aspect-ratio:1;object-fit:cover;border-radius:22%;display:block;margin-bottom:7px}.play-advertisements span{display:block;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.play-detail-info{padding:4px 0 26px;border-bottom:1px solid #ffffff12}.play-detail-info h1{font-size:26px;line-height:1.5;margin:8px 0 14px;color:#eee}.play-episode-caption{color:var(--accent);font-size:14px}.play-detail-meta{display:flex;flex-wrap:wrap;gap:12px;color:#8d8d96;font-size:14px;margin:12px 0}.play-detail-tags{display:flex;align-items:center;gap:10px;flex-wrap:wrap;color:#888;font-size:14px}.play-detail-tags a{color:#c49bad;background:#FFC00010;border-radius:6px;padding:4px 9px;text-decoration:none}.play-detail-description{font-size:14px;color:#999;line-height:1.9;margin:14px 0 20px}.play-detail-actions{display:flex;gap:12px;align-items:center;flex-wrap:wrap}.play-detail-actions button,.play-detail-actions a{display:inline-flex;align-items:center;gap:7px;background:#ffffff06;border:1px solid #ffffff15;border-radius:999px;padding:9px 17px;color:#bbb;font-size:14px;text-decoration:none}.play-detail-actions [aria-pressed="true"]{color:var(--accent);border-color:#FFC00050}.play-detail-actions .play-download{margin-left:auto;background:var(--accent);color:#fff;border-color:var(--accent)}.play-episodes-section{padding:26px 0}.play-section-heading{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:18px}.play-section-heading h2,.play-related h2{font-size:20px;color:#eee;margin:0}.play-section-heading span{font-size:14px;color:#888}.play-episode-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(44px,1fr));gap:10px;max-height:254px;overflow:auto;padding:2px 4px 4px 2px}.play-episode-grid a{display:grid;place-items:center;aspect-ratio:1;min-height:44px;border-radius:9px;background:#252527;color:#bbb;text-decoration:none;font-size:14px;border:1px solid transparent}.play-episode-grid a:hover{color:var(--accent);border-color:#FFC00070}.play-episode-grid a[aria-current="true"]{background:var(--accent);color:white}.play-official{padding:20px 22px;border:1px solid #ffffff10;border-radius:14px;background:#ffffff04;color:#999;font-size:14px;line-height:2}.play-official strong{color:#ddd;display:block;margin-bottom:6px}.play-official a{color:var(--accent);margin-right:18px}.play-comments{margin-top:28px}.play-comment-form textarea{box-sizing:border-box;display:block;width:100%;height:100px;resize:vertical;padding:15px;border:1px solid #ffffff20;border-radius:12px;background:#202023;color:#eee;font:inherit;font-size:14px}.play-comment-form textarea:focus{outline:1px solid var(--accent)}.play-comment-footer{display:flex;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;color:#777;font-size:12px}.play-comment-footer button{border:0;border-radius:999px;background:var(--accent);color:white;padding:10px 23px}.play-comment-list{margin-top:20px}.play-comment-item{display:flex;gap:12px;padding:18px 0;border-bottom:1px solid #ffffff0c}.play-comment-item img{width:36px;height:36px;object-fit:cover;border-radius:50%}.play-comment-item strong{font-size:14px;color:#ccc}.play-comment-item time{font-size:12px;color:#777;margin-left:10px}.play-comment-item p{font-size:14px;color:#aaa;line-height:1.7;margin:8px 0 0;white-space:pre-wrap;overflow-wrap:anywhere}.play-comments-empty{color:#777;text-align:center;font-size:14px;padding:28px}.play-related{border-left:1px solid #ffffff10;padding-left:24px;min-width:0}.play-related h2{margin-bottom:18px}.play-related .card{display:block;width:100%;margin-bottom:24px;opacity:1;animation:none;text-align:left}.play-related .card :is(h2,h3){font-size:16px}.play-related .cover{aspect-ratio:640/914;display:block;overflow:hidden;border-radius:12px}.play-related .poster-source{width:100%;height:100%;object-fit:cover}.play-related .drama-description{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;font-size:12px;color:#888;line-height:1.6}.play-related .drama-meta{font-size:12px}.play-sort button{background:none;border:0;color:#777;padding:4px 8px}.play-sort button[aria-pressed="true"]{color:var(--accent)} +@media(max-width:1100px){.play-detail-layout{grid-template-columns:minmax(0,1fr) 230px;gap:20px}.play-related{padding-left:18px}.play-detail-info h1{font-size:22px}} +@media(max-width:800px){.play-detail-layout{grid-template-columns:minmax(0,1fr)}.play-related{border:0;padding:0}.play-related-list{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}.play-advertisements{grid-template-columns:repeat(4,minmax(0,1fr))}.play-detail-actions .play-download{margin-left:0}.play-detail-info h1{font-size:20px}.play-screen{border-radius:10px}.play-screen-overlay button{width:48px;height:48px;font-size:22px}} + + + +.play-advertisements{grid-template-columns:repeat(14,minmax(0,1fr));gap:14px}.play-episode-grid{max-height:none;overflow:visible;padding:0;grid-template-columns:repeat(auto-fill,minmax(44px,1fr));gap:10px}.play-episode-toggle{display:grid;place-items:center;aspect-ratio:1;min-height:44px;padding:0;border:1px solid transparent;border-radius:9px;background:#252527;color:#ddd}.play-episode-toggle svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.7}.play-episode-toggle:hover{color:var(--accent);border-color:#FFC00070}.play-episode-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:2px} +@media(max-width:1180px){.play-advertisements{grid-template-columns:repeat(7,minmax(0,1fr));gap:18px 14px}} +@media(max-width:760px){.play-advertisements{grid-template-columns:repeat(4,minmax(0,1fr));gap:18px 14px}} + + + +.play-official{padding:28px 30px;background:#1a1a1b;border:0;border-radius:20px;font-size:14px;line-height:1.7}.play-official-line{margin-bottom:20px}.play-official-line:last-child{margin-bottom:0}.play-official-line>span{display:block;color:#c1c1c6}.play-official-line a{display:inline-block;color:var(--accent);text-decoration:none;margin:0;overflow-wrap:anywhere}.play-official-line a:hover{text-decoration:underline}.play-official-inline{display:flex;gap:6px;flex-wrap:wrap} +.play-comments{margin-top:28px;padding:26px;background:#1a1a1b;border-radius:20px}.play-comments .play-section-heading{margin-bottom:18px}.play-comments .play-section-heading h2{font-size:16px;font-weight:500}.play-sort{padding:5px;background:#252527;border-radius:9px}.play-sort button{font-size:14px;padding:5px 8px}.play-sort button[aria-pressed="true"]{color:#eee}.play-sort button:hover{color:var(--accent)} +.play-comment-form{display:flex;gap:14px;padding:14px;background:#252527;border-radius:13px}.play-compose-avatar{width:38px;height:38px;flex:0 0 38px;object-fit:cover;border-radius:50%}.play-compose-body{flex:1;min-width:0}.play-comment-form textarea{height:66px;min-height:66px;border:0;border-radius:9px;background:#090909;padding:12px 14px;font-size:14px}.play-comment-footer{margin-top:12px}.play-comment-footer>span{display:flex;align-items:center;gap:6px;flex-wrap:wrap;font-size:12px}.play-comment-footer em{font-style:normal;color:var(--accent)}.play-comment-footer button{padding:8px 20px;font-size:14px;white-space:nowrap}.play-comment-list{margin-top:12px}.play-comment-item{padding:20px 0;border:0;gap:12px}.play-comment-item>img,.play-comment-avatar{width:36px;height:36px;flex:0 0 36px;border-radius:50%}.sample-avatar{display:grid;place-items:center;color:#eedbe5;font-size:16px}.sample-avatar-0{background:linear-gradient(135deg,#665665,#372d26)}.sample-avatar-1{background:linear-gradient(135deg,#6d6655,#37322c)}.sample-avatar-2{background:linear-gradient(135deg,#506570,#27343a)}.play-comment-body{min-width:0;flex:1}.play-comment-body>strong{font-size:14px;color:#929299;font-weight:400}.play-comment-body>p{margin:8px 0 6px;font-size:14px;line-height:1.7;color:#ccc}.play-comment-meta{display:flex;align-items:center;gap:12px;flex-wrap:wrap;color:#85858d}.play-comment-meta time{margin:0;font-size:12px}.play-comment-meta button{padding:0;border:0;background:none;color:#888;font-size:12px}.play-comment-meta button:hover,.play-comment-meta button[aria-pressed="true"]{color:var(--accent)} +@media(max-width:600px){.play-official{padding:22px}.play-comments{padding:20px 16px}.play-comment-form{gap:10px;padding:12px}.play-compose-avatar{width:30px;height:30px;flex-basis:30px}.play-comment-footer{align-items:flex-start;gap:10px;flex-wrap:wrap}.play-comment-footer button{margin-left:auto}} + + + +/* Shared soft radial backdrop, matching the reference detail page. */ +html{background:#131414} +body{min-height:100vh;background-color:#131414;background-image:radial-gradient(79.79% 63.64% at 51.35% -3.04%,rgba(250,119,5,.1) 0%,rgba(250,119,5,0) 100%),radial-gradient(37.71% 88.68% at 111.22% -1.29%,rgba(58,227,221,.1) 0%,rgba(58,227,221,0) 100%);background-size:100% 920px;background-position:center top;background-repeat:no-repeat} +#channel-page .return-hero{display:flex;align-items:center;justify-content:space-between;gap:32px} +#channel-page .return-hero-copy{flex:1;min-width:0} +#channel-page .return-hero-copy p{margin-bottom:0} +#channel-page .return-hero .return-actions{flex:0 0 auto;justify-content:flex-end;margin-left:auto} +@media(max-width:900px){#channel-page .return-hero{gap:24px}#channel-page .return-hero .return-actions{flex-direction:column;align-items:stretch}#channel-page .return-hero .return-actions>a,#channel-page .return-hero .return-actions>button{justify-content:center}} +@media(max-width:600px){body{background-size:100% 700px}#channel-page .return-hero{flex-wrap:wrap;gap:22px}#channel-page .return-hero .return-actions{flex-direction:row;flex-wrap:wrap;justify-content:flex-end;width:100%}#channel-page .return-hero .return-actions>a,#channel-page .return-hero .return-actions>button{padding:11px 16px}} + + + +#drama-play-page .series-overview{display:grid;grid-template-columns:240px minmax(0,1fr);gap:32px;align-items:start;padding:30px;border:1px solid #ffffff0d;border-radius:20px;background:linear-gradient(125deg,#35292388,#1b1b1bd9 65%)} +#drama-play-page .series-poster{display:block;width:100%;height:auto;aspect-ratio:640/914;object-fit:cover;border-radius:12px} +#drama-play-page .series-info{min-width:0;padding-top:4px} +#drama-play-page .series-title-row{display:flex;align-items:baseline;flex-wrap:wrap;gap:12px 18px} +#drama-play-page .series-title-row h1{margin:0;color:#f5f5f5;font-size:28px;font-weight:600;line-height:1.5} +#drama-play-page .series-heat{display:inline-flex;align-items:center;gap:6px;font-size:14px;color:#999;white-space:nowrap} +#drama-play-page .series-heat-icon{width:16px;height:16px} +#drama-play-page .series-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:20px} +#drama-play-page .series-tags span{padding:5px 9px;border-radius:5px;background:#ffffff08;color:#aaa;font-size:14px} +#drama-play-page .series-tags .series-stat{color:#d4a158;background:#d4a15812} +#drama-play-page .series-tags .series-badge--new{color:#a097ff;background:#7065ff1a} +#drama-play-page .series-tags .series-badge--hot{color:#ff6878;background:#ff4d601a} +#drama-play-page .series-tags .series-badge--free{color:#12d3bd;background:#0bbba91a} +#drama-play-page .series-genres{margin:18px 0 0;font-size:14px;color:#999} +#drama-play-page .series-description{margin:18px 0 26px;font-size:14px;line-height:1.9;color:#aaa} +#drama-play-page .series-actions{display:flex;align-items:center;flex-wrap:wrap;gap:12px} +#drama-play-page .series-actions>a,#drama-play-page .series-actions>button{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:46px;box-sizing:border-box;padding:12px 22px;border-radius:999px;font-size:14px;text-decoration:none;color:#fff} +#drama-play-page .series-actions .detail-play{background:var(--accent)} +#drama-play-page .series-actions .detail-play:hover{filter:brightness(1.1)} +#drama-play-page .series-actions svg{width:18px;height:18px;fill:currentColor} +#drama-play-page .series-download{border:1px solid #ffffff15;background:#ffffff05} +#drama-play-page .series-download:hover{background:#ffffff10} +#drama-play-page.is-overview .play-advertisements{padding-bottom:0} +#drama-play-page.is-overview .series-action-bar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:18px 24px} +#drama-play-page.is-overview .series-action-bar .play-detail-actions{margin-left:auto;gap:10px} +@media(max-width:760px){#drama-play-page.is-overview .series-action-bar .play-detail-actions{margin-left:0}} + +@media(max-width:1180px){#drama-play-page .series-overview{grid-template-columns:180px minmax(0,1fr);padding:24px;gap:24px}#drama-play-page .series-title-row h1{font-size:24px}} +@media(max-width:600px){#drama-play-page .series-overview{grid-template-columns:112px minmax(0,1fr);padding:18px;gap:16px}#drama-play-page .series-title-row h1{font-size:20px}#drama-play-page .series-description{font-size:14px}#drama-play-page .series-actions>a,#drama-play-page .series-actions>button{padding:10px 14px;font-size:14px}#drama-play-page .series-tags{margin-top:12px}} + + +.route-not-found{min-height:100svh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;padding:24px;text-align:center} +.route-not-found h1,.route-not-found p{margin:0} +[data-app-download-entry][hidden]{display:none!important} + + +#query::-webkit-search-cancel-button{-webkit-appearance:none;width:14px;height:14px;cursor:pointer;background:#b8b8bd;mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m4 4 8 8m0-8-8 8' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat;-webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='m4 4 8 8m0-8-8 8' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center/contain no-repeat} +#search-panel-close{color:#b8b8bd!important} + + + +#channel-page[data-channel="rank"] .channel-tabs button{border:1px solid #ffffff0c;font-weight:400;box-sizing:border-box;transition:background .2s,border-color .2s,box-shadow .2s,filter .2s} +#channel-page[data-channel="rank"] .channel-tabs button[aria-pressed="true"]{background:var(--action-fill);border-color:var(--action-stroke);color:#fff;box-shadow:inset 0 1px 0 #ffffff16;font-weight:400} +#channel-page[data-channel="rank"] .channel-tabs button[aria-pressed="true"]:hover{background:var(--action-fill);box-shadow:inset 0 1px 0 #ffffff20,0 3px 12px #FFC00020} +#channel-page[data-channel="rank"] .channel-tabs button:active{filter:brightness(.94)} + + +.bottom-app-download{color:#191919} + + +.side .sidebar-header .sidebar-brand-window{width:121.5px;height:42.768px} +.side .sidebar-header .sidebar-brand{width:121.5px;height:42.768px;object-fit:contain} +.download-panel-brand .download-brand-complete,#entry-navigation img.entry-brand-logo{display:block;width:180px;height:auto;aspect-ratio:500/176;object-fit:contain} +.side-info .side-info-wordmark{width:90px;height:auto;aspect-ratio:500/176} +.hg-footer-original-title>img{width:124px;height:auto;aspect-ratio:500/176} +@media(max-width:760px){.side .sidebar-header .sidebar-brand-window,.side .sidebar-header .sidebar-brand{width:112px;height:39.424px}} +@media(min-width:761px){.sidebar-collapsed .side .sidebar-header .sidebar-brand-window{width:36px;height:36px}.sidebar-collapsed .side .sidebar-header .sidebar-brand{content:url("/assets/160ae2de19037355.svg");width:36px;height:36px;object-fit:contain}} + + +#global-site-header .hg-header-brand{height:44px}#global-site-header .hg-header-brand .hg-complete-logo{width:125px;height:44px;aspect-ratio:500/176;object-fit:contain} + + +.member-library-item[hidden]{display:none!important} +.member-history-pagination{display:flex;justify-content:center;align-items:center;gap:14px;flex-wrap:wrap;margin:32px 0 8px} +.member-history-pagination button{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:46px;min-width:46px;height:46px;padding:0;border:0;border-radius:50%;background:#262628;color:#aaa!important;font:inherit;font-size:16px;cursor:pointer;transition:background .16s,color .16s} +.member-history-pagination button:hover:not(:disabled){color:#fff!important;background:#363638} +.member-history-pagination button[aria-current=page]{background:var(--accent);color:#fff!important} +.member-history-pagination button:disabled{opacity:.4;cursor:default} +.member-history-pagination .pagination-ellipsis{color:#909096;padding:0 5px} +.member-history-pagination .history-page-jump{display:flex;align-items:center;gap:12px;margin-left:8px;color:#929298;font-size:16px} +.member-history-pagination input{box-sizing:border-box;width:70px;height:46px;border:1px solid transparent;border-radius:999px;background:#262628;color:#fff;text-align:center;font:inherit;outline:none;appearance:textfield} +.member-history-pagination input::-webkit-inner-spin-button,.member-history-pagination input::-webkit-outer-spin-button{appearance:none;margin:0} +.member-history-pagination input:focus{border-color:var(--accent)} +.member-history-pagination .history-page-confirm{width:94px;border-radius:999px;background:var(--accent);color:#fff!important} +.member-history-pagination .history-page-confirm:hover{background:var(--accent);filter:brightness(1.1)} +.member-history-pagination button:focus-visible{outline:2px solid var(--accent);outline-offset:4px} +@media(max-width:600px){.member-history-pagination{gap:9px}.member-history-pagination button{width:38px;min-width:38px;height:38px}.member-history-pagination .history-page-jump{justify-content:center;flex-basis:100%;margin:6px 0 0}.member-history-pagination input{height:38px}.member-history-pagination .history-page-confirm{width:80px}} + + + +#drama-play-page .play-screen-controls{display:block;padding:30px 20px 12px;background:linear-gradient(transparent,rgba(0,0,0,.88));z-index:4;color:#eee} +#drama-play-page .player-control-row{display:flex;align-items:center;gap:14px;min-height:38px} +#drama-play-page .player-control-row button{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:34px;padding:0 4px;border:0;border-radius:6px;background:transparent;color:#eee;font-size:14px;cursor:pointer} +#drama-play-page .player-control-row button:hover{color:var(--accent,#FFC000);background:#ffffff12} +#drama-play-page .player-control-row button:disabled{opacity:.35;cursor:default} +#drama-play-page .player-control-row button:focus-visible,#drama-play-page .player-range:focus-visible{outline:2px solid #FFC000;outline-offset:3px} +#drama-play-page .player-control-row svg,#drama-play-page .play-screen-overlay button svg{display:block;width:22px;height:22px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round} +#drama-play-page .player-control-row .player-time{flex:none;font-size:12px;font-variant-numeric:tabular-nums;white-space:nowrap;color:#ddd} +#drama-play-page .player-control-spacer{flex:1} +#drama-play-page .player-range{display:block;appearance:none;border:0;border-radius:8px;height:4px;padding:0;min-height:0;background:linear-gradient(to right,#FFC000 var(--fill,0%),#ffffff45 var(--fill,0%));cursor:pointer;accent-color:#FFC000} +#drama-play-page .player-range::-webkit-slider-thumb{appearance:none;width:11px;height:11px;border:0;border-radius:50%;background:#fff} +#drama-play-page .player-range::-moz-range-thumb{width:11px;height:11px;border:0;border-radius:50%;background:#fff} +#drama-play-page .player-seek{width:100%;margin:0 0 10px} +#drama-play-page .player-seek:disabled{cursor:default;opacity:.55} +#drama-play-page .player-volume{display:flex;align-items:center;gap:6px} +#drama-play-page .player-volume input{width:65px;margin:0} +#drama-play-page .player-menu-wrap{position:relative} +#drama-play-page .player-options{position:absolute;bottom:46px;right:0;width:128px;padding:7px;background:#202023f5;box-shadow:0 4px 20px #0006;border:1px solid #ffffff15;border-radius:10px} +#drama-play-page .player-options[hidden]{display:none} +#drama-play-page .player-options button{display:flex;width:100%;height:34px;font-size:14px} +#drama-play-page .player-options button[aria-checked=true]{color:#FFC000;background:#FFC00014} +#drama-play-page .player-options small{display:block;color:#999;font-size:12px;text-align:center;padding:5px 0} +#drama-play-page .play-screen-overlay[hidden]{display:none} +#drama-play-page .play-screen-overlay{pointer-events:none} +#drama-play-page .play-screen-overlay button{pointer-events:auto;display:grid;place-items:center} +#drama-play-page .play-screen-overlay button svg{width:30px;height:30px} +#drama-play-page .play-screen-overlay button[data-show-unlock]{width:auto;height:auto;min-height:44px;padding:10px 20px;border-radius:999px;font-size:14px} +#drama-play-page .play-screen:fullscreen{border-radius:0;width:100vw;height:100vh;aspect-ratio:auto;background:#000} +@media(max-width:600px){#drama-play-page .play-screen-controls{padding:24px 10px 8px}#drama-play-page .player-control-row{gap:5px}#drama-play-page .player-volume input{width:38px}#drama-play-page .player-time{font-size:10px}#drama-play-page .player-control-row button{font-size:12px}} + +#drama-play-page .play-screen-overlay button[data-media="toggle"]{background:transparent;box-shadow:none;width:76px;height:76px;padding:0;color:#eee;border:0} +#drama-play-page .play-screen-overlay button[data-media="toggle"] svg{width:72px;height:72px} +#drama-play-page .play-screen-overlay button[data-media="toggle"]:hover{color:#FFC000} + + + +#drama-play-page .play-episode-pages{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 20px} +#drama-play-page .play-episode-pages button{border:1px solid transparent;background:#252527;color:#aaa;border-radius:8px;padding:10px 18px;font-size:14px;cursor:pointer;transition:color .15s,background .15s} +#drama-play-page .play-episode-pages button:hover{color:#fff;background:#343436} +#drama-play-page .play-episode-pages button[aria-pressed=true]{color:#FFC000;background:#FFC00018;border-color:#FFC00040} +#drama-play-page .play-episode-locked{position:relative;display:grid;place-items:center;aspect-ratio:1;min-height:44px;padding:0;border:1px solid transparent;border-radius:9px;background:#252527;color:#888;font:inherit;font-size:16px;cursor:pointer} +#drama-play-page .play-episode-locked:hover{border-color:#FFC00070;background:#303032;color:#ddd} +#drama-play-page .episode-lock-icon{position:absolute;bottom:4px;right:4px;width:12px;height:12px;fill:none;stroke:#a9a9ae;stroke-width:1.4} +#drama-play-page .play-episode-locked:focus-visible,#drama-play-page .play-episode-pages button:focus-visible{outline:2px solid #FFC000;outline-offset:3px} +#play-episode-lock{width:min(460px,calc(100vw - 32px));max-height:calc(100dvh - 32px);overflow:auto;box-sizing:border-box;padding:36px;border:1px solid #3b3b3b;border-radius:40px;background:#0d0d0d;color:#fff;text-align:center;box-shadow:0 24px 90px #0005} +#play-episode-lock::backdrop{background:#0009;backdrop-filter:blur(10px)} +#play-episode-lock .unlock-close{position:absolute;right:16px;top:16px;display:grid;place-items:center;width:32px;height:32px;padding:6px;border:0;border-radius:50%;background:transparent;color:#999;cursor:pointer} +#play-episode-lock .unlock-close:hover{background:#ffffff0d;color:#fff} +#play-episode-lock .unlock-close svg{width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round} +#play-episode-lock .unlock-coin{display:grid;place-items:center;width:64px;height:64px;margin:0 auto 22px;border:0;background:none} +#play-episode-lock .unlock-coin .coin-symbol{width:60px;height:60px;box-sizing:border-box;border:2px solid #ffe88c;box-shadow:inset 0 0 0 3px #ffd563} +#play-episode-lock h2{margin:0 0 20px;font-size:26px;line-height:1.4;font-weight:600;color:#fff} +#play-episode-lock .episode-lock-copy{margin:0 0 20px;font-size:16px;line-height:1.6;color:#929292} +#play-episode-lock .unlock-cost-summary{display:flex;justify-content:space-between;gap:20px;padding:20px 18px;border:1px solid #303030;border-radius:14px;background:#1a1a1a;text-align:left} +#play-episode-lock .unlock-cost-summary>div:last-child{text-align:right} +#play-episode-lock .unlock-cost-summary span{display:block;margin-bottom:8px;color:#919191;font-size:14px} +#play-episode-lock .unlock-cost-summary strong{font-size:18px;font-weight:500;color:#fff} +#play-episode-lock .unlock-cost-summary .unlock-cost{color:var(--accent)} +#play-episode-lock .unlock-confirm-note{margin:20px 0;font-size:14px;line-height:1.6;color:#929292} +#play-episode-lock .unlock-pay{display:block;width:100%;padding:18px 12px;border:0;border-radius:20px;background:var(--accent);color:#fff!important;font-size:18px;font-weight:500;cursor:pointer;transition:filter .15s} +#play-episode-lock .unlock-pay:hover{filter:brightness(1.07)} +#play-episode-lock .unlock-pay:disabled,#play-episode-lock .unlock-close:disabled{cursor:wait;opacity:.6} +#play-episode-lock .unlock-error{margin:0 0 16px;color:#ff718b;font-size:14px;line-height:1.6} +#play-episode-lock .unlock-error[hidden]{display:none} +#play-episode-lock .unlock-recharge{display:inline-block;margin-top:20px;padding:0;border:0;background:none;color:var(--accent)!important;font-size:18px;font-weight:500;text-decoration:none} +#play-episode-lock .unlock-recharge:hover{color:var(--accent)!important;filter:brightness(1.15)} +#play-episode-lock :is(button,a):focus-visible{outline:2px solid var(--accent);outline-offset:5px} +@media(max-width:480px){#play-episode-lock{padding:28px 24px;border-radius:30px}#play-episode-lock h2{font-size:22px}} + + + +#drama-play-page .play-episodes-section{max-width:none} +#drama-play-page .play-episode-pages{display:block;position:relative;margin-bottom:22px;padding-right:0} +#drama-play-page .episode-page-track{display:flex;gap:18px;overflow-x:auto;scrollbar-width:none;padding:0} +#drama-play-page .episode-page-track::-webkit-scrollbar{display:none} +#drama-play-page .play-episode-pages .episode-page-track button{flex:none;padding:0 12px;border:0;line-height:1.4;border-radius:8px;background:transparent;color:#999;font-size:18px;white-space:nowrap} +#drama-play-page .play-episode-pages .episode-page-track button:hover{color:#fff!important;background:transparent;border-color:transparent} +#drama-play-page .play-episode-pages .episode-page-track button[aria-pressed=true]{color:#fff!important;background:transparent;border-color:transparent} +#drama-play-page .play-episode-grid{grid-template-columns:repeat(15,minmax(0,1fr));grid-auto-rows:auto;gap:12px;min-height:0;align-content:start;overflow:visible;width:100%;max-width:100%;padding-bottom:4px} +#drama-play-page .play-episode-grid>a,#drama-play-page .play-episode-locked{position:relative;overflow:hidden;display:grid;place-items:center;aspect-ratio:1;border:0;border-radius:12px;background:#252527;color:#eee;font-size:20px;width:100%;height:auto;min-width:0;min-height:0;box-sizing:border-box} +#drama-play-page .play-episode-grid>a:hover,#drama-play-page .play-episode-locked:hover{background:#373739;color:#fff} +#drama-play-page .play-episode-grid>a[aria-current=true]{background:#FFC00017;color:#FFC000} +#drama-play-page .episode-lock-corner{position:absolute;top:0;left:0;width:19px;height:16px;display:grid;place-items:center;border-radius:0 0 8px 0;background:#ffffff0d} +#drama-play-page .episode-lock-icon{position:static;width:10px;height:10px;stroke:#888;fill:#888} +#drama-play-page .episode-lock-icon path{fill:none;stroke-width:2} +#drama-play-page .episode-playing{position:absolute;top:5px;right:5px;display:flex;align-items:center;gap:2px;height:9px} +#drama-play-page .episode-playing i{display:block;width:2px;height:9px;background:currentColor;border-radius:2px} +#drama-play-page .episode-playing i:nth-child(2){height:5px} +#drama-play-page .episode-playing i:nth-child(3){height:7px} +@media(max-width:600px){#drama-play-page .play-episode-grid{gap:12px}#drama-play-page .play-episode-grid>a,#drama-play-page .play-episode-locked{font-size:20px;border-radius:12px}#drama-play-page .episode-playing{top:5px;right:5px}#drama-play-page .episode-page-track{gap:22px}#drama-play-page .play-episode-pages .episode-page-track button{font-size:16px}} + +#drama-play-page .episode-lock-corner{width:21px;height:18px;background:#dba44312} +#drama-play-page .episode-lock-corner .coin-symbol{display:inline-flex;align-items:center;justify-content:center;position:relative;flex:none;box-sizing:border-box;width:13px;height:13px;min-width:13px;margin:0;padding:0;border:1px solid #ffe595;border-radius:50%;background:linear-gradient(135deg,#fff1a1,#ffc735);box-shadow:inset 0 0 0 1px #ffdc69;line-height:1} +#drama-play-page .episode-lock-corner .coin-soft-star{display:block;width:76%;height:76%;margin:0;filter:none} +#drama-play-page .episode-vip-mark{font-size:8px;line-height:1;color:var(--accent)} + +#drama-play-page .play-episode-pages .episode-page-track button[aria-pressed="false"]{color:#888!important} +#drama-play-page .play-episode-pages .episode-page-track button[aria-pressed="false"]:hover{color:#fff!important;background:transparent;border-color:transparent} + + + +#drama-play-page .play-comment-item>img{width:40px;height:40px;flex:0 0 40px;object-fit:cover;border-radius:50%} +#drama-play-page .play-comment-replies{margin-top:18px;padding:16px 18px;background:#ffffff04;border-radius:12px} +#drama-play-page .play-comment-replies .play-comment-child{padding:0;margin:0 0 20px;gap:12px} +#drama-play-page .play-comment-replies .play-comment-child:last-child{margin-bottom:0} +#drama-play-page .play-comment-child>img{width:30px;height:30px;flex-basis:30px} +#drama-play-page .play-comment-child .play-comment-body p{margin:7px 0 10px;font-size:14px;line-height:1.8} +#drama-play-page .play-reply-target{color:#bba5b0} +#drama-play-page .play-reply-context{display:flex;align-items:center;justify-content:space-between;padding:0 0 10px;font-size:14px;color:#bbb} +#drama-play-page .play-reply-context button{padding:3px 6px;background:none;color:#999!important;font-size:12px} +#drama-play-page .play-reply-context button:hover{color:#fff!important} +#drama-play-page .play-comment-meta button{color:#888!important} +#drama-play-page .play-comment-meta button:hover,#drama-play-page .play-comment-meta button[aria-pressed=true]{color:#FFC000!important} +@media(max-width:600px){#drama-play-page .play-comment-replies{padding:12px 10px}#drama-play-page .play-comment-child>img{width:26px;height:26px;flex-basis:26px}} + + + +#drama-play-page .play-banner-ads{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin:26px 0;width:100%;align-items:start} +#drama-play-page .play-banner-ad{display:block;min-width:0;margin:0;padding:0;overflow:hidden;border-radius:12px;background:transparent} +#drama-play-page .play-banner-ad>img{display:block;width:100%;height:auto;max-width:100%;object-fit:contain} +@media(max-width:600px){#drama-play-page .play-banner-ads{gap:10px}#drama-play-page .play-banner-ad{border-radius:8px}} + +#drama-play-page .play-episodes-section{padding-bottom:0} + +#drama-play-page .play-episode-grid{padding-bottom:0} + + + +#drama-play-page .play-detail-actions .play-action-icon{display:block;width:18px;height:18px;flex:0 0 18px;fill:currentColor;stroke:none;color:#aaa} +#drama-play-page .play-detail-actions .play-action-icon path{fill:currentColor;stroke:none} +#drama-play-page .play-detail-actions button[aria-pressed=true] .play-action-icon,#drama-play-page .play-detail-actions button:hover .play-action-icon,#drama-play-page .play-detail-actions a:hover .play-action-icon{color:#FFC000} +#drama-play-page .play-detail-actions .play-download .play-action-icon{color:#fff;width:18px;height:18px} +#drama-play-page .play-detail-actions button,#drama-play-page .play-detail-actions a{gap:8px} + +#drama-play-page .play-detail-actions button:not(.play-download):hover, +#drama-play-page .play-detail-actions button:not(.play-download):hover>span, +#drama-play-page .play-detail-actions a:hover, +#drama-play-page .play-detail-actions a:hover>span{color:#FFC000!important} +#drama-play-page .play-detail-actions .play-download .play-action-icon, +#drama-play-page .play-detail-actions .play-download:hover .play-action-icon, +#drama-play-page .play-detail-actions .play-download:focus-visible .play-action-icon, +#drama-play-page .play-detail-actions .play-download:active .play-action-icon{color:#fff!important;fill:currentColor} + +#drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"], +#drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]{background:var(--accent,#FFC000);border-color:var(--accent,#FFC000);color:#fff!important} +#drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]>span, +#drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]>span, +#drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"] .play-action-icon, +#drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"] .play-action-icon{color:#fff!important} +#drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:hover>span, +#drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:hover>span{color:#fff!important} + +#drama-play-page .play-detail-actions button[data-engage="like"], +#drama-play-page .play-detail-actions button[data-engage="collect"]{box-sizing:border-box;flex:0 0 88px;width:88px;min-width:88px;max-width:88px;justify-content:center;padding:9px 10px;white-space:nowrap} + + + +#drama-play-page .play-sort{display:inline-flex;align-items:center;gap:4px;padding:4px;border:1px solid #ffffff08;border-radius:11px;background:#252527} +#drama-play-page .play-sort button{min-width:56px;padding:7px 13px;border:0;border-radius:8px;background:transparent;color:#929299!important;font-size:14px;font-weight:400;line-height:1.5;transition:color .16s,background .16s,box-shadow .16s;cursor:pointer} +#drama-play-page .play-sort button[aria-pressed="false"]:hover{color:#eee!important;background:#ffffff0d} +#drama-play-page .play-sort button[aria-pressed="true"]{color:#fff!important;background:#FFC000;box-shadow:0 2px 8px #0002;font-weight:400} +#drama-play-page .play-sort button[aria-pressed="true"]:hover{background:#ff6829} +#drama-play-page .play-sort button:active{box-shadow:inset 0 1px 4px #0003} +#drama-play-page .play-sort button:focus-visible{outline:2px solid #ff9b73;outline-offset:2px} +@media(prefers-reduced-motion:reduce){#drama-play-page .play-sort button{transition:none}} + + + +#search-results-page{padding-top:32px;padding-bottom:48px;min-height:80vh} +#search-results-page .search-results-heading{margin:26px 0 28px} +#search-results-page h1{font-size:28px;line-height:1.5;margin:0 0 12px;color:#eee;font-weight:600;overflow-wrap:anywhere} +#search-results-page h1 em{color:var(--accent);font-style:normal} +#search-results-page .cards :is(h2,h3) .search-keyword{color:var(--accent)!important;background:transparent;padding:0;font:inherit} +#search-results-page .search-results-intro{color:#888;font-size:14px;line-height:1.8;margin:0 0 10px} +#search-results-page .search-results-count{color:#aaa;font-size:14px;margin:0} +#search-results-page .search-results-count strong{color:#eee;font-weight:400} +.search-results-form{display:flex;gap:10px;max-width:560px;margin:22px 0 0} +.search-results-form input{flex:1;min-width:0;padding:12px 16px;border:1px solid #ffffff20;border-radius:10px;background:#ffffff06;color:#eee;font:inherit;outline:none} +.search-results-form input:focus{border-color:var(--accent)} +.search-results-form button{padding:0 24px;border-radius:10px;background:var(--accent);color:#fff} +#search-results-page .cards{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:28px 18px} +@media(max-width:1180px){#search-results-page .cards{grid-template-columns:repeat(4,minmax(0,1fr))}} +@media(max-width:760px){#search-results-page{padding-top:22px}#search-results-page h1{font-size:24px}#search-results-page .cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px 12px}} + + + +#library #cards{grid-template-columns:repeat(6,minmax(0,1fr));row-gap:28px} +@media(max-width:1180px){#library #cards{grid-template-columns:repeat(3,minmax(0,1fr))}#library #cards>.card:nth-child(n+7){display:none}} +@media(max-width:760px){#library #cards{grid-template-columns:repeat(2,minmax(0,1fr))}#library #cards>.card:nth-child(n+5){display:none}} + + + +:root{--global-header-height:96px} +body{padding-top:var(--global-header-height)} +html{scroll-padding-top:calc(var(--global-header-height) + 20px)} +#global-site-header{position:fixed;top:0;left:0;right:0;z-index:60;box-sizing:border-box;isolation:isolate} +#global-site-header{background:transparent;border:0;box-shadow:none} +#global-site-header::before{content:none} +#global-site-header>.top{position:relative;margin:0} +@media(max-width:760px){:root{--global-header-height:152px}} +@media(prefers-reduced-motion:reduce){#global-site-header::before{transition:none}} + + + +#global-site-header .top .search,#global-site-header .header-actions .coin-entry,#global-site-header .header-actions .download-entry{background:#ffffff09;border-color:#ffffff1c;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:none;opacity:1} +#global-site-header .top .search:hover,#global-site-header .top .search:focus-within,#global-site-header .header-actions .coin-entry:hover,#global-site-header .header-actions .download-entry:hover{background:#ffffff14;border-color:#ffffff35;opacity:1} +#global-site-header .top .search input{color:#fff;caret-color:var(--accent);text-shadow:0 1px 3px #0008} +#global-site-header .top .search input::placeholder{color:#eeeef2;opacity:1} +#global-site-header .header-actions .coin-entry{color:#f3cf75;text-shadow:0 1px 3px #0008} +@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){#global-site-header .top .search,#global-site-header .header-actions .coin-entry,#global-site-header .header-actions .download-entry{background:#29292de8}} + + + +.drama-data-card .drama-meta{display:flex;align-items:center;justify-content:space-between;flex-wrap:nowrap;gap:10px} +.drama-data-card .drama-tags{flex:1 1 auto;min-width:0} +.drama-data-card .drama-tags>span{flex-shrink:1;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.drama-data-card .drama-tags>span+span{padding-left:8px;margin-left:8px} +.drama-data-card .drama-heat{display:inline-block;flex:0 0 auto;margin-left:auto;color:#8b9189;font-size:12px;font-weight:400;line-height:1.5;white-space:nowrap;font-variant-numeric:tabular-nums} +@media(max-width:760px){.drama-data-card .drama-meta{gap:6px}.drama-data-card .drama-heat{font-size:12px}.drama-data-card .drama-tags>span+span{padding-left:5px;margin-left:5px}} + + + +.channel-pagination,.member-history-pagination{gap:12px;margin:30px 0 20px;font-size:14px} +.channel-pagination .pagination-pages{gap:10px} +.channel-pagination .pagination-pages button,.member-history-pagination>button{width:36px;min-width:36px;height:36px;padding:0;border-radius:50%;font-size:14px} +.channel-pagination .pagination-gap,.member-history-pagination .pagination-ellipsis{padding:0 3px;font-size:14px} +.channel-pagination .pagination-jump,.channel-pagination .pagination-jump label,.member-history-pagination .history-page-jump{gap:9px;font-size:14px} +.member-history-pagination .history-page-jump{margin-left:0} +.channel-pagination .pagination-jump input,.member-history-pagination .history-page-jump input{box-sizing:border-box;width:56px;height:36px;border-radius:999px;font-size:14px;padding:0 6px} +.channel-pagination .pagination-jump button,.member-history-pagination .history-page-confirm{width:76px;min-width:76px;height:36px;padding:0 16px;font-size:14px;border-radius:999px} +.member-history-pagination>button[aria-current="page"]:hover{background:var(--accent)} +@media(max-width:600px){.channel-pagination,.member-history-pagination{gap:9px}.channel-pagination .pagination-pages{gap:8px}} + + + +.side .sidebar-header .sidebar-brand-window{width:121.5px;height:36px} +.side .sidebar-header .sidebar-brand{width:121.5px;height:36px;object-fit:contain} +@media(max-width:760px){.side .sidebar-header .sidebar-brand-window{width:112px;height:33.185px}.side .sidebar-header .sidebar-brand{width:112px;height:33.185px}} +@media(min-width:761px){.sidebar-collapsed .side .sidebar-header .sidebar-brand-window{width:36px;height:36px}.sidebar-collapsed .side .sidebar-header .sidebar-brand{width:121.5px;height:36px}} + + +.download-panel-brand .download-brand-complete{display:block;width:180px;height:auto;aspect-ratio:135/40;object-fit:contain} + +.side-info>strong{display:flex;align-items:baseline;gap:4px}.side-info .side-info-wordmark{display:block;width:70px;max-width:100%;height:auto;object-fit:contain}.side-info .side-info-brand-dot{display:block;flex:0 0 4px;width:4px;height:4px;border-radius:50%;background:var(--accent);transform:translateY(-1px)}.side-info .side-info-slogan{white-space:nowrap;font-size:12px;letter-spacing:0;line-height:1.8} + + +@media(max-width:760px){ + :root{--global-header-height:68px;--mobile-bottom-height:calc(64px + env(safe-area-inset-bottom,0px))} + body{padding-bottom:var(--mobile-bottom-height)} + .shell{margin-left:0;padding-left:16px;padding-right:16px;max-width:100%;min-width:0} + .side{display:none} + #global-site-header{padding-left:16px;padding-right:16px;padding-top:env(safe-area-inset-top,0px)} + #global-site-header .top{height:68px;min-height:68px;padding:12px 0;gap:10px;flex-wrap:nowrap;box-sizing:border-box} + #global-site-header .top .search{order:0;flex:1 1 0;width:0;min-width:0;height:42px;padding:3px 5px 3px 13px;gap:3px} + #global-site-header .top .search input{font-size:14px} + #global-site-header .top .search button[type=submit]{width:30px;height:32px;min-width:30px;flex:0 0 30px;padding:5px} + #global-site-header .top .search button[type=submit] svg{width:18px;height:18px} + #global-site-header .header-actions{width:auto;flex:0 0 auto;gap:8px;justify-content:flex-end} + #global-site-header .header-actions .coin-entry{height:36px;padding:0 8px;gap:4px;margin:0} + #global-site-header .coin-entry .coin-symbol{width:20px;height:20px;flex:0 0 20px} + #global-site-header .coin-caption{font-size:0} + #global-site-header .coin-value{font-size:12px;white-space:nowrap} + #global-site-header .header-download-wrap{display:none} + #global-site-header .header-actions .download-entry{width:34px;height:36px;padding:0;min-width:0;margin:0;justify-content:center} + #global-site-header .download-entry>span{display:none} + #global-site-header .download-entry>svg{width:18px;height:18px} + #global-site-header .header-download-qr{position:fixed;top:var(--global-header-height);left:auto;right:16px;max-width:calc(100vw - 32px);box-sizing:border-box} + #global-site-header .account-entry{width:34px;height:34px;padding:0;gap:0} + #global-site-header .account-avatar,#global-site-header .account-avatar img{width:34px;height:34px} + #global-site-header .account-copy{display:none} + #global-site-header .search-discovery{position:fixed;top:calc(var(--global-header-height) - 4px);left:12px;right:12px;width:auto;max-width:none;max-height:calc(100dvh - var(--global-header-height) - var(--mobile-bottom-height) - 20px);padding:20px;border-radius:18px} + #profile-popover{position:fixed;top:var(--global-header-height);right:16px;max-width:calc(100vw - 32px)} + .mobile-pages{position:fixed;left:0;right:0;bottom:0;z-index:70;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:0;height:var(--mobile-bottom-height);box-sizing:border-box;padding:6px 8px calc(6px + env(safe-area-inset-bottom,0px));border:0;border-top:1px solid #ffffff10;background:#171719ed;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);overflow:visible} + .mobile-pages>a{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:4px 0;min-width:0;min-height:44px;border-radius:10px;font-size:10px;line-height:1.2;color:#96969e;text-decoration:none} + .mobile-pages>a[data-page=home-road],.mobile-pages>a[data-page=apps]{display:none} + .mobile-pages>a svg{width:21px;height:21px;fill:currentColor} + .mobile-pages>a svg.mobile-account-icon{fill:none;stroke:currentColor;stroke-width:1.7} + .mobile-pages>a[aria-current=page]{color:var(--accent);background:none} + .home-content{--module-gap:30px;--promotion-gap:18px;--module-inset:0px} + .home-content .hero{height:440px;border-radius:18px;touch-action:pan-y} + .home-content .hero-art{height:100%;width:100%;object-fit:cover;object-position:64% center;opacity:.8} + .home-content .hero:after{background:linear-gradient(0deg,#0c1114 0%,#0c1114d9 20%,#0c111435 80%)} + .home-content .hero-content{inset:auto 0 100px;width:100%;padding:20px;max-width:none} + .home-content .hero-content .eyebrow{font-size:10px;letter-spacing:1px} + .home-content .hero-content h2{font-size:26px;line-height:1.35;letter-spacing:0;margin:12px 0 8px;white-space:normal;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden} + .home-content .hero-content .english{display:none} + .home-content .hero-content>p{font-size:12px;line-height:1.65;margin:0;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden} + .home-content .hero-rating{margin-top:10px;gap:12px;font-size:12px;flex-wrap:wrap} + .home-content .hero-content .actions{margin-top:14px} + .home-content .hero-content .primary{padding:10px 18px;font-size:14px;min-height:40px} + .home-content .hero-previews{left:42px;right:42px;bottom:24px;max-width:none;width:auto;gap:6px} + .home-content .hero-preview{height:58px;min-height:0;max-height:58px;border-radius:6px} + .home-content .hero-preview img{height:100%;width:100%;object-fit:cover} + .home-content .hero-preview-arrow.prev{left:-30px}.home-content .hero-preview-arrow.next{right:-30px} + .home-content .hero-bottom{display:none} + .section-head{gap:12px}.section-head h2{font-size:20px!important;line-height:1.4}.section-head button{font-size:12px;white-space:nowrap} + .home-content .topic-section>.section-head,.home-content #library>.section-head{margin-bottom:16px} + #library #cards,.cards,#search-results-page .cards{gap:22px 12px} + .drama-data-card>:is(h2,h3),#info-content .member-library-item h3{font-size:14px;line-height:1.5;margin-top:9px} + .drama-description{font-size:12px;line-height:1.6} + .drama-data-card .drama-tags>span,.drama-data-card .drama-heat{font-size:10px} + .drama-data-card .drama-tags>span+span{margin-left:4px;padding-left:4px} + .drama-data-card .cover-corner-badge{font-size:12px;padding:3px 7px;top:8px;right:8px} + .topic-recommendation-grid{grid-template-columns:minmax(0,1fr);gap:12px} + .editorial-row,.editorial-row.reverse{grid-template-columns:minmax(0,1fr);gap:20px;padding:0 0 24px} + .editorial-row.reverse .editorial-posters,.editorial-row.reverse .editorial-copy{order:initial} + .editorial-posters{grid-template-columns:repeat(2,minmax(0,1fr));gap:20px 12px} + .editorial-copy{padding:4px 0}.editorial-copy h3{font-size:24px} + .page-breadcrumb{font-size:12px;gap:8px;flex-wrap:wrap;line-height:1.6} + #drama-play-page{padding-top:16px}.play-detail-layout{margin-top:16px;gap:28px} + #drama-play-page .series-overview{display:block;padding:18px;border-radius:16px} + #drama-play-page .series-poster{width:100%;max-width:100%;height:auto;aspect-ratio:auto;object-fit:contain;margin:0 0 18px} + #drama-play-page .series-title-row h1{font-size:22px;line-height:1.45} + #drama-play-page .series-title-row{gap:8px} + #drama-play-page .series-heat{font-size:12px} + #drama-play-page .series-description{font-size:14px;margin:14px 0 18px} + #drama-play-page .series-actions{width:100%;display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:10px} + #drama-play-page .series-actions>a,#drama-play-page .series-actions>button{padding:10px 8px;min-height:42px;font-size:12px;white-space:nowrap} + #drama-play-page.is-overview .series-action-bar{gap:16px} + #drama-play-page.is-overview .series-action-bar .play-detail-actions{width:100%;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:8px} + #drama-play-page .play-detail-actions [data-engage=like],#drama-play-page .play-detail-actions [data-engage=collect]{width:auto;min-width:0;max-width:none;flex-basis:auto} + #drama-play-page .play-detail-actions button,#drama-play-page .play-detail-actions a{padding:9px 10px;font-size:12px;justify-content:center;gap:5px} + #drama-play-page .play-advertisements{display:flex;gap:12px;overflow-x:auto;scrollbar-width:none;padding:18px 0 0} + #drama-play-page .play-advertisements>a{flex:0 0 52px;min-width:0} + #drama-play-page .play-advertisements img{width:52px;height:52px;border-radius:12px} + #drama-play-page .play-advertisements span{font-size:10px} + #drama-play-page .play-episode-grid{grid-template-columns:repeat(5,minmax(0,1fr));gap:10px} + #drama-play-page .play-episode-grid a,#drama-play-page .play-episode-grid button{font-size:18px;border-radius:10px} + #drama-play-page .play-episode-pages .episode-page-track{gap:16px} + #drama-play-page .play-episode-pages .episode-page-track button{font-size:14px;padding:0;min-height:36px} + #drama-play-page .play-section-heading h2{font-size:18px} + #drama-play-page .play-banner-ads{gap:10px;margin:22px 0} + #drama-play-page .play-official,#drama-play-page .play-comments{padding:20px 16px;border-radius:16px} + #drama-play-page .play-official-line{font-size:14px;line-height:1.7} + #drama-play-page .play-official-inline{flex-wrap:wrap;gap:0 8px} + #drama-play-page .play-comment-form{padding:12px;gap:10px} + #drama-play-page .play-compose-avatar{width:30px;height:30px} + #drama-play-page .play-compose-body{min-width:0} + #drama-play-page .play-comment-form textarea{font-size:14px;min-height:84px} + #drama-play-page .play-comment-footer{gap:8px;flex-wrap:wrap;font-size:12px} + #drama-play-page .play-related-list{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px} + #drama-play-page .player-control-row{gap:6px;flex-wrap:wrap} + #drama-play-page .play-screen-controls{padding:24px 10px 6px} + #drama-play-page .player-time{font-size:10px} + #drama-play-page .player-control-row button{min-width:26px;font-size:12px} + .member-summary{padding:20px;gap:20px;border-radius:16px}.member-identity{gap:14px} + #info-content .member-identity h2{font-size:22px} + .member-library-tabs{gap:0;justify-content:space-between}.member-library-tabs button{font-size:14px} + .member-library-toolbar{flex-direction:row;align-items:center;gap:8px;font-size:12px} + .member-library-cards{gap:22px 12px} + .channel-tabs{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;gap:8px}.channel-tabs button{flex-shrink:0;font-size:14px;padding:9px 14px} + #search-results-page h1{font-size:22px}#search-results-page .search-results-heading{margin:20px 0 24px} + .ui-empty-state{min-height:260px;padding:24px 12px}.ui-empty-state>img{width:164px;height:164px} + .page-tools{bottom:calc(var(--mobile-bottom-height) + 14px)!important;right:12px!important}.page-tools .page-tool-download{display:block}.page-tools #floating-history{display:grid} + .page-tools .page-tool{width:38px;height:38px;min-width:38px;border-radius:12px} + .footer{font-size:12px;line-height:1.8;gap:14px;padding-bottom:24px} + dialog{max-width:calc(100vw - 24px);max-height:calc(100dvh - 32px);overflow:auto} +} +@media(max-width:360px){#global-site-header{padding-left:12px;padding-right:12px}#global-site-header .top{gap:7px}#global-site-header .header-actions{gap:6px}#global-site-header .coin-value{font-size:12px}.home-content .hero{height:420px}} + + + +.mobile-menu-trigger{display:none} +#mobile-navigation-drawer{position:fixed;inset:0 auto 0 0;margin:0;width:min(80vw,360px);max-width:100vw;height:100dvh;max-height:100dvh;box-sizing:border-box;padding:56px 26px 32px;border:0;border-radius:0;background:#262626;color:#eee;overflow-y:auto;overscroll-behavior:contain;box-shadow:12px 0 40px #0003} +#mobile-navigation-drawer[open]{animation:mobile-drawer-in .22s ease-out} +#mobile-navigation-drawer.is-closing{animation:mobile-drawer-out .18s ease-in forwards} +#mobile-navigation-drawer::backdrop{background:#000a;backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px)} +.mobile-drawer-close{position:absolute;right:14px;top:14px;display:grid;place-items:center;width:36px;height:36px;padding:6px;border:0;border-radius:8px;background:transparent;color:#fff} +.mobile-drawer-close svg{width:24px;height:24px;fill:none;stroke:currentColor;stroke-width:2.3;stroke-linecap:round} +.mobile-drawer-brand{display:flex;justify-content:center;margin:0 0 28px} +.mobile-drawer-brand img{display:block;width:170px;max-width:100%;height:auto} +.mobile-drawer-links{display:grid;gap:11px} +.mobile-drawer-links>a{display:flex;align-items:center;min-height:50px;box-sizing:border-box;padding:12px 18px;border:1px solid #ffffff12;border-radius:12px;background:#ffffff06;color:#eee;text-decoration:none;font-size:16px;line-height:1.5} +.mobile-drawer-links>a[aria-current=page]{color:var(--accent);border-color:#FFC0003d;background:#FFC0000a} +.mobile-drawer-links>a:hover{background:#ffffff0c} +.mobile-drawer-contact{margin:26px 0 20px;text-align:center;font-size:14px;line-height:2.4;color:#aaa} +.mobile-drawer-contact p{margin:0}.mobile-drawer-contact a{color:inherit;text-decoration:none}.mobile-drawer-contact a:hover{color:var(--accent)} +.mobile-drawer-social{display:flex;align-items:center;justify-content:center;gap:22px} +.mobile-drawer-social a{display:grid;place-items:center;width:36px;height:36px;color:#aaa} +.mobile-drawer-social svg{width:23px;height:23px;fill:currentColor} +.mobile-drawer-social a+a{position:relative}.mobile-drawer-social a+a:before{content:"";position:absolute;left:-12px;width:1px;height:16px;background:#ffffff25} +#mobile-navigation-drawer :is(a,button):focus-visible{outline:2px solid var(--accent);outline-offset:3px} +@keyframes mobile-drawer-in{from{transform:translateX(-100%)}to{transform:translateX(0)}} +@keyframes mobile-drawer-out{from{transform:translateX(0)}to{transform:translateX(-100%)}} +@media(max-width:760px){ + :root{--mobile-bottom-height:0px} + body{padding-bottom:env(safe-area-inset-bottom,0px)} + body.mobile-drawer-open{overflow:hidden} + .mobile-pages{display:none!important} + #global-site-header .top{gap:8px} + #global-site-header .mobile-menu-trigger{display:grid;place-items:center;flex:0 0 30px;width:30px;height:40px;padding:4px;border:0;background:transparent;color:#eee} + .mobile-menu-trigger svg{width:23px;height:23px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round} + #mobile-navigation-drawer{padding-top:calc(60px + env(safe-area-inset-top,0px));padding-bottom:calc(30px + env(safe-area-inset-bottom,0px))} + .mobile-drawer-close{top:calc(12px + env(safe-area-inset-top,0px))} +} +@media(max-width:390px){#global-site-header .header-actions{gap:5px}#global-site-header .header-actions .coin-entry{padding:0 6px}#global-site-header .top .search{padding-left:9px}#mobile-navigation-drawer{padding-left:22px;padding-right:22px}.mobile-drawer-brand img{width:150px}} +@media(prefers-reduced-motion:reduce){#mobile-navigation-drawer[open],#mobile-navigation-drawer.is-closing{animation:none}} + + + +.home-content .hero{touch-action:pan-y pinch-zoom;user-select:none;-webkit-user-select:none;cursor:grab} +.home-content .hero img{-webkit-user-drag:none} +.home-content .hero.is-swiping,.home-content .hero.is-swiping *{cursor:grabbing} +.home-content .hero .hero-art.fade,.home-content .hero .hero-content.fade{animation:none} +.home-content .hero>.hero-outgoing{position:absolute;inset:0;z-index:5;width:100%;height:100%;min-height:0;border-radius:inherit;pointer-events:none} +.hero-mobile-pagination{display:none} +@media(max-width:760px){ + .home-content .hero-previews,.home-content .hero-bottom{display:none} + .home-content .hero-content{bottom:24px} + .home-content .hero-mobile-pagination{position:absolute;z-index:3;left:auto;right:16px;bottom:4px;display:flex;align-items:center;justify-content:flex-end;gap:0} + .hero-mobile-pagination button{display:grid;place-items:center;width:10px;height:32px;padding:0;border:0;background:transparent;cursor:pointer} + .hero-mobile-pagination button>span{display:block;width:5px;height:5px;border-radius:999px;background:#ffffff70;transition:background .18s,width .18s} + .hero-mobile-pagination button[aria-pressed="true"]{width:28px} + .hero-mobile-pagination button[aria-pressed="true"]>span{width:23px;background:var(--accent)} + .hero-mobile-count{margin-left:10px;color:#fff;font-size:12px;font-weight:500;line-height:32px;font-variant-numeric:tabular-nums;white-space:nowrap;text-shadow:0 1px 4px #0008} + .hero-mobile-pagination button:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;border-radius:6px} +} + + + +#mobile-navigation-drawer{width:min(72vw,280px);padding-left:18px;padding-right:18px;background:#1d1d1f} +#mobile-navigation-drawer .mobile-drawer-brand{justify-content:flex-start;margin-bottom:22px} +#mobile-navigation-drawer .mobile-drawer-brand img{width:140px;height:auto} +#mobile-navigation-drawer .mobile-drawer-links{gap:5px} +#mobile-navigation-drawer .mobile-drawer-links>a{position:relative;gap:13px;min-height:46px;padding:12px 14px;border:0;border-radius:9px;background:transparent;font-size:14px;color:#a8a8ae} +#mobile-navigation-drawer .mobile-drawer-links>a svg{display:block;width:21px;height:21px;flex:0 0 21px;fill:currentColor} +#mobile-navigation-drawer .mobile-drawer-links>a:hover{background:#ffffff06;color:#eee} +#mobile-navigation-drawer .mobile-drawer-links>a[aria-current=page]{background:#FFC00010;color:var(--accent)} +#mobile-navigation-drawer .mobile-drawer-links>a[aria-current=page]:before{content:"";position:absolute;left:0;top:50%;width:3px;height:22px;border-radius:3px;background:var(--accent);transform:translateY(-50%)} +#mobile-navigation-drawer .mobile-drawer-info{margin-top:26px;padding:24px 14px 0;border-top:1px solid #ffffff10} +#mobile-navigation-drawer .mobile-drawer-info>strong{display:flex;align-items:baseline;gap:4px} +#mobile-navigation-drawer .side-info-wordmark{display:block;width:70px;height:auto} +#mobile-navigation-drawer .side-info-brand-dot{display:block;width:4px;height:4px;border-radius:50%;background:var(--accent)} +#mobile-navigation-drawer .side-info-slogan{margin:12px 0 20px;color:#929298;font-size:12px;line-height:1.8;white-space:nowrap} +#mobile-navigation-drawer .policy-links{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:4px 14px} +#mobile-navigation-drawer .policy-links a{display:flex;align-items:center;min-height:36px;color:#96969e;font-size:12px;text-decoration:none} +#mobile-navigation-drawer .policy-links a:hover,#mobile-navigation-drawer .policy-links a[aria-current=page]{color:var(--accent)} +#mobile-navigation-drawer .mobile-drawer-info>small{display:block;margin-top:18px;font-size:10px;line-height:1.9;color:#68686f} +#mobile-navigation-drawer .mobile-drawer-social{justify-content:flex-start;gap:14px;margin-top:20px;padding-left:10px} +#mobile-navigation-drawer .mobile-drawer-social a{width:36px;height:36px} +#mobile-navigation-drawer .mobile-drawer-social img{display:block;width:22px;height:22px;object-fit:contain} +#mobile-navigation-drawer .mobile-drawer-social a+a:before{content:none} + + + +@media(max-width:760px){ +.home-promotions .promo-marquee{overflow-x:auto;overflow-y:hidden;border-radius:0;scrollbar-width:none;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch} +.home-promotions .promo-marquee::-webkit-scrollbar{display:none} +.home-promotions .promo-track{display:block;width:100%;animation:none!important;transform:none} +.home-promotions .promo-group{display:flex;flex-wrap:nowrap;gap:10px;width:100%;padding:0} +/* Five complete tiles plus 30% of the sixth, including five gaps. */ +.home-promotions .promo-tile{flex:0 0 calc((100% - 50px) / 5.3);width:calc((100% - 50px) / 5.3);min-width:0;padding:0;scroll-snap-align:start} +.home-promotions .promo-tile-image{width:100%;aspect-ratio:1;border-radius:22%} +.home-promotions .promo-tile strong{display:block;margin-top:7px;font-size:12px;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +} + + + +.mobile-cover-rank{display:none} +@media(max-width:760px){ + #twist-cards .media{position:relative} + #twist-cards .mobile-cover-rank{display:block;position:absolute;left:10px;top:auto;bottom:10px;z-index:3;font:italic 700 30px/1 Georgia,serif;letter-spacing:-1px;color:#eee;text-shadow:0 2px 5px #000c;pointer-events:none} + #twist-cards .ranked-card:nth-child(-n+3) .mobile-cover-rank{color:var(--accent)} + #twist-cards .rank-caption>strong{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap} + #twist-cards .rank-caption{gap:0;min-height:0;margin:9px 0 9px} + #twist-cards .rank-caption h3{margin:0;font-size:14px;font-weight:500;line-height:1.5} + #twist-cards .drama-description{margin:0 0 7px} + #twist-cards .drama-meta{margin:0} + #twist-cards .rank-caption>span{min-width:0;width:100%} +} + + + +@media(max-width:760px){ + .wide-ad-row,#drama-play-page .play-banner-ads{grid-template-columns:minmax(0,1fr);gap:10px} +} + + + +@media(max-width:760px){ + #martial-cards .curated-card{grid-template-columns:minmax(0,46fr) minmax(0,54fr);gap:12px;padding:12px;align-items:center} + #martial-cards .curated-copy{display:flex;gap:0;padding:0;align-self:center;min-width:0} + #martial-cards .curated-copy .pick{font-size:10px;line-height:1.4;letter-spacing:1px;padding:0;border:0;margin:0 0 6px} + #martial-cards .curated-copy h3{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;overflow:hidden;white-space:normal;font-size:16px;line-height:1.3;letter-spacing:0;margin:0 0 6px} + #martial-cards .martial-description{font-size:12px;line-height:1.4;height:2.8em;min-height:0;margin:0 0 6px} + #martial-cards .martial-description>span{line-height:1.4} + #martial-cards .curated-copy .drama-meta{width:100%;margin:0;gap:4px;line-height:1.5;flex-direction:column;align-items:flex-start} + #martial-cards .curated-copy .drama-heat{margin-left:0} + #martial-cards .curated-copy .drama-tags>span,#martial-cards .curated-copy .drama-heat{font-size:10px} + #martial-cards .curated-copy .watch-link{min-height:26px;padding:4px 8px;margin-top:8px;gap:8px;font-size:12px;line-height:18px;box-sizing:border-box;align-self:flex-start} +} + + + +@media(max-width:760px){ + .page-tools #back-to-top{display:flex;align-items:center;justify-content:center;padding:0;box-sizing:border-box;line-height:0} + .page-tools #back-to-top>svg{display:block;flex:0 0 24px;width:24px;height:24px;margin:0} +} + + + +@media(max-width:760px){ + .romance-editorial .editorial-copy{display:flex;justify-content:flex-end;align-items:center;padding:0;min-height:0;height:auto} + .romance-editorial .editorial-copy> :not(button){display:none} + .romance-editorial .editorial-copy>button{margin:0;padding:8px 0;min-height:36px} + .home-content .romance-editorial .editorial-row{gap:8px;padding:0 0 12px} + .home-content .romance-editorial .editorial-row+.editorial-row{padding-top:12px} + .home-content #romance-rows{gap:0} +} + + + +@media(max-width:760px){ + #drama-play-page.is-overview .series-action-bar .play-detail-actions{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;width:100%;min-width:0;margin:0} + #drama-play-page.is-overview .play-detail-actions>button,#drama-play-page.is-overview .play-detail-actions>a{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:100%;min-width:0;max-width:none;height:38px;min-height:38px;flex:initial;padding:0 1px;gap:2px;font-size:12px;line-height:1;white-space:nowrap} + #drama-play-page.is-overview .play-detail-actions>button>span,#drama-play-page.is-overview .play-detail-actions>a>span{white-space:nowrap;font-weight:400;line-height:1} + #drama-play-page.is-overview .play-detail-actions .play-action-icon{width:12px;height:12px;flex:0 0 12px;margin:0} +} + + + +#drama-play-page .series-description-wrap{margin:18px 0 26px} +#drama-play-page .series-description-wrap .series-description{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden;margin:0;min-height:0;max-height:none} +#drama-play-page .series-description-wrap .series-description.is-expanded{display:block;-webkit-line-clamp:unset;overflow:visible} +#drama-play-page .series-description-toggle{display:block;border:0;background:transparent;padding:6px 0;margin-top:3px;color:var(--accent);font-size:12px;line-height:1.5;cursor:pointer} +#drama-play-page .series-description-toggle[hidden]{display:none} +@media(max-width:760px){#drama-play-page .series-description-wrap{margin:14px 0 18px}} + + + +#drama-play-page .series-actions .series-download-phone{display:block;width:18px;height:18px;flex:0 0 18px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round} + + + +#drama-play-page .play-comments{background:transparent;border:0;border-radius:0;padding:0;box-shadow:none} +#drama-play-page .play-sort{gap:2px;padding:3px;border-radius:999px;flex-shrink:0} +#drama-play-page .play-sort button{min-width:42px;min-height:28px;padding:4px 8px;border-radius:999px;font-size:12px;line-height:20px;font-weight:400;box-sizing:border-box} +#drama-play-page .play-sort button[aria-pressed="true"],#drama-play-page .play-sort button[aria-pressed="true"]:hover{background:#55555b;color:#f2f2f4!important;font-weight:400;box-shadow:none} + + + +@media(max-width:760px){ + #drama-play-page .play-related-list{gap:22px 12px} + #drama-play-page .play-related .card{margin-bottom:0} + #drama-play-page .play-related .card :is(h2,h3){font-size:14px;font-weight:500;line-height:1.5;margin:9px 0 9px} + #drama-play-page .play-related .drama-description{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;-webkit-line-clamp:unset;min-height:0;font-size:12px;line-height:1.8;margin:0 0 7px} + #drama-play-page .play-related .drama-meta{margin:0} +} + + + +@media(max-width:760px){ + #channel-page[data-channel="apps"] .benefits-directory{width:100%;min-width:0} + #channel-page[data-channel="apps"] .benefits-directory>.channel-tabs{display:flex;flex-wrap:nowrap;width:100%;min-width:0;box-sizing:border-box;margin:0 0 24px;padding:8px 0;gap:8px;overflow-x:auto;scrollbar-width:none;top:var(--global-header-height);background:transparent;backdrop-filter:none} + #channel-page[data-channel="apps"] .benefits-directory>.channel-tabs::-webkit-scrollbar{display:none} + #channel-page[data-channel="apps"] .benefits-directory>.channel-tabs button{flex:0 0 auto;min-height:36px;padding:8px 12px;font-size:12px;line-height:18px;white-space:nowrap;border-radius:999px} + #channel-page[data-channel="apps"] .benefit-heading{margin-bottom:16px} + #channel-page[data-channel="apps"] .benefit-heading h2{font-size:18px;margin-bottom:6px} + #channel-page[data-channel="apps"] .benefit-heading p{font-size:12px;line-height:1.7} + #channel-page[data-channel="apps"] .benefit-section{margin-bottom:28px;scroll-margin-top:calc(var(--global-header-height) + 64px)} + #channel-page[data-channel="apps"] .benefit-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:20px 10px} + #channel-page[data-channel="apps"] .benefit-card{box-sizing:border-box;height:100%;padding:0;gap:8px;border:0;border-radius:0;background:transparent;justify-content:flex-start} + #channel-page[data-channel="apps"] .benefit-card:hover{background:transparent} + #channel-page[data-channel="apps"] .benefit-icon{width:100%;height:auto;max-width:72px;aspect-ratio:1;flex:none} + #channel-page[data-channel="apps"] .benefit-copy strong{font-size:12px;line-height:1.5;white-space:normal;overflow-wrap:anywhere;text-overflow:clip} + #channel-page[data-channel="apps"] .benefit-copy small{display:block;font-size:12px;line-height:1.6;margin-top:5px;white-space:normal;overflow:visible;-webkit-line-clamp:unset} +} + + + +@media(max-width:760px){ + #info-content .member-advertisements{width:100%;min-width:0;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;scroll-snap-type:x proximity;-webkit-overflow-scrolling:touch} + #info-content .member-advertisements::-webkit-scrollbar{display:none} + #info-content .member-ad-grid{display:flex;flex-wrap:nowrap;gap:10px;width:100%;padding:0} + #info-content .member-ad-tile{flex:0 0 calc((100% - 50px) / 5.3);width:calc((100% - 50px) / 5.3);min-width:0;gap:7px;font-size:12px;line-height:1.4;scroll-snap-align:start} + #info-content .member-ad-tile img{width:100%;height:auto;aspect-ratio:1;transform:none} +} + + + +@media(max-width:760px){ + #info-page[data-info="account-settings"] .info-layout,#info-page[data-info="account-settings"] #info-content{min-width:0;width:100%;max-width:100%;box-sizing:border-box} + #info-content .member-settings{width:100%;max-width:100%;min-width:0;grid-template-columns:minmax(0,1fr);gap:20px} + #info-content .member-settings-card{box-sizing:border-box;width:100%;min-width:0;max-width:100%;padding:20px} + #info-content .member-avatar-editor{min-width:0;gap:14px} + #info-content .member-avatar-controls{flex:1 1 0;min-width:0} + #info-content .member-avatar-controls p{white-space:normal;overflow-wrap:anywhere;line-height:1.6} + #info-content .member-nickname-editor{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;min-width:0} + #info-content .member-nickname-editor input{width:100%;min-width:0;box-sizing:border-box;padding:10px 12px} + #info-content .member-nickname-editor button{padding:0 12px;font-size:14px} + #info-content .member-settings-action{box-sizing:border-box;gap:12px;min-width:0} + #info-content .member-settings-action>span:first-child{min-width:0;flex:1} + #info-content .member-settings-action small{white-space:normal;overflow-wrap:anywhere;line-height:1.6} + #info-content .member-settings-trailing{gap:8px;white-space:nowrap} + #info-content #member-avatar-confirm{display:flex;flex-wrap:wrap;gap:8px} + #info-content #member-avatar-confirm[hidden]{display:none} + #info-content #member-avatar-confirm button+button{margin-left:0} +} + + + +@media(max-width:760px){ + /* Keep every centered modal inside the same mobile safe gutter. */ + body dialog:not(#mobile-navigation-drawer){box-sizing:border-box;min-width:0!important;max-width:calc(100vw - 48px - env(safe-area-inset-left,0px) - env(safe-area-inset-right,0px))!important;max-height:calc(100dvh - 48px - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px))!important;margin:auto;overflow-y:auto;overscroll-behavior:contain} + body dialog:not(#mobile-navigation-drawer) form{min-width:0;max-width:100%} + #member-auth-dialog,#member-auth-dialog.auth-bind-mode,#wallet-payment-dialog,#member-delete-dialog,#play-episode-lock,.app-download-dialog{padding-left:20px;padding-right:20px} + #member-auth-dialog .auth-input-row{min-width:0} + #member-auth-dialog .auth-input-row input{min-width:0;font-size:14px} + #member-auth-dialog .auth-bind-agreement,#member-auth-dialog .auth-agreement{white-space:normal;overflow-wrap:anywhere} + .app-download-dialog .app-download-code{box-sizing:border-box;max-width:100%;height:auto;aspect-ratio:1} + .app-download-dialog .app-download-code img{max-width:100%;height:auto} + .app-download-dialog .app-download-link button{min-width:0;max-width:100%;width:100%} +} + + + +body dialog button.close,body dialog button.auth-close,body dialog button.wallet-dialog-close{display:flex;align-items:center;justify-content:center;box-sizing:border-box;width:32px;height:32px;min-width:32px;padding:0;line-height:0} +body dialog button .dialog-close-icon{display:block;width:20px;height:20px;flex:0 0 20px;margin:0;fill:none;stroke:currentColor;stroke-width:2.2;stroke-linecap:round;transform:none;pointer-events:none} + + + + #info-content .wallet-pack-panel{box-sizing:border-box;width:100%;min-width:0;padding:20px 12px;border:1px solid #ffd39933;border-radius:24px;background:linear-gradient(150.751deg,#3228201a 0%,#16100b1a 50.05%,#3228201a 100%)} + #info-content .wallet-pack-heading{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;gap:8px 12px;margin:0 0 18px} + #info-content .wallet-pack-heading strong{font-size:16px;font-weight:500;line-height:22px;color:#ffddc4;white-space:nowrap} + #info-content .wallet-pack-heading>span{display:flex;align-items:center;gap:12px;font-size:12px;line-height:18px;color:#ffddc44d;white-space:nowrap} + #info-content .wallet-pack-heading>span:before{content:'';height:10px;width:1px;background:#ffffff1a} + #info-content .wallet-pack-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px 10px} + #info-content .wallet-mobile-package{position:relative;box-sizing:border-box;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;min-width:0;height:152px;padding:20px 4px;border:1px solid #ffd39933;border-radius:18px;overflow:visible;background:#ffffff0d;box-shadow:none;color:#fff;text-align:center} + #info-content .wallet-mobile-package:hover{border-color:#febf8b99;background:#ffffff12} + #info-content .wallet-mobile-package[aria-pressed="true"]{border-color:#febf8b;background:linear-gradient(180deg,#febe8b4d,#febe8b1a);box-shadow:inset 0 0 0 1px #febf8b} + #info-content .wallet-mobile-package:focus-visible{outline:2px solid #febf8b;outline-offset:3px} + #info-content .wallet-figma-coin{display:block;width:24px;height:24px;max-width:none;flex:0 0 24px} + #info-content .wallet-mobile-coins{font-size:14px;font-weight:400;line-height:20px;white-space:nowrap;color:#fff} + #info-content .wallet-mobile-bonus{font-size:12px;line-height:18px;white-space:nowrap;color:#999} + #info-content .wallet-mobile-price{display:flex;align-items:center;justify-content:center;gap:4px;white-space:nowrap} + #info-content .wallet-mobile-price small{font-size:12px;line-height:18px;font-weight:400} + #info-content .wallet-mobile-price strong{font-size:18px;line-height:24px;font-weight:600} + #info-content .wallet-mobile-selected{display:none;position:absolute;left:50%;bottom:-10px;transform:translateX(-50%);width:24px;height:24px;max-width:none} + #info-content .wallet-mobile-package[aria-pressed="true"] .wallet-mobile-selected{display:block} + #info-content .wallet-double-badge{position:absolute;right:-5px;bottom:-6px;width:40px;height:40px;display:grid;place-items:center;pointer-events:none} + #info-content .wallet-double-badge>img{width:37.0911px;height:37.0911px;max-width:none} + #info-content .wallet-double-badge>span{position:absolute;inset:0;display:grid;place-items:center;font-size:10px;font-weight:700;line-height:16px;color:white} + #info-content .wallet-mobile-package:first-child{overflow:visible} + #info-content .wallet-mobile-package .wallet-package-coin{width:24px;height:24px;flex:0 0 24px;border-width:2px;box-sizing:border-box;box-shadow:inset 0 0 0 2px #ffd563} + #info-content .wallet-mobile-package .wallet-double-badge{right:-4px;top:-10px;bottom:auto;width:32px;height:32px;transform:rotate(-8deg)} + #info-content .wallet-mobile-package .wallet-double-badge>img{position:absolute;width:37.0911px;height:37.0911px;transform:scale(.86)} + #info-content .wallet-mobile-package .wallet-double-badge>span{font-size:10px} + #info-content .wallet-mobile-package .wallet-mobile-selected{z-index:2;bottom:-10px} + #info-content .wallet-mobile-package .wallet-mobile-price{position:relative;z-index:1} +@media(min-width:761px){ + #info-content .wallet-pack-panel{padding:26px 24px} + #info-content .wallet-pack-heading{margin-bottom:24px} + #info-content .wallet-pack-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:24px 20px} + #info-content .wallet-mobile-package{height:180px;padding:24px 12px;gap:10px} + #info-content .wallet-mobile-package .wallet-package-coin{width:32px;height:32px;flex-basis:32px} + #info-content .wallet-mobile-coins{font-size:16px;line-height:22px} + #info-content .wallet-mobile-bonus{font-size:14px;line-height:20px} + #info-content .wallet-mobile-price strong{font-size:22px;line-height:28px} + #info-content .wallet-mobile-price small{font-size:14px} + #info-content .wallet-mobile-package .wallet-double-badge{width:46px;height:46px;right:-6px;top:-14px} + #info-content .wallet-mobile-package .wallet-double-badge>img{transform:scale(1.24)} + #info-content .wallet-mobile-package .wallet-double-badge>span{font-size:12px;line-height:18px} +} +@media(max-width:350px){ + #info-content .wallet-pack-panel{padding-left:10px;padding-right:10px} + #info-content .wallet-pack-grid{column-gap:6px} + #info-content .wallet-mobile-coins{font-size:12px} + #info-content .wallet-mobile-bonus{font-size:10px} +} + + + +@media(max-width:760px){ + .page-tools .page-tool{display:flex;align-items:center;justify-content:center;padding:0;box-sizing:border-box;line-height:0} + .page-tools .page-tool>svg{display:block;flex:0 0 22px;width:22px;height:22px;margin:0} + .page-tools .download-qr-panel{display:none!important} +} + + + +.drama-data-card .drama-heat{display:inline-flex;align-items:center;gap:3px} +.drama-data-card .drama-heat-icon{display:block;width:12px;height:12px;flex:0 0 12px;object-fit:contain;margin:0} +@media(max-width:760px){.drama-data-card .drama-heat-icon{width:10px;height:10px;flex-basis:10px}} + + + +body,button,input,textarea,select{font-family:var(--font-ui)} + + + +html:has(.startup-popup[open]){overflow:hidden} +dialog.startup-popup{box-sizing:border-box;margin:auto;padding:0;border:0;max-width:calc(100vw - 48px);max-height:calc(100dvh - 48px);color:#fff;font-family:var(--font-ui);box-shadow:none} +dialog.startup-popup::backdrop{background:#000b;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px)} +#startup-age{width:800px;background:#0d0d0d;border-radius:20px;overflow:auto} +.startup-age-layout{display:grid;grid-template-columns:224px minmax(0,1fr);min-height:360px} +.startup-age-mark{display:grid;place-items:center;background:#1a1a1a;padding:32px} +.startup-age-mark img{display:block} +.startup-age-copy{padding:40px;display:flex;flex-direction:column;justify-content:center;gap:24px;min-width:0} +.startup-age-copy h2{font-size:24px;line-height:32px;font-weight:600;margin:0 0 16px} +.startup-age-copy p{font-size:16px;line-height:24px;color:#999;margin:0} +.startup-age-copy p+p{margin-top:24px} +.startup-age-actions{display:flex;align-items:center;gap:24px} +.startup-age-actions button{border:0;font-size:16px;line-height:24px;min-height:48px;border-radius:8px;cursor:pointer} +.startup-age-confirm{width:240px;background:var(--accent);color:#fff;font-weight:600} +.startup-age-leave{background:transparent;color:#999;padding:12px 16px;white-space:nowrap} +#startup-ad,#startup-grid{width:400px;background:transparent;border-radius:0;overflow:auto;scrollbar-width:none} +.startup-ad-image{display:block;width:100%;height:auto;aspect-ratio:400/602;object-fit:cover;border-radius:40px} +.startup-ad-media{display:block} +.startup-ad-media[hidden],.startup-ad-loading[hidden]{display:none} +.startup-ad-loading{min-height:160px;display:grid;place-items:center;margin:0;border-radius:24px;background:#161616;color:#aaa;text-align:center} +.startup-popup .startup-close{display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:0;border:0;background:none;margin:24px auto 0;cursor:pointer} +.startup-close img{display:block} +.startup-promotions{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));column-gap:26px;row-gap:20px;padding:1px;min-width:0} +#startup-grid{max-height:80vh;max-height:80dvh;overflow:hidden} +/* Scrollable containers can receive a native focus ring in Chromium. */ +#startup-grid .startup-promotions{box-sizing:border-box;max-height:calc(80vh - 60px);max-height:calc(80dvh - 60px);overflow-x:hidden;overflow-y:auto;overscroll-behavior-y:contain;scrollbar-width:none;-webkit-overflow-scrolling:touch;border:0;outline:none;box-shadow:none} +#startup-grid .startup-promotions::-webkit-scrollbar{display:none} +.startup-promotion{display:flex;flex-direction:column;align-items:center;gap:4px;text-decoration:none;color:#fff;min-width:0} +.startup-promotion img{display:block;width:100%;height:auto;aspect-ratio:1 / 1;object-fit:cover;border:0;border-radius:22%;box-sizing:border-box} +.startup-promotion span{font-size:14px;line-height:20px;white-space:nowrap} +.startup-popup button:focus-visible,.startup-popup a:focus-visible{outline:2px solid var(--accent);outline-offset:3px} +.startup-popup .startup-close{border-radius:50%} +.startup-popup .startup-close:focus{outline:none} +#startup-ad .startup-ad-media:focus{outline:none} +#startup-grid .startup-promotion:focus{outline:none} +#startup-grid.startup-keyboard-focus .startup-promotion:focus-visible{outline:2px solid #ffffffb3;outline-offset:-2px;border-radius:12px} +#startup-age .startup-age-actions button:focus{outline:none;box-shadow:none} +#startup-age.startup-keyboard-focus .startup-age-actions button:focus-visible{outline:2px solid #ffffffb3;outline-offset:-4px} +.startup-popup.startup-keyboard-focus .startup-close:focus-visible{outline:2px solid #ffffffb3;outline-offset:-1px} +@media(max-width:760px){ + #startup-age .startup-age-layout{grid-template-columns:minmax(0,1fr)} + .startup-age-mark{padding:24px} + .startup-age-copy{padding:24px;gap:24px} + .startup-age-copy h2{font-size:20px;line-height:28px} + .startup-age-copy p{font-size:14px;line-height:22px} + .startup-age-copy p+p{margin-top:16px} + .startup-age-actions{gap:12px;flex-wrap:wrap} + .startup-age-confirm{width:100%} + .startup-age-leave{width:100%} + .startup-promotions{column-gap:12px;row-gap:16px} + .startup-promotion span{font-size:12px;line-height:18px} + .startup-ad-image{border-radius:28px} +} +@media(max-height:740px) and (min-width:761px){#startup-ad{width:min(400px,calc((100dvh - 108px)*400/602))}} + + + +#entry-navigation{position:relative;isolation:isolate;width:100%;min-height:100dvh;max-width:none!important;max-height:none!important;margin:0;padding:0;border:0;border-radius:0;background:#0f0f0f;color:#fff;overflow:auto;font-family:var(--font-ui);box-shadow:none} +.entry-background{position:fixed;z-index:-1;inset:0;width:100%;height:100%;object-fit:cover;opacity:.1;pointer-events:none} +.entry-stage{position:relative;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:28px 0;box-sizing:border-box} +.entry-content{width:375px;max-width:100%;padding:12px 28px;display:flex;flex-direction:column;align-items:center;gap:24px;box-sizing:border-box} +.entry-content p{margin:0} +.entry-brand{display:flex;flex-direction:column;align-items:center;gap:12px;width:100%} +.entry-brand-logo{display:block;width:180px;height:auto;aspect-ratio:135/40;object-fit:contain} +.entry-brand p{font-size:14px;line-height:20px;color:#ffffff99;text-align:center} +.entry-brand strong{font-size:18px;line-height:24px;color:var(--accent);font-weight:600;text-align:center} +.entry-routes{display:flex;flex-direction:column;gap:12px;width:100%} +.entry-route-empty{margin:0;padding:24px;text-align:center;color:#999;font-size:14px} +.entry-route{position:relative;isolation:isolate;display:flex;justify-content:space-between;align-items:center;gap:12px;width:100%;min-height:64px;padding:10px 16px;border-radius:12px;background:#ffffff0d;color:#f6f2f9;overflow:hidden;text-align:left;transition:background .18s ease} +.entry-route-copy{display:flex;flex-direction:column;gap:4px;font-size:14px;line-height:20px;min-width:0} +.entry-route-copy b{font-weight:500} +.entry-route-copy small{font-size:14px;color:var(--accent)} +.entry-route-cta{display:flex;align-items:center;justify-content:center;gap:2px;flex:none;border:1px solid var(--accent);border-radius:999px;padding:3px 7px;font-size:10px;line-height:16px;color:var(--accent);background:#FFC00014} +.entry-route-cta img{display:block} +.entry-route-glow{position:absolute;left:56px;top:-56.5px;z-index:-1;pointer-events:none;opacity:.72} +.entry-permanent{min-height:64px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px;padding:10px 16px;border-radius:12px;background:#FFC00014;font-size:14px;line-height:20px;width:100%} +.entry-permanent span:last-child{color:var(--accent)} +.entry-download{position:relative;isolation:isolate;overflow:hidden;width:100%;height:64px;flex:none;border-radius:12px;display:grid;place-items:center;color:#f6f2f9;font-size:14px;line-height:20px} +.entry-download img{position:absolute;width:100%;height:280.28%;left:0;top:-62.36%;object-fit:cover;z-index:-2} +.entry-download::before{content:"";position:absolute;inset:0;background:#0009;z-index:-1} +.entry-contact{font-size:12px;line-height:18px;color:var(--accent);text-align:center;overflow-wrap:anywhere} +.entry-channels{width:100%;display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:4px} +.entry-channel{display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0;padding:0;font-size:12px;line-height:18px;color:#fff;white-space:nowrap} +.entry-channel-icon{display:grid;place-items:center;background:#ffffff1a;border-radius:999px;width:30px;height:30px} +.entry-channel-icon img{display:block} +.entry-footer{display:flex;flex-direction:column;gap:12px;font-size:12px;line-height:18px;text-align:center;color:#ffffff99;width:100%} +@media(hover:hover){.entry-route:hover{background:#ffffff17}.entry-channel:hover,.entry-download:hover{color:var(--accent)}} +@media(max-width:760px){.entry-background{object-position:32% center}.entry-stage{padding-top:max(14px,env(safe-area-inset-top));padding-bottom:max(14px,env(safe-area-inset-bottom))}} + + + +.startup-age-mark .startup-age-mobile-icon,.startup-age-mobile-copy,#startup-age>.startup-close{display:none} +@media(max-width:760px){ + #startup-age{width:300px;background:transparent;border-radius:0} + #startup-age .startup-age-layout{display:flex;flex-direction:column;gap:18px;padding:24px 20px;background:#0d0d0d;border:1px solid #ffffff33;border-radius:20px;min-height:0} + #startup-age .startup-age-mark{padding:0;background:none} + .startup-age-desktop-icon{display:none!important} + .startup-age-mark .startup-age-mobile-icon{display:block} + #startup-age .startup-age-copy{padding:0;gap:18px} + .startup-age-desktop-copy{display:none} + .startup-age-mobile-copy{display:block} + #startup-age .startup-age-mobile-copy h2{font-size:18px;line-height:24px;margin:0 0 24px;text-align:center} + #startup-age .startup-age-mobile-copy p{font-size:14px;line-height:20px;text-align:justify;color:#b5b5b5} + #startup-age .startup-age-actions{display:flex;flex-direction:column;gap:12px} + #startup-age .startup-age-actions button{width:100%;height:50px;min-height:50px;border-radius:12px;padding:13px 0;font-size:18px;line-height:24px;font-weight:400} + #startup-age .startup-age-leave{background:#ffffff1a;color:#fff} + #startup-age>.startup-close{display:flex;margin-top:36px} + #startup-ad{width:300px} + #startup-ad .startup-ad-image{aspect-ratio:300/468;border-radius:24px} + #startup-ad .startup-close{margin-top:36px} + #startup-grid{width:304px} + #startup-grid .startup-promotions{padding:0;gap:8px;grid-template-columns:repeat(4,minmax(0,1fr))} + #startup-grid .startup-promotion{gap:3.5px} + #startup-grid .startup-promotion img{height:auto;aspect-ratio:1 / 1;border-radius:22%} + #startup-grid .startup-promotion span{font-size:12px;line-height:18px} + #startup-grid .startup-close{margin-top:24px} +} + + + +#startup-age .startup-age-actions button{transition:background-color .18s ease,color .18s ease,transform .18s ease} +@media(hover:hover) and (pointer:fine){ + #startup-age .startup-age-confirm:hover{background:#ff6829;color:#fff;transform:translateY(-1px)} + #startup-age .startup-age-leave:hover{background:#ffffff20;color:#fff;transform:translateY(-1px)} +} +#startup-age .startup-age-actions button:active{transform:scale(.98)} +#startup-age .startup-age-confirm:active{background:#df4000;color:#fff} +#startup-age .startup-age-leave:active{background:#ffffff2b;color:#fff} +@media(prefers-reduced-motion:reduce){#startup-age .startup-age-actions button{transition:none;transform:none}} + + + +.entry-channel{transition:color .18s ease} +.entry-channel-icon{transition:background-color .18s ease} +#entry-navigation .entry-channel-icon .entry-channel-svg{display:block;flex-shrink:0;width:16px;height:16px;fill:none;stroke:none;stroke-width:0} +#entry-navigation .entry-channel-icon .entry-channel-svg[width="18"]{width:18px;height:18px} +#entry-navigation .entry-channel:has(.entry-channel-svg[width="18"]) .entry-channel-icon{width:32px;height:32px} +@media(hover:hover){#entry-navigation .entry-channel:hover{color:var(--accent)}#entry-navigation .entry-channel:hover .entry-channel-icon{background:#FFC0001f}} +@media(prefers-reduced-motion:reduce){.entry-channel,.entry-channel-icon{transition:none}} + + + +#entry-navigation svg.entry-brand-logo{display:block;width:180px;height:auto;aspect-ratio:135/40;fill:none;stroke:none;stroke-width:0;flex-shrink:0} + + + +.sidebar-collapsed .side .sidebar-header>.sidebar-logo-toggle{display:grid;place-items:center;box-sizing:border-box;width:44px;height:44px;min-height:44px;padding:0;margin:0;gap:0} +.sidebar-collapsed .side .sidebar-header .sidebar-brand-window{margin:0;justify-self:center;align-self:center} + + + +/* Figma desktop header 40001:94038, footer 40001:93696. */ +:root{--accent:#FFC000;--hg-surface:#282828;--hg-content:1552px;--global-header-height:90px} +.side{display:none!important} +html body .shell,html.sidebar-collapsed body .shell{margin-left:auto!important;margin-right:auto!important;max-width:1600px;padding-left:24px;padding-right:24px} +body{padding-top:90px}html{scroll-padding-top:110px} +#global-site-header.shell{max-width:none;margin:0!important;padding:0 24px;background:#282828;border-bottom:0;backdrop-filter:blur(2px)} +#global-site-header>.top{height:90px;min-height:90px;max-width:1520px;margin:0 auto;padding:0;display:flex;flex-wrap:nowrap;gap:0} +.hg-header-brand{display:block;flex:0 0 125px;width:125px;height:36px;margin-right:60px} +.hg-complete-logo{display:block;width:125px;height:36px;object-fit:contain} +.hg-desktop-nav{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-right:auto} +.hg-desktop-nav a{display:flex;align-items:center;justify-content:center;padding:6px 12px;height:34px;white-space:nowrap;color:#fff;font-size:16px;line-height:22px;border-radius:999px;transition:background .18s} +.hg-desktop-nav a:hover{background:#ffffff0d}.hg-desktop-nav a[aria-current=page]{background:var(--accent);font-weight:400} +#global-site-header .top .search{order:0;flex:0 1 400px;width:400px;min-width:180px;height:40px;margin:0 24px 0 30px;padding:0 18px;border:1px solid #ffffff0d;background:#ffffff0d;box-shadow:0 0 12px #0000000d;gap:12px} +#global-site-header .top .search input{font-size:14px;color:#fff;text-shadow:none} +#global-site-header .top .search input::placeholder{color:#ffffff59} +#global-site-header .search button[type=submit]{display:grid;place-items:center;width:20px;height:20px;min-width:20px;padding:0;background:none} +.hg-search-icon{display:block;width:16.6666px;height:16.6663px} +#global-site-header .header-actions{flex:0 0 auto;gap:24px;min-width:0;margin:0} +#global-site-header .header-actions .coin-entry{padding:0;width:28px;height:28px;background:none!important;border:0;box-shadow:none} +#global-site-header .coin-caption{display:none} +#global-site-header .coin-symbol{display:block;position:relative;overflow:hidden;width:28px;height:28px;flex:0 0 28px;background:none;box-shadow:none;border:0} +#global-site-header .coin-symbol:after,#global-site-header .coin-symbol:before{content:none} +#global-site-header .coin-symbol img{position:absolute;max-width:none;width:100%;height:100%;left:0;top:0;transform:none;filter:none} +#global-site-header .header-download-wrap{display:none} +#global-site-header .account-entry{padding:0 0 0 24px;position:relative;gap:8px;display:flex;flex-direction:row;align-items:center;width:auto;height:32px;border-radius:0} +#global-site-header .account-entry:before{content:'';position:absolute;left:0;top:10px;width:1px;height:12px;background:#ffffff1a} +#global-site-header .account-avatar,#global-site-header .account-avatar img{width:32px;height:32px;flex-basis:32px;flex-shrink:0;border-radius:50%;background:none;border:0} +#global-site-header .account-copy{display:flex;justify-content:center}.account-copy strong{font-size:16px;font-weight:400} +#global-site-header .account-copy small{display:none} +.hg-footer{margin-top:70px;padding:24px max(24px,calc((100% - 1520px)/2));min-height:252px;background:#282828;border-top:1px solid #ffffff1a;color:#ffffff8c} +.hg-footer-main{display:flex;justify-content:space-between;align-items:flex-end;gap:36px} +.hg-footer-brand{display:flex;align-items:center;gap:18px;margin-bottom:18px} +.hg-footer-brand .hg-logo-slot{width:178px;height:51.264px;flex-shrink:0} +.hg-footer-brand img{transform:scale(1.424);transform-origin:left top} +.hg-footer-brand span{font-size:18px;line-height:24px} +.hg-footer-addresses{display:grid;gap:4px;font-size:16px;line-height:22px} +.hg-footer-addresses a:hover,.hg-footer-links a:hover{color:var(--accent)} +.hg-footer-links{display:flex;align-items:center;flex-wrap:wrap;gap:15px;padding-bottom:2px;color:#fff;font-size:16px;line-height:22px} +.hg-footer-links a+a{border-left:1px solid #ffffff33;padding-left:15px} +.hg-footer-bottom{display:flex;align-items:center;justify-content:space-between;gap:24px;margin-top:12px;padding-top:12px;border-top:1px solid #ffffff0d;font-size:14px;line-height:20px} +.hg-footer-social{display:flex;align-items:center;gap:16px} +.hg-footer-social a{display:grid;place-items:center;width:38px;height:38px;border-radius:50%;background:#ffffff1a} +.hg-footer-social a:hover{background:var(--accent)} +.hg-footer-social img{display:block;max-width:none} +.hg-footer-social a:first-child img{width:24px;height:24px}.hg-footer-social a:not(:first-child) img{width:20px;height:20px} + + +#entry-navigation .entry-brand-logo{display:block;width:125px;height:36px;transform:scale(1.44);transform-origin:center;margin:8px auto} +#channel-page,#info-page{min-height:calc(100vh - 412px)} +@media(min-width:761px){.mobile-pages{display:none!important}#global-site-header .mobile-menu-trigger{display:none!important}} +@media(min-width:761px) and (max-width:1550px){ + .hg-header-brand{margin-right:26px}.hg-desktop-nav{gap:8px}.hg-desktop-nav a{padding:6px 12px;font-size:14px} + #global-site-header .top .search{margin-left:20px;margin-right:18px;flex-basis:330px}#global-site-header .header-actions{gap:18px}.hg-footer-links{font-size:14px} +} +@media(min-width:761px) and (max-width:1150px){ + #global-site-header>.top{height:114px;min-height:114px;flex-wrap:wrap;align-content:center;row-gap:12px} + .hg-header-brand{order:0;margin-right:auto}.hg-desktop-nav{order:3;width:100%;justify-content:center;margin:0} + #global-site-header .top .search{flex:0 1 340px;margin-left:20px}.hg-footer-main{align-items:flex-start;flex-direction:column;gap:24px} +} +@media(max-width:760px){ + html body .shell,html.sidebar-collapsed body .shell{padding-left:16px;padding-right:16px;margin-left:0!important;margin-right:0!important} + body{padding-top:68px}#global-site-header.shell{padding:env(safe-area-inset-top,0px) 16px 0} + #global-site-header>.top{height:68px;min-height:68px;gap:8px;max-width:none} + .hg-header-brand,.hg-desktop-nav{display:none}#global-site-header .mobile-menu-trigger{display:grid!important} + #global-site-header .top .search{flex:1 1 0;min-width:0;width:0;margin:0;height:40px;padding:0 12px;gap:8px} + #global-site-header .header-actions{gap:10px}#global-site-header .account-entry{padding:0;width:32px;gap:0}#global-site-header .account-entry:before{content:none}#global-site-header .account-copy{display:none} + #global-site-header .header-actions .coin-entry{width:24px;height:24px}#global-site-header .coin-symbol{width:24px;height:24px;flex-basis:24px} + .hg-footer{padding:24px 20px;margin-top:40px;min-height:0}.hg-footer-main{display:block}.hg-footer-brand{align-items:flex-start;flex-direction:column;gap:12px}.hg-footer-brand span{font-size:14px}.hg-footer-addresses{font-size:12px;line-height:20px} + .hg-footer-links{gap:10px;margin-top:22px;font-size:12px}.hg-footer-links a+a{padding-left:10px}.hg-footer-bottom{align-items:flex-start;flex-direction:column;font-size:11px;gap:16px} + #mobile-navigation-drawer .mobile-drawer-brand img{width:125px;height:36px}#mobile-navigation-drawer .side-info-wordmark{width:125px;height:36px} + .mobile-pages{background:#282828f5} +} + + + +:root{--global-header-height:91px} +body{padding-top:var(--global-header-height)} +.home-content{padding-top:24px} +.home-content>.hero-grid{margin-top:0} +@media(min-width:761px) and (max-width:1150px){:root{--global-header-height:115px}} +@media(max-width:760px){:root{--global-header-height:69px}.home-content{padding-top:16px}} + + + +#global-site-header.shell{transition:background-color .22s ease,border-color .22s ease,box-shadow .22s ease} +#global-site-header.shell.is-scrolled{background:rgba(30,30,32,.66);backdrop-filter:blur(18px) saturate(135%);-webkit-backdrop-filter:blur(18px) saturate(135%);border-bottom:0;box-shadow:0 4px 20px rgba(0,0,0,.08)} +@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){#global-site-header.shell.is-scrolled{background:rgba(30,30,32,.94)}} +@media(prefers-reduced-motion:reduce){#global-site-header.shell{transition:none}} + + + +#global-site-header .header-download-wrap{display:block;order:-1} +#global-site-header .header-actions .download-entry{display:flex;align-items:center;justify-content:center;gap:7px;height:40px;padding:0 14px;margin:0;border:1px solid #ffffff1c;border-radius:999px;background:#ffffff09;color:#fff;white-space:nowrap;font-size:14px} +#global-site-header .header-actions .download-entry svg{width:18px;height:18px;flex:0 0 18px} +#global-site-header .header-actions .coin-entry{display:flex;flex-direction:row;align-items:center;gap:7px;width:auto;height:40px;padding:0 14px;border:1px solid #ffffff1c;border-radius:999px;background:#ffffff09!important;box-shadow:none;box-sizing:border-box;white-space:nowrap} +#global-site-header .coin-caption{display:flex;align-items:center;height:100%;font-size:0;line-height:1} +#global-site-header .coin-value{display:flex;align-items:center;height:100%;line-height:1} +#global-site-header .header-actions .coin-entry:hover{background:#ffffff14!important;border-color:#ffffff35} +#global-site-header .coin-value{font-size:14px;font-weight:500;color:#f3cf75} +@media(min-width:761px) and (max-width:1350px){ + #global-site-header>.top{height:114px;min-height:114px;flex-wrap:wrap;align-content:center;row-gap:12px} + .hg-header-brand{order:0;margin-right:auto}.hg-desktop-nav{order:3;width:100%;justify-content:center;margin:0} + #global-site-header .top .search{flex:0 1 340px;margin-left:20px} +} +@media(max-width:760px){ + #global-site-header .header-actions{gap:8px} + #global-site-header .header-actions .download-entry{width:30px;height:34px;padding:0;border:0;background:none} + #global-site-header .download-entry>span{display:none} + #global-site-header .header-actions .coin-entry{gap:4px;height:34px;padding:0 8px} + #global-site-header .coin-value{font-size:12px} + #global-site-header .header-download-qr{position:fixed;top:calc(var(--global-header-height) + 8px);right:16px} +} + + + +.home-content .section-head{display:flex;align-items:center;justify-content:space-between;gap:20px;margin-bottom:20px} +.home-content .section-heading-copy{min-width:0} +.home-content .section-head h2{padding:0;margin:0;font-size:24px;line-height:1.4;letter-spacing:0;font-weight:600} +.home-content .section-head h2::before{content:none} +.home-content .section-head .section-heading-copy>p{margin:8px 0 0;font-size:14px;line-height:1.5;color:#929292;font-weight:400} +.home-content .section-head .topic-more,.home-content .section-head #reset{display:inline-flex;align-items:center;justify-content:center;gap:8px;flex:0 0 auto;min-height:36px;padding:8px 14px;margin:0;border:1px solid #ffffff0d;border-radius:999px;background:#ffffff08;color:#c7c7c7;font-size:12px;line-height:18px;white-space:nowrap} +.home-content .section-head .topic-more span,.home-content .section-head #reset span{font-size:17px;line-height:16px} +.home-content .section-head .topic-more:hover,.home-content .section-head .topic-more:focus-visible,.home-content .section-head #reset:hover,.home-content .section-head #reset:focus-visible{background:#ffffff12;color:var(--accent)!important;border-color:#ffffff20} +@media(max-width:760px){.home-content .section-head{gap:12px}.home-content .section-head h2{font-size:20px}.home-content .section-head .section-heading-copy>p{font-size:12px;margin-top:6px}.home-content .section-head .topic-more,.home-content .section-head #reset{gap:4px;padding:6px 10px;min-height:32px;font-size:11px}} + + + +#global-site-header .account-entry .account-copy{display:none} +#global-site-header .account-entry{gap:0} +.home-content #romance-rows{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:26px 22px} +@media(min-width:761px) and (max-width:1150px){.home-content #romance-rows{grid-template-columns:repeat(4,minmax(0,1fr));gap:24px 18px}} +@media(max-width:760px){.home-content #romance-rows{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}} + + + +.home-content .section-head h2{position:relative;padding-left:18px} +.home-content .section-head h2::before{content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);width:6px;height:1.1em;border-radius:999px;background:var(--accent)} + + + +#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(5,minmax(0,1fr));gap:32px 16px} +.channel-ranking-cards .drama-data-card{position:relative;align-self:start} +.channel-ranking-cards .drama-data-card .cover{overflow:visible;border-radius:8px} +.channel-ranking-cards .drama-data-card .cover>img{border-radius:8px} +.channel-ranking-cards .drama-data-card .cover::after{border-radius:8px} +.channel-ranking-cards .rank-home-badge,.channel-ranking-cards .rank-home-badge[data-rank]{left:0;bottom:-22px;display:block;width:auto;min-width:0;height:auto;padding:0;border-radius:0;background:none;color:#fff;font:900 72px/.95 Arial,sans-serif;letter-spacing:-4px;-webkit-text-stroke:2px #131414;paint-order:stroke fill;text-shadow:0 2px 0 #131414} +.channel-ranking-cards .drama-data-card>:is(h2,h3){margin:32px 0 5px;font-size:16px;line-height:1.5;font-weight:600} +.channel-ranking-cards .drama-description{display:none} +.channel-ranking-cards .drama-meta{margin-top:0} + +#channel-page[data-channel="rank"] .channel-tabs{gap:8px} +#channel-page[data-channel="rank"] .channel-tabs button{border-radius:999px} +@media(max-width:1000px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(3,minmax(0,1fr))}} +@media(max-width:600px){#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:26px 12px}.channel-ranking-cards .rank-home-badge,.channel-ranking-cards .rank-home-badge[data-rank]{font-size:56px;bottom:-18px;letter-spacing:-3px}.channel-ranking-cards .drama-data-card>:is(h2,h3){margin-top:26px;font-size:14px}} + + + +#twist-cards .ranked-card{align-self:start;overflow:visible} +#twist-cards .media{position:relative;overflow:visible;border-radius:8px} +#twist-cards .media>img,#twist-cards .media::after{border-radius:8px} +#twist-cards .rank-home-badge,#twist-cards .rank-home-badge[data-rank]{position:absolute;left:0;bottom:-22px;z-index:3;display:block;width:auto;min-width:0;height:auto;padding:0;border-radius:0;background:none;color:#fff;font:900 72px/.95 Arial,sans-serif;letter-spacing:-4px;-webkit-text-stroke:2px #131414;paint-order:stroke fill;text-shadow:0 2px 0 #131414;pointer-events:none} +#twist-cards .rank-caption{display:block;margin:32px 0 5px} +#twist-cards .rank-caption>span{display:block;min-width:0} +#twist-cards .rank-caption h3{margin:0;font-size:16px;line-height:1.5;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +#twist-cards .drama-meta{margin-top:0} + +@media(max-width:600px){#twist-cards .rank-home-badge,#twist-cards .rank-home-badge[data-rank]{font-size:56px;bottom:-18px;letter-spacing:-3px}#twist-cards .rank-caption{margin-top:26px}#twist-cards .rank-caption h3{font-size:14px}} + + + +.ranking-carousel{position:relative;min-width:0} +#twist-cards.ranking-rail,#channel-page[data-channel="rank"] #channel-content .channel-ranking-cards.ranking-rail{display:grid;grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:calc((100% - (var(--rank-visible) - 1)*var(--rank-gap))/var(--rank-visible));gap:var(--rank-gap);overflow-x:auto;overflow-y:hidden;scroll-snap-type:x mandatory;scrollbar-width:none;padding-bottom:6px;overscroll-behavior-x:contain} +#twist-cards.ranking-rail{--rank-visible:6;--rank-gap:18px} +.channel-ranking-cards.ranking-rail{--rank-visible:5;--rank-gap:16px} +.ranking-rail::-webkit-scrollbar{display:none} +.ranking-rail>button{scroll-snap-align:start;min-width:0} +.ranking-carousel-arrow{position:absolute;top:var(--rank-arrow-top,40%);z-index:5;display:flex;align-items:center;justify-content:center;width:40px;height:40px;padding:0;border:1px solid #ffffff45;border-radius:50%;background:rgba(24,25,27,.78);color:#fff;backdrop-filter:blur(8px);box-shadow:0 2px 8px #0003;transform:translateY(-50%);cursor:pointer;transition:color .18s,background-color .18s,border-color .18s} +.ranking-carousel-arrow.prev{left:-18px}.ranking-carousel-arrow.next{right:-18px} +.ranking-carousel-arrow[hidden]{display:none} +.ranking-carousel-arrow svg{display:block!important;opacity:1!important;visibility:visible!important;width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.8;transition:none;transform:none} +.ranking-carousel-arrow:hover,.ranking-carousel-arrow:focus-visible{background:#FFC000;color:#fff;border-color:#FFC000;transform:translateY(-50%)} +@media(max-width:1180px){#twist-cards.ranking-rail{--rank-visible:3}} +@media(max-width:1000px){.channel-ranking-cards.ranking-rail{--rank-visible:3}} +@media(max-width:760px){#twist-cards.ranking-rail{--rank-visible:2}.ranking-carousel-arrow{width:32px;height:32px}.ranking-carousel-arrow.prev{left:-10px}.ranking-carousel-arrow.next{right:-10px}} +@media(max-width:600px){.channel-ranking-cards.ranking-rail{--rank-visible:2;--rank-gap:12px}} + + + +.drama-data-card .drama-meta{justify-content:flex-start;gap:8px;flex-wrap:wrap} +.drama-data-card .drama-meta .drama-tags{flex:0 1 auto;margin-right:0} +.drama-data-card .drama-heat{display:inline-flex;align-items:center;gap:4px;margin-left:0;color:#ff7959;font-size:12px;line-height:18px} +.drama-data-card .drama-heart-icon{display:block;width:12px;height:12px;fill:currentColor;stroke:none;flex:0 0 12px} +.ranking-carousel-arrow svg path{fill:none!important;stroke:currentColor!important;stroke-width:1.8!important} + + + +#twist-cards .ranking-poster-frame,.channel-ranking-cards .ranking-poster-frame{display:block;overflow:hidden;border-radius:inherit} +#twist-cards .ranked-card .poster-source,.channel-ranking-cards .drama-data-card .poster-source{transform:scale(1);filter:none;box-shadow:none;transition:transform .38s cubic-bezier(.2,.65,.3,1)} +#twist-cards .ranked-card:hover .rank-caption h3{color:var(--text)} +@media(hover:hover) and (pointer:fine){#twist-cards .ranked-card:hover .poster-source,.channel-ranking-cards .drama-data-card:hover .poster-source{transform:scale(1.06)}} +#twist-cards .ranked-card:focus-visible .poster-source,.channel-ranking-cards .drama-data-card:focus-visible .poster-source{transform:scale(1.06)} +@media(prefers-reduced-motion:reduce){#twist-cards .ranked-card .poster-source,.channel-ranking-cards .drama-data-card .poster-source{transition:none!important;transform:none!important}} + + +#hero .eyebrow b{background:rgba(255,192,0,.16);color:var(--accent)} + + +.drama-data-card>.cover,.drama-data-card>.media{position:relative} +.drama-data-card>.cover::before,.drama-data-card>.media::before{content:'';position:absolute;inset:0;z-index:4;border-radius:inherit;padding:1px;background:linear-gradient(180deg,rgba(255,255,255,.2) 0%,rgba(255,255,255,.09) 30%,rgba(255,255,255,.025) 70%,transparent 100%);-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);mask-composite:exclude;pointer-events:none} + + + +.hg-footer{margin-top:64px;padding:44px max(32px,calc((100% - 1520px)/2)) 24px;min-height:0;background:linear-gradient(180deg,#1c1d1f,#18191b);border-top:1px solid #ffffff08;color:#939497} +.hg-footer-main{display:grid;grid-template-columns:minmax(0,1fr) 280px;align-items:start;gap:64px;padding-bottom:34px} +.hg-footer-main>div{display:grid;grid-template-columns:230px minmax(0,1fr);align-items:start;gap:48px} +.hg-footer-brand{display:flex;flex-direction:column;align-items:flex-start;gap:15px;margin:0} +.hg-footer-brand .hg-logo-slot{display:block;width:150px;height:43.2px} +.hg-footer-brand img{width:150px;height:43.2px;transform:none} +.hg-footer-brand>span{font-size:13px;line-height:22px;color:#898b90;letter-spacing:1px} +.hg-footer-addresses{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:24px;padding-top:3px;font-size:14px;line-height:22px} +.hg-footer-addresses>a{display:flex;flex-direction:column;gap:12px;min-width:0} +.hg-address-label{font-size:12px;color:#777a80} +.hg-address-value{font-size:14px;color:#d0d1d4;white-space:nowrap;transition:color .2s} +.hg-address-value>span{display:inline-block;margin-left:4px;font-size:13px;color:#64676e;transition:color .2s} +.hg-footer-addresses>a:hover .hg-address-value,.hg-footer-addresses>a:hover .hg-address-value>span{color:var(--accent)} +.hg-footer-links{display:grid;grid-template-columns:repeat(3,max-content);gap:14px 22px;margin:0;padding:0;font-size:13px;line-height:20px;color:#b6b8bd} +.hg-footer-links::before{content:'关于与帮助';grid-column:1/-1;margin-bottom:1px;font-size:12px;color:#777a80} +.hg-footer-links a+a{border:0;padding:0} +.hg-footer-bottom{margin:0;padding-top:22px;border-top:1px solid #ffffff08;font-size:12px;line-height:20px;color:#696c73} +.hg-footer-social{gap:10px} +.hg-footer-social a{width:32px;height:32px;border:1px solid #ffffff0b;background:#ffffff04;transition:background .2s,border-color .2s} +.hg-footer-social a:hover{background:#FFC00018;border-color:#FFC00040} +.hg-footer-social img{opacity:.65}.hg-footer-social a:hover img{opacity:1} +.hg-footer-social a:first-child img{width:19px;height:19px}.hg-footer-social a:not(:first-child) img{width:16px;height:16px} +@media(max-width:1180px){.hg-footer-main{grid-template-columns:minmax(0,1fr) 240px;gap:32px}.hg-footer-main>div{grid-template-columns:1fr;gap:28px}.hg-footer-addresses{gap:20px}.hg-footer-links{gap:14px 16px}} +@media(max-width:760px){.hg-footer{margin-top:40px;padding:32px 20px 24px}.hg-footer-main{grid-template-columns:1fr;gap:30px;padding-bottom:26px}.hg-footer-main>div{gap:24px}.hg-footer-brand{gap:10px}.hg-footer-brand>span{font-size:12px}.hg-footer-addresses{gap:12px}.hg-footer-addresses>a{gap:7px}.hg-address-label{font-size:11px}.hg-address-value{font-size:12px}.hg-address-value>span{display:none}.hg-footer-links{grid-template-columns:repeat(3,max-content);gap:12px 24px;font-size:12px}.hg-footer-bottom{flex-direction:row;align-items:center;gap:14px;padding-top:20px;font-size:10px}.hg-footer-social{gap:7px}.hg-footer-social a{width:28px;height:28px}} + + + +.hg-footer-links{grid-template-columns:repeat(2,minmax(0,1fr));gap:18px 28px;font-size:14px;line-height:22px;color:#a4a6a2} +.hg-footer-links::before{content:none} +@media(max-width:760px){.hg-footer-links{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px 24px;font-size:14px}} + + + +.hg-footer-original{padding:32px max(32px,calc((100% - 1520px)/2)) 28px;background:#1e1f20} +.hg-footer-original-info{width:220px;max-width:100%} +.hg-footer-original-title{display:block;color:#f2f2f2;font-size:22px;line-height:1.3;font-weight:750;letter-spacing:.5px} +.hg-footer-original-title>span{color:var(--accent);margin-left:3px} +.hg-footer-original-slogan{margin:14px 0 28px;font-size:14px;line-height:22px;color:#93968f} +.hg-footer-original .hg-footer-links{display:grid;grid-template-columns:1fr 1fr;gap:14px 24px;color:#a4a7a1;font-size:14px;line-height:22px} +.hg-footer-original .hg-footer-links::before{content:none} +.hg-footer-original-copyright{display:block;margin-top:24px;color:#777c74;font-size:12px;line-height:24px} +.hg-footer-original .hg-footer-social{display:flex;gap:12px;margin-top:28px} +.hg-footer-original .hg-footer-social a{width:36px;height:36px;background:#ffffff09;border:0} +@media(max-width:760px){.hg-footer-original{padding:28px 20px}.hg-footer-original-info{width:220px}} + + +.hg-footer-original-title>img{display:block;width:124px;height:30px} + + +.hg-footer-original-info{width:100%;max-width:100%;min-width:0} +.hg-footer-original-title{display:flex;align-items:flex-end;gap:6px} +.hg-footer-brand-red-dot{display:block;width:6px;height:6px;margin-bottom:3px;border-radius:50%;background:#ff3b45} +.hg-footer-original .hg-footer-links{display:flex;align-items:center;flex-wrap:nowrap;gap:0;overflow-x:auto;padding-bottom:2px} +.hg-footer-original .hg-footer-links>a{flex:0 0 auto;white-space:nowrap} +.hg-footer-original .hg-footer-links>a+a{border-left:1px solid #ffffff24;margin-left:16px;padding-left:16px} +.hg-footer-original-copyright{white-space:nowrap;margin-top:22px} +.hg-footer-original .hg-footer-social a{display:grid;place-items:center;color:#e4e6e9} +.hg-footer-original .hg-footer-social img,.hg-footer-original .hg-footer-social svg{width:19px;height:19px;opacity:1;fill:currentColor;max-width:100%} +@media(max-width:760px){.hg-footer-original .hg-footer-links{font-size:12px}.hg-footer-original .hg-footer-links>a+a{margin-left:12px;padding-left:12px}.hg-footer-original-copyright{font-size:11px}} + + + +.hg-footer-original-info{display:grid;grid-template-columns:max-content minmax(0,1fr) auto;align-items:center;column-gap:22px;row-gap:22px} +.hg-footer-original-title{grid-column:1;grid-row:1;margin:0} +.hg-footer-original-slogan{grid-column:2;grid-row:1;margin:0} +.hg-footer-original .hg-footer-links{grid-column:1/3;grid-row:2;padding-bottom:0} +.hg-footer-original-copyright{grid-column:1/3;grid-row:3;margin:0} +.hg-footer-original .hg-footer-social{grid-column:3;grid-row:2/4;align-self:center;display:flex;gap:12px;margin:0;padding-right:58px} +.hg-footer-original .hg-footer-social a{transition:background .2s,box-shadow .2s,transform .2s;outline-offset:4px} +.hg-footer-original .hg-footer-social a:hover,.hg-footer-original .hg-footer-social a:focus-visible{background:#FFC00026;box-shadow:inset 0 0 0 1px #FFC00070;transform:translateY(-3px)} +.hg-footer-original .hg-footer-social img,.hg-footer-original .hg-footer-social svg{transition:filter .2s} +.hg-footer-original .hg-footer-social a:hover img,.hg-footer-original .hg-footer-social a:hover svg{filter:brightness(1.2)} +@media(max-width:760px){.hg-footer-original-info{grid-template-columns:max-content minmax(0,1fr);column-gap:14px;row-gap:20px}.hg-footer-original-slogan{font-size:12px;letter-spacing:0}.hg-footer-original .hg-footer-links{grid-column:1/-1}.hg-footer-original-copyright{grid-column:1/-1}.hg-footer-original .hg-footer-social{grid-column:1/-1;grid-row:4;justify-self:end;padding-right:44px}} +@media(prefers-reduced-motion:reduce){.hg-footer-original .hg-footer-social a,.hg-footer-original .hg-footer-social img,.hg-footer-original .hg-footer-social svg{transition:none}.hg-footer-original .hg-footer-social a:hover,.hg-footer-original .hg-footer-social a:focus-visible{transform:none}} + + + +:root{--action-fill:linear-gradient(90deg,rgba(255,192,0,.6) 0%,#FFC000 100%);--action-stroke:#FFC000} +body .primary,body .bottom-app-download,body .feedback-submit-button,body .auth-submit,body .startup-age-confirm{background:var(--action-fill);border:1px solid var(--action-stroke);color:#fff;box-sizing:border-box;transition:filter .2s,box-shadow .2s,transform .2s} +body .primary:hover,body .bottom-app-download:hover,body .feedback-submit-button:hover,body .auth-submit:hover,body #startup-age .startup-age-confirm:hover{background:var(--action-fill);color:#fff;filter:brightness(1.1);box-shadow:0 4px 16px #FFC0001c} +body .primary:active,body .bottom-app-download:active,body .feedback-submit-button:active,body .auth-submit:active,body #startup-age .startup-age-confirm:active{background:var(--action-fill);filter:brightness(.94);box-shadow:none} +.hg-footer-original .hg-footer-social a{background:linear-gradient(135deg,#ffffff0b,#ffffff03);border:1px solid #ffffff16;box-shadow:inset 0 1px 0 #ffffff08} +.hg-footer-original .hg-footer-social a:hover,.hg-footer-original .hg-footer-social a:focus-visible{background:var(--action-fill);border-color:var(--action-stroke);box-shadow:0 4px 14px #FFC00026;transform:translateY(-2px)} +@media(prefers-reduced-motion:reduce){body .primary,body .bottom-app-download,body .feedback-submit-button,body .auth-submit,body .startup-age-confirm{transition:none}} + + + +.hg-desktop-nav a{border:1px solid transparent;transition:background .2s,border-color .2s,box-shadow .2s;color:#fff} +.hg-desktop-nav a[aria-current=page]{background:var(--action-fill);border-color:var(--action-stroke);box-shadow:inset 0 1px 0 #ffffff16;color:#fff} +.hg-desktop-nav a:hover:not([aria-current=page]){background:linear-gradient(90deg,#FFC00014,#FFC00024);border-color:#FFC00045;color:#fff} +.hg-desktop-nav a[aria-current=page]:hover{background:var(--action-fill);box-shadow:inset 0 1px 0 #ffffff20,0 3px 12px #FFC00020} +.hg-desktop-nav a:active{filter:brightness(.94)} +@media(prefers-reduced-motion:reduce){.hg-desktop-nav a{transition:none}} + + + +.hg-desktop-nav a,.hg-desktop-nav a[aria-current=page]{font-weight:400} +#hero .primary .hero-rounded-play{display:block;flex:0 0 16px;width:16px;height:16px;fill:currentColor;stroke:none} +.hero-preview-arrow svg{stroke-width:2;stroke-linecap:round;stroke-linejoin:round} + + + +.ui-empty-state{--empty-heading:#d5d5da;--empty-copy:#8d8d97;--empty-button-bg:#ffffff06;--empty-button-line:#ffffff25;--empty-button-text:#bfc0c8} +.ui-empty-state>img{width:210px;height:180px;object-fit:contain;filter:brightness(.94)} +.ui-empty-state>:is(h2,h3){color:var(--empty-heading)}.ui-empty-state>p{color:var(--empty-copy)} +.ui-empty-state .ui-empty-action{background:var(--empty-button-bg);border-color:var(--empty-button-line);color:var(--empty-button-text)!important} +:is([data-theme="light"],.light-mode,.theme-light) .ui-empty-state,.ui-empty-state[data-theme="light"]{--empty-heading:#36383e;--empty-copy:#85878d;--empty-button-bg:#00000003;--empty-button-line:#00000016;--empty-button-text:#62656d} +:is([data-theme="light"],.light-mode,.theme-light) .ui-empty-state>img,.ui-empty-state[data-theme="light"]>img{filter:none} +.ui-empty-state .ui-empty-action:hover{background:#FFC00010;border-color:var(--accent);color:var(--accent)!important} +@media(prefers-color-scheme:light){html:not([data-theme="dark"]):not(.dark-mode):not(.theme-dark) .ui-empty-state[data-auto-theme]{--empty-heading:#36383e;--empty-copy:#85878d;--empty-button-bg:#00000003;--empty-button-line:#00000016;--empty-button-text:#62656d}} +@media(max-width:600px){.ui-empty-state>img{width:176px;height:154px}} + + + +:root{--modal-surface:linear-gradient(45deg,rgba(17,21,24,.9) 0%,rgba(36,43,49,.9) 100%);--modal-outline:rgba(255,255,255,.05);--modal-shadow:0 6px 40px rgba(0,0,0,.5)} +body dialog:not(#startup-ad):not(#startup-grid){background:var(--modal-surface)!important;border:1px solid var(--modal-outline)!important;box-shadow:var(--modal-shadow)!important;backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);border-radius:20px!important} +body #member-auth-dialog .auth-submit{height:46px;font-size:16px;font-weight:500;border-radius:999px;background:var(--action-fill)!important;border:1px solid var(--action-stroke)!important;color:#fff;box-shadow:inset 0 1px 0 #ffffff16} +body #member-auth-dialog .auth-submit:hover{filter:brightness(1.1);box-shadow:inset 0 1px 0 #ffffff20,0 4px 16px #FFC0001c} +body #member-auth-dialog .auth-submit:active{filter:brightness(.94);box-shadow:inset 0 1px 0 #ffffff16} +@media(max-width:600px){body #startup-age{background:transparent!important;border:0!important;box-shadow:none!important}body #startup-age .startup-age-layout{background:var(--modal-surface)!important;border:1px solid var(--modal-outline)!important;box-shadow:var(--modal-shadow)!important}} + + + +@media(max-width:760px){ + #twist-cards.ranking-rail{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-auto-flow:row;grid-auto-columns:auto;gap:24px 12px;overflow:visible;scroll-snap-type:none;overscroll-behavior:auto} + #twist-cards .ranked-card:nth-child(n+7){display:none} + .ranking-carousel:has(>#twist-cards)>.ranking-carousel-arrow{display:none!important} + body dialog#startup-age:not(#startup-ad):not(#startup-grid){background:transparent!important;border:0!important;border-radius:0!important;box-shadow:none!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important} + body #startup-age .startup-age-layout{background:var(--modal-surface)!important;border:1px solid var(--modal-outline)!important;box-shadow:var(--modal-shadow)!important} + body #startup-age>.startup-close{background:transparent!important;box-shadow:none;border:0} + body>footer.hg-footer{display:none!important} + #mobile-navigation-drawer .side-info-wordmark{width:124px;height:30px;object-fit:contain} + #mobile-navigation-drawer .side-info-brand-dot{flex:0 0 6px;width:6px;height:6px;background:#ff3b4f;transform:translateY(-1px)} +} + + + +html{scrollbar-gutter:stable} +@media(max-width:760px){#global-site-header .header-download-wrap,#global-site-header .header-actions .download-entry{display:none!important}} + + + +body dialog#startup-age:not(#startup-ad):not(#startup-grid){background:linear-gradient(45deg,rgba(17,21,24,.72),rgba(36,43,49,.72))!important;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)} +#startup-age .startup-age-mark{background:rgba(26,26,26,.35)} +@media(max-width:760px){ + body dialog#startup-age:not(#startup-ad):not(#startup-grid){background:transparent!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important} + body #startup-age .startup-age-layout{background:linear-gradient(45deg,rgba(17,21,24,.72),rgba(36,43,49,.72))!important;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px)} + #startup-age .startup-age-mark{background:transparent} +} + + + +@media(min-width:761px){ + body dialog#startup-age:not(#startup-ad):not(#startup-grid){background:transparent!important;backdrop-filter:none!important;-webkit-backdrop-filter:none!important} + #startup-age .startup-age-layout{background:transparent;overflow:hidden;border-radius:20px} + #startup-age .startup-age-mark{background:rgba(26,30,34,.16);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px)} + #startup-age .startup-age-copy{background:linear-gradient(45deg,rgba(17,21,24,.72),rgba(36,43,49,.72))} +} + + + +.home-content .friend-links>.section-head{margin-bottom:18px} +.friend-links-grid{display:flex;flex-wrap:wrap;align-items:center;gap:18px 12px} +.friend-link{display:flex;align-items:center;justify-content:center;min-width:0;padding:9px 16px;flex:0 0 auto;border-radius:6px;background:rgba(255,255,255,.05);color:var(--muted);font-size:12px;line-height:22px;font-weight:400;overflow-wrap:anywhere;text-align:center;border:1px solid transparent;transition:background .18s ease,color .18s ease,border-color .18s ease} +:is([data-theme="light"],.light-mode,.theme-light) .friend-link{background:rgba(0,0,0,.05);color:rgba(0,0,0,.55)} + +@media(max-width:760px){.friend-links-grid{gap:12px}.friend-link{font-size:12px}} + +@media(hover:hover){.friend-links .friend-link:hover{background:#FFC00014;border-color:#FFC00045;color:var(--accent)}} +@media(prefers-reduced-motion:reduce){.friend-link{transition:none}} + + + +.hg-footer-original .hg-footer-original-title>img{display:block;width:180px;height:auto;aspect-ratio:500/176;object-fit:contain;flex-shrink:0} +@media(max-width:760px){.hg-footer-original .hg-footer-original-title>img{width:150px}} + + + +html body .primary, +html body .episodes button.active, +html body .cinema-play, +html body .page-tool:hover, +html body .page-tool[aria-expanded="true"], +html body .bottom-app-download, +html body .app-download-link button, +html body .channel-pagination button[aria-current="page"], +html body #channel-page[data-channel="rank"] .channel-tabs button[aria-pressed="true"], +html body .channel-pagination .pagination-pages button[aria-current="page"], +html body .channel-pagination .pagination-jump button, +html body .channel-pagination .pagination-jump button:hover:not(:disabled), +html body .channel-pagination .pagination-jump button:focus-visible, +html body #channel-page[data-channel="apps"] .channel-tabs button[aria-pressed="true"], +html body .return-actions a, +html body .feedback-submit-button, +html body #detail .detail-play, +html body #detail .series-episodes .episodes button.active, +html body .member-library-tools button[data-library-action="delete"], +html body .member-delete-actions button[value="confirm"], +html body .member-nickname-editor button, +html body .auth-submit, +html body .auth-bind-mode .auth-submit, +html body .member-avatar-controls [data-avatar-action="save"], +html body .wallet-submit, +html body .play-screen-overlay button, +html body .play-detail-actions .play-download, +html body .play-episode-grid a[aria-current="true"], +html body .play-comment-footer button, +html body #drama-play-page .series-actions .detail-play, +html body #channel-page[data-channel="rank"] .channel-tabs button[aria-pressed="true"]:hover, +html body .member-history-pagination button[aria-current=page], +html body .member-history-pagination .history-page-confirm, +html body .member-history-pagination .history-page-confirm:hover, +html body #play-episode-lock .unlock-pay, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"], +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"], +html body #drama-play-page .play-sort button[aria-pressed="true"], +html body .search-results-form button, +html body .member-history-pagination>button[aria-current="page"]:hover, +html body .startup-age-confirm, +html body .hg-desktop-nav a[aria-current=page], +html body .hg-footer-social a:hover, +html body .ranking-carousel-arrow:hover, +html body .ranking-carousel-arrow:focus-visible, +html body .primary:hover, +html body .bottom-app-download:hover, +html body .feedback-submit-button:hover, +html body .auth-submit:hover, +html body #startup-age .startup-age-confirm:hover, +html body .primary:active, +html body .bottom-app-download:active, +html body .feedback-submit-button:active, +html body .auth-submit:active, +html body #startup-age .startup-age-confirm:active, +html body .hg-footer-original .hg-footer-social a:hover, +html body .hg-footer-original .hg-footer-social a:focus-visible, +html body .hg-desktop-nav a[aria-current=page]:hover, +html body #member-auth-dialog .auth-submit, +html body #feedback-form .feedback-option input:checked+span{color:#191919!important} + + + +:root{--like-color:#FFC000} +html body .drama-heat,html body .drama-data-card .drama-heat,html body .drama-likes{color:var(--like-color)} +html body .drama-heart-icon{fill:currentColor;stroke:none} + + + +html body .page-tools{align-items:flex-end} +html body #page-tool-download #floating-download{display:block;width:88px;height:88px;min-width:88px;padding:0;border:0;border-radius:20px;background:none;overflow:hidden;box-shadow:0 6px 22px #0005;line-height:0;transition:transform .18s,box-shadow .18s} +html body #floating-download .floating-app-icon{display:block;width:100%;height:100%;object-fit:contain} +html body #page-tool-download #floating-download:hover{background:none;transform:translateY(-2px);box-shadow:0 8px 26px #0007} +html body #floating-app-close{position:absolute;z-index:2;right:-7px;top:-9px;display:grid;place-items:center;width:24px;height:24px;padding:4px;border:1px solid #ffffff55;border-radius:50%;background:#252529;color:#eee;cursor:pointer;box-shadow:0 2px 6px #0005} +html body #floating-app-close svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:2;stroke-linecap:round} +html body #floating-app-close:hover{color:#191919;background:#FFC000;border-color:#FFC000} +@media(max-width:760px){html body #page-tool-download #floating-download{width:64px;height:64px;min-width:64px;border-radius:15px}html body #floating-app-close{width:22px;height:22px;right:-5px;top:-8px}} + + + +@keyframes app91-bounce{ + 0%,42%,100%{transform:translateY(0) scale(1)} + 10%{transform:translateY(-10px) scale(1.025)} + 20%{transform:translateY(0) scale(1.035,.965)} + 29%{transform:translateY(-5px) scale(1.015)} + 38%{transform:translateY(0) scale(1)} +} +html body #page-tool-download #floating-download{animation:app91-bounce 2.6s ease-in-out infinite;transform-origin:center bottom} +html body #page-tool-download:hover #floating-download,html body #page-tool-download:focus-within #floating-download,html body #page-tool-download.is-open #floating-download,html body #page-tool-download #floating-download[aria-expanded="true"]{animation:none;transform:none} +@media(prefers-reduced-motion:reduce){html body #page-tool-download #floating-download{animation:none}} + + + +.home-content .popular-searches{margin:0;padding:0;border:0} +.home-content .popular-searches .section-head{margin-bottom:20px} +.popular-search-tags{display:flex;flex-wrap:wrap;gap:14px 16px;align-items:center} +.popular-search-tag{display:inline-flex;align-items:center;justify-content:center;min-height:42px;padding:9px 22px;border:1px solid var(--tag-border);border-radius:999px;background:var(--tag-bg);color:var(--tag-text);font-size:15px;font-weight:600;line-height:22px;white-space:nowrap;transition:background .18s,border-color .18s,transform .18s} +.popular-search-tag--0{--tag-bg:#df83aa20;--tag-border:#df83aa45;--tag-text:#eaa3c1} +.popular-search-tag--1{--tag-bg:#d8b75520;--tag-border:#d8b75545;--tag-text:#e8c976} +.popular-search-tag--2{--tag-bg:#68b5df20;--tag-border:#68b5df45;--tag-text:#91cbea} +.popular-search-tag--3{--tag-bg:#86be9520;--tag-border:#86be9545;--tag-text:#a2d6af} +.popular-search-tag--4{--tag-bg:#b09bd520;--tag-border:#b09bd545;--tag-text:#c5b3e5} +.popular-search-tag:hover{color:var(--tag-text);background:var(--tag-border);border-color:var(--tag-text);transform:translateY(-2px)} +.popular-search-tag:focus-visible{outline:2px solid #FFC000;outline-offset:4px} +@media(max-width:760px){.home-content .popular-searches{margin:0;padding:0;border:0}.home-content .popular-searches .section-head{margin-bottom:16px}.popular-search-tags{gap:10px}.popular-search-tag{min-height:36px;padding:6px 15px;font-size:13px;line-height:22px}} +@media(prefers-reduced-motion:reduce){.popular-search-tag{transition:none}.popular-search-tag:hover{transform:none}} + + + +.home-content #library #cards{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));grid-template-rows:auto;grid-auto-flow:row;grid-auto-columns:auto;gap:18px;overflow:visible;scroll-snap-type:none;padding:0;scrollbar-width:none} +.home-content #library #cards>.card:nth-child(n){display:block;min-width:0} +.home-content #library #cards>.card:nth-child(n+7){display:none} +@media(max-width:760px){.home-content #library #cards{grid-template-columns:repeat(2,minmax(0,1fr));gap:22px 12px}} + + + +.home-content .romance-editorial>.section-head{align-items:center;margin-bottom:24px} +.home-content .romance-editorial .section-heading-copy{display:flex;align-items:center;gap:24px;min-width:0} +.home-content .romance-editorial h2{flex-shrink:0;margin:0;font-size:24px;font-weight:600;line-height:32px;white-space:nowrap} +.must-watch-heading-divider{display:block;flex:0 0 2px;width:2px;height:12px;background:#ffffff1a} +.home-content .romance-editorial .must-watch-filters{display:flex;align-items:center;gap:24px;margin:0} +.home-content .must-watch-filters button{position:relative;isolation:isolate;display:block;flex:0 0 32px;width:32px;min-width:32px;height:24px;min-height:24px;margin:0;padding:0;border:0;border-radius:0;background:none;color:#ffffff8c;font-size:16px;font-weight:400;line-height:22px;white-space:nowrap} +.must-watch-filter-label{position:relative;z-index:1;line-height:22px} +.must-watch-filter-mark{display:none;position:absolute;left:0;top:12px;width:32px;height:12px;z-index:0;pointer-events:none} +.must-watch-filter-mark img{display:block} +.home-content .must-watch-filters button[aria-pressed="true"]{background:none;color:#eee;font-weight:400} +.home-content .must-watch-filters button[aria-pressed="true"] .must-watch-filter-mark{display:block} +.home-content .must-watch-filters button:hover{background:none;color:#fff} +.home-content .must-watch-filters button:focus-visible{outline:2px solid #FFC000;outline-offset:5px} +.home-content .romance-editorial .topic-more{display:inline-flex;align-items:center;gap:0;min-height:28px;padding:4px 4px 4px 10px;border:1px solid #ffffff0d;border-radius:999px;background:#ffffff0d;color:#ffffff8c;font-size:12px;line-height:18px;white-space:nowrap} +.must-watch-more-arrow{display:block;flex:0 0 16px;width:16px;height:16px} +.must-watch-more-arrow img{display:block} +@media(max-width:760px){.home-content .romance-editorial>.section-head{align-items:flex-start;margin-bottom:16px}.home-content .romance-editorial .section-heading-copy{flex-wrap:wrap;gap:12px}.home-content .romance-editorial h2{font-size:20px;line-height:28px}.home-content .romance-editorial .must-watch-filters{gap:20px;flex-basis:100%}.must-watch-heading-divider{display:none}} + +.home-content .must-watch-filters button[aria-pressed="false"]{color:#ffffff8c!important;font-weight:400} +.home-content .must-watch-filters button[aria-pressed="false"] .must-watch-filter-mark{display:none!important} +.home-content .must-watch-filters button[aria-pressed="true"]{color:#f3f0e9!important;font-weight:600} +.home-content .must-watch-filters button[aria-pressed="true"] .must-watch-filter-mark{display:block!important} + + + +.home-content .romance-editorial .section-heading-copy{display:flex;flex-direction:column;align-items:flex-start;gap:10px} +.must-watch-heading-row{display:flex;align-items:center;gap:24px;min-width:0} +.home-content .romance-editorial .section-heading-copy .must-watch-description{display:block;margin:0;color:#92958f;font-size:14px;font-weight:400;line-height:22px} +@media(max-width:760px){.must-watch-heading-row{flex-wrap:wrap;gap:12px}.home-content .romance-editorial .section-heading-copy{gap:8px}.home-content .romance-editorial .section-heading-copy .must-watch-description{font-size:12px;line-height:20px}} + + + +html body .rank-home-badge,html body #twist-cards .rank-home-badge,html body .channel-ranking-cards .rank-home-badge,html body .rank-poster-number,html body .channel-rank-number,html body .channel-rank>.number,html body .rank-caption>strong,html body .mobile-cover-rank{font-family:Georgia,"Times New Roman",serif!important;font-weight:700;font-variant-numeric:lining-nums tabular-nums;letter-spacing:-2px} + + + +html body #twist-cards .rank-home-badge[data-rank="1"],html body .channel-ranking-cards .rank-home-badge[data-rank="1"]{color:#FFC000} +html body #twist-cards .rank-home-badge[data-rank="2"],html body .channel-ranking-cards .rank-home-badge[data-rank="2"]{color:#B8D8F0} +html body #twist-cards .rank-home-badge[data-rank="3"],html body .channel-ranking-cards .rank-home-badge[data-rank="3"]{color:#E6A47B} +html body #twist-cards .rank-home-badge[data-rank="4"],html body .channel-ranking-cards .rank-home-badge[data-rank="4"]{color:#BBA2F2} +html body #twist-cards .rank-home-badge[data-rank="5"],html body .channel-ranking-cards .rank-home-badge[data-rank="5"]{color:#76CDB7} +html body #twist-cards .rank-home-badge[data-rank="6"],html body .channel-ranking-cards .rank-home-badge[data-rank="6"]{color:#EF91B4} + + + +#mobile-navigation-drawer .mobile-drawer-info .mobile-drawer-brand-text{display:inline-block;color:#f3f0e9;font-family:var(--font-ui);font-size:22px;font-weight:600;line-height:30px;letter-spacing:0} + + + +@media(max-width:760px),(hover:none){ +html body #channel-page[data-channel="shorts"] #channel-content>.channel-tabs button:not([aria-pressed="true"]),html body #channel-page .adult-tabs a:not([aria-current="page"]){color:#919198!important} +html body #channel-page[data-channel="shorts"] #channel-content>.channel-tabs button[aria-pressed="true"],html body #channel-page .adult-tabs a[aria-current="page"]{color:var(--accent)!important} +} + + + +@media(min-width:1551px){html body #global-site-header .hg-header-brand{margin-right:24px}} +@media(min-width:1151px) and (max-width:1550px){html body #global-site-header .hg-header-brand{margin-right:16px}} +html body #hero .hero-content .actions>.primary,html body .bottom-app-prompt>.bottom-app-download{position:relative;isolation:isolate;overflow:hidden} +html body #hero .hero-content .actions>.primary::after,html body .bottom-app-prompt>.bottom-app-download::after{content:"";position:absolute;top:-50%;bottom:-50%;left:-60%;width:38%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.45),transparent);transform:skewX(-22deg);pointer-events:none;animation:primary91-shine 3.8s ease-in-out infinite} +@keyframes primary91-shine{0%{left:-60%;opacity:0}5%{opacity:1}28%{left:140%;opacity:0}100%{left:140%;opacity:0}} +@media(prefers-reduced-motion:reduce){html body #hero .hero-content .actions>.primary::after,html body .bottom-app-prompt>.bottom-app-download::after{animation:none;content:none}} + + + +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"], +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]>span, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"] .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:hover, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:hover>span, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:hover .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:focus-visible, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:focus-visible>span, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:focus-visible .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:active, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:active>span, +html body #drama-play-page .play-detail-actions button[data-engage="like"][aria-pressed="true"]:active .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"], +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]>span, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"] .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:hover, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:hover>span, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:hover .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:focus-visible, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:focus-visible>span, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:focus-visible .play-action-icon, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:active, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:active>span, +html body #drama-play-page .play-detail-actions button[data-engage="collect"][aria-pressed="true"]:active .play-action-icon{color:#191919!important} + + + +html body #info-content #member-nickname-input:focus,html body #info-content #member-nickname-input:focus-visible{outline:none;outline-offset:0;box-shadow:none;border:1px solid var(--accent)} + +/* Live API content states and shared controls. */ +.api-error{color:#f47b75;font-size:13px;line-height:1.8} +.api-empty-image{display:block;width:100px;height:120px;object-fit:cover;border-radius:16px;opacity:.65;margin-bottom:12px} +.home-content .cards>a.card{display:block;color:inherit} +/* Apply the HTML's module spacing and portrait frames to dynamic home sections. */ +/* Keep API promotion artwork inside the original HTML's fixed square tiles. */ +[data-promotion-row]{container-type:inline-size;--promo-gap:14px;--promo-size:calc((100cqw - 210px)/16);width:100%;min-width:0;overflow-x:auto;overflow-y:hidden;border-radius:0;scrollbar-width:none;scroll-snap-type:none;scroll-behavior:auto;-webkit-overflow-scrolling:touch} +[data-promotion-row]::-webkit-scrollbar{display:none} +[data-promotion-row] .promo-track{display:flex;width:max-content;min-width:100%;animation:none!important;transform:none;will-change:auto} +[data-promotion-row] .promo-group{display:flex;flex:0 0 auto;flex-wrap:nowrap;align-items:flex-start;gap:var(--promo-gap);width:max-content;padding:0} +[data-promotion-row][data-looping] .promo-group{padding-right:var(--promo-gap)} +[data-promotion-row] .promo-tile{flex:0 0 auto;width:var(--promo-size);min-width:0;padding:0} +[data-promotion-row] .promo-tile-image{position:relative;aspect-ratio:1/1;overflow:hidden} +[data-promotion-row] .promo-tile-image>img{position:absolute;inset:0;display:block;width:100%;height:100%;object-fit:contain;object-position:center} +#info-content [data-promotion-row] .promo-tile{color:#c7c7cc;text-decoration:none} +#info-content [data-promotion-row] .promo-tile:hover{color:var(--accent)} +#drama-play-page .play-advertisements{display:block;min-width:0;overflow:visible} +#drama-play-page [data-promotion-row]{--promo-size:calc((100cqw - 182px)/14)} +#drama-play-page .play-advertisements .promo-tile-image>img{width:100%;height:100%;margin:0} +@media(max-width:1180px){[data-promotion-row]{--promo-size:calc((100cqw - 98px)/8)}#drama-play-page [data-promotion-row]{--promo-size:calc((100cqw - 84px)/7)}} +@media(max-width:760px){[data-promotion-row],#drama-play-page [data-promotion-row]{--promo-gap:10px;--promo-size:calc((100cqw - 50px)/5.3)}} +.home-content #home-blocks{display:flex;flex-direction:column;gap:var(--module-gap);min-width:0} +.home-content #home-blocks>.home-block{margin:0;min-width:0} +.home-content .home-block .drama-data-card>.cover{width:100%;aspect-ratio:var(--portrait);background:#191b19} +.home-content .home-block .drama-data-card>.cover>.poster-source{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center} +.home-content .hero-content .primary{display:inline-flex;align-items:center;gap:10px} +.info-layout:has(.info-nav[hidden]){grid-template-columns:minmax(0,1fr)} +#profile-popover>a{display:block;padding:12px 18px;font-size:14px;color:var(--text)} +#profile-popover>a:hover{background:#ffffff09;color:var(--accent)} +#profile-popover .profile-popover-head>img{border-radius:50%;object-fit:cover} +.channel-tabs>a{display:inline-block;padding:10px 18px;border-radius:8px;color:var(--muted);font-size:14px} +.channel-tabs>a[aria-current=page]{color:var(--accent);background:#FFC00014} +.channel-pagination .pagination-pages{flex-wrap:wrap;justify-content:center} +.channel-pagination .pagination-pages>a,.channel-pagination .pagination-pages>span[aria-disabled]{display:grid;place-items:center;flex-shrink:0;width:36px;min-width:36px;height:36px;border:0;border-radius:50%;padding:0;background:#28282b;color:#aaa;font-size:14px} +.channel-pagination .pagination-pages>a:hover:not([aria-current="page"]){background:#343438;color:var(--accent)} +html body .channel-pagination .pagination-pages>a[aria-current="page"]{background:var(--accent);color:#171713} +.channel-pagination .pagination-pages>span[aria-disabled]{opacity:.4;cursor:default} +.channel-pagination .pagination-jump{flex-shrink:0;white-space:nowrap} +.channel-pagination .pagination-jump input{color:var(--text)} +html body .channel-pagination .pagination-jump button{background:var(--accent);color:#191919!important} +@media(max-width:600px){.channel-pagination .pagination-pages{gap:8px}} +#channel-page[data-channel="rank"] .rank-tabs>a{flex-shrink:0;border:1px solid #ffffff0c;border-radius:999px;background:#ffffff05;white-space:nowrap;transition:background .2s,border-color .2s,filter .2s} +html body #channel-page[data-channel="rank"] .rank-tabs>a[aria-current="page"]{background:var(--accent);border-color:var(--accent);color:#171713} +#channel-page[data-channel="rank"] .rank-tabs>a:hover{color:var(--accent);border-color:#FFC00055} +#channel-page[data-channel="rank"] .rank-tabs>a:active{filter:brightness(.94)} +@media(max-width:760px){#channel-page[data-channel="rank"] .rank-tabs>a{padding:9px 14px}} +@media(prefers-reduced-motion:reduce){#channel-page[data-channel="rank"] .rank-tabs>a{transition:none}} +.member-settings input,.member-settings textarea,.feedback-form input,.feedback-form textarea,.feedback-form select{display:block;width:100%;background:#222;color:var(--text);border:1px solid #ffffff20;border-radius:8px;padding:12px;margin-top:8px;font:inherit} +.member-settings textarea,.feedback-form textarea{min-height:120px;resize:vertical} +.feedback-form{display:grid;gap:24px} +.api-transaction{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:18px 0;border-bottom:1px solid var(--line);font-size:14px} +.api-transaction time{color:var(--muted);font-size:12px} +.play-comment{display:flex;gap:14px;padding:20px 0;border-bottom:1px solid var(--line);min-width:0} +.play-comment>div{flex:1;min-width:0}.play-comment>img{width:40px;height:40px;object-fit:cover;border-radius:50%} +.play-comment p{white-space:pre-wrap;overflow-wrap:anywhere;color:#ccc;font-size:14px}.play-comment time{font-size:12px;color:var(--muted)} +.play-comment-actions{display:flex;gap:20px;margin-top:10px;font-size:12px;color:#999} +.play-comment-actions button[aria-pressed=true]{color:var(--accent)} +.wallet-methods label{display:block;padding:14px;border:1px solid var(--line);border-radius:10px} +.wallet-topbar{display:flex;align-items:center;justify-content:space-between;gap:20px} +@media(max-width:760px){.api-transaction{flex-wrap:wrap;gap:8px}.home-content .cards{grid-template-columns:repeat(2,minmax(0,1fr))}.play-comment .play-comment{gap:8px}.play-comment .play-comment>img{width:28px;height:28px}.wallet-topbar{align-items:flex-start;flex-direction:column}} + +/* Detail markup follows the original HTML, with live API content. */ +#drama-play-page .play-detail-layout--single{grid-template-columns:minmax(0,1fr)} +#drama-play-page .series-info{overflow-wrap:anywhere} +#drama-play-page .series-actions>[hidden]{display:none} +#drama-play-page .series-actions:has(>[hidden]){grid-template-columns:minmax(0,1fr)} +#drama-play-page .series-unavailable{color:#999;font-size:14px} +#drama-play-page .play-banner-ad>span{display:none} +#drama-play-page .play-compose-body:has(#comment-error:empty) #comment-error{display:none} +#drama-play-page .play-episode-grid>.ui-empty-state{grid-column:1/-1} +#drama-play-page .play-episode-pages:empty{display:none} + +/* Keep the original section spacing even when the API supplies no banner ads. */ +#drama-play-page .play-banner-ads{margin:26px 0} +#drama-play-page .play-episodes-section+.play-official{margin-top:26px} +#drama-play-page .play-official+.play-comments{margin-top:28px} +@media(max-width:760px){ + #drama-play-page .play-banner-ads{margin:22px 0} + #drama-play-page .play-episodes-section+.play-official{margin-top:22px} +} + +/* Dynamic page markup uses the same frames and controls as the HTML reference. */ +.channel-shell .drama-data-card>.cover,#search-results-page .drama-data-card>.cover,.play-related .drama-data-card>.cover{width:100%;aspect-ratio:var(--portrait);background:#191b19} +.channel-shell .drama-data-card>.cover>.poster-source,#search-results-page .drama-data-card>.cover>.poster-source,.play-related .drama-data-card>.cover>.poster-source{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:center} +.channel-ranking-cards .ranking-poster-frame{position:absolute;inset:0;width:100%;height:100%;border-radius:inherit} +.channel-ranking-cards .ranking-poster-frame>.poster-source{display:block;width:100%;height:100%;object-fit:cover;object-position:center} +.member-library-tabs>a{position:relative;flex-shrink:0;padding:16px 0 20px;color:#929299;font-size:16px;font-weight:500;white-space:nowrap} +.member-library-tabs>a[aria-current="page"]{color:var(--accent)} +.member-library-tabs>a[aria-current="page"]::after{content:'';position:absolute;bottom:0;left:50%;transform:translateX(-50%);width:26px;height:3px;border-radius:3px;background:var(--accent)} +.member-library-tabs>a:hover{color:var(--accent)} +#info-content .member-library-title{display:inline;margin:0;font:inherit;color:inherit} +#info-content .member-library-item .cover>.poster-source{position:absolute;inset:0} +.member-settings input[hidden]{display:none!important} +#info-content .member-settings-card .member-avatar-controls p{margin:8px 0 0;font-size:12px;color:#888;line-height:1.7} +.member-settings-card .member-nickname-editor input{margin:0} +.member-settings-card .member-introduction-label{margin-top:24px} +.member-settings-card .member-security-password{border-top:1px solid #ffffff12;border-bottom:1px solid #ffffff12} +.member-security-password>summary{list-style:none;cursor:pointer}.member-security-password>summary::-webkit-details-marker{display:none} +.member-security-password[open]>summary>span:last-child{transform:rotate(90deg)} +.member-security-password form{padding:0 0 20px}.member-security-password .primary{border-radius:999px;font-size:14px;padding:12px 24px} +#member-auth-dialog:not(.auth-bind-mode) .auth-password-row input{padding-right:52px} +#member-auth-dialog .auth-password-toggle svg{width:21px;height:21px;fill:none;stroke:currentColor;stroke-width:1.6} +#member-auth-dialog .auth-footer button{padding:0;background:none;color:#bbb;font-size:12px} +#member-auth-dialog .auth-footer button:hover{color:var(--accent)} +#feedback-form .feedback-status{display:block;min-height:24px;margin-top:12px;color:var(--accent);font-size:14px;line-height:1.7} +#info-page[data-info="help-center"] #info-content h2:not(:first-child){margin-top:30px} +html body .hg-footer-original .hg-footer-original-title .footer-brand-text{display:block;margin:0;color:#f3f0e9;font-family:var(--font-ui);font-size:24px;font-weight:600;line-height:32px;letter-spacing:0} +@media(min-width:1151px){html body #global-site-header .hg-desktop-nav{gap:24px}} +@media(min-width:761px) and (max-width:1150px){html body #global-site-header .hg-desktop-nav{gap:20px}} + +/* API navigation groups retain the existing navigation typography and colors. */ +.site-navigation-icon{display:block;width:24px;height:24px;flex:0 0 24px;object-fit:contain} +.site-nav-group>summary{display:flex;align-items:center;gap:12px;cursor:pointer;list-style:none;color:inherit} +.site-nav-group>summary::-webkit-details-marker{display:none} +.site-nav-caret{margin-left:auto;font-size:12px} +.site-nav-group[open]>.site-nav-caret,.site-nav-group[open]>summary>.site-nav-caret{transform:rotate(180deg)} +.side>nav>.site-nav-group>summary{padding:14px 16px;color:#96969e;border-radius:10px} +.side>nav>.site-nav-group:has([aria-current=page])>summary{color:var(--accent)} +.site-nav-children>a{display:flex;align-items:center;gap:12px;padding:12px 16px;color:#96969e;text-decoration:none;border-radius:10px} +.site-nav-children>a:hover{background:#ffffff08} +.site-nav-children>a[aria-current=page]{color:var(--accent);background:#FFC00010} +.side .site-nav-children{padding-left:14px} +.hg-desktop-nav>.site-nav-group{position:relative} +.hg-desktop-nav>.site-nav-group>summary{padding:6px 12px;min-height:34px;white-space:nowrap;font-size:16px} +.hg-desktop-nav .site-nav-children{position:absolute;z-index:90;top:100%;left:0;min-width:150px;padding:8px;border:1px solid #ffffff15;border-radius:12px;background:#222;box-shadow:0 12px 32px #0006} +.hg-desktop-nav .site-nav-children>a{justify-content:flex-start;height:auto} +.site-navigation-status{display:block;padding:12px;color:#929298;font-size:13px;white-space:nowrap} +#mobile-navigation-drawer .site-nav-group>summary{padding:12px 14px;color:#a8a8ae} +#mobile-navigation-drawer .site-nav-children{padding-left:12px} +@media(max-width:760px){.mobile-pages{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(64px,1fr);overflow-x:auto}.mobile-pages>a{flex-shrink:0}} +@media(max-width:760px){.member-library-tabs{gap:26px}.member-library-tabs>a{font-size:14px;padding:14px 0 18px}} + +/* Search uses the HTML empty state and section/card spacing with real API results. */ +#search-results-page .search-empty>img{width:210px;height:210px;object-fit:contain;margin:0 0 8px;opacity:1} +#search-results-page .search-recommendations{margin-top:48px;min-width:0} +#search-results-page .search-recommendations>.section-head{margin:0 0 24px} +#search-results-page .search-recommendations .topic-more{display:inline-flex;align-items:center;gap:12px;font-size:12px;color:var(--text);white-space:nowrap} +#search-results-page .search-recommendations .topic-more:hover{color:var(--accent)} +@media(max-width:760px){#search-results-page .search-empty>img{width:176px;height:176px}#search-results-page .search-recommendations{margin-top:32px}#search-results-page .search-recommendations>.section-head{margin-bottom:20px}} + +/* Stable homepage heading remains available to assistive technology without changing layout. */ +.home-page-title,.entry-page-title{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);clip-path:inset(50%);white-space:nowrap;border:0} + +/* Search dropdown links retain the compact controls and cards from the HTML reference. */ +#global-site-header .search-history-tags>a{display:inline-flex;max-width:100%;padding:7px 12px;border-radius:8px;background:#ffffff08;color:#aaa;font-size:12px;overflow-wrap:anywhere} +#global-site-header .search-history-tags>a:hover{background:#ffffff0a;color:var(--accent)} +#global-site-header .search-suggestion-list>.search-suggestion{display:flex;align-items:center;gap:10px;width:100%;min-width:0;min-height:34px;padding:4px 0;border-radius:8px;text-align:left} +#global-site-header .search-suggestion:hover{background:#ffffff0a} +#global-site-header .search-suggestion:nth-child(1) .suggestion-number{background:linear-gradient(135deg,#ffd095,#ed9b3b);color:#fff} +#global-site-header .search-suggestion:nth-child(2) .suggestion-number{background:linear-gradient(135deg,#d3eaf7,#8aaebd);color:#fff} +#global-site-header .search-suggestion:nth-child(3) .suggestion-number{background:linear-gradient(135deg,#ffd0b5,#df9277);color:#fff} +#global-site-header .search-pick-cover{aspect-ratio:2/3} +#global-site-header .search-pick-cover>.poster-source{display:block;width:100%;height:100%;object-fit:cover} +@media(min-width:761px){#global-site-header .search-discovery{width:458px}} + +/* Give detail actions immediate request feedback without replacing the page. */ +#drama-play-page button[data-request-pending]{cursor:wait;opacity:.75} +#drama-play-page button[data-request-pending]::before{content:'';display:inline-block;flex:0 0 14px;width:14px;height:14px;box-sizing:border-box;border:2px solid currentColor;border-right-color:transparent;border-radius:50%;animation:request-pending-spin .7s linear infinite;vertical-align:middle} +#drama-play-page button[data-request-pending]>.play-action-icon{display:none} +#drama-play-page .play-comment-meta button[data-request-pending],#drama-play-page .play-comment-footer button[data-request-pending]{display:inline-flex;align-items:center;gap:7px} +@keyframes request-pending-spin{to{transform:rotate(360deg)}} +@media(prefers-reduced-motion:reduce){#drama-play-page button[data-request-pending]::before{animation:none}} diff --git a/src/app/h5/m3u8/[...path]/route.ts b/src/app/h5/m3u8/[...path]/route.ts new file mode 100644 index 0000000..826382b --- /dev/null +++ b/src/app/h5/m3u8/[...path]/route.ts @@ -0,0 +1,10 @@ +import { proxyMedia } from '@/lib/api/media'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; + +export async function GET(request: Request, context: {params: Promise<{path: string[]}>}) { + const {path} = await context.params; + if (path.some(part => !part || part === '..' || part === '.' || /[\\/?#\u0000-\u001f]/.test(part))) return new Response(null,{status:400}); + return proxyMedia(request,'/h5/m3u8/'+path.map(encodeURIComponent).join('/')); +} diff --git a/src/app/healthz/route.ts b/src/app/healthz/route.ts new file mode 100644 index 0000000..7b220a5 --- /dev/null +++ b/src/app/healthz/route.ts @@ -0,0 +1,6 @@ +export const dynamic='force-dynamic'; + +export function GET() { + return Response.json({status:'ok',environment:process.env.APP_ENV||'production',application:'91-drama-next'}, + {headers:{'Cache-Control':'no-store','X-Robots-Tag':'noindex, nofollow'}}); +} diff --git a/src/app/icon.svg b/src/app/icon.svg new file mode 100644 index 0000000..a6db8b2 --- /dev/null +++ b/src/app/icon.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..07e8461 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,23 @@ +import type { Metadata, Viewport } from "next"; +import "./globals.css"; + +export const metadata: Metadata = { + title: "91短剧 · 百万大神原创短剧", + applicationName: "91短剧", + icons: {icon:[{url:'/favicon.png',sizes:'48x48',type:'image/png'}],apple:[{url:'/apple-touch-icon.png',sizes:'180x180',type:'image/png'}]}, +}; + +export const viewport: Viewport = { + width: "device-width", + initialScale: 1, + viewportFit: "cover", + themeColor: "#131414", +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + {children} + + ); +} diff --git a/src/app/navigation/page.tsx b/src/app/navigation/page.tsx new file mode 100644 index 0000000..d1486b3 --- /dev/null +++ b/src/app/navigation/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from 'next'; +import { NavigationPage } from '@/components/site/NavigationPage'; +import { getPageSeo, getSeoConfig, metadataFromHead } from '@/lib/seo-server'; +import { downloadUrl } from '@/lib/app-download'; + +export const dynamic = 'force-dynamic'; +export async function generateMetadata():Promise{return metadataFromHead((await getPageSeo('/navigation/')).head);} + +export default async function Page() { + const {config,error}=await getSeoConfig(); + if(error)throw new Error('入口配置暂不可用,请稍后重试'); + return ; +} diff --git a/src/app/newenkey/route.ts b/src/app/newenkey/route.ts new file mode 100644 index 0000000..10f1fed --- /dev/null +++ b/src/app/newenkey/route.ts @@ -0,0 +1,5 @@ +import { proxyMedia } from '@/lib/api/media'; + +export const runtime = 'nodejs'; +export const dynamic = 'force-dynamic'; +export function GET(request: Request) {return proxyMedia(request,'/newenkey',true);} diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx new file mode 100644 index 0000000..fd6cd4f --- /dev/null +++ b/src/app/not-found.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from 'next'; + +export const metadata:Metadata={title:'页面不存在 - 91短剧',robots:{index:false,follow:true},alternates:{canonical:null}}; + +export default function NotFound() { + return ( +
+

404

+

页面不存在或地址有误

+ 返回首页 +
+ ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..6054649 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,22 @@ +import { SiteFrame } from "@/components/site/SiteFrame"; + +import { getHome } from "@/lib/api/server"; +import { getPageSeo, metadataFromHead, queryString } from '@/lib/seo-server'; +import { assertPageContent } from '@/lib/page-response'; + +export const dynamic = "force-dynamic"; + +type PageProps={searchParams:Promise>}; + +export async function generateMetadata({searchParams}:PageProps){ + const result=await getPageSeo('/',queryString(await searchParams)); + assertPageContent('/',result.bootstrap.content); + return metadataFromHead(result.head); +} + +export default async function Page({searchParams}:PageProps) { + const search=queryString(await searchParams); + const [{data:home,error},{bootstrap,session,shared}] = await Promise.all([getHome(),getPageSeo('/',search)]); + assertPageContent('/',bootstrap.content); + return ; +} diff --git a/src/app/robots.ts b/src/app/robots.ts new file mode 100644 index 0000000..2fdd1c9 --- /dev/null +++ b/src/app/robots.ts @@ -0,0 +1,16 @@ +import type { MetadataRoute } from 'next'; +import { getSeoOrigin } from '@/lib/seo-server'; + +export const dynamic = 'force-dynamic'; + +/** Keep HTML crawlable so crawlers can read its canonical and noindex directives. */ +export default async function robots(): Promise { + const environment=process.env.APP_ENV||(process.env.NODE_ENV==='development'?'development':'production'); + const origin=environment==='production'?await getSeoOrigin():''; + return { + rules:environment==='production' + ?{userAgent:'*',allow:'/',disallow:['/api/','/healthz']} + :{userAgent:'*',disallow:'/'}, + ...(origin?{sitemap:new URL('/sitemap.xml',origin).href}:{}), + }; +} diff --git a/src/components/site/AppDownloadDialog.tsx b/src/components/site/AppDownloadDialog.tsx new file mode 100644 index 0000000..e16e773 --- /dev/null +++ b/src/components/site/AppDownloadDialog.tsx @@ -0,0 +1,48 @@ +import type { Ref } from "react"; + +interface AppDownloadDialogProps { + download?: string; + qr?: string; + status?: string; + dialogRef?: Ref; + onClose?: () => void; + onCopy?: () => void; +} + +export function AppDownloadDialog({download, qr, status, dialogRef, onClose, onCopy}: AppDownloadDialogProps) { + return ( + + +

+{"下载91短剧 App"} +

+

+{"扫码下载,随时继续追剧"} +

+
+{"91短剧 +
+

+{download === undefined ? "" : download ? "扫码下载安装 91短剧 App" : "暂未配置 App 下载地址"} +

+
+ + +
+

+{status} +

+ +
+ ); +} diff --git a/src/components/site/ChannelPages.tsx b/src/components/site/ChannelPages.tsx new file mode 100644 index 0000000..f127960 --- /dev/null +++ b/src/components/site/ChannelPages.tsx @@ -0,0 +1,76 @@ +import type { InfoPageDefinition } from '@/lib/info-pages'; +import type { PageView } from '@/lib/page-data'; +import { parseSitePath } from '@/lib/routing'; + +export function ChannelPages({info,view}: {info?:InfoPageDefinition;view?:PageView}={}) { + const channel=view?.panel==='channel'?view:undefined; + const route=channel?.bootstrap?parseSitePath(channel.bootstrap.path.split('?')[0]):null; + const topicDetail=route?.group==='page'&&(route.key==='theme'||route.key==='topics'&&!!route.value); + info ||= view?.panel==='info'?{key:view.bootstrap?.info?.key||'',title:view.title,html:view.html}:undefined; + const ChannelTitle=channel?'h1':'div'; + const InfoTitle=info?'h1':'div'; + return ( + <> +
+ +
+ + + + +{channel?.title} + +

+{channel?.subtitle} +

+
+
+
+
+ +
+ +{"INFORMATION & SUPPORT"} + + +{info?.title} + +

+ +

+
+ +
+
+ + ); +} diff --git a/src/components/site/Footer.tsx b/src/components/site/Footer.tsx new file mode 100644 index 0000000..67dec81 --- /dev/null +++ b/src/components/site/Footer.tsx @@ -0,0 +1,61 @@ +export function Footer({content}:{content?:string}={}) { + return ( + <> + + + ); +} diff --git a/src/components/site/HomePage.tsx b/src/components/site/HomePage.tsx new file mode 100644 index 0000000..295d2b7 --- /dev/null +++ b/src/components/site/HomePage.tsx @@ -0,0 +1,127 @@ +import type { HomeData, User } from "@/lib/api/types"; +import { renderHome } from "@/lib/client/render"; + +import type { NavigationEntry } from '@/lib/site-navigation'; +import { SiteNavigation } from './SiteNavigation'; + +export function HomePage({home,error,hideContent=false,navigation=[],address='/',navigationError=false,user=null}: {user?:User|null;home: HomeData; error?: string;hideContent?:boolean;navigation?:NavigationEntry[];address?:string;navigationError?:boolean}) { + return <> +
+
+91短剧 + +
+ + + +
+
+ + + +
+ +
+
+
+{!hideContent&&

91短剧 · 百万大神原创短剧

} +
+; +} diff --git a/src/components/site/NavigationPage.tsx b/src/components/site/NavigationPage.tsx new file mode 100644 index 0000000..acbe2aa --- /dev/null +++ b/src/components/site/NavigationPage.tsx @@ -0,0 +1,169 @@ +"use client"; + +import { useEffect, useRef, useState, type MouseEvent } from "react"; +import { startTracker } from "@/lib/client/analytics"; +import type { Config } from "@/lib/api/types"; +import { navigationPageData } from "@/lib/navigation-page"; +import { AppDownloadDialog } from "./AppDownloadDialog"; +import { createDownloadQr, shouldUseDownloadDialog } from "@/lib/client/download"; + +export function NavigationPage({config, download, error}: {config: Config; download: string; error?: string}) { + useEffect(()=>{startTracker(config);},[config]); + const dialog = useRef(null); + const [status, setStatus] = useState(""); + const [qr, setQr] = useState(); + const {siteName,description,routes,permanent,telegram,tgGroup,twitter}=navigationPageData(config); + const channelCount=2+Number(!!tgGroup)+Number(!!twitter)+Number(!!download); + const openDownload = async (event: MouseEvent) => { + if (!shouldUseDownloadDialog(event)) return; + event.preventDefault();setStatus("");dialog.current?.showModal(); + try {setQr(await createDownloadQr(download));} catch {setStatus("二维码生成失败,请复制下载链接");} + }; + const copyDownload = async () => { + if (!download) {setStatus("暂未配置 App 下载地址"); return;} + try {await navigator.clipboard.writeText(download); setStatus("链接已复制");} + catch {setStatus("复制失败,请手动复制下载链接");} + }; + return ( + <> +
+{"91短剧官方入口背景"} +
+
+
+

{siteName+"官方入口"}

+{siteName} +

+{description} +

+ +{"百万大神原创短剧"} + +
+ +{download && {void openDownload(event);}}> +{siteName+" + +{siteName+" · APP 下载"} + +} +{telegram&& +{"官方联系:" + telegram} +} + +
+{error &&

{error}

} +

+{"本站仅提供视频内容与产品服务"} +
+{"请遵守当地法律法规,仅限成年人访问"} +

+

+{"© 2026 "+siteName+" · 官方发布页"} +

+
+
+
+
+ {download && dialog.current?.close()} onCopy={() => {void copyDownload();}} />} + + ); +} diff --git a/src/components/site/PageTools.tsx b/src/components/site/PageTools.tsx new file mode 100644 index 0000000..f9a6847 --- /dev/null +++ b/src/components/site/PageTools.tsx @@ -0,0 +1,37 @@ +export function PageTools() { + return ( + <> + + + ); +} diff --git a/src/components/site/PlayerPage.tsx b/src/components/site/PlayerPage.tsx new file mode 100644 index 0000000..c054f06 --- /dev/null +++ b/src/components/site/PlayerPage.tsx @@ -0,0 +1,9 @@ +import type { PageView } from '@/lib/page-data'; + +export function PlayerPage({view}:{view?:PageView}) { + return ( + <> +
+ + ); +} diff --git a/src/components/site/Sidebar.tsx b/src/components/site/Sidebar.tsx new file mode 100644 index 0000000..1a92c37 --- /dev/null +++ b/src/components/site/Sidebar.tsx @@ -0,0 +1,98 @@ +import type { NavigationEntry } from '@/lib/site-navigation'; +import { SiteNavigation } from './SiteNavigation'; + +export function Sidebar({navigation,address,error}:{navigation:NavigationEntry[];address:string;error?:boolean}) { + return ( + <> + + + + ); +} diff --git a/src/components/site/SiteDialogs.tsx b/src/components/site/SiteDialogs.tsx new file mode 100644 index 0000000..65ae5a1 --- /dev/null +++ b/src/components/site/SiteDialogs.tsx @@ -0,0 +1,172 @@ +import { AppDownloadDialog } from "./AppDownloadDialog"; + +export function SiteDialogs() { + return ( + <> + + + +
+ +
+ + + + + +

+{"历史记录"} +

+

+{"最近浏览的剧集"} +

+
+ +
+ +
+ + +
+

+{"确认删除"} +

+

+ +

+
+ + +
+
+
+ + + + + + + + +
+ +{"金币图标"} + +
+

+{"使用金币解锁本集"} +

+

+{"解锁后可永久观看本集内容。"} +

+
+
+ +{"当前余额"} + + +{"— 金币"} + +
+
+ +{"本次消耗"} + + +{"— 金币"} + +
+
+

+{"后续付费剧集仍需逐集确认"} +

+ + + +{"金币不足?去充值"} + +
+ + +
+
+{"18岁以上"} +{"18岁以上"} +
+
+
+

+{"本网站仅限成年人访问"} +

+
+

+{"本网站包含仅限18岁及以上成年人浏览的内容。若您未满18岁,或所在地法律不允许您浏览此类内容,请立即离开。"} +

+

+{"继续进入即表示您确认已年满18岁,并达到所在地的法定成年年龄。"} +

+
+
+
+

+{"这是个成人网站"} +

+

+{"本网站包含有年龄限制的内容,包括裸体和露骨色情素材的内容。 点击继续即表示您确认您已年满 18 岁,或在您访问本网站时所在的司法管辖区已是成年人"} +

+
+
+ + +
+
+
+ +
+ + + + + + +
+ +
+ + ); +} diff --git a/src/components/site/SiteFrame.tsx b/src/components/site/SiteFrame.tsx new file mode 100644 index 0000000..8d5a4cf --- /dev/null +++ b/src/components/site/SiteFrame.tsx @@ -0,0 +1,32 @@ +import type { HomeData } from '@/lib/api/types'; +import type { InfoPageDefinition } from '@/lib/info-pages'; +import { getHeaderNavigation } from '@/lib/api/server'; +import { navigationEntries } from '@/lib/site-navigation'; +import { Sidebar } from './Sidebar'; +import { HomePage } from './HomePage'; +import { ChannelPages } from './ChannelPages'; +import { PlayerPage } from './PlayerPage'; +import { PageTools } from './PageTools'; +import { SiteDialogs } from './SiteDialogs'; +import { Footer } from './Footer'; +import { SiteRuntime } from './SiteRuntime'; +import type { SeoBootstrap } from '@/lib/seo'; +import { StructuredData } from './StructuredData'; +import type { PageBootstrap, PageView, SiteSharedData } from '@/lib/page-data'; + +export async function SiteFrame({home,error,info,seo,view,homeLoaded=true,session,shared}: {home:HomeData;error?:string;info?:InfoPageDefinition;seo?:SeoBootstrap;view?:PageView;homeLoaded?:boolean;session?:PageBootstrap['session'];shared?:SiteSharedData}) { + const header=await getHeaderNavigation(); + const navigation=navigationEntries(header.data,seo?.environment.origin); + const address=seo?.path||'/'; + return <> + + + + + + + +