rtc_prd/profile-info.html
seaislee1209 066eb8f820 feat: music-creation page + MiniMax API integration + Flutter dev setup
Music Creation Page:
- Vinyl 3D flip to view lyrics, tonearm animation, glow rotation effect
- Circular SVG progress ring, speech bubble feedback, confirm dialog
- Playlist modal, free creation input, lyrics formatting optimization
- MiniMax API real music generation with SSE streaming progress

Backend:
- FastAPI proxy server.py for MiniMax API calls
- Music + lyrics file persistence to Capybara music/ directory
- GET /api/playlist endpoint for auto-building playlist from files

UI/UX Refinements:
- frontend-design skill compliance across all pages
- Glassmorphism effects, modal interactions, scroll tap prevention
- iPhone 12 Pro responsive layout (390x844)

Flutter Development Preparation:
- Installed flutter-expert skill with 6 reference docs
- Added 5 Cursor Rules: official Flutter, clean architecture, UI performance, testing, Dart standards

Assets:
- 9 Capybara music MP3 files + lyrics TXT files
- MiniMax API documentation

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 18:23:19 +08:00

338 lines
11 KiB
HTML
Raw Permalink 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, viewport-fit=cover">
<title>Airhub - 个人信息</title>
<link rel="stylesheet" href="styles.css">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=DM+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
padding-top: calc(env(safe-area-inset-top, 20px) + 48px);
width: 100%;
box-sizing: border-box;
position: absolute;
top: 0;
z-index: 10;
}
.back-btn {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.4);
width: 44px;
height: 44px;
border-radius: 22px;
display: flex;
align-items: center;
justify-content: center;
color: #1F2937;
cursor: pointer;
transition: all 0.2s;
}
.back-btn:active {
transform: scale(0.92);
}
.page-title {
font-size: 18px;
font-weight: 600;
color: #1F2937;
}
.save-btn {
background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%);
border: none;
padding: 10px 20px;
border-radius: 20px;
color: white;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.save-btn:active {
transform: scale(0.95);
}
.save-btn:disabled {
opacity: 0.5;
}
.profile-content {
padding: calc(env(safe-area-inset-top, 20px) + 120px) 20px 40px;
display: flex;
flex-direction: column;
align-items: center;
}
/* 头像区域 */
.avatar-section {
margin-bottom: 32px;
position: relative;
}
.avatar-large {
width: 100px;
height: 100px;
border-radius: 50px;
background: linear-gradient(135deg, #FFECD2, #FCB69F);
overflow: hidden;
box-shadow: 0 8px 24px rgba(139, 94, 60, 0.2);
}
.avatar-large img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-edit-badge {
position: absolute;
bottom: 0;
right: 0;
width: 32px;
height: 32px;
background: linear-gradient(135deg, #ECCFA8 0%, #C99672 100%);
border-radius: 16px;
display: flex;
align-items: center;
justify-content: center;
border: 3px solid white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
cursor: pointer;
}
.avatar-edit-badge svg {
width: 14px;
height: 14px;
color: white;
}
/* 表单 */
.form-card {
width: 100%;
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(20px);
border-radius: 20px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(139, 94, 60, 0.08);
}
.form-item {
display: flex;
align-items: center;
padding: 18px 20px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
}
.form-item:last-child {
border-bottom: none;
}
.form-label {
width: 80px;
font-size: 15px;
color: #6B7280;
}
.form-value {
flex: 1;
text-align: right;
font-size: 15px;
color: #1F2937;
}
.form-input {
flex: 1;
text-align: right;
font-size: 15px;
color: #1F2937;
border: none;
background: transparent;
outline: none;
}
.form-arrow {
color: #D1D5DB;
font-size: 18px;
margin-left: 8px;
}
.form-item.clickable {
cursor: pointer;
transition: background 0.2s;
}
.form-item.clickable:active {
background: rgba(255, 255, 255, 0.5);
}
</style>
</head>
<body>
<div class="app-container">
<div class="gradient-bg">
<div class="gradient-layer layer-1"></div>
</div>
<header class="page-header">
<button class="back-btn" onclick="history.back()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M15 18l-6-6 6-6"></path>
</svg>
</button>
<span class="page-title">个人信息</span>
<button class="save-btn" onclick="saveProfile()">保存</button>
</header>
<main class="profile-content">
<div class="avatar-section">
<div class="avatar-large">
<img src="Capybara.png" alt="Avatar" id="avatar-img">
</div>
<div class="avatar-edit-badge" onclick="changeAvatar()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z">
</path>
<circle cx="12" cy="13" r="4"></circle>
</svg>
</div>
</div>
<div class="form-card">
<div class="form-item">
<span class="form-label">昵称</span>
<input type="text" class="form-input" id="nickname" value="土豆" placeholder="请输入昵称">
</div>
<div class="form-item clickable" onclick="selectGender()">
<span class="form-label">性别</span>
<span class="form-value" id="gender"></span>
<span class="form-arrow"></span>
</div>
<div class="form-item clickable" onclick="selectBirthday()">
<span class="form-label">生日</span>
<span class="form-value" id="birthday">1994-12-09</span>
<span class="form-arrow"></span>
</div>
</div>
</main>
</div>
<!-- 性别选择弹窗 -->
<div class="modal-overlay" id="gender-modal">
<div class="glass-modal">
<div class="modal-title">选择性别</div>
<div class="modal-actions" style="flex-direction: column; gap: 8px;">
<button class="modal-btn confirm" style="flex: none;" onclick="setGender('男')"></button>
<button class="modal-btn confirm" style="flex: none;" onclick="setGender('女')"></button>
<button class="modal-btn cancel" style="flex: none; margin-top: 8px;"
onclick="closeGenderModal()">取消</button>
</div>
</div>
</div>
<!-- 通用提示弹窗 -->
<div class="modal-overlay" id="message-modal">
<div class="glass-modal">
<div class="modal-title" id="msg-title">提示</div>
<div class="modal-desc" id="msg-desc">内容</div>
<div class="modal-actions">
<button class="modal-btn confirm" onclick="closeMessageModal()">确定</button>
</div>
</div>
</div>
<!-- 输入弹窗 (用于生日) -->
<div class="modal-overlay" id="input-modal">
<div class="glass-modal">
<div class="modal-title" id="input-title">输入信息</div>
<input type="text" class="modal-input" id="input-field"
style="width: 100%; padding: 12px; border-radius: 12px; border: 1px solid rgba(0,0,0,0.1); background: rgba(255,255,255,0.8); margin: 16px 0; font-size: 16px; box-sizing: border-box;"
placeholder="">
<div class="modal-actions">
<button class="modal-btn cancel" onclick="closeInputModal()">取消</button>
<button class="modal-btn confirm" onclick="confirmInput()">确定</button>
</div>
</div>
</div>
<script>
// 通用 Modal
function showMessage(title, desc) {
document.getElementById('msg-title').innerText = title;
document.getElementById('msg-desc').innerText = desc;
document.getElementById('message-modal').classList.add('active');
}
function closeMessageModal() {
document.getElementById('message-modal').classList.remove('active');
}
function changeAvatar() {
showMessage('头像设置', '头像更换功能开发中...');
}
function selectGender() {
document.getElementById('gender-modal').classList.add('active');
}
function closeGenderModal() {
document.getElementById('gender-modal').classList.remove('active');
}
function setGender(gender) {
document.getElementById('gender').innerText = gender;
closeGenderModal();
}
// 生日输入相关
let inputCallback = null;
function selectBirthday() {
const currentVal = document.getElementById('birthday').innerText;
showInput('请输入生日 (YYYY-MM-DD)', currentVal, (val) => {
if (val) document.getElementById('birthday').innerText = val;
});
}
function showInput(title, defaultVal, callback) {
document.getElementById('input-title').innerText = title;
document.getElementById('input-field').value = defaultVal;
inputCallback = callback;
document.getElementById('input-modal').classList.add('active');
}
function closeInputModal() {
document.getElementById('input-modal').classList.remove('active');
inputCallback = null;
}
function confirmInput() {
const val = document.getElementById('input-field').value;
if (inputCallback) inputCallback(val);
closeInputModal();
}
function saveProfile() {
const nickname = document.getElementById('nickname').value;
const gender = document.getElementById('gender').innerText;
const birthday = document.getElementById('birthday').innerText;
showMessage('保存成功', `昵称: ${nickname}\n性别: ${gender}\n生日: ${birthday}`);
setTimeout(() => {
/* Optional: Auto close or navigate back */
}, 2000);
}
</script>
</body>
</html>