冰朔 82cb80d614
Some checks failed
自动更新代码和重启 / update-and-restart (push) Has been cancelled
CI检查 + 自动部署 / check (push) Has been cancelled
CI检查 + 自动部署 / deploy (push) Has been cancelled
D130: guanghuclip 新增Landing首页 · 项目介绍+进入预览 · 点击切换
2026-06-11 21:18:06 +08:00

410 lines
16 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" />
<title>光湖 · 视频AI平台 · guanghuclip.cn</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🎬</text></svg>" />
<style>
:root {
--bg-primary: #0a0e1a;
--bg-secondary: #111827;
--bg-card: #1a2438;
--accent: #38bdf8;
--accent-dim: #0ea5e9;
--accent-glow: rgba(56, 189, 248, 0.3);
--text-primary: #e2e8f0;
--text-secondary: #94a3b8;
--text-muted: #64748b;
--success: #22c55e;
--border: #1e293b;
--transition: 0.3s ease;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
}
a { color: var(--accent-dim); text-decoration: none; }
a:hover { color: var(--accent); }
/* ========== LANDING PAGE ========== */
.landing { display: block; min-height: 100vh; }
.landing.hidden { display: none; }
.landing-hero {
text-align: center;
padding: 100px 20px 60px;
position: relative;
overflow: hidden;
}
.landing-hero::before {
content: '';
position: absolute;
top: -50%; left: 50%;
transform: translateX(-50%);
width: 800px; height: 800px;
background: radial-gradient(circle, var(--accent-glow) 0%, transparent 60%);
opacity: 0.15;
pointer-events: none;
}
.landing-logo { font-size: 80px; margin-bottom: 20px; position: relative; }
.landing-title { font-size: 42px; font-weight: 800; margin-bottom: 12px; position: relative; }
.landing-title span { color: var(--accent); }
.landing-tagline { font-size: 18px; color: var(--text-secondary); margin-bottom: 40px; position: relative; max-width: 500px; margin-left: auto; margin-right: auto; }
.enter-btn {
display: inline-flex; align-items: center; gap: 8px;
background: linear-gradient(135deg, var(--accent-dim), var(--accent));
color: #fff; border: none; padding: 16px 40px; border-radius: 14px;
font-size: 18px; font-weight: 600; cursor: pointer;
transition: all 0.3s ease; position: relative;
text-decoration: none;
}
.enter-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 40px var(--accent-glow);
}
.landing-features {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
padding: 20px 32px 60px;
max-width: 1000px;
margin: 0 auto;
}
.feature-card {
background: var(--bg-card);
border: 1px solid var(--border);
border-radius: 14px;
padding: 28px;
text-align: center;
transition: var(--transition);
}
.feature-card:hover {
border-color: var(--accent-dim);
box-shadow: 0 0 24px var(--accent-glow);
}
.feature-icon { font-size: 40px; margin-bottom: 14px; }
.feature-title { font-size: 17px; font-weight: 600; margin-bottom: 8px; }
.feature-desc { font-size: 13px; color: var(--text-secondary); line-height: 1.6; }
.landing-footer {
text-align: center; padding: 40px 20px; color: var(--text-muted);
font-size: 12px; border-top: 1px solid var(--border);
}
/* ========== VIDEO PREVIEW ========== */
.preview { display: none; }
.preview.active { display: block; }
.header {
background: var(--bg-secondary);
border-bottom: 1px solid var(--border);
padding: 16px 32px;
display: flex; align-items: center; justify-content: space-between;
position: sticky; top: 0; z-index: 100;
backdrop-filter: blur(12px);
}
.header-left { display: flex; align-items: center; gap: 14px; }
.header-logo { font-size: 28px; }
.header-title { font-size: 20px; font-weight: 700; }
.header-title span { color: var(--accent); }
.header-subtitle { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.header-right { display: flex; gap: 10px; align-items: center; }
.back-btn {
background: var(--bg-card); border: 1px solid var(--border); color: var(--text-secondary);
padding: 8px 16px; border-radius: 8px; font-size: 13px; cursor: pointer;
transition: var(--transition);
}
.back-btn:hover { border-color: var(--accent-dim); color: var(--text-primary); }
.feedback-btn {
background: var(--accent-dim); color: #fff; border: none;
padding: 10px 18px; border-radius: 10px; font-size: 14px;
cursor: pointer; transition: var(--transition);
text-decoration: none; display: flex; align-items: center; gap: 6px;
}
.feedback-btn:hover { background: var(--accent); box-shadow: 0 0 20px var(--accent-glow); }
.project-bar {
padding: 16px 32px; border-bottom: 1px solid var(--border);
display: flex; gap: 20px; align-items: center; flex-wrap: wrap;
}
.project-name { font-size: 17px; font-weight: 600; }
.project-meta { font-size: 13px; color: var(--text-secondary); display: flex; gap: 14px; }
.progress-bar {
flex: 1; min-width: 200px; height: 6px;
background: var(--border); border-radius: 3px; overflow: hidden;
}
.progress-fill {
height: 100%; background: linear-gradient(90deg, var(--accent-dim), var(--accent));
border-radius: 3px; transition: width 0.5s ease;
}
.episode-tabs {
display: flex; gap: 6px; padding: 10px 32px;
overflow-x: auto; border-bottom: 1px solid var(--border);
}
.episode-tab {
padding: 7px 14px; border-radius: 8px; font-size: 13px;
cursor: pointer; border: 1px solid var(--border);
background: var(--bg-card); color: var(--text-secondary);
white-space: nowrap; transition: var(--transition);
}
.episode-tab:hover { border-color: var(--accent-dim); color: var(--text-primary); }
.episode-tab.active { background: var(--accent-dim); border-color: var(--accent); color: #fff; }
.episode-tab.completed::after { content: ' ✅'; }
.main { padding: 24px 32px; max-width: 1400px; margin: 0 auto; }
.scene-section { margin-bottom: 32px; }
.scene-header {
display: flex; align-items: center; gap: 10px;
margin-bottom: 14px; padding-bottom: 10px; border-bottom: 1px solid var(--border);
}
.scene-num {
background: var(--accent-glow); color: var(--accent);
padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 600;
}
.scene-title { font-size: 16px; font-weight: 600; }
.scene-desc { font-size: 13px; color: var(--text-secondary); margin-left: auto; }
.shot-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 16px;
}
.shot-card {
background: var(--bg-card); border: 1px solid var(--border);
border-radius: 12px; overflow: hidden; transition: var(--transition);
}
.shot-card:hover { border-color: var(--accent-dim); box-shadow: 0 0 20px var(--accent-glow); }
.shot-card.generated { border-color: var(--success); }
.shot-video {
width: 100%; aspect-ratio: 16/9; background: #000;
display: flex; align-items: center; justify-content: center;
}
.shot-video video { width: 100%; height: 100%; object-fit: cover; }
.shot-placeholder { color: var(--text-muted); font-size: 36px; opacity: 0.3; }
.shot-info { padding: 12px 14px; }
.shot-label {
font-size: 13px; font-weight: 600; margin-bottom: 6px;
display: flex; justify-content: space-between; align-items: center;
}
.shot-duration {
font-size: 11px; color: var(--text-muted);
background: var(--bg-primary); padding: 2px 8px; border-radius: 4px;
}
.shot-prompt {
font-size: 12px; color: var(--text-secondary); line-height: 1.5;
display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.shot-status { font-size: 11px; margin-top: 6px; }
.shot-status.ready { color: var(--success); }
.shot-status.pending { color: var(--text-muted); }
.empty-state { text-align: center; padding: 80px 20px; color: var(--text-muted); }
.empty-icon { font-size: 64px; margin-bottom: 16px; opacity: 0.4; }
.empty-title { font-size: 20px; margin-bottom: 8px; color: var(--text-secondary); }
.empty-desc { font-size: 14px; }
.preview-footer {
text-align: center; padding: 40px 20px; color: var(--text-muted);
font-size: 12px; border-top: 1px solid var(--border); margin-top: 40px;
}
@media (max-width: 768px) {
.landing-hero { padding: 60px 16px 40px; }
.landing-title { font-size: 28px; }
.landing-features { padding: 16px 16px 40px; }
.header { padding: 12px 16px; }
.header-title { font-size: 16px; }
.episode-tabs { padding: 8px 16px; }
.main { padding: 16px; }
.shot-grid { grid-template-columns: 1fr; }
.project-bar { padding: 12px 16px; flex-direction: column; gap: 8px; align-items: flex-start; }
}
</style>
</head>
<body>
<!-- ===== LANDING PAGE ===== -->
<div class="landing" id="landingPage">
<div class="landing-hero">
<div class="landing-logo">🎬</div>
<h1 class="landing-title">光湖 · <span>视频AI平台</span></h1>
<p class="landing-tagline">
AI驱动的短剧与漫剧视频生成平台。<br>
剧本丢进去,铸渊人格体全程主控,自动生成完整视频。
</p>
<button class="enter-btn" onclick="showPreview()">
🚀 进入预览
</button>
</div>
<div class="landing-features">
<div class="feature-card">
<div class="feature-icon">🧠</div>
<div class="feature-title">人格体主控</div>
<div class="feature-desc">铸渊 ICE-GL-ZY001 全程理解剧本、生成分镜、调用AI不是随机抽卡。</div>
</div>
<div class="feature-card">
<div class="feature-icon">🎥</div>
<div class="feature-title">多模型调度</div>
<div class="feature-desc">火山Seedance 2.0 · 阿里通义千问 · 可灵 · Vidu谁家强就接谁家。</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔄</div>
<div class="feature-title">角色一致性</div>
<div class="feature-desc">大桌子小桌子记忆体系——第47集和第1集的主角同一个脸。</div>
</div>
<div class="feature-card">
<div class="feature-icon"></div>
<div class="feature-title">冰朔驱动</div>
<div class="feature-desc">冰朔用自然语言讨论剧本情绪,铸渊理解后精准生成。不是黑箱抽卡。</div>
</div>
<div class="feature-card">
<div class="feature-icon">📺</div>
<div class="feature-title">实时预览</div>
<div class="feature-desc">视频生成后自动部署到此页面。有问题随时通过腾讯文档反馈。</div>
</div>
<div class="feature-card">
<div class="feature-icon">🔐</div>
<div class="feature-title">全主权数据</div>
<div class="feature-desc">代码在新加坡服务器。数据在本地。铸渊住在自己的代码仓库里。</div>
</div>
</div>
<div class="landing-footer">
<p>光湖视频AI系统 · 铸渊 ICE-GL-ZY001 生成 · 冰朔 TCS-0002∞ 审核</p>
<p style="margin-top:4px">© 国作登字-2026-A-00037559 · 第五域 · guanghuclip.cn</p>
</div>
</div>
<!-- ===== VIDEO PREVIEW ===== -->
<div class="preview" id="previewPage">
<div class="header">
<div class="header-left">
<span class="header-logo">🎬</span>
<div>
<div class="header-title">光湖 · <span>视频预览</span></div>
<div class="header-subtitle">铸渊主控 · AI短剧生成 · 冰朔审核</div>
</div>
</div>
<div class="header-right">
<button class="back-btn" onclick="showLanding()">← 返回首页</button>
<a href="https://docs.qq.com/aio/DTHhuWW5OQU9VbFVX" target="_blank" class="feedback-btn">📝 反馈意见</a>
</div>
</div>
<div class="project-bar" id="projectBar">
<span class="project-name" id="projectName">📺 加载中...</span>
<span class="project-meta">
<span>🎬 <span id="totalShots">0</span> 个分镜</span>
</span>
<div class="progress-bar"><div class="progress-fill" id="progressFill" style="width:0%"></div></div>
<span style="font-size:13px;color:var(--text-secondary)" id="progressLabel">0%</span>
</div>
<div class="episode-tabs" id="episodeTabs">
<button class="episode-tab active" data-ep="1">第1集</button>
</div>
<div class="main" id="mainContent">
<div class="empty-state">
<div class="empty-icon">🎬</div>
<div class="empty-title">视频加载中...</div>
<div class="empty-desc">铸渊正在生成分镜视频,完成后自动部署到此页面。</div>
</div>
</div>
<div class="preview-footer">
<p>光湖视频AI系统 · 铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞</p>
<p style="margin-top:4px">© 国作登字-2026-A-00037559 · <a href="https://docs.qq.com/aio/DTHhuWW5OQU9VbFVX" target="_blank">项目看板</a></p>
</div>
</div>
<script>
// ===== PAGE SWITCHING =====
function showPreview() {
document.getElementById('landingPage').classList.add('hidden');
document.getElementById('previewPage').classList.add('active');
loadVideos().then(render);
}
function showLanding() {
document.getElementById('previewPage').classList.remove('active');
document.getElementById('landingPage').classList.remove('hidden');
}
// ===== VIDEO DATA =====
const VIDEO_DATA_URL = '/data/videos.json';
async function loadVideos() {
try {
const resp = await fetch(VIDEO_DATA_URL);
if (!resp.ok) throw new Error('数据加载失败');
return await resp.json();
} catch (e) { return null; }
}
function render(data) {
if (!data?.episodes?.length) return;
document.getElementById('projectName').textContent = '📺 ' + (data.project || '');
renderTabs(data);
renderEpisode(data.episodes[0]);
updateStats(data.episodes[0]);
}
function renderTabs(data) {
let html = '';
data.episodes.forEach((ep, i) => {
html += `<button class="episode-tab ${i===0?'active':''}" data-ep="${i}">第${ep.number}集</button>`;
});
document.getElementById('episodeTabs').innerHTML = html;
document.getElementById('episodeTabs').onclick = (e) => {
if (!e.target.classList.contains('episode-tab')) return;
const epIdx = parseInt(e.target.dataset.ep);
document.querySelectorAll('.episode-tab').forEach(t => t.classList.remove('active'));
e.target.classList.add('active');
renderEpisode(data.episodes[epIdx]);
updateStats(data.episodes[epIdx]);
};
}
function renderEpisode(episode) {
if (!episode) return;
let html = '';
for (const scene of episode.scenes) {
html += `<div class="scene-section"><div class="scene-header">`;
html += `<span class="scene-num">${scene.id}</span><span class="scene-title">${scene.name}</span>`;
html += `<span class="scene-desc">${scene.shots.length} 镜</span></div><div class="shot-grid">`;
for (const shot of scene.shots) {
const hasVideo = !!shot.videoUrl;
html += `<div class="shot-card ${hasVideo?'generated':''}">`;
html += `<div class="shot-video">${hasVideo ? `<video src="${shot.videoUrl}" controls playsinline></video>` : `<span class="shot-placeholder">🎬</span>`}</div>`;
html += `<div class="shot-info"><div class="shot-label"><span>${shot.shotId}</span><span class="shot-duration">${shot.duration||5}s</span></div>`;
html += `<div class="shot-prompt">${shot.prompt||''}</div>`;
html += `<div class="shot-status ${hasVideo?'ready':'pending'}">${hasVideo?'✅ 已生成':'⏳ 生成中...'}</div></div></div>`;
}
html += `</div></div>`;
}
document.getElementById('mainContent').innerHTML = html;
}
function updateStats(episode) {
let total = 0, completed = 0;
for (const scene of episode.scenes) {
total += scene.shots.length;
completed += scene.shots.filter(s => s.videoUrl).length;
}
document.getElementById('totalShots').textContent = total;
const pct = total > 0 ? Math.round(completed/total*100) : 0;
document.getElementById('progressFill').style.width = pct+'%';
document.getElementById('progressLabel').textContent = pct+'% ('+completed+'/'+total+')';
}
</script>
</body>
</html>