chore: seal Design Vision v1 and monorepo scaffold

Archive the differentiated YuXinGu product docs, AI engineering system,
design contract, and Go/Vue scaffold. Next execution prioritizes Cece-parity
over early innovation (see .ai/product/STRATEGY.md).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-02 16:00:44 +08:00
co-authored by Cursor
parent 2686866376
commit 2fb1dfee14
193 changed files with 8854 additions and 1851 deletions
+331
View File
@@ -0,0 +1,331 @@
/* common.js —— 纯数据 + 纯函数,禁止任何 DOM 操作
shared by: pages/shuzi.html / pages/health.html / pages/decode.html */
// ===== from shuzi.html =====
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:'你是天生的领航员,内心有一团不灭的火。你不需要别人告诉你该做什么——你比谁都清楚。独立、果敢、行动力超强,这让你在人群中自带光环。但请记住,真正的强大不是独自承担一切,而是学会信任和依靠。偶尔放下铠甲,你会看到更广阔的世界。'},
2:{title:'温柔沟通·共情者',pos:'善解人意、沟通力满分、团队里的粘合剂',neg:'太在意他人眼光、容易委屈自己、选择困难',job:'心理咨询师、公关专家、教育者、客户关系',elem:'水',desc:'你有一颗比常人更柔软的心。你能听见别人没说出口的话,感受到空气里的情绪——这是你独一无二的天赋。你是团队里最温暖的存在,总能让人感到被理解。但要记得,你的感受同样重要。温柔不是软弱,拒绝不是伤害。学会为自己发声,你的温柔会更有力量。'},
3:{title:'创意火花·行动派',pos:'灵感不断、感染力满分、想到就去做',neg:'三分钟热度、急躁冒进、耐不住寂寞',job:'演员、讲师、创意总监、自媒体人',elem:'木',desc:'你的大脑永远在高速运转,灵感像烟花一样炸开。你就是那个能把想法变成现实的人——当别人还在犹豫,你已经冲出去了。你的热情能点燃整个房间。但有时候,慢一点反而更快。不是每个火花都需要燃烧,学会聚焦,你的光芒会更耀眼。'},
4:{title:'稳妥规划·筑造者',pos:'逻辑缜密、靠谱担当、凡事井井有条',neg:'过于保守、害怕改变、安全感容易动摇',job:'工程师、会计师、项目经理、架构师',elem:'木',desc:'你是那个让所有人放心的人。当世界一片混乱时,你已经在默默列清单了。你的稳不是无趣,是把混乱变成秩序的超能力。每一步都走得扎实,每个细节都心中有数。但生活不是待办清单,偶尔打破计划,会有惊喜。允许自己不那么完美,你已经足够好了。'},
5:{title:'自由灵魂·探索者',pos:'无拘无束、方向感超强、幽默感爆棚',neg:'讨厌束缚、容易逃避、很难安定下来',job:'旅行博主、作家、自由职业者、冒险家',elem:'土',desc:'你就是风,没人能把你关在笼子里。你的灵魂渴望远方,对世界永远保持好奇。你能在迷茫中找到方向,这是你最珍贵的天赋。你的人生不是按部就班,而是充满冒险的故事。但也要记得,自由不是逃避,真正的自由是拥有选择的能力。找到值得你停留的热爱,那里就是你的家。'},
6:{title:'智慧远见·鉴赏家',pos:'眼光毒辣、财富直觉、精益求精',neg:'完美主义、容易焦虑、对自己太狠',job:'投资人、咨询顾问、医生、律师',elem:'金',desc:'你有一双看透本质的眼睛。别人看到表面,你看到价值;别人追逐潮流,你预判趋势。你对品质的要求不是挑剔,是对生活的尊重。但完美不是终点,生活有瑕疵才真实。偶尔放低标准,你会发现世界比你想象的更宽容。你值得拥有最好的,包括对自己温柔一点。'},
7:{title:'深度洞察·求真者',pos:'钻研到底、直觉惊人、看透事物本质',neg:'疏离冷漠、疑心重、社交电量容易耗尽',job:'科学家、侦探、程序员、学者',elem:'水',desc:'别人看热闹,你看门道。你的大脑是永动的探索引擎,对世界的真相充满饥渴。你不需要浅薄的社交,你在意的是深度和意义。这份专注让你在专业领域无人能敌。但请偶尔走出自己的世界,那些看似平凡的人间烟火,也能给你答案。独处是力量,连接是温暖,两者你都值得拥有。'},
8:{title:'责任担当·成就者',pos:'责任感爆棚、商业嗅觉、野心勃勃',neg:'压力山大、控制欲强、容易透支自己',job:'企业家、高管、投资人、政治家',elem:'土',desc:'你注定不是普通人。你肩膀上扛着比常人更重的使命,心中有比常人更大的蓝图。你的责任感和行动力让你天然成为领袖。但你不需要扛起整个世界,真正的领导者懂得分担负重。学会休息不是懈怠,是为了走更远的路。你值得被照顾,不只是照顾别人。'},
9:{title:'博爱大器·成功者',pos:'慈悲大爱、机会磁铁、自带成功气场',neg:'贪多嚼不烂、难以专注、理想主义过头',job:'慈善家、艺人、灵性导师、社会活动家',elem:'火',desc:'你身上有一种让人想靠近的魔力。你的心中装着比个人更大的东西——爱与使命感是你最强的驱动力。机会总是向你涌来,因为你值得。你天生自带好运体质。但不需要抓住所有机会,挑一个最让你心动的,全力以赴。你的存在,本身就是一种治愈。'},
};
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':'规划力MAX,步步为营','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':'王者基因,注定站在C位','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:'爱心泛滥到溢出,聚焦一件最想改变的事会更有力量',
};
// ===== from yangsheng.html =====
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 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=['寅','卯','辰','巳','午','未','申','酉','戌','亥','子','丑'];
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点前搞定重要决策(肝气最旺)。晚上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分钟助消化。晚餐早一点、少一点。'
},
'金':{
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;
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]='死';
});
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};
}
// 子午流注 12 时辰
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:'必须睡觉养胆气'}
];
// 五行-时令风险库(25种组合)
var riskDB={
'木_火':{t:'心火偏旺',d:'木生火,夏季火旺叠加先天木盛,心火易亢。木火相煽,热扰心神。',sy:['失眠多梦','焦躁易怒','口干心烦','舌尖红','小便黄'],act:'午时小憩养心,睡前温水泡脚。喝莲子心茶清心火。减少咖啡和激烈运动。按揉神门穴、内关穴。'},
'木_木':{t:'肝气过亢',d:'春木逢木,肝气太过。木旺生风,上扰清窍。',sy:['头晕目眩','偏头痛','血压波动','眼干眼胀'],act:'每日快走30分钟疏肝。喝菊花枸杞茶清肝明目。按揉太冲穴、行间穴。避免熬夜和情绪激动。'},
'木_土':{t:'肝木克土',d:'先天木旺克制脾土,脾胃运化受阻。',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:'玫瑰花薄荷茶疏肝解郁。多与朋友交流倾诉。快走或瑜伽舒畅气机。按揉太冲、期门穴。'},
};
+210
View File
@@ -0,0 +1,210 @@
/* ---------- 工具 ---------- */
function qp(k){return new URLSearchParams(location.search).get(k);}
function strongestEl(p){return EO.reduce((a,b)=>p[b]>p[a]?b:a,EO[0]);}
function esc(s){return (s||'').replace(/</g,'&lt;');}
/* ---------- 精简 SVG 三角图(核心数字:I,J,K,L → M,N → O ---------- */
function miniPyramid(v){
const base=[v.I,v.J,v.K,v.L], mid=[v.M,v.N], apex=v.O;
const CW=42,CH=40,GY=8,W=CW*4+GY*3,cx=W/2;
function cell(x,y,n,hl){
return '<rect x="'+x+'" y="'+y+'" width="'+CW+'" height="'+CH+'" rx="8" fill="'+(hl?'#C8923A':'#fff8ef')+'" stroke="#e0c48a"/><text x="'+(x+CW/2)+'" y="'+(y+CH/2+5)+'" text-anchor="middle" font-size="17" font-weight="700" fill="'+(hl?'#fff':'#8a6d3b')+'">'+(n!=null?n:'')+'</text>';
}
var s='<svg width="'+W+'" height="'+(CH*3+GY*2)+'" xmlns="http://www.w3.org/2000/svg">';
s+=cell(cx-CW/2,0,apex,true);
s+=cell(cx-CW-GY/2,CH+GY,mid[0])+cell(cx+GY/2,CH+GY,mid[1]);
base.forEach(function(n,i){ s+=cell(i*(CW+GY),(CH+GY)*2,n); });
s+='</svg>';
return s;
}
/* ---------- 今日锦囊 ---------- */
var SEASON_EL={0:'水',1:'水',2:'木',3:'木',4:'木',5:'火',6:'火',7:'火',8:'土',9:'金',10:'金',11:'水'};
var YI_JI={
:{yi:'早睡养肝血 · 舒展拉伸 · 绿色蔬菜',ji:'久坐 · 生闷气 · 熬夜'},
:{yi:'午休15分钟 · 菊花/莲子 · 平心静气',ji:'暴晒 · 辛辣油腻 · 过度兴奋'},
:{yi:'规律三餐 · 健脾山药 · 饭后散步',ji:'思虑过度 · 生冷 · 暴饮暴食'},
:{yi:'滋润养肺 · 白色食物 · 深呼吸',ji:'干燥受凉 · 悲忧 · 剧烈耗气'},
:{yi:'早卧晚起 · 温补黑色食物 · 护腰腹',ji:'受寒 · 过劳 · 惊恐紧张'}
};
function todaySolar(){
var now=new Date(),m=now.getMonth()+1,d=now.getDate();
var cur=ST[0];
for(var i=0;i<ST.length;i++){ var t=ST[i];if(m>t.m||(m===t.m&&d>=t.d)) cur=t; }
return cur;
}
/* ---------- 主渲染 ---------- */
function render(){
var y=+qp('y'),m=+qp('m'),d=+qp('d');
var box=document.getElementById('report');
if(!y||!m||!d||y<1900||y>2100){
box.innerHTML='<div class="err">缺少有效生日参数,请从首页重新进入 🙏</div>';return;
}
document.getElementById('disc').style.display='block';
// === 数字引擎 ===
var v=calc(y,m,d);
var mainNum=v.O;
var person=NUM[mainNum]||{title:'',pos:'',neg:'',job:'',desc:''};
// === 养生引擎(无时辰,默认午时12点) ===
var bz=getBazi(new Date(y,m-1,d),12);
var p5=calc5E(bz);
var strong=strongestEl(p5);
var cst=getConst(p5);
var emo=getEmotion(strong);
// === 今日锦囊 ===
var st=todaySolar();
var sEl=SEASON_EL[new Date().getMonth()];
var yj=YI_JI[sEl];
var paid=localStorage.getItem('jiema_paid')==='1';
box.innerHTML=
'<!-- Part1 数字性格 -->'+
'<div class="card">'+
'<span class="tag tag-num">✨ 数字性格密码</span>'+
'<div class="hex">'+mainNum+'</div>'+
'<div class="p-title">'+mainNum+'号人 · '+esc(person.title)+'</div>'+
'<div class="p-oneline">'+esc((person.desc||'').split(/[。.]/)[0])+'</div>'+
'<div class="pyr">'+miniPyramid(v)+'</div>'+
'<div class="lock '+(paid?'unlocked':'')+'" id="lock-num">'+
'<div class="blur">'+
'<b>天赋优势:</b>'+esc(person.pos)+'<br>'+
'<b>人生课题:</b>'+esc(person.neg)+'<br>'+
'<b>适合方向:</b>'+esc(person.job)+'<br>'+
'<b>能量解读:</b>'+esc(person.desc)+
'</div>'+
'<div class="cover">'+
'<div class="lk-t">🔒 解锁你的完整密码</div>'+
'<div class="lk-d">天赋优势 · 人生课题 · 适合方向 · 12组联合码深度解读</div>'+
'<button class="unlock-btn" onclick="unlock()">¥39.9 立即解锁 →</button>'+
'</div>'+
'</div>'+
'</div>'+
'<!-- Part2 五行体质 -->'+
'<div class="card">'+
'<span class="tag tag-ys">🌿 五行体质画像</span>'+
'<div class="ys-type">'+esc(cst.primary)+'</div>'+
'<div class="ys-dir">核心养护:养'+esc(cst.strong)+' · 需呵护'+esc(cst.weak)+'</div>'+
'<div class="bars">'+
EO.map(function(e){return '<div class="bar-row"><span class="lab" style="color:'+EC[e]+'">'+e+'</span><div class="bar-track"><div class="bar-fill" style="width:'+p5[e]+'%;background:'+EC[e]+'"></div></div><span class="val">'+p5[e]+'%</span></div>';}).join('')+
'</div>'+
'<div class="p-oneline">🎭 情绪画像:'+esc((emo.strength||[]).join(' · '))+'</div>'+
'<div class="lock '+(paid?'unlocked':'')+'" id="lock-ys">'+
'<div class="blur">'+
'<b>体质特征:</b>'+esc(cst.body)+'<br>'+
'<b>调养要点:</b>'+esc(cst.tips)+'<br>'+
'<b>易感倾向:</b>'+esc(cst.diseases)+'<br>'+
'<b>季节风险:</b>'+esc(cst.seasonRisk)+'<br>'+
'<b>食疗方向:</b>'+esc(cst.foodTherapy)+'<br>'+
'<b>作息节律:</b>'+esc(cst.dailyRhythm)+
'</div>'+
'<div class="cover">'+
'<div class="lk-t">🔒 解锁五行体质深度分析</div>'+
'<div class="lk-d">脏腑画像 · 易感倾向 · 食疗 · 运动作息方案</div>'+
'<button class="unlock-btn" onclick="unlock()">¥39.9 立即解锁 →</button>'+
'</div>'+
'</div>'+
'</div>'+
'<!-- Part3 今日养生锦囊(免费) -->'+
'<div class="card jn">'+
'<span class="tag tag-ys">🎯 今日养生锦囊</span>'+
'<div class="st">当前节气:'+st.n+' · 时令 '+sEl+' 当令</div>'+
'<h4>今日调养建议</h4>'+
'<div class="row"><b>宜:</b>'+yj.yi+'</div>'+
'<div class="row"><b>忌:</b>'+yj.ji+'</div>'+
'</div>'+
'<!-- 深度入口 -->'+
'<div class="deep">'+
'<a class="d-num" href="shuzi.html?y='+y+'&m='+m+'&d='+d+'">完整数字报告 →</a>'+
'<a class="d-ys" href="health.html?y='+y+'&m='+m+'&d='+d+'&h=12">完整养生方案 →</a>'+
'</div>'+
'<!-- 契合度(免费给分数) -->'+
'<div class="card">'+
'<span class="tag tag-num">💞 测测你和 TA 的契合度</span>'+
'<div class="mini-form">'+
'<input type="number" id="tY" placeholder="年"><span>年</span>'+
'<input type="number" id="tM" placeholder="月"><span>月</span>'+
'<input type="number" id="tD" placeholder="日"><span>日</span>'+
'<button onclick="matchTest('+y+','+m+','+d+')">测算</button>'+
'</div>'+
'<div id="matchOut"></div>'+
'</div>'+
'<!-- 分享 -->'+
'<div class="card">'+
'<span class="tag tag-num">🦋 分享你的身心密码</span>'+
'<button class="share-btn" onclick="genShare(\''+mainNum+'号人 · '+esc(person.title)+'\',\''+esc(cst.primary)+'\',\'养'+esc(cst.strong)+'\')">📱 生成分享卡片</button>'+
'</div>';
}
/* ---------- 付费墙 ---------- */
function unlock(){
document.getElementById('payModal').classList.add('on');
}
function doUnlock(){
localStorage.setItem('jiema_paid','1');
document.querySelectorAll('.lock').forEach(function(el){el.classList.add('unlocked');});
document.getElementById('payModal').classList.remove('on');
}
/* ---------- 契合度(免费仅分数,深度引导付费/双人报告) ---------- */
function matchTest(y1,m1,d1){
var y=+document.getElementById('tY').value,
m=+document.getElementById('tM').value,
d=+document.getElementById('tD').value;
if(!y||!m||!d){alert('请输入 TA 的完整生日');return;}
var a=calc(y1,m1,d1).O, b=calc(y,m,d).O;
var diff=Math.abs(a-b);
var score=92-diff*6+((a+b)%3)*3;
score=Math.max(60,Math.min(98,score));
document.getElementById('matchOut').innerHTML=
'<div class="match-score">'+score+' 分</div>'+
'<div class="lock" style="margin-top:8px">'+
'<div class="blur">你们在沟通节奏、能量互补、相处课题上的详细解读……</div>'+
'<div class="cover">'+
'<div class="lk-t">🔒 解锁完整契合度解读</div>'+
'<div class="lk-d">关系能量图 · 相处建议 · 沟通指南</div>'+
'<button class="unlock-btn" onclick="alert(\'双人契合度报告 ¥129(模拟)\')">¥129 查看完整解读 →</button>'+
'</div>'+
'</div>';
}
/* ---------- 分享卡片(纯 canvas,无外部库) ---------- */
function genShare(numTitle,constType,ysKey){
var c=document.getElementById('shareCanvas'),g=c.getContext('2d');
var W=c.width,H=c.height;
var grd=g.createLinearGradient(0,0,0,H);
grd.addColorStop(0,'#FF9F80');grd.addColorStop(1,'#FFC8B5');
g.fillStyle=grd;g.fillRect(0,0,W,H);
g.fillStyle='#fff';g.textAlign='center';
g.font='bold 40px sans-serif';g.fillText('愈心解码',W/2,90);
g.font='20px sans-serif';g.fillText('一个生日,读懂身与心',W/2,128);
// 卡片
g.fillStyle='rgba(255,255,255,.95)';
roundRect(g,60,180,W-120,380,24);g.fill();
g.fillStyle='#8a6d3b';g.font='bold 46px sans-serif';
g.fillText(numTitle,W/2,290);
g.fillStyle='#5CB85C';g.font='bold 40px sans-serif';
g.fillText(constType,W/2,390);
g.fillStyle='#666';g.font='24px sans-serif';
g.fillText('养生重点:'+ysKey,W/2,460);
g.fillStyle='#E54D42';g.font='20px sans-serif';
g.fillText('▶ 扫码解锁你的身心密码',W/2,530);
g.fillStyle='#fff';g.font='18px sans-serif';
g.fillText('愈心谷 · 愈见魔方,遇见自己',W/2,H-40);
document.getElementById('shareModal').classList.add('on');
}
function roundRect(g,x,y,w,h,r){
g.beginPath();g.moveTo(x+r,y);g.arcTo(x+w,y,x+w,y+h,r);
g.arcTo(x+w,y+h,x,y+h,r);g.arcTo(x,y+h,x,y,r);g.arcTo(x,y,x+w,y,r);g.closePath();
}
render();
+431
View File
@@ -0,0 +1,431 @@
// ===================== 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));
}
});
// URL 参数预填(从 jiema.html 跳转过来)
window.addEventListener('DOMContentLoaded',function(){
const p=new URLSearchParams(location.search);
const y=p.get('y'),m=p.get('m'),d=p.get('d'),h=p.get('h')||'12';
if(y&&m&&d){
var fb=document.getElementById('fbirth');
var ft=document.getElementById('ftime');
if(fb) fb.value=y+'-'+String(m).padStart(2,'0')+'-'+String(d).padStart(2,'0');
if(ft) ft.value=String(h).padStart(2,'0')+':00';
setTimeout(gen,300);
}
});
+14
View File
@@ -0,0 +1,14 @@
function goDecode(){
var y=document.getElementById('dy').value.trim();
var m=document.getElementById('dm').value.trim();
var d=document.getElementById('dd').value.trim();
if(!y||!m||!d){alert('请输入完整的出生年月日');return;}
location.href='pages/decode.html?y='+encodeURIComponent(y)+'&m='+encodeURIComponent(m)+'&d='+encodeURIComponent(d);
}
['dy','dm','dd'].forEach(function(id){
var el=document.getElementById(id);
if(!el) return;
el.addEventListener('keydown',function(e){
if(e.key==='Enter')goDecode();
});
});
+424
View File
@@ -0,0 +1,424 @@
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();});
})();
+242
View File
@@ -0,0 +1,242 @@
const params = new URLSearchParams(location.search);
const slug = params.get('slug');
let scale = null, answers = [], current = 0, submitted = false, started = false;
async function init() {
if (!slug) { document.getElementById('app').innerHTML = '<div class="loading">缺少量表参数</div>'; return; }
try {
const r = await fetch(`../api/scales/${slug}`);
scale = await r.json();
if (scale.error) throw new Error(scale.error);
answers = new Array(scale.question_count).fill(null);
// Restore from localStorage
const cached = localStorage.getItem(`scale_${slug}`);
if (cached) answers = JSON.parse(cached);
renderStart();
} catch(e) {
document.getElementById('app').innerHTML = `<div class="loading">加载失败: ${e.message}</div>`;
}
}
function renderStart() {
const hasCache = answers.some(a => a !== null);
const answered = answers.filter(a => a !== null).length;
const rawDesc = scale.description || '';
const parts = rawDesc.split('\n\n');
const mainDesc = parts[0] || '';
const metaLine = parts.slice(1).join(' · ') || '';
const timeMin = Math.ceil(scale.question_count / 6);
const timeMax = Math.ceil(scale.question_count / 4);
document.getElementById('app').innerHTML = `
<div style="max-width:500px;margin:0 auto;position:relative;text-align:center;padding:32px 20px 8px">
<a href="scales.html" style="position:absolute;left:20px;top:34px;color:#999;font-size:20px;text-decoration:none;line-height:1">←</a>
<div style="font-size:20px;font-weight:700;color:#333">${scale.name}</div>
</div>
<div class="main" style="justify-content:center;padding:8px 20px 16px">
<div class="qcard" style="text-align:left;flex:none;padding:24px 20px">
${mainDesc ? `<div style="font-size:15px;color:#444;line-height:1.85;margin-bottom:16px">${mainDesc}</div>` : ''}
${metaLine ? `<div style="display:flex;flex-wrap:wrap;gap:6px;margin-bottom:16px">
${metaLine.split('·').filter(m=>m.trim()).map(m =>
`<span style="display:inline-block;padding:4px 10px;background:#f5f5f5;border-radius:6px;font-size:12px;color:#888">${m.trim()}</span>`
).join('')}
</div>` : ''}
<div style="display:flex;align-items:center;gap:12px;padding:12px 14px;background:#f9f9f9;border-radius:8px;margin-bottom:16px;font-size:13px;color:#888">
<span>📝 <b style="color:#555">${scale.question_count}</b> 题</span>
<span>⏱ ${timeMin}-${timeMax} 分钟</span>
<span>🔒 自动保存</span>
</div>
${hasCache
? `<div style="background:#fff5f3;padding:12px;border-radius:8px;margin-bottom:14px;font-size:13px;color:var(--pri);text-align:center">
📌 已有答题进度:<b>${answered}/${scale.question_count}</b> 题
</div>`
: ''}
<button onclick="startTest()" style="display:block;width:100%;padding:15px;border:none;border-radius:10px;background:var(--pri);color:#fff;font-size:17px;font-weight:600;cursor:pointer">
${hasCache ? `继续测试 (${answered}/${scale.question_count})` : '开始测试'}
</button>
${hasCache
? `<button onclick="resetAndStart()" style="display:block;width:100%;padding:12px;border:1px solid #ddd;border-radius:10px;background:#fff;color:#999;font-size:14px;cursor:pointer;margin-top:8px">重新开始</button>`
: ''}
</div>
</div>
`;
}
function startTest() {
started = true;
// Resume from first unanswered question
const firstUnanswered = answers.findIndex(a => a === null);
current = firstUnanswered >= 0 ? firstUnanswered : 0;
render();
}
function resetAndStart() {
answers = new Array(scale.question_count).fill(null);
localStorage.removeItem(`scale_${slug}`);
started = true;
current = 0;
render();
}
function saveCache() {
localStorage.setItem(`scale_${slug}`, JSON.stringify(answers));
}
function selectOption(idx) {
if (submitted) return;
answers[current] = idx;
saveCache();
render();
// Auto advance after short delay
if (current < scale.question_count - 1) {
setTimeout(() => { current++; render(); }, 300);
}
}
function goPrev() { if (current > 0) { current--; render(); } }
function goNext() { if (current < scale.question_count - 1) { current++; render(); } }
async function submit() {
if (submitted) return;
submitted = true;
saveCache();
try {
const r = await fetch('../api/scales/result', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
scale_slug: scale.slug,
scale_name: scale.name,
answers: answers,
})
});
const data = await r.json();
// Clear cache
localStorage.removeItem(`scale_${slug}`);
renderResult(data);
} catch(e) {
submitted = false;
alert('提交失败,请重试');
render();
}
}
function renderResult(data) {
const s = data.scores;
const allAnswered = s.answer_count;
const total = scale.question_count;
const scoreDisplay = s.std_score !== null ? s.std_score : s.raw_score;
const isStd = s.std_score !== null;
const isFactorScale = s.factors && Object.keys(s.factors).length >= 6;
const hasFactors = s.factors && Object.keys(s.factors).length > 0;
// Factor bars (for any factor count, not just 8+)
let factorsHtml = '';
if (hasFactors) {
const factorMax = s.factor_max || {};
const bars = Object.entries(s.factors).map(([k,v]) => {
const max = factorMax[k] || 50;
const pct = Math.min(Math.round(v / max * 100), 100);
return `<div style="margin:6px 0">
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:2px">
<span style="color:#666">${k}</span><span style="color:var(--pri);font-weight:600">${v}<span style="color:#bbb;font-weight:400">/${max}</span></span>
</div>
<div style="height:6px;background:#f0f0f0;border-radius:3px;overflow:hidden">
<div style="height:100%;width:${pct}%;background:linear-gradient(90deg,var(--pri),#F0987A);border-radius:3px;transition:width 0.5s"></div>
</div>
</div>`;
}).join('');
let insightHtml = '';
if (isFactorScale) {
const sorted = Object.entries(s.factors).sort((a,b) => b[1]-a[1]);
const top3 = sorted.slice(0,3).map(([k,v]) => `<b>${k}</b>`).join('、');
insightHtml = `<div style="margin-top:14px;padding:12px;background:#fff5f3;border-radius:8px;font-size:13px;line-height:1.7">
🌟 <b>优势方向:</b>${top3}<br>
<span style="color:#999">得分最高的领域代表天赋倾向,建议重点关注和培养。</span>
</div>`;
}
factorsHtml = `<div style="margin:20px 0 0;text-align:left">
<div style="font-size:14px;font-weight:600;color:#555;margin-bottom:8px">${isFactorScale ? '各维度得分' : '因子得分'}</div>
${bars}
${insightHtml}
</div>`;
}
// Rating badge
let ratingHtml = '';
if (s.rating) {
ratingHtml = `<div style="display:inline-block;margin-top:8px;padding:6px 18px;background:${isFactorScale?'#fff5f3':'#fef0ed'};border-radius:20px;font-size:15px;font-weight:600;color:var(--pri)">${s.rating}</div>`;
}
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
</div>
<div class="result-card" style="text-align:center">
<div style="font-size:13px;color:#bbb;margin-bottom:4px">测评完成 · ${allAnswered}/${total} 题</div>
${isFactorScale
? `<div style="font-size:22px;font-weight:700;color:#333;margin:8px 0 4px">多元智能测评结果</div>`
: `<div style="margin:8px 0">
<span style="font-size:56px;font-weight:700;color:var(--pri);line-height:1">${scoreDisplay}</span>
${isStd ? `<span style="font-size:16px;color:#999;margin-left:4px">分</span>` : ''}
</div>
${isStd ? `<div style="font-size:12px;color:#bbb;margin-top:-4px">原始分 ${s.raw_score} × 1.25 = 标准分 ${scoreDisplay}</div>` : ''}
${!isStd && !isFactorScale ? `<div style="font-size:13px;color:#999">满分 ${s.max_score} · 占比 ${(s.raw_score/s.max_score*100).toFixed(0)}%</div>` : ''}`}
${ratingHtml}
${factorsHtml}
<div style="margin-top:24px;display:flex;gap:10px;justify-content:center">
<a href="scales.html" style="flex:1;padding:12px;background:var(--pri);color:#fff;border-radius:10px;text-decoration:none;font-size:14px;font-weight:500">返回量表库</a>
<a href="scale-test.html?slug=${slug}" style="flex:1;padding:12px;background:#f5f5f5;color:#666;border-radius:10px;text-decoration:none;font-size:14px">重新测试</a>
</div>
<p style="margin-top:16px;color:#ccc;font-size:11px">* 结果仅供参考,不能替代专业诊断</p>
</div>
`;
}
function render() {
if (!scale) return;
const total = scale.question_count;
const pct = ((current + 1) / total * 100).toFixed(0);
const isLast = current === total - 1;
const selected = answers[current];
const q = scale.questions[current];
const opts = scale.options[current];
const optHtml = opts.map((o, i) => `
<button class="option${selected === i ? ' selected' : ''}" onclick="selectOption(${i})">
${String.fromCharCode(65+i)}. ${o}
</button>
`).join('');
const allAnswered = answers.every(a => a !== null);
document.getElementById('app').innerHTML = `
<div class="top-bar">
<a href="scales.html" class="back">←</a>
<div class="title">${scale.name}</div>
<div class="qnum">${current+1}/${total}</div>
</div>
<div class="progress"><div class="bar" style="width:${pct}%"></div></div>
<div class="main">
<div class="qcard">
<div class="qtext"><span class="qidx">${current+1}.</span>${q}</div>
<div class="options">${optHtml}</div>
</div>
</div>
<div class="btns">
<button class="btn-prev" onclick="goPrev()" ${current===0?'disabled':''}>上一题</button>
${isLast
? `<button class="btn-submit" onclick="submit()" ${allAnswered?'':'disabled'}>✓ 提交答卷</button>`
: `<button class="btn-next" onclick="goNext()" ${selected===null?'disabled':''}>下一题 →</button>`
}
</div>
${isLast ? `<div style="text-align:center;padding:0 20px 20px;font-size:12px;color:#999">${allAnswered?'全部答完,可以提交了':'还有题目未作答'}</div>` : ''}
`;
}
init();
+53
View File
@@ -0,0 +1,53 @@
const ICONS = {'心理测试综合':'📋','人格测试':'🎭','抑郁症测试':'🌧️','焦虑症测试':'😰','强迫症测试':'🔄','躁狂症测试':'🔥','双向情感障碍':'🌓','恐惧症测试':'😨','智商测试':'🧠','情商测试':'💝','其它测试':'📌'};
const CAT_KEYS = {'心理测试综合':'cat-综合','人格测试':'cat-人格','抑郁症测试':'cat-抑郁','焦虑症测试':'cat-焦虑','强迫症测试':'cat-强迫','躁狂症测试':'cat-躁狂','双向情感障碍':'cat-双相','恐惧症测试':'cat-恐惧','智商测试':'cat-智商','情商测试':'cat-情商','其它测试':'cat-其他'};
let scales = [], activeCat = '全部';
async function load() {
document.getElementById('content').innerHTML = '<div class="loading">加载中…</div>';
try {
const r = await fetch('../api/scales');
scales = await r.json();
renderCats();
renderScales();
} catch(e) {
document.getElementById('content').innerHTML = '<div class="loading">加载失败,请刷新重试</div>';
}
}
function renderCats() {
const cats = [...new Set(scales.map(s=>s.category))];
const html = ['<button class="cat-btn active" onclick="filter(\'全部\')">全部</button>'];
cats.forEach(c => {
html.push(`<button class="cat-btn" onclick="filter('${c}')">${c}</button>`);
});
document.getElementById('cats').innerHTML = html.join('');
}
function filter(cat) {
activeCat = cat;
document.querySelectorAll('.cat-btn').forEach(b => b.classList.toggle('active', b.textContent === cat));
renderScales();
}
function renderScales() {
const filtered = activeCat === '全部' ? scales : scales.filter(s=>s.category===activeCat);
if (!filtered.length) {
document.getElementById('content').innerHTML = '<div class="empty">暂无数据</div>';
return;
}
const html = filtered.map(s => `
<a href="scale-test.html?slug=${s.slug}" class="scale-card">
<div class="icon ${CAT_KEYS[s.category]||'cat-其他'}">${ICONS[s.category]||'📌'}</div>
<div class="info">
<div class="name">${s.name}</div>
<div class="desc">${s.description||''}</div>
</div>
<div class="count">${s.question_count}题</div>
</a>
`).join('');
document.getElementById('content').innerHTML = `<div class="scales">${html}</div>`;
}
load();
+60
View File
@@ -0,0 +1,60 @@
/* 愈心谷共享壳 —— 底栏注入、高亮、二级返回 */
(function () {
var body = document.body;
if (!body) return;
var navKey = body.getAttribute('data-nav') || 'none';
var backTo = body.getAttribute('data-back') || '';
var pageTitle = body.getAttribute('data-title') || document.title || '';
var base = body.getAttribute('data-base') || '';
// 根据当前路径推断资源根:根目录页用 ''pages/ 下用 '../'
if (!base) {
base = /\/pages\//.test(location.pathname) ? '../' : '';
}
body.classList.add('app-body');
if (navKey === 'none') body.classList.add('no-nav');
function abs(path) {
if (!path) return base;
if (/^(https?:|mailto:|tel:|#)/.test(path)) return path;
return base + path.replace(/^\.\//, '');
}
// 二级顶栏
if (navKey === 'none' && (backTo || pageTitle)) {
var top = document.createElement('div');
top.className = 'app-topbar';
var backHref = abs(backTo || 'yuxingu.html');
top.innerHTML =
'<a class="back" href="' + backHref + '" aria-label="返回">←</a>' +
'<div class="title"></div>';
top.querySelector('.title').textContent = pageTitle;
body.insertBefore(top, body.firstChild);
}
// 一级底栏
if (navKey !== 'none') {
var items = [
{ key: 'home', href: 'yuxingu.html', icon: '⌂', label: '首页' },
{ key: 'know', href: 'pages/know.html', icon: '◉', label: '认识自己' },
{ key: 'scales', href: 'pages/scales.html', icon: '☰', label: '专业测评' },
{ key: 'activity', href: 'pages/activity.html', icon: '✦', label: '私域活动' },
{ key: 'health', href: 'pages/health.html', icon: '♡', label: '健康管理' },
{ key: 'mine', href: 'pages/mine.html', icon: '◍', label: '我的' }
];
var nav = document.createElement('nav');
nav.className = 'app-nav';
nav.setAttribute('aria-label', '主导航');
nav.innerHTML = items.map(function (it) {
var cls = it.key === navKey ? ' active' : '';
return (
'<a href="' + abs(it.href) + '" class="' + cls.trim() + '">' +
'<span class="ni">' + it.icon + '</span>' + it.label +
'</a>'
);
}).join('');
body.appendChild(nav);
}
})();
+465
View File
@@ -0,0 +1,465 @@
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]},
};
// ===== 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 freeHtml=`<div style="text-align:center;margin-bottom:18px;">`;
freeHtml+=`<div style="font-size:2.4em;font-weight:700;color:#c47a78;line-height:1;margin-bottom:4px;">${v.O}号人</div>`;
freeHtml+=`<div style="font-size:0.85em;font-weight:600;color:#5a4636;margin-bottom:6px;">${n.title||''}</div>`;
freeHtml+=`<div style="font-size:0.75em;color:#6b5b4e;line-height:1.7;max-width:340px;margin:0 auto;text-align:left;">${n.desc||''}</div>`;
freeHtml+=`</div>`;
let premiumHtml=``;
premiumHtml+=`<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;">`;
premiumHtml+=`<b>✦ 天赋优势</b>${n.pos||''}<br><b>✦ 人生课题</b>${n.neg||''}<br><b>✦ 五行属性</b>${n.elem||''}<br><b>✦ 适合职业</b>${n.job||''}</div></div>`;
premiumHtml+=`<hr class="divider">`;
premiumHtml+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:8px;">✦ 数字能量图谱 ✦</div>`;
premiumHtml+=`<div style="font-size:0.7em;color:#6b5b4e;text-align:center;margin-bottom:0;">你的生命三角共汇聚了 <b>${allVals.filter(x=>x>=1).length}</b> 个能量数字</div>`;
premiumHtml+=`<div class="scene-3d" id="scene3d"><canvas></canvas><div class="scene-hint">↻ 左右旋转</div></div>`;
premiumHtml+=`<div class="freq-label">🔥 能量最强:<b>${most[0]}</b>(出现 ${most[1]} 次),是你生命中最闪耀的特质</div>`;
if(least.length>0)premiumHtml+=`<div class="freq-label" style="margin-top:0;">💧 能量最弱:${least.map(([n])=>`<b>${n}</b>`).join('、')}(仅 ${least[0][1]} 次),值得你用心浇灌</div>`;
premiumHtml+=`<hr class="divider">`;
premiumHtml+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 三角内外——真实自我与外界眼光 ✦</div>`;
premiumHtml+=`<div style="font-size:0.7em;line-height:1.7;color:#6b5b4e;padding:0 4px;">`;
premiumHtml+=`<div><b>▸ 三角内部</b>:代表你内在真实的自我——潜意识的驱动、与生俱来的性格特质、不为人知的内心世界。</div>`;
premiumHtml+=`<div style="margin-top:3px;"><b>▸ 三角外部</b>:代表外界对你的认知——你在工作、家庭、社交中呈现出的样子,以及他人眼中的你。</div>`;
premiumHtml+=`<div style="margin-top:3px;"><b>▸ 下方基因</b>(父母遗传):代表来自父亲和母亲的先天遗传基因,是你性格的底层土壤。</div></div>`;
premiumHtml+=`<hr class="divider">`;
premiumHtml+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 性格密码深度解读 ✦</div>`;
premiumHtml+=`<div style="font-size:0.7em;line-height:1.8;color:#6b5b4e;padding:0 4px;">`;
premiumHtml+=`<div style="margin-bottom:4px;"><b>潜意识码:${v.I}${v.J}${v.K}${v.L}</b></div>`;
premiumHtml+=`<div style="color:#9b8a78;margin-bottom:8px;">你内心深处的真实驱动力,最本能的反应模式。四个数字分别主宰:思维、情感、行动、人际。</div>`;
premiumHtml+=`<div style="margin-bottom:4px;"><b>内心码:${v.M}${v.N}</b></div>`;
premiumHtml+=`<div style="color:#9b8a78;margin-bottom:8px;">深层情感模式与核心价值观。理性行动 vs 感性人际——两者的平衡就是你面对压力时的姿态。</div>`;
premiumHtml+=`<div style="margin-bottom:4px;"><b>坐镇码:${v.I}${v.M}${v.O}</b></div>`;
premiumHtml+=`<div style="color:#9b8a78;margin-bottom:8px;">性格核心轴线:潜意识→内心→主性格。三环相扣,是你从内驱力到外在呈现的完整链条,也是你最稳定的底色。</div>`;
premiumHtml+=`<div style="margin-bottom:4px;"><b>父亲基因:${v.EF.join('')} ${v.GH.join('')}</b> · <b>母亲基因:${v.AB.join('')} ${v.CD.join('')}</b></div>`;
premiumHtml+=`<div style="color:#9b8a78;">父亲基因 ← 年份 ${v.EF.join('')}${v.GH.join('')})承载家族传承与事业基因<br>母亲基因 ← 月${v.CD.join('')}${v.AB.join('')})影响情感模式与人际底色</div></div>`;
premiumHtml+=`<hr class="divider">`;
premiumHtml+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 数字缺失与能量过剩 ✦</div>`;
premiumHtml+=`<div style="font-size:0.7em;line-height:1.7;color:#6b5b4e;padding:0 4px;">`;
if(miss.both.length>0){premiumHtml+=`<div style="margin-bottom:4px;"><b>⚠ 缺失数字</b></div>`;miss.both.forEach(n=>{premiumHtml+=`<div style="margin-bottom:3px;">· <b>缺${n}</b>${MISS_MSG[n]||''}</div>`;});}
else premiumHtml+=`<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){premiumHtml+=`<div style="margin-top:6px;margin-bottom:4px;"><b>⚡ 能量过剩</b></div>`;over.forEach(n=>{premiumHtml+=`<div style="margin-bottom:3px;">· <b>多${n}</b>(×${freq[n]}):${MISS_STRONG[n]||''}</div>`;});}
premiumHtml+=`</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])=>{const k=''+v[a]+v[b];const jk=Object.keys(JOINT).find(x=>x.startsWith(k));return {code:k,label:jk?JOINT[jk]:'—'};});
premiumHtml+=`<hr class="divider">`;
premiumHtml+=`<div style="font-size:0.82em;font-weight:600;color:#5a4636;text-align:center;margin-bottom:6px;">✦ 12组人生联合码 ✦</div>`;
premiumHtml+=`<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=>{
premiumHtml+=`<div class="joint-age">${ag.label}</div>`;
premiumHtml+=`<div style="font-size:0.65em;color:#9b8a78;text-align:center;margin:-2px 0 6px;line-height:1.5;">${ag.desc}</div>`;
premiumHtml+=`<div class="joint-grid">`;
ag.codes.forEach(jc=>{premiumHtml+=`<div><span class="jc">${jc.code}</span> <span class="jm">${jc.label}</span></div>`;});
premiumHtml+=`</div>`;
});
const paid=localStorage.getItem('jiema_paid');
const lockEl=document.getElementById('missingBlock');
if(paid){
lockEl.innerHTML=freeHtml+premiumHtml;
}else{
lockEl.innerHTML=freeHtml+'<div class="shuzi-lock" id="shuziLock">'+premiumHtml+'</div>';
const lock=document.getElementById('shuziLock');
Array.from(lock.children).forEach(c=>c.classList.add('blur'));
const cover=document.createElement('div');cover.className='cover';
cover.innerHTML='<div class="lk-t">🔒 解锁你的完整数字报告</div><div class="lk-d">天赋优势 · 能量分布 · 性格编码 · 12组联合码</div>';
const btn=document.createElement('button');btn.className='lk-btn';btn.textContent='¥39.9 立即解锁 →';
btn.addEventListener('click',unlockShuzi);cover.appendChild(btn);lock.appendChild(cover);
}
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 unlockShuzi(){localStorage.setItem("jiema_paid","1");const l=document.getElementById("shuziLock");l.querySelectorAll(".blur").forEach(c=>c.classList.remove("blur"));l.classList.add("unlocked");}
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();
// URL 参数预填(从 jiema.html 跳转过来)
(function(){
const p=new URLSearchParams(location.search);
const y=p.get('y'),m=p.get('m'),d=p.get('d');
if(y&&m&&d){
document.getElementById('inY').value=y;
document.getElementById('inM').value=m;
document.getElementById('inD').value=d;
setTimeout(go,200);
}
})();
+1089
View File
File diff suppressed because one or more lines are too long