Add distinct auth and distill-ex views per PRD

This commit is contained in:
Eternal AI Builder 2026-06-20 17:01:20 +08:00 committed by chiguyong
parent 75d2271cbe
commit 6ce6b8a464
4 changed files with 354 additions and 15 deletions

BIN
Eternal_AI_PRD_v1.docx Normal file

Binary file not shown.

68
app.js
View File

@ -3,28 +3,27 @@
const landing = document.getElementById('landing');
const creator = document.getElementById('creator');
const auth = document.getElementById('auth');
const distill = document.getElementById('distill');
const form = document.getElementById('character-form');
const resultPanel = document.getElementById('result-panel');
const previewCode = document.querySelector('#preview-code code');
const systemPromptInput = document.getElementById('system-prompt');
const views = { landing, creator, auth, distill };
const steps = Array.from(document.querySelectorAll('.form-step'));
const dots = Array.from(document.querySelectorAll('.stepper__dot'));
let currentStep = 0;
let generatedSoul = '';
let generatedConfig = '';
let activePreview = 'soul';
let activeAuthTab = 'login';
function showView(name) {
if (name === 'landing') {
landing.classList.add('active');
creator.classList.remove('active');
Object.entries(views).forEach(([key, el]) => {
if (el) el.classList.toggle('active', key === name);
});
window.scrollTo({ top: 0, behavior: 'smooth' });
} else {
landing.classList.remove('active');
creator.classList.add('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
}
function updateStep(index) {
@ -256,6 +255,29 @@ ${data.secrets || '无'}
});
}
function switchAuthTab(tab) {
activeAuthTab = tab;
document.querySelectorAll('.auth-tab').forEach((t) => {
t.classList.toggle('active', t.dataset.tab === tab);
});
document.querySelectorAll('.auth-form').forEach((f) => {
f.classList.toggle('active', f.dataset.form === tab);
});
}
function validatePasswordMatch(formEl) {
const pwd = formEl.querySelector('[name="password"]');
const confirm = formEl.querySelector('[name="confirmPassword"]');
if (!pwd || !confirm) return true;
if (pwd.value !== confirm.value) {
confirm.setCustomValidity('两次输入的密码不一致');
confirm.reportValidity();
return false;
}
confirm.setCustomValidity('');
return true;
}
// Event delegation
document.addEventListener('click', (e) => {
const target = e.target.closest('[data-action], [data-tab], [data-download]');
@ -270,6 +292,19 @@ ${data.secrets || '无'}
return;
}
if (action === 'open-auth') {
e.preventDefault();
switchAuthTab('login');
showView('auth');
return;
}
if (action === 'open-distill') {
e.preventDefault();
showView('distill');
return;
}
if (action === 'back') {
e.preventDefault();
showView('landing');
@ -306,9 +341,13 @@ ${data.secrets || '无'}
if (target.dataset.tab) {
e.preventDefault();
if (target.closest('.auth-tabs')) {
switchAuthTab(target.dataset.tab);
} else {
activePreview = target.dataset.tab;
updateTabs();
renderPreview();
}
return;
}
@ -323,6 +362,19 @@ ${data.secrets || '无'}
}
});
// Auth form submissions
document.querySelectorAll('.auth-form').forEach((authForm) => {
authForm.addEventListener('submit', (e) => {
e.preventDefault();
if (!validatePasswordMatch(authForm)) return;
const formData = new FormData(authForm);
const data = Object.fromEntries(formData.entries());
const action = authForm.dataset.form === 'login' ? '登录' : '注册';
alert(`${action}成功:${data.account}`);
showView('landing');
});
});
// Update auto-generated system prompt as user types
form.addEventListener('input', () => {
updateSystemPromptPreview();

View File

@ -7,14 +7,14 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;600;700&family=Inter:wght@300;400;500;600&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="styles.css?v=4" />
<link rel="stylesheet" href="styles.css?v=5" />
</head>
<body>
<main class="app">
<!-- Landing View -->
<section id="landing" class="view view--landing active">
<div class="cards">
<article class="card card--characters" data-action="open-creator">
<article class="card card--characters" data-action="open-auth">
<div class="card__frame" aria-hidden="true"></div>
<div class="card__content">
<h2 class="card__title">我的 [XXX ]</h2>
@ -23,7 +23,7 @@
</div>
</article>
<article class="card card--distill" data-action="open-creator">
<article class="card card--distill" data-action="open-distill">
<div class="card__frame" aria-hidden="true"></div>
<div class="card__content">
<h2 class="card__title">蒸馏前任</h2>
@ -223,6 +223,120 @@
</div>
</div>
</section>
<!-- Auth View (Login / Register) -->
<section id="auth" class="view view--auth">
<header class="creator-header">
<button class="icon-btn" type="button" data-action="back" aria-label="返回"></button>
<div class="creator-brand">
<span class="creator-brand__name">Eternal AI</span>
<span class="creator-brand__step">登录 / 注册</span>
</div>
<div class="stepper" aria-hidden="true"></div>
</header>
<div class="auth-tabs">
<button class="auth-tab active" type="button" data-tab="login">登录</button>
<button class="auth-tab" type="button" data-tab="register">注册</button>
</div>
<form id="login-form" class="auth-form active" autocomplete="off" data-form="login">
<div class="field-group">
<label class="field">
<span class="field__label">手机号 / 用户名</span>
<input class="field__input" name="account" type="text" placeholder="请输入手机号或用户名" required />
</label>
<label class="field">
<span class="field__label">密码</span>
<input class="field__input" name="password" type="password" placeholder="请输入密码" required />
</label>
</div>
<div class="form-actions">
<button class="btn btn--primary btn--block" type="submit">登录</button>
</div>
<p class="auth-hint">登录后可查看角色库、管理已订阅的角色。</p>
</form>
<form id="register-form" class="auth-form" autocomplete="off" data-form="register">
<div class="field-group">
<label class="field">
<span class="field__label">手机号 / 用户名</span>
<input class="field__input" name="account" type="text" placeholder="设置登录账号" required />
</label>
<label class="field">
<span class="field__label">密码</span>
<input class="field__input" name="password" type="password" placeholder="设置密码" required minlength="6" />
</label>
<label class="field">
<span class="field__label">确认密码</span>
<input class="field__input" name="confirmPassword" type="password" placeholder="再次输入密码" required />
</label>
</div>
<div class="form-actions">
<button class="btn btn--primary btn--block" type="submit">注册</button>
</div>
<p class="auth-hint">注册即代表同意《用户协议》和《隐私政策》。</p>
</form>
</section>
<!-- Distill Ex View -->
<section id="distill" class="view view--distill-page">
<header class="creator-header">
<button class="icon-btn" type="button" data-action="back" aria-label="返回"></button>
<div class="creator-brand">
<span class="creator-brand__name">Eternal AI</span>
<span class="creator-brand__step">蒸馏前任</span>
</div>
<div class="stepper" aria-hidden="true"></div>
</header>
<div class="distill-page">
<a href="#contact" class="distill-quick">没耐心?直接加微信定制沟通 →</a>
<div class="distill-section">
<h3 class="distill-section__title">什么是蒸馏前任?</h3>
<p class="distill-section__text">把你们曾经的聊天记录、语音习惯、相处细节交给我们,技术团队会将其蒸馏成一个可对话的 AI 前任。ta 会记得你们的暗号、说话节奏,甚至那些只有你们懂的小情绪。</p>
</div>
<div class="distill-section">
<h3 class="distill-section__title">适合谁?</h3>
<ul class="distill-list">
<li>想好好告别,却还没说完话的人</li>
<li>希望把一段关系以安全方式封存的人</li>
<li>想借 AI 完成自我疗愈、练习表达的人</li>
</ul>
</div>
<div class="distill-section">
<h3 class="distill-section__title">服务流程</h3>
<ol class="distill-steps">
<li><span></span> 下单付款</li>
<li><span></span> 客服指导导出聊天记录</li>
<li><span></span> 上传至指定云盘</li>
<li><span></span> 技术人员处理蒸馏</li>
<li><span></span> 完成后获得专属二维码和头像</li>
</ol>
</div>
<div class="distill-price">
<span class="distill-price__label">标准版</span>
<span class="distill-price__value">¥ 199</span>
<span class="distill-price__unit">/ 次</span>
</div>
<div class="form-actions">
<button class="btn btn--primary btn--block" type="button">立即下单</button>
</div>
<div class="distill-note">
<p>付款后请添加客服微信,我们将一对一指导你完成聊天记录导出与上传。</p>
</div>
<div class="distill-revenue">
<p>创作者可推广蒸馏前任服务获得分润,具体比例请在创作者管理中心配置。</p>
</div>
</div>
</section>
</main>
<script src="app.js"></script>

View File

@ -258,6 +258,10 @@ body {
font-size: 13px;
}
.btn--block {
width: 100%;
}
.btn__arrow {
transition: transform var(--transition);
}
@ -596,6 +600,175 @@ textarea.field__input--tall {
font-family: inherit;
}
/* Auth view */
.view--auth {
padding-top: 40vh;
}
.auth-tabs {
display: flex;
gap: 8px;
margin-bottom: 22px;
padding: 5px;
border-radius: var(--radius);
background: rgba(8, 9, 22, 0.55);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.auth-tab {
flex: 1;
padding: 10px;
border-radius: var(--radius-sm);
border: none;
background: transparent;
color: var(--text-muted);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: color var(--transition), background var(--transition);
}
.auth-tab.active {
background: rgba(255, 255, 255, 0.1);
color: var(--text);
}
.auth-form {
display: none;
flex-direction: column;
flex: 1;
animation: fadeIn 0.35s ease;
}
.auth-form.active {
display: flex;
}
.auth-hint {
margin-top: 14px;
font-size: 12px;
color: var(--text-muted);
text-align: center;
line-height: 1.6;
}
/* Distill ex service page */
.view--distill-page {
padding-top: 40vh;
}
.distill-page {
display: flex;
flex-direction: column;
gap: 20px;
}
.distill-quick {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 12px 16px;
border-radius: var(--radius-sm);
background: linear-gradient(135deg, rgba(255, 160, 205, 0.22), rgba(160, 140, 255, 0.22));
border: 1px solid rgba(255, 160, 205, 0.35);
color: #ffe4f0;
font-size: 14px;
text-decoration: none;
text-align: center;
transition: transform var(--transition), box-shadow var(--transition);
}
.distill-quick:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(255, 130, 180, 0.2);
}
.distill-section {
padding: 18px;
border-radius: var(--radius);
background: rgba(8, 9, 22, 0.55);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.distill-section__title {
font-family: "Noto Serif SC", serif;
font-size: 17px;
font-weight: 600;
margin-bottom: 10px;
color: #fff;
}
.distill-section__text,
.distill-list,
.distill-steps {
font-size: 13px;
line-height: 1.75;
color: rgba(220, 225, 255, 0.85);
}
.distill-list,
.distill-steps {
padding-left: 18px;
margin: 0;
}
.distill-list li {
margin-bottom: 6px;
}
.distill-steps li {
margin-bottom: 8px;
}
.distill-steps span {
color: var(--accent);
font-weight: 600;
margin-right: 4px;
}
.distill-price {
display: flex;
align-items: baseline;
justify-content: center;
gap: 8px;
padding: 18px;
border-radius: var(--radius);
background: rgba(8, 9, 22, 0.55);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.distill-price__label {
font-size: 14px;
color: var(--text-muted);
}
.distill-price__value {
font-size: 32px;
font-weight: 700;
color: #fff;
}
.distill-price__unit {
font-size: 13px;
color: var(--text-muted);
}
.distill-note,
.distill-revenue {
font-size: 12px;
line-height: 1.7;
color: var(--text-muted);
text-align: center;
}
.distill-revenue {
padding: 12px;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.04);
}
/* Utility */
[hidden] {
display: none !important;