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>
405 lines
14 KiB
HTML
405 lines
14 KiB
HTML
<!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>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: 'DM Sans', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
background: #FEFEFE;
|
|
min-height: 100vh;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Product List uses shared .scroll-container + custom styling */
|
|
.product-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 16px;
|
|
}
|
|
|
|
/* Product Card - Banner Style */
|
|
.product-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 20px;
|
|
padding: 24px;
|
|
min-height: 140px;
|
|
border-radius: 28px;
|
|
cursor: pointer;
|
|
transition: all 0.25s cubic-bezier(0.2, 0.8, 0.2, 1);
|
|
position: relative;
|
|
overflow: hidden;
|
|
/* Glowing Pill Quality Standard (Adapted for Cards) */
|
|
background: linear-gradient(90deg, #22D3EE 0%, #3B82F6 35%, #6366F1 65%, #8B5CF6 100%);
|
|
box-shadow:
|
|
0 0 15px rgba(34, 211, 238, 0.25),
|
|
0 0 30px rgba(99, 102, 241, 0.15),
|
|
0 8px 24px rgba(99, 102, 241, 0.25),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.25);
|
|
}
|
|
|
|
/* Top highlight layer */
|
|
.product-card::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: 50%;
|
|
background: linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, transparent 100%);
|
|
border-radius: 28px 28px 50% 50%;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.product-card:active {
|
|
transform: scale(0.98);
|
|
}
|
|
|
|
/* Per-product Theme Colors - Wide Hue Range, Lower Saturation */
|
|
.product-card.capybara {
|
|
/* Warm family: Dark Apricot → Peach → Sand → Dark Muted Orange (Rich & Complex) */
|
|
background: linear-gradient(90deg, #E6B98D 0%, #E8C9A8 35%, #D4A373 70%, #B07D5A 100%);
|
|
box-shadow:
|
|
0 0 20px rgba(201, 160, 122, 0.25),
|
|
0 8px 24px rgba(166, 124, 82, 0.2),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.product-card.badge-ai {
|
|
/* Cool family: Vibrant Cyan → Sky Blue → Periwinkle → Soft Violet (Complex & Translucent) */
|
|
background: linear-gradient(90deg, #22D3EE 0%, #60A5FA 35%, #818CF8 70%, #A78BFA 100%);
|
|
box-shadow:
|
|
0 0 20px rgba(96, 165, 250, 0.25),
|
|
0 8px 24px rgba(129, 140, 248, 0.2),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.product-card.badge-basic {
|
|
/* Lavender family: Bright Lilac → Orchid → Soft Purple → Muted Violet (Not heavy) */
|
|
background: linear-gradient(90deg, #C084FC 0%, #D8B4FE 35%, #C4B5FD 70%, #A78BFA 100%);
|
|
box-shadow:
|
|
0 0 15px rgba(200, 165, 232, 0.25),
|
|
0 8px 24px rgba(167, 139, 205, 0.2),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.product-card.bracelet {
|
|
/* Warm-Orange family: Bright Orange → Peach → Coral → Soft Terracotta (Light end) */
|
|
background: linear-gradient(90deg, #FDBA74 0%, #FB923C 35%, #FBAF85 70%, #E07B54 100%);
|
|
box-shadow:
|
|
0 0 20px rgba(244, 177, 131, 0.25),
|
|
0 8px 24px rgba(224, 123, 84, 0.2),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
.product-card.vsinger {
|
|
/* Teal family: Emerald → Mint → Aqua → Soft Teal (Translucent) */
|
|
background: linear-gradient(90deg, #34D399 0%, #5EEAD4 35%, #22D3EE 70%, #2DD4BF 100%);
|
|
box-shadow:
|
|
0 0 20px rgba(94, 187, 172, 0.25),
|
|
0 8px 24px rgba(61, 154, 139, 0.2),
|
|
inset 0 1px 1px rgba(255, 255, 255, 0.35);
|
|
}
|
|
|
|
/* Icon Box */
|
|
.p-icon {
|
|
width: 72px;
|
|
height: 72px;
|
|
flex-shrink: 0;
|
|
border-radius: 20px;
|
|
background: rgba(255, 255, 255, 0.25);
|
|
backdrop-filter: blur(8px);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
position: relative;
|
|
}
|
|
|
|
.p-icon img {
|
|
width: 48px;
|
|
height: 48px;
|
|
object-fit: contain;
|
|
filter: brightness(0) invert(1);
|
|
/* White icons */
|
|
}
|
|
|
|
/* Capybara uses colored image */
|
|
.product-card.capybara .p-icon img {
|
|
filter: none;
|
|
width: 56px;
|
|
height: 56px;
|
|
}
|
|
|
|
/* AI Badge on Icon */
|
|
.p-icon .ai-tag {
|
|
position: absolute;
|
|
top: -6px;
|
|
right: -6px;
|
|
background: rgba(255, 255, 255, 0.95);
|
|
color: #6366F1;
|
|
font-size: 9px;
|
|
font-weight: 700;
|
|
padding: 3px 6px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
/* Text Info */
|
|
.p-info {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
}
|
|
|
|
.p-name {
|
|
font-size: 20px;
|
|
font-weight: 700;
|
|
color: #FFFFFF;
|
|
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.p-status {
|
|
font-size: 14px;
|
|
color: rgba(255, 255, 255, 0.85);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.5);
|
|
}
|
|
|
|
.status-dot.online {
|
|
background: #34D399;
|
|
box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.3);
|
|
}
|
|
|
|
/* Arrow */
|
|
.p-arrow {
|
|
width: 28px;
|
|
height: 28px;
|
|
color: rgba(255, 255, 255, 0.7);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Fade in animation */
|
|
.product-card {
|
|
animation: fadeSlideUp 0.4s ease-out backwards;
|
|
}
|
|
|
|
.product-card:nth-child(1) {
|
|
animation-delay: 0.05s;
|
|
}
|
|
|
|
.product-card:nth-child(2) {
|
|
animation-delay: 0.1s;
|
|
}
|
|
|
|
.product-card:nth-child(3) {
|
|
animation-delay: 0.15s;
|
|
}
|
|
|
|
.product-card:nth-child(4) {
|
|
animation-delay: 0.2s;
|
|
}
|
|
|
|
.product-card:nth-child(5) {
|
|
animation-delay: 0.25s;
|
|
}
|
|
|
|
@keyframes fadeSlideUp {
|
|
from {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
}
|
|
|
|
to {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<!-- Reusing gradient background -->
|
|
<div class="gradient-bg">
|
|
<div class="gradient-layer layer-1"></div>
|
|
<div class="gradient-layer layer-2"></div>
|
|
<div class="gradient-layer layer-3"></div>
|
|
</div>
|
|
|
|
<header class="page-header">
|
|
<button class="back-btn" onclick="window.history.back()">
|
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M15 18l-6-6 6-6" />
|
|
</svg>
|
|
</button>
|
|
<h1 class="page-title">选择产品</h1>
|
|
<div class="header-spacer"></div>
|
|
</header>
|
|
|
|
<main class="scroll-container product-list">
|
|
<!-- 1. Plush Core (Capybara) -->
|
|
<div class="product-card capybara" onclick="selectDevice('device-control.html')">
|
|
<div class="p-icon">
|
|
<span class="ai-tag">AI</span>
|
|
<img src="Capybara.png" alt="毛绒机芯">
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="p-name">毛绒机芯</div>
|
|
<div class="p-status">
|
|
<span class="status-dot online"></span>
|
|
已连接
|
|
</div>
|
|
</div>
|
|
<svg class="p-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- 2. Badge AI -->
|
|
<div class="product-card badge-ai" onclick="selectDevice('#')">
|
|
<div class="p-icon">
|
|
<span class="ai-tag">AI</span>
|
|
<img src="icons/icon-product-badge.svg" alt="电子吧唧">
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="p-name">电子吧唧 AI</div>
|
|
<div class="p-status">
|
|
<span class="status-dot"></span>
|
|
离线
|
|
</div>
|
|
</div>
|
|
<svg class="p-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- 3. Badge Basic -->
|
|
<div class="product-card badge-basic" onclick="selectDevice('#')">
|
|
<div class="p-icon">
|
|
<img src="icons/icon-product-badge.svg" alt="普通吧唧">
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="p-name">普通吧唧</div>
|
|
<div class="p-status">
|
|
<span class="status-dot"></span>
|
|
未配对
|
|
</div>
|
|
</div>
|
|
<svg class="p-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- 4. AI Bracelet -->
|
|
<div class="product-card bracelet" onclick="selectDevice('#')">
|
|
<div class="p-icon">
|
|
<span class="ai-tag">AI</span>
|
|
<img src="icons/icon-product-bracelet.svg" alt="AI手链">
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="p-name">AI 手链</div>
|
|
<div class="p-status">
|
|
<span class="status-dot"></span>
|
|
点击扫描
|
|
</div>
|
|
</div>
|
|
<svg class="p-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</div>
|
|
|
|
<!-- 5. VSinger (Luo Tianyi) -->
|
|
<div class="product-card vsinger" onclick="window.open('https://example.com', '_blank')">
|
|
<div class="p-icon">
|
|
<img src="icons/icon-product-luo.svg" alt="洛天依">
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="p-name">洛天依</div>
|
|
<div class="p-status">去下载专属 APP →</div>
|
|
</div>
|
|
<svg class="p-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
stroke-linecap="round" stroke-linejoin="round">
|
|
<path d="M9 18l6-6-6-6" />
|
|
</svg>
|
|
</div>
|
|
</main>
|
|
|
|
<script>
|
|
function selectDevice(url) {
|
|
if (url === '#') {
|
|
// Haptic feedback simulation
|
|
if (navigator.vibrate) navigator.vibrate(10);
|
|
return;
|
|
}
|
|
window.location.href = url;
|
|
}
|
|
|
|
// Scroll Fade Effect (JavaScript Fallback)
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const container = document.querySelector('.scroll-container');
|
|
const cards = document.querySelectorAll('.product-card');
|
|
|
|
// If elements missing, abort
|
|
if (!container || cards.length === 0) return;
|
|
|
|
function handleScroll() {
|
|
cards.forEach(card => {
|
|
const rect = card.getBoundingClientRect();
|
|
// Fade zone logic (Updated for smoother "Half-Fade"):
|
|
// Resting (~130px) -> Opaque.
|
|
// Start fading at 120px (just as it starts moving up).
|
|
// End fading at 0px (top of screen).
|
|
// At header height (~60px), opacity will be 0.5 (Half Visible).
|
|
const startFade = 120;
|
|
const endFade = 0;
|
|
|
|
if (rect.top < startFade) {
|
|
let progress = (rect.top - endFade) / (startFade - endFade);
|
|
// Clamp value between 0 and 1
|
|
progress = Math.min(Math.max(progress, 0), 1);
|
|
|
|
// Apply opacity and scale
|
|
card.style.opacity = progress;
|
|
card.style.transform = `scale(${0.95 + (0.05 * progress)})`;
|
|
} else {
|
|
// Reset properties if outside fade zone
|
|
card.style.opacity = 1;
|
|
card.style.transform = 'scale(1)';
|
|
}
|
|
});
|
|
}
|
|
|
|
// Passive listener for performance
|
|
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
// Initial render check
|
|
handleScroll();
|
|
});
|
|
</script>
|
|
</body>
|
|
|
|
</html> |