Compare commits

...

32 Commits

Author SHA1 Message Date
john a95681ea3a fix: treat pending Gitea CI as acceptable on synced main
Memind CI / Test, build, and release guards (push) Successful in 1m39s
Normal Portal releases should not block when commit status stays pending
after the candidate is already pushed to origin/main.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:36:40 +08:00
john 82b74d71ad fix: relax REL release gate checks for normal Portal publishes
Memind CI / Test, build, and release guards (push) Failing after 1m35s
Ignore local .runtime build output in REL-01, accept missing Gitea CI when
origin/main matches HEAD, and compare two clean runtime rebuilds for REL-03.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:26:01 +08:00
john 90ee4c38d5 fix: honor stored Rybbit analytics secret fallback
Memind CI / Test, build, and release guards (push) Successful in 1m37s
2026-07-27 22:14:06 +08:00
john c7718c683b fix: inject Rybbit tracking on m.tkmind.cn publication pages
Memind CI / Test, build, and release guards (push) Successful in 1m36s
Publication routes and share shells were missing the Rybbit tracker, and
bootstrap now reuses the stored MindSpace analytics secret when Rybbit env
secret is absent.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 22:10:07 +08:00
john 48c61a3279 fix: harden long conversation continuity
Memind CI / Test, build, and release guards (push) Successful in 1m28s
2026-07-27 21:39:34 +08:00
john 5ae166fc44 fix: exclude page actions from long images 2026-07-27 21:21:54 +08:00
john d1be5d9e2a fix: preserve replies after session recovery 2026-07-27 19:59:09 +08:00
john 17c59fde71 fix: isolate visual tool failures from agent runs 2026-07-27 19:20:23 +08:00
john 7002007128 feat: add public page view action 2026-07-27 18:35:29 +08:00
john 77be18d340 Merge branch 'codex/active-development-20260727'
Memind CI / Test, build, and release guards (push) Successful in 1m48s
2026-07-27 18:07:48 +08:00
john 6878827224 mindspace: decouple page list sync latency 2026-07-27 18:07:16 +08:00
john 40dcdaf49f mindspace: guard split-service contract drift 2026-07-27 17:48:34 +08:00
john 70264a4c4c mindspace: version runtime contracts and audit packages 2026-07-27 17:43:38 +08:00
john e94052ff24 mindspace: close authority boundaries 2026-07-27 15:34:35 +08:00
john dfab78c75a feat(workflow): add risk-based release gates 2026-07-27 10:38:24 +08:00
john c88623855f fix(dev): stop stale frontend when portal exits 2026-07-27 10:28:12 +08:00
john 6b479c16f9 test: make thumbnail refresh assertion portable
Memind CI / Test, build, and release guards (push) Successful in 1m50s
2026-07-27 09:43:43 +08:00
john 291178d2c3 chore: stop tracking generated runtime artifacts
Memind CI / Test, build, and release guards (push) Failing after 9m20s
2026-07-27 09:08:55 +08:00
john 56ff170a60 fix(chat): recover session list from snapshots 2026-07-27 09:08:55 +08:00
john 32da0c0583 fix(api): retry transient session reads safely 2026-07-27 09:08:54 +08:00
john 0708d56d61 docs: record obsolete branch disposition 2026-07-27 09:08:54 +08:00
john 5ca6ed9094 fix(release): allow stable fallback for release-only artifact promotions
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:43:00 +08:00
john cb38225704 fix(release): correct ahead/behind parsing for REL-02
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:40:15 +08:00
john 6e25008df2 fix(release): allow REL-02 when main is ahead with successful CI evidence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:38:09 +08:00
john a6a9bb4eab feat(release): add incremental gate and auto CI status for 103 publish
When the runtime artifact is unchanged, carry forward prior gate results and
re-run only REL scenarios; resolve REL-02 from Gitea commit status and allow
stable promotion when canary matches the same artifact bundle.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 08:32:12 +08:00
tkmind 9e2aa4f71d Merge pull request 'fix(release): harden stable promotion after canary acceptance' (#35) from codex/fix-stable-promotion-release into main
Memind CI / Test, build, and release guards (push) Successful in 2m5s
2026-07-27 00:04:28 +00:00
john 41bf775c4c fix(release): harden stable promotion after canary acceptance
Memind CI / Test, build, and release guards (pull_request) Successful in 13m26s
Verify matching 103 canary evidence before 8081 promotion, fix agent-run
drain JSON parsing, tolerate macOS full-backup tar races, and extend goosed
remount health waits for non-interactive SSH releases.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-27 07:09:27 +08:00
tkmind 67bf2c2bbb Merge pull request 'fix: align DeepSeek canary tool rounds with gate' (#34)
Memind CI / Test, build, and release guards (push) Successful in 2h57m1s
Merge production canary DeepSeek tool-round compatibility lifecycle and regression coverage.
2026-07-26 16:25:24 +00:00
john fb6865638a fix: align DeepSeek canary tool rounds with gate
Memind CI / Test, build, and release guards (pull_request) Successful in 1m32s
2026-07-26 23:49:32 +08:00
john 046704816f fix: use container MCP path in portal canary
Memind CI / Test, build, and release guards (push) Successful in 1m41s
2026-07-26 22:34:58 +08:00
john 02f2dc6b17 fix: preserve canary release stdin across edge checks
Memind CI / Test, build, and release guards (push) Successful in 1m35s
2026-07-26 21:24:23 +08:00
john 473ad80882 fix: route portal canary through isolated edge tunnel
Memind CI / Test, build, and release guards (push) Has been cancelled
2026-07-26 20:30:56 +08:00
359 changed files with 19117 additions and 61718 deletions
-63
View File
@@ -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
-151
View File
@@ -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;
}
File diff suppressed because it is too large Load Diff
-22
View File
@@ -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
Binary file not shown.

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&amp;fit=crop&amp;w=200&amp;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&amp;fit=crop&amp;w=200&amp;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>
-159
View File
@@ -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>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 555 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 292 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 672 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 723 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 190 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 376 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

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