Initial commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
谢宇宁
2026-11-05 22:26:18 +07:00
co-authored by Claude Opus 5.5
commit 6fb2b1e486
385 changed files with 48049 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
.git
.cursor
.idea
.node_modules
node_modules
.next
.next-dev
.next-test
.next*
dist
dist.zip
dist.zip.sha256
web_sgdj_h5.tar.gz
web_sgdj_h5.md5
config/*.local.env
test-results
playwright-report
*.log
*.tsbuildinfo
.env*
!.env.example
README.md
AGENTS.md
+20
View File
@@ -0,0 +1,20 @@
# 推荐使用 config/{development|test|production}.env(由 pnpm dev/build 加载)
# 本文件仅作本地覆盖参考;也可用 config/*.local.env(勿提交)
# 直连后端(不走 Next 代理)
# NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:26149
# API_PROXY_TARGET=
# 本地代理调试(推荐):BASE 留空 + 设置代理目标
NEXT_PUBLIC_API_BASE_URL=
API_PROXY_TARGET=https://sgdj-h5.80sygtm.com
# HTTPS_PROXY=http://127.0.0.1:7890
# HTTP_PROXY=http://127.0.0.1:7890
NEXT_PUBLIC_TRACKER_APP_ID=JHA-1049
NEXT_PUBLIC_GTAG_ID=
# 站点正式域名(sitemap / OG / canonical 强制用此值,勿填反代内部域)
# NEXT_PUBLIC_SITE_URL=https://shuguodj.com
# NEXT_PUBLIC_TRACKER_ENCRYPTED_CONFIG=
# NEXT_PUBLIC_YANDEX_METRIKA_ID=
+8
View File
@@ -0,0 +1,8 @@
node_modules/
.next/
out/
.env
.env.local
.env*.local
.DS_Store
*.log
+9
View File
@@ -0,0 +1,9 @@
<!-- BEGIN:nextjs-agent-rules -->
## 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.
<!-- END:nextjs-agent-rules -->
+18
View File
@@ -0,0 +1,18 @@
FROM node:22-alpine AS build
WORKDIR /app
RUN corepack enable && corepack prepare pnpm@10.32.1 --activate
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
ARG APP_ENV=production
RUN pnpm exec tsx scripts/next-env.ts "$APP_ENV" build \
&& pnpm exec tsx scripts/package-standalone.ts "$APP_ENV" \
&& mv "dist/$APP_ENV" /release
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production PORT=3000 HOSTNAME=0.0.0.0
COPY --from=build --chown=node:node /release ./
USER node
EXPOSE 3000
CMD ["node", "start.mjs"]
+56
View File
@@ -0,0 +1,56 @@
# jha-1049-sgdj-web
熟果短剧 H5 / Web。工程基线来自 `jha-1046-hjsq-web`(Next.js 16 · React 19 · pnpm)。当前只有空白首页。
## 环境要求
- Node.js `>= 20.9`(打包 Linux sharp 推荐 **22**)
- pnpm `10+`
## 本地开发
```bash
pnpm install
pnpm dev # development.env,http://localhost:26149
pnpm dev:test # test.env
```
| 命令 | 说明 |
|------|------|
| `pnpm lint` | ESLint |
| `pnpm build` | 正式环境构建 |
| `pnpm build:test` | 测试环境构建 |
## 环境配置
配置文件在 `config/`(可另加 `*.local.env`,勿提交密钥):
| 文件 | 用途 |
|------|------|
| `config/development.env` | 本地开发 |
| `config/test.env` | 测试构建 / 测试服 |
| `config/production.env` | 正式构建 / 正式服 |
正式服(项目代号 `wdb_sgdj`,对齐 `jha-1048-hddj-web`):`API_PROXY_TARGET` → app `139.162.49.252:21036`,H5 `PORT=21038`,站点 `https://shuguodj.com`;后台 ht `websgdj.weballht.com`(web 服务端口 `21037` 不在本仓库)。
## 打包
测试包对齐 `jha-1047-91dj-web`:不含 `node_modules`,保留 `.next-test`,并带 SHA-256。服务器执行 `npm ci --omit=dev` 后 `node start.mjs`。正式包仍带 Linux 运行依赖,只用 MD5。
```bash
pnpm pack:gz # 正式一键打包并部署(上传跳板机 + rsupdate.sh)
pnpm pack:gz:only # 仅生成本地 web_sgdj_h5.tar.gz + .md5
pnpm deploy:prod # 同 pack:gz
pnpm pack:gz:test:only # 测试包,不上传
```
| 环境 | 默认目标 | 目录 | 端口 | 远端命令 |
|------|----------|------|------|----------|
| 测试 | `root@172.104.181.99` | `/server/web_sgdj/h5` | `22889` | `bash run.sh` |
| 正式 | `server@34.92.204.175` | `/home/server/web_sgdj/h5` | `22889` | `bash rsupdate.sh` |
测试产物为根目录的 `web_sgdj_h5.tar.gz` 和 `web_sgdj_h5.tar.gz.sha256`,同时刷新本地 `web_sgdj_h5.md5`。实际上传的是压缩包、SHA-256 和 `deploy/up_next_h5.sh`,不会覆盖服务器上的 `run.sh`。`deploy/run.sh` 只作为首次放到 `/server/web_sgdj/h5/` 的副本。依赖变更后要同时更新 `pnpm-lock.yaml` 和 `package-lock.json`,测试服按后者执行 `npm ci`。
可用 `DEPLOY_HOST`、`DEPLOY_DIR`、`DEPLOY_RUN_DIR`、`DEPLOY_SSH_PORT`、`DEPLOY_PACKAGE_NAME` 覆盖测试目标。旧变量 `DEPLOY_TEST_HOST`、`DEPLOY_TEST_DIR` 仍然有效。
Standalone 用包内入口启动:`node start.mjs`。
+16
View File
@@ -0,0 +1,16 @@
APP_ENV=development
# 浏览器走同源 /api/*(配合 next.config rewrites)
NEXT_PUBLIC_API_BASE_URL=
# 调试上游:https://sgdj-h5.80sygtm.com/swagger/index.html
# 注意:不要把媒资也指回本 H5,否则 /h5/m3u8 会 508「接口代理回环」。
API_PROXY_TARGET=https://sgdj-h5.80sygtm.com
# 媒资密钥回退(H5 自指时);播放列表还可回退 MEDIA_CDN_ORIGIN 改写
MEDIA_API_PROXY_TARGET=http://139.162.49.252:21036
MEDIA_CDN_ORIGIN=https://dyum53hre43nj.cloudfront.net
# app 上 conf/m3u8.key 不可用时的密钥回退(共用媒资 AES key)
MEDIA_KEY_FALLBACK_ORIGINS=https://91dj-h5.80sygtm.com
PORT=26149
NEXT_PUBLIC_TRACKER_APP_ID=JHA-1049
NEXT_PUBLIC_GTAG_ID=
NEXT_PUBLIC_SITE_URL=http://localhost:26149
NEXT_PUBLIC_APP_VERSION=1.0.0
+15
View File
@@ -0,0 +1,15 @@
APP_ENV=production
NEXT_PUBLIC_API_BASE_URL=
# 应用接口在 21036。不要写成 H5 的 21038,也不要写 127.0.0.1(H5 进程里不一定能连到本机 Go)。
# web:21037(独立 web 服务);ht 后台:websgdj.weballht.com;项目代号 wdb_sgdj。
API_PROXY_TARGET=http://139.162.49.252:21036
# Go 未配 cdnDomain/origDomain 时,播放列表回退到媒资 CDN 改写
MEDIA_API_PROXY_TARGET=http://139.162.49.252:21036
MEDIA_CDN_ORIGIN=https://dyum53hre43nj.cloudfront.net
# 待 app 恢复 conf/m3u8.key 后可去掉
MEDIA_KEY_FALLBACK_ORIGINS=https://91dj-h5.80sygtm.com
PORT=21038
NEXT_PUBLIC_TRACKER_APP_ID=JHA-1049
NEXT_PUBLIC_GTAG_ID=
NEXT_PUBLIC_SITE_URL=https://shuguodj.com
NEXT_PUBLIC_APP_VERSION=1.0.0
+15
View File
@@ -0,0 +1,15 @@
APP_ENV=test
# 浏览器走同源 /api/*,服务端再转到测试接口。不要写成 127.0.0.1:26149,那是本站端口。
NEXT_PUBLIC_API_BASE_URL=
API_PROXY_TARGET=https://sgdj-h5.80sygtm.com
# 播放 BFF 直连本机 Go;XFF 必须与 Node 访问媒资密钥的出站公网 IP 一致,否则 ticket 无法解 enkey。
API_INTERNAL_ORIGIN=http://127.0.0.1:21025
MEDIA_TICKET_FORWARDED_FOR=172.104.181.99
MEDIA_API_PROXY_TARGET=http://139.162.49.252:21036
MEDIA_CDN_ORIGIN=https://dyum53hre43nj.cloudfront.net
MEDIA_KEY_FALLBACK_ORIGINS=https://91dj-h5.80sygtm.com
PORT=26149
NEXT_PUBLIC_TRACKER_APP_ID=JHA-1049
NEXT_PUBLIC_GTAG_ID=
NEXT_PUBLIC_SITE_URL=https://sgdj-h5.80sygtm.com
NEXT_PUBLIC_APP_VERSION=1.0.0
Executable
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
# 测试服已有入口。deploy_test.sh 上传压缩包后执行:cd /server/web_sgdj/h5 && bash run.sh
# 本文件供首次放到服务器,发布脚本不会覆盖远端 run.sh。
set -Eeuo pipefail
APP_DIR=/server/web_sgdj/h5
APP_NAME=web_sgdj_h5
ARCHIVE="$APP_DIR/web_sgdj_h5.tar.gz"
CURRENT="$APP_DIR/next"
RELEASES="$APP_DIR/releases"
NODE=/root/.nvm/versions/node/v22.22.3/bin/node
PM2=/root/.nvm/versions/node/v22.22.3/bin/pm2
NPM=/usr/bin/npm
export PATH=/root/.nvm/versions/node/v22.22.3/bin:$PATH
STAGING=''
PREVIOUS=''
ACTIVATED=0
WAS_RUNNING=0
fail() { echo "发布失败:$*" >&2; exit 1; }
cleanup() {
result=$?
trap - EXIT INT TERM
if [ "$result" -ne 0 ] && [ "$ACTIVATED" -eq 1 ]; then
echo '发布失败,正在恢复上一版本…' >&2
"$PM2" delete "$APP_NAME" >/dev/null 2>&1 || true
[ -d "$CURRENT" ] && mv "$CURRENT" "$RELEASES/failed.$(date +%Y%m%d%H%M%S)" || true
if [ -n "$PREVIOUS" ] && [ -d "$PREVIOUS" ]; then
mv "$PREVIOUS" "$CURRENT"
[ "$WAS_RUNNING" -eq 1 ] && "$PM2" start "$CURRENT/start.mjs" --name "$APP_NAME" --interpreter "$NODE" >/dev/null 2>&1 || true
fi
fi
[ -n "$STAGING" ] && [ -d "$STAGING" ] && rm -rf "$STAGING"
exit "$result"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
[ -s "$ARCHIVE" ] || fail "未找到运行包:$ARCHIVE"
for tool in tar "$NODE" "$NPM" "$PM2"; do command -v "$tool" >/dev/null 2>&1 || fail "缺少命令:$tool"; done
"$NODE" -e 'const [a,b]=process.versions.node.split(".").map(Number);process.exit(a>20||a===20&&b>=9?0:1)' || fail '需要 Node.js >=20.9'
mkdir -p "$RELEASES"
while IFS= read -r entry; do
case "$entry" in
/*|../*|*/../*|*/..|*\\*) fail "运行包含非法路径:$entry" ;;
esac
done < <(/bin/tar -tzf "$ARCHIVE")
STAGING=$(mktemp -d "$APP_DIR/.release.XXXXXX")
/bin/tar -xzf "$ARCHIVE" -C "$STAGING" --no-same-owner
for file in start.mjs server.js package.json package-lock.json scripts/next-env.mjs scripts/check-health.mjs .next-test/BUILD_ID config/test.env; do
[ -s "$STAGING/$file" ] || fail "运行包缺少:$file"
done
[ -d "$STAGING/.next-test/static" ] && [ -d "$STAGING/public" ] || fail '运行包缺少 Next 静态资源'
(cd "$STAGING" && "$NPM" ci --omit=dev --no-audit --no-fund)
(cd "$STAGING" && "$NODE" -e 'for (const n of ["next","react","react-dom","dotenv"]) require.resolve(n)') >/dev/null 2>&1 || fail '运行依赖校验失败'
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)')
if [ -f "$CURRENT/config/test.local.env" ] && [ ! -L "$CURRENT/config/test.local.env" ]; then
mkdir -p "$STAGING/config"
cp "$CURRENT/config/test.local.env" "$STAGING/config/test.local.env"
chmod 600 "$STAGING/config/test.local.env"
fi
if "$PM2" jlist 2>/dev/null | grep -q "\"name\":\"$APP_NAME\""; then WAS_RUNNING=1; fi
"$PM2" delete "$APP_NAME" >/dev/null 2>&1 || true
if [ -d "$CURRENT" ]; then
PREVIOUS="$RELEASES/next.$(date +%Y%m%d%H%M%S)"
mv "$CURRENT" "$PREVIOUS"
fi
mv "$STAGING" "$CURRENT"; STAGING=''; ACTIVATED=1
"$PM2" start "$CURRENT/start.mjs" --name "$APP_NAME" --interpreter "$NODE"
"$NODE" "$CURRENT/scripts/check-health.mjs" "http://127.0.0.1:$PORT" test
"$PM2" save
timestamp=$(date +%Y%m%d%H%M%S)
mv "$ARCHIVE" "$RELEASES/web_sgdj_h5.$timestamp.tar.gz"
echo "发布完成:$APP_NAME;端口:$PORT;上一版本:${PREVIOUS:-无}"
+90
View File
@@ -0,0 +1,90 @@
#!/bin/bash
# Linux 测试服 tar.gz 更新入口;需预先配置 Supervisor 程序并手动调用。
# 流程对齐 jha-1047-91dj-web:校验 SHA-256、解包、npm ci、切换版本、健康检查。
set -Eeuo pipefail
DIR=$(cd "$(dirname "$0")" && pwd)
PROGRAM="${DEPLOY_PROGRAM:-sgdj_next_h5}"
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_sgdj_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"
UPLOAD=$(mktemp -d "$DIR/.sgdj-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"
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/.sgdj-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 '包内缺少静态资源'
(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)')
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:-无}"
Executable
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
# 熟果短剧正式服(wdb_sgdj):构建 → Linux standalone + MD5 → 可选触发 rsupdate.sh。
# 对齐 jha-1048-hddj-web;H5 监听 21038,包目录 /home/server/web_sgdj/h5。
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_sgdj/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_sgdj_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
need node; need pnpm
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'
[ -d node_modules/next ] && [ -d node_modules/dotenv ] || fail '请先执行 pnpm install'
[ -s config/production.env ] && [ -s pnpm-lock.yaml ] || fail '缺少 config/production.env 或 pnpm-lock.yaml'
if [ "${DEPLOY_DRY_RUN:-0}" = 1 ]; then
printf 'DRY RUN:正式环境;Linux %s/%s;不构建、不连接服务器。\n' "$TARGET_ARCH" "$TARGET_LIBC"; public_profile production
printf ' pnpm run build → pnpm run bundle → pnpm 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 '构建正式环境 standalone'
pnpm run build
step '整理正式环境 standalone 和目标 Linux 原生依赖'
pnpm run bundle
pnpm run bundle:linux -- "$TARGET_ARCH" "$TARGET_LIBC"
for file in start.mjs server.js .next/BUILD_ID config/production.env runtime-target.json ecosystem.config.cjs; 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"
Executable
+94
View File
@@ -0,0 +1,94 @@
#!/bin/bash
# 熟果短剧测试服:构建 → tar.gz + SHA-256 → 可选上传并执行 run.sh。
# 打包方式对齐 jha-1047-91dj-web:测试包不含 node_modules,服务器 npm ci 后再 node start.mjs。
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
cd "$SCRIPT_DIR"
source "$SCRIPT_DIR/scripts/deploy-common.sh"
HOST="${DEPLOY_HOST:-${DEPLOY_TEST_HOST:-root@172.104.181.99}}"
SSH_PORT="${DEPLOY_SSH_PORT:-22889}"
REMOTE_DIR="${DEPLOY_DIR:-${DEPLOY_TEST_DIR:-/server/web_sgdj/h5}}"
# 远端启动脚本目录独立于上传目录,按服务器约定执行。
RUN_DIR="${DEPLOY_RUN_DIR:-$REMOTE_DIR}"
CHECK_URL="${DEPLOY_CHECK_URL:-}"
PACKAGE_NAME="${DEPLOY_PACKAGE_NAME:-web_sgdj_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"
printf '兼容旧变量:DEPLOY_TEST_HOST、DEPLOY_TEST_DIR。\n'
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 ' pnpm run build:test → pnpm test → pnpm 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 '构建测试环境并运行回归测试'
pnpm run build:test
pnpm test
step '整理 standalone 测试包'
pnpm run bundle:test
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 "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');
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%/}"
+28
View File
@@ -0,0 +1,28 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
// Override default ignores of eslint-config-next.
globalIgnores([
// Default ignores of eslint-config-next:
".next/**",
".next-test/**",
".next-dev/**",
"dist/**",
"out/**",
"build/**",
"public/js/**",
"next-env.d.ts",
]),
{
rules: {
// 接口图需 XOR 解密,统一走 DecryptedImage / 原生 <img>,不能用 next/image
"@next/next/no-img-element": "off",
},
},
]);
export default eslintConfig;
+40
View File
@@ -0,0 +1,40 @@
import type { NextConfig } from "next";
/** 构建期兜底代理。请求期以 API_PROXY_TARGET 为准,见 src/lib/api/upstream-proxy.ts。 */
const apiProxyTarget = (process.env.API_PROXY_TARGET || "").replace(/\/$/, "");
const appEnv = process.env.APP_ENV;
const nextConfig: NextConfig = {
output: "standalone",
// 仅在显式 APP_ENV=test|development 时分目录;未设置 / production 仍用 .next
distDir:
appEnv === "test" ? ".next-test" : appEnv === "development" ? ".next-dev" : ".next",
poweredByHeader: false,
cacheComponents: true,
partialPrefetching: true,
// 本机用 127.0.0.1 打开时,允许跨源访问 Next 开发资源
allowedDevOrigins: ["127.0.0.1"],
turbopack: {
rules: {
"*.css": {
loaders: ["@tailwindcss/turbopack"],
as: "*.css",
},
},
},
async rewrites() {
if (!apiProxyTarget) return [];
// fallback:动态 Route Handler(upstream-proxy)优先;避免 afterFiles 抢走
// /api/[...path] 后走 Next httpxy(本机 IPv6 不通时 AggregateError → 500)。
// /h5/m3u8、/enkey、/newenkey 由 route handler(lib/media/proxy)代理,密钥地址不带尾斜杠
const proxy = [
{
source: "/api/:path*",
destination: `${apiProxyTarget}/api/:path*`,
},
];
return { fallback: proxy };
},
};
export default nextConfig;
+8238
View File
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
{
"name": "jha-1049-sgdj-web",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "tsx scripts/next-env.ts development dev",
"dev:test": "tsx scripts/next-env.ts test dev",
"build": "tsx scripts/next-env.ts production build",
"build:dev": "tsx scripts/next-env.ts development build",
"build:test": "tsx scripts/next-env.ts test build",
"start": "tsx scripts/next-env.ts production start",
"start:dev": "tsx scripts/next-env.ts development start",
"start:test": "tsx scripts/next-env.ts test start",
"lint": "eslint",
"test": "node --test tests/*.test.mjs",
"bundle": "tsx scripts/package-standalone.ts production",
"bundle:test": "tsx scripts/package-standalone.ts test",
"bundle:linux": "tsx scripts/prepare-production-runtime.ts",
"package:prod": "bash scripts/package-prod.sh --package-only",
"pack:gz": "bash scripts/package-prod.sh",
"pack:gz:only": "bash scripts/package-prod.sh --package-only",
"pack:gz:test": "bash scripts/package-test.sh",
"pack:gz:test:only": "bash scripts/package-test.sh --package-only",
"deploy:test": "bash deploy_test.sh",
"deploy:prod": "bash deploy_prod.sh"
},
"dependencies": {
"@heroui/react": "^3.2.6",
"@heroui/styles": "^3.2.6",
"@internationalized/date": "^3.12.4",
"@react-aria/ssr": "^3.10.1",
"@react-aria/utils": "^3.34.1",
"dotenv": "^17.4.2",
"next": "16.4.1",
"qrcode.react": "^4.2.0",
"react": "19.3.0",
"react-aria": "^3.53.1",
"react-aria-components": "^1.22.1",
"react-dom": "19.3.0",
"swiper": "^14.3.0",
"web-streams-polyfill": "^4.3.0",
"xgplayer": "^3.0.26",
"xgplayer-hls": "^3.0.26"
},
"devDependencies": {
"@tailwindcss/turbopack": "^4",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.4.1",
"tailwindcss": "^4",
"tsx": "^4.20.0",
"typescript": "^5"
},
"packageManager": "pnpm@10.32.1",
"engines": {
"node": ">=20.9.0"
},
"type": "module",
"browserslist": [
"chrome >= 80",
"android >= 5",
"ios >= 13",
"safari >= 13",
"not dead"
]
}
+5251
View File
File diff suppressed because it is too large Load Diff
@@ -0,0 +1 @@
6d41643810d540ce924edb982c3d3171
Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

+1
View File
@@ -0,0 +1 @@
google-site-verification: google5ce6ca462e54c213.html
+1
View File
@@ -0,0 +1 @@
<svg viewBox="0 0 1024 1024" fill="currentColor"><path d="M407.505455 311.621818a113.570909 113.570909 0 0 0-189.207273 40.261818L52.363636 807.796364a90.763636 90.763636 0 0 0 113.338182 118.225454l456.145455-152.901818a116.363636 116.363636 0 0 0 46.545454-189.672727zM519.912727 325.818182h6.050909a37.701818 37.701818 0 0 0 36.770909-31.650909c23.272727-143.592727 6.516364-215.04 5.818182-218.065455A37.003636 37.003636 0 0 0 523.869091 46.545455a38.167273 38.167273 0 0 0-27.927273 46.545454s13.265455 62.370909-6.749091 188.741818A37.701818 37.701818 0 0 0 519.912727 325.818182zM658.850909 347.694545a37.003636 37.003636 0 0 0 50.967273-13.730909 335.592727 335.592727 0 0 0 31.883636-126.603636 37.701818 37.701818 0 0 0-35.374545-39.563636 37.236364 37.236364 0 0 0-39.098182 35.607272 268.334545 268.334545 0 0 1-21.876364 93.090909 37.701818 37.701818 0 0 0 13.498182 51.2zM721.454545 454.283636a37.701818 37.701818 0 0 0 23.272728 71.912728 85.876364 85.876364 0 0 1 69.818182 11.636363 37.003636 37.003636 0 0 0 52.363636-6.749091 37.934545 37.934545 0 0 0-5.818182-52.829091 159.650909 159.650909 0 0 0-139.636364-23.970909z"/><path d="M837.818182 246.690909a70.749091 69.818182 90 1 0 139.636363 0 70.749091 69.818182 90 1 0-139.636363 0Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 125 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" aria-hidden="true"><path d="M983.04 512c0 153.31328-228.10624 307.2-471.04 307.2-90.3168 0-178.56512-21.25824-253.80864-55.86944l129.49504-129.47456a174.08 174.08 0 1 0 246.1696-246.1696l128.57344-128.57344C891.392 317.44 983.04 414.8224 983.04 512zM512 204.8c65.00352 0 128.96256 11.01824 187.76064 30.1056l-117.69856 117.69856a174.08 174.08 0 0 0-229.4784 229.4784l-151.18336 151.1424C105.02144 674.67264 40.96 593.26464 40.96 512c0-153.31328 228.10624-307.2 471.04-307.2z m112.64 307.2a112.64 112.64 0 0 1-193.49504 78.41792l159.27296-159.27296c21.0944 20.48 34.22208 49.13152 34.22208 80.85504zM512 399.36c7.2704 0 14.37696 0.69632 21.27872 2.00704l-131.91168 131.8912A112.64 112.64 0 0 1 512 399.36z" fill="#A3A3AD"/><path d="M872.796352 148.719008m21.72232 21.72232l0 0q21.72232 21.72232 0 43.444641l-680.632703 680.632703q-21.72232 21.72232-43.444641 0l0 0q-21.72232-21.72232 0-43.444641l680.632703-680.632703q21.72232-21.72232 43.444641 0Z" fill="#A3A3AD"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" aria-hidden="true"><path d="M512 204.8C269.06624 204.8 40.96 358.68672 40.96 512s228.10624 307.2 471.04 307.2 471.04-153.88672 471.04-307.2-228.10624-307.2-471.04-307.2z" fill="#A3A3AD"/><path d="M512 337.92a174.08 174.08 0 1 0 0 348.16 174.08 174.08 0 0 0 0-348.16z m0 61.44a112.64 112.64 0 1 1 0 225.28 112.64 112.64 0 0 1 0-225.28z" fill="#232326"/></svg>

After

Width:  |  Height:  |  Size: 422 B

+14
View File
@@ -0,0 +1,14 @@
<svg preserveAspectRatio="none" overflow="visible" style="display: block;" width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="&#231;&#153;&#187;&#229;&#189;&#149;">
<g clip-path="url(#clip0_0_185)">
<circle id="Ellipse 10" cx="16" cy="16" r="16" fill="#FF4A4A" fill-opacity="0.08"/>
<ellipse id="Ellipse 11" cx="16" cy="20.5" rx="8" ry="3.5" fill="#FF4B02"/>
<path id="Rectangle 17" d="M12 11.5C12 9.01472 14.0147 7 16.5 7H19C19.5523 7 20 7.44772 20 8V12C20 14.2091 18.2091 16 16 16V16C13.7909 16 12 14.2091 12 12V11.5Z" fill="#FF4B02"/>
</g>
</g>
<defs>
<clipPath id="clip0_0_185">
<path d="M0 16C0 7.16344 7.16344 0 16 0V0C24.8366 0 32 7.16344 32 16V16C32 24.8366 24.8366 32 16 32V32C7.16344 32 0 24.8366 0 16V16Z" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Some files were not shown because too many files have changed in this diff Show More