小程序初始化

This commit is contained in:
2026-02-27 11:32:50 +08:00
parent 93574dbb45
commit 97e1ea2706
252 changed files with 32427 additions and 12363 deletions
+258
View File
@@ -0,0 +1,258 @@
import { state } from './data.js';
import { RegistrationWizard, AppPages } from './components.js';
const DOM = {
onboarding: document.getElementById('onboarding'),
wizardContent: document.getElementById('wizard-content'),
mainContent: document.getElementById('main-content'),
pageRender: document.getElementById('page-render'),
bottomNav: document.getElementById('bottom-nav'),
musicToggle: document.getElementById('music-toggle'),
bgMusic: document.getElementById('bg-music'),
musicDisc: document.getElementById('music-disc'),
profileBtn: document.getElementById('profile-btn')
};
function initStars() {
const starContainer = document.getElementById('stars');
starContainer.innerHTML = '';
for (let i = 0; i < 60; i++) {
const star = document.createElement('div');
star.className = 'star';
const size = Math.random() * 3 + 1;
const xMove = (Math.random() - 0.5) * 100;
const yMove = (Math.random() - 0.5) * 100;
const duration = 15 + Math.random() * 20;
const delay = Math.random() * -20;
const opacity = 0.2 + Math.random() * 0.5;
star.style.width = `${size}px`;
star.style.height = `${size}px`;
star.style.left = `${Math.random() * 100}%`;
star.style.top = `${Math.random() * 100}%`;
star.style.setProperty('--x', `${xMove}px`);
star.style.setProperty('--y', `${yMove}px`);
star.style.setProperty('--duration', `${duration}s`);
star.style.setProperty('--opacity', opacity);
star.style.animationDelay = `${delay}s`;
starContainer.appendChild(star);
}
}
function updateWizard() {
DOM.wizardContent.innerHTML = `
<div class="flex-1 overflow-y-auto custom-scroll">
${RegistrationWizard.renderStep(state.onboardingStep, state.user)}
</div>
<div class="pt-6 flex justify-between items-center">
<div class="flex gap-1.5">
${[0, 1, 2, 3, 4].map(i => `<div class="w-6 h-1 rounded-full transition-all duration-700 ${i === state.onboardingStep ? 'bg-purple-400 w-10 shadow-[0_0_12px_rgba(168,85,247,0.6)]' : 'bg-white/10'}"></div>`).join('')}
</div>
<button id="next-step" class="px-10 py-3.5 bg-gradient-to-r from-purple-500 to-violet-400 rounded-2xl text-white text-sm font-bold shadow-xl shadow-purple-500/10 active:scale-95 transition-all">
${state.onboardingStep === 4 ? '同步星海' : '继续'}
</button>
</div>
`;
lucide.createIcons();
document.getElementById('next-step').addEventListener('click', () => {
saveCurrentStepData();
if (state.onboardingStep < 4) {
state.onboardingStep++;
gsap.fromTo('#wizard-content > div:first-child', { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 0.5, ease: "power2.out" });
updateWizard();
} else {
completeOnboarding();
}
});
}
function saveCurrentStepData() {
const s = state.onboardingStep;
if (s === 0) {
state.user.nickname = document.getElementById('nickname').value;
state.user.gender = document.getElementById('gender').value;
state.user.zodiac = document.getElementById('zodiac').value;
state.user.mbti = document.getElementById('mbti').value;
state.user.hobbies = document.getElementById('hobbies').value.split(',').map(v => v.trim()).filter(v => v);
} else if (s === 1) {
state.user.childhood = document.getElementById('childhood').value;
state.user.childhoodDate = document.getElementById('childhood-date').value;
} else if (s === 2) {
state.user.happyMoment = document.getElementById('happy-moment').value;
state.user.happyDate = document.getElementById('happy-date').value;
} else if (s === 3) {
state.user.lowPoint = document.getElementById('low-point').value;
state.user.lowDate = document.getElementById('low-date').value;
} else if (s === 4) {
state.user.aspirations = document.getElementById('aspirations').value;
}
}
function completeOnboarding() {
gsap.to(DOM.onboarding, {
opacity: 0,
duration: 1.5,
ease: "power3.inOut",
onComplete: () => {
DOM.onboarding.style.display = 'none';
DOM.mainContent.style.display = 'flex';
DOM.bottomNav.style.display = 'flex';
switchTab('record');
}
});
}
function switchTab(tab) {
document.querySelectorAll('.nav-item').forEach(el => {
el.classList.toggle('active', el.dataset.tab === tab);
});
let content = "";
if (tab === 'record') content = AppPages.record(state.events);
if (tab === 'script') content = AppPages.script(state.scripts, state.isGenerating, state.scriptConfig, state.npcConfig, state.user, state.customPersonas);
if (tab === 'path') content = AppPages.path(state.currentPath);
if (tab === 'profile') content = AppPages.profile(state.user);
DOM.pageRender.innerHTML = content;
lucide.createIcons();
if (tab === 'record') {
document.getElementById('save-event').addEventListener('click', handleSaveEvent);
}
if (tab === 'script') {
document.getElementById('gen-script')?.addEventListener('click', handleGenerateScript);
document.getElementById('edit-name')?.addEventListener('input', (e) => state.user.nickname = e.target.value);
document.getElementById('edit-zodiac')?.addEventListener('change', (e) => state.user.zodiac = e.target.value);
document.getElementById('edit-mbti')?.addEventListener('change', (e) => state.user.mbti = e.target.value);
document.getElementById('edit-job')?.addEventListener('input', (e) => state.user.profession = e.target.value);
document.getElementById('add-persona-btn')?.addEventListener('click', () => {
const name = document.getElementById('npc-name').value;
const role = document.getElementById('npc-role').value;
if (name) {
state.customPersonas.push({ name, role, id: Date.now() });
switchTab('script');
}
});
document.querySelectorAll('.delete-persona-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const idx = e.currentTarget.dataset.idx;
state.customPersonas.splice(idx, 1);
switchTab('script');
});
});
document.getElementById('npc-name')?.addEventListener('input', (e) => state.npcConfig.name = e.target.value);
document.getElementById('npc-role')?.addEventListener('change', (e) => state.npcConfig.role = e.target.value);
document.getElementById('npc-relation')?.addEventListener('change', (e) => state.npcConfig.relation = e.target.value);
document.getElementById('npc-desc')?.addEventListener('input', (e) => state.npcConfig.desc = e.target.value);
document.querySelectorAll('.style-btn').forEach(btn => {
btn.addEventListener('click', () => {
state.scriptConfig.style = btn.dataset.val;
switchTab('script');
});
});
document.querySelectorAll('.length-btn').forEach(btn => {
btn.addEventListener('click', () => {
state.scriptConfig.length = btn.dataset.val;
switchTab('script');
});
});
document.querySelectorAll('.select-script').forEach(btn => {
btn.addEventListener('click', (e) => {
const id = e.currentTarget.dataset.id;
state.currentPath = state.scripts.find(s => s.id == id);
switchTab('path');
});
});
}
}
function handleSaveEvent() {
const title = document.getElementById('event-title').value;
const time = document.getElementById('event-time').value;
const content = document.getElementById('event-content').value;
if (!title || !content) return;
const newEvent = {
id: Date.now(),
title,
time: time || new Date().toISOString().split('T')[0],
content,
aiReply: "星河守望者正在解读这段紫色波频...",
isNew: true
};
state.events.unshift(newEvent);
switchTab('record');
setTimeout(() => {
const fullReply = `这段记忆碎片在星海中漾起涟漪。你在${title}中展现的特质,正在重新定义你人生OS的底层代码。继续保持这份觉知。`;
state.events[0].aiReply = fullReply;
state.events[0].isNew = false;
switchTab('record');
}, 1500);
}
function handleGenerateScript() {
const theme = document.getElementById('script-theme').value;
if (!theme) return;
if (state.isGenerating) return;
state.isGenerating = true;
switchTab('script');
setTimeout(() => {
const { name, role, relation, desc } = state.npcConfig;
const npcFrag = name ? `,在 ${name}(你的${role},关系:${relation})的陪伴下,` : "";
const script = {
id: Date.now(),
title: `${theme}》· 星源篇`,
summary: `基于你(${state.user.nickname},一名${state.user.profession})过往的紫气东来,在${state.scriptConfig.style}的世界观中${npcFrag}你将重新定义自我。${desc ? '命运齿轮已然转动:' + desc : ''}`,
persona: state.user.mbti || "追光者",
steps: [
{ task: "紫微入命", desc: `在"${theme}"的意志中开启新的人生循环。`, done: true },
{ task: "能量共振", desc: name ? `${name} 达成灵魂契约。` : "在孤独中寻找真理,掌握世界的运行法则。", done: true },
{ task: "代码重写", desc: "在最为艰难的时刻,将过去的所有记录化作突围的力量。", done: false },
{ task: "最终归宿", desc: "达成人生OS的最高成就,实现与理想自我的合一。", done: false }
]
};
state.scripts.unshift(script);
state.isGenerating = false;
switchTab('script');
}, 2500);
}
DOM.musicToggle.addEventListener('click', () => {
if (state.isPlaying) {
DOM.bgMusic.pause();
DOM.musicDisc.classList.remove('animate-spin-slow');
DOM.musicToggle.classList.add('opacity-40');
} else {
DOM.bgMusic.play();
DOM.musicDisc.classList.add('animate-spin-slow');
DOM.musicToggle.classList.remove('opacity-40');
}
state.isPlaying = !state.isPlaying;
});
DOM.bottomNav.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (btn) switchTab(btn.dataset.tab);
});
DOM.profileBtn.addEventListener('click', () => switchTab('profile'));
initStars();
updateWizard();
lucide.createIcons();
+334
View File
@@ -0,0 +1,334 @@
import { zodiacs, mbtis, hintWords, scriptStyles, scriptLengths, npcRoles, npcRelations } from './data.js';
export const RegistrationWizard = {
renderStep: (step, user) => {
const steps = [
`<div class="space-y-6">
<div class="space-y-2">
<h2 class="text-3xl font-light tracking-tight text-white">系统初始化</h2>
<p class="text-purple-400/60 text-sm italic">定义你在人生OS中的唯一代码</p>
</div>
<div class="space-y-4">
<input id="nickname" type="text" placeholder="你的昵称" class="w-full bg-transparent border-b border-purple-500/30 py-3 focus:border-purple-400 outline-none transition-all" value="${user.nickname}">
<div class="grid grid-cols-2 gap-4">
<select id="gender" class="bg-white/5 p-3 rounded-xl border border-white/10 outline-none text-purple-100">
<option value="">性别</option><option value="male" ${user.gender==='male'?'selected':''}>男性</option><option value="female" ${user.gender==='female'?'selected':''}>女性</option>
</select>
<select id="zodiac" class="bg-white/5 p-3 rounded-xl border border-white/10 outline-none text-purple-100">
<option value="">星座</option>
${zodiacs.map(z => `<option value="${z}" ${user.zodiac===z?'selected':''}>${z}</option>`).join('')}
</select>
</div>
<select id="mbti" class="w-full bg-white/5 p-3 rounded-xl border border-white/10 outline-none text-purple-100">
<option value="">MBTI人格</option>
${mbtis.map(m => `<option value="${m}" ${user.mbti===m?'selected':''}>${m}</option>`).join('')}
</select>
<input id="hobbies" type="text" placeholder="兴趣爱好 (逗号分隔)" class="w-full bg-transparent border-b border-purple-500/30 py-3 focus:border-purple-400 outline-none" value="${user.hobbies.join(', ')}">
</div>
</div>`,
`<div class="space-y-6">
<div class="space-y-2">
<h2 class="text-3xl font-light text-white">回溯起源</h2>
<p class="text-purple-400/60 text-sm">一段让你感到温暖的早期时光</p>
</div>
<div class="space-y-4">
<input type="date" id="childhood-date" value="${user.childhoodDate}" class="w-full bg-white/5 p-3 rounded-xl border border-white/10 text-sm text-purple-200">
<textarea id="childhood" placeholder="回忆一段具体且温暖的午后..." class="w-full h-32 bg-white/5 p-4 rounded-xl border border-white/10 outline-none resize-none">${user.childhood}</textarea>
<div class="hint-container">
<p class="text-[10px] text-purple-400/50 mb-3 px-1 flex items-center gap-2 uppercase tracking-widest">
<i data-lucide="sparkles" class="w-3 h-3"></i> 灵感气泡
</p>
<div class="flex flex-wrap gap-2">
${hintWords.childhood.map((h, i) => `<button class="hint-chip bubble-anim" style="--delay: ${i}" onclick="document.getElementById('childhood').value += '${h} '"><span>${h}</span></button>`).join('')}
</div>
</div>
</div>
</div>`,
`<div class="space-y-6">
<div class="space-y-2">
<h2 class="text-3xl font-light text-white">悦然时刻</h2>
<p class="text-purple-400/60 text-sm">一段感受到生命跃动的经历</p>
</div>
<div class="space-y-4">
<input type="date" id="happy-date" value="${user.happyDate}" class="w-full bg-white/5 p-3 rounded-xl border border-white/10 text-sm text-purple-200">
<textarea id="happy-moment" placeholder="想一个令你会心一笑的瞬间..." class="w-full h-32 bg-white/5 p-4 rounded-xl border border-white/10 outline-none resize-none">${user.happyMoment}</textarea>
<div class="hint-container">
<p class="text-[10px] text-purple-400/50 mb-3 px-1 flex items-center gap-2 uppercase tracking-widest">
<i data-lucide="sparkles" class="w-3 h-3"></i> 灵感气泡
</p>
<div class="flex flex-wrap gap-2">
${hintWords.happy.map((h, i) => `<button class="hint-chip bubble-anim" style="--delay: ${i}" onclick="document.getElementById('happy-moment').value += '${h} '"><span>${h}</span></button>`).join('')}
</div>
</div>
</div>
</div>`,
`<div class="space-y-6">
<div class="space-y-2">
<h2 class="text-3xl font-light text-white">破茧成蝶</h2>
<p class="text-purple-400/60 text-sm">在宁静中默默积蓄力量的日子</p>
</div>
<div class="space-y-4">
<input type="date" id="low-date" value="${user.lowDate}" class="w-full bg-white/5 p-3 rounded-xl border border-white/10 text-sm text-purple-200">
<textarea id="low-point" placeholder="描述那段有些艰难但让你成长的日子..." class="w-full h-32 bg-white/5 p-4 rounded-xl border border-white/10 outline-none resize-none">${user.lowPoint}</textarea>
<div class="hint-container">
<p class="text-[10px] text-purple-400/50 mb-3 px-1 flex items-center gap-2 uppercase tracking-widest">
<i data-lucide="sparkles" class="w-3 h-3"></i> 灵感气泡
</p>
<div class="flex flex-wrap gap-2">
${hintWords.low.map((h, i) => `<button class="hint-chip bubble-anim" style="--delay: ${i}" onclick="document.getElementById('low-point').value += '${h} '"><span>${h}</span></button>`).join('')}
</div>
</div>
</div>
</div>`,
`<div class="space-y-6">
<div class="space-y-2">
<h2 class="text-3xl font-light text-white">未来憧憬</h2>
<p class="text-purple-400/60 text-sm">对未来理想生活状态的预见</p>
</div>
<div class="space-y-4">
<textarea id="aspirations" placeholder="你想成为怎样的人?尝试描述那幅画面..." class="w-full h-48 bg-white/5 p-4 rounded-xl border border-white/10 outline-none resize-none">${user.aspirations}</textarea>
<div class="flex items-center gap-2 p-4 glass-gold rounded-xl border-purple-500/30">
<i data-lucide="sparkles" class="w-5 h-5 text-purple-400"></i>
<span class="text-xs text-purple-200/80 italic">“照见未来,便是创造的开始。”</span>
</div>
</div>
</div>`
];
return steps[step];
}
};
export const AppPages = {
record: (events) => `
<div class="space-y-6 pb-4">
<div class="glass p-5 rounded-3xl space-y-4 border-white/10">
<div class="flex items-center gap-2 mb-2">
<div class="w-2 h-2 rounded-full bg-purple-400"></div>
<span class="text-[10px] font-bold text-purple-400 uppercase tracking-widest">记叙当下</span>
</div>
<input id="event-title" type="text" placeholder="为这段记忆命名" class="w-full bg-transparent border-b border-white/10 py-2 outline-none text-purple-50">
<input id="event-time" type="date" class="bg-transparent text-sm text-purple-400/60 outline-none">
<textarea id="event-content" placeholder="此刻的心境或发生的事..." class="w-full bg-transparent h-20 outline-none resize-none text-sm text-white/80"></textarea>
<button id="save-event" class="w-full py-3.5 bg-gradient-to-r from-purple-600/40 to-purple-400/40 border border-purple-500/30 rounded-2xl text-white text-sm font-medium hover:from-purple-600/60 transition-all">镌刻至星海</button>
</div>
<div class="space-y-6" id="event-list">
${events.map(ev => `
<div class="glass p-5 rounded-3xl space-y-3 relative overflow-hidden group border-white/5">
<div class="flex justify-between items-start">
<h3 class="font-medium text-purple-100">${ev.title}</h3>
<span class="text-[10px] text-purple-400/50">${ev.time}</span>
</div>
<p class="text-sm text-white/70 leading-relaxed">${ev.content}</p>
<div class="pt-3 border-t border-white/5 space-y-2 ai-card-glow-gold rounded-2xl p-4 bg-purple-500/5">
<div class="flex items-center gap-2 text-[10px] text-purple-400 font-bold uppercase tracking-widest">
<i data-lucide="sparkle" class="w-3 h-3 text-purple-400"></i> Life Harmony AI
</div>
<p class="text-xs text-purple-100/80 italic leading-snug ${ev.isNew ? 'typing-text' : ''}">${ev.aiReply}</p>
</div>
</div>
`).join('')}
</div>
</div>
`,
script: (scripts, isGenerating, config, npc, user, customPersonas) => `
<div class="space-y-6">
<h2 class="text-xl font-light text-purple-100">剧本生成器</h2>
<!-- User Base Info Section -->
<div class="glass p-5 rounded-3xl border-purple-500/20 space-y-4">
<div class="flex items-center justify-between">
<span class="text-[10px] text-purple-400 uppercase tracking-wider font-bold">我的基础人设</span>
<span class="text-[8px] text-white/30 italic">可自由修改</span>
</div>
<div class="grid grid-cols-2 gap-3">
<input id="edit-name" type="text" value="${user.nickname}" placeholder="姓名" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[11px] outline-none text-white focus:border-purple-500/50">
<select id="edit-zodiac" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[11px] outline-none text-white">
${zodiacs.map(z => `<option value="${z}" ${user.zodiac===z?'selected':''}>${z}</option>`).join('')}
</select>
<select id="edit-mbti" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[11px] outline-none text-white">
${mbtis.map(m => `<option value="${m}" ${user.mbti===m?'selected':''}>${m}</option>`).join('')}
</select>
<input id="edit-job" type="text" value="${user.profession}" placeholder="职业" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[11px] outline-none text-white focus:border-purple-500/50">
</div>
</div>
<div class="glass p-6 rounded-[2rem] border-white/10 space-y-6">
<div class="space-y-2">
<label class="text-[10px] text-purple-400 uppercase tracking-wider font-bold">1. 剧本主题</label>
<input id="script-theme" type="text" placeholder="如:巅峰重现、治愈之旅、赛博觉醒..." class="w-full bg-white/5 border border-white/10 rounded-2xl p-4 text-sm outline-none focus:border-purple-400/50 transition-all">
</div>
<!-- NPC / Other Persona Setup -->
<div class="space-y-4">
<div class="flex items-center justify-between">
<label class="text-[10px] text-purple-400 uppercase tracking-wider font-bold">2. 关键配角/新的人设</label>
<button id="add-persona-btn" class="flex items-center gap-1 text-[9px] text-purple-400 border border-purple-400/30 px-2 py-1 rounded-lg hover:bg-purple-500/10 transition-all">
<i data-lucide="plus" class="w-3 h-3"></i> 添加
</button>
</div>
<div class="bg-white/5 border border-white/10 rounded-2xl p-4 space-y-4">
<div class="grid grid-cols-3 gap-2">
<input id="npc-name" type="text" placeholder="姓名" value="${npc.name}" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[10px] outline-none text-white focus:border-purple-500/50">
<select id="npc-role" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[10px] outline-none text-white">
${npcRoles.map(r => `<option value="${r}" ${npc.role===r?'selected':''}>${r}</option>`).join('')}
</select>
<select id="npc-relation" class="bg-white/5 border border-white/10 rounded-xl p-3 text-[10px] outline-none text-white">
${npcRelations.map(r => `<option value="${r}" ${npc.relation===r?'selected':''}>${r}</option>`).join('')}
</select>
</div>
<textarea id="npc-desc" placeholder="自由描述TA的人设特点或关键剧情点..." class="w-full h-16 bg-white/5 border border-white/10 rounded-xl p-3 text-[10px] outline-none resize-none text-white focus:border-purple-500/50">${npc.desc}</textarea>
</div>
<!-- Custom Personas List -->
<div class="flex flex-wrap gap-2">
${customPersonas.map((p, idx) => `
<div class="group relative px-3 py-1.5 bg-purple-500/10 border border-purple-500/30 rounded-full flex items-center gap-2">
<span class="text-[10px] text-purple-200">${p.name} (${p.role})</span>
<button class="delete-persona-btn opacity-40 hover:opacity-100 transition-opacity" data-idx="${idx}">
<i data-lucide="x" class="w-3 h-3"></i>
</button>
</div>
`).join('')}
</div>
</div>
<div class="space-y-2">
<label class="text-[10px] text-purple-400 uppercase tracking-wider font-bold">3. 核心参数</label>
<div class="grid grid-cols-2 gap-3">
<div class="space-y-2">
<p class="text-[9px] text-white/40 ml-1">叙事风格</p>
<div class="flex flex-wrap gap-1.5">
${scriptStyles.map(s => `
<button data-val="${s}" class="style-btn px-3 py-1.5 rounded-xl text-[10px] border transition-all ${config.style === s ? 'bg-purple-400/20 border-purple-400 text-purple-300' : 'bg-white/5 border-white/5 text-white/40'}">${s}</button>
`).join('')}
</div>
</div>
<div class="space-y-2">
<p class="text-[9px] text-white/40 ml-1">故事篇幅</p>
<div class="flex flex-wrap gap-1.5">
${scriptLengths.map(l => `
<button data-val="${l}" class="length-btn px-3 py-1.5 rounded-xl text-[10px] border transition-all ${config.length === l ? 'bg-purple-400/20 border-purple-400 text-purple-300' : 'bg-white/5 border-white/5 text-white/40'}">${l}</button>
`).join('')}
</div>
</div>
</div>
</div>
<button id="gen-script" class="w-full py-4 mt-2 bg-gradient-to-r from-purple-500 to-violet-500 text-white rounded-2xl font-bold text-sm shadow-[0_8px_20px_rgba(168,85,247,0.3)] flex items-center justify-center gap-2 active:scale-95 transition-all">
${isGenerating ? '<div class="starlight-gathering-gold w-4 h-4"></div> 命运编织中...' : '生成平行人生剧本'}
</button>
</div>
${scripts.length === 0 && !isGenerating ? `
<div class="h-40 glass rounded-[2rem] flex flex-col items-center justify-center p-8 text-center space-y-4 opacity-40">
<i data-lucide="film" class="w-8 h-8 text-purple-500"></i>
<p class="text-[10px] text-white/60">尚未生成剧本,定义你的未来篇章</p>
</div>
` : ''}
${isGenerating ? `
<div class="glass p-10 rounded-[2rem] flex flex-col items-center gap-4 animate-pulse border-purple-500/30">
<div class="starlight-gathering-gold"></div>
<p class="text-xs text-purple-300/60 font-serif tracking-widest">正在采集星海中的深紫色碎屑...</p>
</div>
` : scripts.map(s => `
<div class="glass p-6 rounded-[2rem] space-y-4 border-l-4 border-purple-400 relative overflow-hidden group">
<div class="absolute top-0 right-0 p-3 opacity-10 group-hover:opacity-30 transition-opacity">
<i data-lucide="crown" class="w-12 h-12 text-purple-400"></i>
</div>
<h3 class="text-lg text-purple-100 font-medium">${s.title}</h3>
<p class="text-xs text-white/50 line-clamp-3 leading-relaxed">${s.summary}</p>
<div class="flex justify-between items-center pt-2">
<span class="px-3 py-1 bg-purple-400/10 text-purple-400 text-[10px] rounded-full border border-purple-400/20">${s.persona}</span>
<button class="text-xs text-purple-400 font-bold select-script flex items-center gap-1" data-id="${s.id}">
路径映射 <i data-lucide="arrow-right" class="w-3 h-3"></i>
</button>
</div>
</div>
`).join('')}
</div>
`,
path: (activePath) => `
<div class="space-y-6">
<h2 class="text-xl font-light text-purple-100">实现路径</h2>
${!activePath ? `
<div class="glass p-12 rounded-[2rem] text-center space-y-4 border-white/5">
<i data-lucide="milestone" class="mx-auto w-10 h-10 text-purple-500/20"></i>
<p class="text-xs text-purple-300/40 italic">“未来尚未写就,请先在剧本页选择一个目标。”</p>
</div>
` : `
<div class="space-y-6">
<div class="p-6 glass-gold rounded-[2rem] border-purple-400/30 mb-4">
<div class="text-[10px] uppercase tracking-widest text-purple-400 mb-2 font-bold">目标:${activePath.title}</div>
<div class="text-sm font-medium text-purple-100">${activePath.summary.slice(0, 80)}...</div>
</div>
<div class="relative pl-8 space-y-12">
<div class="absolute left-[15px] top-4 bottom-4 w-0.5 bg-gradient-to-b from-purple-400 via-purple-400/20 to-transparent opacity-30"></div>
${activePath.steps.map((step, i) => `
<div class="relative">
<div class="absolute -left-10 w-6 h-6 rounded-full border-2 border-purple-400 bg-[#080B12] flex items-center justify-center z-10 shadow-[0_0_15px_rgba(168,85,247,0.4)]">
<div class="w-2 h-2 rounded-full ${step.done ? 'bg-purple-400 animate-pulse' : 'bg-white/10'}"></div>
</div>
<div class="glass p-5 rounded-[1.5rem] ${step.done ? 'border-purple-400/30' : 'opacity-40'}">
<div class="text-[10px] text-purple-400 font-bold mb-1 uppercase tracking-tighter">节点 ${i+1}</div>
<h4 class="text-sm font-medium mb-1 text-white">${step.task}</h4>
<p class="text-[10px] text-white/50 leading-relaxed">${step.desc}</p>
</div>
</div>
`).join('')}
</div>
</div>
`}
</div>
`,
profile: (user) => `
<div class="space-y-8 pb-10">
<div class="flex flex-col items-center text-center space-y-4">
<div class="w-24 h-24 rounded-full border-2 border-purple-400/30 p-1 relative shadow-[0_0_40px_rgba(168,85,247,0.1)]">
<img src="https://api.dicebear.com/7.x/avataaars/svg?seed=${user.nickname || 'life'}&backgroundColor=A855F7" class="w-full h-full rounded-full bg-purple-900/10">
<div class="absolute bottom-1 right-1 w-6 h-6 bg-purple-500 rounded-full border-2 border-[#080B12] flex items-center justify-center">
<i data-lucide="shield-check" class="w-3 h-3 text-white"></i>
</div>
</div>
<div>
<h2 class="text-2xl font-light text-purple-50">${user.nickname || '未同步系统'}</h2>
<p class="text-[10px] text-purple-400 font-mono tracking-widest uppercase">${user.mbti || 'QUESTER'} · ${user.zodiac || 'STAR'} · ${user.profession || '星民'}</p>
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div class="glass p-5 rounded-[1.5rem] flex flex-col items-center gap-1">
<span class="text-[10px] text-white/30 uppercase tracking-widest">觉醒深度</span>
<span class="text-xl font-light text-purple-200">Lv.4</span>
</div>
<div class="glass p-5 rounded-[1.5rem] flex flex-col items-center gap-1">
<span class="text-[10px] text-white/30 uppercase tracking-widest">星历契合</span>
<span class="text-xl font-light text-purple-200">98%</span>
</div>
</div>
<div class="space-y-3">
<button class="w-full glass p-5 rounded-[1.5rem] flex justify-between items-center text-sm hover:bg-white/5 transition-all">
<div class="flex items-center gap-4 text-white/80"><i data-lucide="user" class="w-5 h-5 text-purple-400"></i><span>个人档案设置</span></div>
<i data-lucide="chevron-right" class="w-4 h-4 opacity-30"></i>
</button>
<button class="w-full glass p-5 rounded-[1.5rem] flex justify-between items-center text-sm hover:bg-white/5 transition-all">
<div class="flex items-center gap-4 text-white/80"><i data-lucide="layers" class="w-5 h-5 text-purple-400"></i><span>多账号切换</span></div>
<i data-lucide="chevron-right" class="w-4 h-4 opacity-30"></i>
</button>
<button class="w-full glass p-5 rounded-[1.5rem] flex justify-between items-center text-sm hover:bg-white/5 transition-all">
<div class="flex items-center gap-4 text-white/80"><i data-lucide="mail" class="w-5 h-5 text-purple-400"></i><span>与开发者对话</span></div>
<i data-lucide="external-link" class="w-4 h-4 opacity-30"></i>
</button>
</div>
<button class="w-full py-4 text-[10px] text-white/20 font-bold tracking-[0.3em] uppercase hover:text-purple-500/50 transition-colors">Terminate Life Harmony</button>
</div>
`
};
+55
View File
@@ -0,0 +1,55 @@
export const state = {
user: {
nickname: "",
gender: "",
zodiac: "",
mbti: "",
profession: "星际探索者", // Added profession
hobbies: [],
childhood: "",
childhoodDate: "",
happyMoment: "",
happyDate: "",
lowPoint: "",
lowDate: "",
aspirations: "",
},
events: [
{
id: 1,
time: "2025-06-12",
title: "开启人生OS",
content: "在繁星之下的那晚,我决定建立人生OS,系统地梳理我的过去与未来。",
aiReply: "这是一个极具觉知力的决定。紫色的星云预示着智慧的觉醒,从这一刻起,你开始重新编写人生的底层代码。",
isNew: false
}
],
scripts: [],
customPersonas: [], // New for user-defined personas
scriptConfig: {
style: "爽文",
length: "中篇"
},
npcConfig: {
name: "",
role: "伙伴",
relation: "信任",
desc: ""
},
currentPath: null,
onboardingStep: 0,
isPlaying: false,
isGenerating: false
};
export const zodiacs = ["白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手座", "摩羯座", "水瓶座", "双鱼座"];
export const mbtis = ["INTJ", "INTP", "ENTJ", "ENTP", "INFJ", "INFP", "ENFJ", "ENFP", "ISTJ", "ISFJ", "ESTJ", "ESFJ", "ISTP", "ISFP", "ESTP", "ESFP"];
export const scriptStyles = ["爽文", "治愈", "热血", "玄幻", "职场", "赛博"];
export const scriptLengths = ["短篇", "中篇", "长篇", "史诗"];
export const npcRoles = ["伙伴", "宿敌", "导师", "挚爱", "下属", "路人"];
export const npcRelations = ["信任", "对立", "暧昧", "敬畏", "背叛", "守护"];
export const hintWords = {
childhood: ["秘密花园", "老旧弄堂", "秋千", "夏蝉", "被保护的", "好奇心"],
happy: ["突破", "共鸣", "清晨阳光", "认可", "旅行终点", "深呼吸"],
low: ["迷茫", "无力感", "雨后街道", "重新出发", "裂痕中的光", "蜕变"]
};
+83
View File
@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Life OS | 人生系统 - 紫禁之巅</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<link rel="stylesheet" href="styles.css">
</head>
<body class="bg-[#0F071A] text-purple-50 overflow-hidden">
<div id="app-container" class="relative mx-auto h-screen max-w-[375px] w-full overflow-hidden flex flex-col shadow-2xl border-x border-purple-900/20">
<!-- Background Elements -->
<div class="background-wrap fixed inset-0 -z-10 pointer-events-none bg-[#0F071A]">
<div class="absolute inset-0 bg-gradient-to-b from-[#1A0B2E] via-[#0F071A] to-[#050208]"></div>
<div id="stars"></div>
<!-- Decorative Aurora/Glow -->
<div class="absolute top-[-10%] left-[-10%] w-[120%] h-[60%] bg-purple-600/10 blur-[120px] rounded-full animate-pulse-slow"></div>
<div class="absolute bottom-[-10%] right-[-10%] w-[100%] h-[50%] bg-violet-600/5 blur-[100px] rounded-full"></div>
</div>
<!-- Onboarding Overlay -->
<div id="onboarding" class="absolute inset-0 z-50 bg-[#0F071A]/95 backdrop-blur-3xl transition-all duration-700">
<div id="wizard-content" class="h-full flex flex-col p-8 pt-16">
<!-- Content injected by JS -->
</div>
</div>
<!-- Main Content Area -->
<main id="main-content" class="hidden flex-1 flex flex-col overflow-y-auto pb-24 scroll-smooth">
<header class="p-6 flex justify-between items-center sticky top-0 bg-[#0F071A]/60 backdrop-blur-xl z-30 border-b border-white/5">
<div class="flex items-center gap-3">
<div class="w-10 h-10 rounded-xl bg-gradient-to-br from-purple-400 to-violet-600 p-[1px] shadow-lg shadow-purple-500/10">
<img src="https://r2-bucket.flowith.net/f/f7a4832f24552794/life_os_app_icon_index_1%401024x1024.jpeg" class="w-full h-full rounded-xl object-cover mix-blend-screen opacity-80" alt="logo">
</div>
<div>
<h1 class="text-sm font-bold tracking-[0.4em] text-purple-100 uppercase">人生OS</h1>
<p class="text-[8px] text-purple-400/60 tracking-widest">LIFE HARMONY v3.1</p>
</div>
</div>
<button id="profile-btn" class="p-2.5 bg-white/5 rounded-full backdrop-blur-md border border-white/10 hover:bg-white/10 transition-colors group">
<i data-lucide="user-cog" class="w-5 h-5 text-purple-200 group-active:rotate-90 transition-transform"></i>
</button>
</header>
<div id="page-render" class="flex-1 px-5 pt-4">
<!-- Dynamic Pages -->
</div>
</main>
<!-- Bottom Navigation -->
<nav id="bottom-nav" class="hidden absolute bottom-0 left-0 right-0 h-20 bg-[#0F071A]/80 backdrop-blur-3xl border-t border-white/5 flex items-center justify-around px-4 z-40">
<button data-tab="record" class="nav-item flex flex-col items-center gap-1 active">
<i data-lucide="book-open" class="w-6 h-6"></i>
<span class="text-[8px] uppercase tracking-widest font-bold">回溯过去</span>
</button>
<button data-tab="script" class="nav-item flex flex-col items-center gap-1">
<i data-lucide="sparkles" class="w-6 h-6"></i>
<span class="text-[8px] uppercase tracking-widest font-bold">创造未来</span>
</button>
<button data-tab="path" class="nav-item flex flex-col items-center gap-1">
<i data-lucide="map" class="w-6 h-6"></i>
<span class="text-[8px] uppercase tracking-widest font-bold">路径实现</span>
</button>
</nav>
<!-- Global Music Player -->
<div id="music-player" class="fixed bottom-24 right-4 z-40">
<button id="music-toggle" class="w-11 h-11 rounded-full bg-white/5 border border-white/10 backdrop-blur-xl flex items-center justify-center shadow-lg transition-all opacity-40">
<div id="music-disc" class="absolute inset-0 rounded-full border border-purple-500/10 pointer-events-none"></div>
<i data-lucide="music-4" class="w-5 h-5 text-purple-300"></i>
</button>
<audio id="bg-music" loop>
<source src="https://v3b.fal.media/files/b/0a8c9a0b/rStj8V-2tCe6bVYpCCcLN_output.mp3" type="audio/mpeg">
</audio>
</div>
</div>
<script type="module" src="app.js"></script>
</body>
</html>
+150
View File
@@ -0,0 +1,150 @@
@import url('https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Inter:wght@300;400;500;600&display=swap');
:root {
--primary: #A855F7; /* Purple 500 */
--primary-light: #C084FC; /* Purple 400 */
--accent: #E879F9; /* Fuchsia 400 */
--bg-dark: #0F071A; /* Dark Deep Purple */
}
body {
font-family: 'Inter', -apple-system, sans-serif;
background-color: var(--bg-dark);
}
h1, h2, .font-serif {
font-family: 'Cinzel', serif;
}
.glass {
background: rgba(168, 85, 247, 0.05);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(168, 85, 247, 0.15);
}
.glass-gold {
background: linear-gradient(135deg, rgba(168, 85, 247, 0.15), rgba(232, 121, 249, 0.1));
border: 1px solid rgba(168, 85, 247, 0.3);
}
.ai-card-glow-gold {
background: rgba(168, 85, 247, 0.08);
border: 1px solid rgba(168, 85, 247, 0.25);
box-shadow: 0 0 20px rgba(168, 85, 247, 0.1);
}
.nav-item {
color: rgba(255, 255, 255, 0.3);
transition: all 0.4s cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-item.active {
color: var(--primary-light);
transform: translateY(-4px);
text-shadow: 0 0 15px rgba(168, 85, 247, 0.8);
}
.animate-spin-slow {
animation: spin 12s linear infinite;
}
.animate-pulse-slow {
animation: pulse 6s ease-in-out infinite;
}
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
@keyframes pulse { 0%, 100% { opacity: 0.3; } 50% { opacity: 0.7; } }
#stars {
position: absolute;
width: 100%;
height: 100%;
}
.star {
position: absolute;
background: white;
border-radius: 50%;
filter: blur(0.5px);
animation: float-star var(--duration) ease-in-out infinite;
opacity: var(--opacity);
}
@keyframes float-star {
0%, 100% { transform: translate(0, 0) scale(1); opacity: var(--opacity); }
50% { transform: translate(var(--x), var(--y)) scale(1.5); opacity: 1; }
}
.hint-container {
@apply p-6 rounded-[2.5rem] bg-purple-900/10 border border-purple-500/10 mt-6;
box-shadow: inset 0 0 30px rgba(168, 85, 247, 0.08);
}
.hint-chip {
@apply relative px-1 py-1 bg-white/5 rounded-full text-[11px] text-purple-200 border border-white/10 transition-all cursor-pointer inline-block whitespace-nowrap overflow-hidden;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
backdrop-filter: blur(8px);
}
.hint-chip span {
@apply relative z-10 px-3 py-1 rounded-full bg-purple-500/20 text-purple-100 font-medium;
display: inline-block;
}
.hint-chip:hover {
@apply border-purple-400/50 scale-105;
box-shadow: 0 8px 15px rgba(168, 85, 247, 0.3);
}
.hint-chip:hover span {
@apply bg-purple-400/40 text-white;
}
.hint-chip:active {
@apply scale-95 opacity-80;
}
.bubble-anim {
animation: bubble-float 5s ease-in-out infinite;
animation-delay: calc(var(--delay, 0) * 0.4s);
}
@keyframes bubble-float {
0%, 100% { transform: translateY(0) rotate(0deg); }
33% { transform: translateY(-4px) rotate(1deg); }
66% { transform: translateY(2px) rotate(-1deg); }
}
.typing-text {
overflow: hidden;
white-space: pre-wrap;
animation: reveal 2s steps(60, end);
}
@keyframes reveal { from { opacity: 0; transform: translateY(5px); } to { opacity: 1; transform: translateY(0); } }
.starlight-gathering-gold {
width: 44px;
height: 44px;
border-radius: 50%;
border: 2px solid var(--primary);
border-top-color: transparent;
animation: spin 0.8s infinite linear;
}
input[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1) sepia(100%) saturate(200%) hue-rotate(240deg);
opacity: 0.8;
}
select {
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%23A855F7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
background-size: 16px;
}
::-webkit-scrollbar { width: 4px; }
::-webkit-scrollbar-thumb { background: rgba(168, 85, 247, 0.3); border-radius: 10px; }