Răsfoiți Sursa

first commit

bob.yuxinyang 1 lună în urmă
comite
76b319365f

+ 35 - 0
.gitignore

@@ -0,0 +1,35 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+# Environment files (keep examples in Git)
+.env
+.env.*
+!.env.example
+!.env.*.example
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Deployment credentials stay local
+*.pem
+*.pem.pub
+deploy.config
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?

+ 8 - 0
.oxlintrc.json

@@ -0,0 +1,8 @@
+{
+  "$schema": "./node_modules/oxlint/configuration_schema.json",
+  "plugins": ["react", "oxc"],
+  "rules": {
+    "react/rules-of-hooks": "error",
+    "react/only-export-components": ["warn", { "allowConstantExport": true }]
+  }
+}

+ 26 - 0
README.md

@@ -0,0 +1,26 @@
+# 纯粹星辰官网
+
+React + Vite 静态网站。
+
+## 本地开发
+
+```bash
+npm install
+npm run dev
+```
+
+## 一键部署
+
+服务器和域名信息保存在 `deploy.config`,私钥文件仅保存在本机并已被 Git 忽略。
+
+```bash
+./deploy.sh
+```
+
+脚本会自动构建、上传并切换到新版本,服务器保留最近 5 个版本。站点使用独立 Docker 容器接入现有网关,不占用宿主机的新端口,也不会覆盖其他网站。
+
+首次迁移到一台全新服务器时才需要运行:
+
+```bash
+./provision-server.sh
+```

+ 79 - 0
deploy.sh

@@ -0,0 +1,79 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")"
+source ./deploy.config
+
+KEY_PATH="${KEY_FILE/#\~/$HOME}"
+RELEASE_ID="$(date -u +%Y%m%d%H%M%S)"
+ARCHIVE="${TMPDIR:-/tmp}/lessncosmos-${RELEASE_ID}.tar.gz"
+REMOTE_ARCHIVE="/tmp/lessncosmos-${RELEASE_ID}.tar.gz"
+
+if [[ ! -f "$KEY_PATH" ]]; then
+  echo "Deployment key not found: $KEY_PATH" >&2
+  exit 1
+fi
+
+chmod 600 "$KEY_PATH"
+npm run build
+COPYFILE_DISABLE=1 tar -C dist -czf "$ARCHIVE" .
+
+SSH=(ssh -i "$KEY_PATH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new "$SERVER_USER@$SERVER_HOST")
+SCP=(scp -i "$KEY_PATH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
+
+"${SCP[@]}" "$ARCHIVE" "$SERVER_USER@$SERVER_HOST:$REMOTE_ARCHIVE"
+
+"${SSH[@]}" bash -s -- \
+  "$REMOTE_ARCHIVE" "$REMOTE_ROOT" "$RELEASE_ID" \
+  "$SITE_CONTAINER" "$DOCKER_NETWORK" "$PROXY_CONTAINER" "$DOMAIN" <<'REMOTE'
+set -euo pipefail
+
+archive="$1"
+remote_root="$2"
+release_id="$3"
+site_container="$4"
+docker_network="$5"
+proxy_container="$6"
+domain="$7"
+release_dir="$remote_root/releases/$release_id"
+
+if ! docker inspect "$proxy_container" >/dev/null 2>&1; then
+  echo "Proxy container not found: $proxy_container" >&2
+  exit 1
+fi
+
+if ! docker network inspect "$docker_network" >/dev/null 2>&1; then
+  echo "Docker network not found: $docker_network" >&2
+  exit 1
+fi
+
+if ! grep -q "server_name $domain;" /opt/meishi_ccdw_life/nginx.conf; then
+  echo "Domain gateway configuration is missing; run the one-time provisioning first." >&2
+  exit 1
+fi
+
+mkdir -p "$release_dir"
+tar -xzf "$archive" -C "$release_dir"
+ln -sfn "$release_dir" "$remote_root/current"
+rm -f "$archive"
+
+if docker inspect "$site_container" >/dev/null 2>&1; then
+  docker rm -f "$site_container" >/dev/null
+fi
+
+docker run -d \
+  --name "$site_container" \
+  --restart unless-stopped \
+  --network "$docker_network" \
+  -v "$remote_root/current:/usr/share/nginx/html:ro" \
+  docker.m.daocloud.io/library/nginx:alpine >/dev/null
+
+docker exec "$proxy_container" nginx -t
+docker exec "$proxy_container" nginx -s reload
+
+find "$remote_root/releases" -mindepth 1 -maxdepth 1 -type d \
+  -printf '%T@ %p\n' | sort -nr | tail -n +6 | cut -d' ' -f2- | xargs -r rm -rf
+REMOTE
+
+rm -f "$ARCHIVE"
+echo "Deployed: https://$DOMAIN"

BIN
design-qa-comparison.png


BIN
design-qa-implementation.png


+ 46 - 0
design-qa.md

@@ -0,0 +1,46 @@
+# Design QA
+
+- source visual truth path: `/var/folders/85/gmzs1gr91kd7hwdq6qg_n9nr0000gn/T/codex-clipboard-10d15b30-f8f6-4ed3-a036-1ec633db8c92.png`
+- implementation screenshot path: `/Users/yuxin/local/code/ccxc-website/design-qa-implementation.png`
+- comparison image path: `/Users/yuxin/local/code/ccxc-website/design-qa-comparison.png`
+- viewport: 1760 × 1416
+- state: page scrolled to the bottom; footer visible
+
+## Full-view comparison evidence
+
+The side-by-side comparison confirms that the footer boundary line highlighted in the reference is absent in the implementation. The header border, logo, navigation, page background, footer copy, and surrounding whitespace remain unchanged.
+
+## Focused region comparison evidence
+
+No additional crop was needed because the affected footer boundary spans the full viewport width and is clearly legible in the full-view comparison.
+
+## Required fidelity surfaces
+
+- Fonts and typography: unchanged; the footer family, weight, size, line height, and tracking remain consistent with the existing page.
+- Spacing and layout rhythm: unchanged; footer height, padding, centering, and page flow are preserved.
+- Colors and visual tokens: the unwanted translucent gray footer border is removed; all other foreground and background tokens are unchanged.
+- Image quality and asset fidelity: unchanged; the existing brand mark renders sharply and no assets were replaced.
+- Copy and content: unchanged.
+
+## Findings
+
+- No actionable P0, P1, or P2 differences remain for the scoped annotation.
+
+## Comparison history
+
+- Initial finding (P2): a full-width divider remained visible above the footer in the marked bottom-of-page state.
+- Fix: removed the footer's top border while preserving other page dividers.
+- Post-fix evidence: `design-qa-comparison.png` shows the marked divider absent at the same 1760 × 1416 viewport; computed footer border width is `0px`.
+
+## Interaction and runtime checks
+
+- Scrolled the page to its maximum scroll position.
+- Confirmed the footer remains visible and correctly centered.
+- Checked browser console warnings and errors: none.
+- Production build completed successfully.
+
+## Follow-up polish
+
+- None required for this scoped change.
+
+final result: passed

+ 13 - 0
index.html

@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>纯粹星辰 | less and cosmos.</title>
+    <meta name="description" content="纯粹星辰官方网站。一念纯粹,万里星辰。">
+  </head>
+  <body>
+    <div id="root"></div>
+    <script type="module" src="/src/main.jsx"></script>
+  </body>
+</html>

+ 39 - 0
nginx.conf

@@ -0,0 +1,39 @@
+# Managed lessncosmos.com gateway blocks.
+# These blocks are inserted inside the existing Docker Nginx `http` section.
+
+server {
+    listen 80;
+    server_name lessncosmos.com;
+
+    location ^~ /.well-known/acme-challenge/ {
+        proxy_pass http://lessncosmos-site:80;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+
+    location / {
+        return 301 https://$host$request_uri;
+    }
+}
+
+server {
+    listen 443 ssl;
+    server_name lessncosmos.com;
+
+    ssl_certificate /etc/letsencrypt/live/lessncosmos.com/fullchain.pem;
+    ssl_certificate_key /etc/letsencrypt/live/lessncosmos.com/privkey.pem;
+    ssl_session_timeout 5m;
+    ssl_protocols TLSv1.2 TLSv1.3;
+    ssl_ciphers HIGH:!aNULL:!MD5;
+    ssl_prefer_server_ciphers on;
+
+    location / {
+        proxy_pass http://lessncosmos-site:80;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+    }
+}

+ 1332 - 0
package-lock.json

@@ -0,0 +1,1332 @@
+{
+  "name": "website",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "website",
+      "version": "0.0.0",
+      "dependencies": {
+        "react": "^19.2.7",
+        "react-dom": "^19.2.7"
+      },
+      "devDependencies": {
+        "@types/react": "^19.2.17",
+        "@types/react-dom": "^19.2.3",
+        "@vitejs/plugin-react": "^6.0.3",
+        "oxlint": "^1.71.0",
+        "vite": "^8.1.1"
+      }
+    },
+    "node_modules/@emnapi/core": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz",
+      "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/wasi-threads": "1.2.2",
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/runtime": {
+      "version": "1.11.1",
+      "resolved": "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz",
+      "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@emnapi/wasi-threads": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz",
+      "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@napi-rs/wasm-runtime": {
+      "version": "1.1.6",
+      "resolved": "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz",
+      "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@tybys/wasm-util": "^0.10.3"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/Brooooooklyn"
+      },
+      "peerDependencies": {
+        "@emnapi/core": "^1.7.1",
+        "@emnapi/runtime": "^1.7.1"
+      }
+    },
+    "node_modules/@oxc-project/types": {
+      "version": "0.138.0",
+      "resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.138.0.tgz",
+      "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/Boshen"
+      }
+    },
+    "node_modules/@oxlint/binding-android-arm-eabi": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz",
+      "integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-android-arm64": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz",
+      "integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-darwin-arm64": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz",
+      "integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-darwin-x64": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz",
+      "integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-freebsd-x64": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz",
+      "integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-arm-gnueabihf": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz",
+      "integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-arm-musleabihf": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz",
+      "integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-arm64-gnu": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz",
+      "integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-arm64-musl": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz",
+      "integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-ppc64-gnu": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz",
+      "integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-riscv64-gnu": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz",
+      "integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-riscv64-musl": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz",
+      "integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-s390x-gnu": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz",
+      "integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-x64-gnu": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz",
+      "integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-linux-x64-musl": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz",
+      "integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-openharmony-arm64": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz",
+      "integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-win32-arm64-msvc": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz",
+      "integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-win32-ia32-msvc": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz",
+      "integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@oxlint/binding-win32-x64-msvc": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz",
+      "integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-android-arm64": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz",
+      "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-arm64": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz",
+      "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-darwin-x64": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz",
+      "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-freebsd-x64": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz",
+      "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz",
+      "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-gnu": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz",
+      "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-arm64-musl": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz",
+      "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz",
+      "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-s390x-gnu": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz",
+      "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-gnu": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz",
+      "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-linux-x64-musl": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz",
+      "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-openharmony-arm64": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz",
+      "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openharmony"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-wasm32-wasi": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz",
+      "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==",
+      "cpu": [
+        "wasm32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/core": "1.11.1",
+        "@emnapi/runtime": "1.11.1",
+        "@napi-rs/wasm-runtime": "^1.1.6"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-arm64-msvc": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz",
+      "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/binding-win32-x64-msvc": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz",
+      "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      }
+    },
+    "node_modules/@rolldown/pluginutils": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+      "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@tybys/wasm-util": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz",
+      "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==",
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@types/react": {
+      "version": "19.2.17",
+      "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.2.17.tgz",
+      "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "csstype": "^3.2.2"
+      }
+    },
+    "node_modules/@types/react-dom": {
+      "version": "19.2.3",
+      "resolved": "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz",
+      "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+      "dev": true,
+      "license": "MIT",
+      "peerDependencies": {
+        "@types/react": "^19.2.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-react": {
+      "version": "6.0.3",
+      "resolved": "https://registry.npmmirror.com/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz",
+      "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@rolldown/pluginutils": "^1.0.1"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "peerDependencies": {
+        "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
+        "babel-plugin-react-compiler": "^1.0.0",
+        "vite": "^8.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@rolldown/plugin-babel": {
+          "optional": true
+        },
+        "babel-plugin-react-compiler": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.2.3",
+      "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
+      "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/detect-libc": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
+      "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/fdir": {
+      "version": "6.5.0",
+      "resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
+      "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/lightningcss": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz",
+      "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+      "dev": true,
+      "license": "MPL-2.0",
+      "dependencies": {
+        "detect-libc": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "lightningcss-android-arm64": "1.32.0",
+        "lightningcss-darwin-arm64": "1.32.0",
+        "lightningcss-darwin-x64": "1.32.0",
+        "lightningcss-freebsd-x64": "1.32.0",
+        "lightningcss-linux-arm-gnueabihf": "1.32.0",
+        "lightningcss-linux-arm64-gnu": "1.32.0",
+        "lightningcss-linux-arm64-musl": "1.32.0",
+        "lightningcss-linux-x64-gnu": "1.32.0",
+        "lightningcss-linux-x64-musl": "1.32.0",
+        "lightningcss-win32-arm64-msvc": "1.32.0",
+        "lightningcss-win32-x64-msvc": "1.32.0"
+      }
+    },
+    "node_modules/lightningcss-android-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+      "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-arm64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+      "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+      "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-freebsd-x64": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+      "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm-gnueabihf": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+      "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-gnu": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+      "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-musl": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+      "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-gnu": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+      "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-musl": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+      "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-arm64-msvc": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+      "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-x64-msvc": {
+      "version": "1.32.0",
+      "resolved": "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+      "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MPL-2.0",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.15",
+      "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz",
+      "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+      "dev": true,
+      "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/oxlint": {
+      "version": "1.72.0",
+      "resolved": "https://registry.npmmirror.com/oxlint/-/oxlint-1.72.0.tgz",
+      "integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "oxlint": "bin/oxlint"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/Boshen"
+      },
+      "optionalDependencies": {
+        "@oxlint/binding-android-arm-eabi": "1.72.0",
+        "@oxlint/binding-android-arm64": "1.72.0",
+        "@oxlint/binding-darwin-arm64": "1.72.0",
+        "@oxlint/binding-darwin-x64": "1.72.0",
+        "@oxlint/binding-freebsd-x64": "1.72.0",
+        "@oxlint/binding-linux-arm-gnueabihf": "1.72.0",
+        "@oxlint/binding-linux-arm-musleabihf": "1.72.0",
+        "@oxlint/binding-linux-arm64-gnu": "1.72.0",
+        "@oxlint/binding-linux-arm64-musl": "1.72.0",
+        "@oxlint/binding-linux-ppc64-gnu": "1.72.0",
+        "@oxlint/binding-linux-riscv64-gnu": "1.72.0",
+        "@oxlint/binding-linux-riscv64-musl": "1.72.0",
+        "@oxlint/binding-linux-s390x-gnu": "1.72.0",
+        "@oxlint/binding-linux-x64-gnu": "1.72.0",
+        "@oxlint/binding-linux-x64-musl": "1.72.0",
+        "@oxlint/binding-openharmony-arm64": "1.72.0",
+        "@oxlint/binding-win32-arm64-msvc": "1.72.0",
+        "@oxlint/binding-win32-ia32-msvc": "1.72.0",
+        "@oxlint/binding-win32-x64-msvc": "1.72.0"
+      },
+      "peerDependencies": {
+        "oxlint-tsgolint": ">=0.22.1",
+        "vite-plus": "*"
+      },
+      "peerDependenciesMeta": {
+        "oxlint-tsgolint": {
+          "optional": true
+        },
+        "vite-plus": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz",
+      "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.16",
+      "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.16.tgz",
+      "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
+      "dev": true,
+      "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.12",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/react": {
+      "version": "19.2.7",
+      "resolved": "https://registry.npmmirror.com/react/-/react-19.2.7.tgz",
+      "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/react-dom": {
+      "version": "19.2.7",
+      "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.7.tgz",
+      "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
+      "license": "MIT",
+      "dependencies": {
+        "scheduler": "^0.27.0"
+      },
+      "peerDependencies": {
+        "react": "^19.2.7"
+      }
+    },
+    "node_modules/rolldown": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.4.tgz",
+      "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@oxc-project/types": "=0.138.0",
+        "@rolldown/pluginutils": "^1.0.0"
+      },
+      "bin": {
+        "rolldown": "bin/cli.mjs"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "optionalDependencies": {
+        "@rolldown/binding-android-arm64": "1.1.4",
+        "@rolldown/binding-darwin-arm64": "1.1.4",
+        "@rolldown/binding-darwin-x64": "1.1.4",
+        "@rolldown/binding-freebsd-x64": "1.1.4",
+        "@rolldown/binding-linux-arm-gnueabihf": "1.1.4",
+        "@rolldown/binding-linux-arm64-gnu": "1.1.4",
+        "@rolldown/binding-linux-arm64-musl": "1.1.4",
+        "@rolldown/binding-linux-ppc64-gnu": "1.1.4",
+        "@rolldown/binding-linux-s390x-gnu": "1.1.4",
+        "@rolldown/binding-linux-x64-gnu": "1.1.4",
+        "@rolldown/binding-linux-x64-musl": "1.1.4",
+        "@rolldown/binding-openharmony-arm64": "1.1.4",
+        "@rolldown/binding-wasm32-wasi": "1.1.4",
+        "@rolldown/binding-win32-arm64-msvc": "1.1.4",
+        "@rolldown/binding-win32-x64-msvc": "1.1.4"
+      }
+    },
+    "node_modules/scheduler": {
+      "version": "0.27.0",
+      "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz",
+      "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+      "license": "MIT"
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "dev": true,
+      "license": "BSD-3-Clause",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
+      "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.5.0",
+        "picomatch": "^4.0.4"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "dev": true,
+      "license": "0BSD",
+      "optional": true
+    },
+    "node_modules/vite": {
+      "version": "8.1.3",
+      "resolved": "https://registry.npmmirror.com/vite/-/vite-8.1.3.tgz",
+      "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "lightningcss": "^1.32.0",
+        "picomatch": "^4.0.4",
+        "postcss": "^8.5.16",
+        "rolldown": "~1.1.3",
+        "tinyglobby": "^0.2.17"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^20.19.0 || >=22.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^20.19.0 || >=22.12.0",
+        "@vitejs/devtools": "^0.3.0",
+        "esbuild": "^0.27.0 || ^0.28.0",
+        "jiti": ">=1.21.0",
+        "less": "^4.0.0",
+        "sass": "^1.70.0",
+        "sass-embedded": "^1.70.0",
+        "stylus": ">=0.54.8",
+        "sugarss": "^5.0.0",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "@vitejs/devtools": {
+          "optional": true
+        },
+        "esbuild": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    }
+  }
+}

+ 23 - 0
package.json

@@ -0,0 +1,23 @@
+{
+  "name": "website",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "lint": "oxlint",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "react": "^19.2.7",
+    "react-dom": "^19.2.7"
+  },
+  "devDependencies": {
+    "@types/react": "^19.2.17",
+    "@types/react-dom": "^19.2.3",
+    "@vitejs/plugin-react": "^6.0.3",
+    "oxlint": "^1.71.0",
+    "vite": "^8.1.1"
+  }
+}

+ 140 - 0
provision-server.sh

@@ -0,0 +1,140 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+cd "$(dirname "$0")"
+source ./deploy.config
+
+KEY_PATH="${KEY_FILE/#\~/$HOME}"
+RELEASE_ID="$(date -u +%Y%m%d%H%M%S)"
+ARCHIVE="${TMPDIR:-/tmp}/lessncosmos-initial-${RELEASE_ID}.tar.gz"
+REMOTE_ARCHIVE="/tmp/lessncosmos-initial-${RELEASE_ID}.tar.gz"
+
+chmod 600 "$KEY_PATH"
+npm run build
+COPYFILE_DISABLE=1 tar -C dist -czf "$ARCHIVE" .
+
+SSH=(ssh -i "$KEY_PATH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new "$SERVER_USER@$SERVER_HOST")
+SCP=(scp -i "$KEY_PATH" -o BatchMode=yes -o StrictHostKeyChecking=accept-new)
+
+"${SCP[@]}" "$ARCHIVE" "$SERVER_USER@$SERVER_HOST:$REMOTE_ARCHIVE"
+"${SCP[@]}" nginx.conf "$SERVER_USER@$SERVER_HOST:/tmp/lessncosmos-nginx.conf"
+
+"${SSH[@]}" bash -s -- \
+  "$REMOTE_ARCHIVE" "$REMOTE_ROOT" "$RELEASE_ID" \
+  "$SITE_CONTAINER" "$DOCKER_NETWORK" "$PROXY_CONTAINER" "$DOMAIN" <<'REMOTE'
+set -euo pipefail
+
+archive="$1"
+remote_root="$2"
+release_id="$3"
+site_container="$4"
+docker_network="$5"
+proxy_container="$6"
+domain="$7"
+gateway_config="/opt/meishi_ccdw_life/nginx.conf"
+release_dir="$remote_root/releases/$release_id"
+begin_marker="# BEGIN MANAGED $domain"
+end_marker="# END MANAGED $domain"
+
+if grep -q "server_name $domain" "$gateway_config" && ! grep -qF "$begin_marker" "$gateway_config"; then
+  echo "Refusing to continue: $domain is already configured outside the managed block." >&2
+  exit 1
+fi
+
+mkdir -p "$release_dir"
+tar -xzf "$archive" -C "$release_dir"
+ln -sfn "$release_dir" "$remote_root/current"
+rm -f "$archive"
+
+if docker inspect "$site_container" >/dev/null 2>&1; then
+  docker rm -f "$site_container" >/dev/null
+fi
+
+docker run -d \
+  --name "$site_container" \
+  --restart unless-stopped \
+  --network "$docker_network" \
+  -v "$remote_root/current:/usr/share/nginx/html:ro" \
+  docker.m.daocloud.io/library/nginx:alpine >/dev/null
+
+backup="$gateway_config.backup-$(date -u +%Y%m%d%H%M%S)"
+cp "$gateway_config" "$backup"
+
+clean_config="$(mktemp)"
+awk -v begin="$begin_marker" -v end="$end_marker" '
+  $0 == begin { skip=1; next }
+  $0 == end { skip=0; next }
+  !skip { print }
+' "$gateway_config" > "$clean_config"
+
+stage_block="$(mktemp)"
+cat > "$stage_block" <<STAGE
+$begin_marker
+    server {
+        listen 80;
+        server_name $domain;
+        location / {
+            proxy_pass http://$site_container:80;
+            proxy_set_header Host \$host;
+            proxy_set_header X-Real-IP \$remote_addr;
+            proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
+            proxy_set_header X-Forwarded-Proto \$scheme;
+        }
+    }
+$end_marker
+STAGE
+
+head -n -1 "$clean_config" > "$gateway_config"
+cat "$stage_block" >> "$gateway_config"
+printf '}\n' >> "$gateway_config"
+
+if ! docker exec "$proxy_container" nginx -t; then
+  cp "$backup" "$gateway_config"
+  exit 1
+fi
+docker exec "$proxy_container" nginx -s reload
+
+mkdir -p "$remote_root/current/.well-known/acme-challenge"
+printf 'ready' > "$remote_root/current/.well-known/acme-challenge/deployment-check"
+challenge=""
+for _ in 1 2 3 4 5 6 7 8 9 10; do
+  challenge="$(curl -fsS -H "Host: $domain" http://127.0.0.1/.well-known/acme-challenge/deployment-check 2>/dev/null || true)"
+  [[ "$challenge" == "ready" ]] && break
+  sleep 1
+done
+test "$challenge" = "ready"
+
+certbot certonly --webroot \
+  --webroot-path "$remote_root/current" \
+  --domain "$domain" \
+  --non-interactive --agree-tos --register-unsafely-without-email
+
+final_block="$(mktemp)"
+{
+  printf '%s\n' "$begin_marker"
+  sed 's/^/    /' /tmp/lessncosmos-nginx.conf
+  printf '%s\n' "$end_marker"
+} > "$final_block"
+
+head -n -1 "$clean_config" > "$gateway_config"
+cat "$final_block" >> "$gateway_config"
+printf '}\n' >> "$gateway_config"
+
+if ! docker exec "$proxy_container" nginx -t; then
+  cp "$backup" "$gateway_config"
+  docker exec "$proxy_container" nginx -s reload
+  exit 1
+fi
+docker exec "$proxy_container" nginx -s reload
+
+cat > /etc/letsencrypt/renewal-hooks/deploy/reload-lessncosmos-nginx.sh <<HOOK
+#!/bin/sh
+docker exec $proxy_container nginx -s reload
+HOOK
+chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-lessncosmos-nginx.sh
+
+rm -f /tmp/lessncosmos-nginx.conf "$clean_config" "$stage_block" "$final_block"
+echo "Provisioned: https://$domain"
+REMOTE
+
+rm -f "$ARCHIVE"

+ 15 - 0
public/brand/logo-chinese-slogan-light-bg.svg

@@ -0,0 +1,15 @@
+<svg width="884" height="280" viewBox="0 0 884 280" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
+  <title id="title">纯粹星辰 logo with slogan on light background</title>
+  <desc id="desc">The Less and Cosmos symbol with Chinese brand name and slogan on Cosmos White background.</desc>
+  <rect width="884" height="280" fill="#F5F5F7"/>
+  <g transform="translate(88 82)">
+    <circle cx="12" cy="36" r="4" fill="#B89652"/>
+    <path d="M38 43C76 11 142 1 204 15C228 21 249 31 266 44" stroke="#4E5862" stroke-width="1.15" stroke-linecap="round" opacity="0.78"/>
+    <path d="M66 49C117 31 187 28 246 45" stroke="#0B0C10" stroke-width="0.62" stroke-linecap="round" opacity="0.26"/>
+    <circle cx="268" cy="45" r="1.35" fill="#0B0C10" opacity="0.76"/>
+  </g>
+  <text x="398" y="125" fill="#0B0C10" font-family="'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif" font-size="46" font-weight="300" letter-spacing="18">纯粹星辰</text>
+  <line x1="400" y1="150" x2="796" y2="150" stroke="#D8DADF" stroke-width="1"/>
+  <text x="404" y="190" fill="#59616A" font-family="'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif" font-size="20" font-weight="300" letter-spacing="10">一念纯粹,万里星辰。</text>
+  <text x="407" y="222" fill="#59616A" font-family="Inter, 'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="14" font-weight="200" letter-spacing="4.8" opacity="0.8">less and cosmos.</text>
+</svg>

+ 14 - 0
public/brand/logo-chinese-slogan.svg

@@ -0,0 +1,14 @@
+<svg width="1120" height="280" viewBox="0 0 1120 280" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
+  <title id="title">纯粹星辰 logo with slogan</title>
+  <desc id="desc">The Less and Cosmos symbol with Chinese brand name and slogan.</desc>
+  <g transform="translate(88 82)">
+    <circle cx="12" cy="36" r="4" fill="#E6C687"/>
+    <path d="M38 43C76 11 142 1 204 15C228 21 249 31 266 44" stroke="#C5C6C7" stroke-width="1.15" stroke-linecap="round" opacity="0.82"/>
+    <path d="M66 49C117 31 187 28 246 45" stroke="#F5F5F7" stroke-width="0.62" stroke-linecap="round" opacity="0.28"/>
+    <circle cx="268" cy="45" r="1.35" fill="#F5F5F7" opacity="0.8"/>
+  </g>
+  <text x="398" y="125" fill="#F5F5F7" font-family="'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif" font-size="46" font-weight="300" letter-spacing="18">纯粹星辰</text>
+  <line x1="400" y1="150" x2="796" y2="150" stroke="#1F2833" stroke-width="1"/>
+  <text x="404" y="190" fill="#C5C6C7" font-family="'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif" font-size="20" font-weight="300" letter-spacing="10">一念纯粹,万里星辰。</text>
+  <text x="407" y="222" fill="#C5C6C7" font-family="Inter, 'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="14" font-weight="200" letter-spacing="4.8" opacity="0.74">less and cosmos.</text>
+</svg>

+ 14 - 0
public/brand/logo-less-and-cosmos-light-bg.svg

@@ -0,0 +1,14 @@
+<svg width="912" height="240" viewBox="0 0 912 240" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
+  <title id="title">Less and Cosmos logo on light background</title>
+  <desc id="desc">The Less and Cosmos full logo lockup on Cosmos White background.</desc>
+  <rect width="912" height="240" fill="#F5F5F7"/>
+  <g transform="translate(72 72)">
+    <circle cx="12" cy="36" r="4" fill="#B89652"/>
+    <path d="M38 43C76 11 142 1 204 15C228 21 249 31 266 44" stroke="#4E5862" stroke-width="1.15" stroke-linecap="round" opacity="0.78"/>
+    <path d="M66 49C117 31 187 28 246 45" stroke="#0B0C10" stroke-width="0.62" stroke-linecap="round" opacity="0.26"/>
+    <circle cx="268" cy="45" r="1.35" fill="#0B0C10" opacity="0.76"/>
+  </g>
+  <text x="374" y="105" fill="#0B0C10" font-family="Inter, 'Helvetica Neue', Helvetica, Arial, sans-serif" font-size="42" font-weight="200" letter-spacing="5.6">less and cosmos.</text>
+  <text x="379" y="151" fill="#59616A" font-family="'Noto Sans SC', 'Source Han Sans SC', 'PingFang SC', 'Microsoft YaHei', sans-serif" font-size="18" font-weight="300" letter-spacing="9">一念纯粹,万里星辰。</text>
+  <line x1="374" y1="126" x2="840" y2="126" stroke="#D8DADF" stroke-width="1"/>
+</svg>

+ 9 - 0
public/brand/logo-mark-light-bg.svg

@@ -0,0 +1,9 @@
+<svg width="320" height="96" viewBox="0 0 320 96" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
+  <title id="title">Less and Cosmos mark on light background</title>
+  <desc id="desc">A minimal dot and orbit symbol on Cosmos White background.</desc>
+  <rect width="320" height="96" fill="#F5F5F7"/>
+  <circle cx="46" cy="50" r="4" fill="#B89652"/>
+  <path d="M72 57C110 25 176 15 238 29C262 35 283 45 300 58" stroke="#4E5862" stroke-width="1.15" stroke-linecap="round" opacity="0.78"/>
+  <path d="M100 63C151 45 221 42 280 59" stroke="#0B0C10" stroke-width="0.62" stroke-linecap="round" opacity="0.26"/>
+  <circle cx="302" cy="59" r="1.35" fill="#0B0C10" opacity="0.76"/>
+</svg>

+ 8 - 0
public/brand/logo-mark.svg

@@ -0,0 +1,8 @@
+<svg width="320" height="96" viewBox="0 0 320 96" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
+  <title id="title">Less and Cosmos logo mark</title>
+  <desc id="desc">A minimal spark dot followed by a thin orbital arc.</desc>
+  <circle cx="46" cy="50" r="4" fill="#E6C687"/>
+  <path d="M72 57C110 25 176 15 238 29C262 35 283 45 300 58" stroke="#C5C6C7" stroke-width="1.15" stroke-linecap="round" opacity="0.82"/>
+  <path d="M100 63C151 45 221 42 280 59" stroke="#F5F5F7" stroke-width="0.62" stroke-linecap="round" opacity="0.28"/>
+  <circle cx="302" cy="59" r="1.35" fill="#F5F5F7" opacity="0.8"/>
+</svg>

Fișier diff suprimat deoarece este prea mare
+ 0 - 0
public/favicon.svg


+ 24 - 0
public/icons.svg

@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <symbol id="bluesky-icon" viewBox="0 0 16 17">
+    <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
+    <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
+  </symbol>
+  <symbol id="discord-icon" viewBox="0 0 20 19">
+    <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
+  </symbol>
+  <symbol id="documentation-icon" viewBox="0 0 21 20">
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
+  </symbol>
+  <symbol id="github-icon" viewBox="0 0 19 19">
+    <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
+  </symbol>
+  <symbol id="social-icon" viewBox="0 0 20 20">
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
+    <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
+  </symbol>
+  <symbol id="x-icon" viewBox="0 0 19 19">
+    <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
+  </symbol>
+</svg>

+ 198 - 0
src/App.jsx

@@ -0,0 +1,198 @@
+import { useEffect, useRef, useState } from "react";
+
+const content = {
+  zh: {
+    nav: ["智囊团", "AI 服务", "预约咨询"],
+    brand: "纯粹星辰",
+    eyebrow: "LESS AND COSMOS.",
+    motto: "一念纯粹,万里星辰。",
+    statement: "静心想透产业问题,专注执行 AI 落地。",
+    comingSoon: "正在建设中",
+    footer: "© 2026 杭州纯粹星辰人工智能科技有限公司。保留所有权利。",
+    logoAlt: "纯粹星辰,一念纯粹,万里星辰。",
+  },
+  en: {
+    nav: ["Think Tank", "AI Services", "Consultation"],
+    brand: "less and cosmos.",
+    eyebrow: "PURE INTENTION. COSMIC DISTANCE.",
+    motto: "One pure intention. A cosmos ahead.",
+    statement: "Think deeply about industry problems. Execute AI with focus.",
+    comingSoon: "Coming soon.",
+    footer:
+      "© 2026 Less and Cosmos (Hangzhou) AI Technology Co., Ltd. All rights reserved.",
+    logoAlt: "Less and Cosmos. One pure intention. A cosmos ahead.",
+  },
+};
+
+function clamp(value, min = 0, max = 1) {
+  return Math.min(max, Math.max(min, value));
+}
+
+export default function App() {
+  const storyRef = useRef(null);
+  const headerBrandRef = useRef(null);
+  const noticeTimerRef = useRef(null);
+  const [notice, setNotice] = useState(null);
+  const [language, setLanguage] = useState(() => {
+    const saved = window.localStorage.getItem("lessncosmos-language");
+    return saved === "en" ? "en" : "zh";
+  });
+
+  const copy = content[language];
+  const isEnglish = language === "en";
+
+  useEffect(() => {
+    document.documentElement.lang = isEnglish ? "en" : "zh-CN";
+    window.localStorage.setItem("lessncosmos-language", language);
+  }, [isEnglish, language]);
+
+  useEffect(() => {
+    const story = storyRef.current;
+    if (!story) return undefined;
+
+    let frame = 0;
+    const update = () => {
+      frame = 0;
+      const rect = story.getBoundingClientRect();
+      const distance = Math.max(story.offsetHeight - window.innerHeight, 1);
+      const progress = clamp(-rect.top / distance);
+      const orbitProgress = clamp((progress - 0.08) / 0.5);
+      const copyProgress = clamp((progress - 0.42) / 0.28);
+
+      story.style.setProperty("--scroll-progress", progress.toFixed(4));
+      story.style.setProperty("--orbit-progress", orbitProgress.toFixed(4));
+      story.style.setProperty("--copy-progress", copyProgress.toFixed(4));
+
+      const featuredLogo = story.querySelector(".brand-copy h1");
+      const headerHeight = document.querySelector(".header")?.offsetHeight ?? 0;
+      const featuredLogoIsOutside =
+        featuredLogo?.getBoundingClientRect().bottom <= headerHeight;
+      headerBrandRef.current?.classList.toggle(
+        "is-visible",
+        featuredLogoIsOutside,
+      );
+    };
+
+    const requestUpdate = () => {
+      if (!frame) frame = window.requestAnimationFrame(update);
+    };
+
+    update();
+    window.addEventListener("scroll", requestUpdate, { passive: true });
+    window.addEventListener("resize", requestUpdate);
+
+    return () => {
+      window.removeEventListener("scroll", requestUpdate);
+      window.removeEventListener("resize", requestUpdate);
+      if (frame) window.cancelAnimationFrame(frame);
+    };
+  }, []);
+
+  useEffect(
+    () => () => {
+      if (noticeTimerRef.current) window.clearTimeout(noticeTimerRef.current);
+    },
+    [],
+  );
+
+  const toggleLanguage = () => setLanguage(isEnglish ? "zh" : "en");
+  const showComingSoon = () => {
+    if (noticeTimerRef.current) window.clearTimeout(noticeTimerRef.current);
+    setNotice({ id: Date.now(), message: copy.comingSoon });
+    noticeTimerRef.current = window.setTimeout(() => setNotice(null), 2400);
+  };
+
+  return (
+    <div className="site">
+      <header className="header">
+        <a
+          className="header__brand"
+          href="#top"
+          aria-label={copy.brand}
+          ref={headerBrandRef}
+        >
+          <img
+            src="/brand/logo-mark-light-bg.svg"
+            alt={copy.brand}
+          />
+        </a>
+
+        <div className="header__right">
+          <nav className="nav" aria-label={isEnglish ? "Main navigation" : "主导航"}>
+            {copy.nav.map((item) => (
+              <button
+                className="nav__item"
+                type="button"
+                onClick={showComingSoon}
+                key={item}
+              >
+                {item}
+              </button>
+            ))}
+          </nav>
+
+          <button
+            className="language"
+            type="button"
+            onClick={toggleLanguage}
+            aria-label={isEnglish ? "切换至中文" : "Switch to English"}
+          >
+            <span className="language__icon" aria-hidden="true" />
+            <span>{isEnglish ? "中" : "EN"}</span>
+          </button>
+        </div>
+      </header>
+
+      {notice && (
+        <div className="notice" role="status" aria-live="polite" key={notice.id}>
+          <span className="notice__dot" aria-hidden="true" />
+          <span>{notice.message}</span>
+        </div>
+      )}
+
+      <main id="top">
+        <section className="story" ref={storyRef} aria-label={copy.brand}>
+          <div className="story__stage">
+            <div className="hero-logo" aria-hidden="true">
+              <img
+                src={
+                  isEnglish
+                    ? "/brand/logo-less-and-cosmos-light-bg.svg"
+                    : "/brand/logo-chinese-slogan-light-bg.svg"
+                }
+                alt=""
+              />
+            </div>
+
+            <div className="orbit-scene" id="brand">
+              <div className="orbit-scene__origin" aria-hidden="true" />
+              <div className="orbit-reveal orbit-reveal--solid" aria-hidden="true">
+                <span className="orbit-line orbit-line--solid" />
+              </div>
+              <div className="orbit-reveal orbit-reveal--dashed" aria-hidden="true">
+                <span className="orbit-line orbit-line--dashed" />
+              </div>
+
+              <div className={`brand-copy${isEnglish ? " brand-copy--english" : ""}`}>
+                <p className="brand-copy__eyebrow">{copy.eyebrow}</p>
+                <h1>{copy.brand}</h1>
+                <span className="brand-copy__rule" aria-hidden="true" />
+                <p className="brand-copy__statement">{copy.statement}</p>
+              </div>
+            </div>
+
+            <div className="scroll-cue" aria-hidden="true">
+              <span />
+              <small>{isEnglish ? "SCROLL" : "向下"}</small>
+            </div>
+          </div>
+        </section>
+      </main>
+
+      <footer className="footer">
+        <p>{copy.footer}</p>
+        <p className="footer__filing">ICP备案号待补充</p>
+      </footer>
+    </div>
+  );
+}

BIN
src/assets/hero.png


Fișier diff suprimat deoarece este prea mare
+ 0 - 0
src/assets/vite.svg


+ 34 - 0
src/components/Accounts.jsx

@@ -0,0 +1,34 @@
+import { accounts } from "../data/accounts";
+
+function AccountCard({ name, mark, description, tags }) {
+  return (
+    <article className="account-card">
+      <div className="account-top">
+        <h3 className="account-name">{name}</h3>
+        <div className="account-mark" aria-hidden="true">{mark}</div>
+      </div>
+      <p className="account-desc">{description}</p>
+      <div className="account-meta">
+        {tags.map((tag) => (
+          <span key={tag}>{tag}</span>
+        ))}
+      </div>
+    </article>
+  );
+}
+
+export default function Accounts() {
+  return (
+    <section className="accounts" id="accounts" aria-labelledby="accounts-title">
+      <div className="accounts-inner">
+        <p className="section-kicker">Owned Media</p>
+        <h2 className="section-title" id="accounts-title">纯粹星辰旗下账号</h2>
+        <div className="account-grid" id="accountGrid">
+          {accounts.map((account) => (
+            <AccountCard key={account.name} {...account} />
+          ))}
+        </div>
+      </div>
+    </section>
+  );
+}

+ 10 - 0
src/components/Footer.jsx

@@ -0,0 +1,10 @@
+export default function Footer() {
+  return (
+    <footer className="footer">
+      <div className="footer-inner">
+        <span>PURE INTENTION. COSMIC DISTANCE.</span>
+        <span>LESS AND COSMOS.</span>
+      </div>
+    </footer>
+  );
+}

+ 15 - 0
src/components/Hero.jsx

@@ -0,0 +1,15 @@
+export default function Hero() {
+  return (
+    <section className="hero" aria-labelledby="brand-title">
+      <div className="hero-inner">
+        <img
+          className="hero-logo"
+          src="/brand/logo-chinese-slogan.svg"
+          alt="纯粹星辰,一念纯粹,万里星辰。"
+        />
+        <h1 className="sr-only" id="brand-title">纯粹星辰 less and cosmos.</h1>
+        <a className="hero-action" href="#accounts">探索纯粹厂牌</a>
+      </div>
+    </section>
+  );
+}

+ 46 - 0
src/components/Nav.jsx

@@ -0,0 +1,46 @@
+import { useEffect, useRef, useState } from "react";
+
+export default function Nav() {
+  const navRef = useRef(null);
+  const brandRef = useRef(null);
+  const [isScrolled, setIsScrolled] = useState(false);
+
+  useEffect(() => {
+    const hero = document.querySelector(".hero");
+
+    function updateNavLogo() {
+      if (!hero) return;
+      const revealPoint = hero.offsetHeight * 0.62;
+      const shouldShowLogo = window.scrollY > revealPoint;
+      setIsScrolled(shouldShowLogo);
+    }
+
+    updateNavLogo();
+    window.addEventListener("scroll", updateNavLogo, { passive: true });
+    window.addEventListener("resize", updateNavLogo, { passive: true });
+
+    return () => {
+      window.removeEventListener("scroll", updateNavLogo);
+      window.removeEventListener("resize", updateNavLogo);
+    };
+  }, []);
+
+  return (
+    <header className={`nav${isScrolled ? " is-scrolled" : ""}`} id="siteNav" ref={navRef}>
+      <div className="nav-inner">
+        <a
+          className="brand-mini"
+          href="#top"
+          aria-label="纯粹星辰首页"
+          aria-hidden={!isScrolled}
+          tabIndex={isScrolled ? 0 : -1}
+          ref={brandRef}
+        >
+          <img src="/brand/logo-mark.svg" alt="" />
+          <span>less and cosmos.</span>
+        </a>
+        <a className="join-link" href="mailto:hello@lessandcosmos.com">JOIN</a>
+      </div>
+    </header>
+  );
+}

+ 88 - 0
src/components/StarsCanvas.jsx

@@ -0,0 +1,88 @@
+import { useEffect, useRef } from "react";
+
+export default function StarsCanvas() {
+  const canvasRef = useRef(null);
+
+  useEffect(() => {
+    const canvas = canvasRef.current;
+    const context = canvas.getContext("2d");
+    const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+
+    let stars = [];
+    let width = 0;
+    let height = 0;
+    let animationFrame = 0;
+
+    function resizeStars() {
+      const ratio = Math.min(window.devicePixelRatio || 1, 2);
+      width = window.innerWidth;
+      height = window.innerHeight;
+      canvas.width = Math.floor(width * ratio);
+      canvas.height = Math.floor(height * ratio);
+      canvas.style.width = `${width}px`;
+      canvas.style.height = `${height}px`;
+      context.setTransform(ratio, 0, 0, ratio, 0, 0);
+
+      const starCount = Math.min(180, Math.max(80, Math.floor((width * height) / 8200)));
+      stars = Array.from({ length: starCount }, (_, index) => ({
+        x: Math.random() * width,
+        y: Math.random() * height,
+        radius: index % 13 === 0 ? Math.random() * 1.8 + 0.8 : Math.random() * 1.1 + 0.2,
+        alpha: Math.random() * 0.7 + 0.18,
+        speed: Math.random() * 0.16 + 0.04,
+        drift: Math.random() * 0.12 - 0.06,
+      }));
+    }
+
+    function drawStars() {
+      context.clearRect(0, 0, width, height);
+
+      const glow = context.createRadialGradient(
+        width * 0.5, height * 0.18, 0,
+        width * 0.5, height * 0.18, Math.max(width, height) * 0.62
+      );
+      glow.addColorStop(0, "rgba(230, 198, 135, 0.08)");
+      glow.addColorStop(0.46, "rgba(31, 40, 51, 0.08)");
+      glow.addColorStop(1, "rgba(11, 12, 16, 0)");
+      context.fillStyle = glow;
+      context.fillRect(0, 0, width, height);
+
+      for (const star of stars) {
+        context.beginPath();
+        context.arc(star.x, star.y, star.radius, 0, Math.PI * 2);
+        context.fillStyle = `rgba(245, 245, 247, ${star.alpha})`;
+        context.fill();
+
+        if (!prefersReducedMotion) {
+          star.y += star.speed;
+          star.x += star.drift;
+          if (star.y > height + 4) star.y = -4;
+          if (star.x < -4) star.x = width + 4;
+          if (star.x > width + 4) star.x = -4;
+        }
+      }
+
+      if (!prefersReducedMotion) {
+        animationFrame = requestAnimationFrame(drawStars);
+      }
+    }
+
+    function handleResize() {
+      cancelAnimationFrame(animationFrame);
+      resizeStars();
+      drawStars();
+    }
+
+    resizeStars();
+    drawStars();
+
+    window.addEventListener("resize", handleResize, { passive: true });
+
+    return () => {
+      cancelAnimationFrame(animationFrame);
+      window.removeEventListener("resize", handleResize);
+    };
+  }, []);
+
+  return <canvas ref={canvasRef} className="stars" aria-hidden="true" />;
+}

+ 8 - 0
src/data/accounts.js

@@ -0,0 +1,8 @@
+export const accounts = [
+  {
+    name: "投资实战派",
+    mark: "投",
+    description: "面向真实市场的投资观察与实战复盘,保持克制、清醒、长期主义。",
+    tags: ["投资", "实战复盘", "长期主义"],
+  },
+];

+ 594 - 0
src/index.css

@@ -0,0 +1,594 @@
+:root {
+  color: #0b0c10;
+  background: #f5f5f7;
+  font-family: Inter, "Helvetica Neue", Helvetica, Arial, "PingFang SC",
+    "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+html {
+  min-width: 320px;
+  min-height: 100%;
+  scroll-behavior: smooth;
+}
+
+body,
+#root {
+  min-width: 320px;
+  min-height: 100%;
+  margin: 0;
+}
+
+body {
+  background: #f5f5f7;
+}
+
+button,
+a {
+  color: inherit;
+  font: inherit;
+}
+
+.site {
+  min-height: 100vh;
+  overflow: clip;
+  background: #f5f5f7;
+}
+
+.header {
+  position: fixed;
+  z-index: 20;
+  top: 0;
+  right: 0;
+  left: 0;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 76px;
+  padding: 10px clamp(22px, 4vw, 64px);
+  border-bottom: 1px solid rgba(78, 88, 98, 0.1);
+  background: rgba(245, 245, 247, 0.88);
+  backdrop-filter: blur(18px);
+}
+
+.header__brand {
+  display: block;
+  width: clamp(112px, 12vw, 160px);
+  overflow: hidden;
+  opacity: 0;
+  pointer-events: none;
+  transform: translateY(-5px);
+  transition:
+    opacity 520ms cubic-bezier(0.22, 1, 0.36, 1),
+    transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.header__brand.is-visible {
+  opacity: 1;
+  pointer-events: auto;
+  transform: translateY(0);
+}
+
+.header__brand img {
+  display: block;
+  width: 100%;
+  height: auto;
+}
+
+.header__right,
+.nav,
+.language {
+  display: flex;
+  align-items: center;
+}
+
+.header__right {
+  gap: clamp(22px, 3vw, 48px);
+}
+
+.nav {
+  gap: clamp(22px, 3vw, 44px);
+}
+
+.nav__item {
+  position: relative;
+  border: 0;
+  padding: 10px 0;
+  color: rgba(11, 12, 16, 0.66);
+  background: transparent;
+  font-size: 12px;
+  font-weight: 300;
+  letter-spacing: 0.12em;
+  white-space: nowrap;
+  cursor: pointer;
+  transition: color 200ms ease;
+}
+
+.nav__item::after {
+  position: absolute;
+  right: 0;
+  bottom: 4px;
+  left: 0;
+  height: 1px;
+  background: #b89652;
+  content: "";
+  opacity: 0;
+  transform: scaleX(0.25);
+  transition:
+    opacity 220ms ease,
+    transform 320ms cubic-bezier(0.22, 1, 0.36, 1);
+}
+
+.nav__item:hover,
+.nav__item:focus-visible {
+  color: #0b0c10;
+}
+
+.nav__item:hover::after,
+.nav__item:focus-visible::after {
+  opacity: 0.82;
+  transform: scaleX(1);
+}
+
+.nav__item:focus-visible {
+  outline: 1px solid rgba(184, 150, 82, 0.7);
+  outline-offset: 4px;
+}
+
+.notice {
+  position: fixed;
+  z-index: 30;
+  top: 92px;
+  left: 50%;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+  min-height: 38px;
+  border: 1px solid rgba(78, 88, 98, 0.14);
+  border-radius: 999px;
+  padding: 0 17px;
+  color: rgba(11, 12, 16, 0.68);
+  background: rgba(245, 245, 247, 0.9);
+  box-shadow: 0 12px 36px rgba(31, 40, 51, 0.07);
+  backdrop-filter: blur(16px);
+  font-size: 11px;
+  font-weight: 300;
+  letter-spacing: 0.14em;
+  pointer-events: none;
+  animation: notice-show 2.4s cubic-bezier(0.22, 1, 0.36, 1) both;
+}
+
+.notice__dot {
+  width: 4px;
+  height: 4px;
+  border-radius: 50%;
+  background: #b89652;
+}
+
+@keyframes notice-show {
+  0% {
+    opacity: 0;
+    filter: blur(3px);
+    transform: translate(-50%, -8px);
+  }
+
+  14%,
+  78% {
+    opacity: 1;
+    filter: blur(0);
+    transform: translate(-50%, 0);
+  }
+
+  100% {
+    opacity: 0;
+    transform: translate(-50%, -4px);
+  }
+}
+
+.language {
+  gap: 8px;
+  min-width: 52px;
+  border: 0;
+  padding: 8px 0;
+  color: rgba(11, 12, 16, 0.62);
+  background: transparent;
+  font-size: 11px;
+  font-weight: 400;
+  letter-spacing: 0.08em;
+  cursor: pointer;
+  transition: color 180ms ease;
+}
+
+.language:hover,
+.language:focus-visible {
+  color: #0b0c10;
+}
+
+.language:focus-visible {
+  outline: 1px solid #b89652;
+  outline-offset: 4px;
+}
+
+.language__icon {
+  position: relative;
+  display: block;
+  width: 16px;
+  height: 16px;
+  border: 1px solid currentColor;
+  border-radius: 50%;
+}
+
+.language__icon::before,
+.language__icon::after {
+  position: absolute;
+  content: "";
+}
+
+.language__icon::before {
+  top: 2px;
+  bottom: 2px;
+  left: 50%;
+  width: 6px;
+  border-right: 1px solid currentColor;
+  border-left: 1px solid currentColor;
+  border-radius: 50%;
+  transform: translateX(-50%);
+}
+
+.language__icon::after {
+  top: 50%;
+  right: 1px;
+  left: 1px;
+  border-top: 1px solid currentColor;
+}
+
+.story {
+  --scroll-progress: 0;
+  --orbit-progress: 0;
+  --copy-progress: 0;
+  position: relative;
+  height: 180vh;
+  height: 180svh;
+}
+
+.story__stage {
+  position: sticky;
+  top: 0;
+  height: 100vh;
+  height: 100svh;
+  overflow: hidden;
+}
+
+.hero-logo,
+.orbit-scene {
+  position: absolute;
+  inset: 0;
+}
+
+.hero-logo {
+  z-index: 2;
+  display: grid;
+  place-items: center;
+  padding: 96px 20px 54px;
+  opacity: clamp(0, calc(1 - var(--scroll-progress) * 5), 1);
+  filter: blur(calc(var(--scroll-progress) * 5px));
+  transform: translateY(calc(var(--scroll-progress) * -18px));
+  pointer-events: none;
+}
+
+.hero-logo img {
+  display: block;
+  width: min(720px, 92vw);
+  height: auto;
+  animation: logo-reveal 1.15s cubic-bezier(0.22, 1, 0.36, 1) 100ms both;
+}
+
+@keyframes logo-reveal {
+  from {
+    opacity: 0;
+    filter: blur(5px);
+    transform: translateY(8px) scale(0.99);
+  }
+
+  to {
+    opacity: 1;
+    filter: blur(0);
+    transform: translateY(0) scale(1);
+  }
+}
+
+.orbit-scene {
+  z-index: 1;
+  opacity: clamp(0, calc((var(--scroll-progress) - 0.04) * 12), 1);
+}
+
+.orbit-scene__origin {
+  position: absolute;
+  z-index: 3;
+  top: 58%;
+  left: 8vw;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #b89652;
+  box-shadow: 0 0 0 6px rgba(184, 150, 82, 0.06);
+  opacity: clamp(0, calc(var(--orbit-progress) * 6), 1);
+  transform: scale(clamp(0.5, var(--orbit-progress), 1));
+}
+
+.orbit-reveal {
+  position: absolute;
+  top: 13%;
+  left: 7.8vw;
+  width: 64vw;
+  height: 54vh;
+  overflow: hidden;
+  clip-path: inset(0 calc((1 - var(--orbit-progress)) * 100%) 0 0);
+}
+
+.orbit-reveal--dashed {
+  top: 20%;
+  left: 9vw;
+  width: 59vw;
+}
+
+.orbit-line {
+  position: absolute;
+  top: 24%;
+  left: -5%;
+  display: block;
+  width: 110%;
+  height: 88%;
+  border-radius: 50%;
+  transform: rotate(-10deg);
+  transform-origin: left center;
+}
+
+.orbit-line--solid {
+  border-top: 1px solid rgba(78, 88, 98, 0.48);
+}
+
+.orbit-line--dashed {
+  border-top: 1px dashed rgba(78, 88, 98, 0.34);
+  transform: rotate(-4deg);
+}
+
+.brand-copy {
+  position: absolute;
+  z-index: 4;
+  top: 50%;
+  right: clamp(32px, 8vw, 128px);
+  width: min(38vw, 560px);
+  opacity: var(--copy-progress);
+  transform: translateY(calc(-50% + (1 - var(--copy-progress)) * 18px));
+  text-align: left;
+}
+
+.brand-copy__eyebrow,
+.brand-copy h1,
+.brand-copy__statement {
+  margin: 0;
+  font-weight: 300;
+}
+
+.brand-copy__eyebrow {
+  margin-bottom: 14px;
+  color: rgba(78, 88, 98, 0.62);
+  font-size: 10px;
+  letter-spacing: 0.34em;
+}
+
+.brand-copy h1 {
+  color: #0b0c10;
+  font-size: clamp(34px, 4.6vw, 66px);
+  line-height: 1.1;
+  letter-spacing: 0.28em;
+  white-space: nowrap;
+}
+
+.brand-copy--english h1 {
+  font-size: clamp(30px, 3.8vw, 56px);
+  font-weight: 200;
+  letter-spacing: 0.055em;
+}
+
+.brand-copy__rule {
+  display: block;
+  width: calc(var(--copy-progress) * 100%);
+  height: 1px;
+  margin: 24px 0 20px;
+  background: rgba(78, 88, 98, 0.2);
+}
+
+.brand-copy__statement {
+  max-width: 520px;
+  margin-top: clamp(38px, 6vh, 72px);
+  color: rgba(11, 12, 16, 0.78);
+  font-size: clamp(17px, 1.7vw, 24px);
+  line-height: 1.8;
+  letter-spacing: 0.08em;
+}
+
+.brand-copy--english .brand-copy__statement {
+  letter-spacing: 0.025em;
+}
+
+.scroll-cue {
+  position: absolute;
+  z-index: 5;
+  bottom: 28px;
+  left: 50%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  color: rgba(78, 88, 98, 0.42);
+  opacity: clamp(0, calc(1 - var(--scroll-progress) * 8), 1);
+  transform: translateX(-50%);
+}
+
+.scroll-cue span {
+  width: 1px;
+  height: 22px;
+  background: currentColor;
+  animation: scroll-pulse 1.8s ease-in-out infinite;
+}
+
+.scroll-cue small {
+  font-size: 8px;
+  letter-spacing: 0.3em;
+}
+
+@keyframes scroll-pulse {
+  0%,
+  100% {
+    opacity: 0.35;
+    transform: scaleY(0.55);
+    transform-origin: top;
+  }
+
+  50% {
+    opacity: 1;
+    transform: scaleY(1);
+  }
+}
+
+.footer {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  min-height: 70vh;
+  min-height: 70svh;
+  padding: 56px 24px max(32px, env(safe-area-inset-bottom));
+  color: rgba(11, 12, 16, 0.46);
+  text-align: center;
+}
+
+.footer p {
+  margin: 0;
+  font-size: 11px;
+  font-weight: 300;
+  line-height: 1.8;
+  letter-spacing: 0.05em;
+}
+
+.footer__filing {
+  margin-top: 8px !important;
+  opacity: 0.72;
+}
+
+@media (max-width: 820px) {
+  .header {
+    min-height: 64px;
+    padding: 8px 16px;
+  }
+
+  .header__brand {
+    width: 112px;
+  }
+
+  .header__right {
+    gap: 14px;
+  }
+
+  .nav {
+    gap: 14px;
+  }
+
+  .nav__item {
+    font-size: 10px;
+    letter-spacing: 0.06em;
+  }
+
+  .language {
+    min-width: 42px;
+  }
+
+  .notice {
+    top: 78px;
+  }
+
+  .hero-logo img {
+    width: min(620px, 96vw);
+  }
+
+  .orbit-scene__origin {
+    top: 41%;
+    left: 6vw;
+  }
+
+  .orbit-reveal {
+    top: 8%;
+    left: 5.5vw;
+    width: 90vw;
+    height: 42vh;
+  }
+
+  .orbit-reveal--dashed {
+    top: 13%;
+    left: 7vw;
+    width: 86vw;
+  }
+
+  .brand-copy {
+    top: auto;
+    right: 24px;
+    bottom: 12vh;
+    left: 24px;
+    width: auto;
+    transform: translateY(calc((1 - var(--copy-progress)) * 18px));
+    text-align: center;
+  }
+
+  .brand-copy h1 {
+    font-size: clamp(32px, 10vw, 48px);
+  }
+
+  .brand-copy__rule {
+    margin-right: auto;
+    margin-left: auto;
+  }
+
+  .brand-copy__statement {
+    margin-right: auto;
+    margin-left: auto;
+  }
+}
+
+@media (max-width: 600px) {
+  .header__brand {
+    width: 96px;
+  }
+
+  .nav {
+    display: none;
+  }
+
+  .brand-copy__statement {
+    margin-top: 30px;
+    font-size: 16px;
+  }
+
+  .footer {
+    padding-right: 20px;
+    padding-left: 20px;
+  }
+}
+
+@media (prefers-reduced-motion: reduce) {
+  html {
+    scroll-behavior: auto;
+  }
+
+  .hero-logo img,
+  .scroll-cue span {
+    animation: none;
+  }
+}

+ 10 - 0
src/main.jsx

@@ -0,0 +1,10 @@
+import { StrictMode } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+import App from "./App";
+
+createRoot(document.getElementById("root")).render(
+  <StrictMode>
+    <App />
+  </StrictMode>
+);

+ 6 - 0
vite.config.js

@@ -0,0 +1,6 @@
+import { defineConfig } from "vite";
+import react from "@vitejs/plugin-react";
+
+export default defineConfig({
+  plugins: [react()],
+});

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff