小程序初始化
This commit is contained in:
@@ -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();
|
||||
@@ -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>
|
||||
`
|
||||
};
|
||||
@@ -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: ["迷茫", "无力感", "雨后街道", "重新出发", "裂痕中的光", "蜕变"]
|
||||
};
|
||||
@@ -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>
|
||||
@@ -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; }
|
||||
Reference in New Issue
Block a user