Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b553107817 | |||
| 065b1588b7 | |||
| 7eedda092d | |||
| 6307f974cf | |||
| 491a62aed6 | |||
| e1ad094f93 | |||
| b10f0a7600 | |||
| 31cfdf7a94 | |||
| 9b68dc6b77 | |||
| 7f9453433c | |||
| 5a13950bc3 | |||
| 7a381db132 | |||
| 864197162d | |||
| 8cd85a9b11 | |||
| 23dedf1264 | |||
| 1e91195853 | |||
| 6070ec209a | |||
| 8bfb7959fc | |||
| bc20e93893 | |||
| 0630157068 | |||
| 7ba63a83b1 | |||
| 3de116d3e7 | |||
| aff6ddf9d3 | |||
| a95681ea3a | |||
| 82b74d71ad | |||
| 90ee4c38d5 | |||
| c7718c683b | |||
| 48c61a3279 | |||
| 5ae166fc44 | |||
| d1be5d9e2a | |||
| 17c59fde71 | |||
| 7002007128 | |||
| 77be18d340 | |||
| 6878827224 | |||
| 40dcdaf49f | |||
| 70264a4c4c | |||
| e94052ff24 | |||
| dfab78c75a | |||
| c88623855f | |||
| 6b479c16f9 | |||
| 291178d2c3 | |||
| 56ff170a60 | |||
| 32da0c0583 | |||
| 0708d56d61 | |||
| 5ca6ed9094 | |||
| cb38225704 | |||
| 6e25008df2 | |||
| a6a9bb4eab | |||
| 9e2aa4f71d | |||
| 41bf775c4c | |||
| 67bf2c2bbb | |||
| fb6865638a | |||
| 046704816f |
@@ -319,6 +319,11 @@ VITE_TKMIND_WORKING_DIR=/Users/john/PycharmProjects/tkmind
|
|||||||
# IMAGE_MAKE_SEMANTIC_REVIEW_MAX_ATTEMPTS=3
|
# IMAGE_MAKE_SEMANTIC_REVIEW_MAX_ATTEMPTS=3
|
||||||
# H5_WECHAT_MP_FRESH_PAGE_THUMBNAILS=1
|
# H5_WECHAT_MP_FRESH_PAGE_THUMBNAILS=1
|
||||||
# H5_WECHAT_MP_FRESH_THUMBNAIL_REPAIR=0
|
# H5_WECHAT_MP_FRESH_THUMBNAIL_REPAIR=0
|
||||||
|
# 微信 Page Data 独立 Aider 审核;默认关闭,灰度名单支持 userId/username/slug/displayName/nickname 或 *。
|
||||||
|
# 该链路不创建 h5_agent_runs,审核收据写入用户工作区 .memind/page-data-reviews/。
|
||||||
|
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_ENABLED=0
|
||||||
|
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_USERS=
|
||||||
|
# H5_WECHAT_MP_PAGE_DATA_AIDER_REVIEW_TIMEOUT_MS=900000
|
||||||
# Sandbox MCP 调用 Portal 内部生图入口;容器内通常使用 host.docker.internal。
|
# Sandbox MCP 调用 Portal 内部生图入口;容器内通常使用 host.docker.internal。
|
||||||
# MINDSPACE_AGENT_API_BASE_URL=http://127.0.0.1:8081/api
|
# MINDSPACE_AGENT_API_BASE_URL=http://127.0.0.1:8081/api
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
Portal runtime artifact
|
|
||||||
|
|
||||||
This directory is meant to run on production without the source tree.
|
|
||||||
Required persisted items to inherit on the host:
|
|
||||||
.env
|
|
||||||
MindSpace/
|
|
||||||
data/
|
|
||||||
users/
|
|
||||||
.tailscale/
|
|
||||||
public/plaza-covers/
|
|
||||||
logs/
|
|
||||||
|
|
||||||
Bundled alongside server.mjs (required for sandbox-fs MCP):
|
|
||||||
mindspace-sandbox-mcp.mjs (esbuild bundle; includes schedule-service deps)
|
|
||||||
|
|
||||||
Post-deploy validation (scripts/check-mindspace-public-links.mjs):
|
|
||||||
Scans MindSpace/*/public/*.html for missing relative href/src/cover targets.
|
|
||||||
|
|
||||||
Runtime install mode: bundle-node-modules
|
|
||||||
Bundle target: node24
|
|
||||||
|
|
||||||
Platform agent skills (synced into user workspaces):
|
|
||||||
skills/
|
|
||||||
Key runtime differences must stay in .env, not in the artifact:
|
|
||||||
DATABASE_URL / MYSQL_*
|
|
||||||
H5_PUBLIC_BASE_URL
|
|
||||||
TKMIND_API_TARGETS / TKMIND_API_TARGET / TKMIND_API_TARGET_1
|
|
||||||
H5_USERS_ROOT / MINDSPACE_STORAGE_ROOT / MEMIND_SHARED_PUBLISH_ROOT
|
|
||||||
|
|
||||||
Deployment and operations transport:
|
|
||||||
H5 public domain: mm.tkmind.cn
|
|
||||||
Current public path: mm.tkmind.cn -> local nginx -> Portal :8081
|
|
||||||
Legacy rollback-only path: m.tkmind.cn -> 105 nginx -> reverse SSH tunnel -> Portal :8081
|
|
||||||
Future H5 traffic must not depend on 105 forwarding unless explicitly rolling back.
|
|
||||||
|
|
||||||
Streaming runtime operations:
|
|
||||||
node scripts/check-stream-runtime.mjs
|
|
||||||
node scripts/runtime-worker-drain.mjs status
|
|
||||||
node scripts/runtime-worker-drain.mjs reconcile
|
|
||||||
node scripts/runtime-worker-drain.mjs reconcile --apply
|
|
||||||
node scripts/runtime-worker-metrics.mjs status
|
|
||||||
node scripts/runtime-worker-metrics.mjs sample
|
|
||||||
bash scripts/install-runtime-metrics-agent.sh
|
|
||||||
node scripts/runtime-worker-heartbeat.mjs once
|
|
||||||
node scripts/runtime-worker-heartbeat.mjs serve
|
|
||||||
bash scripts/install-runtime-heartbeat-agent.sh
|
|
||||||
node scripts/runtime-slo-report.mjs
|
|
||||||
node scripts/runtime-slo-report.mjs --write-report
|
|
||||||
node scripts/runtime-slo-report.mjs --write-report --prune --retention-days 30
|
|
||||||
bash scripts/install-runtime-slo-report-agent.sh
|
|
||||||
bash scripts/install-runtime-slo-soak-agent.sh
|
|
||||||
node scripts/runtime-worker-drain.mjs drain goosed-3
|
|
||||||
node scripts/runtime-worker-drain.mjs undrain goosed-3
|
|
||||||
node scripts/check-tool-runtime.mjs
|
|
||||||
node scripts/check-agent-code-run-entry.mjs
|
|
||||||
curl -sk https://mm.tkmind.cn/api/runtime/status # includes toolRuntime.queue
|
|
||||||
node scripts/agent-run-worker.mjs --status
|
|
||||||
node scripts/agent-run-worker.mjs --once
|
|
||||||
bash scripts/install-agent-run-worker-agent.sh # installs disabled by default
|
|
||||||
node scripts/check-agent-run-worker.mjs # read-only LaunchAgent/queue check
|
|
||||||
node scripts/agent-run-guard.mjs # dry-run auto-pause guard check
|
|
||||||
node scripts/agent-run-guard.mjs --apply # stop worker and disable code-run gate when thresholds trip
|
|
||||||
bash scripts/install-agent-run-guard-agent.sh # installs guard LaunchAgent
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
import fs from 'node:fs';
|
|
||||||
import path from 'node:path';
|
|
||||||
|
|
||||||
const SKIP_REFERENCE = /^(?:https?:|mailto:|javascript:|data:|tel:|#|\/\/)/i;
|
|
||||||
const DOWNLOAD_EXTENSIONS = /\.(?:docx|doc|pdf|xlsx|xls|pptx|ppt|zip|rar|7z|csv)$/i;
|
|
||||||
|
|
||||||
export function isFilesystemPublicReference(raw) {
|
|
||||||
const value = String(raw ?? '').trim();
|
|
||||||
if (!value) return false;
|
|
||||||
if (SKIP_REFERENCE.test(value)) return false;
|
|
||||||
// /api/... and /MindSpace/... are server routes, not on-disk siblings of public HTML.
|
|
||||||
if (value.startsWith('/')) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @deprecated use isFilesystemPublicReference */
|
|
||||||
export function isRelativePublicReference(raw) {
|
|
||||||
return isFilesystemPublicReference(raw);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isDownloadLikeReference(raw, source = '') {
|
|
||||||
if (!isFilesystemPublicReference(raw)) return false;
|
|
||||||
const ref = normalizeReferencePath(raw);
|
|
||||||
if (DOWNLOAD_EXTENSIONS.test(ref)) return true;
|
|
||||||
return /\bdownload\b/i.test(String(source ?? ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function normalizeReferencePath(raw) {
|
|
||||||
const trimmed = String(raw ?? '')
|
|
||||||
.trim()
|
|
||||||
.split('#')[0]
|
|
||||||
.split('?')[0]
|
|
||||||
.trim();
|
|
||||||
if (!trimmed) return '';
|
|
||||||
try {
|
|
||||||
return decodeURIComponent(trimmed);
|
|
||||||
} catch {
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveReferencePath(htmlDir, raw) {
|
|
||||||
const ref = normalizeReferencePath(raw);
|
|
||||||
if (!ref) return null;
|
|
||||||
return path.resolve(htmlDir, ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseMindspaceCoverPaths(contentAttr) {
|
|
||||||
const paths = [];
|
|
||||||
if (!contentAttr) return paths;
|
|
||||||
let parsed = null;
|
|
||||||
try {
|
|
||||||
parsed = JSON.parse(contentAttr);
|
|
||||||
} catch {
|
|
||||||
for (const key of ['cover', 'image']) {
|
|
||||||
const match = contentAttr.match(new RegExp(`"${key}"\\s*:\\s*"([^"]+)"`));
|
|
||||||
if (match?.[1]) paths.push(match[1]);
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
for (const key of ['cover', 'image']) {
|
|
||||||
if (parsed?.[key]) paths.push(String(parsed[key]));
|
|
||||||
}
|
|
||||||
return paths;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function collectPublicHtmlReferences(html, htmlDir, { downloadsOnly = false } = {}) {
|
|
||||||
const refs = [];
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
const add = (raw, source) => {
|
|
||||||
if (downloadsOnly && !isDownloadLikeReference(raw, source)) return;
|
|
||||||
if (!downloadsOnly && !isFilesystemPublicReference(raw)) return;
|
|
||||||
const ref = normalizeReferencePath(raw);
|
|
||||||
if (!ref) return;
|
|
||||||
const key = `${source}\0${ref}`;
|
|
||||||
if (seen.has(key)) return;
|
|
||||||
seen.add(key);
|
|
||||||
refs.push({
|
|
||||||
ref,
|
|
||||||
source,
|
|
||||||
resolvedPath: resolveReferencePath(htmlDir, ref),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const match of String(html ?? '').matchAll(/\b(?:href|src)\s*=\s*["']([^"']+)["']/gi)) {
|
|
||||||
add(match[1], match[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!downloadsOnly) {
|
|
||||||
for (const match of String(html ?? '').matchAll(
|
|
||||||
/<meta\b[^>]*\bname\s*=\s*["']mindspace-cover["'][^>]*>/gi,
|
|
||||||
)) {
|
|
||||||
const tag = match[0];
|
|
||||||
const contentMatch =
|
|
||||||
tag.match(/\bcontent\s*=\s*"([^"]*)"/i) ?? tag.match(/\bcontent\s*=\s*'([^']*)'/i);
|
|
||||||
for (const coverPath of parseMindspaceCoverPaths(contentMatch?.[1])) {
|
|
||||||
add(coverPath, 'mindspace-cover');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return refs;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function findMissingPublicHtmlReferences(htmlPath, html = null, options = {}) {
|
|
||||||
const resolvedHtmlPath = path.resolve(htmlPath);
|
|
||||||
const content = html ?? fs.readFileSync(resolvedHtmlPath, 'utf8');
|
|
||||||
const htmlDir = path.dirname(resolvedHtmlPath);
|
|
||||||
const missing = [];
|
|
||||||
const seen = new Set();
|
|
||||||
|
|
||||||
for (const item of collectPublicHtmlReferences(content, htmlDir, options)) {
|
|
||||||
const key = item.ref;
|
|
||||||
if (seen.has(key)) continue;
|
|
||||||
seen.add(key);
|
|
||||||
if (!item.resolvedPath || !fs.existsSync(item.resolvedPath)) {
|
|
||||||
missing.push({ htmlPath: resolvedHtmlPath, ...item });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return missing;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function listPublicHtmlFiles(publishRoot, { userId = null } = {}) {
|
|
||||||
const root = path.resolve(publishRoot);
|
|
||||||
if (!fs.existsSync(root)) return [];
|
|
||||||
|
|
||||||
const userDirs = userId
|
|
||||||
? [path.join(root, userId)]
|
|
||||||
: fs.readdirSync(root, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => path.join(root, d.name));
|
|
||||||
|
|
||||||
const files = [];
|
|
||||||
for (const userDir of userDirs) {
|
|
||||||
const publicDir = path.join(userDir, 'public');
|
|
||||||
if (!fs.existsSync(publicDir) || !fs.statSync(publicDir).isDirectory()) continue;
|
|
||||||
for (const name of fs.readdirSync(publicDir)) {
|
|
||||||
if (!name.toLowerCase().endsWith('.html')) continue;
|
|
||||||
files.push(path.join(publicDir, name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return files.sort();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function scanPublicHtmlLinks(publishRoot, options = {}) {
|
|
||||||
const issues = [];
|
|
||||||
for (const htmlPath of listPublicHtmlFiles(publishRoot, options)) {
|
|
||||||
issues.push(...findMissingPublicHtmlReferences(htmlPath, null, options));
|
|
||||||
}
|
|
||||||
return issues;
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "tkmind-h5-portal-runtime",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=22"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@node-rs/argon2": "^2.0.2",
|
|
||||||
"@resvg/resvg-js": "^2.6.2",
|
|
||||||
"debug": "^4.4.3",
|
|
||||||
"express": "^4.21.2",
|
|
||||||
"http-proxy-middleware": "^3.0.3",
|
|
||||||
"jsonrepair": "^3.14.0",
|
|
||||||
"mysql2": "^3.22.5",
|
|
||||||
"playwright": "^1.61.1",
|
|
||||||
"qrcode": "^1.5.4",
|
|
||||||
"redis": "^4.7.1",
|
|
||||||
"sharp": "^0.35.2",
|
|
||||||
"undici": "^6.26.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
nXjhx0ErC6MhQ0SZ
|
|
||||||
|
Before Width: | Height: | Size: 61 KiB |
@@ -1,50 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>微信分享预览 · 连云港三天两夜攻略 🌊</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
body { margin: 0; min-height: 100vh; padding: 24px; background: #ececec; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif; color: #111; }
|
|
||||||
.wrap { max-width: 420px; margin: 0 auto; }
|
|
||||||
h1 { margin: 0 0 8px; font-size: 18px; }
|
|
||||||
.hint, .note { margin: 0 0 16px; color: #666; font-size: 13px; line-height: 1.6; }
|
|
||||||
.card { display: grid; grid-template-columns: 1fr 72px; gap: 12px; padding: 12px; border-radius: 8px; background: #fff; box-shadow: 0 1px 0 rgba(0,0,0,.06); }
|
|
||||||
.card-main { min-width: 0; }
|
|
||||||
.card-title { margin: 0; font-size: 16px; font-weight: 600; line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
||||||
.card-desc { margin: 6px 0 0; color: #888; font-size: 13px; line-height: 1.45; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
|
|
||||||
.card-foot { display: flex; align-items: center; gap: 6px; margin-top: 10px; color: #999; font-size: 12px; }
|
|
||||||
.card-foot img { width: 16px; height: 16px; border-radius: 999px; object-fit: cover; }
|
|
||||||
.card-thumb { width: 72px; height: 72px; border-radius: 4px; object-fit: cover; background: #f3f3f3; }
|
|
||||||
.card-thumb-empty { display: grid; place-items: center; color: #bbb; font-size: 12px; }
|
|
||||||
.meta { margin-top: 18px; padding: 14px; border-radius: 10px; background: rgba(255,255,255,.72); font-size: 12px; line-height: 1.7; word-break: break-all; }
|
|
||||||
code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="wrap">
|
|
||||||
<h1>微信链接卡片预览</h1>
|
|
||||||
<p class="hint">本地模拟的是「粘贴链接后看到的卡片」,不是 JS-SDK 内部分享弹层。源链接:<code>https://m.tkmind.cn/MindSpace/john/public/lianyungang-guide.html</code></p>
|
|
||||||
<p class="note">修复后:标题 + 描述 + 缩略图 + 底部「TKMind 智趣」小图标应同时出现。</p>
|
|
||||||
<article class="card" aria-label="微信分享卡片预览">
|
|
||||||
<div class="card-main">
|
|
||||||
<h2 class="card-title">连云港三天两夜攻略 🌊</h2>
|
|
||||||
<p class="card-desc">三天两夜·沙滩酒店·必吃美食</p>
|
|
||||||
<div class="card-foot">
|
|
||||||
<img src="http://127.0.0.1:8081/brand/tkmind-icon.png" alt="" onerror="this.style.display='none'">
|
|
||||||
<span>TKMind 智趣</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<img class="card-thumb" src="https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=200&q=80" alt="">
|
|
||||||
</article>
|
|
||||||
<div class="meta">
|
|
||||||
<div><strong>og:title</strong> 连云港三天两夜攻略 🌊</div>
|
|
||||||
<div><strong>og:description</strong> 三天两夜·沙滩酒店·必吃美食</div>
|
|
||||||
<div><strong>og:site_name</strong> TKMind 智趣</div>
|
|
||||||
<div><strong>og:image</strong> https://images.unsplash.com/photo-1507525428034-b723cf961d3e?auto=format&fit=crop&w=200&q=80</div>
|
|
||||||
<div><strong>icon</strong> /brand/tkmind-icon.png</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<title>微信分享预览工具</title>
|
|
||||||
<style>
|
|
||||||
* { box-sizing: border-box; }
|
|
||||||
body { margin: 0; min-height: 100vh; padding: 24px; background: #f3f3f3; font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, sans-serif; color: #111; }
|
|
||||||
.panel { max-width: 560px; margin: 0 auto; padding: 20px; border-radius: 16px; background: #fff; box-shadow: 0 8px 30px rgba(0,0,0,.06); }
|
|
||||||
h1 { margin: 0 0 8px; font-size: 22px; }
|
|
||||||
p { margin: 0 0 16px; color: #666; line-height: 1.6; font-size: 14px; }
|
|
||||||
label { display: block; margin-bottom: 8px; font-size: 13px; font-weight: 600; }
|
|
||||||
input { width: 100%; padding: 12px 14px; border: 1px solid #ddd; border-radius: 10px; font: inherit; }
|
|
||||||
button { margin-top: 14px; width: 100%; padding: 12px 14px; border: 0; border-radius: 10px; background: #2f6f57; color: #fff; font: inherit; font-weight: 700; cursor: pointer; }
|
|
||||||
code { background: rgba(0,0,0,.06); padding: 1px 4px; border-radius: 4px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="panel">
|
|
||||||
<h1>微信分享预览</h1>
|
|
||||||
<p>输入已发布页面路径,本地模拟微信「粘贴链接后」看到的卡片。开发环境走 Portal API:<code>/dev/wechat-share-preview</code></p>
|
|
||||||
<form id="preview-form">
|
|
||||||
<label for="url">页面 URL 或路径</label>
|
|
||||||
<input id="url" name="url" placeholder="/MindSpace/john/public/demo.html" required>
|
|
||||||
<button type="submit">生成预览</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
document.getElementById('preview-form').addEventListener('submit', (event) => {
|
|
||||||
event.preventDefault();
|
|
||||||
const value = document.getElementById('url').value.trim();
|
|
||||||
if (!value) return;
|
|
||||||
const target = value.startsWith('http')
|
|
||||||
? `/dev/wechat-share-preview?url=${encodeURIComponent(value)}`
|
|
||||||
: `/dev/wechat-share-preview?url=${encodeURIComponent(value.startsWith('/') ? value : `/${value}`)}`;
|
|
||||||
window.location.href = target;
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="description" content="John 的专属欢迎页面 - TKMind 为您呈现">
|
|
||||||
<meta name="mindspace-cover" content='{"tag":"报告","emoji":"👋","accent":"#f093fb","accent2":"#24243e","subtitle":"Hello John","cover":"https://images.unsplash.com/photo-1579547945413-497e1b99dac0?w=1200&q=80"}'>
|
|
||||||
<title>Hello, John!</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: linear-gradient(135deg, #0f0c29, #302b63, #24243e);
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 动态粒子背景 */
|
|
||||||
.bg-particles {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-particles span {
|
|
||||||
position: absolute;
|
|
||||||
width: 6px;
|
|
||||||
height: 6px;
|
|
||||||
background: rgba(255, 255, 255, 0.15);
|
|
||||||
border-radius: 50%;
|
|
||||||
animation: float 20s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-particles span:nth-child(1) { top: 10%; left: 10%; animation-duration: 18s; animation-delay: 0s; }
|
|
||||||
.bg-particles span:nth-child(2) { top: 30%; left: 80%; width: 10px; height: 10px; animation-duration: 22s; animation-delay: 2s; }
|
|
||||||
.bg-particles span:nth-child(3) { top: 70%; left: 20%; animation-duration: 16s; animation-delay: 4s; }
|
|
||||||
.bg-particles span:nth-child(4) { top: 85%; left: 85%; width: 8px; height: 8px; animation-duration: 24s; animation-delay: 1s; }
|
|
||||||
.bg-particles span:nth-child(5) { top: 50%; left: 50%; width: 4px; height: 4px; animation-duration: 20s; animation-delay: 3s; }
|
|
||||||
.bg-particles span:nth-child(6) { top: 15%; left: 60%; animation-duration: 19s; animation-delay: 5s; }
|
|
||||||
.bg-particles span:nth-child(7) { top: 60%; left: 5%; width: 7px; height: 7px; animation-duration: 21s; animation-delay: 0.5s; }
|
|
||||||
.bg-particles span:nth-child(8) { top: 40%; left: 70%; width: 5px; height: 5px; animation-duration: 17s; animation-delay: 2.5s; }
|
|
||||||
|
|
||||||
@keyframes float {
|
|
||||||
0% { transform: translateY(0) rotate(0deg); opacity: 0; }
|
|
||||||
10% { opacity: 1; }
|
|
||||||
90% { opacity: 1; }
|
|
||||||
100% { transform: translateY(-100vh) rotate(720deg); opacity: 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
text-align: center;
|
|
||||||
padding: 60px 80px;
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
border-radius: 24px;
|
|
||||||
backdrop-filter: blur(20px);
|
|
||||||
-webkit-backdrop-filter: blur(20px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
box-shadow:
|
|
||||||
0 25px 50px rgba(0, 0, 0, 0.3),
|
|
||||||
inset 0 1px 0 rgba(255, 255, 255, 0.1);
|
|
||||||
animation: fadeIn 1.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 72px;
|
|
||||||
font-weight: 700;
|
|
||||||
background: linear-gradient(135deg, #f093fb, #f5576c, #4facfe);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
animation: pulse 3s ease-in-out infinite;
|
|
||||||
letter-spacing: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 24px;
|
|
||||||
color: rgba(255, 255, 255, 0.7);
|
|
||||||
letter-spacing: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji {
|
|
||||||
font-size: 48px;
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
animation: wave 2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
margin-top: 36px;
|
|
||||||
font-size: 13px;
|
|
||||||
color: rgba(255, 255, 255, 0.3);
|
|
||||||
letter-spacing: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(30px) scale(0.95);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0) scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { transform: scale(1); }
|
|
||||||
50% { transform: scale(1.03); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes wave {
|
|
||||||
0%, 100% { transform: rotate(0deg); }
|
|
||||||
25% { transform: rotate(15deg); }
|
|
||||||
75% { transform: rotate(-15deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
|
||||||
.card {
|
|
||||||
padding: 40px 32px;
|
|
||||||
margin: 20px;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
font-size: 48px;
|
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.emoji {
|
|
||||||
font-size: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="bg-particles">
|
|
||||||
<span></span><span></span><span></span><span></span>
|
|
||||||
<span></span><span></span><span></span><span></span>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<span class="emoji">👋</span>
|
|
||||||
<h1>Hello!</h1>
|
|
||||||
<p>Welcome, John</p>
|
|
||||||
<div class="footer">powered by TKMind</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 569 KiB |
|
Before Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 555 KiB |
|
Before Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 555 KiB |
|
Before Width: | Height: | Size: 569 KiB |
|
Before Width: | Height: | Size: 292 KiB |
|
Before Width: | Height: | Size: 328 KiB |
|
Before Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 672 KiB |
|
Before Width: | Height: | Size: 723 KiB |
|
Before Width: | Height: | Size: 672 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 723 KiB |
|
Before Width: | Height: | Size: 723 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 672 KiB |
|
Before Width: | Height: | Size: 458 KiB |
|
Before Width: | Height: | Size: 152 KiB |
|
Before Width: | Height: | Size: 672 KiB |
|
Before Width: | Height: | Size: 723 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 132 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 309 KiB |
|
Before Width: | Height: | Size: 338 KiB |
|
Before Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 294 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 376 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 334 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 210 KiB |
|
Before Width: | Height: | Size: 310 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 613 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 130 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 306 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 342 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 613 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 227 KiB |
|
Before Width: | Height: | Size: 274 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 110 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 227 KiB |