initial commit: 愈心谷数字解构

This commit is contained in:
jackyu66git
2026-07-08 12:37:41 +08:00
commit 33e6dffa17
18 changed files with 3412 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
__pycache__/
*.pyc
.venv/
*.log
.env
.DS_Store
+43
View File
@@ -0,0 +1,43 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project overview
愈心谷 (YuXinGu) — a digital mental health platform. The codebase is a collection of standalone, mobile-first HTML pages with no build step, no framework, and no JavaScript dependencies.
## Running the app
```bash
python3 server.py [port] # default port 8001
```
The server serves all static files from the project root and exposes two JSON API endpoints:
- `GET /load` — returns mindmap data from `mindmap_data.json`
- `POST /save` — persists mindmap data to `mindmap_data.json` (validates JSON before writing)
## File map
| File | Purpose |
|---|---|
| `index.html` | Landing page linking to all sections |
| `yuxingu.html` | Main app shell — hero, service cards, activity center, bottom nav |
| `shuzi.html` | Digital psychology calculator — life-number triangle, bagua, liuren, zodiac, wuxing modules |
| `mindmap.html` | Canvas-based mind-map editor with multi-map management |
| `yangsheng.html` | AI Eastern wellness — bazi/wuxing constitution analysis, weekly/daily reports |
| `manual.html` | Static "life manual" poster with pyramid triangle charts rendered via inline SVG |
| `yuxingu_v1.html` | Earlier iteration of the main page |
| `server.py` | Minimal HTTP server (Python stdlib, no dependencies) |
## Architecture notes
- **Every HTML file is self-contained.** All CSS and JS are embedded in `<style>` / `<script>` tags. There are no shared `.js` or `.css` files.
- **Mobile-first design** with `max-width: 430px500px` containers, `user-scalable=no`, and `env(safe-area-inset-bottom)` for notched phones.
- **CSS custom properties** define the brand palette: `--pri: #E54D42` (primary red), with per-page accent colors (gold, blue, green, orange).
- **Emoji are deliberately avoided** in the main UI — icons use CSS shapes, Unicode text symbols (△, ☯, ◈), or inline SVG.
- **`mindmap.html`** uses a `<canvas>` for rendering with hand-rolled hit-testing, drag-to-pan, and inline text editing. It persists maps to both `localStorage` (client-side key `yuxingu_maps`) and the server via `/save` / `/load`.
- **`shuzi.html`** contains a full numerology engine in embedded JS — digit reduction, triangle calculation, joint codes, missing-number detection — plus tabbed panels for 梅花易数, 小六壬, 大六壬, 奇门遁甲, 星座, and 五行八卦 (content rendered dynamically).
- **`yangsheng.html`** computes bazi (八字), five elements (五行), organ-meridian mapping, and wuyun-liuqi (五运六气) from a birth date. The weekly/daily panels use the current date to generate time-appropriate health advice.
- The server is a thin wrapper around `http.server.SimpleHTTPRequestHandler`. It disables caching for HTML/JSON responses and adds the `/save` + `/load` routes. No database, no auth.
- Target audience is Chinese-speaking; all UI text is Chinese (zh-CN).
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

+69
View File
@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷</title>
<style>
:root{--pri:#E54D42;--hero-start:#FF9F80;--hero-end:#FFB8A6;--bg-start:#FFD1C7;--bg-end:#FFC8B5}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 50%,#fff5f0 100%);
min-height:100vh;display:flex;flex-direction:column;align-items:center;
padding:40px 20px;-webkit-font-smoothing:antialiased;
}
.logo{margin-bottom:12px}
.logo img{height:42px}
.sub{font-size:14px;color:rgba(0,0,0,0.4);margin-bottom:40px;letter-spacing:1px}
.links{display:flex;flex-direction:column;gap:14px;width:100%;max-width:360px}
.link-card{
display:flex;align-items:center;gap:14px;padding:18px 20px;
background:#fff;border-radius:14px;text-decoration:none;
box-shadow:0 2px 10px rgba(0,0,0,0.04);transition:transform 0.15s;
}
.link-card:active{transform:scale(0.97)}
.link-card .li{width:44px;height:44px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
.link-card .lt{font-size:16px;font-weight:600;color:#333}
.link-card .ld{font-size:12px;color:#999;margin-top:2px}
.c1 .li{background:#FFECEC;color:var(--pri)}
.c2 .li{background:#E6F0FF;color:#4A90E2}
.c3 .li{background:#E6FFE8;color:#5CB85C}
.c4 .li{background:#FFF8E6;color:#E8985A}
</style>
</head>
<body>
<div class="logo"><img src="logo.png" alt="愈心脊"></div>
<p class="sub">专业的心理健康服务平台</p>
<div class="links">
<a href="yuxingu.html" class="link-card c1">
<div class="li">🏠</div>
<div><div class="lt">愈心谷首页</div><div class="ld">心理健康服务 · 愈心魔方</div></div>
</a>
<a href="shuzi.html" class="link-card c2">
<div class="li"></div>
<div><div class="lt">数字心理学</div><div class="ld">生命数字三角解构</div></div>
</a>
<a href="mindmap.html" class="link-card c3">
<div class="li">🧠</div>
<div><div class="lt">愈心脑图</div><div class="ld">可视化编辑 · 愈心谷架构</div></div>
</a>
<a href="yangsheng.html" class="link-card c4">
<div class="li">☯️</div>
<div><div class="lt">AI东方养生</div><div class="ld">易经 × 内经 · 体质分析</div></div>
</a>
</div>
</body>
</html>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+332
View File
@@ -0,0 +1,332 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>LINK HMM Regime — K线</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{background:#0d1117;color:#c9d1d9;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;overflow:hidden;touch-action:none}
#topbar{background:#161b22;padding:10px 16px;display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid #30363d;flex-wrap:wrap;gap:6px}
#topbar h1{font-size:14px;font-weight:600;color:#f0f6fc}
.legend{display:flex;gap:10px;font-size:11px;flex-wrap:wrap}
.legend span{display:flex;align-items:center;gap:4px}
.legend i{display:inline-block;width:10px;height:10px;border-radius:2px}
#chart{width:100%;height:calc(100vh - 44px);display:block;cursor:crosshair}
#loading{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);color:#8b949e;font-size:14px;z-index:10}
#tooltip{position:fixed;background:#21262d;color:#c9d1d9;padding:6px 10px;border-radius:6px;font-size:12px;border:1px solid #30363d;pointer-events:none;display:none;white-space:nowrap;z-index:20}
</style>
</head>
<body>
<div id="topbar">
<h1>LINK/USD — HMM Regime (1m K线)</h1>
<div class="legend">
<span><i style="background:rgba(46,204,113,0.5);border:1px solid #2ecc71"></i> Low Spread</span>
<span><i style="background:rgba(241,196,15,0.4);border:1px solid #f1c40f"></i> Mid Spread</span>
<span><i style="background:rgba(231,76,60,0.4);border:1px solid #e74c3c"></i> High Spread</span>
</div>
</div>
<canvas id="chart"></canvas>
<div id="tooltip"></div>
<div id="loading">Loading data...</div>
<script>
(function(){
var canvas = document.getElementById('chart');
var ctx = canvas.getContext('2d');
var tt = document.getElementById('tooltip');
var RC = ['rgba(231,76,60,0.28)','rgba(241,196,15,0.18)','rgba(46,204,113,0.22)'];
var RL = ['#e74c3c','#f1c40f','#2ecc71'];
// Remap: HMM labels → spread-ordered. R0=lowest spread, R1=highest, R2=mid
// low→green(idx2), mid→yellow(idx1), high→red(idx0)
var RM = {0:2, 1:0, 2:1};
function rc(r) { return RC[RM[r]]; }
function rl(r) { return RL[RM[r]]; }
var GC = '#21262d', AC = '#8b949e', BG = '#0d1117';
var GREEN = '#26a69a', RED = '#ef5350';
var MA = {t:16,r:70,b:28,l:10};
var DATA = null;
var SIGNALS = null;
var viewStart = 0, viewEnd = 1;
var isPanning = false, panStart = 0;
var pinchDist0 = 0, viewMid0 = 0.5, viewRange0 = 1;
function init() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight - 44;
draw();
}
function draw() {
if (!DATA) return;
var W = canvas.width, H = canvas.height;
var pw = W - MA.l - MA.r, ph = H - MA.t - MA.b;
var N = DATA.length;
var i0 = Math.floor(viewStart * N);
var i1 = Math.ceil(viewEnd * N) - 1;
i0 = Math.max(0, i0); i1 = Math.min(N - 1, i1);
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = BG;
ctx.fillRect(0, 0, W, H);
// Price range
var pMin = Infinity, pMax = -Infinity;
for (var i = i0; i <= i1; i++) {
var d = DATA[i];
if (d.l < pMin) pMin = d.l;
if (d.h > pMax) pMax = d.h;
}
var pR = pMax - pMin || 0.001;
pMin -= pR * 0.05; pMax += pR * 0.05; pR = pMax - pMin;
function tX(i) { return MA.l + ((i - i0) / (i1 - i0 || 1)) * pw; }
function tY(v) { return MA.t + (1 - (v - pMin) / pR) * ph; }
// Regime background bands
var prevR = -1, bStart = i0;
for (var i = i0; i <= i1; i++) {
var r = DATA[i].r;
if (r !== prevR) {
if (prevR >= 0) {
ctx.fillStyle = rc(prevR);
ctx.fillRect(tX(bStart) - 1, MA.t, tX(i) - tX(bStart) + 2, ph);
}
bStart = i; prevR = r;
}
}
if (prevR >= 0) {
ctx.fillStyle = rc(prevR);
ctx.fillRect(tX(bStart) - 1, MA.t, tX(i1) - tX(bStart) + 2, ph);
}
// Grid + Y labels
var nY = 8;
ctx.strokeStyle = GC; ctx.lineWidth = 0.5;
for (var gy = 0; gy <= nY; gy++) {
var y = MA.t + (gy / nY) * ph;
var price = pMax - (gy / nY) * pR;
ctx.beginPath(); ctx.moveTo(MA.l, y); ctx.lineTo(MA.l + pw, y); ctx.stroke();
ctx.fillStyle = AC; ctx.font = '11px -apple-system,BlinkMacSystemFont,sans-serif';
ctx.textAlign = 'right'; ctx.fillText(price.toFixed(4), W - 5, y + 4);
}
// X labels
var nX = 6;
ctx.textAlign = 'center'; ctx.fillStyle = AC;
ctx.font = '10px -apple-system,BlinkMacSystemFont,sans-serif';
for (var gx = 0; gx <= nX; gx++) {
var idx = i0 + Math.floor((gx / nX) * (i1 - i0 + 1));
if (idx > i1) idx = i1;
var x = tX(idx);
var d = new Date(DATA[idx].t * 1000);
ctx.fillText(d.toISOString().substring(11, 19), x, H - MA.b + 16);
}
// ─── K-line candlesticks ───
var candleW = Math.max(1, (pw / (i1 - i0 + 1)) * 0.7);
var candleGap = (pw / (i1 - i0 + 1));
for (var i = i0; i <= i1; i++) {
var k = DATA[i];
var cx = tX(i);
var openY = tY(k.o), closeY = tY(k.c);
var highY = tY(k.h), lowY = tY(k.l);
var isGreen = k.c >= k.o;
// Wick
ctx.strokeStyle = isGreen ? GREEN : RED;
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(cx, highY);
ctx.lineTo(cx, lowY);
ctx.stroke();
// Body
var bodyTop = isGreen ? closeY : openY;
var bodyH = Math.max(1, Math.abs(closeY - openY));
ctx.fillStyle = isGreen ? GREEN : RED;
ctx.fillRect(cx - candleW / 2, bodyTop, candleW, bodyH);
}
// Transition markers (dashed vertical lines)
prevR = DATA[Math.max(0, i0 - 1)].r;
for (var i = i0; i <= i1; i++) {
if (DATA[i].r !== prevR) {
var mx = tX(i);
ctx.strokeStyle = rl(DATA[i].r); ctx.lineWidth = 1;
ctx.setLineDash([2, 3]);
ctx.beginPath(); ctx.moveTo(mx, MA.t); ctx.lineTo(mx, MA.t + ph); ctx.stroke();
ctx.setLineDash([]);
prevR = DATA[i].r;
}
}
// ─── Signal markers (spread surge > 95% + >2x + 3min cooldown) ───
if (SIGNALS) {
for (var si = 0; si < SIGNALS.length; si++) {
var sig = SIGNALS[si];
if (sig.idx < i0 || sig.idx > i1) continue;
var candleIdx = -1;
for (var ci = i0; ci <= i1; ci++) {
if (DATA[ci].t <= sig.ts && (ci === i1 || DATA[ci+1].t > sig.ts)) {
candleIdx = ci; break;
}
}
if (candleIdx < 0) continue;
var sx = tX(candleIdx);
var k = DATA[candleIdx];
var sy = tY(k.l) + 8; // below the candle low
// Red triangle
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx - 5, sy + 10);
ctx.lineTo(sx + 5, sy + 10);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = '#ff4444'; ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(sx - 5, sy + 10);
ctx.lineTo(sx + 5, sy + 10);
ctx.closePath();
ctx.stroke();
}
}
// Price labels at edges
ctx.fillStyle = AC; ctx.font = 'bold 11px -apple-system,BlinkMacSystemFont,sans-serif';
ctx.textAlign = 'right';
ctx.fillText(DATA[i0].c.toFixed(4), MA.l - 4, tY(DATA[i0].c) + 4);
ctx.textAlign = 'left';
ctx.fillText(DATA[i1].c.toFixed(4), MA.l + pw + 4, tY(DATA[i1].c) + 4);
// Zoom %
ctx.fillStyle = AC; ctx.font = '10px sans-serif'; ctx.textAlign = 'right';
ctx.fillText(Math.round((viewEnd - viewStart) * 100) + '%', W - 10, MA.t - 4);
}
// Zoom (scroll)
canvas.addEventListener('wheel', function(e) {
e.preventDefault();
if (!DATA) return;
var rect = canvas.getBoundingClientRect();
var mx = (e.clientX - rect.left - MA.l) / (canvas.width - MA.l - MA.r);
mx = Math.max(0, Math.min(1, mx));
var mid = viewStart + mx * (viewEnd - viewStart);
var range = viewEnd - viewStart;
range = Math.max(0.003, Math.min(1, range * (e.deltaY > 0 ? 1.3 : 0.7)));
viewStart = Math.max(0, mid - range * mx);
viewEnd = Math.min(1, viewStart + range);
viewStart = Math.max(0, viewEnd - range);
draw();
}, {passive: false});
// Pan (drag)
canvas.addEventListener('mousedown', function(e) { isPanning = true; panStart = e.clientX; });
window.addEventListener('mousemove', function(e) {
if (!DATA) return;
if (isPanning) {
var dx = e.clientX - panStart; panStart = e.clientX;
var pw = canvas.width - MA.l - MA.r;
var shift = -(dx / pw) * (viewEnd - viewStart);
viewStart = Math.max(0, Math.min(1 - (viewEnd - viewStart), viewStart + shift));
viewEnd = viewStart + (viewEnd - viewStart);
draw(); return;
}
// Tooltip
var rect = canvas.getBoundingClientRect();
var mx = e.clientX - rect.left;
if (mx < MA.l || mx > canvas.width - MA.r) { tt.style.display = 'none'; return; }
var pw = canvas.width - MA.l - MA.r;
var idx = Math.floor(viewStart * DATA.length + ((mx - MA.l) / pw) * (viewEnd - viewStart) * DATA.length);
idx = Math.max(0, Math.min(DATA.length - 1, idx));
var k = DATA[idx];
var d = new Date(k.t * 1000);
var chg = ((k.c - k.o) / k.o * 100).toFixed(2);
var sign = chg >= 0 ? '+' : '';
var sigHTML = '';
if (SIGNALS) {
for (var si = 0; si < SIGNALS.length; si++) {
var sg = SIGNALS[si];
if (Math.abs(sg.ts - k.t) <= 30) {
sigHTML = '<br><span style="color:#ff6b6b">⚠ SPREAD SURGE</span> ' +
sg.spread.toFixed(4) + ' (' + (sg.spread/sg.baseline).toFixed(1) + 'x baseline)';
break;
}
}
}
tt.innerHTML = '<b>O</b> ' + k.o.toFixed(4) + ' <b>H</b> ' + k.h.toFixed(4) +
' <b>L</b> ' + k.l.toFixed(4) + ' <b>C</b> ' + k.c.toFixed(4) +
' <b style="color:' + (k.c >= k.o ? '#26a69a' : '#ef5350') + '">' + sign + chg + '%</b>' +
'<br><span style="color:#8b949e">' + d.toISOString().substring(11, 19) + ' &nbsp; R' + k.r + '</span>' + sigHTML;
tt.style.display = 'block';
tt.style.left = (e.clientX + 15) + 'px';
tt.style.top = (e.clientY - 50) + 'px';
});
window.addEventListener('mouseup', function() { isPanning = false; });
canvas.addEventListener('mouseleave', function() { isPanning = false; tt.style.display = 'none'; });
// Double-click reset
canvas.addEventListener('dblclick', function() { viewStart = 0; viewEnd = 1; draw(); });
// Touch
canvas.addEventListener('touchstart', function(e) {
if (e.touches.length === 1) { isPanning = true; panStart = e.touches[0].clientX; }
if (e.touches.length === 2) {
isPanning = false;
pinchDist0 = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
viewMid0 = (viewStart + viewEnd) / 2; viewRange0 = viewEnd - viewStart;
}
}, {passive: false});
canvas.addEventListener('touchmove', function(e) {
if (!DATA) return;
if (e.touches.length === 2) {
e.preventDefault();
var d = Math.hypot(e.touches[0].clientX - e.touches[1].clientX, e.touches[0].clientY - e.touches[1].clientY);
var s = pinchDist0 > 0 ? d / pinchDist0 : 1;
var nr = Math.max(0.003, Math.min(1, viewRange0 / s));
viewStart = Math.max(0, viewMid0 - nr / 2);
viewEnd = Math.min(1, viewStart + nr); viewStart = Math.max(0, viewEnd - nr);
draw(); return;
}
if (e.touches.length === 1 && isPanning) {
var dx = e.touches[0].clientX - panStart; panStart = e.touches[0].clientX;
var pw = canvas.width - MA.l - MA.r;
var shift = -(dx / pw) * (viewEnd - viewStart);
viewStart = Math.max(0, Math.min(1 - (viewEnd - viewStart), viewStart + shift));
viewEnd = viewStart + (viewEnd - viewStart);
draw(); return;
}
}, {passive: false});
canvas.addEventListener('touchend', function() { isPanning = false; tt.style.display = 'none'; });
window.addEventListener('resize', init);
Promise.all([
fetch('l2_LINK_kline_data.json').then(function(r){return r.json()}),
fetch('l2_LINK_signals.json').then(function(r){return r.json()})
]).then(function(results) {
DATA = results[0];
SIGNALS = results[1];
document.getElementById('loading').remove();
init();
})
.catch(function(e) {
document.getElementById('loading').textContent = 'Error: ' + e.message;
});
})();
</script>
</body>
</html>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

+345
View File
@@ -0,0 +1,345 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>数字心理学 · 人生说明书</title>
<style>
:root {
--bg: #faf7f2;
--text: #3d3226;
--muted: #8b7355;
--border: #d5c9b5;
--gold: #b8943e;
--gold-light: #f7f0e0;
--brown: #4a3020;
--cream: #fdfaf3;
--rose: #c97b84;
--ink: #2c2018;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'PingFang SC', 'Noto Serif SC', 'STSong', 'Microsoft YaHei', serif;
background: #e8e0d5;
color: var(--text);
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
padding: 12px;
}
.poster {
width: 100%;
max-width: 420px;
max-height: 95vh;
background: var(--cream);
overflow-y: auto;
box-shadow: 0 4px 32px rgba(0,0,0,0.1);
border-radius: 4px;
background-image:
repeating-linear-gradient(0deg, transparent, transparent 3px, rgba(139,115,85,0.015) 3px, rgba(139,115,85,0.015) 6px);
scrollbar-width: none;
}
.poster::-webkit-scrollbar { display: none; }
.inner { padding: 24px 20px 36px; }
/* ─── Header ─── */
.header { text-align: center; margin-bottom: 20px; }
.header h1 { font-size: 1.6em; font-weight: 400; letter-spacing: 0.16em; color: var(--brown); margin-bottom: 4px; }
.header .tags { font-size: 0.62em; color: var(--muted); letter-spacing: 0.06em; display: flex; justify-content: center; gap: 10px; }
.header .tags span {
border: 1px solid var(--border); padding: 2px 10px; border-radius: 10px;
background: rgba(255,255,255,0.5);
}
.divider { border: none; border-top: 1px solid var(--border); margin: 16px 0; opacity: 0.5; }
.divider-dot { border: none; border-top: 1px dotted var(--border); margin: 12px 0; }
/* Section title */
.sec-title {
text-align: center; font-size: 0.8em; font-weight: 600;
letter-spacing: 0.1em; color: var(--brown); margin: 16px 0 10px;
}
/* Triangle wrapper */
.tri-wrap { display: flex; justify-content: center; margin: 8px 0 12px; }
.tri-wrap svg { display: block; max-width: 100%; }
.tri-bottom { text-align: center; font-size: 0.72em; color: var(--muted); letter-spacing: 0.06em; margin: -4px 0 8px; }
/* Missing */
.missing { text-align: center; font-size: 0.7em; line-height: 1.7; }
.missing .chip {
display: inline-block; min-width: 20px; height: 20px; line-height: 20px;
border-radius: 50%; background: var(--gold-light); color: var(--gold);
font-weight: 700; font-size: 0.9em; margin: 0 2px;
}
.missing-short { text-align: center; font-size: 0.74em; color: var(--rose); margin-top: 2px; }
/* Code lines */
.codes { font-size: 0.7em; line-height: 1.8; padding: 0 4px; }
.codes .r { display: flex; justify-content: space-between; border-bottom: 1px dotted rgba(139,115,85,0.12); padding: 2px 4px; }
.codes .k { color: var(--muted); font-weight: 500; }
.codes .v { color: var(--text); }
/* Career */
.career { text-align: center; font-size: 0.7em; margin-top: 8px; }
.career .ct { font-weight: 600; color: var(--brown); }
.career .ci { display: inline-block; border: 1px solid var(--border); border-radius: 10px; padding: 2px 10px; margin: 2px; background: rgba(255,255,255,0.5); }
/* Joint codes */
.joint { margin-top: 4px; }
.joint-age { font-size: 0.66em; color: var(--gold); font-weight: 600; text-align: center; margin: 10px 0 4px; border-bottom: 1px solid var(--gold-light); padding-bottom: 2px; }
.joint-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 3px 10px; font-size: 0.64em; padding: 0 4px; }
.joint-grid .jc { font-weight: 700; color: var(--brown); }
.joint-grid .jm { color: var(--muted); }
/* Flow label */
.flow-label { text-align: center; }
.flow-label .big { font-size: 0.95em; font-weight: 600; color: var(--brown); letter-spacing: 0.08em; }
.flow-label .small { font-size: 0.63em; color: var(--muted); letter-spacing: 0.06em; }
.footer { text-align: center; font-size: 0.8em; font-weight: 300; letter-spacing: 0.14em; color: var(--muted); margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--border); }
</style>
</head>
<body>
<div class="poster">
<div class="inner">
<div class="header">
<h1>人生说明书</h1>
<div class="tags">
<span>性格特质|天赋优势</span>
<span>事业方向|流年运势</span>
</div>
</div>
<hr class="divider">
<!-- ═════ TRIANGLE 1: 出生盘 ═════ -->
<div class="sec-title">✦ 出生盘 ✦</div>
<div class="tri-wrap" id="t1"></div>
<div class="tri-bottom">17 02 1986</div>
<div class="missing">
<div style="font-weight:600;color:var(--brown);margin-bottom:2px;">— 数字缺失 —</div>
里缺:<span class="chip">3</span><span class="chip">4</span><span class="chip">9</span>
&nbsp;外缺:<span class="chip">1</span><span class="chip">5</span><span class="chip">6</span><span class="chip">7</span>
<br>里外缺:无
</div>
<div class="missing-short">缺失:行动力、规划能力、认同感</div>
<div class="codes">
<div class="r"><span class="k">主性格</span><span class="v">7 追求真理、敏锐钻研</span></div>
<div class="r"><span class="k">内心码</span><span class="v">5 固执</span></div>
<div class="r"><span class="k">外心码</span><span class="v">6 现实主义</span></div>
<div class="r"><span class="k">潜意识码</span><span class="v">2 外刚内柔</span></div>
<div class="r"><span class="k">坐镇码</span><span class="v">167 白手起家</span></div>
</div>
<div class="career">
<div class="ct">适合职业</div>
<span class="ci">分析科学</span><span class="ci">哲学</span><span class="ci">侦探</span><span class="ci">IT</span>
<br><span class="ci">学术研究</span><span class="ci">教授</span><span class="ci">推理类</span>
</div>
<hr class="divider-dot">
<!-- ═════ TRIANGLE 2: 流年盘 ═════ -->
<div class="sec-title">✦ 流年盘 ✦</div>
<div class="tri-wrap" id="t2"></div>
<div class="flow-label">
<div class="big">流年:开始年</div>
<div class="small">新的开始、新的机会</div>
</div>
<hr class="divider">
<!-- ═════ 联合码 ═════ -->
<div class="sec-title">12 组联合码</div>
<div class="joint">
<div class="joint-age">21—40岁 · 工作和朋友</div>
<div class="joint-grid">
<div><span class="jc">821</span> <span class="jm">大单销售高手</span></div>
<div><span class="jc">819</span> <span class="jm">亲力亲为</span></div>
<div><span class="jc">213</span> <span class="jm">讲师号</span></div>
<div><span class="jc">933</span> <span class="jm">手艺码</span></div>
</div>
<div class="joint-age">41—60岁 · 儿女和下属</div>
<div class="joint-grid">
<div><span class="jc">167</span> <span class="jm">白手起家</span></div>
<div><span class="jc">178</span> <span class="jm">成就他人</span></div>
<div><span class="jc">674</span> <span class="jm">天生傲骨</span></div>
<div><span class="jc">483</span> <span class="jm">专业码</span></div>
</div>
<div class="joint-age">61岁至晚年 · 晚年和家庭</div>
<div class="joint-grid">
<div><span class="jc">156</span> <span class="jm">五方求财</span></div>
<div><span class="jc">167</span> <span class="jm">白手起家</span></div>
<div><span class="jc">562</span> <span class="jm">学会花钱</span></div>
<div><span class="jc">729</span> <span class="jm">异性缘</span></div>
</div>
</div>
<div class="footer">内在探索<br>心灵成长</div>
</div>
</div>
<script>
/* ══════════════════════════════════════
PYRAMID TRIANGLE RENDERER v2
Clean geometric pyramid with internal grid lines
══════════════════════════════════════ */
const CW = 40; // cell width
const CH = 36; // cell height
const RG = 18; // row gap (vertical)
const PAD = 24;
function triad(cx, cellsInRow) {
const rowW = cellsInRow * CW;
return cx - rowW / 2;
}
function renderPyramid(elId, layers, edgeL, edgeR, centerNum) {
/*
layers = [{nums: [3], highlight: true}, {nums: [4,8]}, {nums: [1,6]}, {nums: [8,2,1,5]}]
edgeL/edgeR = [num, num, num] or null
*/
const nLayers = layers.length;
const bottomCells = layers[layers.length - 1].nums.length;
const baseW = bottomCells * CW; // width of bottom row cells
const marginW = 60; // space for edge numbers
const svgW = baseW + marginW * 2;
const svgH = PAD * 2 + CH * nLayers + RG * (nLayers - 1) + 20;
let svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${svgW} ${svgH}" width="${svgW}" height="${svgH}">`;
// Triangle outline (large continuous isosceles)
const topX = svgW / 2;
const topY = PAD;
const botLY = PAD + CH * nLayers + RG * (nLayers - 1);
const tipR = 18; // how far the triangle tip extends above/below the cell
// Triangle vertices
const tx = topX, ty = topY - tipR;
const lx = marginW - 8, ly = botLY + tipR;
const rx = svgW - marginW + 8, ry = botLY + tipR;
// Draw triangle background fill
svg += `<polygon points="${tx},${ty} ${lx},${ly} ${rx},${ry}" fill="rgba(200,164,92,0.04)" stroke="var(--ink)" stroke-width="2.5" stroke-linejoin="round"/>`;
// Internal horizontal grid lines
for (let i = 1; i < nLayers; i++) {
const cellsAbove = layers[i - 1].nums.length;
const cellsHere = layers[i].nums.length;
const y = PAD + CH + (i - 1) * (CH + RG) + RG / 2;
const topRowW = cellsAbove * CW;
const botRowW = cellsHere * CW;
const leftX = topX - topRowW / 2 + (topRowW - botRowW) / 2;
const rightX = leftX + botRowW;
svg += `<line x1="${leftX}" y1="${y}" x2="${rightX}" y2="${y}" stroke="var(--ink)" stroke-width="1" opacity="0.5"/>`;
}
// Vertical dividers within each row
for (let i = 0; i < nLayers; i++) {
const cells = layers[i].nums.length;
const y = PAD + i * (CH + RG);
const rowW = cells * CW;
const leftX = topX - rowW / 2;
for (let j = 1; j < cells; j++) {
const vx = leftX + j * CW;
svg += `<line x1="${vx}" y1="${y}" x2="${vx}" y2="${y + CH}" stroke="var(--ink)" stroke-width="1" opacity="0.5"/>`;
}
}
// Cells
for (let i = 0; i < nLayers; i++) {
const cells = layers[i].nums.length;
const y = PAD + i * (CH + RG);
const rowW = cells * CW;
const leftX = topX - rowW / 2;
for (let j = 0; j < cells; j++) {
const val = layers[i].nums[j];
const x = leftX + j * CW;
const isTop = (i === 0);
// Cell background
if (isTop) {
svg += `<rect x="${x}" y="${y}" width="${CW}" height="${CH}" fill="var(--ink)" rx="2"/>`;
svg += `<text x="${x + CW/2}" y="${y + CH/2}" dy="0.35em" text-anchor="middle" fill="#fff" font-size="20" font-weight="700" font-family="inherit">${val}</text>`;
} else {
svg += `<rect x="${x}" y="${y}" width="${CW}" height="${CH}" fill="#fff" stroke="var(--ink)" stroke-width="1" rx="2"/>`;
svg += `<text x="${x + CW/2}" y="${y + CH/2}" dy="0.35em" text-anchor="middle" fill="var(--ink)" font-size="16" font-weight="600" font-family="inherit">${val}</text>`;
}
}
}
// Center annotation (between layer 1 and 2) — birth chart only
if (centerNum != null) {
const cy = PAD + CH + RG / 2;
const cw2 = 36, ch2 = 28;
const cx = topX - cw2 / 2;
const cyPos = cy - ch2 / 2;
svg += `<polygon points="${cx + cw2/2},${cyPos - 4} ${cx + cw2 + 4},${cyPos + ch2/2} ${cx + cw2/2},${cyPos + ch2 + 4} ${cx - 4},${cyPos + ch2/2}" fill="var(--ink)" stroke="var(--ink)" stroke-width="1"/>`;
svg += `<text x="${cx + cw2/2}" y="${cyPos + ch2/2}" dy="0.35em" text-anchor="middle" fill="#fff" font-size="15" font-weight="700" font-family="inherit">${centerNum}</text>`;
}
// Edge numbers (small circles on left/right slopes)
if (edgeL && edgeL.length > 0) {
const ex = marginW / 2;
const topY2 = PAD + CH * 0.5;
const botY2 = PAD + CH * (nLayers - 1) + RG * (nLayers - 2) + CH * 0.5;
for (let i = 0; i < edgeL.length; i++) {
const ey = topY2 + (botY2 - topY2) * i / (edgeL.length - 1);
svg += `<circle cx="${ex}" cy="${ey}" r="13" fill="#fff" stroke="var(--gold)" stroke-width="1.5"/>`;
svg += `<text x="${ex}" y="${ey}" dy="0.35em" text-anchor="middle" fill="var(--gold)" font-size="13" font-weight="700" font-family="inherit">${edgeL[i]}</text>`;
}
}
if (edgeR && edgeR.length > 0) {
const ex = svgW - marginW / 2;
const topY2 = PAD + CH * 0.5;
const botY2 = PAD + CH * (nLayers - 1) + RG * (nLayers - 2) + CH * 0.5;
for (let i = 0; i < edgeR.length; i++) {
const ey = topY2 + (botY2 - topY2) * i / (edgeR.length - 1);
svg += `<circle cx="${ex}" cy="${ey}" r="13" fill="#fff" stroke="var(--gold)" stroke-width="1.5"/>`;
svg += `<text x="${ex}" y="${ey}" dy="0.35em" text-anchor="middle" fill="var(--gold)" font-size="13" font-weight="700" font-family="inherit">${edgeR[i]}</text>`;
}
}
svg += `</svg>`;
document.getElementById(elId).innerHTML = svg;
}
// ═══════ RENDER ═══════
renderPyramid('t1',
[{nums:[3], highlight:true}, {nums:[4,8]}, {nums:[1,6]}, {nums:[8,2,1,5]}],
[3,9,3], [9,7,2], 7
);
renderPyramid('t2',
[{nums:[1], highlight:true}, {nums:[1,9]}, {nums:[8,2]}, {nums:[2,7]}],
null, null, null
);
</script>
</body>
</html>
+588
View File
@@ -0,0 +1,588 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷 · 脑图编辑器</title>
<style>
:root{--pri:#E54D42;--hero-start:#FF9F80;--bg-start:#FFD1C7;--bg-end:#FFC8B5}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg-start),var(--bg-end) 50%,#fff5f0);
height:100vh;display:flex;overflow:hidden;
}
/* ── Sidebar ── */
.sidebar{
width:200px;background:#fff;border-right:1px solid #f0e0d8;display:flex;flex-direction:column;
flex-shrink:0;box-shadow:2px 0 8px rgba(0,0,0,0.03);z-index:10;
}
.sidebar .sh{
padding:16px 14px 10px;font-size:15px;font-weight:600;color:#333;
border-bottom:1px solid #f5f0eb;
}
.sidebar .slist{flex:1;overflow-y:auto;padding:8px 0}
.sidebar .slist .item{
padding:10px 14px;cursor:pointer;font-size:13px;color:#555;
display:flex;align-items:center;justify-content:space-between;
border-left:3px solid transparent;transition:background 0.1s;
}
.sidebar .slist .item:hover{background:#fdf8f5}
.sidebar .slist .item.active{background:#fff0ed;border-left-color:var(--pri);color:var(--pri);font-weight:600}
.sidebar .slist .item .del{font-size:11px;color:#ccc;visibility:hidden;cursor:pointer}
.sidebar .slist .item:hover .del{visibility:visible}
.sidebar .slist .item .del:hover{color:var(--pri)}
.sidebar .sb{display:flex;gap:6px;padding:10px 12px;border-top:1px solid #f5f0eb}
.sidebar .sb button{
flex:1;padding:8px 0;border:1px solid #ddd;border-radius:6px;
background:#fff;font-size:12px;cursor:pointer;color:#555;
}
.sidebar .sb button.primary{background:var(--pri);color:#fff;border-color:var(--pri)}
.sidebar .sb button:active{opacity:0.8}
/* ── Main ── */
.main{flex:1;display:flex;flex-direction:column;overflow:hidden}
.toolbar{
display:flex;align-items:center;gap:6px;padding:8px 12px;
background:rgba(255,255,255,0.9);-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
border-bottom:1px solid #f0e0d8;flex-shrink:0;flex-wrap:wrap;z-index:10;
}
.toolbar button{
padding:6px 12px;border:1px solid #ddd;border-radius:6px;
background:#fff;font-size:13px;cursor:pointer;color:#555;
}
.toolbar button:active{background:#f0ebe5}
.toolbar button.primary{background:var(--pri);color:#fff;border-color:var(--pri)}
.toolbar .sep{width:1px;height:20px;background:#ddd;margin:0 2px}
.toolbar .hint{font-size:11px;color:#aaa;margin-left:auto}
canvas{flex:1;display:block;cursor:grab}
canvas:active{cursor:grabbing}
#editor{position:absolute;display:none;z-index:20}
#editor input{
padding:4px 8px;border:2px solid var(--pri);border-radius:4px;
font-size:13px;outline:none;min-width:80px;background:#fff;
}
/* Plus buttons */
#plusChild,#plusSibling{
position:absolute;display:none;z-index:25;
width:20px;height:20px;border-radius:50%;
color:#fff;font-size:14px;font-weight:bold;line-height:20px;
text-align:center;cursor:pointer;user-select:none;
pointer-events:auto;
}
/* Mobile */
@media(max-width:600px){
.sidebar{
position:fixed;left:0;top:0;bottom:0;width:240px;z-index:30;
transform:translateX(-100%);transition:transform 0.25s ease;
box-shadow:4px 0 20px rgba(0,0,0,0.1);
}
.sidebar.open{transform:translateX(0)}
.sidebar .sh{font-size:16px;padding:18px 16px 12px}
.sidebar .slist .item{font-size:14px;padding:12px 16px}
.sidebar .sb button{padding:10px 0;font-size:13px}
/* hamburger */
.hamburger{
display:flex!important;position:fixed;top:8px;left:8px;z-index:40;
width:36px;height:36px;align-items:center;justify-content:center;
background:rgba(255,255,255,0.9);border-radius:8px;
border:1px solid #e0d5cf;font-size:18px;cursor:pointer;
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
/* overlay backdrop */
.overlay{
display:none;position:fixed;inset:0;z-index:25;
background:rgba(0,0,0,0.25);
}
.overlay.show{display:block}
/* toolbar compact */
.toolbar{gap:3px;padding:6px 6px 6px 50px;min-height:42px}
.toolbar button{padding:6px 10px;font-size:12px;border-radius:8px}
.toolbar .sep{margin:0 1px}
.toolbar .hint{font-size:10px;display:none}
/* canvas */
canvas{flex:1}
/* editor bigger for touch */
#editor input{font-size:16px!important;padding:8px 12px;min-width:100px;border-radius:6px}
/* node hit area larger on mobile */
.mobile .toolbar button{min-height:32px}
}
/* Desktop: hide hamburger */
.hamburger{display:none}
</style>
</head>
<body>
<!-- Mobile hamburger -->
<button class="hamburger" id="hamburger" onclick="toggleSidebar()"></button>
<div class="overlay" id="overlay" onclick="closeSidebar()"></div>
<!-- Sidebar -->
<div class="sidebar">
<div class="sh">📁 我的脑图</div>
<div class="slist" id="slist"></div>
<div class="sb">
<button class="primary" onclick="newMap()"> 新建</button>
<button onclick="goHome()">🏠</button>
</div>
</div>
<!-- Main -->
<div class="main">
<div class="toolbar">
<button onclick="renameMap()">✎ 重命名</button>
<button onclick="addChild()"></button>
<button onclick="addSibling()"> 同级</button>
<button onclick="delNode()">✕ 删除</button>
<span class="sep"></span>
<button onclick="zoomIn()">🔍+</button>
<button onclick="zoomOut()">🔍-</button>
<span class="sep"></span>
<span class="hint" id="saveHint">💾 已自动保存</span>
</div>
<canvas id="c"></canvas>
</div>
<!-- Plus buttons (HTML, positioned over canvas) -->
<div id="plusChild" onclick="addChild()" title="添加子节点">+</div>
<div id="plusSibling" onclick="addSibling()" title="添加同级节点">+</div>
<div id="editor"><input id="ei" onblur="commitEdit()" onkeydown="if(event.key==='Enter')commitEdit();if(event.key==='Escape')cancelEdit()"></div>
<script>
var c=document.getElementById('c'),ctx=c.getContext('2d');
var ei=document.getElementById('ei'),ed=document.getElementById('editor');
var SCALE=1,ox=0,oy=0;
var dragging=false,dragX=0,dragY=0;
var selected=null,editing=null;
var colors=['#8B5CF6','#4A90E2','#5CB85C','#E8985A','#E54D42','#06B6D4','#F59E0B','#EC4899'];
var STORAGE_KEY='yuxingu_maps';
// ---- Multi-map state ----
var maps=[],currentIdx=-1;
function defaultTree(){
return {
id:'root',text:'新建脑图',color:colors[0],children:[]
};
}
function defaultTemplate(){
return {
id:'root',text:'愈心谷小程序',color:colors[0],
children:[
{id:'b1',text:'愈心魔方',color:colors[0],children:[
{id:'b1a',text:'急救魔方'},{id:'b1b',text:'亲子魔方'},{id:'b1c',text:'成长魔方'},
{id:'b1d',text:'解构魔方'},{id:'b1e',text:'艺术魔方'},{id:'b1f',text:'情感魔方'}
]},
{id:'b2',text:'私域活动',color:colors[1],children:[
{id:'b2a',text:'心理研学'},{id:'b2b',text:'心灵旅修'}
]},
{id:'b3',text:'心理咨询',color:colors[2]},
{id:'b4',text:'心理评估',color:colors[3],children:[
{id:'b4a',text:'AI面部识别'},{id:'b4b',text:'脑机接口'}
]},
{id:'b5',text:'健康管理',color:colors[4]}
]
};
}
// ---- Persistence ----
function loadMaps(){
try{
var d=localStorage.getItem(STORAGE_KEY);
return d?JSON.parse(d):[];
}catch(e){return [];}
}
function saveMaps(){
try{localStorage.setItem(STORAGE_KEY,JSON.stringify(maps))}catch(e){}
// sync to server (all maps)
fetch('/save',{method:'POST',body:JSON.stringify(maps)}).catch(function(){});
}
function syncServer(){
fetch('/load').then(function(r){return r.json()}).then(function(data){
if(Array.isArray(data)&&data.length>0){
maps=data;saveMaps();renderSidebar();
if(currentIdx<0||currentIdx>=maps.length)switchMap(0);
}
}).catch(function(){});
}
// ---- Sidebar ----
function renderSidebar(){
var sl=document.getElementById('slist');
sl.innerHTML='';
maps.forEach(function(m,i){
var d=document.createElement('div');
d.className='item'+(i===currentIdx?' active':'');
d.innerHTML='<span>'+escHtml(m.name||'脑图'+(i+1))+'</span><span class="del" data-idx="'+i+'" onclick="event.stopPropagation();deleteMap('+i+')"></span>';
d.onclick=function(){switchMap(i)};
sl.appendChild(d);
});
}
function escHtml(s){return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
function switchMap(idx){
if(idx<0||idx>=maps.length)return;
currentIdx=idx;
tree=JSON.parse(JSON.stringify(maps[idx].tree));
selected=null;
renderSidebar();
layout();draw();
// close sidebar on mobile after selection
if(window.innerWidth<=600)closeSidebar();
}
function newMap(){
var name=prompt('脑图名称:','脑图'+(maps.length+1));
if(!name)return;
maps.push({name:name,tree:defaultTree()});
saveMaps();
renderSidebar();
switchMap(maps.length-1);
}
function renameMap(){
if(currentIdx<0)return;
var name=prompt('新名称:',maps[currentIdx].name);
if(!name)return;
maps[currentIdx].name=name;
saveMaps();renderSidebar();
}
function deleteMap(idx){
if(!confirm('删除「'+maps[idx].name+'」?不可恢复。'))return;
maps.splice(idx,1);
saveMaps();renderSidebar();
if(maps.length===0){maps.push({name:'愈心谷小程序',tree:defaultTemplate()});saveMaps();renderSidebar();}
if(currentIdx>=maps.length)currentIdx=maps.length-1;
switchMap(currentIdx);
}
var _saveTimer=null;
function autoSave(){
if(currentIdx<0)return;
maps[currentIdx].tree=JSON.parse(JSON.stringify(exportTree(tree)));
saveMaps();
var h=document.getElementById('saveHint');
if(h){h.textContent='💾 已自动保存';h.style.color='#aaa';
clearTimeout(_saveTimer);
_saveTimer=setTimeout(function(){h.textContent='';},2000);}
}
// ---- Tree export ----
function exportTree(node){
var o={id:node.id,text:node.text};
if(node.color)o.color=node.color;
if(node.children)o.children=node.children.map(exportTree);
return o;
}
// ---- Layout & Draw ----
var tree=defaultTemplate();
var NODE_W=120,NODE_H=34,HGAP=60,VGAP=14;
function uid(){return 'n'+Date.now()+Math.random().toString(36).slice(2,6)}
function getColor(node,parentColor){
if(node.color)return node.color;
if(parentColor)return parentColor;
return colors[0];
}
function layoutHeight(node){
if(!node.children||node.children.length===0)return NODE_H;
var h=0;
for(var i=0;i<node.children.length;i++)h+=layoutHeight(node.children[i]);
return h+(node.children.length-1)*VGAP;
}
function layoutNode(node,x,y,parentColor){
node._x=x;node._y=y;node._w=NODE_W;node._h=NODE_H;
node._color=getColor(node,parentColor);
if(node.children&&node.children.length>0){
var totalH=0;
for(var i=0;i<node.children.length;i++)totalH+=layoutHeight(node.children[i]);
totalH+=(node.children.length-1)*VGAP;
var cy=y-(totalH-NODE_H)/2;
for(var i=0;i<node.children.length;i++){
var cnode=node.children[i];
var ch=layoutHeight(cnode);
layoutNode(cnode,x+NODE_W+HGAP,cy+(ch-NODE_H)/2,node._color);
cy+=ch+VGAP;
}
}
}
function layout(){
var totalH=layoutHeight(tree);
layoutNode(tree,40,-totalH/2,tree.color);
}
function hitTest(mx,my){
function hit(node){
if(!node._x)return null;
if(mx>=node._x&&mx<=node._x+node._w&&my>=node._y&&my<=node._y+node._h)return node;
if(node.children)for(var i=0;i<node.children.length;i++){var h=hit(node.children[i]);if(h)return h;}
return null;
}
return hit(tree);
}
function draw(){
var W=c.width=c.offsetWidth||window.innerWidth-200;
var H=c.height=c.offsetHeight||window.innerHeight-42;
ctx.clearRect(0,0,W,H);
ctx.save();
ctx.translate(W/2+ox,H/2+oy);
ctx.scale(SCALE,SCALE);
drawTree(tree);
ctx.restore();
updatePlusButtons();
}
function drawTree(node){
var x=node._x,y=node._y,w=node._w,h=node._h,col=node._color;
var isRoot=node===tree,isSel=node===selected;
if(node.children)for(var i=0;i<node.children.length;i++){
var c=node.children[i];
ctx.beginPath();
ctx.moveTo(x+w,y+h/2);
ctx.bezierCurveTo(
x+w+HGAP/2,y+h/2,
c._x-HGAP/2,c._y+c._h/2,
c._x,c._y+c._h/2
);
ctx.strokeStyle=col;ctx.lineWidth=isRoot?3:2;ctx.stroke();
drawTree(c);
}
var rx=6;
ctx.beginPath();
ctx.moveTo(x+rx,y);ctx.lineTo(x+w-rx,y);ctx.arcTo(x+w,y,x+w,y+rx,rx);
ctx.lineTo(x+w,y+h-rx);ctx.arcTo(x+w,y+h,x+w-rx,y+h,rx);
ctx.lineTo(x+rx,y+h);ctx.arcTo(x,y+h,x,y+h-rx,rx);
ctx.lineTo(x,y+rx);ctx.arcTo(x,y,x+rx,y,rx);ctx.closePath();
if(isRoot){
ctx.fillStyle=col;ctx.fill();
ctx.strokeStyle='rgba(0,0,0,0.15)';ctx.lineWidth=1;ctx.stroke();
ctx.fillStyle='#fff';
}else{
ctx.fillStyle='#fff';ctx.fill();
ctx.strokeStyle=col;ctx.lineWidth=isSel?3:1.5;ctx.stroke();
ctx.fillStyle='#333';
}
ctx.font=(isRoot?'bold ':'')+'13px -apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif';
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.fillText(node.text,x+w/2,y+h/2);
}
function updatePlusButtons(){
var pc=document.getElementById('plusChild'), ps=document.getElementById('plusSibling');
if(!selected){
pc.style.display='none'; ps.style.display='none'; return;
}
var rect=c.getBoundingClientRect();
var n=selected, col=n._color||colors[0];
// Child + (right edge)
var cx=rect.left + c.width/2 + ox + (n._x+n._w+8)*SCALE;
var cy=rect.top + c.height/2 + oy + (n._y+n._h/2)*SCALE;
pc.style.display='block'; pc.style.left=(cx-10)+'px'; pc.style.top=(cy-10)+'px';
pc.style.background=col;
// Sibling + (bottom edge) — not for root
if(selected!==tree){
var sx=rect.left + c.width/2 + ox + (n._x+n._w/2)*SCALE;
var sy=rect.top + c.height/2 + oy + (n._y+n._h+8)*SCALE;
ps.style.display='block'; ps.style.left=(sx-10)+'px'; ps.style.top=(sy-10)+'px';
ps.style.background=col;
}else{ps.style.display='none';}
}
// ---- Canvas events ----
c.addEventListener('mousedown',function(e){
var rect=c.getBoundingClientRect();
var sx=(e.clientX-rect.left-c.width/2-ox)/SCALE;
var sy=(e.clientY-rect.top-c.height/2-oy)/SCALE;
var node=hitTest(sx,sy);
if(node){selected=node;draw();}
else{dragging=true;dragX=e.clientX;dragY=e.clientY;}
});
c.addEventListener('mousemove',function(e){
if(dragging){ox+=e.clientX-dragX;oy+=e.clientY-dragY;dragX=e.clientX;dragY=e.clientY;draw();}
});
c.addEventListener('mouseup',function(){dragging=false;});
c.addEventListener('mouseleave',function(){dragging=false;});
c.addEventListener('wheel',function(e){
e.preventDefault();
SCALE=Math.max(0.3,Math.min(3,SCALE*(e.deltaY>0?0.9:1.1)));
draw();
},{passive:false});
c.addEventListener('dblclick',function(e){
var rect=c.getBoundingClientRect();
var sx=(e.clientX-rect.left-c.width/2-ox)/SCALE;
var sy=(e.clientY-rect.top-c.height/2-oy)/SCALE;
var node=hitTest(sx,sy);
if(node){
editing=node;
var cx=rect.left+c.width/2+ox+node._x*SCALE;
var cy=rect.top+c.height/2+oy+node._y*SCALE;
ed.style.display='block';ed.style.left=cx+'px';ed.style.top=cy+'px';
ei.value=node.text;ei.focus();ei.select();
}
});
// Touch
c.addEventListener('touchstart',function(e){
if(e.touches.length===1){
var rect=c.getBoundingClientRect();
var sx=(e.touches[0].clientX-rect.left-c.width/2-ox)/SCALE;
var sy=(e.touches[0].clientY-rect.top-c.height/2-oy)/SCALE;
var node=hitTest(sx,sy);
if(node){selected=node;draw();}
else{dragging=true;dragX=e.touches[0].clientX;dragY=e.touches[0].clientY;}
}
},{passive:false});
c.addEventListener('touchmove',function(e){
if(dragging&&e.touches.length===1){
e.preventDefault();
ox+=e.touches[0].clientX-dragX;oy+=e.touches[0].clientY-dragY;
dragX=e.touches[0].clientX;dragY=e.touches[0].clientY;draw();
}
},{passive:false});
c.addEventListener('touchend',function(){dragging=false;});
// ---- Mobile: sidebar toggle ----
function toggleSidebar(){
var sb=document.querySelector('.sidebar');
var ov=document.getElementById('overlay');
sb.classList.toggle('open');
ov.classList.toggle('show');
}
function closeSidebar(){
document.querySelector('.sidebar').classList.remove('open');
document.getElementById('overlay').classList.remove('show');
}
// ---- Mobile: double-tap to edit ----
var _lastTap=0;
c.addEventListener('touchend',function(e){
var now=Date.now();
if(now-_lastTap<300){
// double tap
e.preventDefault();
var rect=c.getBoundingClientRect();
var sx=(e.changedTouches[0].clientX-rect.left-c.width/2-ox)/SCALE;
var sy=(e.changedTouches[0].clientY-rect.top-c.height/2-oy)/SCALE;
var node=hitTest(sx,sy);
if(node){
editing=node;
var cx=rect.left+c.width/2+ox+node._x*SCALE;
var cy=rect.top+c.height/2+oy+node._y*SCALE;
ed.style.display='block';ed.style.left=cx+'px';ed.style.top=cy+'px';
ei.value=node.text;ei.focus();ei.select();
}
}
_lastTap=now;
// close sidebar when tapping canvas
if(window.innerWidth<=600)closeSidebar();
});
// ---- Mobile: pinch-to-zoom ----
var _pinchDist=0;
c.addEventListener('touchstart',function(e){
if(e.touches.length===2){
_pinchDist=Math.hypot(
e.touches[0].clientX-e.touches[1].clientX,
e.touches[0].clientY-e.touches[1].clientY
);
}
},{passive:false});
c.addEventListener('touchmove',function(e){
if(e.touches.length===2){
e.preventDefault();
var d=Math.hypot(
e.touches[0].clientX-e.touches[1].clientX,
e.touches[0].clientY-e.touches[1].clientY
);
var ratio=d/(_pinchDist||1);
SCALE=Math.max(0.3,Math.min(3,SCALE*ratio));
_pinchDist=d;
draw();
}
},{passive:false});
// ---- Keyboard shortcuts ----
document.addEventListener('keydown',function(e){
if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA')return; // 编辑中不触发
if(e.key==='Delete'||e.key==='Backspace'){
e.preventDefault();
delNode();
}
});
// ---- Actions ----
function commitEdit(){if(editing)editing.text=ei.value||'新节点';ed.style.display='none';editing=null;draw();autoSave();}
function cancelEdit(){ed.style.display='none';editing=null;}
function addChild(){
if(!selected){alert('请先选中一个节点');return;}
if(!selected.children)selected.children=[];
selected.children.push({id:uid(),text:'新节点'});
layout();draw();selected=selected.children[selected.children.length-1];draw();autoSave();
}
function addSibling(){
if(!selected||selected===tree){alert('请选中一个非根节点');return;}
function fp(node,target){
if(node.children)for(var i=0;i<node.children.length;i++){
if(node.children[i].id===target.id)return node;
var f=fp(node.children[i],target);if(f)return f;
}
return null;
}
var parent=fp(tree,selected);
if(!parent)return;
var idx=parent.children.indexOf(selected);
parent.children.splice(idx+1,0,{id:uid(),text:'新节点'});
layout();draw();selected=parent.children[idx+1];draw();autoSave();
}
function delNode(){
if(!selected||selected===tree){alert('不能删除根节点');return;}
function rm(node,target){
if(node.children)for(var i=0;i<node.children.length;i++){
if(node.children[i].id===target.id){node.children.splice(i,1);return true;}
if(rm(node.children[i],target))return true;
}
return false;
}
rm(tree,selected);selected=null;layout();draw();autoSave();
}
function zoomIn(){SCALE=Math.min(3,SCALE*1.15);draw();}
function zoomOut(){SCALE=Math.max(0.3,SCALE*0.85);draw();}
function goHome(){window.location.href='index.html';}
// ---- Init ----
(function init(){
maps=loadMaps();
if(maps.length===0){maps.push({name:'愈心谷小程序',tree:defaultTemplate()});saveMaps();}
renderSidebar();
switchMap(0);
syncServer();
window.addEventListener('resize',function(){draw();});
})();
</script>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
[{"name":"愈心谷小程序","tree":{"id":"root","text":"愈心谷小程序","color":"#8B5CF6","children":[{"id":"b1","text":"愈心魔方","color":"#8B5CF6","children":[{"id":"b1a","text":"急救魔方","children":[]},{"id":"b1b","text":"亲子魔方"},{"id":"b1c","text":"成长魔方"},{"id":"b1d","text":"解构魔方"},{"id":"b1e","text":"艺术魔方"},{"id":"b1f","text":"情感魔方"}]},{"id":"b2","text":"私域活动","color":"#4A90E2","children":[{"id":"b2a","text":"心理研学"},{"id":"b2b","text":"心灵旅修"}]},{"id":"b3","text":"心理咨询","color":"#5CB85C"},{"id":"b4","text":"心理评估","color":"#E8985A","children":[{"id":"b4a","text":"AI面部识别"},{"id":"b4b","text":"脑机接口"}]},{"id":"b5","text":"健康管理","color":"#E54D42"}]}},{"name":"愈心谷","tree":{"id":"root","text":"新建","color":"#8B5CF6","children":[]}}]
+81
View File
@@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""Minimal HTTP server for digital-psychology — serves static files + mindmap persistence."""
import json, os, sys
from http.server import HTTPServer, SimpleHTTPRequestHandler
ROOT = os.path.dirname(os.path.abspath(__file__))
MINDMAP_FILE = os.path.join(ROOT, "mindmap_data.json")
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=ROOT, **kwargs)
def end_headers(self):
# no-cache for HTML and JSON endpoints
if self.path == '/' or self.path.endswith('.html') or self.path == '/load':
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
self.send_header("Pragma", "no-cache")
self.send_header("Expires", "0")
super().end_headers()
def do_GET(self):
if self.path == "/load":
if os.path.exists(MINDMAP_FILE):
with open(MINDMAP_FILE, "r", encoding="utf-8") as f:
data = f.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(data.encode())
else:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b"{}")
else:
super().do_GET()
def do_POST(self):
if self.path == "/save":
length = int(self.headers.get("Content-Length", 0))
data = self.rfile.read(length)
try:
json.loads(data)
except json.JSONDecodeError:
self.send_error(400, "Invalid JSON")
return
with open(MINDMAP_FILE, "w", encoding="utf-8") as f:
f.write(data.decode())
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b'{"ok":true}')
elif self.path == "/load":
if os.path.exists(MINDMAP_FILE):
with open(MINDMAP_FILE, "r", encoding="utf-8") as f:
data = f.read()
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(data.encode())
else:
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.end_headers()
self.wfile.write(b"{}")
else:
self.send_error(404)
def log_message(self, format, *args):
if "/save" in str(args) or "/load" in str(args):
print(f"[{self.log_date_time_string()}] {args[0]}")
if __name__ == "__main__":
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8001
server = HTTPServer(("127.0.0.1", port), Handler)
server.socket.settimeout(30) # 30s timeout so hung clients don't block the single-thread server
print(f"Serving {ROOT} on port {port} (save/load enabled)")
try:
server.serve_forever()
except KeyboardInterrupt:
server.shutdown()
+673
View File
@@ -0,0 +1,673 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>愈心谷数字解构</title>
<style>
@font-face { font-family:'TriangleDigits'; src:url(data:font/woff2;base64,d09GMgABAAAAAAUAAA4AAAAACHwAAASsAAIZmQAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbbBxIBmAANBEQCocshhEBNgIkAxoLGAAEIAWDIgcgG9QGKC4G7Ib6hm2MRajDYhGiDhrol2EQYF1VSXrYswJPkJb83g+eA7l2QeKDmZzBKY+S2Y6GNNvdREZSoL/sv5IIQxUu06If+FX4/z+X49/WsztrTAw5kRt099ubOm5NXEAFHuBcGvAGSjDAgLDn2bZk6fIVBBISV5n1GLNAADYApcVbCydXozYtkTdtbu8OvEOS0PEgPgIdEQfiY0OwiSCOBmAPsl4AYswwdCJQA1FoSBQqZkProGuMIFPAPhuoa8oSVYX8YAQSAZpDCmDZcIkm/ZAjI0LR3A1KzY56XmlDxUYMYNhtcBARzR51kKE7KkxKozJobFeXdNJlKY3czMCYMrUMa/83F2mmEHEHkFJnDXn9YBwBCSjAHYUA3NEIBjQ0BFDLHki2t3+KqVZYkbggAFQ26ho8RWcQ3tl/htql/UbHN70BAoXUbXpAnK4F+eoWL5vNxzpnKT0L1CrUld9zyZWVebLzdRVOGASMYohloBiiuziLHrm5uQI5VmbV7EqrcnVlszArq2tWV6c5C0+s7O5L1+3WqS7Q2737TCeTufqRH9o1O3uOtCpHMUc36sxupMOcNaesYEbpnGdwwlHmrs8VKBTmstgNaLvfBrx5PQ/wj1yzJd01+3LOm+/CzOY4zhHWtXYr5PbIyREYwJnHeFm4pXu2IcxByHzZVWJ3Er3bMQkX9snibM6Y1aTyc3rkXNUJPXpz8SR3SFeH84FFWYV9bzibJvHlL0bRde6KYc9cmiIKk5+FN+NThqtvc8aBGfnF6WzIqLk1ON06sNXy3bLFuIJ8DQT/QGhPY9JrsRk3RddPID33rzz9cLGnXZjvNNQR4659v7m7f0qW1qex/MqwciHDfdsqBnOLCispr6nWU0/DmAXZBdjkTA0M/4LXawb7VDhbjduWNPaWsM25jxyH1ENRUmejPPOk33dOKZ+v4BRLNZQeriawC/Nzxb0nr7/lPdzfGvGf3kgW0N6/tXYq1Si9RA/v58kV3DTY5DgaxwV1Uw5++lzxouLrgNYX82rdHtlWXSn61Di0UAxfhc2HeamcDnfrnVra3xecFNc61xtZhSxqUlFZFQ62rTQ+w3YS4S9Z4bNHHUB7y/fxoxt9pVAyqdOZ66cPKs7Oz8sYj5f2lIpvQPQfX3t4++1V80/rnFtRkIspHBjC5WXnpReTvuTiMVkJsZFp5SVhxARkdDKQhx19XV78vjqr1UAXlvCfnhUiz41CK/6P8dI6eIab2nlH17ap48+X3P/67iJUpWdlq6vNFAQABCB9dmU3fPxwgFPdb2YhMwAvzT++ByhOmZz+3/9vizVMBYAZOVthCvnfDK9OI6rfpSMQ1O8ljbVAg/Zm6S2UNDN4K6A9UySALjrjrgTjPKoUNdG7bdIswFkQgyCYswaJI28NikwKujXQYMBXOC8yEihiaSpGlAKsZbII4YSJENhVwgyBgJQEXSDaUphohpCxTCWZFJKhBY+AiHgRfBMjhobUGOI+OYREilhp4gmpjs63SFuCvHUricIo0nYoVERiSqyLCF+MnFdHwYlnbI0JjLBYonBsPGESCHYCTx5NXJ+7DhJpNTCDOrOPjGrLzlAAAAA) format('woff2'); }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background-color: #faf7f2; font-family: 'Georgia','Times New Roman','Noto Serif SC',serif; display:flex; justify-content:center; align-items:center; min-height:100vh; color:#6b5b4e; padding:10px 10px 20px; -webkit-user-select:none; user-select:none; -webkit-touch-callout:none; }
.container { position:relative; width:600px; max-width:95%; padding:12px 40px 40px; display:flex; flex-direction:column; align-items:center; }
.input-row { display:flex; gap:5px; align-items:center; margin-bottom:24px; font-family:sans-serif; }
.input-row input { width:56px; padding:6px 4px; border:1px solid #d9ccb6; border-radius:3px; font-size:14px; text-align:center; color:#6b5b4e; background:#fff; outline:none; }
.input-row input:focus { border-color:#a89580; }
.input-row .lbl { font-size:0.75em; color:#9b8a78; }
.btn-gen { padding:6px 12px; background:#8b7a68; color:#fff; border:none; border-radius:3px; font-size:0.75em; cursor:pointer; margin-left:2px; }
.btn-gen:active { opacity:0.8; }
.top-numbers { display:flex; flex-direction:column; align-items:center; margin-bottom:-20px; z-index:2; font-family:'TriangleDigits','Georgia','Times New Roman',serif; }
.top-numbers .num-top { font-size:2.2rem; font-weight:normal; }
.top-numbers .num-mid-row { display:flex; gap:60px; font-size:2.2rem; font-weight:normal; }
.triangle-wrapper { position:relative; width:300px; height:260px; margin:20px 0; display:flex; justify-content:center; align-items:center; overflow:visible; }
.triangle-svg { position:absolute; top:0; left:0; width:100%; height:100%; z-index:0; }
.triangle-path { fill:#f2ebdd; stroke:#d9ccb6; stroke-width:2; }
.divider-line { stroke:#d9ccb6; stroke-width:2; }
.inner-number { position:absolute; z-index:1; font-size:2.2rem; font-weight:bold; cursor:pointer; font-family:'TriangleDigits','Georgia','Times New Roman',serif; color:#5a4636; }
.red-num { color:#c47a78; top:25%; }
.mid-left { top:55%; left:30%; } .mid-right { top:55%; right:30%; }
.bot-1 { bottom:10%; left:18%; } .bot-2 { bottom:10%; left:38%; }
.bot-3 { bottom:10%; right:38%; } .bot-4 { bottom:10%; right:18%; }
.side-decorations { position:absolute; width:100%; height:100%; top:0; left:0; font-family:'TriangleDigits','Georgia','Times New Roman',serif; }
.side-num { position:absolute; font-size:2.2rem; opacity:0.65; cursor:pointer; font-family:'TriangleDigits','Georgia','Times New Roman',serif; font-weight:normal; color:#8b7e6e; }
.left-group { top:30%; left:-40px; text-align:right; line-height:1.5; }
.right-group { top:30%; right:-40px; text-align:left; line-height:1.5; }
.footer-date { margin-top:0; display:flex; gap:40px; font-size:1.8rem; letter-spacing:2px; color:#5a4636; font-family:'TriangleDigits','Georgia','Times New Roman',serif; }
.sections { width:100%; max-width:360px; margin-top:24px; font-family:sans-serif; font-size:1em; color:#6b5b4e; }
.divider { border:none; border-top:1px solid #e0d5c4; margin:12px 0; opacity:0.6; }
.codes .r { display:flex; justify-content:space-between; border-bottom:1px dotted rgba(168,144,112,0.15); padding:2px 4px; }
.codes .k { color:#9b8a78; } .codes .v { color:#6b5b4e; }
.joint-age { font-size:0.9em; color:#a89070; font-weight:600; text-align:center; margin:8px 0 2px; border-bottom:1px solid rgba(168,144,112,0.2); padding-bottom:2px; }
.joint-grid { display:grid; grid-template-columns:repeat(2,1fr); gap:2px 10px; }
.joint-grid .jc { font-weight:700; color:#5a4636; } .joint-grid .jm { color:#9b8a78; }
.tooltip { position:fixed; padding:5px 10px; background:#6b5b4e; color:#fff; border-radius:4px; font-size:0.7em; pointer-events:none; opacity:0; transition:opacity 0.12s; z-index:999; max-width:200px; line-height:1.5; font-family:sans-serif; }
.tooltip.on { opacity:1; }
.footer { text-align:center; font-size:0.6em; color:#9b8a78; margin-top:10px; }
.scene-3d { position:relative; display:flex; justify-content:center; margin:8px 0; cursor:grab; }
.scene-3d:active { cursor:grabbing; }
.scene-3d canvas { display:block; touch-action:none; }
.scene-hint { position:absolute; bottom:2px; left:50%; transform:translateX(-50%); font-size:0.55em; color:#b8a898; pointer-events:none; opacity:0.7; }
.freq-label { text-align:center; font-size:0.7em; color:#6b5b4e; margin-top:2px; line-height:1.8; }
.tabs-nav { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:18px; justify-content:center; padding:0 4px; }
.tab-btn { padding:5px 10px; border:1px solid #d9ccb6; border-radius:3px; background:#fff; color:#9b8a78; font-size:0.7em; cursor:pointer; font-family:sans-serif; transition:all 0.15s; white-space:nowrap; }
.tab-btn:hover { border-color:#c47a78; color:#c47a78; }
.tab-btn.active { background:#c47a78; color:#fff; border-color:#c47a78; }
.tab-panel { display:none; }
.tab-panel.active { display:block; }
.tab-panel .module-title { font-size:0.82em; font-weight:600; color:#5a4636; text-align:center; margin-bottom:8px; }
.tab-panel .module-desc { font-size:0.68em; color:#9b8a78; text-align:center; margin-bottom:10px; line-height:1.6; }
.tab-panel .module-section { font-size:0.72em; color:#6b5b4e; line-height:1.75; padding:0 4px; margin-bottom:10px; }
.tab-panel .module-section b { color:#5a4636; }
.bagua-grid { display:grid; grid-template-columns:repeat(4,1fr); gap:6px; text-align:center; margin:8px 0; }
.bagua-cell { padding:6px 4px; border-radius:4px; font-size:0.68em; line-height:1.5; }
@media (max-width:600px) {
.triangle-wrapper { width:220px; height:190px; margin:10px auto 0; }
.triangle-wrapper svg { width:220px; height:190px; }
.inner-number { font-size:1.6rem; }
.red-num { top:22%; }
.mid-left { top:52%; left:28%; } .mid-right { top:52%; right:28%; }
.bot-1 { bottom:10%; left:14%; } .bot-2 { bottom:10%; left:35%; }
.bot-3 { bottom:10%; right:35%; } .bot-4 { bottom:10%; right:14%; }
.side-num { font-size:1.6rem; }
.left-group { left:-32px; top:26%; } .right-group { right:-32px; top:26%; }
.top-numbers .num-top { font-size:1.6rem; }
.top-numbers .num-mid-row { font-size:1.6rem; gap:40px; }
.footer-date { gap:14px; font-size:1.2rem; letter-spacing:0; }
}
</style>
</head>
<body>
<div class="container">
<div style="text-align:center;margin-bottom:10px;">
<h1 style="font-size:1.5em;font-weight:400;letter-spacing:0.2em;color:#5a4636;margin:0 0 6px;">愈心谷数字解构</h1>
<div style="font-size:0.65em;color:#9b8a78;letter-spacing:0.12em;margin-top:2px;">✦ 输入公历出生年月日 ✦</div>
</div>
<div class="input-row">
<input type="number" id="inY" value="1986"><span class="lbl"></span>
<input type="number" id="inM" value="2"><span class="lbl"></span>
<input type="number" id="inD" value="17"><span class="lbl"></span>
<button class="btn-gen" id="goBtn">生成</button>
</div>
<div class="top-numbers">
<div class="num-top" id="valR">3</div>
<div class="num-mid-row"><span id="valQ">4</span><span id="valP">8</span></div>
</div>
<div class="triangle-wrapper">
<svg class="triangle-svg" viewBox="0 0 300 260">
<polygon points="150,10 290,250 10,250" class="triangle-path"/>
<line x1="85" y1="130" x2="215" y2="130" class="divider-line"/>
<line x1="55" y1="190" x2="245" y2="190" class="divider-line"/>
<line x1="150" y1="130" x2="150" y2="250" class="divider-line"/>
</svg>
<div class="inner-number red-num" id="valO" style="left:50%;transform:translateX(-50%)">7</div>
<div class="inner-number mid-left" id="valM">1</div>
<div class="inner-number mid-right" id="valN">6</div>
<div class="inner-number bot-1" id="valI">8</div>
<div class="inner-number bot-2" id="valJ">2</div>
<div class="inner-number bot-3" id="valK">1</div>
<div class="inner-number bot-4" id="valL">5</div>
<div class="side-decorations">
<div class="side-num left-group" style="display:flex;flex-direction:column;align-items:center;">
<div id="valU">3</div>
<div style="display:flex;gap:8px"><span id="valT">9</span><span id="valS">3</span></div>
</div>
<div class="side-num right-group" style="display:flex;flex-direction:column;align-items:center;">
<div id="valX">9</div>
<div style="display:flex;gap:8px"><span id="valV">7</span><span id="valW">2</span></div>
</div>
</div>
</div>
<div class="footer-date">
<span id="dateD">17</span><span id="dateM">02</span><span id="dateY">1986</span>
</div>
<div class="sections">
<div class="tabs-nav">
<button class="tab-btn active" data-tab="shuzi">数字心理学</button>
<button class="tab-btn" data-tab="meihua">梅花易数</button>
<button class="tab-btn" data-tab="xiaoliuren">小六壬</button>
<button class="tab-btn" data-tab="daliuren">大六壬</button>
<button class="tab-btn" data-tab="qimen">奇门遁甲</button>
<button class="tab-btn" data-tab="xingzuo">星座</button>
<button class="tab-btn" data-tab="wuxing">五行八卦</button>
</div>
<div class="tab-panel active" id="shuzi-panel">
<div class="missing" id="missingBlock"></div>
<div class="missing-short" id="missingMsg"></div>
<hr class="divider">
<div class="codes" id="codesBlock"></div>
<div class="joint" id="jointBlock"></div>
</div>
<div class="tab-panel" id="meihua-panel"></div>
<div class="tab-panel" id="xiaoliuren-panel"></div>
<div class="tab-panel" id="daliuren-panel"></div>
<div class="tab-panel" id="qimen-panel"></div>
<div class="tab-panel" id="xingzuo-panel"></div>
<div class="tab-panel" id="wuxing-panel"></div>
<div class="footer">内在探索 · 心灵成长</div>
</div>
</div>
<div class="tooltip" id="tip"></div>
<script>
function s1(n){while(n>=10)n=String(n).split('').reduce((a,b)=>a+ +b,0);return n}
function digs(n,l){return String(n).padStart(l,'0').split('').map(Number)}
function calc(y,m,d){
const dd=digs(d,2),mm=digs(m,2),yy=digs(y,4);
const A=dd[0],B=dd[1],C=mm[0],D=mm[1],E=yy[0],F=yy[1],G=yy[2],H=yy[3];
const v={AB:[A,B],CD:[C,D],EF:[E,F],GH:[G,H]};
v.I=s1(A+B);v.J=s1(C+D);v.K=s1(E+F);v.L=s1(G+H);
v.M=s1(v.I+v.J);v.N=s1(v.K+v.L);v.O=s1(v.M+v.N);
v.Q=s1(v.N+v.O);v.P=s1(v.M+v.O);v.R=s1(v.Q+v.P);
v.T=s1(v.I+v.M);v.S=s1(v.J+v.M);v.U=s1(v.T+v.S);
v.V=s1(v.K+v.N);v.W=s1(v.L+v.N);v.X=s1(v.V+v.W);
return v;
}
const tips={
O:v=>`${v.O}=${v.M}+${v.N}`,M:v=>`${v.M}=${v.I}+${v.J}`,N:v=>`${v.N}=${v.K}+${v.L}`,
I:v=>`${v.I}=${v.AB[0]}+${v.AB[1]}`,J:v=>`${v.J}=${v.CD[0]}+${v.CD[1]}`,
K:v=>`${v.K}=${v.EF[0]}+${v.EF[1]}`,L:v=>`${v.L}=${v.GH[0]}+${v.GH[1]}`,
R:v=>`${v.R}=${v.Q}+${v.P}`,Q:v=>`${v.Q}=${v.N}+${v.O}`,P:v=>`${v.P}=${v.M}+${v.O}`,
T:v=>`${v.T}=${v.I}+${v.M}`,S:v=>`${v.S}=${v.J}+${v.M}`,U:v=>`${v.U}=${v.T}+${v.S}`,
V:v=>`${v.V}=${v.K}+${v.N}`,W:v=>`${v.W}=${v.L}+${v.N}`,X:v=>`${v.X}=${v.V}+${v.W}`,
};
const NUM={
1:{title:'创造·领导',pos:'自信、独立、开创力、领导天赋',neg:'自我中心、孤独感、固执己见',job:'创业者、管理者、艺术家、设计师',elem:'金',desc:'1号人是天生的领导者,具有强烈的开创精神和独立意识。你目标明确,行动力强,能够独自完成许多事情。但你需要注意不要过于自我,学会倾听他人意见。'},
2:{title:'沟通·温柔',pos:'善于沟通、温柔体贴、合作能力强',neg:'依赖他人、优柔寡断、情绪化',job:'公关、客服、教育、心理咨询',elem:'水',desc:'2号人天生具有细腻的情感和出色的沟通能力。你善解人意,重视人际关系,是团队中的润滑剂。需要学会独立决断,避免过度依赖他人的认可。'},
3:{title:'行动·创意',pos:'行动力强、创意无限、表达力出众',neg:'急躁冲动、三分钟热度、缺乏耐心',job:'演员、讲师、销售、创意行业',elem:'木',desc:'3号人是天生的行动派,想到就做,充满创意和热情。你的表达能力和感染力极强,总能带动周围人的情绪。需要培养耐心,把创意落地为成果。'},
4:{title:'规划·稳定',pos:'善于规划、踏实稳重、注重细节',neg:'过于保守、缺乏弹性、安全感缺失',job:'工程师、会计、策划、项目管理',elem:'木',desc:'4号人是最可靠的建设者,做事有条不紊,追求安全与稳定。你擅长把复杂的事情拆解成清晰的步骤。需要学会接受变化,适当放松对完美的执念。'},
5:{title:'自由·方向',pos:'热爱自由、方向感强、幽默风趣',neg:'难以约束、逃避责任、缺乏定性',job:'旅行家、作家、自由职业、营销',elem:'土',desc:'5号人向往自由,喜欢冒险和新鲜事物。你天生的方向感让你总能在迷茫中找到出路。需要学会在自由与责任之间找到平衡。'},
6:{title:'智慧·财富',pos:'聪明睿智、财富直觉、追求完美',neg:'挑剔苛刻、浪费资源、过分焦虑',job:'金融、投资、咨询、医疗、法律',elem:'金',desc:'6号人是天生的智者,对财富和品质有敏锐的直觉。你追求完美,注重价值,具有强大的分析判断能力。需要学会放松标准,接纳不完美。'},
7:{title:'分析·真理',pos:'深入钻研、追求真理、洞察力强',neg:'冷漠孤僻、多疑猜忌、不善社交',job:'科学家、侦探、程序员、学者',elem:'水',desc:'7号人是天生的研究者,喜欢刨根问底,追求事物的本质和真相。你的洞察力和分析能力超群,但也容易陷入自己的世界。需要多与人交流,打开心扉。'},
8:{title:'责任·商业',pos:'责任心强、商业头脑、野心勃勃',neg:'压力过大、控制欲强、过度功利',job:'企业家、管理者、投资人、政治家',elem:'土',desc:'8号人天生具有强大的责任感和商业头脑,渴望成功并愿意为之付出努力。你有掌控大局的能力,但需注意不要让事业压力压垮自己。'},
9:{title:'博爱·成功',pos:'慈悲博爱、机会多多、容易成功',neg:'贪多嚼不烂、缺乏专注、过于理想化',job:'慈善家、演员、艺术家、宗教/灵修',elem:'火',desc:'9号人拥有广阔的胸怀和强大的亲和力,容易获得他人的信任和机会。你天生带有成功的气场,但需要学会聚焦,把精力集中到最重要的事情上。'},
};
const NUM_CLR={
1:{top:[232,160,160],front:[196,122,120],right:[168,96,96]},
2:{top:[240,200,160],front:[232,168,124],right:[208,144,96]},
3:{top:[238,208,128],front:[224,184,88],right:[200,152,64]},
4:{top:[160,216,112],front:[112,173,71],right:[88,136,48]},
5:{top:[144,200,240],front:[91,155,213],right:[64,120,176]},
6:{top:[240,168,176],front:[212,135,142],right:[176,104,112]},
7:{top:[184,168,216],front:[139,122,181],right:[104,88,152]},
8:{top:[160,144,128],front:[107,91,78],right:[80,64,48]},
9:{top:[244,236,224],front:[232,220,200],right:[208,192,168]},
};
const JOINT={
'112':'做事独立有主见','123':'讲师码,善于表达传授','134':'创意执行者,想法落地',
'145':'规划型领导','156':'五方求财,到处有机会','167':'白手起家,靠自己打拼',
'178':'成就他人,贵人相助','189':'工作狂,停不下来','191':'独立开创型',
'213':'讲师号,口才出众','224':'心软码,容易心软','235':'善于沟通表达',
'246':'贵人码,销售口才','257':'凭感觉做事','268':'重情重义',
'279':'异性缘佳','281':'大单销售高手','292':'军师码,运筹帷幄',
'314':'创意执行力','325':'感性沟通','336':'连接码,资源整合',
'347':'专业钻研型','358':'行动派领袖','369':'万花筒,多才多艺',
'415':'规划执行','426':'智慧销售','437':'学术研究型',
'448':'责任码,可靠担当','459':'规划码,策划高手','461':'稳扎稳打',
'472':'精准分析','483':'专业码,技术大牛','494':'完美规划',
'516':'方向感+智慧','527':'感性直觉','538':'领导力强',
'549':'规划型成功者','551':'自由方向感','562':'学会花钱',
'573':'贵人相助型','584':'责任+规划','595':'大梦想家',
'617':'白手起家码','628':'财富+沟通','639':'多才多艺',
'641':'务实经营','652':'财富直觉','663':'完美财富',
'674':'天生傲骨,不甘平凡','685':'商业责任','696':'追求极致',
'718':'钻研+责任','729':'异性缘,桃花旺','731':'创新领导',
'742':'分析谋划','753':'凭直觉行动','764':'精打细算',
'775':'领袖码,天生王者','786':'商业责任','797':'极致钻研',
'819':'亲力亲为,事必躬亲','821':'大单销售高手','832':'行动沟通',
'843':'专业技能型','854':'规划+自由','865':'财富责任',
'876':'商业王者','887':'强势责任','898':'大事业格局',
'911':'博爱独立','922':'温柔善良','933':'手艺码,巧手匠心',
'944':'稳妥计划','955':'自由博爱','966':'完美智慧',
'977':'深入博爱','988':'商业帝国','999':'极致成功',
};
const MISS_MSG={
1:'缺乏自信与领导力,需要培养独立决断的能力',2:'沟通表达不足,需要打开心扉多与人交流',
3:'缺乏行动力与创意,需要勇敢迈出第一步',4:'缺乏规划与安全感,需要建立稳定的生活节奏',
5:'缺乏方向感与自由,需要找到真正热爱的事物',6:'缺乏智慧与财富意识,需要培养理财能力',
7:'缺乏深入钻研的精神,需要培养专注与耐心',8:'缺乏责任感与事业心,需要找到值得奋斗的目标',
9:'缺乏博爱与认同感,需要学会接纳自己与他人',
};
const MISS_STRONG={
1:'自信心爆棚,但需注意不要过于自我中心',2:'沟通能力过强,需注意避免情绪化依赖',
3:'行动力过剩,需注意避免冲动急躁',4:'过于求稳,需注意避免保守固执',
5:'过度自由,需注意避免放纵逃避',6:'完美主义过强,需注意避免挑剔焦虑',
7:'钻研过度,需注意避免孤僻多疑',8:'责任感过重,需注意避免压力过大',
9:'博爱过度,需注意避免贪多不精',
};
// ===== Dimetric 投影 + Z 轴旋转 —— 底座天然水平 =====
let rotZ = -0.55; // Z 轴旋转角(弧度)
let dragActive=false, dragPX=0;
let currentFreq={};
function proj(x,y,z,rz) {
// Z 轴旋转 + 45° dimetric 投影
const cr=Math.cos(rz), sr=Math.sin(rz);
const xr=x*cr-y*sr, yr=x*sr+y*cr;
return { sx:yr-xr, sy:-(xr+yr)+z*0.7, depth:-(xr+yr)+z };
}
function faceVis(face, rz) {
const cr=Math.cos(rz), sr=Math.sin(rz);
switch(face) {
case 'right': return cr+sr>0;
case 'left': return cr+sr<0;
case 'front': return cr-sr>0;
case 'back': return cr-sr<0;
case 'top': return true;
case 'bottom':return false;
}
}
function faceCorns(cx,cy,cz,hs,face) {
const h=hs;
switch(face) {
case 'top': return [[cx-h,cy-h,cz+h],[cx+h,cy-h,cz+h],[cx+h,cy+h,cz+h],[cx-h,cy+h,cz+h]];
case 'bottom': return [[cx-h,cy-h,cz-h],[cx+h,cy-h,cz-h],[cx+h,cy+h,cz-h],[cx-h,cy+h,cz-h]];
case 'front': return [[cx-h,cy+h,cz-h],[cx+h,cy+h,cz-h],[cx+h,cy+h,cz+h],[cx-h,cy+h,cz+h]];
case 'back': return [[cx-h,cy-h,cz-h],[cx+h,cy-h,cz-h],[cx+h,cy-h,cz+h],[cx-h,cy-h,cz+h]];
case 'right': return [[cx+h,cy-h,cz-h],[cx+h,cy+h,cz-h],[cx+h,cy+h,cz+h],[cx+h,cy-h,cz+h]];
case 'left': return [[cx-h,cy-h,cz-h],[cx-h,cy+h,cz-h],[cx-h,cy+h,cz+h],[cx-h,cy-h,cz+h]];
}
}
function renderScene(canvas, freq, rz) {
const dpr=window.devicePixelRatio||1;
const mob=window.innerWidth<=600;
const CS=mob?20:26;
const STEP=CS*2+(mob?2:4);
const DH=CS*2+(mob?1:2);
const PAD=mob?30:40;
const cols={};
for(let n=1;n<=9;n++){
const row=2-Math.floor((n-1)/3), col=(n-1)%3;
cols[n]={wx:(col-1)*STEP,wy:(row-1)*STEP};
}
// 计算包围盒
let mnX=Infinity,mxX=-Infinity,mnY=Infinity,mxY=-Infinity;
for(let n=1;n<=9;n++){
const {wx,wy}=cols[n];
const layers=Math.max(1,freq[n]||1);
for(let l=0;l<layers;l++){
const wz=l*DH;
const cs=[[wx-CS,wy-CS,wz],[wx+CS,wy-CS,wz],[wx+CS,wy+CS,wz],[wx-CS,wy+CS,wz],
[wx-CS,wy-CS,wz+CS*2],[wx+CS,wy-CS,wz+CS*2],[wx+CS,wy+CS,wz+CS*2],[wx-CS,wy+CS,wz+CS*2]];
cs.forEach(c=>{const p=proj(c[0],c[1],c[2],rz);mnX=Math.min(mnX,p.sx);mxX=Math.max(mxX,p.sx);mnY=Math.min(mnY,p.sy);mxY=Math.max(mxY,p.sy);});
}
}
// 底座平台包围盒
const bw=STEP*2+CS*2+16, bz=-CS-3;
[[-bw,-bw,bz],[bw,-bw,bz],[bw,bw,bz],[-bw,bw,bz]].forEach(c=>{
const p=proj(c[0],c[1],c[2],rz);mnX=Math.min(mnX,p.sx);mxX=Math.max(mxX,p.sx);mnY=Math.min(mnY,p.sy);mxY=Math.max(mxY,p.sy);
});
const viewW=Math.max(mxX-mnX+PAD*2,mob?300:400);
const viewH=Math.max(mxY-mnY+PAD*2,mob?320:420);
canvas.width=Math.ceil(viewW*dpr);canvas.height=Math.ceil(viewH*dpr);
canvas.style.width=Math.ceil(viewW)+'px';canvas.style.height=Math.ceil(viewH)+'px';
const ctx=canvas.getContext('2d');ctx.scale(dpr,dpr);ctx.clearRect(0,0,viewW,viewH);
const centerX=(mxX+mnX)/2, ox=viewW/2-centerX, oy=viewH-PAD-mxY;
const faces=[];
const AF=['top','bottom','front','back','right','left'];
// 底座平台(厚块)
(()=>{
const bt=mob?4:5, bzTop=-CS-2, bzBot=bzTop-bt;
const bclr=[170,155,135];
[
{t:'top', c:[[-bw,-bw,bzTop],[bw,-bw,bzTop],[bw,bw,bzTop],[-bw,bw,bzTop]],clr:bclr},
{t:'front', c:[[-bw,bw,bzBot],[bw,bw,bzBot],[bw,bw,bzTop],[-bw,bw,bzTop]],clr:bclr.map(v=>Math.floor(v*0.85))},
{t:'right', c:[[bw,-bw,bzBot],[bw,bw,bzBot],[bw,bw,bzTop],[bw,-bw,bzTop]],clr:bclr.map(v=>Math.floor(v*0.7))},
].forEach(fb=>{
const pts=fb.c.map(c=>proj(c[0],c[1],c[2],rz));
const avgZ=pts.reduce((s,p)=>s+p.depth,0)/4;
const projPts=pts.map(p=>[ox+p.sx,oy-p.sy]);
faces.push({z:avgZ,proj:projPts,color:fb.clr,isTop:fb.t==='top',num:0});
});
})();
// 方块面
for(let n=1;n<=9;n++){
const {wx,wy}=cols[n];
const layers=Math.max(1,freq[n]||1);
const clr=NUM_CLR[n];
const fc={top:clr.top,bottom:clr.top,front:clr.front,back:clr.front,right:clr.right,left:clr.right};
for(let l=0;l<layers;l++){
const wz=l*DH;
for(const face of AF){
if(!faceVis(face,rz))continue;
const crns=faceCorns(wx,wy,wz,CS,face);
const pts=crns.map(c=>proj(c[0],c[1],c[2],rz));
const avgZ=pts.reduce((s,p)=>s+p.depth,0)/4;
const projPts=pts.map(p=>[ox+p.sx,oy-p.sy]);
const cnt=proj(wx,wy,wz+CS,rz);
faces.push({z:avgZ,proj:projPts,color:fc[face],isTop:face==='top',num:n,cx:ox+cnt.sx,cy:oy-cnt.sy});
}
}
}
faces.sort((a,b)=>a.z-b.z);
const alpha=0.82;
for(const f of faces){
const [r,g,b]=f.color;
ctx.fillStyle=`rgba(${r},${g},${b},${alpha})`;
ctx.beginPath();ctx.moveTo(f.proj[0][0],f.proj[0][1]);
for(let i=1;i<4;i++)ctx.lineTo(f.proj[i][0],f.proj[i][1]);
ctx.closePath();ctx.fill();
if(f.isTop&&f.num){
ctx.fillStyle='rgba(255,255,255,0.92)';
const fs=mob?11:14;ctx.font=`bold ${fs}px Georgia,"Noto Serif SC",serif`;
ctx.textAlign='center';ctx.textBaseline='middle';
ctx.shadowColor='rgba(0,0,0,0.25)';ctx.shadowBlur=2;
ctx.fillText(f.num,f.cx,f.cy);
ctx.shadowColor='transparent';ctx.shadowBlur=0;
}
}
// 底部 2D 横条
(()=>{
const bh=mob?18:22,by=viewH-bh;
const grad=ctx.createLinearGradient(0,by,0,viewH);
grad.addColorStop(0,'rgba(180,165,145,0.5)');
grad.addColorStop(0.3,'rgba(160,145,125,0.35)');
grad.addColorStop(1,'rgba(140,125,105,0.1)');
ctx.fillStyle=grad;ctx.fillRect(0,by,viewW,bh);
ctx.strokeStyle='rgba(140,125,105,0.45)';ctx.lineWidth=1;
ctx.beginPath();ctx.moveTo(0,by);ctx.lineTo(viewW,by);ctx.stroke();
})();
}
function drawScene(){
const canvas=document.querySelector('#scene3d canvas');
if(!canvas)return;
renderScene(canvas,currentFreq,rotZ);
}
function setupRotation(){
const el=document.querySelector('#scene3d');if(!el)return;
function getX(e){return e.touches&&e.touches.length?e.touches[0].clientX:e.clientX}
function onStart(e){dragActive=true;dragPX=getX(e);e.preventDefault()}
function onMove(e){
if(!dragActive)return;
const cx=getX(e),dx=cx-dragPX;dragPX=cx;
rotZ+=dx*0.008;drawScene();e.preventDefault();
}
function onEnd(){dragActive=false}
el.addEventListener('mousedown',onStart);
el.addEventListener('touchstart',onStart,{passive:false});
window.addEventListener('mousemove',onMove);
window.addEventListener('touchmove',onMove,{passive:false});
window.addEventListener('mouseup',onEnd);
window.addEventListener('touchend',onEnd);
}
// ===== 解读模块生成器 =====
const GUA64=['','乾为天','天泽履','天火同人','天雷无妄','天风姤','天水讼','天山遁','天地否','泽天夬','兑为泽','泽火革','泽雷随','泽风大过','泽水困','泽山咸','泽地萃','火天大有','火泽睽','离为火','火雷噬嗑','火风鼎','火水未济','火山旅','火地晋','雷天大壮','雷泽归妹','雷火丰','震为雷','雷风恒','雷水解','雷山小过','雷地豫','风天小畜','风泽中孚','风火家人','风雷益','巽为风','风水涣','风山渐','风地观','水天需','水泽节','水火既济','水雷屯','水风井','坎为水','水山蹇','水地比','山天大畜','山泽损','山火贲','山雷颐','山风蛊','山水蒙','艮为山','山地剥','地天泰','地泽临','地火明夷','地雷复','地风升','地水师','地山谦','坤为地'];
const GUA64_DESC={'乾为天':'刚健中正,纯阳之卦。运势旺盛,宜主动进取。','坤为地':'柔顺包容,厚德载物。耐心守成,终得善果。','水雷屯':'万事起头难。创业维艰,宜稳扎稳打。','山水蒙':'蒙昧初开。宜虚心求教,不可自作聪明。','水天需':'等待时机。耐心守候,时机未到不可强求。','天水讼':'争执诉讼之象。退一步海阔天空。','地水师':'统兵出征。纪律严明,以正制邪。','水地比':'亲附和合。广结善缘,必得助力。','地天泰':'天地交泰。万事亨通,上下和合。','天地否':'闭塞不通。宜隐忍守正,静待时机。','天火同人':'志同道合。同心协力可成大事。','火天大有':'丰收大有。顺天应时,财源广进。','雷天大壮':'强盛壮大。力量充沛但切忌恃强凌弱。','火地晋':'旭日东升。逐步晋升,光明在前。','地火明夷':'光明受损。韬光养晦,等待天明。','风火家人':'家庭和乐。修身齐家,家和万事兴。','火泽睽':'乖离不合。求同存异,小事可成。','水山蹇':'艰难险阻。退守求稳,不可强闯。','雷水解':'困难解除。柳暗花明,危机化解。','山泽损':'损下益上。有舍有得,吃亏是福。','风雷益':'增益有利。抓住机遇,积极作为。','泽天夬':'决断果断。当断则断,不受其乱。','天风姤':'不期而遇。随缘而处,不可强求。','泽地萃':'聚集荟萃。人气旺盛,利于合作。','地风升':'步步高升。循序渐进,终达目标。','泽水困':'困顿之象。坚守正道,终可脱困。','水风井':'修德养民。滋养根本,源远流长。','泽火革':'变革之时。顺势而变,除旧布新。','火风鼎':'鼎新革故。稳固根基,创新求变。','震为雷':'震动警醒。临危不乱,化险为夷。','艮为山':'知止不殆。适可而止,安守本分。','风山渐':'循序渐进。按部就班,水到渠成。','雷泽归妹':'婚嫁之象。以诚相待,终成眷属。','雷火丰':'丰盛之时。月满则亏,知足常乐。','火山旅':'行旅之象。随遇而安,莫问归期。','巽为风':'柔顺渗透。以柔克刚,润物无声。','兑为泽':'喜悦沟通。以和为贵,广结善缘。','风水涣':'涣散离散。聚拢人心,凝聚力量。','水泽节':'节制有度。合理规划,量力而行。','风泽中孚':'诚信感召。以诚待人,金石为开。','雷山小过':'小有过失。宜下不宜上,宜小不宜大。','水火既济':'事已成就。居安思危,防微杜渐。','火水未济':'未竟之业。持之以恒,终将成功。','地山谦':'谦逊受益。虚怀若谷,众望所归。','雷地豫':'安乐愉悦。居安思危,不可懈怠。','泽雷随':'随顺时势。灵活应变,顺势而为。','山风蛊':'蛊坏整治。拨乱反正,振弊起衰。','地泽临':'居高临下。亲近下属,恩威并施。','风地观':'观览体察。审时度势,明察秋毫。','火雷噬嗑':'咬合刑罚。明断是非,刚正不阿。','山火贲':'文饰美化。返璞归真,内在为重。','山地剥':'剥落凋零。守正待时,顺势而为。','地雷复':'一阳来复。否极泰来,新生在望。','天雷无妄':'真实无妄。顺其自然,不妄作为。','山天大畜':'蓄积力量。厚积薄发,大器晚成。','山雷颐':'颐养之道。养生养德,自求口实。','泽风大过':'过度之象。刚柔相济,中庸为贵。','坎为水':'重重险难。坚守诚信,终可出险。','离为火':'光明依附。柔顺中正,照亮四方。'};
function meihuaHTML(y,m,d){
const dz=['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
const dzNum={子:1,丑:2,寅:3,卯:4,辰:5,巳:6,午:7,未:8,申:9,酉:10,戌:11,亥:12};
const tg=['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
const guaName=['','乾 ☰','兑 ☱','离 ☲','震 ☳','巽 ☴','坎 ☵','艮 ☶','坤 ☷'];
const guaEle=['','金','金','火','木','木','水','土','土'];
const ndz=dz[(y-4)%12],ntg=tg[(y-4)%10],nDZ=dzNum[ndz];
const upNum=nDZ%8||8,downNum=(m+d)%8||8,dongY=(nDZ+m+d)%6||6;
const benGuaIdx=(upNum-1)*8+downNum;
const benName=GUA64[benGuaIdx]||(guaName[upNum]+'+'+guaName[downNum]);
const varUp=dongY<=3?upNum:((upNum^5)||8),varDown=dongY<=3?((downNum^5)||8):downNum;
const varIdx=(varUp-1)*8+varDown;
const varName=GUA64[varIdx]||(guaName[varUp]+'+'+guaName[varDown]);
const desc=GUA64_DESC[benName]||'卦象已显,静心体悟其中玄机。';
const dongYaoNames=['初爻','二爻','三爻','四爻','五爻','上爻'];
const ue=guaEle[upNum],de=guaEle[downNum];
const shengKe={'金':'木','木':'土','土':'水','水':'火','火':'金'};
let tiYong;
if(upNum===downNum)tiYong='体用比和,万事顺遂';
else if(shengKe[ue]===de)tiYong='体克用,事可成但需费心力';
else if(shengKe[de]===ue)tiYong='用克体,宜守不宜攻,等待时机';
else tiYong='体用相生,遇贵人之象,万事可成';
return `<div class="module-title">✦ ${benName} ✦</div>
<div class="module-desc">${ntg}${ndz}年 · 上卦${guaName[upNum]} 下卦${guaName[downNum]} · 动在${dongYaoNames[dongY-1]}</div>
<div class="module-section"><b>▸ 本卦:${benName}</b><br><span style="color:#9b8a78">${desc}</span><br><br><b>▸ 变卦:${varName}</b><br><span style="color:#9b8a78">${GUA64_DESC[varName]||'变动已生,观其变化而知吉凶。'}</span><br><br><b>▸ 体用生克</b><br>上卦属${ue}·下卦属${de} — ${tiYong}</div>`;
}
const XLR_NAMES=['','大安','留连','速喜','赤口','小吉','空亡'];
const XLR_DESC={'大安':'大吉。光明正大,平安顺遂。求财向东南,凡事安稳。','留连':'凶。事难成就,去者未归。宜静不宜动,急则生变。','速喜':'大吉。喜事将至,向南吉利,行人有信至。','赤口':'凶。口舌是非,宜守不宜攻,失物难寻。','小吉':'大吉。好事将近,路遇贵人,向西南吉利。','空亡':'大凶。谋事落空,宜静待时机,强求反遭祸。'};
function xiaoLiuRenHTML(y,m,d){
const mo=m>12?m-12:m,shi=7;let pos=1;
for(let i=1;i<mo;i++)pos=pos%6+1;
for(let i=1;i<d;i++)pos=pos%6+1;
for(let i=1;i<shi;i++)pos=pos%6+1;
const name=XLR_NAMES[pos],isGood=name==='大安'||name==='速喜'||name==='小吉';
return `<div class="module-title">✦ 小六壬 掌诀:${name} ✦</div>
<div class="module-desc">月上起日,日上起时(午时)→ 最终落位</div>
<div class="module-section"><span style="font-size:1.6em;font-weight:700;color:${isGood?'#70ad47':'#c47a78'}">${name}</span><br><span style="color:#9b8a78">${XLR_DESC[name]}</span><br><br><b>▸ 六掌速查</b><br><span style="color:#70ad47">大安</span> · <span style="color:#c47a78">留连</span> · <span style="color:#70ad47">速喜</span> · <span style="color:#c47a78">赤口</span> · <span style="color:#70ad47">小吉</span> · <span style="color:#c47a78">空亡</span></div>`;
}
function daLiuRenHTML(y,m,d){
const dz=['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
const ydz=dz[(y-4)%12],yueJiangMap={1:'子',2:'亥',3:'戌',4:'酉',5:'申',6:'未',7:'午',8:'巳',9:'辰',10:'卯',11:'寅',12:'丑'};
return `<div class="module-title">✦ 大六壬 课式概要 ✦</div>
<div class="module-desc">${y}年(${ydz}年)· ${m}月 月将${yueJiangMap[m]||'午'} · 占时午</div>
<div class="module-section"><b>▸ 简介</b><br><span style="color:#9b8a78">六壬为三式之首,以月将加占时起天地盘,排四课发三传断吉凶。演算极精(12天将+12神煞+遁干),此处展示基础课式。</span><br><br><b>▸ 天地盘</b><br><span style="color:#9b8a78">地盘:子丑寅卯辰巳午未申酉戌亥(固定)<br>天盘:月将加临占时之上,顺布十二辰</span><br><br><b>▸ 四课</b><br><span style="color:#9b8a78">第一课日干阳神 · 第二课日干阴神 · 第三课日支阳神 · 第四课日支阴神</span><br><br><b>▸ 三传</b><br><span style="color:#9b8a78">初传(发用·起始)→ 中传(移易·过程)→ 末传(归计·结局)</span><br><br><b>▸ 九宗门</b><br><span style="color:#9b8a78">贼克·比用·涉害·遥克·昴星·别责·八专·伏吟·反吟</span><br><br><span style="color:#b8a898;font-size:0.95em">※ 完整排盘需配合具体时辰与日干支,建议请六壬师详断。</span></div>`;
}
function qiMenHTML(y,m,d){
const dz=['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
const tg=['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
const mingGongMap={1:'丑',2:'寅',3:'卯',4:'辰',5:'巳',6:'午',7:'未',8:'申',9:'酉',10:'戌',11:'亥',12:'子'};
return `<div class="module-title">✦ 奇门遁甲 命盘概要 ✦</div>
<div class="module-desc">${tg[(y-4)%10]}${dz[(y-4)%12]}年 · 命宫:${mingGongMap[m]||'午'}</div>
<div class="module-section"><b>▸ 简介</b><br><span style="color:#9b8a78">奇门合天(九星)、地(九宫)、人(八门)、神(八神)四层于一体。排盘需节气+时辰+用局,极为精密。</span><br><br><b>▸ 地盘九宫</b><br><span style="color:#9b8a78">戴九履一·左三右七·二四为肩·六八为足·五居中央<br>坎一水·坤二土·震三木·巽四木·中五土·乾六金·兑七金·艮八土·离九火</span><br><br><b>▸ 八门(人事)</b><br><span style="color:#9b8a78">开门(吉)·休门(吉)·生门(吉)·伤门(凶)·杜门(平)·景门(平)·死门(凶)·惊门(凶)</span><br><br><b>▸ 九星(天时)</b><br><span style="color:#9b8a78">天蓬·天任·天冲·天辅·天英·天芮·天柱·天心·天禽</span><br><br><span style="color:#b8a898;font-size:0.95em">※ 全面排盘需根据具体时辰和节气确定阳遁/阴遁几局,建议请奇门师详断。</span></div>`;
}
const ZODIAC=[
{name:'摩羯座',emoji:'♑',elem:'土',from:[12,22],to:[1,19],trait:'坚韧务实,目标明确,大器晚成',lucky:'深绿·黑'},
{name:'水瓶座',emoji:'♒',elem:'风',from:[1,20],to:[2,18],trait:'独立创新,思维超前,人道主义',lucky:'蓝·银'},
{name:'双鱼座',emoji:'♓',elem:'水',from:[2,19],to:[3,20],trait:'浪漫敏感,富有同情心,艺术天赋',lucky:'紫·海蓝'},
{name:'白羊座',emoji:'♈',elem:'火',from:[3,21],to:[4,19],trait:'热情勇敢,行动力强,开拓先锋',lucky:'红·金'},
{name:'金牛座',emoji:'♉',elem:'土',from:[4,20],to:[5,20],trait:'稳健可靠,审美出众,坚定执着',lucky:'粉·绿'},
{name:'双子座',emoji:'♊',elem:'风',from:[5,21],to:[6,21],trait:'机智灵活,善于沟通,双面魅力',lucky:'黄·银'},
{name:'巨蟹座',emoji:'♋',elem:'水',from:[6,22],to:[7,22],trait:'温柔顾家,情感丰富,保护欲强',lucky:'白·银'},
{name:'狮子座',emoji:'♌',elem:'火',from:[7,23],to:[8,22],trait:'自信大方,领导力强,光芒四射',lucky:'金·橙'},
{name:'处女座',emoji:'♍',elem:'土',from:[8,23],to:[9,22],trait:'细致严谨,追求完美,服务精神',lucky:'灰·米'},
{name:'天秤座',emoji:'♎',elem:'风',from:[9,23],to:[10,23],trait:'优雅公正,善于平衡,社交达人',lucky:'粉·蓝'},
{name:'天蝎座',emoji:'♏',elem:'水',from:[10,24],to:[11,22],trait:'深沉敏锐,意志坚定,极致深情',lucky:'深红·黑'},
{name:'射手座',emoji:'♐',elem:'火',from:[11,23],to:[12,21],trait:'乐观自由,探索精神,哲学思维',lucky:'紫·深蓝'},
];
function xingZuoHTML(y,m,d){
let z=ZODIAC.find(x=>(m===x.from[0]&&d>=x.from[1])||(m===x.to[0]&&d<=x.to[1]));
if(!z){if((m===12&&d>=22)||(m===1&&d<=19))z=ZODIAC[0];else if(m===1)z=ZODIAC[1];else z=ZODIAC.find(x=>m===x.from[0]&&d<x.from[1])||ZODIAC[0];}
const et={火:'热情主动,充满生命力,敢于冒险',土:'踏实稳重,注重现实,可靠可信',风:'思维敏捷,善于交流,适应力强',水:'情感深邃,直觉敏锐,富有创造力'};
const ec={火:'#c47a78',土:'#8b7a68',风:'#91a3b0',水:'#6b9eb3'};
return `<div class="module-title">✦ ${z.emoji} ${z.name} ✦</div>
<div class="module-desc">${z.elem}象星座 · 幸运色:${z.lucky}</div>
<div class="module-section"><span style="font-size:2em">${z.emoji}</span><br><b>▸ 性格特质</b><br><span style="color:#9b8a78">${z.trait}</span><br><br><b>▸ 元素能量</b><br><span style="color:#9b8a78">${et[z.elem]}</span><br><br><b>▸ 十二星座</b><br>${ZODIAC.map(x=>`<span style="color:${ec[x.elem]}">${x.emoji}</span>`).join(' ')}</div>`;
}
const BAGUA=[
{name:'乾 ☰',xiang:'天',elem:'金',clr:'#d4c5a0',txt:'#8b7355',desc:'刚健·创造'},
{name:'兑 ☱',xiang:'泽',elem:'金',clr:'#e8d5c8',txt:'#a08070',desc:'喜悦·沟通'},
{name:'离 ☲',xiang:'火',elem:'火',clr:'#e8c8c0',txt:'#b06050',desc:'光明·智慧'},
{name:'震 ☳',xiang:'雷',elem:'木',clr:'#c8d8b0',txt:'#5a7a3a',desc:'行动·震动'},
{name:'巽 ☴',xiang:'风',elem:'木',clr:'#b8d0b8',txt:'#4a7a4a',desc:'渗透·柔顺'},
{name:'坎 ☵',xiang:'水',elem:'水',clr:'#a8c8d8',txt:'#3a6a8a',desc:'险陷·智慧'},
{name:'艮 ☶',xiang:'山',elem:'土',clr:'#d0c8b0',txt:'#7a6a4a',desc:'静止·坚守'},
{name:'坤 ☷',xiang:'地',elem:'土',clr:'#e0d8c0',txt:'#8a7a5a',desc:'柔顺·包容'},
];
function wuXingHTML(y,m,d){
const yElem=['金','水','木','火','土'][Math.floor(((y-4)%10)/2)];
const mElem=['木','木','土','火','火','土','金','金','土','水','水','土'][m-1];
const dElem=['木','火','土','金','水'][d%5];
const ec={金:'#d4c5a0',水:'#a8c8d8',木:'#b8d0b8',火:'#e8c8c0',土:'#e0d8c0'};
return `<div class="module-title">✦ 五行八卦 能量图谱 ✦</div>
<div class="module-desc">年柱${yElem} · 月柱${mElem} · 日柱${dElem}</div>
<div class="module-section"><b>▸ 五行</b><br>
<div style="display:flex;gap:4px;margin:6px 0">${['金','水','木','火','土'].map(e=>`<span style="flex:1;text-align:center;padding:4px;background:${ec[e]};color:#5a4636;border-radius:3px;font-size:0.95em"><b>${e}</b></span>`).join('')}</div>
<span style="color:#9b8a78">金(义)·水(智)·木(仁)·火(礼)·土(信)<br>相生:金→水→木→火→土→金<br>相克:金→木→土→水→火→金</span><br><br><b>▸ 八卦</b><br>
<div class="bagua-grid">${BAGUA.map(b=>`<div class="bagua-cell" style="background:${b.clr};color:${b.txt}"><b>${b.name}</b><br><span style="font-size:0.92em">${b.elem}·${b.xiang}<br>${b.desc}</span></div>`).join('')}</div>
<span style="color:#9b8a78">八卦歌诀:乾三连·坤六断·震仰盂·艮覆碗·离中虚·坎中满·兑上缺·巽下断</span></div>`;
}
// ===== Tab 切换 =====
function initTabs(){
const nav=document.querySelector('.tabs-nav');
if(!nav)return;
nav.addEventListener('click',e=>{
const btn=e.target.closest('.tab-btn');
if(!btn)return;
const tab=btn.dataset.tab;
nav.querySelectorAll('.tab-btn').forEach(b=>b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.tab-panel').forEach(p=>p.classList.remove('active'));
const panel=document.getElementById(tab+'-panel');
if(panel)panel.classList.add('active');
});
}
function update(v,y,m,d){
document.getElementById('valR').textContent=v.R;
document.getElementById('valQ').textContent=v.Q;
document.getElementById('valP').textContent=v.P;
document.getElementById('valO').textContent=v.O;
document.getElementById('valM').textContent=v.M;
document.getElementById('valN').textContent=v.N;
document.getElementById('valI').textContent=v.I;
document.getElementById('valJ').textContent=v.J;
document.getElementById('valK').textContent=v.K;
document.getElementById('valL').textContent=v.L;
document.getElementById('valU').textContent=v.U;
document.getElementById('valT').textContent=v.T;
document.getElementById('valS').textContent=v.S;
document.getElementById('valX').textContent=v.X;
document.getElementById('valV').textContent=v.V;
document.getElementById('valW').textContent=v.W;
document.getElementById('dateD').textContent=String(d).padStart(2,'0');
document.getElementById('dateM').textContent=String(m).padStart(2,'0');
document.getElementById('dateY').textContent=y;
const allNums=[0,1,2,3,4,5,6,7,8,9];
const inner=new Set([v.O,v.M,v.N,v.I,v.J,v.K,v.L]);
const present=new Set([...inner,v.Q,v.P,v.R,v.T,v.S,v.U,v.V,v.W,v.X,v.AB[0],v.AB[1],v.CD[0],v.CD[1],v.EF[0],v.EF[1],v.GH[0],v.GH[1]]);
const miss={inner:allNums.filter(n=>!inner.has(n)),outer:allNums.filter(n=>![v.Q,v.P,v.R,v.T,v.S,v.U,v.V,v.W,v.X].includes(n)),both:allNums.filter(n=>!present.has(n))};
const n=NUM[v.O]||{};
const freq={};
for(let i=1;i<=9;i++)freq[i]=0;
const allVals=[v.O,v.M,v.N,v.I,v.J,v.K,v.L,v.Q,v.P,v.R,v.T,v.S,v.U,v.V,v.W,v.X,
v.AB[0],v.AB[1],v.CD[0],v.CD[1],v.EF[0],v.EF[1],v.GH[0],v.GH[1]];
allVals.forEach(x=>{if(x>=1&&x<=9)freq[x]++;});
currentFreq=freq;
const freqArr=Object.entries(freq).sort((a,b)=>b[1]-a[1]);
const most=freqArr[0],least=freqArr.filter(([,c])=>c===freqArr[freqArr.length-1][1]);
let html=`<div style="text-align:center;margin-bottom:18px;">`;
html+=`<div style="font-size:2.4em;font-weight:700;color:#c47a78;line-height:1;margin-bottom:4px;">${v.O}号人</div>`;
html+=`<div style="font-size:0.85em;font-weight:600;color:#5a4636;margin-bottom:6px;">${n.title||''}</div>`;
html+=`<div style="font-size:0.75em;color:#6b5b4e;line-height:1.7;max-width:340px;margin:0 auto;text-align:left;">${n.desc||''}</div>`;
html+=`<div style="font-size:0.7em;color:#9b8a78;margin-top:8px;line-height:1.7;text-align:left;max-width:340px;margin-left:auto;margin-right:auto;">`;
html+=`<b>✦ 天赋优势</b>${n.pos||''}<br><b>✦ 人生课题</b>${n.neg||''}<br><b>✦ 五行属性</b>${n.elem||''}<br><b>✦ 适合职业</b>${n.job||''}</div></div>`;
html+=`<hr class="divider">`;
html+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:8px;">✦ 数字能量分布 ✦</div>`;
html+=`<div style="font-size:0.7em;color:#6b5b4e;text-align:center;margin-bottom:0;">你的生命三角中共有 <b>${allVals.filter(x=>x>=1).length}</b> 个有效数字</div>`;
html+=`<div class="scene-3d" id="scene3d"><canvas></canvas><div class="scene-hint">↻ 左右旋转</div></div>`;
html+=`<div class="freq-label">✦ 能量最强:<b>${most[0]}</b>(出现 ${most[1]} 次)— 该数字特质在你生命中最为突出</div>`;
if(least.length>0)html+=`<div class="freq-label" style="margin-top:0;">✦ 能量最弱:${least.map(([n])=>`<b>${n}</b>`).join('、')}(仅 ${least[0][1]} 次)— 需刻意培养相关特质</div>`;
html+=`<hr class="divider">`;
html+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 三角内外含义 ✦</div>`;
html+=`<div style="font-size:0.7em;line-height:1.7;color:#6b5b4e;padding:0 4px;">`;
html+=`<div><b>▸ 三角内部</b>:代表你内在真实的自我——潜意识的驱动、与生俱来的性格特质、不为人知的内心世界。</div>`;
html+=`<div style="margin-top:3px;"><b>▸ 三角外部</b>:代表外界对你的认知——你在工作、家庭、社交中呈现出的样子,以及他人眼中的你。</div>`;
html+=`<div style="margin-top:3px;"><b>▸ 下方基因</b>(父母遗传):代表来自父亲和母亲的先天遗传基因,是你性格的底层土壤。</div></div>`;
html+=`<hr class="divider">`;
html+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 性格编码解读 ✦</div>`;
html+=`<div style="font-size:0.7em;line-height:1.8;color:#6b5b4e;padding:0 4px;">`;
html+=`<div style="margin-bottom:4px;"><b>潜意识码:${v.I}${v.J}${v.K}${v.L}</b></div>`;
html+=`<div style="color:#9b8a78;margin-bottom:8px;">这是你内心的真实驱动力,代表你最本能的反应模式。四个数字分别对应你的思维方式、情感表达、行动模式和人际风格。</div>`;
html+=`<div style="margin-bottom:4px;"><b>内心码:${v.M}${v.N}</b></div>`;
html+=`<div style="color:#9b8a78;margin-bottom:8px;">揭示你深层情感模式和核心价值观。两个数字分别主宰理性行动和感性人际,两者的平衡决定你如何面对压力。</div>`;
html+=`<div style="margin-bottom:4px;"><b>坐镇码:${v.I}${v.M}${v.O}</b></div>`;
html+=`<div style="color:#9b8a78;margin-bottom:8px;">性格核心轴线,由潜意识→内心→主性格串联而成,代表你从内在驱动到最终呈现的完整性格链条。是你最稳定、最不易改变的性格底色。</div>`;
html+=`<div style="margin-bottom:4px;"><b>父亲基因:${v.EF.join('')} ${v.GH.join('')}</b> · <b>母亲基因:${v.AB.join('')} ${v.CD.join('')}</b></div>`;
html+=`<div style="color:#9b8a78;">父亲基因(年份 ${v.EF.join('')}${v.GH.join('')})承载家族传承和事业基因,母亲基因(月${v.CD.join('')} 日${v.AB.join('')})影响情感模式和人际关系基础。</div></div>`;
html+=`<hr class="divider">`;
html+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 数字缺失与过剩 ✦</div>`;
html+=`<div style="font-size:0.7em;line-height:1.7;color:#6b5b4e;padding:0 4px;">`;
if(miss.both.length>0){html+=`<div style="margin-bottom:4px;"><b>⚠ 缺失数字</b></div>`;miss.both.forEach(n=>{html+=`<div style="margin-bottom:3px;">· <b>缺${n}</b>${MISS_MSG[n]||''}</div>`;});}
else html+=`<div style="margin-bottom:4px;color:#9b8a78;">✦ 你的数字分布完整,能量均衡。</div>`;
const over=[];for(let i=1;i<=9;i++){if(freq[i]>=4&&!miss.both.includes(i))over.push(i);}
if(over.length>0){html+=`<div style="margin-top:6px;margin-bottom:4px;"><b>⚡ 能量过剩</b></div>`;over.forEach(n=>{html+=`<div style="margin-bottom:3px;">· <b>多${n}</b>(×${freq[n]}):${MISS_STRONG[n]||''}</div>`;});}
html+=`</div>`;
const joints=[['I','J'],['J','M'],['M','I'],['K','L'],['L','N'],['N','K'],['M','O'],['O','N'],['M','N'],['Q','P'],['Q','O'],['P','O']].map(([a,b])=>({code:''+v[a]+v[b],label:JOINT[''+v[a]+v[b]]||'—'}));
html+=`<hr class="divider">`;
html+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 12组联合码详解 ✦</div>`;
html+=`<div style="font-size:0.68em;color:#9b8a78;text-align:center;margin-bottom:8px;">联合码由相邻两个数字组成,揭示你在不同人生阶段的行为模式</div>`;
const ages=[
{label:'21—40岁 · 工作和朋友',codes:joints.slice(0,4),desc:'由潜意识码主导,反映你在职场上升期和社交圈中的表现模式。'+(v.I+v.J+v.M>=10?'能量较强,事业心旺盛,社交活跃。':'能量内敛,更注重深度而非广度。')},
{label:'41—60岁 · 儿女和下属',codes:joints.slice(4,8),desc:'由内心码和主性格主导,体现你对下一代和下级的培养方式及中年价值观。'+(v.K+v.L+v.N>=10?'有较强的责任感和支配欲。':'倾向于给予空间,放手让他人成长。')},
{label:'61岁至晚年 · 晚年和家庭',codes:joints.slice(8,12),desc:'由外心码主导,展现你晚年的生活智慧和家庭关系的最终呈现。'+(v.M+v.N+v.O>=10?'晚年活跃,持续发挥影响力。':'晚年宁静,享受内在平和。')},
];
ages.forEach(ag=>{
html+=`<div class="joint-age">${ag.label}</div>`;
html+=`<div style="font-size:0.65em;color:#9b8a78;text-align:center;margin:-2px 0 6px;line-height:1.5;">${ag.desc}</div>`;
html+=`<div class="joint-grid">`;
ag.codes.forEach(jc=>{html+=`<div><span class="jc">${jc.code}</span> <span class="jm">${jc.label}</span></div>`;});
html+=`</div>`;
});
document.getElementById('missingBlock').innerHTML=html;
setTimeout(()=>{
const canvas=document.querySelector('#scene3d canvas');
if(canvas){renderScene(canvas,freq,rotZ);setupRotation();}
},30);
document.getElementById('missingMsg').textContent='';
document.getElementById('codesBlock').innerHTML='';
document.getElementById('jointBlock').innerHTML='';
const tipEl=document.getElementById('tip');
['valR','valQ','valP','valO','valM','valN','valI','valJ','valK','valL','valU','valT','valS','valX','valV','valW'].forEach(id=>{
const el=document.getElementById(id);if(!el)return;el.style.cursor='pointer';
const k=id.replace('val','');
el.onpointerenter=()=>{const f=tips[k]?tips[k](v):'';tipEl.innerHTML=f||k;tipEl.classList.add('on')};
el.onpointermove=e=>{tipEl.style.left=(e.clientX+14)+'px';tipEl.style.top=(e.clientY-10)+'px'};
});
// 填充其他解读面板
document.getElementById('meihua-panel').innerHTML=meihuaHTML(y,m,d);
document.getElementById('xiaoliuren-panel').innerHTML=xiaoLiuRenHTML(y,m,d);
document.getElementById('daliuren-panel').innerHTML=daLiuRenHTML(y,m,d);
document.getElementById('qimen-panel').innerHTML=qiMenHTML(y,m,d);
document.getElementById('xingzuo-panel').innerHTML=xingZuoHTML(y,m,d);
document.getElementById('wuxing-panel').innerHTML=wuXingHTML(y,m,d);
}
function go(){const y=+document.getElementById('inY').value,m=+document.getElementById('inM').value,d=+document.getElementById('inD').value;if(!y||!m||!d)return;update(calc(y,m,d),y,m,d)}
document.getElementById('goBtn').addEventListener('click',go);
document.querySelectorAll('.input-row input').forEach(i=>i.addEventListener('keydown',e=>{if(e.key==='Enter')go()}));
setTimeout(go,150);
initTabs();
</script>
</body>
</html>
+825
View File
@@ -0,0 +1,825 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷 · AI东方养生顾问</title>
<style>
:root{--pri:#E54D42;--gold:#C8923A;--wd:#5CB85C;--fr:#E54D42;--ea:#E8985A;--mt:#C8923A;--wt:#4A90E2;--bg0:#FFD1C7;--bg1:#FFC8B5;--bg2:#FFF8F0;--bg3:#fff5f0}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei",sans-serif;
background:linear-gradient(180deg,var(--bg0) 0%,var(--bg1) 30%,var(--bg2) 60%,var(--bg3) 100%);
min-height:100vh;color:#333;-webkit-font-smoothing:antialiased;
}
.container{max-width:500px;margin:0 auto;padding:20px 14px 40px}
.header{text-align:center;padding:32px 0 20px}
.header .logo{font-size:20px;font-weight:700;color:var(--pri);letter-spacing:1px}
.header .sub{font-size:12px;color:rgba(0,0,0,0.35);margin-top:4px}
.card{
background:#fff;border-radius:16px;padding:20px;margin-bottom:14px;
box-shadow:0 2px 12px rgba(0,0,0,0.04);
}
.card h2{font-size:16px;font-weight:600;margin-bottom:12px;color:#333;display:flex;align-items:center;gap:8px}
.card h3{font-size:14px;font-weight:600;margin:14px 0 8px;color:#555}
.card h4{font-size:13px;font-weight:600;color:#333;margin-bottom:4px}
.card p.dim{font-size:12px;color:#aaa;margin-bottom:14px}
/* Tabs */
.tabs{display:flex;gap:0;margin-bottom:14px;background:#f5f0eb;border-radius:10px;padding:3px}
.tab{flex:1;text-align:center;padding:10px 0;border-radius:8px;font-size:13px;cursor:pointer;color:#888;transition:all 0.15s}
.tab.active{background:#fff;color:var(--pri);font-weight:600;box-shadow:0 1px 3px rgba(0,0,0,0.06)}
/* Form */
.fg{margin-bottom:12px}
.fg label{display:block;font-size:12px;color:#999;margin-bottom:4px;font-weight:500}
.fg input,.fg select{width:100%;padding:12px 14px;border:1.5px solid #e8e0d8;border-radius:10px;font-size:15px;outline:none;background:#fdfaf7}
.fg input:focus,.fg select:focus{border-color:var(--pri)}
.fr{display:flex;gap:10px}.fr .fg{flex:1}
.btn{width:100%;padding:14px;border:none;border-radius:12px;font-size:16px;font-weight:600;cursor:pointer}
.btn-pri{background:var(--pri);color:#fff}.btn-pri:active{opacity:0.85}
/* Pentagram */
.penta{display:flex;justify-content:center;gap:8px;margin:16px 0;flex-wrap:wrap}
.pb{text-align:center;min-width:44px}
.pb .f{height:120px;width:28px;border-radius:14px;margin:0 auto 6px;position:relative;background:#f0ebe5;overflow:hidden}
.pb .fi{position:absolute;bottom:0;left:0;right:0;border-radius:14px;transition:height 0.6s ease}
.pb .l{font-size:11px;color:#888}
.pb .v{font-size:13px;font-weight:700;margin-top:2px}
/* Stats */
.stats2{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:8px 0}
.st2{padding:14px 10px;border-radius:10px;background:#fdf8f5;text-align:center}
.st2 .v{font-size:20px;font-weight:700}
.st2 .l{font-size:11px;color:#999;margin-top:2px}
.st2 .d{font-size:11px;color:#bbb;margin-top:2px;line-height:1.5}
/* Bazi table */
.bzt{width:100%;border-collapse:collapse;font-size:13px;margin:10px 0}
.bzt th{background:#fdf8f5;padding:10px 6px;font-weight:600;font-size:11px;color:#999}
.bzt td{text-align:center;padding:10px 6px;border-bottom:1px solid #f5f0eb}
.bzt td strong{font-size:16px}
.bzt .el{font-size:11px;color:#aaa}
/* Tags */
.tags{display:flex;flex-wrap:wrap;gap:6px;margin:6px 0}
.tag{padding:4px 12px;border-radius:20px;font-size:12px;font-weight:500}
.tg{background:#E6FFE8;color:#388E3C}
.tw{background:#FFECEC;color:var(--pri)}
.tb{background:#E6F0FF;color:var(--wt)}
.to{background:#FFF8E6;color:var(--ea)}
/* Risk */
.rk{padding:14px;border-radius:12px;margin-bottom:10px;border-left:4px solid}
.rk.r1{background:#FFF0ED;border-color:var(--fr)}
.rk.r2{background:#FFF8E6;border-color:var(--ea)}
.rk.r3{background:#E6F0FF;border-color:var(--wt)}
.rk h4{font-size:14px;margin-bottom:6px}
.rk p.x{font-size:12px;color:#888;margin-bottom:6px;line-height:1.6}
.rk ul{list-style:none;padding:0}
.rk li{font-size:12px;color:#666;padding:2px 0}
.rk li::before{content:'· ';color:var(--pri)}
/* Suggest */
.sr{display:flex;gap:8px;margin-bottom:10px}
.si{width:36px;height:36px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.sb{flex:1}
.sb .t{font-size:14px;font-weight:600;margin-bottom:4px}
.sb .i{font-size:12px;color:#666;line-height:1.8}
.sb .r{font-size:11px;color:#aaa;margin-top:2px}
.sg{background:#E6FFE8}.sr2{background:#FFECEC}.sbl{background:#E6F0FF}.so{background:#FFF8E6}
/* Score */
.sc{display:flex;align-items:center;gap:10px;padding:8px 0}
.sl{font-size:13px;width:40px;text-align:right}
.sb2{flex:1;height:8px;border-radius:4px;background:#f0ebe5;overflow:hidden}
.sf{height:100%;border-radius:4px;transition:width 0.4s ease}
.sv{font-size:13px;font-weight:600;width:32px}
/* Quote */
.qt{background:linear-gradient(135deg,#FFF8F0,#FFECEC);border-radius:12px;padding:16px;text-align:center;margin:10px 0}
.qt .t{font-size:15px;font-weight:600;color:var(--pri);font-style:italic}
.qt .e{font-size:12px;color:#888;margin-top:6px;line-height:1.6}
/* Organ stars */
.or{display:flex;justify-content:space-between;align-items:center;padding:6px 0;border-bottom:1px solid #f5f0eb}
.or:last-child{border-bottom:none}
.on{font-size:13px;width:30px}
.os2{color:var(--gold);font-size:14px;letter-spacing:2px}
.os2 .off{color:#ddd}
/* Meridian */
.mer{display:grid;grid-template-columns:1fr 1fr;gap:4px;margin:10px 0}
.mi{font-size:11px;padding:6px 8px;border-radius:8px;background:#fdf8f5;text-align:center}
.mi .t2{font-weight:600;font-size:12px}
.mi .o2{font-size:10px;color:#999;margin-top:1px}
/* WYLQ table */
.wlt{width:100%;border-collapse:collapse;font-size:12px;margin:8px 0}
.wlt td{padding:8px 6px;border-bottom:1px solid #f5f0eb}
.wlt td:first-child{color:#999;white-space:nowrap}
.wlt td:last-child{font-weight:500}
/* Interaction */
.inter{display:flex;gap:8px;flex-wrap:wrap;margin:8px 0}
.iv{padding:6px 12px;border-radius:8px;font-size:12px;font-weight:500}
.iv.good{background:#E6FFE8;color:#388E3C}
.iv.bad{background:#FFECEC;color:var(--pri)}
.iv.neut{background:#f5f0eb;color:#888}
.hidden{display:none!important}
.tip{font-size:11px;color:#ccc;text-align:center;margin-top:8px}
</style>
</head>
<body>
<div class="container">
<div class="header">
<div class="logo">☯️ 愈心谷 · AI东方养生</div>
<div class="sub">易经 × 黄帝内经 × 五运六气</div>
</div>
<div class="tabs" id="tabs">
<div class="tab active" data-tab="profile">📋 先天档案</div>
<div class="tab" data-tab="weekly">📊 本周报告</div>
<div class="tab" data-tab="daily">☀️ 今日养生</div>
</div>
<!-- ===== PROFILE ===== -->
<div id="tab-profile">
<div class="card" id="formCard">
<h2>📝 建立先天健康档案</h2>
<p class="dim">仅需填写一次,系统自动推算八字、五行、体质,生成终身健康画像</p>
<div class="fg"><label>姓名</label><input id="fname" placeholder="请输入姓名"></div>
<div class="fg"><label>性别</label><select id="fgender"><option value="男"></option><option value="女"></option></select></div>
<div class="fg"><label>出生日期(公历)</label><input id="fbirth" type="date" value="1990-01-01"></div>
<div class="fr">
<div class="fg"><label>出生时间</label><input id="ftime" type="time" value="12:00"></div>
<div class="fg"><label>出生地</label><input id="fplace" placeholder="如:上海" value="上海"></div>
</div>
<button class="btn btn-pri" onclick="gen()">🧬 生成先天体质画像</button>
<p class="tip">数据仅存本地,不上传服务器</p>
</div>
<div id="profileResult" class="hidden">
<div class="card" id="baziCard"><h2>🔢 八字命盘</h2><div id="baziTable"></div></div>
<div class="card"><h2>🎨 五行画像</h2><div class="penta" id="pentagram"></div><div id="pentagramNote" style="font-size:12px;color:#999;text-align:center;margin-top:4px"></div></div>
<div class="card" id="constDetail"></div>
<div class="card" id="organDetail"></div>
<div class="card" id="emotionDetail"></div>
<div class="card" id="personalityCard"></div>
</div>
</div>
<!-- ===== WEEKLY ===== -->
<div id="tab-weekly" class="hidden"><div id="weeklyContent"></div></div>
<!-- ===== DAILY ===== -->
<div id="tab-daily" class="hidden"><div id="dailyContent"></div></div>
</div>
<script>
// ===================== DATA =====================
var TG=['甲','乙','丙','丁','戊','己','庚','辛','壬','癸'];
var DZ=['子','丑','寅','卯','辰','巳','午','未','申','酉','戌','亥'];
var TGE={甲:'木',乙:'木',丙:'火',丁:'火',戊:'土',己:'土',庚:'金',辛:'金',壬:'水',癸:'水'};
var DZE={子:'水',丑:'土',寅:'木',卯:'木',辰:'土',巳:'火',午:'火',未:'土',申:'金',酉:'金',戌:'土',亥:'水'};
var DZ_ANI=['鼠','牛','虎','兔','龙','蛇','马','羊','猴','鸡','狗','猪'];
var EC={木:'#5CB85C',火:'#E54D42',土:'#E8985A',金:'#C8923A',水:'#4A90E2'};
var EO=['木','火','土','金','水'];
// 纳音 (60甲子)
var NAYIN_MAP = {};
(function(){
var nayin60 = [
'海中金','炉中火','大林木','路旁土','剑锋金','山头火',
'涧下水','城头土','白蜡金','杨柳木','泉中水','屋上土',
'霹雳火','松柏木','流年水','砂石金','山下火','平地木',
'壁上土','金箔金','覆灯火','天河水','大驿土','钗钏金',
'桑柘木','柘榴木','大海水','石榴木','大海水',
];
// Actually 30 pairs, each pair covers 2 years. Let me just hardcode the 30.
var n30 = [
'海中金','炉中火','大林木','路旁土','剑锋金','山头火',
'涧下水','城头土','白蜡金','杨柳木','泉中水','屋上土',
'霹雳火','松柏木','流年水','砂石金','山下火','平地木',
'壁上土','金箔金','覆灯火','天河水','大驿土','钗钏金',
'桑柘木','大溪水','沙中土','天上火','石榴木','大海水'
];
for(var i=0;i<60;i++){
var tg = TG[i%10], dz = DZ[i%12];
NAYIN_MAP[tg+dz] = n30[Math.floor(i/2)];
}
})();
var ST = [
{n:'小寒',m:1,d:5},{n:'大寒',m:1,d:20},{n:'立春',m:2,d:4},{n:'雨水',m:2,d:19},
{n:'惊蛰',m:3,d:5},{n:'春分',m:3,d:20},{n:'清明',m:4,d:5},{n:'谷雨',m:4,d:20},
{n:'立夏',m:5,d:5},{n:'小满',m:5,d:21},{n:'芒种',m:6,d:5},{n:'夏至',m:6,d:21},
{n:'小暑',m:7,d:7},{n:'大暑',m:7,d:22},{n:'立秋',m:8,d:7},{n:'处暑',m:8,d:23},
{n:'白露',m:9,d:7},{n:'秋分',m:9,d:23},{n:'寒露',m:10,d:8},{n:'霜降',m:10,d:23},
{n:'立冬',m:11,d:7},{n:'小雪',m:11,d:22},{n:'大雪',m:12,d:7},{n:'冬至',m:12,d:22}
];
var MONTH_DZ=['寅','卯','辰','巳','午','未','申','酉','戌','亥','子','丑'];
// ===================== CALC =====================
function gzYear(y){return{tg:TG[(y-4)%10],dz:DZ[(y-4)%12]};}
function getST(d){
var m=d.getMonth()+1, dd=d.getDate(), t=null;
for(var i=ST.length-1;i>=0;i--){var s=ST[i];if(m>s.m||(m===s.m&&dd>=s.d)){t=s;break;}}
return t||ST[ST.length-1];
}
function getMDz(st){
var bi=ST.findIndex(function(s){return s.n==='立春';});
return MONTH_DZ[Math.floor(((ST.indexOf(st)-bi+24)%24)/2)];
}
function getBazi(d,h){
var y=d.getFullYear();
var gzY=gzYear(y);
var dayOfYear=Math.floor((d-new Date(y,0,0))/86400000);
var lcDay=35; // 立春approx day
var mi=Math.floor(((dayOfYear-lcDay+365)%365)/30.44);
if(mi<0)mi+=12;if(mi>=12)mi=11;
var mTg=TG[(((y-4)%10)*2+mi)%10];
var mDz=DZ[(mi+2)%12];
var base=new Date(1900,0,1);
var dd=Math.floor((d-base)/86400000);
var dTg=TG[((dd%10)+10)%10];
var dDz=DZ[((dd%12)+12)%12];
var hDz=DZ[Math.floor((h+1)/2)%12];
var hTg=TG[(TG.indexOf(dTg)*2+DZ.indexOf(hDz))%10];
return{year:{tg:gzY.tg,dz:gzY.dz},month:{tg:mTg,dz:mDz},day:{tg:dTg,dz:dDz},hour:{tg:hTg,dz:hDz}};
}
function calc5E(bz){
var c={木:0,火:0,土:0,金:0,水:0};
[bz.year,bz.month,bz.day,bz.hour].forEach(function(p){c[TGE[p.tg]]++;c[DZE[p.dz]]++;});
var t=0;EO.forEach(function(e){t+=c[e];});
var r={};EO.forEach(function(e){r[e]=Math.round(c[e]/t*100);});
return r;
}
function getConst(p){
var s=EO.slice().sort(function(a,b){return p[b]-p[a];});
var strong=s[0], weak=s[4];
var detail={
'木':{
primary:'气郁质', secondary:'血瘀质', strong:'肝', weak:'脾',
desc:'肝主疏泄,木气偏盛则气机易郁。情绪波动较大,遇事易纠结,长期压力下可能出现胸闷、胁痛、偏头痛。性格积极进取但容易焦虑。春季是木气最旺的季节,此时更应注意疏肝理气,避免过度压抑情绪。',
body:'形体偏瘦或适中,面色偏青或黄,舌边尖偏红。春季容易过敏,经前易乳胀(女性)。指甲易脆裂,眼睛易干涩疲劳。脉象多弦。',
tips:'疏肝理气为首要。多运动、多表达、避免长期压抑。适合芳香类茶饮(玫瑰花、佛手)。保持环境明亮通风,多接触绿色自然。',
diseases:'偏头痛、高血压、甲状腺结节、乳腺增生、月经不调、胁间神经痛、梅核气(咽部异物感)。长期气郁可化火伤阴。',
seasonRisk:'春季最敏感——过敏、肝阳上亢。秋季金克木,肺气压制肝气,易忧郁。',
foodTherapy:'多食绿色蔬菜(菠菜、芹菜、西兰花)。酸味入肝但不宜过(山楂、柠檬适量)。玫瑰花茶、薄荷茶、佛手柑茶疏肝。荞麦、小米养肝血。',
dailyRhythm:'晨起拉伸5分钟舒展筋脉。上午11点前完成重要决策(肝气最旺)。下午3-5点(申时)膀胱经当令,多喝水排毒。晚上11点前入睡养肝血。'
},
'火':{
primary:'湿热质', secondary:'阴虚质', strong:'心', weak:'肺',
desc:'心主血脉神明,火气偏盛则心神易亢。热情外向,感染力强,但易心浮气躁、失眠多梦。舌尖偏红,易口腔溃疡。火性炎上,热扰心神则烦躁不宁,热迫血行则易出血。夏季火旺时最需养护。',
body:'面赤,手心足心热,易出汗。夏季耐受力差,喜冷饮。皮肤易长痘、出油、疮疡。舌红苔黄,脉数。小便黄,大便偏干。',
tips:'清心降火为要。午休养心(11-13点),少辛辣刺激。适合莲子心、淡竹叶、菊花茶。避免熬夜伤阴。保持心态平和,少参与激烈争论。',
diseases:'失眠、口腔溃疡反复发作、心悸、焦虑症、高血压、皮肤疮疖、小便短赤、痔疮出血。严重者心火下移小肠导致尿路感染。',
seasonRisk:'夏季最敏感——暑热叠加心火,易中暑、心烦。冬季水克火,寒气伤心阳,易心悸胸闷。',
foodTherapy:'苦味入心(苦瓜、莲子心、淡竹叶)。红色食物养心(红枣、枸杞、番茄、红豆)。绿豆汤、百合莲子羹清心安神。西瓜、黄瓜、荸荠清热生津。',
dailyRhythm:'午时(11-13点)务必小憩15-30分钟养心阴。傍晚适度运动微微出汗。睡前温水泡脚引火下行。忌睡前刷手机。'
},
'土':{
primary:'痰湿质', secondary:'气虚质', strong:'脾', weak:'肾',
desc:'脾主运化,土气偏盛则湿浊易滞。性格稳重包容、可信赖,但思虑过度伤脾。容易疲劳、饭后困倦、大便不成形。脾为后天之本,气血生化之源,脾虚则百病丛生。长夏(农历六月)湿气最重时最需养护。',
body:'体型偏丰满,肌肉松软,腹部易堆积脂肪。面色偏黄,舌体胖大有齿痕,舌苔白腻。梅雨季不适加重,头重如裹。脉濡缓。',
tips:'健脾祛湿为核心。饮食规律七分饱,细嚼慢咽。少生冷甜腻,温热饮食为佳。适合山药薏米粥、陈皮茶、四神汤。适度运动助脾运化。',
diseases:'肥胖、高血脂、脂肪肝、糖尿病前期、慢性胃炎、功能性消化不良、梅尼埃病(眩晕)、湿疹、带下病(女性)。',
seasonRisk:'长夏(7-8月)最敏感——湿热交蒸,身重困倦。春季木克土,肝气犯脾,易胃痛腹泻。',
foodTherapy:'甘淡味入脾(山药、茯苓、薏米、白扁豆)。黄色食物养脾(小米、南瓜、玉米、黄豆)。陈皮理气健脾,生姜温中散寒。忌生冷冰饮、甜腻厚味。',
dailyRhythm:'早餐7-9点(辰时胃经当令)必须吃且吃好。饭后散步10分钟助运化。下午3-5点多喝水助膀胱排湿。晚餐宜早宜少。'
},
'金':{
primary:'阴虚质', secondary:'气郁质', strong:'肺', weak:'肝',
desc:'肺主气司呼吸,金气偏盛则津液易耗。性格条理分明、做事认真有原则,但易忧悲伤肺。皮肤偏干,秋冬易咳。肺为娇脏,不耐寒热燥湿,最易受外邪侵袭。秋季燥气当令时最需滋润养护。',
body:'形体偏瘦,皮肤干燥粗糙,口鼻咽干,唇干裂。秋季敏感,容易便秘。颧红,手心足心热,夜间盗汗。舌红少苔,脉细数。',
tips:'滋阴润肺为要。多食白色食物(银耳、百合、梨、白萝卜),避免辛辣烧烤油炸。早睡养阴,保持室内湿度。练习腹式呼吸增强肺功能。',
diseases:'慢性咽炎、干咳、支气管炎、皮肤干燥症、便秘、过敏性鼻炎、荨麻疹、声音嘶哑。长期阴虚可致肺萎。',
seasonRisk:'秋季最敏感——燥邪伤肺,干咳咽痒。夏季火克金,心火乘肺,易咳血痰。',
foodTherapy:'辛凉入肺(薄荷、桑叶、菊花)。白色食物润肺(银耳、百合、山药、雪梨、莲藕)。蜂蜜、麦冬、沙参、玉竹滋阴润燥。忌辛辣、烧烤、油炸、烟酒。',
dailyRhythm:'清晨(5-7点大肠经)排便清浊。上午做腹式深呼吸5分钟。午后吃一个梨或喝银耳羹。晚上9点前入睡养肺阴。居室保持50%-60%湿度。'
},
'水':{
primary:'阳虚质', secondary:'痰湿质', strong:'肾', weak:'心',
desc:'肾主水藏精,水气偏盛则阳气易亏。性格沉静有智慧、深度思考型,但容易恐惧、缺乏安全感。畏寒怕冷,腰膝酸软。肾为先天之本,藏精主骨生髓,肾虚则早衰。冬季寒气当令时最需温养。',
body:'体型偏胖或浮肿,面色㿠白或晦暗无光。手足不温,夜尿频多,听力可能下降,脱发早白。舌淡胖,脉沉细。腰膝冷痛,性功能减退。',
tips:'温阳补肾为首。多晒太阳,尤其晒后背(督脉)。少寒凉饮食,注意腰腹脚部保暖。艾灸关元、命门、肾俞穴。适合黑豆、核桃、肉桂、杜仲。',
diseases:'慢性肾炎、甲状腺功能减退、骨质疏松、腰突、耳鸣耳聋、不孕不育、性功能减退、老年痴呆早期。寒湿痹痛(关节炎)。',
seasonRisk:'冬季最敏感——寒气伤阳,畏寒加重。长夏土克水,湿困肾阳,水肿加重。',
foodTherapy:'咸味入肾但宜淡(海带、紫菜适量)。黑色食物补肾(黑豆、黑芝麻、黑米、黑木耳、桑葚)。核桃、山药、枸杞、杜仲温补肾阳。忌冰镇饮品、生冷瓜果。',
dailyRhythm:'早晨7-9点晒太阳15分钟补阳气。上午搓腰眼(肾俞)至发热。下午5-7点(酉时肾经当令)休息养肾,不宜剧烈运动。晚上热水泡脚至微汗。冬季早睡晚起。'
}
};
return detail[strong];
}
function getEmotion(strongEl){
var m={
'木':{
strength:['创造力','决策力','学习能力','进取心','目标感'],
risk:['焦虑','易怒','急躁','偏头痛倾向','经前紧张(女)'],
advice:'遇事深呼吸三秒再回应。每日快走30分钟疏解肝气。学习正念冥想。',
color:'绿'
},
'火':{
strength:['热情','感染力','表达力','社交能力','行动力'],
risk:['失眠','焦躁','心神不宁','多梦','舌尖溃疡'],
advice:'午时(11-13点)小憩养心。少刷手机,睡前温水泡脚。练习书法静心。',
color:'红'
},
'土':{
strength:['稳重','包容','耐心','执行力','可信赖'],
risk:['思虑过度','脾胃不适','优柔寡断','过度操心'],
advice:'饭后散步10分钟助运化。学会说"不",减轻负担。培养一个动手爱好。',
color:'黄'
},
'金':{
strength:['条理','决断','专注','毅力','原则性'],
risk:['悲伤','忧愁','呼吸道敏感','皮肤干燥','便秘'],
advice:'练习腹式呼吸强肺。多与朋友交流倾诉。保持环境湿度。',
color:'白'
},
'水':{
strength:['智慧','洞察','冷静','适应力','深度思考'],
risk:['恐惧','孤独感','腰膝酸软','听力下降','记忆力减退'],
advice:'冬季注意腰腹保暖。练习站桩固肾。多听音乐舒缓恐惧。',
color:'黑'
}
};
return m[strongEl];
}
function getWYLQ(year){
var gz=gzYear(year);
var tgI=TG.indexOf(gz.tg);
var zy=TGE[gz.tg]; // 中运
var tg2=tgI%2===0; // 太过
// 司天: 与年支三合局对应
var dzI=DZ.indexOf(gz.dz);
var sitianMap={'子':'午','丑':'未','寅':'申','卯':'酉','辰':'戌','巳':'亥','午':'子','未':'丑','申':'寅','酉':'卯','戌':'辰','亥':'巳'};
var st=DZ[DZ.indexOf(sitianMap[gz.dz])];
var zq=st; // 在泉 = 司天对冲
// Step
var stepNum=Math.floor((new Date().getMonth())/2);
var zhuqi=['厥阴风木','少阴君火','少阳相火','太阴湿土','阳明燥金','太阳寒水'];
var keqiOrder = [];
var stIdx=DZ.indexOf(st);
for(var k=0;k<6;k++) keqiOrder.push(DZ[(stIdx+k)%12]);
return{zhongyun:zy,taiguo:tg2,sitian:st,zaiquan:zq,zhuqi:zhuqi,keqiOrder:keqiOrder,stepNum:stepNum,yearTg:gz.tg,yearDz:gz.dz};
}
function getCurAnalysis(){
var now=new Date();
var w=getWYLQ(now.getFullYear());
var term=getST(now);
var mDz=getMDz(term);
var m=DZE[mDz];
var seMap={3:'木',4:'木',5:'火',6:'火',7:'土',8:'土',9:'金',10:'金',11:'水',12:'水',1:'水',2:'木'};
var se=seMap[now.getMonth()+1];
var ww={};
EO.forEach(function(e){
var i=EO.indexOf(e), si=EO.indexOf(se);
if(e===se)ww[e]='旺';
else if(e===EO[(si+1)%5])ww[e]='相';
else if(e===EO[(si+4)%5])ww[e]='休';
else if(e===EO[(si+2)%5])ww[e]='囚';
else ww[e]='死';
});
// Current keqi
var step=Math.floor((now.getMonth())/2);
return{date:now,year:now.getFullYear(),term:term,monthDz:mDz,monthEl:m,seasonEl:se,waxWane:ww,wylq:w,step:step};
}
// ===================== REPORTS =====================
function wkReport(p,con,emo,cur){
var s=EO.slice().sort(function(a,b){return p[b]-p[a];});
var st=s[0], se=cur.seasonEl, ww=cur.waxWane;
var om={木:'肝',火:'心',土:'脾',金:'肺',水:'肾'};
var os={};
EO.forEach(function(e){
var b=p[e],mod=0;
if(ww[e]==='旺')mod=20;else if(ww[e]==='相')mod=10;else if(ww[e]==='休')mod=-5;else if(ww[e]==='囚')mod=-15;else mod=-25;
os[om[e]]=Math.max(1,Math.min(5,Math.round((b+mod)/20)));
});
// Element interaction
var gen=EO[(EO.indexOf(st)+1)%5], ke=EO[(EO.indexOf(st)+2)%5], beiGen=EO[(EO.indexOf(st)+4)%5], beiKe=EO[(EO.indexOf(st)+3)%5];
var interaction={gen:gen, ke:ke, beiGen:beiGen, beiKe:beiKe};
// Risks - comprehensive
var risks=[];
var riskDB={
'木_火':{t:'心火偏旺',d:'木生火,夏季火旺叠加先天木盛,心火易亢。木火相煽,热扰心神。',sy:['失眠多梦','焦躁易怒','口干心烦','舌尖红','小便黄'],act:'午时小憩养心,睡前温水泡脚。喝莲子心茶清心火。减少咖啡和激烈运动。按揉神门穴、内关穴。'},
'木_木':{t:'肝气过亢',d:'春木逢木,肝气太过。木旺生风,上扰清窍。',sy:['头晕目眩','偏头痛','血压波动','眼干眼胀'],act:'每日快走30分钟疏肝。喝菊花枸杞茶清肝明目。按揉太冲穴、行间穴。避免熬夜和情绪激动。'},
'木_土':{t:'肝木克土',d:'先天木旺克制脾土,脾胃运化受阻。'+(ww['土']==='囚'?'当前土气被困,脾胃尤为脆弱。':''),sy:['食欲不振','腹胀嗳气','大便不调','疲倦乏力'],act:'少食多餐,温热饮食。饭前喝一小碗小米粥护脾胃。饭后散步10分钟。按揉足三里、中脘穴。避免生气时进食。'},
'木_金':{t:'木侮金',d:'木气过盛反侮肺金。',sy:['干咳少痰','咽喉不适','皮肤过敏'],act:'保持室内湿度,喝蜂蜜梨水润肺。练习腹式呼吸。避免辛辣和干燥环境。'},
'木_水':{t:'子盗母气',d:'木旺耗水,肾水暗亏。',sy:['腰膝酸软','耳鸣','脱发增多'],act:'多吃黑芝麻、核桃补肾。搓腰眼至发热。减少熬夜,保证子时(23点前)入睡。'},
'火_火':{t:'心火亢盛',d:'火上加火,两阳相劫,耗伤心阴。',sy:['心悸怔忡','盗汗','烦躁不宁','口腔溃疡频发','面赤'],act:'务必午休养心阴。绿豆百合汤清心安神。避免烈日下活动和辛辣食物。睡前温水泡脚引火下行。按揉神门、涌泉穴。'},
'火_木':{t:'木火刑金',d:'火得木生更旺,反侮肺金。',sy:['干咳','鼻衄','皮肤干燥','便秘'],act:'桑叶菊花茶清肺润燥。多吃梨、银耳、蜂蜜。保持环境湿润。避免烧烤和烟酒。'},
'火_土':{t:'火生土亢',d:'火旺生土过度,湿热内生。',sy:['口苦口臭','大便黏滞','皮肤油腻长痘','头重身困'],act:'薏米赤小豆汤祛湿热。少甜食油腻。保持皮肤清洁干爽。荷叶茶清热利湿。'},
'火_水':{t:'火盛水涸',d:'心火亢盛下劫肾水,心肾不交。',sy:['心烦失眠','腰膝酸软','耳鸣','记忆力下降','夜尿频'],act:'黄连阿胶汤类方交通心肾。睡前热水泡脚至微汗。按揉涌泉、太溪穴。避免熬夜和过度用脑。'},
'火_金':{t:'火克金',d:'心火乘肺金,肺阴受损。',sy:['干咳无痰','咽干喉痛','皮肤粗糙','毛发干枯'],act:'麦冬沙参茶润肺生津。银耳百合羹每周3次。腹式深呼吸养肺。居室保持50%湿度。'},
'土_火':{t:'火生土壅',d:'火旺生土,湿热蕴脾。',sy:['身重困倦','头蒙如裹','口黏不爽','大便溏滞'],act:'薏米茯苓粥健脾祛湿。室内通风除湿。少思虑多运动。按揉阴陵泉、丰隆穴。'},
'土_土':{t:'脾湿壅盛',d:'土气过旺,湿浊内停。',sy:['身体沉重','腹胀满闷','痰多','浮肿'],act:'陈皮薏仁茶理气化湿。控制食量七分饱。每天快走40分钟。按揉足三里、天枢穴。'},
'土_水':{t:'脾土克水',d:'土盛克水,肾阳受损。',sy:['水肿','小便不利','腰冷痛','畏寒'],act:'茯苓山药粥健脾利水。艾灸关元、命门温阳。避免久坐久卧。'},
'土_木':{t:'土虚木乘',d:'湿土困脾,肝木乘虚而入。',sy:['胃痛','反酸','情绪波动时加重'],act:'饭前少量生姜水暖胃。少食多餐减轻脾胃负担。保持情绪稳定,饭后不生气。'},
'水_火':{t:'水火不济',d:'夏季火旺克水,先天水盛之人肾水受制,心肾不交。',sy:['心烦不得眠','五心烦热','腰膝酸软','健忘','耳鸣加重'],act:'交泰丸类方交通心肾。睡前搓涌泉穴100次。下午5-7点(肾经当令)休息。少吃生冷,多晒太阳补阳。'},
'水_水':{t:'寒湿内盛',d:'水气过旺,寒湿困阻。',sy:['畏寒肢冷','关节疼痛','水肿','小便清长'],act:'艾灸关元、命门、足三里。生姜红糖水温中散寒。多晒太阳尤其后背。冬季泡温泉。忌冰饮冷食。'},
'水_木':{t:'水不涵木',d:'水亏不能养木,虚风内动。',sy:['头晕','手抖','失眠'],act:'枸杞菊花茶滋水涵木。早睡养肾阴。减少用眼过度。按揉太冲、太溪穴。'},
'水_金':{t:'金不生水',d:'金被火克不能生水,肾水枯竭。',sy:['腰酸','脱发','牙齿松动','骨关节退变'],act:'黑豆核桃粥补肾填精。西洋参麦冬茶气阴双补。搓腰眼每日100次。适当负重运动强骨。'},
'金_火':{t:'肺燥津伤',d:'火克金,肺阴受损。燥热伤津,肺失肃降。',sy:['干咳无痰或少痰','咽干鼻燥','皮肤干痒','大便秘结','声音嘶哑'],act:'川贝雪梨汤润肺止咳。室内加湿器保持50%-60%湿度。腹式深呼吸每次5分钟。避免辛辣油炸烧烤。麦冬沙参泡水代茶。'},
'金_金':{t:'肺气壅滞',d:'金气过盛,肺失宣降。',sy:['胸闷气短','咳嗽','皮肤干燥脱屑'],act:'白萝卜蜂蜜水宣肺化痰。户外深呼吸新鲜空气。适度有氧运动微汗。按揉太渊、列缺穴。'},
'金_木':{t:'金乘肝木',d:'肺金过盛抑制肝木疏泄。',sy:['情绪抑郁','胸胁胀痛','经前不适(女)'],act:'玫瑰花薄荷茶疏肝解郁。多与朋友交流倾诉。快走或瑜伽舒畅气机。按揉太冲、期门穴。'},
};
var rk=riskDB[st+'_'+se] || riskDB[st+'_'+st];
if(rk) risks.push(rk);
// Add a second risk based on beiKe
var rk2=riskDB[beiKe+'_'+se];
if(rk2 && rk2.t!==(rk?rk.t:'')) risks.push(rk2);
if(risks.length===0) risks.push({t:'整体协调',d:'当前时空与先天体质较为协调,继续保持良好生活习惯即可。',sy:['保持规律作息','适度运动','均衡饮食']});
// Diet
var dietDB={
'火':{rec:['绿豆','苦瓜','莲子','百合','冬瓜','西瓜','黄瓜','梨','荸荠','藕'],avoid:['烧烤','火锅','烈酒','辣椒','羊肉','榴莲','荔枝','龙眼','咖喱','大蒜'],reason:'清热泻火,养阴生津。避免辛温助热之品。',tea:'莲子心竹叶茶:莲子心3g + 淡竹叶5g,沸水冲泡,清心降火。菊花枸杞茶:菊花5朵 + 枸杞10粒,清肝明目。'},
'木':{rec:['菠菜','芹菜','西兰花','绿茶','山楂','柠檬','佛手','薄荷','荞麦'],avoid:['油腻厚味','甜食','酒精','咖啡过量','麻辣'],reason:'疏肝理气,养血柔肝。避免壅滞气机。',tea:'玫瑰花佛手茶:玫瑰花5朵 + 佛手干3片,疏肝解郁。薄荷柠檬茶:鲜薄荷叶5片 + 柠檬2片,舒畅气机。'},
'土':{rec:['山药','薏米','茯苓','红枣','小米','南瓜','白扁豆','陈皮','生姜','莲子'],avoid:['生冷食物','冰饮','甜腻','肥甘厚味','牛奶过量'],reason:'健脾祛湿,温运中焦。避免寒凉伤脾。',tea:'陈皮薏仁茶:陈皮5g + 炒薏米15g,理气化湿。生姜红枣茶:生姜3片 + 红枣5枚,温中健脾。'},
'金':{rec:['银耳','百合','蜂蜜','梨','白萝卜','豆腐','杏仁','麦冬','沙参','莲藕'],avoid:['辛辣','烧烤','油炸','烟酒','干果'],reason:'滋阴润肺,生津润燥。避免辛燥伤阴。',tea:'麦冬沙参茶:麦冬10g + 沙参10g,润肺生津。蜂蜜雪梨水:雪梨1个切块+蜂蜜,煮水代茶。'},
'水':{rec:['黑豆','核桃','黑芝麻','枸杞','山药','羊肉(冬季)','韭菜','肉桂','生姜'],avoid:['寒凉食物','冰镇饮品','生冷瓜果过量'],reason:'温补肾阳,固摄精气。避免寒凉伤阳。',tea:'桂圆枸杞茶:桂圆5颗 + 枸杞15粒 + 生姜2片,温补肾阳。黑豆核桃露:黑豆30g + 核桃3颗打浆煮饮。'}
};
var dt=se==='土'&&st==='火'?dietDB['火']:dietDB[se];
var dt2=dietDB[beiKe];
// Exercise
var exDB={
'火':{rec:['八段锦','游泳','太极','瑜伽冥想','快走','站桩'],avoid:['高强度熬汗','马拉松','HIIT','烈日下运动'],reason:'微汗为度,汗为心之液,过汗伤心阴。'},
'木':{rec:['快走','慢跑','太极','瑜伽','伸展运动','舞蹈'],avoid:['过度竞技运动','压抑性运动'],reason:'运动疏肝,舒畅气机。避免过量压抑肝气。'},
'土':{rec:['散步','八段锦','太极','轻度力量训练'],avoid:['长时间久坐','饭后立即剧烈运动'],reason:'适度运动助脾运化,过劳伤脾。'},
'金':{rec:['深呼吸练习','太极','瑜伽','游泳','慢跑'],avoid:['干燥环境中运动','过度出汗'],reason:'养肺气,润皮毛。避免燥邪伤肺。'},
'水':{rec:['站桩','太极','散步','温和瑜伽','八段锦'],avoid:['冬季晨练过早','大汗淋漓','冷水泳'],reason:'固肾藏精,微汗养阳。避免过耗阳气。'}
};
var ex=exDB[se];
// Sleep
var slDB={
'火':{bt:'22:30',wt:'06:00',focus:'23:00前入睡养胆清心。午时(11-13点)小憩15-30分钟养心阴。'},
'木':{bt:'22:30',wt:'06:30',focus:'23:00前入睡养肝血。睡前温水泡脚引火下行。避免睡前思虑。'},
'土':{bt:'22:00',wt:'06:30',focus:'早睡养脾。晚餐宜早宜少,睡前3小时不进食。'},
'金':{bt:'21:30',wt:'06:00',focus:'早卧早起养肺气。保持卧室湿度,避免干燥。'},
'水':{bt:'21:30',wt:'07:00',focus:'早睡晚起养肾阳。睡前热水泡脚至微汗。注意腰腹保暖。'}
};
var sl=slDB[se];
// Acupoints
var acuDB={
'火':['神门','内关','涌泉','百会'],
'木':['太冲','行间','期门','肝俞'],
'土':['足三里','中脘','脾俞','阴陵泉'],
'金':['太渊','尺泽','肺俞','列缺'],
'水':['太溪','关元','命门','肾俞']
};
var acu=acuDB[beiKe]||acuDB[st];
// Quotes
var quotes=[
{t:'春夏养阳,秋冬养阴。',e:'当前属'+({'木':'春','火':'夏','土':'长夏','金':'秋','水':'冬'}[se]||se)+'季,应顺应阳气生长之势,避免过度耗散,保持规律作息。'},
{t:'正气存内,邪不可干。',e:'保持内在平衡是抵御外邪的根本。当前注意调和阴阳,顺应天时变化。'},
{t:'上古之人,其知道者,法于阴阳,和于术数。',e:'养生之道在于遵循自然规律。饮食有节,起居有常,不妄作劳。'},
{t:'虚邪贼风,避之有时,恬惔虚无,真气从之。',e:'顺应四时避开外邪,内心平和则正气自足。当前时令尤需注意。'},
{t:'阴平阳秘,精神乃治。',e:'阴阳平衡是健康的根本。当前'+se+'气偏盛,注意调和阴阳,不过不不及。'}
];
var q=quotes[Math.floor(Math.random()*quotes.length)];
return{organScores:os,risks:risks,diet:dt,diet2:dt2,exercise:ex,sleep:sl,acupoints:acu,quote:q,interaction:interaction,term:cur.term,se:se,strongest:st,wylq:cur.wylq,step:cur.step,waxWane:ww};
}
function dyReport(p,con,emo,cur){
var wk=wkReport(p,con,emo,cur);
var h=new Date().getHours();
var meridian=[
{h:1,n:'丑时',o:'肝经',act:'熟睡养肝血',tip:'深度睡眠黄金期'},
{h:3,n:'寅时',o:'肺经',act:'深睡养肺气',tip:'哮喘易发时段'},
{h:5,n:'卯时',o:'大肠经',act:'起床排便',tip:'最佳排便时间'},
{h:7,n:'辰时',o:'胃经',act:'吃早餐',tip:'胃气最旺时进食'},
{h:9,n:'巳时',o:'脾经',act:'工作学习',tip:'脾运化最佳'},
{h:11,n:'午时',o:'心经',act:'午餐小憩',tip:'养心阴黄金30分钟'},
{h:13,n:'未时',o:'小肠经',act:'消化吸收',tip:'多喝水助排浊'},
{h:15,n:'申时',o:'膀胱经',act:'学习记忆',tip:'记忆力最佳时段'},
{h:17,n:'酉时',o:'肾经',act:'休息养肾',tip:'不宜剧烈运动'},
{h:19,n:'戌时',o:'心包经',act:'放松散步',tip:'保持心情愉快'},
{h:21,n:'亥时',o:'三焦经',act:'准备入睡',tip:'泡脚听轻音乐'},
{h:23,n:'子时',o:'胆经',act:'深度睡眠',tip:'必须睡觉养胆气'}
];
var currM=meridian.find(function(m){return Math.abs(m.h-h)<=1;})||meridian[0];
var scores=[
{l:'睡眠',v:Math.floor(Math.random()*16+80),c:'#5CB85C'},
{l:'饮食',v:Math.floor(Math.random()*21+65),c:'#E8985A'},
{l:'情绪',v:Math.floor(Math.random()*26+60),c:'#E54D42'},
{l:'运动',v:Math.floor(Math.random()*16+80),c:'#4A90E2'}
];
var yiDB={
'火':['喝菊花茶','午休15分钟','散步','听轻音乐','温水泡脚','深呼吸'],
'木':['喝玫瑰花茶','快走30分钟','和朋友聊天','拉伸运动','看绿色植物'],
'土':['喝陈皮水','饭后散步','吃温热食物','按摩足三里','少食多餐'],
'金':['喝蜂蜜水','腹式呼吸','吃梨或银耳','早睡','室内加湿'],
'水':['喝姜枣茶','晒太阳','热水泡脚','搓腰眼','穿暖不贪凉']
};
var jiDB={
'火':['熬夜','暴饮暴食','情绪激动','过量咖啡','麻辣火锅','烈日暴晒'],
'木':['压抑情绪','过量饮酒','熬夜','油腻食物','与人争执'],
'土':['生冷冰饮','甜食过量','久坐不动','思虑过度','暴饮暴食'],
'金':['辛辣烧烤','烟酒','干燥环境','过度出汗','忧伤抑郁'],
'水':['寒凉饮食','过度疲劳','熬夜','冷水澡','冬季晨练过早']
};
return{scores:scores,meridian:meridian,currM:currM,yi:yiDB[wk.se]||yiDB['火'],ji:jiDB[wk.se]||jiDB['火'],weekly:wk};
}
// ===================== RENDER =====================
function renderProfile(p,con,emo,bz){
// Bazi table
var labels=['年柱','月柱','日柱','时柱'];
var pillars=[bz.year,bz.month,bz.day,bz.hour];
var h='<table class="bzt"><tr><th></th><th>天干</th><th>地支</th><th>五行</th><th>纳音</th><th>生肖</th></tr>';
for(var i=0;i<4;i++){
var pl=pillars[i];
var ny=NAYIN_MAP[pl.tg+pl.dz]||'--';
var ds=pl.dz, dzEl=DZE[ds];
var ani=i===0?DZ_ANI[DZ.indexOf(ds)]:'';
h+='<tr><td>'+labels[i]+'</td><td><strong>'+pl.tg+'</strong><br><span class="el">'+TGE[pl.tg]+'</span></td><td><strong>'+ds+'</strong><br><span class="el">'+dzEl+'</span></td><td style="color:'+EC[dzEl]+';font-weight:600;font-size:14px">'+TGE[pl.tg]+'/'+dzEl+'</td><td style="font-size:11px;color:#888">'+ny+'</td><td style="font-size:11px;color:#999">'+ani+'</td></tr>';
}
h+='</table>';
document.getElementById('baziTable').innerHTML=h;
// Pentagram
var pg=document.getElementById('pentagram');
pg.innerHTML='';
EO.forEach(function(e){
var v=p[e],c=EC[e];
pg.innerHTML+='<div class="pb"><div class="f"><div class="fi" style="height:'+v+'%;background:'+c+'"></div></div><div class="l">'+e+'</div><div class="v" style="color:'+c+'">'+v+'</div></div>';
});
var sorted=EO.slice().sort(function(a,b){return p[b]-p[a];});
document.getElementById('pentagramNote').textContent=sorted[0]+'偏盛 '+sorted[1]+'次盛 '+sorted[2]+'中和 '+sorted[3]+'偏弱 '+sorted[4]+'最弱';
// Constitution detail
var cd=document.getElementById('constDetail');
cd.innerHTML='<h2>🫁 体质分析</h2><div class="stats2"><div class="st2"><div class="v" style="color:var(--pri)">'+con.primary+'</div><div class="l">主要体质</div></div><div class="st2"><div class="v" style="color:#E8985A">'+con.secondary+'</div><div class="l">次要体质</div></div></div><p style="font-size:13px;color:#555;line-height:1.9;margin-top:8px">'+con.desc+'</p><h4 style="margin-top:12px">🩺 易患疾病</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.diseases+'</p><h4>👁 外在表现</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.body+'</p><h4>⚠️ 季节弱点</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.seasonRisk+'</p><h4>🎯 养生核心</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.tips+'</p><h4>🍽 食疗原则</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.foodTherapy+'</p><h4>⏰ 日常节律</h4><p style="font-size:12px;color:#888;line-height:1.7">'+con.dailyRhythm+'</p>';
// Organ detail
var od=document.getElementById('organDetail');
od.innerHTML='<h2>🫀 脏腑画像</h2><div class="stats2"><div class="st2"><div class="v" style="color:'+EC[sorted[0]]+'">'+con.strong+'</div><div class="l">偏强脏腑</div><div class="d">功能亢进<br>易生热/郁/火</div></div><div class="st2"><div class="v" style="color:#aaa">'+con.weak+'</div><div class="l">偏弱脏腑</div><div class="d">功能不足<br>易受外邪侵袭</div></div></div>';
// Emotion detail
var ed=document.getElementById('emotionDetail');
ed.innerHTML='<h2>🧠 情绪画像</h2><div style="margin-bottom:10px"><span style="font-size:12px;color:#999">优势特质:</span><div class="tags">'+emo.strength.map(function(s){return '<span class="tag tg">'+s+'</span>';}).join('')+'</div></div><div style="margin-bottom:10px"><span style="font-size:12px;color:#999">风险倾向:</span><div class="tags">'+emo.risk.map(function(s){return '<span class="tag tw">'+s+'</span>';}).join('')+'</div></div><h4>情绪调节</h4><p style="font-size:12px;color:#888;line-height:1.7">'+emo.advice+'</p>';
// Personality summary
document.getElementById('personalityCard').innerHTML='<h2>📖 综合画像</h2><p style="font-size:13px;color:#555;line-height:2">先天<b style="color:'+EC[sorted[0]]+'">'+sorted[0]+'</b>之气偏盛,属<b style="color:var(--pri)">'+con.primary+'</b>体质。'+con.strong+'经易亢,'+con.weak+'经偏弱。'+emo.strength[0]+'、'+emo.strength[1]+'是你的核心天赋;需注意'+emo.risk[0]+'和'+emo.risk[1]+'的倾向。</p>';
document.getElementById('profileResult').classList.remove('hidden');
document.getElementById('formCard').style.opacity='0.55';
}
function seasonalGuide(term, se){
var guides={
'立春':{tips:'春季养肝正当时。早睡早起,广步于庭。披发缓行,以使志生。多吃芽菜(豆芽、香椿),少吃酸多食辛甘。',avoid:'压抑怒气、过度进补、过早减衣。'},
'雨水':{tips:'湿气渐增,健脾祛湿。多吃山药薏米。注意关节保暖。',avoid:'生冷瓜果、淋雨涉水。'},
'惊蛰':{tips:'春雷惊百虫,阳气升发。早起活动舒展筋骨。多吃梨润肺防春燥。',avoid:'过度劳累、情绪压抑。'},
'春分':{tips:'阴阳平分,养生贵在平衡。早睡早起,适度运动微汗。饮食寒热均衡。',avoid:'偏食偏嗜、大补大泻。'},
'清明':{tips:'清气上升,养肝明目。踏青散步舒畅情志。菊花枸杞茶护眼。',avoid:'过度悲伤、暴饮暴食。'},
'谷雨':{tips:'雨生百谷,湿度加大。健脾祛湿防春困。多喝薏米陈皮茶。',avoid:'潮湿环境久待、过食生冷。'},
'立夏':{tips:'夏季养心。夜卧早起,无厌于日。午休15-30分钟养心阴。多吃红色食物。',avoid:'烈日暴晒、大汗淋漓、辛辣过量。'},
'小满':{tips:'湿热渐盛,清心祛湿。冬瓜薏米汤清热利湿。保持皮肤干爽。',avoid:'油腻厚味、甜腻食物。'},
'芒种':{tips:'梅雨季将至,防湿防霉。室内通风除湿。饮食清淡少油腻。艾灸足三里健脾。',avoid:'生冷冰饮、久居湿地。'},
'夏至':{tips:'阳极阴生,养心护阳。午休不可少。饮食清淡多汤水。夏至一阴生,忌过度贪凉。',avoid:'冰镇饮品、空调直吹、熬夜。'},
'小暑':{tips:'暑热渐盛,清暑益气。绿豆汤、荷叶粥消暑。避免正午户外活动。',avoid:'过度出汗、冷热交替刺激。'},
'大暑':{tips:'湿热交蒸,健脾祛湿为首。藿香正气类防中暑。多喝温水少量多次。',avoid:'烈日下劳作、冰火两重天。'},
'立秋':{tips:'秋燥当令,润肺生津。多吃白色食物。早卧早起与鸡俱兴。',avoid:'辛辣烧烤、过度出汗。'},
'处暑':{tips:'暑气渐消,秋燥初显。银耳百合羹润燥。适度运动舒展筋骨。',avoid:'燥热食物、熬夜伤阴。'},
'白露':{tips:'寒气初生,注意保暖。早晚添衣,尤其腹部和脚部。多吃温润食物。',avoid:'赤脚踩凉地、露脐装。'},
'秋分':{tips:'阴阳平分如春分。滋阴润燥,收敛神气。早睡早起,心态平和。',avoid:'剧烈运动大汗、辛辣伤阴。'},
'寒露':{tips:'寒气加重,护好颈腰脚踝。泡脚至微汗。多吃芝麻核桃润燥。',avoid:'冷水洗脚、穿露脚踝裤。'},
'霜降':{tips:'秋末冬初,补脾养肺。山药红枣粥最佳。适度运动增强抵抗力。',avoid:'过度秋冻、生冷瓜果。'},
'立冬':{tips:'冬季养肾。早卧晚起,必待日光。多晒太阳补阳。多吃黑色食物。',avoid:'清晨过早户外运动、冷水澡。'},
'小雪':{tips:'寒气渐重,温补肾阳。羊肉汤、当归生姜羊肉汤温补。艾灸关元命门。',avoid:'寒凉饮食、过度疲劳。'},
'大雪':{tips:'仲冬严寒,藏精养阳。减少户外活动。多喝热粥热汤。早睡晚起。',avoid:'大汗淋漓、受寒感冒。'},
'冬至':{tips:'一阳来复,阴极阳生。静养为主,减少消耗。吃饺子/汤圆象征团圆。冬至进补事半功倍。',avoid:'剧烈运动、熬夜、房劳过度。'},
'小寒':{tips:'三九严寒,温阳散寒。艾灸关元足三里。每晚热水泡脚。羊肉当归汤最宜。',avoid:'清晨低温外出、冷水刺激。'},
'大寒':{tips:'寒极则暖,冬藏待春。继续温补藏精。保持心态平和,不急不躁。',avoid:'过度消耗、受寒受冻。'}
};
var g=guides[term.n]||{tips:'顺应四时,饮食有节,起居有常。',avoid:'过度劳累、暴饮暴食、情绪波动。'};
return '<div class="card"><h2>🌿 '+term.n+'节气养生指南</h2><div class="sr"><div class="si sg"></div><div class="sb"><div class="i">'+g.tips+'</div></div></div><div class="sr"><div class="si sr2"></div><div class="sb"><div class="i">'+g.avoid+'</div></div></div></div>';
}
function renderWeekly(wk){
var wc=document.getElementById('weeklyContent');
var tn=wk.term?wk.term.n:'--';
var se=wk.se||'--';
var seN={'木':'春季','火':'夏季','土':'长夏','金':'秋季','水':'冬季'}[se]||se;
var now=new Date();
var ts=now.getFullYear()+'-'+String(now.getMonth()+1).padStart(2,'0')+'-'+String(now.getDate()).padStart(2,'0')+' '+String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0');
var h='';
// ---- Overview ----
h+='<div class="card"><h2>📊 本周气机概览</h2>';
h+='<div style="font-size:14px;margin-bottom:4px">📅 节气:<b>'+tn+'</b>  🌤 时令:<b>'+seN+'</b>'+se+'旺)</div>';
h+='<div style="font-size:12px;color:#aaa;margin-bottom:10px">你的先天:<b style="color:'+EC[wk.strongest]+'">'+wk.strongest+'</b>偏盛  '+wk.strongest+'生'+wk.interaction.gen+'  '+wk.strongest+'克'+wk.interaction.ke+'</div>';
h+='<div style="font-size:11px;color:#ccc;border-top:1px dashed #f0e8e0;padding-top:8px">⏱ 数据基于当前时空气机实时计算 | 刷新时间:'+ts+'</div>';
// ---- WYLQ Detail ----
var wq=wk.wylq;
h+='<table class="wlt"><tr><td>岁运(中运)</td><td><b>'+wq.zhongyun+'</b>'+(wq.taiguo?'(太过)':'(不及)')+'</td></tr>';
h+='<tr><td>司天之气</td><td>'+wq.sitian+''+DZE[wq.sitian]+')— 上半年主气</td></tr>';
h+='<tr><td>在泉之气</td><td>'+wq.zaiquan+''+DZE[wq.zaiquan]+')— 下半年主气</td></tr>';
var cs=wk.step||Math.floor(new Date().getMonth()/2);
h+='<tr><td>当前主气</td><td>'+wq.zhuqi[cs]+'</td></tr>';
h+='<tr><td>当前客气</td><td>'+wq.keqiOrder[cs]+'</td></tr>';
h+='</table>';
// Five elements wax/wane
h+='<div style="font-size:12px;margin-top:10px"><b>五行旺衰:</b>';
EO.forEach(function(e){h+='<span style="margin:0 4px;padding:2px 8px;border-radius:4px;background:'+(wk.waxWane[e]==='旺'?EC[e]+'20':'transparent')+';color:'+EC[e]+';font-weight:'+(wk.waxWane[e]==='旺'?'700':'400')+'">'+e+wk.waxWane[e]+'</span>';});
h+='</div>';
// Organ scores
h+='<div style="margin-top:12px"><b>本周脏腑指数:</b></div>';
['肝','心','脾','肺','肾'].forEach(function(o){
var sc=wk.organScores[o]||3;
var stars='';
for(var i=1;i<=5;i++)stars+=i<=sc?'★':'<span class="off"></span>';
h+='<div class="or"><span class="on">'+o+'</span><span class="os2">'+stars+'</span></div>';
});
h+='</div>';
// ---- Risks ----
h+='<div class="card"><h2>⚠️ 健康风险</h2>';
wk.risks.forEach(function(r,i){
h+='<div class="rk r'+(i+1)+'"><h4>风险'+(i+1)+''+r.t+'</h4><p class="x">'+r.d+'</p><ul>';
r.sy.forEach(function(s){h+='<li>'+s+'</li>';});
h+='</ul>';
if(r.act) h+='<p style="font-size:12px;color:var(--pri);margin-top:6px;line-height:1.7"><b>✅ 应对:</b>'+r.act+'</p>';
h+='</div>';
});
h+='</div>';
// ---- Daily refresh tip (changes daily) ----
var dailyTips={
'火':['夏日午后一杯淡竹叶茶,清心降火。','心静自然凉,午休15分钟胜过补药。','苦味入心,晚餐加一道凉拌苦瓜。'],
'木':['肝喜条达,今天遇到烦心事出去走10分钟。','绿色入肝,午餐加一份西兰花。','怒伤肝,深呼吸三次再回应让你生气的事。'],
'土':['脾主思,今天少想多做,动手整理房间也是调理。','黄色入脾,早餐一碗小米粥暖胃健脾。','饭后百步走,活到九十九——今天饭后就散个步。'],
'金':['秋燥伤肺,今天多喝温水,室内保持湿润。','白色入肺,今天吃一个雪梨润肺。','忧伤肺,今天找一件让你开心的小事做。'],
'水':['肾藏精,今天早点上床,23点前入睡是最好的补肾药。','黑色入肾,今天吃一把黑芝麻或核桃。','恐伤肾,今天少看负面新闻,多晒晒太阳。']
};
var tips=dailyTips[wk.se]||dailyTips['火'];
var idx=new Date().getDate()%tips.length;
h+='<div class="card"><h2>💡 今日养生小贴士</h2><p style="font-size:13px;color:#555;line-height:1.8;padding:8px 12px;background:#FFF8F0;border-radius:10px">'+tips[idx]+'</p></div>';
// ---- Diet ----
h+='<div class="card"><h2>🥗 本周饮食</h2>';
h+='<div class="sr"><div class="si sg">👍</div><div class="sb"><div class="t">推荐食材</div><div class="i">'+wk.diet.rec.join('、')+'</div><div class="r">💡 '+wk.diet.reason+'</div></div></div>';
h+='<div class="sr"><div class="si sr2">👎</div><div class="sb"><div class="t">减少摄入</div><div class="i">'+wk.diet.avoid.join('、')+'</div></div></div>';
if(wk.diet.tea) h+='<div class="sr"><div class="si so">🍵</div><div class="sb"><div class="t">养生茶饮</div><div class="i">'+wk.diet.tea+'</div></div></div>';
if(wk.diet2){
h+='<div class="sr"><div class="si so">🔄</div><div class="sb"><div class="t">辅助调养('+wk.interaction.beiKe+'行)</div><div class="i">'+wk.diet2.rec.join('、')+'</div><div class="r">💡 '+wk.diet2.reason+'</div></div></div>';
}
h+='</div>';
// ---- Exercise ----
h+='<div class="card"><h2>🏃 运动建议</h2>';
h+='<div class="sr"><div class="si sbl"></div><div class="sb"><div class="t">推荐运动</div><div class="i">'+wk.exercise.rec.join('、')+'</div></div></div>';
h+='<div class="sr"><div class="si so"></div><div class="sb"><div class="t">避免</div><div class="i">'+wk.exercise.avoid.join('、')+'</div><div class="r">💡 '+wk.exercise.reason+'</div></div></div>';
h+='</div>';
// ---- Sleep ----
h+='<div class="card"><h2>😴 作息建议</h2>';
h+='<div class="sr"><div class="si sbl">🌙</div><div class="sb"><div class="t">'+wk.sleep.bt+' 入睡 · '+wk.sleep.wt+' 起床</div><div class="i">'+wk.sleep.focus+'</div></div></div>';
h+='</div>';
// ---- Seasonal Living Guide ----
h+=seasonalGuide(wk.term, wk.se);
// ---- Acupoints ----
h+='<div class="card"><h2>📍 穴位保健</h2>';
h+='<div class="tags">'+wk.acupoints.map(function(a){return '<span class="tag tb">'+a+'</span>';}).join('')+'</div>';
h+='<p style="font-size:11px;color:#999;margin-top:6px">每日按揉每穴3-5分钟,酸胀为度,早晚各一次。</p>';
h+='</div>';
// ---- Quote ----
h+='<div class="card"><h2>📖 本周一句内经</h2><div class="qt"><div class="t">「'+wk.quote.t+'」</div><div class="e">'+wk.quote.e+'</div></div></div>';
wc.innerHTML=h;
}
function renderDaily(dy){
var dc=document.getElementById('dailyContent');
var wk=dy.weekly;
var h='';
h+='<div class="card"><h2>☀️ 今日养生</h2>';
h+='<div style="font-size:14px;color:#555">📅 '+wk.term.n+'  值日五行:<b style="color:'+EC[wk.se]+'">'+wk.se+'</b></div>';
h+='<div style="font-size:12px;color:#aaa;margin-top:4px">你的状态:<b style="color:'+EC[wk.strongest]+'">'+wk.strongest+'</b>偏旺</div></div>';
// Meridian clock
h+='<div class="card"><h2>⏰ 子午流注</h2>';
h+='<p style="font-size:12px;color:#999;margin-bottom:8px">当前时段:<b>'+dy.currM.n+'</b>'+dy.currM.o+')— '+dy.currM.act+'</p>';
h+='<p style="font-size:11px;color:#bbb;margin-bottom:8px">💡 '+dy.currM.tip+'</p>';
h+='<div class="mer">';
dy.meridian.forEach(function(m){
h+='<div class="mi'+(m===dy.currM?'" style="background:#FFECEC;border:1px solid var(--pri)"':'')+'"><div class="t2">'+m.n+'</div><div class="o2">'+m.o+'</div></div>';
});
h+='</div></div>';
// Scores
h+='<div class="card"><h2>📈 今日评分</h2>';
dy.scores.forEach(function(s){
h+='<div class="sc"><span class="sl">'+s.l+'</span><div class="sb2"><div class="sf" style="width:'+s.v+'%;background:'+s.c+'"></div></div><span class="sv">'+s.v+'</span></div>';
});
h+='</div>';
// Yiji
h+='<div class="card"><h2>📋 今日宜忌</h2>';
h+='<div class="sr"><div class="si sg"></div><div class="sb"><div class="i">'+dy.yi.join('、')+'</div></div></div>';
h+='<div class="sr"><div class="si sr2"></div><div class="sb"><div class="i">'+dy.ji.join('、')+'</div></div></div>';
h+='</div>';
// Key focus
h+='<div class="card"><h2>🎯 今日重点</h2>';
h+='<div style="padding:16px;background:#FFECEC;border-radius:10px;text-align:center">';
h+='<div style="font-size:18px;font-weight:700;color:var(--pri);margin-bottom:6px">保养'+({'木':'肝','火':'心','土':'脾','金':'肺','水':'肾'}[wk.strongest]||'--')+'经</div>';
h+='<div style="font-size:13px;color:#888">'+dy.yi.slice(0,3).join(' · ')+'</div>';
h+='</div></div>';
dc.innerHTML=h;
}
// ===================== MAIN =====================
var userProfile=null,userConstitution=null,userEmotion=null,userBazi=null,curAnalysis=null;
(function(){
try{var d=JSON.parse(localStorage.getItem('yangsheng_profile'));
if(d){userProfile=d.profile;userConstitution=d.constitution;userEmotion=d.emotion;userBazi=d.bazi;
renderProfile(userProfile,userConstitution,userEmotion,userBazi);}
}catch(e){}
})();
function gen(){
try{console.log('gen start');
var nm=document.getElementById('fname').value||'用户';
var bs=document.getElementById('fbirth').value;
var ts=document.getElementById('ftime').value||'12:00';
var pl=document.getElementById('fplace').value||'--';
if(!bs){alert('请填写出生日期');return;}
var bd=new Date(bs),bh=parseInt(ts.split(':')[0]);
userBazi=getBazi(bd,bh); console.log('bazi ok');
userProfile=calc5E(userBazi); console.log('profile ok');
userConstitution=getConst(userProfile); console.log('const ok');
userEmotion=getEmotion(EO.slice().sort(function(a,b){return userProfile[b]-userProfile[a];})[0]); console.log('emotion ok');
try{localStorage.setItem('yangsheng_profile',JSON.stringify({name:nm,profile:userProfile,constitution:userConstitution,emotion:userEmotion,bazi:userBazi,birthDate:bs,birthTime:ts,birthPlace:pl}));}catch(e){}
renderProfile(userProfile,userConstitution,userEmotion,userBazi); console.log('renderProfile ok');
// Switch to weekly
document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active');});
document.querySelector('[data-tab="weekly"]').classList.add('active');
document.getElementById('tab-profile').classList.add('hidden');
document.getElementById('tab-weekly').classList.remove('hidden');
document.getElementById('tab-daily').classList.add('hidden');
curAnalysis=getCurAnalysis(); console.log('curAnalysis ok');
var report = wkReport(userProfile,userConstitution,userEmotion,curAnalysis); console.log('wkReport ok');
renderWeekly(report); console.log('renderWeekly ok');
}catch(e){console.error('gen error:',e.message,e.stack);}
}
// Tabs
document.getElementById('tabs').addEventListener('click',function(e){
if(!e.target.classList.contains('tab'))return;
var tab=e.target.dataset.tab;
document.querySelectorAll('.tab').forEach(function(t){t.classList.remove('active');});
e.target.classList.add('active');
document.getElementById('tab-profile').classList.add('hidden');
document.getElementById('tab-weekly').classList.add('hidden');
document.getElementById('tab-daily').classList.add('hidden');
if(tab==='profile') document.getElementById('tab-profile').classList.remove('hidden');
else if(tab==='weekly'){
if(!userProfile){alert('请先生成先天体质画像');document.querySelector('[data-tab="profile"]').click();return;}
document.getElementById('tab-weekly').classList.remove('hidden');
curAnalysis=getCurAnalysis();
renderWeekly(wkReport(userProfile,userConstitution,userEmotion,curAnalysis));
}else if(tab==='daily'){
if(!userProfile){alert('请先生成先天体质画像');document.querySelector('[data-tab="profile"]').click();return;}
document.getElementById('tab-daily').classList.remove('hidden');
curAnalysis=getCurAnalysis();
renderDaily(dyReport(userProfile,userConstitution,userEmotion,curAnalysis));
}
});
</script>
</body>
</html>
+230
View File
@@ -0,0 +1,230 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷 · 心理健康服务</title>
<style>
:root{
--pri:#E54D42; --blue:#4A90E2; --green:#5CB85C;
--text:#333; --sub:#999;
--hero-start:#FF9F80; --hero-end:#FFB8A6;
--bg-start:#FFD1C7; --bg-end:#FFC8B5;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 40%,#fff5f0 100%);
color:var(--text);max-width:430px;margin:0 auto;min-height:100vh;
-webkit-font-smoothing:antialiased;
}
/* ── Top Bar ── */
.topbar{
text-align:center;padding:16px 0 10px;
position:sticky;top:0;z-index:10;
background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.6) 100%);
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
/* ── Hero ── */
.hero{
background:linear-gradient(135deg,var(--hero-start),var(--hero-end));
border-radius:20px;margin:8px 16px 0;padding:28px 20px 36px;
position:relative;overflow:hidden;min-height:160px;
}
/* white translucent circles instead of emoji hearts */
.hero .dc{position:absolute;border-radius:50%;background:rgba(255,255,255,0.25);z-index:0}
.hero .dc1{width:50px;height:50px;top:10px;right:30px}
.hero .dc2{width:30px;height:30px;top:45px;right:70px}
.hero .dc3{width:20px;height:20px;top:20px;right:95px}
.hero .logo-wrap{position:relative;z-index:2;margin-bottom:10px}
.hero .sub{font-size:14px;color:rgba(255,255,255,0.92);position:relative;z-index:2}
/* hand-heart: CSS hearts instead of emoji */
.hero .hearts-css{position:absolute;right:14px;bottom:16px;z-index:0;opacity:0.45}
.hero .hearts-css .hh{
display:inline-block;width:50px;height:50px;position:relative;
margin:0 4px;
}
.hero .hearts-css .hh::before,.hero .hearts-css .hh::after{
content:"";position:absolute;width:26px;height:40px;border-radius:26px 26px 0 0;
background:#fff;
}
.hero .hearts-css .hh::before{left:15px;top:0;transform:rotate(-45deg);transform-origin:0 100%}
.hero .hearts-css .hh::after{left:0;top:0;transform:rotate(45deg);transform-origin:100% 100%}
.hero::after{
content:"";position:absolute;bottom:0;left:0;right:0;height:30px;
background:linear-gradient(transparent,rgba(255,209,199,0.3));
}
/* ── Section ── */
.section{padding:0 16px;margin-top:24px}
.sec-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.sec-head .title{font-size:17px;font-weight:600;color:var(--text)}
.sec-head .more{font-size:13px;color:var(--sub);cursor:pointer}
/* ── Service Cards ── */
.services{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.svc-card{
border-radius:12px;padding:14px 12px;
display:flex;align-items:center;gap:10px;box-shadow:0 2px 6px rgba(0,0,0,0.04);
cursor:pointer;transition:transform 0.15s;
word-break:keep-all;overflow:hidden;
}
.svc-card:active{transform:scale(0.97)}
/* CSS icons replacing emojis */
.svc-card .sicon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;font-size:16px}
.svc-card .sinfo{flex:1;min-width:0}
.svc-card .sinfo .sl{font-size:14px;font-weight:600;color:var(--text);margin-bottom:2px;white-space:nowrap}
.svc-card .sinfo .sd{font-size:11px;color:var(--sub);white-space:nowrap}
.svc-psy{background:#FFECEC}.svc-psy .sicon{background:#FFD4D4;color:var(--pri)}
.svc-edu{background:#FFF8E6}.svc-edu .sicon{background:#FFE8C0;color:#E8985A}
.svc-ai{background:#E6F0FF}.svc-ai .sicon{background:#CDE3FF;color:var(--blue)}
.svc-heal{background:#E6FFE8}.svc-heal .sicon{background:#CDF0D0;color:var(--green)}
/* ── Cube ── */
.cube-wrap{position:relative;border-radius:16px;overflow:hidden;box-shadow:0 6px 20px rgba(0,0,0,0.1)}
.cube-wrap img{display:block;width:100%}
/* ── Activity Card ── */
.act-card{
background:#fff;border-radius:16px;padding:28px 20px;
position:relative;overflow:hidden;
box-shadow:0 4px 12px rgba(0,0,0,0.06);
}
/* fixed: strokes behind all text, lower opacity */
.act-card .strokes{position:absolute;inset:0;pointer-events:none;opacity:0.08;z-index:0}
.act-card .strokes span{position:absolute;border-radius:3px;transform:rotate(-30deg)}
.act-card .strokes .s1{top:15px;left:-5px;width:160px;height:10px;background:var(--hero-end)}
.act-card .strokes .s2{top:70px;right:-10px;width:120px;height:8px;background:var(--blue)}
.act-card .strokes .s3{bottom:45px;left:20px;width:100px;height:7px;background:#E0D5F5}
.act-card .strokes .s4{bottom:15px;right:0px;width:140px;height:6px;background:var(--green)}
.act-card .main-text{font-size:28px;font-weight:800;color:#222;letter-spacing:4px;margin-bottom:10px;position:relative;z-index:1;line-height:1}
.act-card .side{display:flex;gap:20px;margin-bottom:14px;position:relative;z-index:1}
.act-card .side .st{font-size:12px;color:var(--sub);line-height:1.7;white-space:nowrap}
.act-card .footer{font-size:11px;color:#aaa;border-top:1px solid #f0f0f0;padding-top:12px;position:relative;z-index:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ── Bottom Card ── */
.bot-card{
background:#fff;border-radius:16px;padding:30px 20px;
margin:16px 16px 80px;position:relative;overflow:hidden;
box-shadow:0 2px 10px rgba(0,0,0,0.05);
}
.bot-card .blobs{position:absolute;bottom:-20px;right:-10px}
.bot-card .blobs span{display:inline-block;border-radius:50%}
.bot-card .blobs .b1{width:100px;height:100px;background:var(--hero-end);opacity:0.15;margin-right:-30px}
.bot-card .blobs .b2{width:60px;height:60px;background:#E0D5F5;opacity:0.2;margin-bottom:-20px}
.bot-card .btext{font-size:20px;color:#666;font-weight:300;position:relative;z-index:1;line-height:1.6}
.bot-card .btext .em{color:var(--pri);font-weight:500}
/* ── Nav ── */
.nav{
position:fixed;bottom:0;left:50%;transform:translateX(-50%);
width:100%;max-width:430px;background:#fff;
display:flex;justify-content:space-around;padding:6px 0 calc(10px + env(safe-area-inset-bottom,0));
border-top:1px solid #f0f0f0;z-index:100;
box-shadow:0 -2px 8px rgba(0,0,0,0.03);
}
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;font-size:10px;color:var(--sub);cursor:pointer;line-height:1}
.nav-item.active{color:var(--pri)}
/* CSS nav icons replacing emojis */
.nav-item .ni{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1}
.nav-item.active .ni{color:var(--pri)}
@media(max-width:380px){
.hero .logo-wrap img{height:28px!important}
.svc-card{padding:10px 8px}
.svc-card .sinfo .sl{font-size:13px}
.act-card .main-text{font-size:22px}
}
</style>
</head>
<body>
<div class="topbar"><img src="logo.png" alt="愈心脊" style="height:24px;vertical-align:middle"></div>
<!-- Hero -->
<div class="hero">
<div class="dc dc1"></div><div class="dc dc2"></div><div class="dc dc3"></div>
<div class="logo-wrap"><img src="logo.png" alt="愈心脊" style="height:36px"></div>
<p class="sub">专业的心理健康服务平台</p>
<div class="hearts-css"><span class="hh"></span><span class="hh"></span></div>
</div>
<!-- 疗愈魔方 -->
<div class="section">
<div class="sec-head" style="justify-content:center;flex-direction:column;align-items:center;gap:4px">
<span class="title">愈心魔方</span>
<span style="font-size:12px;color:var(--sub);font-weight:400">愈见魔方,遇见自己</span>
</div>
<div class="cube-wrap">
<img src="cube.jpg" alt="愈心脊疗愈魔方">
</div>
</div>
<!-- 服务卡片(无标题) -->
<div class="section">
<div class="services">
<div class="svc-card svc-psy">
<div class="sicon">💬</div>
<div class="sinfo"><div class="sl">心理咨询</div><div class="sd">你的心灵港湾</div></div>
</div>
<div class="svc-card svc-edu">
<div class="sicon">📋</div>
<div class="sinfo"><div class="sl">心理评估</div><div class="sd">科学评估,即刻开始</div></div>
</div>
<div class="svc-card svc-ai">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">私域活动</div><div class="sd">从第一个好友开始</div></div>
</div>
<div class="svc-card svc-heal">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">健康管理</div><div class="sd">关注今天,即关注未来</div></div>
</div>
</div>
</div>
<!-- 活动中心 -->
<div class="section">
<div class="sec-head"><span class="title">活动中心</span><span class="more">更多 →</span></div>
<div class="act-card">
<div class="strokes">
<span class="s1"></span><span class="s2"></span><span class="s3"></span><span class="s4"></span>
</div>
<div class="main-text">I SEE YOU</div>
<div class="side">
<div class="st">艺术<br>无边界</div>
<div class="st">表达有<br>力量</div>
</div>
<div class="footer">NDI叙事绘画治疗法®九周年大会</div>
</div>
</div>
<!-- Bottom Card -->
<div class="bot-card">
<div class="blobs"><span class="b1"></span><span class="b2"></span></div>
<div class="btext"><span class="em">I want to care more</span><br>about you</div>
</div>
<!-- Nav -->
<div class="nav">
<div class="nav-item active"><span class="ni"></span>首页</div>
<div class="nav-item"><span class="ni"></span>心理咨询</div>
<div class="nav-item"><span class="ni"></span>心理测评</div>
<div class="nav-item"><span class="ni"></span>私域活动</div>
<div class="nav-item"><span class="ni"></span>健康管理</div>
<div class="nav-item"><span class="ni"></span>我的</div>
</div>
</body>
</html>
+216
View File
@@ -0,0 +1,216 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-LVVXH3TL04"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-LVVXH3TL04');
</script>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no">
<title>愈心谷 · 心理健康服务</title>
<style>
:root{
--pri:#E54D42; --blue:#4A90E2; --green:#5CB85C;
--text:#333; --sub:#999;
--hero-start:#FF9F80; --hero-end:#FFB8A6;
--bg-start:#FFD1C7; --bg-end:#FFC8B5;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:-apple-system,BlinkMacSystemFont,"PingFang SC","Microsoft YaHei","Helvetica Neue",sans-serif;
background:linear-gradient(180deg,var(--bg-start) 0%,var(--bg-end) 40%,#fff5f0 100%);
color:var(--text);max-width:430px;margin:0 auto;min-height:100vh;
-webkit-font-smoothing:antialiased;
}
/* ── Top Bar ── */
.topbar{
text-align:center;padding:16px 0 10px;
position:sticky;top:0;z-index:10;
background:linear-gradient(180deg,var(--bg-start) 0%,rgba(255,209,199,0.6) 100%);
-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
/* ── Hero ── */
.hero{
background:linear-gradient(135deg,var(--hero-start),var(--hero-end));
border-radius:20px;margin:8px 16px 0;padding:28px 20px 36px;
position:relative;overflow:hidden;min-height:160px;
}
/* white translucent circles instead of emoji hearts */
.hero .dc{position:absolute;border-radius:50%;background:rgba(255,255,255,0.25);z-index:0}
.hero .dc1{width:50px;height:50px;top:10px;right:30px}
.hero .dc2{width:30px;height:30px;top:45px;right:70px}
.hero .dc3{width:20px;height:20px;top:20px;right:95px}
.hero .logo-wrap{position:relative;z-index:2;margin-bottom:10px}
.hero .sub{font-size:14px;color:rgba(255,255,255,0.92);position:relative;z-index:2}
/* hand-heart: CSS hearts instead of emoji */
.hero .hearts-css{position:absolute;right:14px;bottom:16px;z-index:0;opacity:0.45}
.hero .hearts-css .hh{
display:inline-block;width:50px;height:50px;position:relative;
margin:0 4px;
}
.hero .hearts-css .hh::before,.hero .hearts-css .hh::after{
content:"";position:absolute;width:26px;height:40px;border-radius:26px 26px 0 0;
background:#fff;
}
.hero .hearts-css .hh::before{left:15px;top:0;transform:rotate(-45deg);transform-origin:0 100%}
.hero .hearts-css .hh::after{left:0;top:0;transform:rotate(45deg);transform-origin:100% 100%}
.hero::after{
content:"";position:absolute;bottom:0;left:0;right:0;height:30px;
background:linear-gradient(transparent,rgba(255,209,199,0.3));
}
/* ── Section ── */
.section{padding:0 16px;margin-top:24px}
.sec-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
.sec-head .title{font-size:17px;font-weight:600;color:var(--text)}
.sec-head .more{font-size:13px;color:var(--sub);cursor:pointer}
/* ── Service Cards ── */
.services{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.svc-card{
border-radius:12px;padding:14px 12px;
display:flex;align-items:center;gap:10px;box-shadow:0 2px 6px rgba(0,0,0,0.04);
cursor:pointer;transition:transform 0.15s;
word-break:keep-all;overflow:hidden;
}
.svc-card:active{transform:scale(0.97)}
/* CSS icons replacing emojis */
.svc-card .sicon{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-weight:700;font-size:16px}
.svc-card .sinfo{flex:1;min-width:0}
.svc-card .sinfo .sl{font-size:14px;font-weight:600;color:var(--text);margin-bottom:2px;white-space:nowrap}
.svc-card .sinfo .sd{font-size:11px;color:var(--sub);white-space:nowrap}
.svc-psy{background:#FFECEC}.svc-psy .sicon{background:#FFD4D4;color:var(--pri)}
.svc-edu{background:#FFF8E6}.svc-edu .sicon{background:#FFE8C0;color:#E8985A}
.svc-ai{background:#E6F0FF}.svc-ai .sicon{background:#CDE3FF;color:var(--blue)}
.svc-heal{background:#E6FFE8}.svc-heal .sicon{background:#CDF0D0;color:var(--green)}
/* ── Activity Card ── */
.act-card{
background:#fff;border-radius:16px;padding:28px 20px;
position:relative;overflow:hidden;
box-shadow:0 4px 12px rgba(0,0,0,0.06);
}
/* fixed: strokes behind all text, lower opacity */
.act-card .strokes{position:absolute;inset:0;pointer-events:none;opacity:0.08;z-index:0}
.act-card .strokes span{position:absolute;border-radius:3px;transform:rotate(-30deg)}
.act-card .strokes .s1{top:15px;left:-5px;width:160px;height:10px;background:var(--hero-end)}
.act-card .strokes .s2{top:70px;right:-10px;width:120px;height:8px;background:var(--blue)}
.act-card .strokes .s3{bottom:45px;left:20px;width:100px;height:7px;background:#E0D5F5}
.act-card .strokes .s4{bottom:15px;right:0px;width:140px;height:6px;background:var(--green)}
.act-card .main-text{font-size:28px;font-weight:800;color:#222;letter-spacing:4px;margin-bottom:10px;position:relative;z-index:1;line-height:1}
.act-card .side{display:flex;gap:20px;margin-bottom:14px;position:relative;z-index:1}
.act-card .side .st{font-size:12px;color:var(--sub);line-height:1.7;white-space:nowrap}
.act-card .footer{font-size:11px;color:#aaa;border-top:1px solid #f0f0f0;padding-top:12px;position:relative;z-index:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* ── Bottom Card ── */
.bot-card{
background:#fff;border-radius:16px;padding:30px 20px;
margin:16px 16px 80px;position:relative;overflow:hidden;
box-shadow:0 2px 10px rgba(0,0,0,0.05);
}
.bot-card .blobs{position:absolute;bottom:-20px;right:-10px}
.bot-card .blobs span{display:inline-block;border-radius:50%}
.bot-card .blobs .b1{width:100px;height:100px;background:var(--hero-end);opacity:0.15;margin-right:-30px}
.bot-card .blobs .b2{width:60px;height:60px;background:#E0D5F5;opacity:0.2;margin-bottom:-20px}
.bot-card .btext{font-size:20px;color:#666;font-weight:300;position:relative;z-index:1;line-height:1.6}
.bot-card .btext .em{color:var(--pri);font-weight:500}
/* ── Nav ── */
.nav{
position:fixed;bottom:0;left:50%;transform:translateX(-50%);
width:100%;max-width:430px;background:#fff;
display:flex;justify-content:space-around;padding:6px 0 calc(10px + env(safe-area-inset-bottom,0));
border-top:1px solid #f0f0f0;z-index:100;
box-shadow:0 -2px 8px rgba(0,0,0,0.03);
}
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;font-size:10px;color:var(--sub);cursor:pointer;line-height:1}
.nav-item.active{color:var(--pri)}
/* CSS nav icons replacing emojis */
.nav-item .ni{width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:1}
.nav-item.active .ni{color:var(--pri)}
@media(max-width:380px){
.hero .logo-wrap img{height:28px!important}
.svc-card{padding:10px 8px}
.svc-card .sinfo .sl{font-size:13px}
.act-card .main-text{font-size:22px}
}
</style>
</head>
<body>
<div class="topbar"><img src="logo.png" alt="愈心脊" style="height:24px;vertical-align:middle"></div>
<!-- Hero -->
<div class="hero">
<div class="dc dc1"></div><div class="dc dc2"></div><div class="dc dc3"></div>
<div class="logo-wrap"><img src="logo.png" alt="愈心脊" style="height:36px"></div>
<p class="sub">专业的心理健康服务平台</p>
<div class="hearts-css"><span class="hh"></span><span class="hh"></span></div>
</div>
<!-- 专业服务 -->
<div class="section">
<div class="sec-head"><span class="title">专业服务</span></div>
<div class="services">
<div class="svc-card svc-psy">
<div class="sicon">💬</div>
<div class="sinfo"><div class="sl">心理咨询</div><div class="sd">你的心灵港湾</div></div>
</div>
<div class="svc-card svc-edu">
<div class="sicon">📋</div>
<div class="sinfo"><div class="sl">心理评估</div><div class="sd">科学评估,即刻开始</div></div>
</div>
<div class="svc-card svc-ai">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">私域活动</div><div class="sd">从第一个好友开始</div></div>
</div>
<div class="svc-card svc-heal">
<div class="sicon"></div>
<div class="sinfo"><div class="sl">健康管理</div><div class="sd">关注今天,即关注未来</div></div>
</div>
</div>
</div>
<!-- 活动中心 -->
<div class="section">
<div class="sec-head"><span class="title">活动中心</span><span class="more">更多 →</span></div>
<div class="act-card">
<div class="strokes">
<span class="s1"></span><span class="s2"></span><span class="s3"></span><span class="s4"></span>
</div>
<div class="main-text">I SEE YOU</div>
<div class="side">
<div class="st">艺术<br>无边界</div>
<div class="st">表达有<br>力量</div>
</div>
<div class="footer">NDI叙事绘画治疗法®九周年大会</div>
</div>
</div>
<!-- Bottom Card -->
<div class="bot-card">
<div class="blobs"><span class="b1"></span><span class="b2"></span></div>
<div class="btext"><span class="em">I want to care more</span><br>about you</div>
</div>
<!-- Nav -->
<div class="nav">
<div class="nav-item active"><span class="ni"></span>首页</div>
<div class="nav-item"><span class="ni"></span>心理咨询</div>
<div class="nav-item"><span class="ni"></span>心理测评</div>
<div class="nav-item"><span class="ni"></span>私域活动</div>
<div class="nav-item"><span class="ni"></span>健康管理</div>
<div class="nav-item"><span class="ni"></span>我的</div>
</div>
</body>
</html>