Files
life-echo/assets/demo.html
iammm0 71c70275a8 docs: 更新文档与 demo
- 更新 docs/ 与 app-ios/docs/
- 更新 app-ios/demo、assets/demo、app-ios 代码与计划

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-13 10:05:20 +08:00

1805 lines
51 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>岁月时书 - Demo</title>
<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;500;600;700&family=Noto+Sans+SC:wght@300;400;500;600&display=swap" rel="stylesheet">
<style>
:root {
--deep-purple: #200028;
--slate-purple: #8C8EA3;
--medium-purple: #A177A6;
--lavender: #CEB0DA;
--blush: #DBBABA;
--cream: #FAF7F8;
--white: #FFFFFF;
--font-serif: 'Noto Serif SC', serif;
--font-sans: 'Noto Sans SC', sans-serif;
--safe-top: env(safe-area-inset-top, 47px);
--safe-bottom: env(safe-area-inset-bottom, 34px);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
-webkit-tap-highlight-color: transparent;
}
body {
font-family: var(--font-sans);
background: #1a1a2e;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
/* Phone Frame */
.phone-frame {
width: 393px;
height: 852px;
background: var(--cream);
border-radius: 55px;
overflow: hidden;
position: relative;
box-shadow:
0 0 0 11px #1a1a1a,
0 0 0 13px #3a3a3a,
0 25px 50px -12px rgba(0, 0, 0, 0.5);
}
/* Dynamic Island */
.dynamic-island {
position: absolute;
top: 11px;
left: 50%;
transform: translateX(-50%);
width: 126px;
height: 37px;
background: #000;
border-radius: 24px;
z-index: 1000;
}
/* Screen Content */
.screen {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
}
/* Pages Container */
.pages-container {
width: 300%;
height: calc(100% - 90px);
display: flex;
transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94);
padding-top: 60px;
}
.page {
width: 33.333%;
height: 100%;
overflow-y: auto;
overflow-x: hidden;
-webkit-overflow-scrolling: touch;
}
.page::-webkit-scrollbar {
display: none;
}
/* Navigation Bar */
.nav-bar {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 90px;
background: var(--white);
display: flex;
justify-content: space-around;
align-items: flex-start;
padding-top: 12px;
border-top: 1px solid rgba(32, 0, 40, 0.08);
z-index: 100;
}
.nav-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
cursor: pointer;
opacity: 0.5;
transition: all 0.3s ease;
padding: 4px 20px;
}
.nav-item.active {
opacity: 1;
}
.nav-item svg {
width: 26px;
height: 26px;
stroke: var(--deep-purple);
fill: none;
stroke-width: 1.8;
transition: all 0.3s ease;
}
.nav-item.active svg {
stroke: var(--medium-purple);
}
.nav-item span {
font-size: 11px;
color: var(--deep-purple);
font-weight: 500;
letter-spacing: 0.3px;
}
.nav-item.active span {
color: var(--medium-purple);
}
/* ========== Page 1: Create Memoir ========== */
.page-create {
background: var(--cream);
padding: 0 24px 24px;
}
.header-stats {
text-align: center;
padding: 20px 0 30px;
}
.book-title-wrapper {
display: flex;
justify-content: center;
margin-bottom: 16px;
}
.book-title {
font-family: var(--font-serif);
font-size: 28px;
font-weight: 600;
color: var(--deep-purple);
letter-spacing: 2px;
cursor: pointer;
position: relative;
display: inline-block;
}
.book-title .edit-icon {
position: absolute;
right: -26px;
top: 50%;
transform: translateY(-50%);
width: 18px;
height: 18px;
stroke: var(--slate-purple);
fill: none;
opacity: 0;
transition: opacity 0.2s ease;
}
.book-title:hover .edit-icon {
opacity: 1;
}
/* Edit Title Modal */
.edit-modal {
position: fixed;
inset: 0;
background: rgba(32, 0, 40, 0.6);
display: none;
align-items: center;
justify-content: center;
z-index: 300;
padding: 24px;
}
.edit-modal.visible {
display: flex;
}
.edit-modal-content {
background: var(--white);
border-radius: 20px;
padding: 24px;
width: 100%;
max-width: 320px;
animation: modalIn 0.3s ease;
}
@keyframes modalIn {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
.edit-modal-title {
font-size: 16px;
font-weight: 600;
color: var(--deep-purple);
margin-bottom: 16px;
text-align: center;
}
.edit-modal-input {
width: 100%;
padding: 14px 16px;
border: 2px solid var(--lavender);
border-radius: 12px;
font-size: 18px;
font-family: var(--font-serif);
color: var(--deep-purple);
text-align: center;
outline: none;
transition: border-color 0.2s ease;
}
.edit-modal-input:focus {
border-color: var(--medium-purple);
}
.edit-modal-hint {
font-size: 12px;
color: var(--slate-purple);
text-align: center;
margin-top: 8px;
}
.edit-modal-btns {
display: flex;
gap: 12px;
margin-top: 20px;
}
.edit-modal-btn {
flex: 1;
padding: 12px;
border-radius: 12px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
border: none;
}
.edit-modal-btn.cancel {
background: var(--cream);
color: var(--slate-purple);
}
.edit-modal-btn.confirm {
background: var(--medium-purple);
color: var(--white);
}
.edit-modal-btn:active {
transform: scale(0.96);
}
.stats-row {
display: flex;
justify-content: center;
gap: 32px;
}
.stat-item {
text-align: center;
}
.stat-value {
font-size: 22px;
font-weight: 600;
color: var(--deep-purple);
}
.stat-label {
font-size: 12px;
color: var(--slate-purple);
margin-top: 2px;
}
/* Main Action Button */
.main-action {
display: flex;
flex-direction: column;
align-items: center;
margin: 20px 0 40px;
}
.record-btn {
width: 140px;
height: 140px;
border-radius: 50%;
border: none;
background: var(--medium-purple);
color: var(--white);
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
box-shadow: 0 8px 24px rgba(32, 0, 40, 0.2);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.record-btn:active {
transform: scale(0.95);
}
.record-btn.recording {
background: #e05555;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 10px 40px rgba(224, 85, 85, 0.3), 0 0 0 8px rgba(224, 85, 85, 0.2); }
50% { box-shadow: 0 10px 40px rgba(224, 85, 85, 0.5), 0 0 0 16px rgba(224, 85, 85, 0.1); }
}
.record-btn svg {
width: 36px;
height: 36px;
fill: var(--white);
position: relative;
z-index: 1;
}
.record-btn span {
font-size: 15px;
font-weight: 500;
letter-spacing: 1px;
position: relative;
z-index: 1;
}
.record-hint {
margin-top: 16px;
font-size: 13px;
color: var(--slate-purple);
}
/* Timer Display */
.timer-display {
display: none;
margin-top: 16px;
font-size: 32px;
font-weight: 300;
color: var(--deep-purple);
font-variant-numeric: tabular-nums;
}
.timer-display.visible {
display: block;
}
/* Progress Section */
.progress-section {
background: var(--white);
border-radius: 20px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 12px rgba(32, 0, 40, 0.06);
}
.progress-title {
font-size: 13px;
color: var(--slate-purple);
margin-bottom: 12px;
font-weight: 500;
}
.progress-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.progress-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.progress-item-label {
font-size: 14px;
color: var(--deep-purple);
}
.progress-item-value {
font-size: 14px;
font-weight: 600;
color: var(--medium-purple);
}
/* Session Summary */
.session-summary {
background: var(--white);
border-radius: 20px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 12px rgba(32, 0, 40, 0.06);
display: none;
}
.session-summary.visible {
display: block;
animation: slideUp 0.4s ease;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.summary-header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 16px;
}
.summary-header svg {
width: 20px;
height: 20px;
stroke: var(--medium-purple);
fill: none;
}
.summary-header span {
font-size: 14px;
font-weight: 600;
color: var(--deep-purple);
}
.summary-content {
display: flex;
flex-direction: column;
gap: 10px;
}
.summary-row {
display: flex;
justify-content: space-between;
font-size: 14px;
}
.summary-row .label {
color: var(--slate-purple);
}
.summary-row .value {
color: var(--deep-purple);
font-weight: 500;
}
.next-topic {
margin-top: 12px;
padding: 12px;
background: var(--lavender);
border-radius: 12px;
}
.next-topic-label {
font-size: 11px;
color: var(--slate-purple);
margin-bottom: 4px;
}
.next-topic-value {
font-size: 14px;
color: var(--deep-purple);
font-weight: 500;
}
/* Go to Book Button */
.goto-book {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 16px;
background: var(--white);
border-radius: 16px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 12px rgba(32, 0, 40, 0.06);
}
.goto-book:active {
transform: scale(0.98);
background: var(--cream);
}
.goto-book svg {
width: 22px;
height: 22px;
stroke: var(--medium-purple);
fill: none;
}
.goto-book span {
font-size: 15px;
color: var(--deep-purple);
font-weight: 500;
}
/* ========== Page 2: My Memoir ========== */
.page-memoir {
background: var(--cream);
padding: 0;
}
/* Book View Tabs */
.memoir-tabs {
display: flex;
padding: 0 20px;
gap: 20px;
border-bottom: 1px solid rgba(32, 0, 40, 0.08);
background: var(--white);
}
.memoir-tab {
padding: 16px 0;
font-size: 15px;
color: var(--slate-purple);
cursor: pointer;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
}
.memoir-tab.active {
color: var(--deep-purple);
border-bottom-color: var(--medium-purple);
}
/* Table of Contents */
.toc-view {
padding: 24px 20px;
}
.toc-header {
text-align: center;
margin-bottom: 32px;
}
.toc-book-title {
font-family: var(--font-serif);
font-size: 32px;
font-weight: 600;
color: var(--deep-purple);
margin-bottom: 8px;
}
.toc-subtitle {
font-size: 14px;
color: var(--slate-purple);
}
.toc-update {
margin-top: 8px;
font-size: 12px;
color: var(--medium-purple);
}
.toc-list {
display: flex;
flex-direction: column;
gap: 12px;
}
.toc-item {
display: flex;
align-items: center;
padding: 16px;
background: var(--white);
border-radius: 14px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(32, 0, 40, 0.04);
}
.toc-item:active {
transform: scale(0.98);
background: var(--lavender);
}
.toc-number {
width: 32px;
height: 32px;
border-radius: 10px;
background: var(--lavender);
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: 600;
color: var(--deep-purple);
margin-right: 14px;
}
.toc-info {
flex: 1;
}
.toc-chapter-title {
font-size: 15px;
color: var(--deep-purple);
font-weight: 500;
margin-bottom: 2px;
}
.toc-status {
font-size: 12px;
color: var(--slate-purple);
}
.toc-status.complete {
color: var(--medium-purple);
}
.toc-arrow {
width: 20px;
height: 20px;
stroke: var(--slate-purple);
fill: none;
}
/* Chapter Reading View */
.reading-view {
display: none;
padding: 24px 20px;
}
.reading-view.active {
display: block;
}
.reading-back {
display: flex;
align-items: center;
gap: 6px;
margin-bottom: 24px;
cursor: pointer;
color: var(--slate-purple);
font-size: 14px;
}
.reading-back svg {
width: 18px;
height: 18px;
stroke: currentColor;
fill: none;
}
.chapter-header {
margin-bottom: 24px;
}
.chapter-number {
font-size: 13px;
color: var(--medium-purple);
font-weight: 500;
margin-bottom: 4px;
}
.chapter-title {
font-family: var(--font-serif);
font-size: 26px;
font-weight: 600;
color: var(--deep-purple);
}
.chapter-content {
font-family: var(--font-serif);
font-size: 16px;
line-height: 1.9;
color: var(--deep-purple);
}
.chapter-content p {
text-indent: 2em;
margin-bottom: 1.2em;
}
.chapter-image {
margin: 24px -20px;
padding: 0 20px;
}
.chapter-image img {
width: 100%;
height: 180px;
object-fit: cover;
border-radius: 12px;
background: var(--lavender);
}
.chapter-image-caption {
text-align: center;
font-size: 12px;
color: var(--slate-purple);
margin-top: 8px;
font-style: italic;
}
.chapter-quote {
margin: 24px 0;
padding: 16px 20px;
background: var(--lavender);
border-left: 3px solid var(--medium-purple);
border-radius: 0 12px 12px 0;
font-size: 15px;
font-style: italic;
color: var(--deep-purple);
}
/* Export Actions */
.memoir-actions {
position: fixed;
bottom: 100px;
left: 50%;
transform: translateX(-50%);
display: flex;
gap: 12px;
padding: 8px;
background: var(--white);
border-radius: 16px;
box-shadow: 0 4px 20px rgba(32, 0, 40, 0.15);
z-index: 50;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
}
.memoir-actions.visible {
opacity: 1;
pointer-events: auto;
}
.action-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 16px;
border-radius: 10px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.action-btn svg {
width: 18px;
height: 18px;
}
.action-btn.primary {
background: var(--medium-purple);
color: var(--white);
}
.action-btn.primary svg {
stroke: var(--white);
fill: none;
}
.action-btn.secondary {
background: var(--cream);
color: var(--deep-purple);
}
.action-btn.secondary svg {
stroke: var(--deep-purple);
fill: none;
}
/* ========== Page 3: Profile ========== */
.page-profile {
background: var(--cream);
padding: 0 20px 24px;
}
.profile-header {
padding: 20px 0 24px;
text-align: center;
}
.profile-avatar {
width: 72px;
height: 72px;
border-radius: 50%;
background: var(--lavender);
margin: 0 auto 12px;
display: flex;
align-items: center;
justify-content: center;
}
.profile-avatar svg {
width: 36px;
height: 36px;
stroke: var(--deep-purple);
fill: none;
}
.profile-name {
font-size: 18px;
font-weight: 600;
color: var(--deep-purple);
margin-bottom: 4px;
}
.profile-plan {
font-size: 13px;
color: var(--medium-purple);
display: flex;
align-items: center;
justify-content: center;
gap: 4px;
}
.profile-plan-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--medium-purple);
}
/* Profile Sections */
.profile-section {
margin-bottom: 20px;
}
.section-title {
font-size: 12px;
color: var(--slate-purple);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 0 4px;
margin-bottom: 10px;
}
.section-card {
background: var(--white);
border-radius: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(32, 0, 40, 0.04);
}
.section-item {
display: flex;
align-items: center;
padding: 16px;
cursor: pointer;
transition: background 0.2s ease;
border-bottom: 1px solid rgba(32, 0, 40, 0.06);
}
.section-item:last-child {
border-bottom: none;
}
.section-item:active {
background: var(--cream);
}
.section-item-icon {
width: 36px;
height: 36px;
border-radius: 10px;
background: var(--lavender);
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.section-item-icon svg {
width: 20px;
height: 20px;
stroke: var(--deep-purple);
fill: none;
}
.section-item-content {
flex: 1;
}
.section-item-label {
font-size: 15px;
color: var(--deep-purple);
}
.section-item-desc {
font-size: 12px;
color: var(--slate-purple);
margin-top: 2px;
}
.section-item-arrow {
width: 18px;
height: 18px;
stroke: var(--slate-purple);
fill: none;
}
.section-item-toggle {
width: 48px;
height: 28px;
border-radius: 14px;
background: var(--slate-purple);
position: relative;
transition: background 0.3s ease;
cursor: pointer;
}
.section-item-toggle.active {
background: var(--medium-purple);
}
.section-item-toggle::after {
content: '';
position: absolute;
top: 3px;
left: 3px;
width: 22px;
height: 22px;
border-radius: 50%;
background: var(--white);
transition: transform 0.3s ease;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.section-item-toggle.active::after {
transform: translateX(20px);
}
/* Toast */
.toast {
position: fixed;
bottom: 120px;
left: 50%;
transform: translateX(-50%) translateY(100px);
background: var(--deep-purple);
color: var(--white);
padding: 12px 24px;
border-radius: 12px;
font-size: 14px;
opacity: 0;
transition: all 0.3s ease;
z-index: 200;
}
.toast.visible {
transform: translateX(-50%) translateY(0);
opacity: 1;
}
/* Real-time Transcript */
.transcript-overlay {
position: absolute;
inset: 0;
background: rgba(32, 0, 40, 0.95);
z-index: 60;
display: none;
flex-direction: column;
padding: 80px 24px 120px;
}
.transcript-overlay.visible {
display: flex;
}
.transcript-close {
position: absolute;
top: 70px;
right: 20px;
width: 36px;
height: 36px;
border-radius: 50%;
background: rgba(255,255,255,0.1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.transcript-close svg {
width: 20px;
height: 20px;
stroke: var(--white);
fill: none;
}
.transcript-header {
text-align: center;
margin-bottom: 30px;
}
.transcript-timer {
font-size: 48px;
font-weight: 300;
color: var(--white);
font-variant-numeric: tabular-nums;
}
.transcript-status {
font-size: 14px;
color: var(--lavender);
margin-top: 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.recording-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #e05555;
animation: blink 1s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
.transcript-content {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.transcript-content::-webkit-scrollbar {
display: none;
}
.transcript-message {
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.transcript-message.user {
text-align: right;
}
.transcript-message.ai {
text-align: left;
}
.transcript-bubble {
display: inline-block;
max-width: 85%;
padding: 12px 16px;
border-radius: 16px;
font-size: 15px;
line-height: 1.5;
}
.transcript-message.user .transcript-bubble {
background: var(--medium-purple);
color: var(--white);
border-bottom-right-radius: 4px;
}
.transcript-message.ai .transcript-bubble {
background: rgba(255,255,255,0.1);
color: var(--white);
border-bottom-left-radius: 4px;
}
.transcript-end-btn {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
padding: 16px 48px;
background: #e05555;
color: var(--white);
border: none;
border-radius: 30px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
}
.transcript-end-btn svg {
width: 20px;
height: 20px;
fill: var(--white);
}
/* Waveform Animation */
.waveform {
display: flex;
align-items: center;
justify-content: center;
gap: 3px;
height: 30px;
margin-top: 16px;
}
.waveform-bar {
width: 4px;
background: var(--lavender);
border-radius: 2px;
animation: wave 0.8s ease-in-out infinite;
}
.waveform-bar:nth-child(1) { animation-delay: 0s; height: 10px; }
.waveform-bar:nth-child(2) { animation-delay: 0.1s; height: 20px; }
.waveform-bar:nth-child(3) { animation-delay: 0.2s; height: 15px; }
.waveform-bar:nth-child(4) { animation-delay: 0.3s; height: 25px; }
.waveform-bar:nth-child(5) { animation-delay: 0.4s; height: 18px; }
.waveform-bar:nth-child(6) { animation-delay: 0.5s; height: 22px; }
.waveform-bar:nth-child(7) { animation-delay: 0.6s; height: 12px; }
@keyframes wave {
0%, 100% { transform: scaleY(1); }
50% { transform: scaleY(1.8); }
}
</style>
</head>
<body>
<div class="phone-frame">
<div class="dynamic-island"></div>
<div class="screen">
<div class="pages-container" id="pagesContainer">
<!-- Page 1: Create Memoir -->
<div class="page page-create">
<div class="header-stats">
<div class="book-title-wrapper">
<h1 class="book-title" id="bookTitle">
<span id="bookTitleText">《这一生》</span>
<svg class="edit-icon" viewBox="0 0 24 24"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" stroke-width="2"/></svg>
</h1>
</div>
<div class="stats-row">
<div class="stat-item">
<div class="stat-value" id="todayMinutes">0</div>
<div class="stat-label">今日已聊(分钟)</div>
</div>
<div class="stat-item">
<div class="stat-value" id="totalHours">2:18</div>
<div class="stat-label">累计时长</div>
</div>
</div>
</div>
<div class="main-action">
<button class="record-btn" id="recordBtn">
<svg viewBox="0 0 24 24" id="micIcon">
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
<path d="M19 10v2a7 7 0 0 1-14 0v-2M12 19v4M8 23h8" stroke="currentColor" stroke-width="2" fill="none"/>
</svg>
<svg viewBox="0 0 24 24" id="stopIcon" style="display:none;">
<rect x="6" y="6" width="12" height="12" rx="2"/>
</svg>
<span id="recordBtnText">开始聊天</span>
</button>
<p class="record-hint" id="recordHint">你可以一直说,我会认真听</p>
<div class="timer-display" id="timerDisplay">00:00</div>
</div>
<div class="progress-section">
<div class="progress-title">回忆录进度</div>
<div class="progress-items">
<div class="progress-item">
<span class="progress-item-label">预估页数</span>
<span class="progress-item-value">约 18 页</span>
</div>
<div class="progress-item">
<span class="progress-item-label">已整理章节</span>
<span class="progress-item-value">3 章</span>
</div>
<div class="progress-item">
<span class="progress-item-label">最近更新</span>
<span class="progress-item-value">刚刚</span>
</div>
</div>
</div>
<div class="session-summary" id="sessionSummary">
<div class="summary-header">
<svg viewBox="0 0 24 24"><path d="M9 11l3 3L22 4M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11" stroke-width="2"/></svg>
<span>本次聊天已保存</span>
</div>
<div class="summary-content">
<div class="summary-row">
<span class="label">聊天时长</span>
<span class="value" id="summaryDuration">8分42秒</span>
</div>
<div class="summary-row">
<span class="label">新增内容</span>
<span class="value">童年与家庭2段</span>
</div>
</div>
<div class="next-topic">
<div class="next-topic-label">推荐下次聊</div>
<div class="next-topic-value">上学的日子</div>
</div>
</div>
<div class="goto-book" id="gotoBook">
<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 016.5 17H20M4 19.5A2.5 2.5 0 004 14.5V5a2 2 0 012-2h14v16.5" stroke-width="2"/></svg>
<span>阅读我的回忆录</span>
</div>
</div>
<!-- Page 2: My Memoir -->
<div class="page page-memoir">
<div class="memoir-tabs">
<div class="memoir-tab active" data-tab="toc">目录</div>
<div class="memoir-tab" data-tab="reading">正在阅读</div>
</div>
<div class="toc-view" id="tocView">
<div class="toc-header">
<h1 class="toc-book-title">这一生</h1>
<p class="toc-subtitle">我的回忆录</p>
<p class="toc-update">更新于 2 分钟前</p>
</div>
<div class="toc-list">
<div class="toc-item" data-chapter="1">
<div class="toc-number">01</div>
<div class="toc-info">
<div class="toc-chapter-title">童年与家庭</div>
<div class="toc-status complete">已整理 · 约3页</div>
</div>
<svg class="toc-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="toc-item" data-chapter="2">
<div class="toc-number">02</div>
<div class="toc-info">
<div class="toc-chapter-title">上学的日子</div>
<div class="toc-status">部分整理 · 约2页</div>
</div>
<svg class="toc-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="toc-item" data-chapter="3">
<div class="toc-number">03</div>
<div class="toc-info">
<div class="toc-chapter-title">工作与事业</div>
<div class="toc-status">待补充</div>
</div>
<svg class="toc-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="toc-item" data-chapter="4">
<div class="toc-number">04</div>
<div class="toc-info">
<div class="toc-chapter-title">爱情与婚姻</div>
<div class="toc-status">待补充</div>
</div>
<svg class="toc-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
</div>
</div>
<div class="reading-view" id="readingView">
<div class="reading-back" id="backToToc">
<svg viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" stroke-width="2"/></svg>
<span>返回目录</span>
</div>
<div class="chapter-header">
<div class="chapter-number">第一章</div>
<h2 class="chapter-title">童年与家庭</h2>
</div>
<div class="chapter-content">
<p>我出生在一个普通的农村家庭那是1955年的深秋。母亲常说我出生的那天院子里的老槐树落了一地金黄的叶子风一吹像是漫天飞舞的蝴蝶。</p>
<div class="chapter-quote">
"日子虽然清苦,但那时候的快乐是最纯粹的。"
</div>
<p>父亲是村里的木匠,手艺在十里八乡都有名气。他的手上满是老茧,却能用那双手雕出最精美的花纹。每到农闲时节,总有人家请他去做嫁妆,一张八仙桌,一对太师椅,都是他的拿手活计。</p>
<div class="chapter-image">
<div style="width:100%;height:180px;background:var(--lavender);border-radius:12px;display:flex;align-items:center;justify-content:center;color:var(--deep-purple);font-size:14px;">老家门口的那条路</div>
<div class="chapter-image-caption">老家门口的那条路AI 生成)</div>
</div>
<p>母亲是个勤快人,总是天不亮就起床,喂鸡、扫院子、生火做饭,一刻也闲不住。她不识字,但会唱很多老歌,夜里纳鞋底的时候,常常哼着小调,那旋律到现在我还记得。</p>
<p>我们家有三个孩子,我排行老二。大姐比我大三岁,小弟比我小五岁。那时候日子虽然清苦,但一家人在一起,总有说不完的话,笑不完的乐子。</p>
</div>
</div>
</div>
<!-- Page 3: Profile -->
<div class="page page-profile">
<div class="profile-header">
<div class="profile-avatar">
<svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z" stroke-width="2"/></svg>
</div>
<div class="profile-name">李明华</div>
<div class="profile-plan">
<span class="profile-plan-dot"></span>
<span>免费体验版</span>
</div>
</div>
<div class="profile-section">
<div class="section-title">套餐与付费</div>
<div class="section-card">
<div class="section-item" id="upgradePlan">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">升级套餐</div>
<div class="section-item-desc">解锁完整导出与更多功能</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">我的订单</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
</div>
</div>
<div class="profile-section">
<div class="section-title">数据与隐私</div>
<div class="section-card">
<div class="section-item" id="exportData">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M17 8l-5-5-5 5M12 3v12" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">导出所有数据</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
</div>
</div>
<div class="profile-section">
<div class="section-title">设置</div>
<div class="section-card">
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M12 6v6l4 2" stroke-width="2"/><circle cx="12" cy="12" r="10" stroke-width="2" fill="none"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">语速</div>
<div class="section-item-desc">标准</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M4 7V4h16v3M9 20h6M12 4v16" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">大字模式</div>
</div>
<div class="section-item-toggle" id="largeFontToggle"></div>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">夜间模式</div>
</div>
<div class="section-item-toggle" id="darkModeToggle"></div>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9M13.73 21a2 2 0 01-3.46 0" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">每日提醒</div>
<div class="section-item-desc">每天提醒聊5分钟</div>
</div>
<div class="section-item-toggle active" id="reminderToggle"></div>
</div>
</div>
</div>
<div class="profile-section">
<div class="section-title">帮助</div>
<div class="section-card">
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2" fill="none"/><path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3M12 17h.01" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">常见问题</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">反馈与客服</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
<div class="section-item">
<div class="section-item-icon">
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2" fill="none"/><path d="M12 16v-4M12 8h.01" stroke-width="2"/></svg>
</div>
<div class="section-item-content">
<div class="section-item-label">关于我们</div>
</div>
<svg class="section-item-arrow" viewBox="0 0 24 24"><path d="M9 18l6-6-6-6" stroke-width="2"/></svg>
</div>
</div>
</div>
</div>
</div>
<!-- Navigation Bar -->
<nav class="nav-bar">
<div class="nav-item active" data-page="0">
<svg viewBox="0 0 24 24"><path d="M12 1a3 3 0 00-3 3v8a3 3 0 006 0V4a3 3 0 00-3-3z" fill="currentColor"/><path d="M19 10v2a7 7 0 01-14 0v-2M12 19v4M8 23h8"/></svg>
<span>聊天</span>
</div>
<div class="nav-item" data-page="1">
<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 016.5 17H20M4 19.5A2.5 2.5 0 004 14.5V5a2 2 0 012-2h14v16.5"/></svg>
<span>回忆录</span>
</div>
<div class="nav-item" data-page="2">
<svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2M12 11a4 4 0 100-8 4 4 0 000 8z"/></svg>
<span>我的</span>
</div>
</nav>
<!-- Memoir Actions (floating) -->
<div class="memoir-actions" id="memoirActions">
<div class="action-btn primary" id="exportPdf">
<svg viewBox="0 0 24 24"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" stroke-width="2"/></svg>
<span>导出 PDF</span>
</div>
<div class="action-btn secondary" id="shareLink">
<svg viewBox="0 0 24 24"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><path d="M8.59 13.51l6.83 3.98M15.41 6.51l-6.82 3.98" stroke-width="2"/></svg>
<span>分享</span>
</div>
</div>
<!-- Transcript Overlay -->
<div class="transcript-overlay" id="transcriptOverlay">
<div class="transcript-close" id="transcriptClose">
<svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12" stroke-width="2"/></svg>
</div>
<div class="transcript-header">
<div class="transcript-timer" id="transcriptTimer">00:00</div>
<div class="transcript-status">
<span class="recording-dot"></span>
<span>正在记录...</span>
</div>
<div class="waveform">
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
<div class="waveform-bar"></div>
</div>
</div>
<div class="transcript-content" id="transcriptContent">
<!-- Messages will be added here -->
</div>
<button class="transcript-end-btn" id="endRecording">
<svg viewBox="0 0 24 24"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>
<span>结束聊天</span>
</button>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
<!-- Edit Title Modal -->
<div class="edit-modal" id="editModal">
<div class="edit-modal-content">
<div class="edit-modal-title">编辑回忆录名称</div>
<input type="text" class="edit-modal-input" id="editTitleInput" value="这一生" maxlength="20">
<div class="edit-modal-hint">建议 2-10 个字</div>
<div class="edit-modal-btns">
<button class="edit-modal-btn cancel" id="editCancel">取消</button>
<button class="edit-modal-btn confirm" id="editConfirm">确定</button>
</div>
</div>
</div>
</div>
</div>
<script>
// State
let currentPage = 0;
let isRecording = false;
let recordingSeconds = 0;
let recordingInterval = null;
let sessionDuration = 0;
// Elements
const pagesContainer = document.getElementById('pagesContainer');
const navItems = document.querySelectorAll('.nav-item');
const recordBtn = document.getElementById('recordBtn');
const recordBtnText = document.getElementById('recordBtnText');
const micIcon = document.getElementById('micIcon');
const stopIcon = document.getElementById('stopIcon');
const recordHint = document.getElementById('recordHint');
const timerDisplay = document.getElementById('timerDisplay');
const sessionSummary = document.getElementById('sessionSummary');
const transcriptOverlay = document.getElementById('transcriptOverlay');
const transcriptTimer = document.getElementById('transcriptTimer');
const transcriptContent = document.getElementById('transcriptContent');
const transcriptClose = document.getElementById('transcriptClose');
const endRecordingBtn = document.getElementById('endRecording');
const gotoBook = document.getElementById('gotoBook');
const memoirActions = document.getElementById('memoirActions');
const tocView = document.getElementById('tocView');
const readingView = document.getElementById('readingView');
const backToToc = document.getElementById('backToToc');
const memoirTabs = document.querySelectorAll('.memoir-tab');
const tocItems = document.querySelectorAll('.toc-item');
const toast = document.getElementById('toast');
const toggles = document.querySelectorAll('.section-item-toggle');
const bookTitle = document.getElementById('bookTitle');
const bookTitleText = document.getElementById('bookTitleText');
const tocBookTitle = document.querySelector('.toc-book-title');
const editModal = document.getElementById('editModal');
const editTitleInput = document.getElementById('editTitleInput');
const editCancel = document.getElementById('editCancel');
const editConfirm = document.getElementById('editConfirm');
// Demo conversation
const demoConversation = [
{ type: 'ai', text: '您好!今天想聊些什么呢?可以从童年开始,或者聊聊最近想起的往事。' },
{ type: 'user', text: '我想聊聊我小时候的事,那时候我们住在农村...' },
{ type: 'ai', text: '好的,农村的童年一定有很多有趣的回忆。您还记得那时候住的房子是什么样的吗?' },
{ type: 'user', text: '是一个土坯房,院子里有一棵很大的槐树。每到夏天,树上开满了白色的花,香得很。' },
{ type: 'ai', text: '槐花的香味,确实令人难忘。您有没有和小伙伴们一起在槐树下玩耍的经历呢?' }
];
// Navigate to page
function navigateTo(pageIndex) {
currentPage = pageIndex;
pagesContainer.style.transform = `translateX(-${pageIndex * 33.333}%)`;
navItems.forEach((item, index) => {
item.classList.toggle('active', index === pageIndex);
});
// Show/hide memoir actions
memoirActions.classList.toggle('visible', pageIndex === 1);
}
// Format time
function formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}
// Start recording
function startRecording() {
isRecording = true;
recordingSeconds = 0;
recordBtn.classList.add('recording');
recordBtnText.textContent = '录音中...';
micIcon.style.display = 'none';
stopIcon.style.display = 'block';
recordHint.style.display = 'none';
timerDisplay.classList.add('visible');
// Show transcript overlay and hide nav bar
transcriptOverlay.classList.add('visible');
document.querySelector('.nav-bar').style.display = 'none';
transcriptContent.innerHTML = '';
// Start timer
recordingInterval = setInterval(() => {
recordingSeconds++;
const timeStr = formatTime(recordingSeconds);
timerDisplay.textContent = timeStr;
transcriptTimer.textContent = timeStr;
// Add demo messages
if (recordingSeconds <= demoConversation.length * 3) {
const msgIndex = Math.floor((recordingSeconds - 1) / 3);
if (recordingSeconds % 3 === 1 && msgIndex < demoConversation.length) {
addTranscriptMessage(demoConversation[msgIndex]);
}
}
}, 1000);
}
// Stop recording
function stopRecording() {
isRecording = false;
sessionDuration = recordingSeconds;
clearInterval(recordingInterval);
recordBtn.classList.remove('recording');
recordBtnText.textContent = '开始聊天';
micIcon.style.display = 'block';
stopIcon.style.display = 'none';
recordHint.style.display = 'block';
timerDisplay.classList.remove('visible');
// Hide transcript overlay and show nav bar
transcriptOverlay.classList.remove('visible');
document.querySelector('.nav-bar').style.display = 'flex';
// Show session summary
document.getElementById('summaryDuration').textContent = formatTime(sessionDuration).replace(':', '分') + '秒';
sessionSummary.classList.add('visible');
// Update today's minutes
const todayMins = parseInt(document.getElementById('todayMinutes').textContent) + Math.ceil(sessionDuration / 60);
document.getElementById('todayMinutes').textContent = todayMins;
showToast('聊天已保存,正在整理...');
}
// Add transcript message
function addTranscriptMessage(message) {
const msgEl = document.createElement('div');
msgEl.className = `transcript-message ${message.type}`;
msgEl.innerHTML = `<div class="transcript-bubble">${message.text}</div>`;
transcriptContent.appendChild(msgEl);
transcriptContent.scrollTop = transcriptContent.scrollHeight;
}
// Show reading view
function showReadingView() {
tocView.style.display = 'none';
readingView.classList.add('active');
memoirTabs[0].classList.remove('active');
memoirTabs[1].classList.add('active');
}
// Show TOC view
function showTocView() {
tocView.style.display = 'block';
readingView.classList.remove('active');
memoirTabs[0].classList.add('active');
memoirTabs[1].classList.remove('active');
}
// Show toast
function showToast(message) {
toast.textContent = message;
toast.classList.add('visible');
setTimeout(() => {
toast.classList.remove('visible');
}, 2000);
}
// Event listeners
navItems.forEach((item, index) => {
item.addEventListener('click', () => navigateTo(index));
});
recordBtn.addEventListener('click', () => {
if (isRecording) {
stopRecording();
} else {
startRecording();
}
});
transcriptClose.addEventListener('click', stopRecording);
endRecordingBtn.addEventListener('click', stopRecording);
gotoBook.addEventListener('click', () => navigateTo(1));
tocItems.forEach(item => {
item.addEventListener('click', showReadingView);
});
backToToc.addEventListener('click', showTocView);
memoirTabs.forEach(tab => {
tab.addEventListener('click', () => {
if (tab.dataset.tab === 'toc') {
showTocView();
} else {
showReadingView();
}
});
});
document.getElementById('exportPdf').addEventListener('click', () => {
showToast('PDF 生成中...');
});
document.getElementById('shareLink').addEventListener('click', () => {
showToast('链接已复制');
});
toggles.forEach(toggle => {
toggle.addEventListener('click', () => {
toggle.classList.toggle('active');
});
});
document.getElementById('upgradePlan').addEventListener('click', () => {
showToast('套餐页面开发中...');
});
document.getElementById('exportData').addEventListener('click', () => {
showToast('数据导出中...');
});
// Edit title functionality
bookTitle.addEventListener('click', () => {
const currentTitle = bookTitleText.textContent.replace(/[《》]/g, '');
editTitleInput.value = currentTitle;
editModal.classList.add('visible');
editTitleInput.focus();
editTitleInput.select();
});
editCancel.addEventListener('click', () => {
editModal.classList.remove('visible');
});
editConfirm.addEventListener('click', () => {
const newTitle = editTitleInput.value.trim();
if (newTitle) {
bookTitleText.textContent = `${newTitle}`;
tocBookTitle.textContent = newTitle;
editModal.classList.remove('visible');
showToast('已保存');
}
});
editTitleInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
editConfirm.click();
} else if (e.key === 'Escape') {
editCancel.click();
}
});
editModal.addEventListener('click', (e) => {
if (e.target === editModal) {
editModal.classList.remove('visible');
}
});
</script>
</body>
</html>