新增简易的8080面板😊
This commit is contained in:
274
video/architecture.html
Normal file
274
video/architecture.html
Normal file
@@ -0,0 +1,274 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FutureOSS - 架构解析</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', 'PingFang SC', sans-serif;
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
.bg-lines {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background:
|
||||
repeating-linear-gradient(45deg, rgba(79,172,254,0.05) 0px, rgba(79,172,254,0.05) 1px, transparent 1px, transparent 40px),
|
||||
repeating-linear-gradient(-45deg, rgba(79,172,254,0.05) 0px, rgba(79,172,254,0.05) 1px, transparent 1px, transparent 40px);
|
||||
z-index: 0;
|
||||
}
|
||||
#play-overlay {
|
||||
position: fixed; top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.9);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
z-index: 100; cursor: pointer; transition: opacity 0.5s;
|
||||
}
|
||||
#play-overlay.hidden { opacity: 0; pointer-events: none; }
|
||||
.play-circle {
|
||||
width: 120px; height: 120px;
|
||||
border: 4px solid #4facfe; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 3rem; animation: pulse 2s ease-in-out infinite;
|
||||
transition: transform 0.3s, background 0.3s;
|
||||
}
|
||||
.play-circle:hover { transform: scale(1.1); background: rgba(79,172,254,0.2); }
|
||||
@keyframes pulse {
|
||||
0%,100% { box-shadow: 0 0 0 0 rgba(79,172,254,0.4); }
|
||||
50% { box-shadow: 0 0 0 30px rgba(79,172,254,0); }
|
||||
}
|
||||
|
||||
#stage { position: relative; width: 100%; height: 100%; z-index: 1; display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
/* 架构图 */
|
||||
.arch-diagram {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 20px;
|
||||
opacity: 0; transform: scale(0.5); transition: all 1s cubic-bezier(0.68,-0.55,0.265,1.55);
|
||||
}
|
||||
.arch-diagram.show { opacity: 1; transform: scale(1); }
|
||||
|
||||
.layer {
|
||||
display: flex; gap: 20px; justify-content: center; flex-wrap: wrap;
|
||||
opacity: 0; transform: translateY(40px); transition: all 0.8s cubic-bezier(0.68,-0.55,0.265,1.55);
|
||||
}
|
||||
.layer.show { opacity: 1; transform: translateY(0); }
|
||||
|
||||
.block {
|
||||
padding: 20px 30px;
|
||||
border-radius: 12px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
min-width: 140px;
|
||||
position: relative;
|
||||
}
|
||||
.block::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: -3px;
|
||||
border-radius: 14px;
|
||||
background: linear-gradient(135deg, #4facfe, #00f2fe);
|
||||
z-index: -1;
|
||||
opacity: 0.5;
|
||||
}
|
||||
.block.core { background: #1a3a5c; }
|
||||
.block.plugin { background: #1a2a4c; }
|
||||
.block.infra { background: #0f2a3c; }
|
||||
.block span { display: block; font-size: 1.5rem; margin-bottom: 8px; }
|
||||
|
||||
.arrow-down {
|
||||
font-size: 1.5rem;
|
||||
color: #4facfe;
|
||||
opacity: 0;
|
||||
animation: none;
|
||||
}
|
||||
.arrow-down.show {
|
||||
opacity: 1;
|
||||
animation: bounce 1s infinite;
|
||||
}
|
||||
@keyframes bounce {
|
||||
0%,100% { transform: translateY(0); }
|
||||
50% { transform: translateY(8px); }
|
||||
}
|
||||
|
||||
.layer-label {
|
||||
position: absolute;
|
||||
left: -180px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
writing-mode: vertical-lr;
|
||||
letter-spacing: 4px;
|
||||
}
|
||||
|
||||
#desc {
|
||||
position: absolute;
|
||||
bottom: 15%;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
font-size: 1.2rem;
|
||||
color: #aaa;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s;
|
||||
}
|
||||
#desc.show { opacity: 1; }
|
||||
|
||||
#progress {
|
||||
position: fixed; bottom: 0; left: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #4facfe, #00f2fe, #f093fb);
|
||||
width: 0%; transition: width 0.3s; z-index: 50;
|
||||
}
|
||||
#title {
|
||||
position: fixed; top: 40px; left: 50%; transform: translateX(-50%);
|
||||
font-size: 1.8rem; font-weight: 700; opacity: 0; transition: opacity 0.8s; z-index: 10;
|
||||
text-shadow: 0 4px 20px rgba(79,172,254,0.5);
|
||||
}
|
||||
#title.show { opacity: 1; }
|
||||
#controls {
|
||||
position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
|
||||
display: flex; gap: 20px; z-index: 50; opacity: 0; transition: opacity 0.5s;
|
||||
}
|
||||
#controls.show { opacity: 1; }
|
||||
.ctrl-btn {
|
||||
width: 50px; height: 50px; border-radius: 50%; background: rgba(255,255,255,0.1);
|
||||
border: none; color: #fff; font-size: 1.3rem; cursor: pointer;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
.ctrl-btn:hover { background: rgba(79,172,254,0.5); transform: scale(1.1); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-lines"></div>
|
||||
<div id="play-overlay">
|
||||
<div class="play-circle">▶</div>
|
||||
</div>
|
||||
|
||||
<div id="title">🏗️ 架构解析</div>
|
||||
|
||||
<div id="stage">
|
||||
<div class="arch-diagram" id="arch">
|
||||
<!-- 前端层 -->
|
||||
<div class="layer" id="layer-frontend">
|
||||
<div class="label" style="position:absolute;left:-160px;top:50%;transform:translateY(-50%);color:#666;font-size:0.8rem;writing-mode:vertical-lr;letter-spacing:3px;">前端层</div>
|
||||
<div class="block plugin"><span>🌐</span>WebUI 容器</div>
|
||||
<div class="block plugin"><span>📊</span>Dashboard</div>
|
||||
<div class="block plugin"><span>💻</span>Log Terminal</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">⬇</div>
|
||||
|
||||
<!-- HTTP 层 -->
|
||||
<div class="layer" id="layer-http">
|
||||
<div class="label" style="position:absolute;left:-160px;top:50%;transform:translateY(-50%);color:#666;font-size:0.8rem;writing-mode:vertical-lr;letter-spacing:3px;">服务层</div>
|
||||
<div class="block plugin"><span>🔌</span>HTTP API<br><small style="color:#888">:8080</small></div>
|
||||
<div class="block plugin"><span>🔌</span>TCP Server<br><small style="color:#888">:8082</small></div>
|
||||
<div class="block plugin"><span>🔌</span>WebSocket</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">⬇</div>
|
||||
|
||||
<!-- 核心层 -->
|
||||
<div class="layer" id="layer-core">
|
||||
<div class="label" style="position:absolute;left:-160px;top:50%;transform:translateY(-50%);color:#666;font-size:0.8rem;writing-mode:vertical-lr;letter-spacing:3px;">核心层</div>
|
||||
<div class="block core"><span>📦</span>Plugin Loader</div>
|
||||
<div class="block core"><span>🌉</span>Plugin Bridge</div>
|
||||
<div class="block core"><span>🔐</span>签名验证</div>
|
||||
</div>
|
||||
|
||||
<div class="arrow-down">⬇</div>
|
||||
|
||||
<!-- 基础设施 -->
|
||||
<div class="layer" id="layer-infra">
|
||||
<div class="label" style="position:absolute;left:-160px;top:50%;transform:translateY(-50%);color:#666;font-size:0.8rem;writing-mode:vertical-lr;letter-spacing:3px;">基础设施</div>
|
||||
<div class="block infra"><span>💾</span>Plugin Storage</div>
|
||||
<div class="block infra"><span>🎨</span>Logger</div>
|
||||
<div class="block infra"><span>📦</span>Pkg Manager</div>
|
||||
<div class="block infra"><span>🔗</span>Dependency</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="desc">一切皆为插件,从核心到界面</div>
|
||||
</div>
|
||||
|
||||
<div id="progress"></div>
|
||||
<div id="controls">
|
||||
<button class="ctrl-btn" id="btn-replay" title="重播">↻</button>
|
||||
<button class="ctrl-btn" id="btn-sound" title="音效">🔊</button>
|
||||
<button class="ctrl-btn" id="btn-back" title="返回">←</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
||||
let audioCtx=null, soundEnabled=true, bgMusic=null;
|
||||
function initAudio(){if(!audioCtx)audioCtx=new AudioCtx();}
|
||||
function playBgMusic(){
|
||||
if(!soundEnabled||!audioCtx)return;stopBgMusic();
|
||||
const dur=30,sr=audioCtx.sampleRate,buf=audioCtx.createBuffer(2,sr*dur,sr);
|
||||
for(let ch=0;ch<2;ch++){const d=buf.getChannelData(ch);for(let i=0;i<d.length;i++){const t=i/sr;d[i]=(Math.sin(2*Math.PI*196*t)*0.08+Math.sin(2*Math.PI*294*t)*0.06+Math.sin(2*Math.PI*392*t)*0.05)*Math.exp(-t%6);}}
|
||||
bgMusic=audioCtx.createBufferSource();bgMusic.buffer=buf;bgMusic.loop=true;
|
||||
const g=audioCtx.createGain();g.gain.value=0.1;bgMusic.connect(g).connect(audioCtx.destination);bgMusic.start();
|
||||
}
|
||||
function stopBgMusic(){if(bgMusic){try{bgMusic.stop();}catch(e){}bgMusic=null;}}
|
||||
function playPop(){
|
||||
if(!soundEnabled||!audioCtx)return;
|
||||
const o=audioCtx.createOscillator(),g=audioCtx.createGain();o.type='sine';
|
||||
o.frequency.setValueAtTime(700,audioCtx.currentTime);o.frequency.exponentialRampToValueAtTime(250,audioCtx.currentTime+0.18);
|
||||
g.gain.setValueAtTime(0.2,audioCtx.currentTime);g.gain.exponentialRampToValueAtTime(0.01,audioCtx.currentTime+0.18);
|
||||
o.connect(g).connect(audioCtx.destination);o.start();o.stop(audioCtx.currentTime+0.18);
|
||||
}
|
||||
document.getElementById('btn-sound').addEventListener('click',()=>{
|
||||
soundEnabled=!soundEnabled;document.getElementById('btn-sound').textContent=soundEnabled?'🔊':'🔇';
|
||||
if(!soundEnabled)stopBgMusic();else playBgMusic();
|
||||
});
|
||||
|
||||
const overlay=document.getElementById('play-overlay');
|
||||
const title=document.getElementById('title');
|
||||
const progress=document.getElementById('progress');
|
||||
const controls=document.getElementById('controls');
|
||||
const arch=document.getElementById('arch');
|
||||
const desc=document.getElementById('desc');
|
||||
const layers=[document.getElementById('layer-frontend'),document.getElementById('layer-http'),document.getElementById('layer-core'),document.getElementById('layer-infra')];
|
||||
const arrows=document.querySelectorAll('.arrow-down');
|
||||
|
||||
function setProgress(p){progress.style.width=p+'%';}
|
||||
|
||||
function startAnimation(){
|
||||
setProgress(0);
|
||||
arch.classList.remove('show');
|
||||
layers.forEach(l=>l.classList.remove('show'));
|
||||
arrows.forEach(a=>a.classList.remove('show'));
|
||||
title.classList.remove('show');
|
||||
desc.classList.remove('show');
|
||||
controls.classList.remove('show');
|
||||
overlay.classList.add('hidden');
|
||||
initAudio();playBgMusic();playPop();
|
||||
|
||||
setTimeout(()=>{title.classList.add('show');setProgress(10);},300);
|
||||
setTimeout(()=>{arch.classList.add('show');setProgress(15);playPop();},800);
|
||||
|
||||
layers.forEach((layer,i)=>{
|
||||
setTimeout(()=>{layer.classList.add('show');playPop();setProgress(25+(i+1)*15);},1500+i*1200);
|
||||
});
|
||||
|
||||
arrows.forEach((arrow,i)=>{
|
||||
setTimeout(()=>{arrow.classList.add('show');},2000+i*1200);
|
||||
});
|
||||
|
||||
setTimeout(()=>{desc.classList.add('show');setProgress(85);},1500+layers.length*1200+500);
|
||||
setTimeout(()=>{controls.classList.add('show');setProgress(100);},1500+layers.length*1200+2000);
|
||||
}
|
||||
|
||||
overlay.addEventListener('click',startAnimation);
|
||||
document.getElementById('btn-replay').addEventListener('click',startAnimation);
|
||||
document.getElementById('btn-back').addEventListener('click',()=>{stopBgMusic();location.href='index.html';});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
113
video/index.html
Normal file
113
video/index.html
Normal file
@@ -0,0 +1,113 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FutureOSS - 视频展示</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
background: #0a0a0a;
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
header {
|
||||
padding: 40px 20px;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
header h1 { font-size: 2.5rem; margin-bottom: 10px; }
|
||||
header p { opacity: 0.8; font-size: 1.1rem; }
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
padding: 40px 20px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 30px;
|
||||
width: 100%;
|
||||
}
|
||||
.card {
|
||||
background: #1a1a1a;
|
||||
border-radius: 16px;
|
||||
overflow: hidden;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.card:hover {
|
||||
transform: translateY(-8px);
|
||||
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.3);
|
||||
}
|
||||
.card-thumb {
|
||||
height: 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 4rem;
|
||||
}
|
||||
.card:nth-child(1) .card-thumb { background: linear-gradient(135deg, #667eea, #764ba2); }
|
||||
.card:nth-child(2) .card-thumb { background: linear-gradient(135deg, #f093fb, #f5576c); }
|
||||
.card:nth-child(3) .card-thumb { background: linear-gradient(135deg, #4facfe, #00f2fe); }
|
||||
.card-info { padding: 20px; }
|
||||
.card-info h3 { margin-bottom: 8px; font-size: 1.2rem; }
|
||||
.card-info p { color: #888; font-size: 0.9rem; }
|
||||
.play-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding: 8px 16px;
|
||||
background: #667eea;
|
||||
border-radius: 8px;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.card:hover .play-btn { background: #764ba2; }
|
||||
footer {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>🎬 FutureOSS 视频展示</h1>
|
||||
<p>用动画和声音了解我们的项目</p>
|
||||
</header>
|
||||
<div class="container">
|
||||
<div class="card" onclick="location.href='intro.html'">
|
||||
<div class="card-thumb">📦</div>
|
||||
<div class="card-info">
|
||||
<h3>项目特性展示</h3>
|
||||
<p>立方体动画演示核心特性,插件化、安全性、实时监控...</p>
|
||||
<div class="play-btn">▶ 点击播放</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" onclick="location.href='plugin-demo.html'">
|
||||
<div class="card-thumb">🔌</div>
|
||||
<div class="card-info">
|
||||
<h3>插件开发演示</h3>
|
||||
<p>从零开发一个插件,看这里就对了</p>
|
||||
<div class="play-btn">▶ 点击播放</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card" onclick="location.href='architecture.html'">
|
||||
<div class="card-thumb">🏗️</div>
|
||||
<div class="card-info">
|
||||
<h3>架构解析</h3>
|
||||
<p>深入了解 FutureOSS 的技术架构和设计思想</p>
|
||||
<div class="play-btn">▶ 点击播放</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>FutureOSS © 2026 — 一切皆为插件</p>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
553
video/intro.html
Normal file
553
video/intro.html
Normal file
@@ -0,0 +1,553 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>FutureOSS</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/remixicon@4.1.0/fonts/remixicon.css" rel="stylesheet">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{background:#0a0a0a;color:#ddd;overflow:hidden;height:100vh;width:100vw;font-family:'PingFang SC','Microsoft YaHei',sans-serif}
|
||||
|
||||
.letterbox{position:fixed;left:0;width:100%;height:7vh;background:#000;z-index:300;pointer-events:none}
|
||||
.letterbox.top{top:0}.letterbox.bottom{bottom:0}
|
||||
|
||||
#grid-bg{position:fixed;inset:0;z-index:0;background-image:linear-gradient(rgba(100,120,180,.04) 1px,transparent 1px),linear-gradient(90deg,rgba(100,120,180,.04) 1px,transparent 1px);background-size:50px 50px;animation:gs 30s linear infinite}
|
||||
@keyframes gs{to{background-position:50px 50px}}
|
||||
|
||||
#stage{position:fixed;inset:0;z-index:10}
|
||||
.sc{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .6s}
|
||||
.sc.active{opacity:1;pointer-events:auto}
|
||||
|
||||
#subtitle-bar{position:fixed;bottom:12%;left:50%;transform:translateX(-50%);width:65%;text-align:center;z-index:100;pointer-events:none}
|
||||
#subtitle{font-size:.95rem;line-height:1.8;font-weight:300;color:rgba(255,255,255,.45);text-shadow:0 2px 20px rgba(0,0,0,.9);opacity:0}
|
||||
#subtitle.show{opacity:1;transition:opacity .4s}
|
||||
|
||||
#progress{position:fixed;bottom:0;left:0;height:1px;background:rgba(100,120,180,.3);width:0%;transition:width .3s;z-index:400}
|
||||
|
||||
#overlay{position:fixed;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:999;cursor:pointer;background:#0a0a0a;transition:opacity .8s}
|
||||
#overlay.hidden{opacity:0;pointer-events:none}
|
||||
#overlay .pt{font-size:.65rem;color:#222;letter-spacing:8px;margin-bottom:30px}
|
||||
.pb{width:50px;height:50px;border:1px solid #333;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.2rem;color:#555}
|
||||
|
||||
#controls{position:fixed;bottom:2.5%;left:50%;transform:translateX(-50%);display:flex;gap:8px;z-index:400;opacity:0;transition:opacity .4s}
|
||||
#controls.show{opacity:1}
|
||||
.cb{width:32px;height:32px;border-radius:50%;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.06);color:#444;font-size:.75rem;cursor:pointer;display:flex;align-items:center;justify-content:center}
|
||||
.cb:hover{color:#fff;background:rgba(255,255,255,.1)}
|
||||
|
||||
/* 黑屏 */
|
||||
.blank{}
|
||||
|
||||
/* 纯文字 */
|
||||
.plain-text{font-size:2.2rem;font-weight:200;text-align:center;padding:0 40px;color:rgba(255,255,255,.5);opacity:0;transform:translateY(15px)}
|
||||
.plain-text.show{opacity:1;transform:translateY(0);transition:all 1.2s ease}
|
||||
|
||||
/* 左右分屏 */
|
||||
.split{display:flex;width:85%;max-width:1000px;gap:60px;align-items:center;padding:0 30px}
|
||||
.split-left{flex:1;font-size:1.8rem;font-weight:300;color:rgba(255,255,255,.6);line-height:1.6;opacity:0;transform:translateX(-30px)}
|
||||
.split-left.show{opacity:1;transform:translateX(0);transition:all .9s ease}
|
||||
.split-right{flex:1;opacity:0;transform:translateX(30px)}
|
||||
.split-right.show{opacity:1;transform:translateX(0);transition:all .9s ease .2s}
|
||||
|
||||
/* 痛点列表 */
|
||||
.pain-list{list-style:none}
|
||||
.pain-list li{padding:8px 0;font-size:.95rem;color:rgba(255,255,255,.4);opacity:0;transform:translateY(8px)}
|
||||
.pain-list li.show{opacity:1;transform:translateY(0);transition:all .5s ease}
|
||||
.pain-list li::before{content:'\D7';color:#553333;margin-right:10px;font-weight:bold}
|
||||
|
||||
/* 解决方案列表 */
|
||||
.sol-list{list-style:none}
|
||||
.sol-list li{padding:8px 0;font-size:.9rem;color:rgba(120,140,180,.7);opacity:0;transform:translateY(8px)}
|
||||
.sol-list li.show{opacity:1;transform:translateY(0);transition:all .5s ease}
|
||||
.sol-list li::before{content:'\2713';color:#4a6;margin-right:10px;font-weight:bold}
|
||||
|
||||
/* 项目名 */
|
||||
.proj-name{font-size:3.5rem;font-weight:600;color:rgba(255,255,255,.7);opacity:0;transform:translateY(15px)}
|
||||
.proj-name.show{opacity:1;transform:translateY(0);transition:all 1s ease}
|
||||
.proj-tag{font-size:.85rem;color:#333;letter-spacing:4px;margin-top:12px;opacity:0}
|
||||
.proj-tag.show{opacity:1;transition:opacity .6s}
|
||||
|
||||
/* 概念行 */
|
||||
.c-line{font-size:1.5rem;font-weight:300;text-align:center;padding:0 30px;color:rgba(255,255,255,.45);opacity:0;transform:translateY(10px)}
|
||||
.c-line.show{opacity:1;transform:translateY(0);transition:all .8s ease}
|
||||
.em{color:#8899bb;font-weight:400}
|
||||
|
||||
/* 特性展示 */
|
||||
.feat-split{display:flex;width:88%;max-width:950px;gap:50px;align-items:flex-start}
|
||||
.feat-code{flex:1;background:rgba(255,255,255,.015);border:1px solid rgba(255,255,255,.05);border-radius:8px;padding:20px;font-family:Consolas,'Courier New',monospace;font-size:.75rem;line-height:1.8;color:#666;opacity:0;transform:translateX(-20px)}
|
||||
.feat-code.show{opacity:1;transform:translateX(0);transition:all .7s ease}
|
||||
.feat-code .kw{color:#7788aa}
|
||||
.feat-code .str{color:#6a6}
|
||||
.feat-code .fn{color:#aaa}
|
||||
.feat-code .cm{color:#333}
|
||||
.feat-label{flex:1;padding:10px 0}
|
||||
.feat-label .title{font-size:1.3rem;font-weight:500;color:rgba(255,255,255,.7);margin-bottom:10px;opacity:0;transform:translateX(15px)}
|
||||
.feat-label .title.show{opacity:1;transform:translateX(0);transition:all .6s ease .15s}
|
||||
.feat-label .desc{font-size:.85rem;color:rgba(255,255,255,.3);line-height:1.7;opacity:0;transform:translateX(15px)}
|
||||
.feat-label .desc.show{opacity:1;transform:translateX(0);transition:all .6s ease .3s}
|
||||
|
||||
/* 终端 */
|
||||
.term-box{background:rgba(255,255,255,.015);border:1px solid rgba(255,255,255,.05);border-radius:8px;padding:20px 25px;font-family:Consolas,monospace;max-width:480px;width:85%;opacity:0;transform:translateY(8px)}
|
||||
.term-box.show{opacity:1;transform:translateY(0);transition:all .7s ease}
|
||||
.tline{font-size:.75rem;margin-bottom:5px;opacity:0;color:#444}
|
||||
.tline.show{opacity:1;transition:opacity .25s}
|
||||
.tline .p{color:#6677aa}
|
||||
.tline .c{color:#bbb}
|
||||
.tline .o{color:#4a5a4a}
|
||||
.tline .i{color:#333}
|
||||
.blk::after{content:'\2588';animation:blink .8s infinite;color:#6677aa}
|
||||
@keyframes blink{0%,100%{opacity:1}50%{opacity:0}}
|
||||
|
||||
/* 对比 */
|
||||
.compare{display:flex;width:85%;max-width:800px;gap:30px}
|
||||
.compare-col{flex:1;padding:15px 20px;border-radius:8px;opacity:0;transform:translateY(10px)}
|
||||
.compare-col.show{opacity:1;transform:translateY(0);transition:all .7s ease}
|
||||
.compare-col.old{background:rgba(200,50,50,.03);border:1px solid rgba(200,50,50,.08)}
|
||||
.compare-col.new{background:rgba(50,150,100,.03);border:1px solid rgba(50,150,100,.08)}
|
||||
.compare-col h4{font-size:.6rem;letter-spacing:3px;margin-bottom:12px;font-weight:400}
|
||||
.compare-col.old h4{color:#553333}
|
||||
.compare-col.new h4{color:#3a5a3a}
|
||||
.compare-col p{font-size:.8rem;color:#444;line-height:1.8;font-family:Consolas,monospace}
|
||||
|
||||
/* 数字 */
|
||||
.num-row{display:flex;gap:30px;align-items:center}
|
||||
.num-block{text-align:center;opacity:0;transform:translateY(8px)}
|
||||
.num-block.show{opacity:1;transform:translateY(0);transition:all .6s ease}
|
||||
.num-v{font-size:3rem;font-weight:600;color:rgba(255,255,255,.6);line-height:1}
|
||||
.num-u{font-size:1rem;color:#445;margin-top:3px}
|
||||
.num-l{font-size:.7rem;color:#2a2a2a;margin-top:5px;letter-spacing:2px}
|
||||
.nsep{width:1px;height:60px;background:rgba(255,255,255,.04)}
|
||||
|
||||
/* 结尾 */
|
||||
.end-box{text-align:center}
|
||||
.end-n{font-size:2.5rem;font-weight:600;color:rgba(255,255,255,.6);opacity:0;transform:translateY(12px)}
|
||||
.end-n.show{opacity:1;transform:translateY(0);transition:all .8s ease}
|
||||
.end-t{font-size:.65rem;color:#222;letter-spacing:4px;margin-top:10px;opacity:0}
|
||||
.end-t.show{opacity:1;transition:opacity .5s}
|
||||
.end-u{margin-top:20px;opacity:0}
|
||||
.end-u.show{opacity:1;transition:opacity .5s}
|
||||
.end-u a{display:block;color:#333;font-size:.65rem;text-decoration:none;margin-bottom:4px}
|
||||
.end-u a:hover{color:#555}
|
||||
|
||||
/* 闪光 */
|
||||
#flash{position:fixed;inset:0;background:#fff;opacity:0;pointer-events:none;z-index:400}
|
||||
|
||||
@media(max-width:768px){
|
||||
.split,.feat-split,.compare{flex-direction:column;gap:20px}
|
||||
.plain-text{font-size:1.5rem}
|
||||
.proj-name{font-size:2.2rem}
|
||||
.num-row{flex-direction:column;gap:15px}
|
||||
.nsep{width:40px;height:1px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="letterbox top"></div>
|
||||
<div class="letterbox bottom"></div>
|
||||
<div id="grid-bg"></div>
|
||||
|
||||
<div id="overlay">
|
||||
<div class="pt">FUTUREOSS</div>
|
||||
<div class="pb"><i class="ri-play-fill"></i></div>
|
||||
</div>
|
||||
<div id="flash"></div>
|
||||
<div id="subtitle-bar"><div id="subtitle"></div></div>
|
||||
<div id="progress"></div>
|
||||
<div id="controls">
|
||||
<button class="cb" id="btn-replay"><i class="ri-restart-line"></i></button>
|
||||
<button class="cb" id="btn-sound"><i class="ri-volume-up-fill"></i></button>
|
||||
</div>
|
||||
|
||||
<div id="stage">
|
||||
|
||||
<!-- 0: 黑屏 -->
|
||||
<div class="sc" id="s0"></div>
|
||||
|
||||
<!-- 1: 开场文字 -->
|
||||
<div class="sc" id="s1">
|
||||
<div class="plain-text" id="m1">不知道你有没有这种感觉</div>
|
||||
</div>
|
||||
|
||||
<!-- 2: 继续 -->
|
||||
<div class="sc" id="s2">
|
||||
<div class="plain-text" id="m2">每次搭一个新项目,都很累</div>
|
||||
</div>
|
||||
|
||||
<!-- 3: 左右分屏 - 痛点在右 -->
|
||||
<div class="sc" id="s3">
|
||||
<div class="split">
|
||||
<div class="split-left" id="sl-why">因为每次都在<br>重复同样的事</div>
|
||||
<div class="split-right" id="sr-pain">
|
||||
<ul class="pain-list">
|
||||
<li id="p0">装环境装到怀疑人生</li>
|
||||
<li id="p1">找个配置文件翻遍整个项目</li>
|
||||
<li id="p2">加个小功能要大改架构</li>
|
||||
<li id="p3">出了问题连日志都看不明白</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 4: 转折 -->
|
||||
<div class="sc" id="s4">
|
||||
<div class="plain-text" id="m4">于是我们想</div>
|
||||
</div>
|
||||
|
||||
<!-- 5: 左右分屏 - 解法在右 -->
|
||||
<div class="sc" id="s5">
|
||||
<div class="split">
|
||||
<div class="split-left" id="sl-want">能不能做一个<br>不用操心的框架?</div>
|
||||
<div class="split-right" id="sr-sol">
|
||||
<ul class="sol-list">
|
||||
<li id="s0">不用再重复写基础代码</li>
|
||||
<li id="s1">不用再手动管理依赖</li>
|
||||
<li id="s2">不用在几十个文件里找配置</li>
|
||||
<li id="s3">不用担心插件被篡改</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 6: 揭示 -->
|
||||
<div class="sc" id="s6" style="text-align:center">
|
||||
<div class="proj-name" id="rn">FutureOSS</div>
|
||||
<div class="proj-tag" id="rt">一切皆为插件</div>
|
||||
</div>
|
||||
|
||||
<!-- 7: 概念 -->
|
||||
<div class="sc" id="s7">
|
||||
<div class="c-line" id="c1">不是<span class="em">部分功能</span>可插拔</div>
|
||||
<div class="c-line" id="c2">而是<span class="em">所有东西</span>都是插件</div>
|
||||
<div class="c-line" id="c3">仪表盘、日志、前端<span class="em">全部是</span></div>
|
||||
</div>
|
||||
|
||||
<!-- 8: 特性1 插件化 -->
|
||||
<div class="sc" id="s8">
|
||||
<div class="feat-split">
|
||||
<div class="feat-code" id="f8c">
|
||||
<span class="cm"># 新建一个目录 丢进去</span>
|
||||
<span class="kw">store</span>/@{你}/<span class="str">hello/</span>
|
||||
<span class="fn"> main.py</span>
|
||||
<span class="fn"> manifest.json</span>
|
||||
<span class="fn"> README.md</span>
|
||||
|
||||
<span class="cm"># 启动自动加载 删掉自动消失</span>
|
||||
<span class="cm"># 不用改一行核心代码</span>
|
||||
</div>
|
||||
<div class="feat-label">
|
||||
<div class="title" id="f8t"><i class="ri-box-3-line" style="margin-right:8px"></i>插件化</div>
|
||||
<div class="desc" id="f8d">想加功能?新建一个目录丢进去。想删?直接 rm -rf。不用改一行核心代码。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 9: 特性2 签名 -->
|
||||
<div class="sc" id="s9">
|
||||
<div class="feat-split">
|
||||
<div class="feat-code" id="f9c">
|
||||
<span class="kw">验证中...</span>
|
||||
<span class="o"> SHA-256 校验通过</span>
|
||||
<span class="o"> RSA-4096 签名合法</span>
|
||||
<span class="o"> 来源: @Falck</span>
|
||||
|
||||
<span class="cm"># 每个官方插件启动前</span>
|
||||
<span class="cm"># 自动验证,失败直接拒绝加载</span>
|
||||
</div>
|
||||
<div class="feat-label">
|
||||
<div class="title" id="f9t"><i class="ri-lock-password-line" style="margin-right:8px"></i>签名验证</div>
|
||||
<div class="desc" id="f9d">不是事后检查,是加载前就验证。改了一个字节,整个插件拒绝加载。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 10: 特性3 日志 -->
|
||||
<div class="sc" id="s10">
|
||||
<div class="feat-split">
|
||||
<div class="feat-code" id="f10c">
|
||||
<span class="kw">Log</span>.<span class="str">info</span>(<span class="o">"app"</span>, <span class="o">"启动"</span>)
|
||||
<span class="kw">Log</span>.<span class="str">warn</span>(<span class="o">"db"</span>, <span class="o">"将满"</span>)
|
||||
<span class="kw">Log</span>.<span class="str">error</span>(<span class="o">"api"</span>, <span class="o">"超时"</span>)
|
||||
<span class="kw">Log</span>.<span class="str">tip</span>(<span class="o">"app"</span>, <span class="o">"已加载 20 插件"</span>)
|
||||
|
||||
<span class="cm"># 终端自动着色</span>
|
||||
<span class="cm"># 不用 grep 找关键字</span>
|
||||
</div>
|
||||
<div class="feat-label">
|
||||
<div class="title" id="f10t"><i class="ri-palette-line" style="margin-right:8px"></i>彩色日志</div>
|
||||
<div class="desc" id="f10d">info 白 warn 黄 error 红。不用在一堆黑白文字里翻来覆去。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 11: 特性4 商店 -->
|
||||
<div class="sc" id="s11">
|
||||
<div class="feat-split">
|
||||
<div class="feat-code" id="f11c">
|
||||
<span class="kw">$</span> <span class="str">pkg install @author/plugin</span>
|
||||
<span class="o"> 下载完成</span>
|
||||
<span class="o"> 签名验证通过</span>
|
||||
<span class="o"> 已安装,重启生效</span>
|
||||
|
||||
<span class="cm"># 不用 git clone</span>
|
||||
<span class="cm"># 不用手动复制目录</span>
|
||||
<span class="cm"># 一行命令搞定</span>
|
||||
</div>
|
||||
<div class="feat-label">
|
||||
<div class="title" id="f11t"><i class="ri-store-3-line" style="margin-right:8px"></i>插件商店</div>
|
||||
<div class="desc" id="f11d">一条命令安装插件。不用去仓库翻目录、不用手动复制文件。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 12: 特性5 监控 -->
|
||||
<div class="sc" id="s12">
|
||||
<div class="feat-split">
|
||||
<div class="feat-code" id="f12c">
|
||||
<span class="fn">仪表盘实时展示:</span>
|
||||
CPU 78%
|
||||
MEM 56%
|
||||
NET 12.3M/s 4.1M/s
|
||||
DISK RD 45MB WR 23MB
|
||||
LOAD 1.2 0.8 0.6
|
||||
|
||||
<span class="cm"># 打开浏览器就有</span>
|
||||
<span class="cm"># 不用装 Prometheus</span>
|
||||
<span class="cm"># 不用配 Grafana</span>
|
||||
</div>
|
||||
<div class="feat-label">
|
||||
<div class="title" id="f12t"><i class="ri-dashboard-line" style="margin-right:8px"></i>实时监控</div>
|
||||
<div class="desc" id="f12d">启动就有仪表盘。不用搭 Prometheus、不用配 Grafana,打开浏览器就行。</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 13: 对比 -->
|
||||
<div class="sc" id="s13">
|
||||
<div class="compare">
|
||||
<div class="compare-col old" id="cm-old">
|
||||
<h4>以前</h4>
|
||||
<p>git clone xxx<br>pip install -r req.txt<br>python -m venv .venv<br>source .venv/bin/activate<br>pip install flask mysql...<br>改配置文件半小时<br>python main.py<br>报错 -> 查日志 -> 改配置<br>python main.py<br>又报错...</p>
|
||||
</div>
|
||||
<div class="compare-col new" id="cm-new">
|
||||
<h4>现在</h4>
|
||||
<p style="font-size:1.1rem;color:#fff;font-weight:600">bash start.sh</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 14: 终端 -->
|
||||
<div class="sc" id="s14">
|
||||
<div class="term-box" id="term">
|
||||
<div class="tline" id="t0"><span class="p">$</span> <span class="c">bash start.sh</span></div>
|
||||
<div class="tline" id="t1"><span class="i">检测环境...</span></div>
|
||||
<div class="tline" id="t2"><span class="i">自动安装依赖...</span></div>
|
||||
<div class="tline" id="t3"><span class="o">虚拟环境创建完成</span></div>
|
||||
<div class="tline" id="t4"><span class="o">20 个插件加载完成</span></div>
|
||||
<div class="tline" id="t5"><span class="o">http://localhost:8080</span></div>
|
||||
<div class="tline" id="t6"><span class="p">$</span> <span class="blk"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 15: 数字 -->
|
||||
<div class="sc" id="s15">
|
||||
<div class="num-row">
|
||||
<div class="num-block" id="nb0"><div class="num-v"><span id="nv0">0</span></div><div class="num-u">+ 官方插件</div></div>
|
||||
<div class="nsep"></div>
|
||||
<div class="num-block" id="nb1"><div class="num-v"><span id="nv1">0</span></div><div class="num-u">行命令</div></div>
|
||||
<div class="nsep"></div>
|
||||
<div class="num-block" id="nb2"><div class="num-v"><span id="nv2">0</span></div><div class="num-l">% 插件化</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 16: 结尾 -->
|
||||
<div class="sc" id="s16" style="text-align:center">
|
||||
<div class="end-box">
|
||||
<div class="end-n" id="en">FutureOSS</div>
|
||||
<div class="end-t" id="et">一切皆为插件</div>
|
||||
<div class="end-u" id="eu">
|
||||
<a href="https://gitee.com/starlight-apk/feature-oss" target="_blank">gitee.com/starlight-apk/feature-oss</a>
|
||||
<a href="https://futureoss.date" target="_blank">futureoss.date</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 音效
|
||||
var AC=window.AudioContext||window.webkitAudioContext;
|
||||
var ac=null,sfxOn=true,bgSrc=null,timer=null;
|
||||
function initA(){if(!ac)ac=new AC()}
|
||||
function startBg(){if(!sfxOn||!ac)return;stopBg();var d=120,sr=ac.sampleRate,b=ac.createBuffer(2,sr*d,sr);for(var c=0;c<2;c++){var ch=b.getChannelData(c);for(var i=0;i<ch.length;i++){var t=i/sr;ch[i]=(Math.sin(6.28*220*t)*.04+Math.sin(6.28*330*t)*.025+Math.sin(6.28*440*t)*.02)*(.5+.5*Math.sin(6.28*.1*t))}}bgSrc=ac.createBufferSource();bgSrc.buffer=b;bgSrc.loop=true;var g=ac.createGain();g.gain.value=.05;bgSrc.connect(g).connect(ac.destination);bgSrc.start()}
|
||||
function stopBg(){if(bgSrc){try{bgSrc.stop()}catch(e){}bgSrc=null}}
|
||||
function pop(){if(!sfxOn||!ac)return;var o=ac.createOscillator(),g=ac.createGain();o.type='sine';o.frequency.setValueAtTime(300+Math.random()*400,ac.currentTime);o.frequency.exponentialRampToValueAtTime(80,ac.currentTime+.06);g.gain.setValueAtTime(.1,ac.currentTime);g.gain.exponentialRampToValueAtTime(.01,ac.currentTime+.06);o.connect(g).connect(ac.destination);o.start();o.stop(ac.currentTime+.06)}
|
||||
function swo(){if(!sfxOn||!ac)return;var sz=ac.sampleRate*.35,bf=ac.createBuffer(1,sz,ac.sampleRate),d=bf.getChannelData(0);for(var i=0;i<sz;i++)d[i]=(Math.random()*2-1)*Math.exp(-i/(sz*.06));var s=ac.createBufferSource();s.buffer=bf;var f=ac.createBiquadFilter();f.type='bandpass';f.frequency.value=400;f.Q.value=.3;var g=ac.createGain();g.gain.value=.02;s.connect(f).connect(g).connect(ac.destination);s.start()}
|
||||
|
||||
document.getElementById('btn-sound').onclick=function(){sfxOn=!sfxOn;var ic=this.querySelector('i');ic.className=sfxOn?'ri-volume-up-fill':'ri-volume-mute-line';if(!sfxOn)stopBg();else startBg()}
|
||||
|
||||
// 工具
|
||||
var sub=document.getElementById('subtitle'),prog=document.getElementById('progress'),
|
||||
overlay=document.getElementById('overlay'),ctrls=document.getElementById('controls'),
|
||||
fl=document.getElementById('flash'),st0;
|
||||
function setSub(h){sub.innerHTML=h;sub.classList.add('show')}
|
||||
function hideSub(){sub.classList.remove('show')}
|
||||
function showS(id){var ss=document.querySelectorAll('.sc');for(var i=0;i<ss.length;i++)ss[i].classList.remove('active');var el=document.getElementById(id);if(el)el.classList.add('active');swo()}
|
||||
function doFlash(){fl.style.opacity='.08';fl.style.transition='opacity .5s';setTimeout(function(){fl.style.opacity='0'},50)}
|
||||
function setProg(p){prog.style.width=p+'%'}
|
||||
function animNum(elId,tg){var el=document.getElementById(elId),dur=1000,s=performance.now();function tk(n){var p=Math.min((n-s)/dur,1),e=1-Math.pow(1-p,3);el.textContent=Math.floor(tg*e);if(p<1)requestAnimationFrame(tk)}requestAnimationFrame(tk)}
|
||||
|
||||
// 时间轴
|
||||
var tl=[],ti=0,tot;
|
||||
|
||||
function build(){
|
||||
tl=[];
|
||||
var m=function(s){return s*1000}
|
||||
|
||||
// 0: 黑屏
|
||||
tl.push({t:m(.5),fn:function(){showS('s0')}})
|
||||
tl.push({t:m(2),fn:function(){setSub('......')}})
|
||||
tl.push({t:m(4.5),fn:function(){hideSub()}})
|
||||
|
||||
// 1: 开场
|
||||
tl.push({t:m(5),fn:function(){showS('s1')}})
|
||||
tl.push({t:m(5.6),fn:function(){pop();document.getElementById('m1').classList.add('show')}})
|
||||
tl.push({t:m(6.5),fn:function(){setSub('不知道你有没有这种感觉')}})
|
||||
|
||||
// 2: 继续
|
||||
tl.push({t:m(9),fn:function(){document.getElementById('m1').classList.remove('show')}})
|
||||
tl.push({t:m(10),fn:function(){showS('s2')}})
|
||||
tl.push({t:m(10.6),fn:function(){pop();document.getElementById('m2').classList.add('show')}})
|
||||
tl.push({t:m(11.5),fn:function(){setSub('每次搭一个新项目,都很累')}})
|
||||
|
||||
// 3: 痛点
|
||||
tl.push({t:m(14),fn:function(){doFlash();showS('s3')}})
|
||||
tl.push({t:m(14.5),fn:function(){pop();document.getElementById('sl-why').classList.add('show')}})
|
||||
tl.push({t:m(15.5),fn:function(){setSub('因为每次都在重复同样的事')}})
|
||||
tl.push({t:m(17),fn:function(){pop();document.getElementById('sr-pain').classList.add('show')}})
|
||||
tl.push({t:m(17.8),fn:function(){pop();document.getElementById('p0').classList.add('show')}})
|
||||
tl.push({t:m(19),fn:function(){pop();document.getElementById('p1').classList.add('show')}})
|
||||
tl.push({t:m(20.2),fn:function(){pop();document.getElementById('p2').classList.add('show')}})
|
||||
tl.push({t:m(21.4),fn:function(){pop();document.getElementById('p3').classList.add('show')}})
|
||||
|
||||
// 4: 转折
|
||||
tl.push({t:m(24),fn:function(){showS('s4')}})
|
||||
tl.push({t:m(24.6),fn:function(){pop();document.getElementById('m4').classList.add('show')}})
|
||||
tl.push({t:m(25.5),fn:function(){setSub('于是我们想......')}})
|
||||
|
||||
// 5: 解法
|
||||
tl.push({t:m(28),fn:function(){showS('s5')}})
|
||||
tl.push({t:m(28.5),fn:function(){pop();document.getElementById('sl-want').classList.add('show')}})
|
||||
tl.push({t:m(29.5),fn:function(){setSub('能不能做一个不用操心的框架?')}})
|
||||
tl.push({t:m(31),fn:function(){pop();document.getElementById('sr-sol').classList.add('show')}})
|
||||
tl.push({t:m(31.8),fn:function(){pop();document.getElementById('s0').classList.add('show')}})
|
||||
tl.push({t:m(33),fn:function(){pop();document.getElementById('s1').classList.add('show')}})
|
||||
tl.push({t:m(34.2),fn:function(){pop();document.getElementById('s2').classList.add('show')}})
|
||||
tl.push({t:m(35.4),fn:function(){pop();document.getElementById('s3').classList.add('show')}})
|
||||
|
||||
// 6: 揭示
|
||||
tl.push({t:m(38),fn:function(){doFlash();showS('s6')}})
|
||||
tl.push({t:m(38.5),fn:function(){pop();document.getElementById('rn').classList.add('show')}})
|
||||
tl.push({t:m(40),fn:function(){hideSub()}})
|
||||
tl.push({t:m(41),fn:function(){pop();document.getElementById('rt').classList.add('show')}})
|
||||
tl.push({t:m(42),fn:function(){setSub('FutureOSS -- 一切皆为插件')}})
|
||||
|
||||
// 7: 概念
|
||||
tl.push({t:m(44),fn:function(){showS('s7')}})
|
||||
tl.push({t:m(44.5),fn:function(){pop();document.getElementById('c1').classList.add('show')}})
|
||||
tl.push({t:m(46),fn:function(){setSub('不是部分功能可插拔')}})
|
||||
tl.push({t:m(47.5),fn:function(){pop();document.getElementById('c2').classList.add('show')}})
|
||||
tl.push({t:m(49),fn:function(){setSub('而是所有东西都是插件')}})
|
||||
tl.push({t:m(50.5),fn:function(){pop();document.getElementById('c3').classList.add('show')}})
|
||||
|
||||
// 8: 插件化
|
||||
tl.push({t:m(52),fn:function(){doFlash();showS('s8')}})
|
||||
tl.push({t:m(52.3),fn:function(){pop();document.getElementById('f8c').classList.add('show')}})
|
||||
tl.push({t:m(53),fn:function(){pop();document.getElementById('f8t').classList.add('show');document.getElementById('f8d').classList.add('show')}})
|
||||
tl.push({t:m(54),fn:function(){setSub('想加功能新建目录丢进去 想删直接删掉')}})
|
||||
|
||||
// 9: 签名
|
||||
tl.push({t:m(56.5),fn:function(){showS('s9')}})
|
||||
tl.push({t:m(56.8),fn:function(){pop();document.getElementById('f9c').classList.add('show')}})
|
||||
tl.push({t:m(57.5),fn:function(){pop();document.getElementById('f9t').classList.add('show');document.getElementById('f9d').classList.add('show')}})
|
||||
tl.push({t:m(58.5),fn:function(){setSub('改一个字节 整个插件拒绝加载')}})
|
||||
|
||||
// 10: 日志
|
||||
tl.push({t:m(61),fn:function(){showS('s10')}})
|
||||
tl.push({t:m(61.3),fn:function(){pop();document.getElementById('f10c').classList.add('show')}})
|
||||
tl.push({t:m(62),fn:function(){pop();document.getElementById('f10t').classList.add('show');document.getElementById('f10d').classList.add('show')}})
|
||||
tl.push({t:m(63),fn:function(){setSub('不用在一堆黑白文字里翻来覆去')}})
|
||||
|
||||
// 11: 商店
|
||||
tl.push({t:m(65.5),fn:function(){showS('s11')}})
|
||||
tl.push({t:m(65.8),fn:function(){pop();document.getElementById('f11c').classList.add('show')}})
|
||||
tl.push({t:m(66.5),fn:function(){pop();document.getElementById('f11t').classList.add('show');document.getElementById('f11d').classList.add('show')}})
|
||||
tl.push({t:m(67.5),fn:function(){setSub('一条命令安装 不用翻目录 不用手动复制')}})
|
||||
|
||||
// 12: 监控
|
||||
tl.push({t:m(70),fn:function(){showS('s12')}})
|
||||
tl.push({t:m(70.3),fn:function(){pop();document.getElementById('f12c').classList.add('show')}})
|
||||
tl.push({t:m(71),fn:function(){pop();document.getElementById('f12t').classList.add('show');document.getElementById('f12d').classList.add('show')}})
|
||||
tl.push({t:m(72),fn:function(){setSub('不用搭Prometheus 不用配Grafana')}})
|
||||
|
||||
// 13: 对比
|
||||
tl.push({t:m(74.5),fn:function(){doFlash();showS('s13')}})
|
||||
tl.push({t:m(74.8),fn:function(){pop();document.getElementById('cm-old').classList.add('show')}})
|
||||
tl.push({t:m(77),fn:function(){setSub('以前: clone 装依赖 配环境 改配置 启动 报错 查日志 改...')}})
|
||||
tl.push({t:m(79),fn:function(){pop();document.getElementById('cm-new').classList.add('show')}})
|
||||
tl.push({t:m(80),fn:function(){hideSub()}})
|
||||
tl.push({t:m(80.5),fn:function(){setSub('现在: bash start.sh')}})
|
||||
|
||||
// 14: 终端
|
||||
tl.push({t:m(82),fn:function(){showS('s14')}})
|
||||
tl.push({t:m(82.3),fn:function(){pop();document.getElementById('term').classList.add('show')}})
|
||||
tl.push({t:m(83.5),fn:function(){pop();document.getElementById('t0').classList.add('show')}})
|
||||
tl.push({t:m(84.5),fn:function(){pop();document.getElementById('t1').classList.add('show')}})
|
||||
tl.push({t:m(85.5),fn:function(){pop();document.getElementById('t2').classList.add('show')}})
|
||||
tl.push({t:m(86.5),fn:function(){pop();document.getElementById('t3').classList.add('show')}})
|
||||
tl.push({t:m(87.2),fn:function(){pop();document.getElementById('t4').classList.add('show')}})
|
||||
tl.push({t:m(88),fn:function(){pop();document.getElementById('t5').classList.add('show')}})
|
||||
tl.push({t:m(88.8),fn:function(){pop();document.getElementById('t6').classList.add('show')}})
|
||||
tl.push({t:m(90),fn:function(){setSub('一行命令 自动装依赖 创建环境 启动服务')}})
|
||||
|
||||
// 15: 数字
|
||||
tl.push({t:m(92),fn:function(){doFlash();showS('s15')}})
|
||||
tl.push({t:m(92.3),fn:function(){pop();document.getElementById('nb0').classList.add('show');animNum('nv0',20)}})
|
||||
tl.push({t:m(93.5),fn:function(){pop();document.getElementById('nb1').classList.add('show');animNum('nv1',1)}})
|
||||
tl.push({t:m(94.7),fn:function(){pop();document.getElementById('nb2').classList.add('show');animNum('nv2',100)}})
|
||||
tl.push({t:m(96),fn:function(){setSub('20+ 插件 1 行命令 100% 插件化')}})
|
||||
|
||||
// 16: 结尾
|
||||
tl.push({t:m(98),fn:function(){doFlash();showS('s16')}})
|
||||
tl.push({t:m(98.3),fn:function(){pop();document.getElementById('en').classList.add('show')}})
|
||||
tl.push({t:m(99.5),fn:function(){hideSub()}})
|
||||
tl.push({t:m(100),fn:function(){pop();document.getElementById('et').classList.add('show')}})
|
||||
tl.push({t:m(101),fn:function(){setSub('一切皆为插件')}})
|
||||
tl.push({t:m(102.5),fn:function(){pop();document.getElementById('eu').classList.add('show')}})
|
||||
tl.push({t:m(104),fn:function(){setSub('代码开源 欢迎贡献')}})
|
||||
tl.push({t:m(106),fn:function(){ctrls.classList.add('show');setProg(100)}})
|
||||
|
||||
return m(108)
|
||||
}
|
||||
|
||||
function resetAll(){
|
||||
var ss=document.querySelectorAll('.sc');for(var i=0;i<ss.length;i++)ss[i].classList.remove('active')
|
||||
var ids=['m1','m2','m4','sl-why','sr-pain','p0','p1','p2','p3','sl-want','sr-sol','s0','s1','s2','s3','rn','rt','c1','c2','c3',
|
||||
'f8c','f8t','f8d','f9c','f9t','f9d','f10c','f10t','f10d','f11c','f11t','f11d','f12c','f12t','f12d',
|
||||
'cm-old','cm-new','term','t0','t1','t2','t3','t4','t5','t6','nb0','nb1','nb2','en','et','eu']
|
||||
for(var i=0;i<ids.length;i++){var e=document.getElementById(ids[i]);if(e)e.classList.remove('show')}
|
||||
document.getElementById('nv0').textContent='0';document.getElementById('nv1').textContent='0';document.getElementById('nv2').textContent='0'
|
||||
hideSub();setProg(0);ctrls.classList.remove('show')
|
||||
}
|
||||
|
||||
function start(){
|
||||
overlay.classList.add('hidden');resetAll();initA();startBg()
|
||||
tot=build();ti=0;st0=performance.now()
|
||||
function next(){while(ti<tl.length){var it=tl[ti],el=performance.now()-st0,dl=it.t-el;if(dl>0){timer=setTimeout(function(){it.fn();ti++;setProg(it.t/tot*100);next()},dl);return;}it.fn();ti++}}
|
||||
next()
|
||||
}
|
||||
|
||||
overlay.onclick=start
|
||||
document.getElementById('btn-replay').onclick=function(){clearTimeout(timer);start()}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
324
video/plugin-demo.html
Normal file
324
video/plugin-demo.html
Normal file
@@ -0,0 +1,324 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>FutureOSS - 插件开发演示</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: 'Segoe UI', 'PingFang SC', sans-serif;
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
.bg-grid {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background-image:
|
||||
linear-gradient(rgba(240, 147, 251, 0.1) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(240, 147, 251, 0.1) 1px, transparent 1px);
|
||||
background-size: 40px 40px;
|
||||
z-index: 0;
|
||||
}
|
||||
#play-overlay {
|
||||
position: fixed;
|
||||
top: 0; left: 0;
|
||||
width: 100%; height: 100%;
|
||||
background: rgba(0,0,0,0.9);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
#play-overlay.hidden { opacity: 0; pointer-events: none; }
|
||||
.play-circle {
|
||||
width: 120px; height: 120px;
|
||||
border: 4px solid #f093fb;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 3rem;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
transition: transform 0.3s, background 0.3s;
|
||||
}
|
||||
.play-circle:hover {
|
||||
transform: scale(1.1);
|
||||
background: rgba(240, 147, 251, 0.2);
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { box-shadow: 0 0 0 0 rgba(240, 147, 251, 0.4); }
|
||||
50% { box-shadow: 0 0 0 30px rgba(240, 147, 251, 0); }
|
||||
}
|
||||
|
||||
/* 时间线 */
|
||||
#timeline {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1;
|
||||
}
|
||||
.step {
|
||||
position: absolute;
|
||||
width: 70%;
|
||||
padding: 40px;
|
||||
background: rgba(255,255,255,0.03);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(240, 147, 251, 0.3);
|
||||
opacity: 0;
|
||||
transform: scale(0.8) translateY(50px);
|
||||
transition: all 0.8s cubic-bezier(0.68, -0.55, 0.265, 1.55);
|
||||
}
|
||||
.step.active {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
.step.prev {
|
||||
opacity: 0;
|
||||
transform: scale(0.6) translateX(-100px);
|
||||
}
|
||||
.step-number {
|
||||
width: 50px; height: 50px;
|
||||
background: linear-gradient(135deg, #f093fb, #f5576c);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.step h3 { font-size: 1.5rem; margin-bottom: 15px; }
|
||||
.step p { color: #aaa; line-height: 1.8; font-size: 1.05rem; }
|
||||
.step code {
|
||||
display: block;
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: #1a1a1a;
|
||||
border-radius: 8px;
|
||||
font-family: 'Consolas', monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #4facfe;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#progress {
|
||||
position: fixed;
|
||||
bottom: 0; left: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #f093fb, #f5576c, #4facfe);
|
||||
width: 0%;
|
||||
transition: width 0.3s;
|
||||
z-index: 50;
|
||||
}
|
||||
#title {
|
||||
position: fixed;
|
||||
top: 40px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
opacity: 0;
|
||||
transition: opacity 0.8s;
|
||||
z-index: 10;
|
||||
text-shadow: 0 4px 20px rgba(240, 147, 251, 0.5);
|
||||
}
|
||||
#title.show { opacity: 1; }
|
||||
#controls {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
z-index: 50;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s;
|
||||
}
|
||||
#controls.show { opacity: 1; }
|
||||
.ctrl-btn {
|
||||
width: 50px; height: 50px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.1);
|
||||
border: none;
|
||||
color: #fff;
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s, transform 0.2s;
|
||||
}
|
||||
.ctrl-btn:hover { background: rgba(240, 147, 251, 0.5); transform: scale(1.1); }
|
||||
|
||||
/* 装饰几何 */
|
||||
.geo {
|
||||
position: absolute;
|
||||
border: 2px solid rgba(240, 147, 251, 0.2);
|
||||
border-radius: 4px;
|
||||
animation: spin 10s linear infinite;
|
||||
}
|
||||
@keyframes spin { to { transform: rotate(360deg); } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-grid"></div>
|
||||
<div class="geo" style="width:80px;height:80px;top:15%;left:10%;"></div>
|
||||
<div class="geo" style="width:60px;height:60px;bottom:20%;right:15%;border-radius:50%;animation-duration:8s;"></div>
|
||||
<div class="geo" style="width:100px;height:100px;top:60%;left:80%;animation-duration:12s;animation-direction:reverse;"></div>
|
||||
|
||||
<div id="play-overlay">
|
||||
<div class="play-circle">▶</div>
|
||||
</div>
|
||||
|
||||
<div id="title">🔌 从零开发一个插件</div>
|
||||
|
||||
<div id="timeline">
|
||||
<div class="step" data-step="0">
|
||||
<div class="step-number">1</div>
|
||||
<h3>📁 创建插件目录</h3>
|
||||
<p>按照规范在 <code>store/@{作者名}/插件名/</code> 下创建目录</p>
|
||||
<code>
|
||||
store/@{myname}/hello-world/<br>
|
||||
├── main.py<br>
|
||||
├── manifest.json<br>
|
||||
└── README.md
|
||||
</code>
|
||||
</div>
|
||||
<div class="step" data-step="1">
|
||||
<div class="step-number">2</div>
|
||||
<h3>📝 编写 manifest.json</h3>
|
||||
<p>声明插件名称、版本、依赖和描述信息</p>
|
||||
<code>
|
||||
{<br>
|
||||
"name": "hello-world",<br>
|
||||
"version": "1.0.0",<br>
|
||||
"author": "@{myname}",<br>
|
||||
"description": "我的第一个插件"<br>
|
||||
}
|
||||
</code>
|
||||
</div>
|
||||
<div class="step" data-step="2">
|
||||
<div class="step-number">3</div>
|
||||
<h3>🐍 编写 main.py</h3>
|
||||
<p>实现插件的初始化逻辑,注册路由或事件</p>
|
||||
<code>
|
||||
class Plugin:<br>
|
||||
def init(self, app):<br>
|
||||
@app.route("/hello")<br>
|
||||
def hello():<br>
|
||||
return {"msg": "Hello, FutureOSS!"}
|
||||
</code>
|
||||
</div>
|
||||
<div class="step" data-step="3">
|
||||
<div class="step-number">4</div>
|
||||
<h3>🚀 启动 & 测试</h3>
|
||||
<p>运行项目,访问 <code>http://localhost:8080/hello</code> 验证插件是否正常工作</p>
|
||||
<code>
|
||||
bash start.sh<br>
|
||||
curl http://localhost:8080/hello<br>
|
||||
# → {"msg": "Hello, FutureOSS!"}
|
||||
</code>
|
||||
</div>
|
||||
<div class="step" data-step="4">
|
||||
<div class="step-number">5</div>
|
||||
<h3>📦 发布到商店</h3>
|
||||
<p>打包插件并上传到 Gitee 商店,其他人一键安装!</p>
|
||||
<code>
|
||||
python tools/sign_single_plugin.py @{myname}/hello-world<br>
|
||||
# 上传到 Gitee 商店仓库
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="progress"></div>
|
||||
<div id="controls">
|
||||
<button class="ctrl-btn" id="btn-replay" title="重播">↻</button>
|
||||
<button class="ctrl-btn" id="btn-sound" title="音效">🔊</button>
|
||||
<button class="ctrl-btn" id="btn-back" title="返回">←</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const AudioCtx = window.AudioContext || window.webkitAudioContext;
|
||||
let audioCtx = null, soundEnabled = true, bgMusic = null;
|
||||
function initAudio() { if (!audioCtx) audioCtx = new AudioCtx(); }
|
||||
function playBgMusic() {
|
||||
if (!soundEnabled || !audioCtx) return;
|
||||
stopBgMusic();
|
||||
const dur = 30, sr = audioCtx.sampleRate;
|
||||
const buf = audioCtx.createBuffer(2, sr * dur, sr);
|
||||
for (let ch = 0; ch < 2; ch++) {
|
||||
const d = buf.getChannelData(ch);
|
||||
for (let i = 0; i < d.length; i++) {
|
||||
const t = i / sr;
|
||||
d[i] = (Math.sin(2*Math.PI*262*t)*0.1 + Math.sin(2*Math.PI*330*t)*0.08 + Math.sin(2*Math.PI*392*t)*0.06) * Math.exp(-t%5);
|
||||
}
|
||||
}
|
||||
bgMusic = audioCtx.createBufferSource();
|
||||
bgMusic.buffer = buf; bgMusic.loop = true;
|
||||
const g = audioCtx.createGain(); g.gain.value = 0.12;
|
||||
bgMusic.connect(g).connect(audioCtx.destination);
|
||||
bgMusic.start();
|
||||
}
|
||||
function stopBgMusic() { if (bgMusic) { try{bgMusic.stop();}catch(e){} bgMusic=null; } }
|
||||
function playPop() {
|
||||
if (!soundEnabled || !audioCtx) return;
|
||||
const o = audioCtx.createOscillator(), g = audioCtx.createGain();
|
||||
o.type='sine'; o.frequency.setValueAtTime(900, audioCtx.currentTime);
|
||||
o.frequency.exponentialRampToValueAtTime(300, audioCtx.currentTime+0.15);
|
||||
g.gain.setValueAtTime(0.25, audioCtx.currentTime);
|
||||
g.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime+0.15);
|
||||
o.connect(g).connect(audioCtx.destination); o.start(); o.stop(audioCtx.currentTime+0.15);
|
||||
}
|
||||
document.getElementById('btn-sound').addEventListener('click', () => {
|
||||
soundEnabled=!soundEnabled;
|
||||
document.getElementById('btn-sound').textContent=soundEnabled?'🔊':'🔇';
|
||||
if(!soundEnabled) stopBgMusic(); else playBgMusic();
|
||||
});
|
||||
|
||||
const overlay = document.getElementById('play-overlay');
|
||||
const title = document.getElementById('title');
|
||||
const progress = document.getElementById('progress');
|
||||
const controls = document.getElementById('controls');
|
||||
const steps = document.querySelectorAll('.step');
|
||||
let animationTimer = null;
|
||||
|
||||
function setProgress(p) { progress.style.width = p + '%'; }
|
||||
|
||||
function startAnimation() {
|
||||
setProgress(0);
|
||||
steps.forEach(s => { s.classList.remove('active','prev'); });
|
||||
title.classList.remove('show');
|
||||
controls.classList.remove('show');
|
||||
overlay.classList.add('hidden');
|
||||
initAudio(); playBgMusic(); playPop();
|
||||
|
||||
setTimeout(() => { title.classList.add('show'); setProgress(5); }, 300);
|
||||
|
||||
steps.forEach((step, i) => {
|
||||
setTimeout(() => {
|
||||
steps.forEach((s, j) => {
|
||||
if (j < i) s.classList.add('prev'), s.classList.remove('active');
|
||||
else s.classList.remove('prev');
|
||||
});
|
||||
step.classList.add('active');
|
||||
playPop();
|
||||
setProgress(10 + (i + 1) * 18);
|
||||
}, 1000 + i * 3500);
|
||||
});
|
||||
|
||||
setTimeout(() => { controls.classList.add('show'); setProgress(100); }, 1000 + steps.length * 3500 + 500);
|
||||
}
|
||||
|
||||
overlay.addEventListener('click', startAnimation);
|
||||
document.getElementById('btn-replay').addEventListener('click', startAnimation);
|
||||
document.getElementById('btn-back').addEventListener('click', () => { stopBgMusic(); location.href='index.html'; });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user