first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
/* html-ppt :: animations.css
|
||||
* Apply by adding class="anim-<name>" or data-anim="<name>".
|
||||
* Durations are deliberately snappy; tweak --anim-dur per element.
|
||||
*/
|
||||
:root{--anim-dur:.7s;--anim-ease:cubic-bezier(.4,0,.2,1)}
|
||||
|
||||
/* ---------- FADE DIRECTIONALS ---------- */
|
||||
@keyframes kf-fade-up{from{opacity:0;transform:translateY(32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-down{from{opacity:0;transform:translateY(-32px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-left{from{opacity:0;transform:translateX(-40px)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-fade-right{from{opacity:0;transform:translateX(40px)}to{opacity:1;transform:none}}
|
||||
.anim-fade-up{animation:kf-fade-up var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-down{animation:kf-fade-down var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-left{animation:kf-fade-left var(--anim-dur) var(--anim-ease) both}
|
||||
.anim-fade-right{animation:kf-fade-right var(--anim-dur) var(--anim-ease) both}
|
||||
|
||||
/* ---------- RISE / DROP / ZOOM / BLUR / GLITCH ---------- */
|
||||
@keyframes kf-rise{from{opacity:0;transform:translateY(60px) scale(.97);filter:blur(6px)}to{opacity:1;transform:none;filter:none}}
|
||||
@keyframes kf-drop{from{opacity:0;transform:translateY(-60px) scale(.97)}to{opacity:1;transform:none}}
|
||||
@keyframes kf-zoom{0%{opacity:0;transform:scale(.6)}60%{transform:scale(1.04)}100%{opacity:1;transform:scale(1)}}
|
||||
@keyframes kf-blur{from{opacity:0;filter:blur(18px)}to{opacity:1;filter:none}}
|
||||
@keyframes kf-glitch{0%{opacity:0;transform:translateX(0);clip-path:inset(0 0 0 0)}
|
||||
20%{opacity:1;transform:translateX(-6px);clip-path:inset(20% 0 30% 0)}
|
||||
40%{transform:translateX(4px);clip-path:inset(50% 0 10% 0)}
|
||||
60%{transform:translateX(-3px);clip-path:inset(10% 0 60% 0)}
|
||||
80%{transform:translateX(2px);clip-path:inset(0 0 0 0)}
|
||||
100%{opacity:1;transform:none}}
|
||||
.anim-rise-in{animation:kf-rise .9s var(--anim-ease) both}
|
||||
.anim-drop-in{animation:kf-drop .8s var(--anim-ease) both}
|
||||
.anim-zoom-pop{animation:kf-zoom .7s cubic-bezier(.22,1.3,.36,1) both}
|
||||
.anim-blur-in{animation:kf-blur .8s var(--anim-ease) both}
|
||||
.anim-glitch-in{animation:kf-glitch .8s steps(5,end) both}
|
||||
|
||||
/* ---------- TYPEWRITER ---------- */
|
||||
.anim-typewriter{display:inline-block;overflow:hidden;white-space:nowrap;border-right:2px solid currentColor;
|
||||
width:0;animation:kf-type 2.4s steps(40,end) forwards, kf-caret 1s step-end infinite}
|
||||
@keyframes kf-type{to{width:100%}}
|
||||
@keyframes kf-caret{50%{border-color:transparent}}
|
||||
|
||||
/* ---------- GLOW / SHIMMER / GRADIENT-FLOW ---------- */
|
||||
@keyframes kf-neon{0%,100%{text-shadow:0 0 8px var(--accent),0 0 20px var(--accent)}
|
||||
50%{text-shadow:0 0 16px var(--accent),0 0 40px var(--accent),0 0 80px var(--accent)}}
|
||||
.anim-neon-glow{animation:kf-neon 2s ease-in-out infinite}
|
||||
|
||||
.anim-shimmer-sweep{position:relative;overflow:hidden}
|
||||
.anim-shimmer-sweep::after{content:"";position:absolute;inset:0;
|
||||
background:linear-gradient(110deg,transparent 40%,rgba(255,255,255,.55) 50%,transparent 60%);
|
||||
transform:translateX(-100%);animation:kf-shimmer 2.4s var(--anim-ease) infinite}
|
||||
@keyframes kf-shimmer{to{transform:translateX(100%)}}
|
||||
|
||||
.anim-gradient-flow{background:linear-gradient(90deg,var(--accent),var(--accent-2,var(--accent)),var(--accent-3,var(--accent)),var(--accent));
|
||||
background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
|
||||
animation:kf-gradflow 4s linear infinite}
|
||||
@keyframes kf-gradflow{to{background-position:300% 0}}
|
||||
|
||||
/* ---------- STAGGER LIST ---------- */
|
||||
.anim-stagger-list > *{opacity:0;animation:kf-rise .65s var(--anim-ease) both}
|
||||
.anim-stagger-list > *:nth-child(1){animation-delay:.05s}
|
||||
.anim-stagger-list > *:nth-child(2){animation-delay:.15s}
|
||||
.anim-stagger-list > *:nth-child(3){animation-delay:.25s}
|
||||
.anim-stagger-list > *:nth-child(4){animation-delay:.35s}
|
||||
.anim-stagger-list > *:nth-child(5){animation-delay:.45s}
|
||||
.anim-stagger-list > *:nth-child(6){animation-delay:.55s}
|
||||
.anim-stagger-list > *:nth-child(7){animation-delay:.65s}
|
||||
.anim-stagger-list > *:nth-child(8){animation-delay:.75s}
|
||||
.anim-stagger-list > *:nth-child(n+9){animation-delay:.85s}
|
||||
|
||||
/* ---------- COUNTER-UP (JS-driven, marker class only) ---------- */
|
||||
.counter{font-variant-numeric:tabular-nums}
|
||||
|
||||
/* ---------- SVG PATH DRAW ---------- */
|
||||
.anim-path-draw path,.anim-path-draw line,.anim-path-draw polyline,.anim-path-draw circle,.anim-path-draw rect{
|
||||
stroke-dasharray:1000;stroke-dashoffset:1000;animation:kf-draw 2s var(--anim-ease) forwards}
|
||||
@keyframes kf-draw{to{stroke-dashoffset:0}}
|
||||
|
||||
/* ---------- PARALLAX TILT (hover) ---------- */
|
||||
.anim-parallax-tilt{transform-style:preserve-3d;transition:transform .4s var(--anim-ease)}
|
||||
.anim-parallax-tilt:hover{transform:perspective(900px) rotateX(6deg) rotateY(-8deg) translateZ(10px)}
|
||||
|
||||
/* ---------- CARD FLIP 3D ---------- */
|
||||
@keyframes kf-flip{from{transform:perspective(1200px) rotateY(-90deg);opacity:0}
|
||||
to{transform:perspective(1200px) rotateY(0);opacity:1}}
|
||||
.anim-card-flip-3d{animation:kf-flip .9s var(--anim-ease) both;transform-style:preserve-3d;backface-visibility:hidden}
|
||||
|
||||
/* ---------- CUBE ROTATE 3D ---------- */
|
||||
@keyframes kf-cube{from{transform:perspective(1200px) rotateX(20deg) rotateY(-90deg) translateZ(-200px);opacity:0}
|
||||
to{transform:perspective(1200px) rotateX(0) rotateY(0) translateZ(0);opacity:1}}
|
||||
.anim-cube-rotate-3d{animation:kf-cube 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- PAGE TURN 3D ---------- */
|
||||
@keyframes kf-pageturn{from{transform:perspective(1600px) rotateY(-85deg);transform-origin:left center;opacity:0}
|
||||
to{transform:perspective(1600px) rotateY(0);opacity:1}}
|
||||
.anim-page-turn-3d{animation:kf-pageturn 1s var(--anim-ease) both;transform-origin:left center}
|
||||
|
||||
/* ---------- PERSPECTIVE ZOOM ---------- */
|
||||
@keyframes kf-pzoom{from{opacity:0;transform:perspective(1400px) translateZ(-400px) rotateX(12deg)}
|
||||
to{opacity:1;transform:none}}
|
||||
.anim-perspective-zoom{animation:kf-pzoom 1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MARQUEE SCROLL ---------- */
|
||||
.anim-marquee-scroll{display:flex;gap:48px;white-space:nowrap;animation:kf-marquee 20s linear infinite}
|
||||
@keyframes kf-marquee{from{transform:translateX(0)}to{transform:translateX(-50%)}}
|
||||
|
||||
/* ---------- KEN BURNS ---------- */
|
||||
@keyframes kf-kenburns{0%{transform:scale(1) translate(0,0)}100%{transform:scale(1.15) translate(-2%,-1%)}}
|
||||
.anim-kenburns{animation:kf-kenburns 14s ease-in-out infinite alternate}
|
||||
|
||||
/* ---------- CONFETTI BURST (pseudo — pure CSS sparkles) ---------- */
|
||||
.anim-confetti-burst{position:relative}
|
||||
.anim-confetti-burst::before,.anim-confetti-burst::after{
|
||||
content:"";position:absolute;top:50%;left:50%;width:8px;height:8px;border-radius:50%;
|
||||
background:var(--accent);box-shadow:
|
||||
20px -30px 0 var(--accent-2,var(--accent)),-25px -20px 0 var(--accent-3,var(--accent)),
|
||||
30px 20px 0 var(--good,#1aaf6c),-30px 25px 0 var(--warn,#f5a524),
|
||||
40px -10px 0 var(--bad,#e0445a),-45px 0 0 var(--accent),
|
||||
10px 40px 0 var(--accent-2,var(--accent)),-15px -40px 0 var(--accent-3,var(--accent));
|
||||
opacity:0;animation:kf-confetti 1.2s var(--anim-ease) forwards}
|
||||
.anim-confetti-burst::after{animation-delay:.15s;transform:rotate(45deg)}
|
||||
@keyframes kf-confetti{0%{opacity:0;transform:scale(.2)}30%{opacity:1}100%{opacity:0;transform:scale(2.2)}}
|
||||
|
||||
/* ---------- SPOTLIGHT ---------- */
|
||||
@keyframes kf-spot{0%{clip-path:circle(0% at 50% 50%)}100%{clip-path:circle(140% at 50% 50%)}}
|
||||
.anim-spotlight{animation:kf-spot 1.1s var(--anim-ease) both}
|
||||
|
||||
/* ---------- MORPH SHAPE (SVG) ---------- */
|
||||
.anim-morph-shape path{animation:kf-morph 6s ease-in-out infinite alternate}
|
||||
@keyframes kf-morph{0%{d:path("M60,120 Q120,20 180,120 T300,120")}
|
||||
100%{d:path("M60,120 Q120,220 180,120 T300,120")}}
|
||||
|
||||
/* ---------- RIPPLE REVEAL ---------- */
|
||||
@keyframes kf-ripple{0%{clip-path:circle(0% at 20% 80%);opacity:.4}
|
||||
100%{clip-path:circle(160% at 20% 80%);opacity:1}}
|
||||
.anim-ripple-reveal{animation:kf-ripple 1.2s var(--anim-ease) both}
|
||||
|
||||
/* reduced motion */
|
||||
@media (prefers-reduced-motion: reduce){
|
||||
[class*="anim-"]{animation:none!important;transition:none!important}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/* html-ppt :: fx-runtime.js
|
||||
* Canvas FX autoloader + lifecycle manager.
|
||||
* - Dynamically loads all fx modules listed in FX_LIST
|
||||
* - Initializes [data-fx] elements when their slide becomes active
|
||||
* - Calls handle.stop() when the slide leaves
|
||||
*/
|
||||
(function(){
|
||||
'use strict';
|
||||
|
||||
const FX_LIST = [
|
||||
'_util',
|
||||
'particle-burst','confetti-cannon','firework','starfield','matrix-rain',
|
||||
'knowledge-graph','neural-net','constellation','orbit-ring','galaxy-swirl',
|
||||
'word-cascade','letter-explode','chain-react','magnetic-field','data-stream',
|
||||
'gradient-blob','sparkle-trail','shockwave','typewriter-multi','counter-explosion'
|
||||
];
|
||||
|
||||
// Resolve base path of this script so it works from any page location.
|
||||
const myScript = document.currentScript || (function(){
|
||||
const all = document.getElementsByTagName('script');
|
||||
for (const s of all){ if (s.src && s.src.indexOf('fx-runtime.js')>-1) return s; }
|
||||
return null;
|
||||
})();
|
||||
const base = myScript ? myScript.src.replace(/fx-runtime\.js.*$/, 'fx/') : 'assets/animations/fx/';
|
||||
|
||||
let loaded = 0;
|
||||
const total = FX_LIST.length;
|
||||
const ready = new Promise((resolve) => {
|
||||
if (!total) return resolve();
|
||||
FX_LIST.forEach((name) => {
|
||||
const s = document.createElement('script');
|
||||
s.src = base + name + '.js';
|
||||
s.async = false;
|
||||
s.onload = s.onerror = () => { if (++loaded >= total) resolve(); };
|
||||
document.head.appendChild(s);
|
||||
});
|
||||
});
|
||||
|
||||
window.__hpxActive = window.__hpxActive || new Map();
|
||||
|
||||
function initFxIn(root){
|
||||
if (!window.HPX) return;
|
||||
const els = root.querySelectorAll('[data-fx]');
|
||||
els.forEach((el) => {
|
||||
if (window.__hpxActive.has(el)) return;
|
||||
const name = el.getAttribute('data-fx');
|
||||
const fn = window.HPX[name];
|
||||
if (typeof fn !== 'function') return;
|
||||
try {
|
||||
const handle = fn(el, {}) || { stop(){} };
|
||||
window.__hpxActive.set(el, handle);
|
||||
} catch(e){ console.warn('[hpx-fx]', name, e); }
|
||||
});
|
||||
}
|
||||
|
||||
function stopFxIn(root){
|
||||
const els = root.querySelectorAll('[data-fx]');
|
||||
els.forEach((el) => {
|
||||
const h = window.__hpxActive.get(el);
|
||||
if (h && typeof h.stop === 'function'){
|
||||
try{ h.stop(); }catch(e){}
|
||||
}
|
||||
window.__hpxActive.delete(el);
|
||||
});
|
||||
}
|
||||
|
||||
function reinitFxIn(root){
|
||||
stopFxIn(root);
|
||||
initFxIn(root);
|
||||
}
|
||||
window.__hpxReinit = reinitFxIn;
|
||||
|
||||
function boot(){
|
||||
ready.then(() => {
|
||||
const active = document.querySelector('.slide.is-active') || document.querySelector('.slide');
|
||||
if (active) initFxIn(active);
|
||||
|
||||
// Watch all slides for class changes
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
slides.forEach((sl) => {
|
||||
const mo = new MutationObserver((muts) => {
|
||||
for (const m of muts){
|
||||
if (m.attributeName === 'class'){
|
||||
if (sl.classList.contains('is-active')) initFxIn(sl);
|
||||
else stopFxIn(sl);
|
||||
}
|
||||
}
|
||||
});
|
||||
mo.observe(sl, { attributes: true, attributeFilter: ['class'] });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading'){
|
||||
document.addEventListener('DOMContentLoaded', boot);
|
||||
} else {
|
||||
boot();
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,63 @@
|
||||
/* html-ppt fx :: shared helpers */
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
const U = window.HPX._u = {};
|
||||
|
||||
U.css = (el, name, fb) => {
|
||||
const v = getComputedStyle(el).getPropertyValue(name).trim();
|
||||
return v || fb;
|
||||
};
|
||||
|
||||
U.accent = (el, fb) => U.css(el, '--accent', fb || '#7c5cff');
|
||||
U.accent2 = (el, fb) => U.css(el, '--accent-2', fb || '#22d3ee');
|
||||
U.text = (el, fb) => U.css(el, '--text-1', fb || '#eaeaf2');
|
||||
|
||||
U.palette = (el) => [
|
||||
U.accent(el, '#7c5cff'),
|
||||
U.accent2(el, '#22d3ee'),
|
||||
U.css(el, '--ok', '#22c55e'),
|
||||
U.css(el, '--warn', '#f59e0b'),
|
||||
U.css(el, '--danger', '#ef4444'),
|
||||
];
|
||||
|
||||
U.canvas = (el) => {
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const c = document.createElement('canvas');
|
||||
c.style.cssText = 'position:absolute;inset:0;width:100%;height:100%;pointer-events:none;display:block;';
|
||||
el.appendChild(c);
|
||||
const ctx = c.getContext('2d');
|
||||
let w = 0, h = 0, dpr = Math.max(1, Math.min(2, window.devicePixelRatio||1));
|
||||
const fit = () => {
|
||||
const r = el.getBoundingClientRect();
|
||||
w = Math.max(1, r.width|0);
|
||||
h = Math.max(1, r.height|0);
|
||||
c.width = (w*dpr)|0;
|
||||
c.height = (h*dpr)|0;
|
||||
ctx.setTransform(dpr,0,0,dpr,0,0);
|
||||
};
|
||||
fit();
|
||||
const ro = new ResizeObserver(fit);
|
||||
ro.observe(el);
|
||||
return {
|
||||
c, ctx,
|
||||
get w(){return w;}, get h(){return h;}, get dpr(){return dpr;},
|
||||
destroy(){
|
||||
try{ro.disconnect();}catch(e){}
|
||||
if (c.parentNode) c.parentNode.removeChild(c);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
U.loop = (fn) => {
|
||||
let raf = 0, stopped = false, t0 = performance.now();
|
||||
const tick = (t) => {
|
||||
if (stopped) return;
|
||||
fn((t - t0)/1000);
|
||||
raf = requestAnimationFrame(tick);
|
||||
};
|
||||
raf = requestAnimationFrame(tick);
|
||||
return () => { stopped = true; cancelAnimationFrame(raf); };
|
||||
};
|
||||
|
||||
U.rand = (a,b) => a + Math.random()*(b-a);
|
||||
})();
|
||||
@@ -0,0 +1,41 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['chain-react'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
const N = 8;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const cy = k.h/2;
|
||||
const pad = 60;
|
||||
const dx = (k.w - pad*2)/(N-1);
|
||||
const period = 2.4;
|
||||
const phase = (t % period) / period; // 0..1
|
||||
for (let i=0;i<N;i++){
|
||||
const x = pad + i*dx;
|
||||
const my = i/(N-1);
|
||||
const d = Math.abs(phase - my);
|
||||
const pulse = Math.max(0, 1 - d*6);
|
||||
const r = 18 + pulse*18;
|
||||
// glow
|
||||
const g = ctx.createRadialGradient(x,cy,0,x,cy,r*2);
|
||||
g.addColorStop(0, `rgba(124,92,255,${0.4*pulse})`);
|
||||
g.addColorStop(1, 'rgba(0,0,0,0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.fillRect(x-r*2, cy-r*2, r*4, r*4);
|
||||
// circle
|
||||
ctx.fillStyle = pulse>0.1 ? ac2 : ac;
|
||||
ctx.beginPath(); ctx.arc(x,cy,r,0,Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle='rgba(255,255,255,0.4)'; ctx.lineWidth=2;
|
||||
ctx.stroke();
|
||||
// connectors
|
||||
if (i<N-1){
|
||||
ctx.strokeStyle='rgba(200,200,230,0.3)'; ctx.lineWidth=2;
|
||||
ctx.beginPath(); ctx.moveTo(x+r,cy); ctx.lineTo(x+dx-r,cy); ctx.stroke();
|
||||
}
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,49 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['confetti-cannon'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let parts = [];
|
||||
const fire = () => {
|
||||
for (let side=0; side<2; side++){
|
||||
const x0 = side===0 ? 20 : k.w-20;
|
||||
const y0 = k.h - 20;
|
||||
for (let i=0;i<40;i++){
|
||||
const a = side===0 ? U.rand(-Math.PI*0.7, -Math.PI*0.4) : U.rand(-Math.PI*0.6, -Math.PI*0.3) - Math.PI/2 - Math.PI/6;
|
||||
const spd = U.rand(300, 520);
|
||||
parts.push({
|
||||
x: x0, y: y0,
|
||||
vx: Math.cos(a)*spd, vy: Math.sin(a)*spd,
|
||||
w: U.rand(6,12), h: U.rand(3,7),
|
||||
rot: Math.random()*Math.PI, vr: U.rand(-6,6),
|
||||
c: pal[(Math.random()*pal.length)|0],
|
||||
life: 1
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
fire();
|
||||
let last = 0;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - last > 3) { fire(); last = t; }
|
||||
const dt = 1/60;
|
||||
parts = parts.filter(p => p.life > 0 && p.y < k.h+40);
|
||||
for (const p of parts){
|
||||
p.vy += 520*dt;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.rot += p.vr*dt;
|
||||
p.life -= 0.006;
|
||||
ctx.save();
|
||||
ctx.translate(p.x, p.y); ctx.rotate(p.rot);
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.fillRect(-p.w/2, -p.h/2, p.w, p.h);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,44 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['constellation'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#9fb4ff');
|
||||
const N = 70;
|
||||
let pts = [];
|
||||
const seed = () => {
|
||||
pts = Array.from({length:N}, () => ({
|
||||
x: Math.random()*k.w, y: Math.random()*k.h,
|
||||
vx: U.rand(-0.3,0.3), vy: U.rand(-0.3,0.3)
|
||||
}));
|
||||
};
|
||||
seed();
|
||||
let lw=k.w, lh=k.h;
|
||||
const stop = U.loop(() => {
|
||||
if (k.w!==lw||k.h!==lh){ seed(); lw=k.w; lh=k.h; }
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
for (const p of pts){
|
||||
p.x += p.vx; p.y += p.vy;
|
||||
if (p.x<0||p.x>k.w) p.vx*=-1;
|
||||
if (p.y<0||p.y>k.h) p.vy*=-1;
|
||||
}
|
||||
for (let i=0;i<N;i++){
|
||||
for (let j=i+1;j<N;j++){
|
||||
const a=pts[i], b=pts[j];
|
||||
const d = Math.hypot(a.x-b.x, a.y-b.y);
|
||||
if (d < 150){
|
||||
ctx.globalAlpha = 1 - d/150;
|
||||
ctx.strokeStyle = ac; ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = ac;
|
||||
for (const p of pts){
|
||||
ctx.beginPath(); ctx.arc(p.x,p.y,1.8,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,58 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['counter-explosion'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const target = parseInt(el.getAttribute('data-fx-to') || '2400', 10);
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
// number overlay
|
||||
const num = document.createElement('div');
|
||||
num.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font:900 120px system-ui,sans-serif;color:var(--text-1,#fff);pointer-events:none;text-shadow:0 4px 40px rgba(124,92,255,0.5);';
|
||||
num.textContent = '0';
|
||||
el.appendChild(num);
|
||||
let parts = [];
|
||||
let state = 'count'; // count | burst | hold
|
||||
let stateT = 0;
|
||||
let value = 0;
|
||||
let cycle = 0;
|
||||
const burst = () => {
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
for (let i=0;i<120;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(120, 400);
|
||||
parts.push({x:cx,y:cy,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,r:U.rand(2,5),c:pal[(Math.random()*pal.length)|0]});
|
||||
}
|
||||
};
|
||||
const stop = U.loop(() => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const dt = 1/60;
|
||||
stateT += dt;
|
||||
if (state === 'count'){
|
||||
const dur = 2.2;
|
||||
const p = Math.min(1, stateT/dur);
|
||||
const eased = 1 - Math.pow(1-p,3);
|
||||
value = Math.round(target*eased);
|
||||
num.textContent = value.toLocaleString();
|
||||
if (p >= 1){ state='burst'; stateT=0; burst(); }
|
||||
} else if (state === 'burst'){
|
||||
if (stateT > 0.05 && stateT < 0.3 && parts.length < 200) {}
|
||||
if (stateT > 2.5){ state='hold'; stateT=0; }
|
||||
} else if (state === 'hold'){
|
||||
if (stateT > 1.5){
|
||||
state='count'; stateT=0; value=0; num.textContent='0'; cycle++;
|
||||
}
|
||||
}
|
||||
parts = parts.filter(p => p.life > 0);
|
||||
for (const p of parts){
|
||||
p.vy += 260*dt; p.vx *= 0.985; p.vy *= 0.985;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt; p.life -= 0.01;
|
||||
ctx.globalAlpha = Math.max(0,p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x,p.y,p.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); if (num.parentNode) num.parentNode.removeChild(num); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,45 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['data-stream'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#22d3ee'), ac2 = U.accent2(el,'#7c5cff');
|
||||
const rows = [];
|
||||
const rh = 22;
|
||||
const genRow = (y) => ({
|
||||
y, dir: Math.random()<0.5?-1:1,
|
||||
speed: U.rand(30, 90),
|
||||
offset: Math.random()*2000,
|
||||
text: Array.from({length:120}, () => {
|
||||
const r = Math.random();
|
||||
if (r<0.3) return Math.random()<0.5?'0':'1';
|
||||
if (r<0.6) return '0x' + Math.floor(Math.random()*256).toString(16).padStart(2,'0');
|
||||
return Math.random().toString(16).slice(2,6);
|
||||
}).join(' ')
|
||||
});
|
||||
const init = () => {
|
||||
rows.length = 0;
|
||||
const n = Math.ceil(k.h/rh);
|
||||
for (let i=0;i<n;i++) rows.push(genRow(i*rh + rh*0.7));
|
||||
};
|
||||
init();
|
||||
let lh = k.h;
|
||||
const stop = U.loop((t) => {
|
||||
if (k.h!==lh){ init(); lh=k.h; }
|
||||
ctx.fillStyle = 'rgba(5,8,14,0.35)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.font = '13px ui-monospace,Menlo,monospace';
|
||||
for (let i=0;i<rows.length;i++){
|
||||
const r = rows[i];
|
||||
const x = r.dir>0
|
||||
? ((t*r.speed + r.offset) % (k.w+400)) - 400
|
||||
: k.w - (((t*r.speed + r.offset) % (k.w+400)) - 400);
|
||||
ctx.fillStyle = (i%3===0)?ac:ac2;
|
||||
ctx.globalAlpha = 0.65 + (i%2)*0.3;
|
||||
ctx.fillText(r.text, x, r.y);
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,51 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['firework'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let rockets = [], sparks = [];
|
||||
const launch = () => {
|
||||
rockets.push({
|
||||
x: U.rand(k.w*0.2, k.w*0.8), y: k.h+10,
|
||||
vx: U.rand(-30,30), vy: U.rand(-520,-380),
|
||||
tgtY: U.rand(k.h*0.15, k.h*0.45),
|
||||
c: pal[(Math.random()*pal.length)|0]
|
||||
});
|
||||
};
|
||||
const burst = (x, y, c) => {
|
||||
const n = 70;
|
||||
for (let i=0;i<n;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(60, 240);
|
||||
sparks.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s,life:1,c});
|
||||
}
|
||||
};
|
||||
let last = -1;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.18)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.7) { launch(); last = t; }
|
||||
const dt = 1/60;
|
||||
rockets = rockets.filter(r => {
|
||||
r.x += r.vx*dt; r.y += r.vy*dt; r.vy += 260*dt;
|
||||
ctx.fillStyle = r.c;
|
||||
ctx.beginPath(); ctx.arc(r.x, r.y, 2.5, 0, Math.PI*2); ctx.fill();
|
||||
if (r.y <= r.tgtY || r.vy >= 0) { burst(r.x, r.y, r.c); return false; }
|
||||
return true;
|
||||
});
|
||||
sparks = sparks.filter(p => p.life > 0);
|
||||
for (const p of sparks){
|
||||
p.vy += 90*dt;
|
||||
p.vx *= 0.98; p.vy *= 0.98;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.life -= 0.012;
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, 2, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,33 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['galaxy-swirl'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const N = 800;
|
||||
const parts = Array.from({length:N}, (_,i) => {
|
||||
const arm = i%3;
|
||||
const t = Math.random();
|
||||
const r = t*180 + 8;
|
||||
const base = (arm/3)*Math.PI*2;
|
||||
return { r, a: base + Math.log(r+1)*1.6 + U.rand(-0.2,0.2),
|
||||
c: pal[arm%pal.length],
|
||||
s: U.rand(0.8, 2.2) };
|
||||
});
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.15)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
for (const p of parts){
|
||||
const a = p.a + t*0.15;
|
||||
const x = cx + Math.cos(a)*p.r;
|
||||
const y = cy + Math.sin(a)*p.r*0.7;
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.beginPath(); ctx.arc(x,y,p.s,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,39 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['gradient-blob'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const blobs = Array.from({length:4}, (_,i) => ({
|
||||
x: U.rand(0,1), y: U.rand(0,1),
|
||||
vx: U.rand(-0.08,0.08), vy: U.rand(-0.08,0.08),
|
||||
r: U.rand(180,320),
|
||||
c: pal[i%pal.length]
|
||||
}));
|
||||
const hex2rgb = (h) => {
|
||||
const m = h.replace('#','').match(/.{2}/g);
|
||||
if (!m) return [124,92,255];
|
||||
return m.map(x=>parseInt(x,16));
|
||||
};
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(10,12,22,0.2)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.globalCompositeOperation = 'lighter';
|
||||
for (const b of blobs){
|
||||
b.x += b.vx*0.01; b.y += b.vy*0.01;
|
||||
if (b.x<0||b.x>1) b.vx*=-1;
|
||||
if (b.y<0||b.y>1) b.vy*=-1;
|
||||
const px = b.x*k.w, py = b.y*k.h;
|
||||
const r = b.r + Math.sin(t*0.8 + b.x*6)*30;
|
||||
const [R,G,B] = hex2rgb(b.c);
|
||||
const grad = ctx.createRadialGradient(px,py,0,px,py,r);
|
||||
grad.addColorStop(0, `rgba(${R},${G},${B},0.55)`);
|
||||
grad.addColorStop(1, `rgba(${R},${G},${B},0)`);
|
||||
ctx.fillStyle = grad;
|
||||
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalCompositeOperation = 'source-over';
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,69 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['knowledge-graph'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const tx = U.text(el, '#e7e7ef');
|
||||
const labels = ['AI','ML','LLM','Graph','Node','Edge','Claude','GPT','RAG','Vector',
|
||||
'Embed','Neural','Agent','Tool','Memory','Logic','Data','Train','Infer','Token',
|
||||
'Prompt','Chain','Plan','Skill','Cloud','Edge','GPU','Code','Task','Flow'];
|
||||
const N = 28;
|
||||
const nodes = Array.from({length:N}, (_,i) => ({
|
||||
x: U.rand(40, 300), y: U.rand(40, 200),
|
||||
vx: 0, vy: 0, label: labels[i%labels.length],
|
||||
c: pal[i%pal.length]
|
||||
}));
|
||||
const edges = [];
|
||||
const made = new Set();
|
||||
while (edges.length < 50){
|
||||
const a = (Math.random()*N)|0, b = (Math.random()*N)|0;
|
||||
if (a===b) continue;
|
||||
const key = a<b ? a+'-'+b : b+'-'+a;
|
||||
if (made.has(key)) continue;
|
||||
made.add(key); edges.push([a,b]);
|
||||
}
|
||||
const stop = U.loop(() => {
|
||||
// physics
|
||||
for (let i=0;i<N;i++){
|
||||
for (let j=i+1;j<N;j++){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
const dx=b.x-a.x, dy=b.y-a.y;
|
||||
let d2=dx*dx+dy*dy; if (d2<1) d2=1;
|
||||
const d=Math.sqrt(d2);
|
||||
const f=1600/d2;
|
||||
const fx=(dx/d)*f, fy=(dy/d)*f;
|
||||
a.vx-=fx; a.vy-=fy; b.vx+=fx; b.vy+=fy;
|
||||
}
|
||||
}
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
const dx=b.x-a.x, dy=b.y-a.y, d=Math.hypot(dx,dy)||1;
|
||||
const f=(d-90)*0.008;
|
||||
const fx=(dx/d)*f, fy=(dy/d)*f;
|
||||
a.vx+=fx; a.vy+=fy; b.vx-=fx; b.vy-=fy;
|
||||
}
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
for (const n of nodes){
|
||||
n.vx += (cx-n.x)*0.002;
|
||||
n.vy += (cy-n.y)*0.002;
|
||||
n.vx *= 0.85; n.vy *= 0.85;
|
||||
n.x += n.vx; n.y += n.vy;
|
||||
}
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
ctx.strokeStyle = 'rgba(180,180,220,0.25)'; ctx.lineWidth=1;
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
ctx.font='11px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
|
||||
for (const n of nodes){
|
||||
ctx.fillStyle = n.c;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,7,0,Math.PI*2); ctx.fill();
|
||||
ctx.fillStyle = tx;
|
||||
ctx.fillText(n.label, n.x, n.y-14);
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,50 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['letter-explode'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const src = el.querySelector('[data-fx-text]') || el;
|
||||
const text = (el.getAttribute('data-fx-text-value') || src.textContent || 'EXPLODE').trim();
|
||||
// Build a container, hide source text
|
||||
const wrap = document.createElement('div');
|
||||
wrap.style.cssText = 'position:absolute;inset:0;display:flex;align-items:center;justify-content:center;pointer-events:none;';
|
||||
const inner = document.createElement('div');
|
||||
inner.style.cssText = 'font-size:64px;font-weight:900;letter-spacing:0.02em;color:var(--text-1,#fff);white-space:nowrap;';
|
||||
wrap.appendChild(inner);
|
||||
el.appendChild(wrap);
|
||||
const spans = [];
|
||||
for (const ch of text){
|
||||
const s = document.createElement('span');
|
||||
s.textContent = ch === ' ' ? '\u00A0' : ch;
|
||||
s.style.display='inline-block';
|
||||
s.style.transform='translate(0,0)';
|
||||
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
|
||||
s.style.opacity='0';
|
||||
inner.appendChild(s);
|
||||
spans.push(s);
|
||||
}
|
||||
let stopped = false;
|
||||
const run = () => {
|
||||
if (stopped) return;
|
||||
spans.forEach((s,i) => {
|
||||
const dx = U.rand(-400, 400), dy = U.rand(-300, 300);
|
||||
s.style.transition='none';
|
||||
s.style.transform=`translate(${dx}px,${dy}px) rotate(${U.rand(-180,180)}deg)`;
|
||||
s.style.opacity='0';
|
||||
});
|
||||
// force reflow
|
||||
void inner.offsetWidth;
|
||||
spans.forEach((s,i) => {
|
||||
setTimeout(() => {
|
||||
if (stopped) return;
|
||||
s.style.transition='transform 900ms cubic-bezier(.2,.9,.3,1), opacity 900ms';
|
||||
s.style.transform='translate(0,0) rotate(0deg)';
|
||||
s.style.opacity='1';
|
||||
}, i*35);
|
||||
});
|
||||
};
|
||||
run();
|
||||
const iv = setInterval(run, 4500);
|
||||
return { stop(){ stopped=true; clearInterval(iv); if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,40 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['magnetic-field'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const N = 60;
|
||||
const parts = Array.from({length:N}, (_,i) => ({
|
||||
phase: Math.random()*Math.PI*2,
|
||||
freq: U.rand(0.4, 1.2),
|
||||
amp: U.rand(30, 90),
|
||||
y0: U.rand(0.15, 0.85),
|
||||
c: pal[i%pal.length],
|
||||
trail: []
|
||||
}));
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.08)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
for (const p of parts){
|
||||
const x = ((t*80 + p.phase*50) % (k.w+100)) - 50;
|
||||
const y = k.h*p.y0 + Math.sin(x*0.02 + p.phase + t*p.freq)*p.amp;
|
||||
p.trail.push([x,y]);
|
||||
if (p.trail.length > 18) p.trail.shift();
|
||||
ctx.strokeStyle = p.c;
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
for (let i=0;i<p.trail.length;i++){
|
||||
const [tx,ty] = p.trail[i];
|
||||
if (i===0) ctx.moveTo(tx,ty); else ctx.lineTo(tx,ty);
|
||||
}
|
||||
ctx.globalAlpha = 0.7;
|
||||
ctx.stroke();
|
||||
ctx.globalAlpha = 1;
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(x,y,2.5,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,33 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['matrix-rain'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const glyphs = 'アイウエオカキクケコサシスセソタチツテトナニヌネノ0123456789ABCDEF'.split('');
|
||||
const fs = 16;
|
||||
let cols = 0, drops = [];
|
||||
const init = () => {
|
||||
cols = Math.ceil(k.w/fs);
|
||||
drops = Array.from({length:cols}, () => U.rand(-20, 0));
|
||||
};
|
||||
init();
|
||||
let lw = k.w, lh = k.h;
|
||||
const stop = U.loop(() => {
|
||||
if (k.w!==lw || k.h!==lh){ init(); lw=k.w; lh=k.h; }
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.08)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
ctx.font = fs+'px monospace';
|
||||
for (let i=0;i<cols;i++){
|
||||
const ch = glyphs[(Math.random()*glyphs.length)|0];
|
||||
const x = i*fs, y = drops[i]*fs;
|
||||
ctx.fillStyle = '#9fffc9';
|
||||
ctx.fillText(ch, x, y);
|
||||
ctx.fillStyle = '#00ff6a';
|
||||
ctx.fillText(ch, x, y - fs);
|
||||
drops[i] += 1;
|
||||
if (y > k.h && Math.random() > 0.975) drops[i] = 0;
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,75 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['neural-net'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
const layers = [4,6,6,3];
|
||||
let nodes = [], edges = [], pulses = [];
|
||||
const layout = () => {
|
||||
nodes = [];
|
||||
const pad = 40;
|
||||
const cw = k.w - pad*2, ch = k.h - pad*2;
|
||||
for (let L=0; L<layers.length; L++){
|
||||
const x = pad + (cw * L / (layers.length-1));
|
||||
const n = layers[L];
|
||||
for (let i=0;i<n;i++){
|
||||
const y = pad + (ch * (i+0.5) / n);
|
||||
nodes.push({x,y,L,i});
|
||||
}
|
||||
}
|
||||
edges = [];
|
||||
for (let L=0; L<layers.length-1; L++){
|
||||
const a = nodes.filter(n=>n.L===L), b = nodes.filter(n=>n.L===L+1);
|
||||
for (const x of a) for (const y of b) edges.push([nodes.indexOf(x),nodes.indexOf(y)]);
|
||||
}
|
||||
};
|
||||
layout();
|
||||
let lw=k.w, lh=k.h, last=0;
|
||||
const stop = U.loop((t) => {
|
||||
if (k.w!==lw||k.h!==lh){ layout(); lw=k.w; lh=k.h; }
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
ctx.strokeStyle = 'rgba(160,160,200,0.22)'; ctx.lineWidth=1;
|
||||
for (const [i,j] of edges){
|
||||
const a=nodes[i], b=nodes[j];
|
||||
ctx.beginPath(); ctx.moveTo(a.x,a.y); ctx.lineTo(b.x,b.y); ctx.stroke();
|
||||
}
|
||||
if (t - last > 0.25){
|
||||
last = t;
|
||||
const starts = nodes.filter(n=>n.L===0);
|
||||
const s = starts[(Math.random()*starts.length)|0];
|
||||
pulses.push({node:s, L:0, t:0});
|
||||
}
|
||||
pulses = pulses.filter(p => p.L < layers.length-1);
|
||||
for (const p of pulses){
|
||||
p.t += 0.03;
|
||||
if (p.t >= 1){
|
||||
const next = nodes.filter(n=>n.L===p.L+1);
|
||||
p.node2 = next[(Math.random()*next.length)|0];
|
||||
if (!p._started){ p._started = true; }
|
||||
}
|
||||
}
|
||||
// animate progression
|
||||
for (const p of pulses){
|
||||
if (!p.target){
|
||||
const next = nodes.filter(n=>n.L===p.L+1);
|
||||
p.target = next[(Math.random()*next.length)|0];
|
||||
}
|
||||
p.t += 0.04;
|
||||
const a = p.node, b = p.target;
|
||||
const x = a.x + (b.x-a.x)*Math.min(1,p.t);
|
||||
const y = a.y + (b.y-a.y)*Math.min(1,p.t);
|
||||
ctx.fillStyle = ac2;
|
||||
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
|
||||
if (p.t >= 1){ p.node = b; p.target=null; p.L++; p.t=0; }
|
||||
}
|
||||
for (const n of nodes){
|
||||
ctx.fillStyle = ac;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,6,0,Math.PI*2); ctx.fill();
|
||||
ctx.strokeStyle = ac2; ctx.lineWidth=1.5;
|
||||
ctx.beginPath(); ctx.arc(n.x,n.y,8,0,Math.PI*2); ctx.stroke();
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,38 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['orbit-ring'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const rings = [
|
||||
{r:40, n:3, sp:1.2, c:pal[0]},
|
||||
{r:75, n:5, sp:0.8, c:pal[1]},
|
||||
{r:110, n:8, sp:-0.6, c:pal[2]},
|
||||
{r:145, n:12, sp:0.4, c:pal[3]},
|
||||
{r:180, n:16, sp:-0.3, c:pal[4]}
|
||||
];
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
// radial glow
|
||||
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,210);
|
||||
g.addColorStop(0,'rgba(124,92,255,0.25)');
|
||||
g.addColorStop(1,'rgba(0,0,0,0)');
|
||||
ctx.fillStyle = g; ctx.fillRect(0,0,k.w,k.h);
|
||||
for (const R of rings){
|
||||
ctx.strokeStyle = 'rgba(200,200,230,0.2)'; ctx.lineWidth=1;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,R.r,0,Math.PI*2); ctx.stroke();
|
||||
for (let i=0;i<R.n;i++){
|
||||
const a = (i/R.n)*Math.PI*2 + t*R.sp;
|
||||
const x = cx + Math.cos(a)*R.r;
|
||||
const y = cy + Math.sin(a)*R.r;
|
||||
ctx.fillStyle = R.c;
|
||||
ctx.beginPath(); ctx.arc(x,y,4,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.beginPath(); ctx.arc(cx,cy,5,0,Math.PI*2); ctx.fill();
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,42 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['particle-burst'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
let parts = [];
|
||||
const spawn = () => {
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
const n = 90;
|
||||
for (let i=0;i<n;i++){
|
||||
const a = Math.random()*Math.PI*2;
|
||||
const s = U.rand(80, 260);
|
||||
parts.push({
|
||||
x: cx, y: cy,
|
||||
vx: Math.cos(a)*s, vy: Math.sin(a)*s,
|
||||
life: 1, r: U.rand(2,5),
|
||||
c: pal[(Math.random()*pal.length)|0]
|
||||
});
|
||||
}
|
||||
};
|
||||
spawn();
|
||||
let lastSpawn = 0;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - lastSpawn > 2.5) { spawn(); lastSpawn = t; }
|
||||
const dt = 1/60;
|
||||
parts = parts.filter(p => p.life > 0);
|
||||
for (const p of parts){
|
||||
p.vy += 220*dt;
|
||||
p.vx *= 0.985; p.vy *= 0.985;
|
||||
p.x += p.vx*dt; p.y += p.vy*dt;
|
||||
p.life -= 0.012;
|
||||
ctx.globalAlpha = Math.max(0, p.life);
|
||||
ctx.fillStyle = p.c;
|
||||
ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,39 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['shockwave'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const ac = U.accent(el,'#7c5cff'), ac2 = U.accent2(el,'#22d3ee');
|
||||
let waves = [];
|
||||
let last = -1;
|
||||
const stop = U.loop((t) => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.12)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.6){ last = t; waves.push({t:0}); }
|
||||
const cx=k.w/2, cy=k.h/2;
|
||||
const max = Math.hypot(k.w,k.h)/2;
|
||||
waves = waves.filter(w => w.t < 1);
|
||||
for (const w of waves){
|
||||
w.t += 0.012;
|
||||
const r = w.t * max;
|
||||
const alpha = 1 - w.t;
|
||||
ctx.strokeStyle = w.t<0.5?ac2:ac;
|
||||
ctx.globalAlpha = alpha;
|
||||
ctx.lineWidth = 3 + (1-w.t)*3;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,r,0,Math.PI*2); ctx.stroke();
|
||||
ctx.strokeStyle = '#fff';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.globalAlpha = alpha*0.4;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,r*0.92,0,Math.PI*2); ctx.stroke();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
// core
|
||||
const g = ctx.createRadialGradient(cx,cy,0,cx,cy,40);
|
||||
g.addColorStop(0,'rgba(255,255,255,0.9)');
|
||||
g.addColorStop(1,'rgba(124,92,255,0)');
|
||||
ctx.fillStyle = g;
|
||||
ctx.beginPath(); ctx.arc(cx,cy,40,0,Math.PI*2); ctx.fill();
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,62 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['sparkle-trail'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
k.c.style.pointerEvents = 'none';
|
||||
el.style.cursor = 'crosshair';
|
||||
const pal = U.palette(el);
|
||||
let sparks = [];
|
||||
const onMove = (e) => {
|
||||
const r = el.getBoundingClientRect();
|
||||
const x = e.clientX - r.left, y = e.clientY - r.top;
|
||||
for (let i=0;i<3;i++){
|
||||
sparks.push({
|
||||
x, y,
|
||||
vx: U.rand(-60,60), vy: U.rand(-80,20),
|
||||
life: 1, c: pal[(Math.random()*pal.length)|0],
|
||||
r: U.rand(1.5,3.5)
|
||||
});
|
||||
}
|
||||
};
|
||||
// auto-wiggle if no mouse moves
|
||||
let auto = true, autoT = 0;
|
||||
const onAny = () => { auto = false; };
|
||||
el.addEventListener('pointermove', onMove);
|
||||
el.addEventListener('pointerenter', onAny);
|
||||
const stop = U.loop(() => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.15)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
if (auto){
|
||||
autoT += 0.04;
|
||||
const x = k.w/2 + Math.cos(autoT)*k.w*0.3;
|
||||
const y = k.h/2 + Math.sin(autoT*1.3)*k.h*0.3;
|
||||
for (let i=0;i<3;i++){
|
||||
sparks.push({
|
||||
x, y,
|
||||
vx: U.rand(-60,60), vy: U.rand(-80,20),
|
||||
life: 1, c: pal[(Math.random()*pal.length)|0],
|
||||
r: U.rand(1.5,3.5)
|
||||
});
|
||||
}
|
||||
}
|
||||
const dt = 1/60;
|
||||
sparks = sparks.filter(s => s.life > 0);
|
||||
for (const s of sparks){
|
||||
s.vy += 160*dt;
|
||||
s.x += s.vx*dt; s.y += s.vy*dt;
|
||||
s.life -= 0.018;
|
||||
ctx.globalAlpha = Math.max(0, s.life);
|
||||
ctx.fillStyle = s.c;
|
||||
ctx.beginPath(); ctx.arc(s.x,s.y,s.r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){
|
||||
el.removeEventListener('pointermove', onMove);
|
||||
el.removeEventListener('pointerenter', onAny);
|
||||
el.style.cursor = '';
|
||||
stop(); k.destroy();
|
||||
}};
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,30 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['starfield'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const tx = U.text(el, '#ffffff');
|
||||
const N = 260;
|
||||
const stars = Array.from({length:N}, () => ({
|
||||
x: U.rand(-1,1), y: U.rand(-1,1), z: Math.random()
|
||||
}));
|
||||
const stop = U.loop(() => {
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.25)';
|
||||
ctx.fillRect(0,0,k.w,k.h);
|
||||
const cx = k.w/2, cy = k.h/2;
|
||||
for (const s of stars){
|
||||
s.z -= 0.006;
|
||||
if (s.z <= 0.02) { s.x = U.rand(-1,1); s.y = U.rand(-1,1); s.z = 1; }
|
||||
const px = cx + (s.x/s.z)*cx;
|
||||
const py = cy + (s.y/s.z)*cy;
|
||||
if (px<0||py<0||px>k.w||py>k.h) continue;
|
||||
const r = (1-s.z)*2.4;
|
||||
ctx.globalAlpha = 1-s.z;
|
||||
ctx.fillStyle = tx;
|
||||
ctx.beginPath(); ctx.arc(px,py,r,0,Math.PI*2); ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,51 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['typewriter-multi'] = function(el){
|
||||
if (getComputedStyle(el).position === 'static') el.style.position = 'relative';
|
||||
const lines = [
|
||||
(el.getAttribute('data-fx-line1') || '> initializing knowledge graph...'),
|
||||
(el.getAttribute('data-fx-line2') || '> loading 28 concept nodes'),
|
||||
(el.getAttribute('data-fx-line3') || '> agent ready. awaiting prompt_'),
|
||||
];
|
||||
const wrap = document.createElement('div');
|
||||
wrap.style.cssText = 'position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:14px;padding:32px 48px;font:600 22px ui-monospace,Menlo,monospace;color:var(--text-1,#e7e7ef);';
|
||||
el.appendChild(wrap);
|
||||
const rows = lines.map((txt) => {
|
||||
const row = document.createElement('div');
|
||||
row.style.cssText = 'white-space:pre;display:flex;align-items:center;';
|
||||
const span = document.createElement('span'); span.textContent = '';
|
||||
const cur = document.createElement('span');
|
||||
cur.textContent = '\u2588';
|
||||
cur.style.cssText = 'display:inline-block;margin-left:2px;color:var(--accent,#22d3ee);animation:hpxBlink 1s steps(2) infinite;';
|
||||
row.appendChild(span); row.appendChild(cur);
|
||||
wrap.appendChild(row);
|
||||
return {row, span, txt, i:0};
|
||||
});
|
||||
// inject blink keyframes once
|
||||
if (!document.getElementById('hpx-blink-kf')){
|
||||
const st = document.createElement('style');
|
||||
st.id = 'hpx-blink-kf';
|
||||
st.textContent = '@keyframes hpxBlink{50%{opacity:0}}';
|
||||
document.head.appendChild(st);
|
||||
}
|
||||
let stopped = false;
|
||||
const speeds = [55, 70, 45];
|
||||
rows.forEach((r, idx) => {
|
||||
const tick = () => {
|
||||
if (stopped) return;
|
||||
if (r.i < r.txt.length){
|
||||
r.span.textContent += r.txt[r.i++];
|
||||
setTimeout(tick, speeds[idx]);
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
if (stopped) return;
|
||||
r.i = 0; r.span.textContent = '';
|
||||
tick();
|
||||
}, 2200);
|
||||
}
|
||||
};
|
||||
setTimeout(tick, idx*400);
|
||||
});
|
||||
return { stop(){ stopped = true; if (wrap.parentNode) wrap.parentNode.removeChild(wrap); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,47 @@
|
||||
(function(){
|
||||
window.HPX = window.HPX || {};
|
||||
window.HPX['word-cascade'] = function(el){
|
||||
const U = window.HPX._u;
|
||||
const k = U.canvas(el), ctx = k.ctx;
|
||||
const pal = U.palette(el);
|
||||
const WORDS = ['AI','知识','Graph','Claude','LLM','Agent','Vector','RAG','Token','神经',
|
||||
'Prompt','Chain','Skill','Code','Cloud','GPU','Flow','推理','Data','Model'];
|
||||
let items = [];
|
||||
let last = -1;
|
||||
let piles = {}; // column -> stack height
|
||||
const stop = U.loop((t) => {
|
||||
ctx.clearRect(0,0,k.w,k.h);
|
||||
if (t - last > 0.18){
|
||||
last = t;
|
||||
const w = WORDS[(Math.random()*WORDS.length)|0];
|
||||
items.push({
|
||||
text: w, x: U.rand(40, k.w-40), y: -20,
|
||||
vy: 0, c: pal[(Math.random()*pal.length)|0],
|
||||
size: U.rand(16,26), landed: false
|
||||
});
|
||||
}
|
||||
ctx.textAlign='center'; ctx.textBaseline='middle';
|
||||
for (const it of items){
|
||||
if (!it.landed){
|
||||
it.vy += 0.4;
|
||||
it.y += it.vy;
|
||||
const col = Math.round(it.x/60);
|
||||
const floor = k.h - (piles[col]||0) - it.size*0.6;
|
||||
if (it.y >= floor){
|
||||
it.y = floor; it.landed = true;
|
||||
piles[col] = (piles[col]||0) + it.size*1.1;
|
||||
if ((piles[col]||0) > k.h*0.8) piles[col] = 0; // reset if too high
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = it.c;
|
||||
ctx.font = `700 ${it.size}px system-ui,sans-serif`;
|
||||
ctx.fillText(it.text, it.x, it.y);
|
||||
}
|
||||
// prune old landed
|
||||
if (items.length > 120){
|
||||
items = items.filter(i => !i.landed).concat(items.filter(i=>i.landed).slice(-60));
|
||||
}
|
||||
});
|
||||
return { stop(){ stop(); k.destroy(); } };
|
||||
};
|
||||
})();
|
||||
@@ -0,0 +1,150 @@
|
||||
/* html-ppt :: base.css — reset + shared tokens + layout primitives */
|
||||
/* Default tokens. Themes in assets/themes/*.css override the :root block. */
|
||||
:root {
|
||||
--bg: #ffffff;
|
||||
--bg-soft: #f7f7f8;
|
||||
--surface: #ffffff;
|
||||
--surface-2: #f2f2f4;
|
||||
--border: rgba(0,0,0,.08);
|
||||
--border-strong: rgba(0,0,0,.16);
|
||||
--text-1: #111216;
|
||||
--text-2: #55596a;
|
||||
--text-3: #8a8f9e;
|
||||
--accent: #3b6cff;
|
||||
--accent-2: #7a5cff;
|
||||
--accent-3: #ff5c8a;
|
||||
--good: #1aaf6c;
|
||||
--warn: #f5a524;
|
||||
--bad: #e0445a;
|
||||
--grad: linear-gradient(135deg,#3b6cff,#7a5cff 55%,#ff5c8a);
|
||||
--grad-soft: linear-gradient(135deg,#eef2ff,#f5ecff 55%,#ffeef5);
|
||||
--radius: 18px;
|
||||
--radius-sm: 12px;
|
||||
--radius-lg: 26px;
|
||||
--shadow: 0 10px 30px rgba(18,24,40,.08), 0 2px 6px rgba(18,24,40,.04);
|
||||
--shadow-lg: 0 24px 60px rgba(18,24,40,.14), 0 6px 16px rgba(18,24,40,.06);
|
||||
--font-sans: 'Inter','Noto Sans SC',-apple-system,BlinkMacSystemFont,Helvetica,Arial,sans-serif;
|
||||
--font-serif: 'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-mono: 'JetBrains Mono','IBM Plex Mono',SFMono-Regular,Menlo,monospace;
|
||||
--font-display: var(--font-sans);
|
||||
--letter-tight: -.03em;
|
||||
--letter-normal: -.01em;
|
||||
--ease: cubic-bezier(.4,0,.2,1);
|
||||
}
|
||||
|
||||
*,*::before,*::after{box-sizing:border-box}
|
||||
html,body{margin:0;padding:0;background:var(--bg);color:var(--text-1);
|
||||
font-family:var(--font-sans);font-weight:400;line-height:1.6;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
letter-spacing:var(--letter-normal)}
|
||||
img,svg,video{max-width:100%;display:block}
|
||||
a{color:var(--accent);text-decoration:none}
|
||||
a:hover{text-decoration:underline}
|
||||
code,kbd,pre,samp{font-family:var(--font-mono)}
|
||||
|
||||
/* ================= SLIDE SYSTEM ================= */
|
||||
.deck{position:relative;width:100vw;height:100vh;overflow:hidden;background:var(--bg)}
|
||||
.slide{
|
||||
position:absolute;inset:0;
|
||||
display:flex;flex-direction:column;justify-content:center;
|
||||
padding:72px 96px;
|
||||
box-sizing:border-box;
|
||||
opacity:0;pointer-events:none;
|
||||
transition:opacity .5s var(--ease), transform .5s var(--ease);
|
||||
transform:translateX(30px);
|
||||
overflow:hidden;
|
||||
}
|
||||
.slide.is-active{opacity:1;pointer-events:auto;transform:translateX(0);z-index:2}
|
||||
.slide.is-prev{transform:translateX(-30px)}
|
||||
|
||||
/* single-page standalone (used when a layout file is opened directly) */
|
||||
body.single .slide{position:relative;width:100vw;height:100vh;opacity:1;transform:none;pointer-events:auto}
|
||||
|
||||
/* ================= TYPOGRAPHY ================= */
|
||||
.eyebrow{font-size:13px;font-weight:500;letter-spacing:.16em;text-transform:uppercase;color:var(--text-3)}
|
||||
.kicker{font-size:14px;font-weight:600;color:var(--accent);letter-spacing:.08em;text-transform:uppercase}
|
||||
h1.title,.h1{font-family:var(--font-display);font-size:72px;line-height:1.05;font-weight:800;letter-spacing:var(--letter-tight);margin:0 0 18px;color:var(--text-1)}
|
||||
h2.title,.h2{font-family:var(--font-display);font-size:54px;line-height:1.1;font-weight:700;letter-spacing:var(--letter-tight);margin:0 0 14px}
|
||||
h3,.h3{font-size:32px;line-height:1.2;font-weight:600;letter-spacing:var(--letter-normal);margin:0 0 10px}
|
||||
h4,.h4{font-size:22px;line-height:1.3;font-weight:600;margin:0 0 8px}
|
||||
.lede{font-size:22px;line-height:1.55;color:var(--text-2);font-weight:300;max-width:62ch}
|
||||
.dim{color:var(--text-2)}
|
||||
.dim2{color:var(--text-3)}
|
||||
.mono{font-family:var(--font-mono)}
|
||||
.serif{font-family:var(--font-serif)}
|
||||
.gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
|
||||
/* ================= LAYOUT PRIMITIVES ================= */
|
||||
.stack>*+*{margin-top:14px}
|
||||
.row{display:flex;gap:24px;align-items:center}
|
||||
.row.wrap{flex-wrap:wrap}
|
||||
.grid{display:grid;gap:24px}
|
||||
.g2{grid-template-columns:repeat(2,1fr)}
|
||||
.g3{grid-template-columns:repeat(3,1fr)}
|
||||
.g4{grid-template-columns:repeat(4,1fr)}
|
||||
.center{display:flex;align-items:center;justify-content:center;text-align:center}
|
||||
.fill{flex:1}
|
||||
.sp-t{padding-top:24px}.sp-b{padding-bottom:24px}
|
||||
.mt-s{margin-top:8px}.mt-m{margin-top:18px}.mt-l{margin-top:32px}
|
||||
.mb-s{margin-bottom:8px}.mb-m{margin-bottom:18px}.mb-l{margin-bottom:32px}
|
||||
|
||||
/* ================= CARDS ================= */
|
||||
.card{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
|
||||
padding:26px 28px;box-shadow:var(--shadow);position:relative;overflow:hidden}
|
||||
.card-soft{background:var(--surface-2);border:1px solid var(--border)}
|
||||
.card-outline{background:transparent;border:1.5px solid var(--border-strong);box-shadow:none}
|
||||
.card-accent{background:var(--surface);border-top:3px solid var(--accent)}
|
||||
.card-hover{transition:transform .3s var(--ease),box-shadow .3s var(--ease)}
|
||||
.card-hover:hover{transform:translateY(-4px);box-shadow:var(--shadow-lg)}
|
||||
|
||||
.pill{display:inline-block;padding:4px 12px;border-radius:999px;font-size:12px;font-weight:500;
|
||||
background:var(--surface-2);color:var(--text-2);border:1px solid var(--border)}
|
||||
.pill-accent{background:color-mix(in srgb,var(--accent) 12%,transparent);color:var(--accent);border-color:color-mix(in srgb,var(--accent) 28%,transparent)}
|
||||
|
||||
/* ================= BARS / DIVIDERS ================= */
|
||||
.divider{height:1px;background:var(--border);width:100%}
|
||||
.divider-accent{height:3px;width:72px;background:var(--accent);border-radius:2px}
|
||||
|
||||
/* ================= CHROME (header/footer/progress) ================= */
|
||||
.deck-header{position:absolute;top:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);letter-spacing:.12em;text-transform:uppercase;z-index:10;pointer-events:none}
|
||||
.deck-footer{position:absolute;bottom:24px;left:40px;right:40px;display:flex;align-items:center;justify-content:space-between;
|
||||
font-size:12px;color:var(--text-3);z-index:10;pointer-events:none}
|
||||
.slide-number::before{content:attr(data-current)}
|
||||
.slide-number::after{content:" / " attr(data-total)}
|
||||
.progress-bar{position:fixed;left:0;right:0;bottom:0;height:3px;background:transparent;z-index:20}
|
||||
.progress-bar > span{display:block;height:100%;width:0;background:var(--accent);transition:width .3s var(--ease)}
|
||||
|
||||
/* ================= PRESENTER / OVERVIEW ================= */
|
||||
.notes{display:none!important}
|
||||
.notes-overlay{position:fixed;inset:auto 0 0 0;max-height:42vh;background:rgba(20,22,30,.95);color:#e8ebf4;
|
||||
padding:20px 32px;font-size:16px;line-height:1.6;border-top:1px solid rgba(255,255,255,.1);transform:translateY(100%);
|
||||
transition:transform .3s var(--ease);z-index:40;overflow:auto;font-family:var(--font-sans)}
|
||||
.notes-overlay.open{transform:translateY(0)}
|
||||
.overview{position:fixed;inset:0;background:rgba(10,12,18,.92);backdrop-filter:blur(12px);z-index:50;
|
||||
display:none;padding:40px;overflow:auto}
|
||||
.overview.open{display:grid;grid-template-columns:repeat(4,1fr);gap:20px;align-content:start}
|
||||
.overview .thumb{background:var(--surface);border:1px solid var(--border);border-radius:12px;
|
||||
aspect-ratio:16/9;overflow:hidden;cursor:pointer;position:relative;color:var(--text-1);padding:16px;
|
||||
font-size:11px;transition:transform .2s var(--ease)}
|
||||
.overview .thumb:hover{transform:scale(1.04)}
|
||||
.overview .thumb .n{position:absolute;top:8px;left:10px;font-weight:700;font-size:14px;color:var(--text-3)}
|
||||
.overview .thumb .t{position:absolute;bottom:10px;left:14px;right:14px;font-weight:600;color:var(--text-1)}
|
||||
|
||||
/* ================= PRESENTER VIEW ================= */
|
||||
/* Presenter view opens in a separate popup window (S key).
|
||||
* All presenter styles are self-contained in the popup HTML generated by runtime.js.
|
||||
* The audience window (this file) is NOT affected — it stays as normal deck view.
|
||||
* Only the .notes class below is needed to hide speaker notes from audience. */
|
||||
|
||||
/* ================= UTILITY ================= */
|
||||
.hidden{display:none!important}
|
||||
.nowrap{white-space:nowrap}
|
||||
.tr{text-align:right}.tc{text-align:center}.tl{text-align:left}
|
||||
.uppercase{text-transform:uppercase;letter-spacing:.12em}
|
||||
|
||||
/* ================= PRINT ================= */
|
||||
@media print{
|
||||
.slide{position:relative;opacity:1!important;transform:none!important;page-break-after:always;height:100vh}
|
||||
.deck-header,.deck-footer,.progress-bar,.notes-overlay,.overview{display:none!important}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* html-ppt :: shared webfonts */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@200;300;400;500;600;700;900&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@300;400;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,800;1,400&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@300;400;500;700&display=swap');
|
||||
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&display=swap');
|
||||
@@ -0,0 +1,960 @@
|
||||
/* html-ppt :: runtime.js
|
||||
* Keyboard-driven deck runtime. Zero dependencies.
|
||||
*
|
||||
* Features:
|
||||
* ← → / space / PgUp PgDn / Home End navigation
|
||||
* F fullscreen
|
||||
* S presenter mode (opens a NEW WINDOW with current/next slide preview + notes + timer)
|
||||
* The original window stays as audience view, synced via BroadcastChannel.
|
||||
* Slide previews use CSS transform:scale() at design resolution for pixel-perfect layout.
|
||||
* N quick notes overlay (bottom drawer)
|
||||
* O slide overview grid
|
||||
* T cycle themes (reads data-themes on <html> or <body>)
|
||||
* A cycle demo animation on current slide
|
||||
* URL hash #/N deep-link to slide N (1-based)
|
||||
* Progress bar auto-managed
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
const ANIMS = ['fade-up','fade-down','fade-left','fade-right','rise-in','drop-in',
|
||||
'zoom-pop','blur-in','glitch-in','typewriter','neon-glow','shimmer-sweep',
|
||||
'gradient-flow','stagger-list','counter-up','path-draw','parallax-tilt',
|
||||
'card-flip-3d','cube-rotate-3d','page-turn-3d','perspective-zoom',
|
||||
'marquee-scroll','kenburns','confetti-burst','spotlight','morph-shape','ripple-reveal'];
|
||||
|
||||
function ready(fn){ if(document.readyState!='loading')fn(); else document.addEventListener('DOMContentLoaded',fn);}
|
||||
|
||||
/* ========== Parse URL for preview-only mode ==========
|
||||
* When loaded as iframe.src = "index.html?preview=3", runtime enters a
|
||||
* locked single-slide mode: only slide N is visible, no chrome, no keys,
|
||||
* no hash updates. This is how the presenter window shows pixel-perfect
|
||||
* previews — by loading the actual deck file in an iframe and telling it
|
||||
* to display only a specific slide.
|
||||
*/
|
||||
function getPreviewIdx() {
|
||||
const m = /[?&]preview=(\d+)/.exec(location.search || '');
|
||||
return m ? parseInt(m[1], 10) - 1 : -1;
|
||||
}
|
||||
|
||||
ready(function () {
|
||||
const deck = document.querySelector('.deck');
|
||||
if (!deck) return;
|
||||
const slides = Array.from(deck.querySelectorAll('.slide'));
|
||||
if (!slides.length) return;
|
||||
|
||||
const previewOnlyIdx = getPreviewIdx();
|
||||
const isPreviewMode = previewOnlyIdx >= 0 && previewOnlyIdx < slides.length;
|
||||
|
||||
/* ===== Preview-only mode: show one slide, hide everything else ===== */
|
||||
if (isPreviewMode) {
|
||||
function showSlide(i) {
|
||||
slides.forEach((s, j) => {
|
||||
const active = (j === i);
|
||||
s.classList.toggle('is-active', active);
|
||||
s.style.display = active ? '' : 'none';
|
||||
if (active) {
|
||||
s.style.opacity = '1';
|
||||
s.style.transform = 'none';
|
||||
s.style.pointerEvents = 'auto';
|
||||
}
|
||||
});
|
||||
}
|
||||
showSlide(previewOnlyIdx);
|
||||
/* Hide chrome that the presenter shouldn't see in preview */
|
||||
const hideSel = '.progress-bar, .notes-overlay, .overview, .notes, aside.notes, .speaker-notes';
|
||||
document.querySelectorAll(hideSel).forEach(el => { el.style.display = 'none'; });
|
||||
document.documentElement.setAttribute('data-preview', '1');
|
||||
document.body.setAttribute('data-preview', '1');
|
||||
/* Auto-detect theme base path for theme switching in preview mode */
|
||||
function getPreviewThemeBase() {
|
||||
const base = document.documentElement.getAttribute('data-theme-base');
|
||||
if (base) return base;
|
||||
const tl = document.getElementById('theme-link');
|
||||
if (tl) {
|
||||
const raw = tl.getAttribute('href') || '';
|
||||
const ls = raw.lastIndexOf('/');
|
||||
if (ls >= 0) return raw.substring(0, ls + 1);
|
||||
}
|
||||
return 'assets/themes/';
|
||||
}
|
||||
const previewThemeBase = getPreviewThemeBase();
|
||||
|
||||
/* Listen for postMessage from parent presenter window:
|
||||
* - preview-goto: switch visible slide WITHOUT reloading
|
||||
* - preview-theme: switch theme CSS link to match audience window */
|
||||
window.addEventListener('message', function(e) {
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'preview-goto') {
|
||||
const n = parseInt(e.data.idx, 10);
|
||||
if (n >= 0 && n < slides.length) showSlide(n);
|
||||
} else if (e.data.type === 'preview-theme' && e.data.name) {
|
||||
let link = document.getElementById('theme-link');
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.id = 'theme-link';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = previewThemeBase + e.data.name + '.css';
|
||||
document.documentElement.setAttribute('data-theme', e.data.name);
|
||||
}
|
||||
});
|
||||
/* Signal to parent that preview iframe is ready */
|
||||
try { window.parent && window.parent.postMessage({ type: 'preview-ready' }, '*'); } catch(e) {}
|
||||
return;
|
||||
}
|
||||
|
||||
let idx = 0;
|
||||
const total = slides.length;
|
||||
|
||||
/* ===== BroadcastChannel for presenter sync ===== */
|
||||
const CHANNEL_NAME = 'html-ppt-presenter-' + location.pathname;
|
||||
let bc;
|
||||
try { bc = new BroadcastChannel(CHANNEL_NAME); } catch(e) { bc = null; }
|
||||
|
||||
// Are we running inside the presenter popup? (legacy flag, now unused)
|
||||
const isPresenterWindow = false;
|
||||
|
||||
/* ===== progress bar ===== */
|
||||
let bar = document.querySelector('.progress-bar');
|
||||
if (!bar) {
|
||||
bar = document.createElement('div');
|
||||
bar.className = 'progress-bar';
|
||||
bar.innerHTML = '<span></span>';
|
||||
document.body.appendChild(bar);
|
||||
}
|
||||
const barFill = bar.querySelector('span');
|
||||
|
||||
/* ===== notes overlay (N key) ===== */
|
||||
let notes = document.querySelector('.notes-overlay');
|
||||
if (!notes) {
|
||||
notes = document.createElement('div');
|
||||
notes.className = 'notes-overlay';
|
||||
document.body.appendChild(notes);
|
||||
}
|
||||
|
||||
/* ===== overview grid (O key) ===== */
|
||||
let overview = document.querySelector('.overview');
|
||||
if (!overview) {
|
||||
overview = document.createElement('div');
|
||||
overview.className = 'overview';
|
||||
slides.forEach((s, i) => {
|
||||
const t = document.createElement('div');
|
||||
t.className = 'thumb';
|
||||
// Force 16:9 aspect ratio robustly
|
||||
t.style.padding = '0 0 56.25% 0';
|
||||
t.style.height = '0';
|
||||
t.style.position = 'relative';
|
||||
t.style.overflow = 'hidden';
|
||||
|
||||
const title = s.getAttribute('data-title') ||
|
||||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1));
|
||||
|
||||
// Create a container for the mini-slide
|
||||
const mini = document.createElement('div');
|
||||
mini.className = 'mini-slide';
|
||||
mini.style.position = 'absolute';
|
||||
mini.style.top = '0';
|
||||
mini.style.left = '0';
|
||||
mini.style.width = '1920px';
|
||||
mini.style.height = '1080px';
|
||||
mini.style.transformOrigin = 'top left';
|
||||
mini.style.pointerEvents = 'none';
|
||||
mini.style.background = 'var(--bg)';
|
||||
|
||||
// Clone the slide content
|
||||
const clone = s.cloneNode(true);
|
||||
clone.className = 'slide is-active'; // force active styles
|
||||
clone.style.position = 'absolute';
|
||||
clone.style.inset = '0';
|
||||
clone.style.transform = 'none';
|
||||
clone.style.opacity = '1';
|
||||
clone.style.padding = '72px 96px'; // ensure padding is kept
|
||||
|
||||
mini.appendChild(clone);
|
||||
t.appendChild(mini);
|
||||
|
||||
// Add the number and title overlay
|
||||
const overlay = document.createElement('div');
|
||||
overlay.style.position = 'absolute';
|
||||
overlay.style.inset = '0';
|
||||
overlay.style.background = 'linear-gradient(to bottom, rgba(0,0,0,0.2) 0%, transparent 40%, transparent 60%, rgba(0,0,0,0.8) 100%)';
|
||||
overlay.style.color = '#fff';
|
||||
overlay.style.zIndex = '10';
|
||||
overlay.style.pointerEvents = 'none';
|
||||
|
||||
const n = document.createElement('div');
|
||||
n.className = 'n';
|
||||
n.textContent = i + 1;
|
||||
n.style.position = 'absolute';
|
||||
n.style.top = '12px';
|
||||
n.style.left = '16px';
|
||||
n.style.fontWeight = '700';
|
||||
n.style.fontSize = '16px';
|
||||
n.style.color = '#fff';
|
||||
n.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
|
||||
|
||||
const text = document.createElement('div');
|
||||
text.className = 't';
|
||||
text.textContent = title.trim().slice(0,80);
|
||||
text.style.position = 'absolute';
|
||||
text.style.bottom = '12px';
|
||||
text.style.left = '16px';
|
||||
text.style.right = '16px';
|
||||
text.style.fontWeight = '600';
|
||||
text.style.fontSize = '14px';
|
||||
text.style.color = '#fff';
|
||||
text.style.textShadow = '0 1px 4px rgba(0,0,0,0.8)';
|
||||
|
||||
overlay.appendChild(n);
|
||||
overlay.appendChild(text);
|
||||
t.appendChild(overlay);
|
||||
|
||||
t.addEventListener('click', () => { go(i); toggleOverview(false); });
|
||||
overview.appendChild(t);
|
||||
});
|
||||
document.body.appendChild(overview);
|
||||
}
|
||||
|
||||
/* ===== navigation ===== */
|
||||
function go(n, fromRemote){
|
||||
n = Math.max(0, Math.min(total-1, n));
|
||||
slides.forEach((s,i) => {
|
||||
s.classList.toggle('is-active', i===n);
|
||||
s.classList.toggle('is-prev', i<n);
|
||||
});
|
||||
idx = n;
|
||||
barFill.style.width = ((n+1)/total*100)+'%';
|
||||
const numEl = document.querySelector('.slide-number');
|
||||
if (numEl) { numEl.setAttribute('data-current', n+1); numEl.setAttribute('data-total', total); }
|
||||
|
||||
// notes (bottom overlay)
|
||||
const note = slides[n].querySelector('.notes, aside.notes, .speaker-notes');
|
||||
notes.innerHTML = note ? note.innerHTML : '';
|
||||
|
||||
// hash
|
||||
const hashTarget = '#/'+(n+1);
|
||||
if (location.hash !== hashTarget && !isPresenterWindow) {
|
||||
history.replaceState(null,'', hashTarget);
|
||||
}
|
||||
|
||||
// re-trigger entry animations
|
||||
slides[n].querySelectorAll('[data-anim]').forEach(el => {
|
||||
const a = el.getAttribute('data-anim');
|
||||
el.classList.remove('anim-'+a);
|
||||
void el.offsetWidth;
|
||||
el.classList.add('anim-'+a);
|
||||
});
|
||||
|
||||
// counter-up
|
||||
slides[n].querySelectorAll('.counter').forEach(el => {
|
||||
const target = parseFloat(el.getAttribute('data-to')||el.textContent);
|
||||
const dur = parseInt(el.getAttribute('data-dur')||'1200',10);
|
||||
const start = performance.now();
|
||||
const from = 0;
|
||||
function tick(now){
|
||||
const t = Math.min(1,(now-start)/dur);
|
||||
const v = from + (target-from)*(1-Math.pow(1-t,3));
|
||||
el.textContent = (target % 1 === 0) ? Math.round(v) : v.toFixed(1);
|
||||
if (t<1) requestAnimationFrame(tick);
|
||||
}
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
|
||||
// Broadcast to other window (audience ↔ presenter)
|
||||
if (!fromRemote && bc) {
|
||||
bc.postMessage({ type: 'go', idx: n });
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== listen for remote navigation / theme changes ===== */
|
||||
if (bc) {
|
||||
bc.onmessage = function(e) {
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'go' && typeof e.data.idx === 'number') {
|
||||
go(e.data.idx, true);
|
||||
} else if (e.data.type === 'theme' && e.data.name) {
|
||||
/* Sync theme across windows */
|
||||
const i = themes.indexOf(e.data.name);
|
||||
if (i >= 0) themeIdx = i;
|
||||
applyTheme(e.data.name);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function toggleNotes(force){ notes.classList.toggle('open', force!==undefined?force:!notes.classList.contains('open')); }
|
||||
function toggleOverview(force){
|
||||
const isOpen = force!==undefined ? force : !overview.classList.contains('open');
|
||||
overview.classList.toggle('open', isOpen);
|
||||
if (isOpen) {
|
||||
requestAnimationFrame(() => {
|
||||
const thumbs = overview.querySelectorAll('.thumb');
|
||||
if (thumbs.length) {
|
||||
const scale = thumbs[0].clientWidth / 1920;
|
||||
overview.querySelectorAll('.mini-slide').forEach(m => {
|
||||
m.style.transform = 'scale(' + scale + ')';
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ========== PRESENTER MODE — Magnetic-card popup window ========== */
|
||||
/* Opens a new window with 4 draggable, resizable cards:
|
||||
* CURRENT — iframe(?preview=N) pixel-perfect preview of current slide
|
||||
* NEXT — iframe(?preview=N+1) pixel-perfect preview of next slide
|
||||
* SCRIPT — large speaker notes (逐字稿)
|
||||
* TIMER — elapsed timer + page counter + controls
|
||||
* Cards remember position/size in localStorage.
|
||||
* Two windows sync via BroadcastChannel.
|
||||
*/
|
||||
let presenterWin = null;
|
||||
|
||||
function openPresenterWindow() {
|
||||
if (presenterWin && !presenterWin.closed) {
|
||||
presenterWin.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build absolute URL of THIS deck file (without hash/query)
|
||||
const deckUrl = location.protocol + '//' + location.host + location.pathname;
|
||||
|
||||
// Collect slide titles + notes (HTML strings)
|
||||
const slideMeta = slides.map((s, i) => {
|
||||
const note = s.querySelector('.notes, aside.notes, .speaker-notes');
|
||||
return {
|
||||
title: s.getAttribute('data-title') ||
|
||||
(s.querySelector('h1,h2,h3')||{}).textContent || ('Slide '+(i+1)),
|
||||
notes: note ? note.innerHTML : ''
|
||||
};
|
||||
});
|
||||
|
||||
/* Capture current theme so presenter previews match the audience */
|
||||
const currentTheme = root.getAttribute('data-theme') || (themes[themeIdx] || '');
|
||||
const presenterHTML = buildPresenterHTML(deckUrl, slideMeta, total, idx, CHANNEL_NAME, currentTheme);
|
||||
|
||||
presenterWin = window.open('', 'html-ppt-presenter', 'width=1280,height=820,menubar=no,toolbar=no');
|
||||
if (!presenterWin) {
|
||||
alert('请允许弹出窗口以使用演讲者视图');
|
||||
return;
|
||||
}
|
||||
presenterWin.document.open();
|
||||
presenterWin.document.write(presenterHTML);
|
||||
presenterWin.document.close();
|
||||
}
|
||||
|
||||
function buildPresenterHTML(deckUrl, slideMeta, total, startIdx, channelName, currentTheme) {
|
||||
const metaJSON = JSON.stringify(slideMeta);
|
||||
const deckUrlJSON = JSON.stringify(deckUrl);
|
||||
const channelJSON = JSON.stringify(channelName);
|
||||
const themeJSON = JSON.stringify(currentTheme || '');
|
||||
const storageKey = 'html-ppt-presenter:' + location.pathname;
|
||||
|
||||
// Build the document as a single template string for clarity
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Presenter View</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
html, body {
|
||||
width: 100%; height: 100%; overflow: hidden;
|
||||
background: #1a1d24;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 30%, rgba(88,166,255,.04), transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(188,140,255,.04), transparent 50%);
|
||||
color: #e6edf3;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans SC", sans-serif;
|
||||
}
|
||||
/* Stage: positioned area where cards live */
|
||||
#stage { position: absolute; inset: 0; overflow: hidden; }
|
||||
|
||||
/* Magnetic card */
|
||||
.pcard {
|
||||
position: absolute;
|
||||
background: #0d1117;
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.02);
|
||||
display: flex; flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-width: 180px; min-height: 100px;
|
||||
transition: box-shadow .2s, border-color .2s;
|
||||
}
|
||||
.pcard.dragging { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(88,166,255,.5); border-color: #58a6ff; transition: none; z-index: 9999; }
|
||||
.pcard.resizing { box-shadow: 0 16px 48px rgba(0,0,0,.6), 0 0 0 2px rgba(63,185,80,.5); border-color: #3fb950; transition: none; z-index: 9999; }
|
||||
.pcard:hover { border-color: rgba(88,166,255,.3); }
|
||||
|
||||
/* Card header (drag handle) */
|
||||
.pcard-head {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255,255,255,.04);
|
||||
border-bottom: 1px solid rgba(255,255,255,.06);
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.pcard-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dot-color, #58a6ff); flex-shrink: 0; }
|
||||
.pcard-title {
|
||||
font-size: 11px; letter-spacing: .15em; text-transform: uppercase;
|
||||
font-weight: 700; color: #8b949e; flex: 1;
|
||||
}
|
||||
.pcard-meta { font-size: 11px; color: #6e7681; }
|
||||
|
||||
/* Card body */
|
||||
.pcard-body { flex: 1; position: relative; overflow: hidden; min-height: 0; }
|
||||
|
||||
/* Preview cards (CURRENT/NEXT) — iframe-based pixel-perfect render */
|
||||
.pcard-preview .pcard-body { background: #000; }
|
||||
.pcard-preview iframe {
|
||||
position: absolute; top: 0; left: 0;
|
||||
width: 1920px; height: 1080px;
|
||||
border: none;
|
||||
transform-origin: top left;
|
||||
pointer-events: none;
|
||||
background: transparent;
|
||||
}
|
||||
.pcard-preview .preview-end {
|
||||
position: absolute; inset: 0;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
color: #484f58; font-size: 14px; letter-spacing: .12em;
|
||||
}
|
||||
|
||||
/* Notes card */
|
||||
.pcard-notes .pcard-body {
|
||||
padding: 14px 18px;
|
||||
overflow-y: auto;
|
||||
font-size: 18px; line-height: 1.75;
|
||||
color: #d0d7de;
|
||||
font-family: "Noto Sans SC", -apple-system, sans-serif;
|
||||
}
|
||||
.pcard-notes .pcard-body p { margin: 0 0 .7em 0; }
|
||||
.pcard-notes .pcard-body strong { color: #f0883e; }
|
||||
.pcard-notes .pcard-body em { color: #58a6ff; font-style: normal; }
|
||||
.pcard-notes .pcard-body code {
|
||||
font-family: "SF Mono", monospace; font-size: .9em;
|
||||
background: rgba(255,255,255,.08); padding: 1px 6px; border-radius: 4px;
|
||||
}
|
||||
.pcard-notes .empty { color: #484f58; font-style: italic; }
|
||||
|
||||
/* Timer card */
|
||||
.pcard-timer .pcard-body {
|
||||
display: flex; flex-direction: column; gap: 14px;
|
||||
padding: 18px 20px; justify-content: center;
|
||||
}
|
||||
.timer-display {
|
||||
font-family: "SF Mono", "JetBrains Mono", monospace;
|
||||
font-size: 42px; font-weight: 700;
|
||||
color: #3fb950;
|
||||
letter-spacing: .04em;
|
||||
line-height: 1;
|
||||
}
|
||||
.timer-row {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
font-size: 14px; color: #8b949e;
|
||||
}
|
||||
.timer-row .label { font-size: 10px; letter-spacing: .15em; text-transform: uppercase; color: #6e7681; }
|
||||
.timer-row .val { color: #e6edf3; font-weight: 600; font-family: "SF Mono", monospace; }
|
||||
.timer-controls { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
.timer-btn {
|
||||
background: rgba(255,255,255,.06);
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
color: #e6edf3;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
.timer-btn:hover { background: rgba(88,166,255,.15); border-color: #58a6ff; }
|
||||
.timer-btn:active { transform: translateY(1px); }
|
||||
|
||||
/* Resize handle */
|
||||
.pcard-resize {
|
||||
position: absolute; right: 0; bottom: 0;
|
||||
width: 18px; height: 18px;
|
||||
cursor: nwse-resize;
|
||||
background: linear-gradient(135deg, transparent 50%, rgba(255,255,255,.25) 50%, rgba(255,255,255,.25) 60%, transparent 60%, transparent 70%, rgba(255,255,255,.25) 70%, rgba(255,255,255,.25) 80%, transparent 80%);
|
||||
z-index: 5;
|
||||
}
|
||||
.pcard-resize:hover { background: linear-gradient(135deg, transparent 50%, #58a6ff 50%, #58a6ff 60%, transparent 60%, transparent 70%, #58a6ff 70%, #58a6ff 80%, transparent 80%); }
|
||||
|
||||
/* Bottom hint bar */
|
||||
.hint-bar {
|
||||
position: fixed; bottom: 0; left: 0; right: 0;
|
||||
background: rgba(0,0,0,.6);
|
||||
backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(255,255,255,.08);
|
||||
padding: 6px 16px;
|
||||
font-size: 11px; color: #8b949e;
|
||||
display: flex; gap: 18px; align-items: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
.hint-bar kbd {
|
||||
background: rgba(255,255,255,.08);
|
||||
padding: 1px 6px; border-radius: 3px;
|
||||
font-family: "SF Mono", monospace;
|
||||
font-size: 10px;
|
||||
border: 1px solid rgba(255,255,255,.1);
|
||||
color: #e6edf3;
|
||||
}
|
||||
.hint-bar .reset-layout {
|
||||
margin-left: auto;
|
||||
background: transparent; border: 1px solid rgba(255,255,255,.15);
|
||||
color: #8b949e; padding: 3px 10px; border-radius: 4px;
|
||||
font-size: 11px; cursor: pointer; font-family: inherit;
|
||||
}
|
||||
.hint-bar .reset-layout:hover { background: rgba(248,81,73,.15); border-color: #f85149; color: #f85149; }
|
||||
|
||||
body.is-dragging-card * { user-select: none !important; }
|
||||
body.is-dragging-card iframe { pointer-events: none !important; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="stage">
|
||||
<div class="pcard pcard-preview" id="card-cur" style="--dot-color:#58a6ff">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">CURRENT</span>
|
||||
<span class="pcard-meta" id="cur-meta">—</span>
|
||||
</div>
|
||||
<div class="pcard-body"><iframe id="iframe-cur"></iframe></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-preview" id="card-nxt" style="--dot-color:#bc8cff">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">NEXT</span>
|
||||
<span class="pcard-meta" id="nxt-meta">—</span>
|
||||
</div>
|
||||
<div class="pcard-body"><iframe id="iframe-nxt"></iframe></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-notes" id="card-notes" style="--dot-color:#f0883e">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">SPEAKER SCRIPT · 逐字稿</span>
|
||||
</div>
|
||||
<div class="pcard-body" id="notes-body"></div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
|
||||
<div class="pcard pcard-timer" id="card-timer" style="--dot-color:#3fb950">
|
||||
<div class="pcard-head" data-drag>
|
||||
<span class="pcard-dot"></span>
|
||||
<span class="pcard-title">TIMER</span>
|
||||
</div>
|
||||
<div class="pcard-body">
|
||||
<div class="timer-display" id="timer-display">00:00</div>
|
||||
<div class="timer-row">
|
||||
<span class="label">Slide</span>
|
||||
<span class="val" id="timer-count">1 / ${total}</span>
|
||||
</div>
|
||||
<div class="timer-controls">
|
||||
<button class="timer-btn" id="btn-prev">← Prev</button>
|
||||
<button class="timer-btn" id="btn-next">Next →</button>
|
||||
<button class="timer-btn" id="btn-reset">⏱ Reset</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pcard-resize" data-resize></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hint-bar">
|
||||
<span><kbd>← →</kbd> 翻页</span>
|
||||
<span><kbd>R</kbd> 重置计时</span>
|
||||
<span><kbd>Esc</kbd> 关闭</span>
|
||||
<span style="color:#6e7681">拖动卡片头部移动 · 拖动右下角调整大小</span>
|
||||
<button class="reset-layout" id="reset-layout">重置布局</button>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
var slideMeta = ${metaJSON};
|
||||
var total = ${total};
|
||||
var idx = ${startIdx};
|
||||
var deckUrl = ${deckUrlJSON};
|
||||
var STORAGE_KEY = ${JSON.stringify(storageKey)};
|
||||
var bc;
|
||||
try { bc = new BroadcastChannel(${channelJSON}); } catch(e) {}
|
||||
|
||||
var iframeCur = document.getElementById('iframe-cur');
|
||||
var iframeNxt = document.getElementById('iframe-nxt');
|
||||
var notesBody = document.getElementById('notes-body');
|
||||
var curMeta = document.getElementById('cur-meta');
|
||||
var nxtMeta = document.getElementById('nxt-meta');
|
||||
var timerDisplay = document.getElementById('timer-display');
|
||||
var timerCount = document.getElementById('timer-count');
|
||||
|
||||
/* ===== Default card layout ===== */
|
||||
function defaultLayout() {
|
||||
var w = window.innerWidth;
|
||||
var h = window.innerHeight - 36; /* leave room for hint bar */
|
||||
return {
|
||||
'card-cur': { x: 16, y: 16, w: Math.round(w*0.55) - 24, h: Math.round(h*0.62) - 16 },
|
||||
'card-nxt': { x: Math.round(w*0.55) + 8, y: 16, w: w - Math.round(w*0.55) - 24, h: Math.round(h*0.42) - 16 },
|
||||
'card-notes': { x: Math.round(w*0.55) + 8, y: Math.round(h*0.42) + 8, w: w - Math.round(w*0.55) - 24, h: h - Math.round(h*0.42) - 16 },
|
||||
'card-timer': { x: 16, y: Math.round(h*0.62) + 8, w: Math.round(w*0.55) - 24, h: h - Math.round(h*0.62) - 16 }
|
||||
};
|
||||
}
|
||||
|
||||
/* ===== Apply / save / restore layout ===== */
|
||||
function applyLayout(layout) {
|
||||
Object.keys(layout).forEach(function(id){
|
||||
var el = document.getElementById(id);
|
||||
var l = layout[id];
|
||||
if (el && l) {
|
||||
el.style.left = l.x + 'px';
|
||||
el.style.top = l.y + 'px';
|
||||
el.style.width = l.w + 'px';
|
||||
el.style.height = l.h + 'px';
|
||||
}
|
||||
});
|
||||
rescaleAll();
|
||||
}
|
||||
function readLayout() {
|
||||
try {
|
||||
var saved = localStorage.getItem(STORAGE_KEY);
|
||||
if (saved) return JSON.parse(saved);
|
||||
} catch(e) {}
|
||||
return defaultLayout();
|
||||
}
|
||||
function saveLayout() {
|
||||
var layout = {};
|
||||
['card-cur','card-nxt','card-notes','card-timer'].forEach(function(id){
|
||||
var el = document.getElementById(id);
|
||||
if (el) {
|
||||
layout[id] = {
|
||||
x: parseInt(el.style.left,10) || 0,
|
||||
y: parseInt(el.style.top,10) || 0,
|
||||
w: parseInt(el.style.width,10) || 300,
|
||||
h: parseInt(el.style.height,10) || 200
|
||||
};
|
||||
}
|
||||
});
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(layout)); } catch(e) {}
|
||||
}
|
||||
|
||||
/* ===== iframe rescale to fit card body ===== */
|
||||
function rescaleIframe(iframe) {
|
||||
if (!iframe || iframe.style.display === 'none') return;
|
||||
var body = iframe.parentElement;
|
||||
var cw = body.clientWidth, ch = body.clientHeight;
|
||||
if (!cw || !ch) return;
|
||||
var s = Math.min(cw / 1920, ch / 1080);
|
||||
iframe.style.transform = 'scale(' + s + ')';
|
||||
/* Center the scaled iframe in the body */
|
||||
var sw = 1920 * s, sh = 1080 * s;
|
||||
iframe.style.left = Math.max(0, (cw - sw) / 2) + 'px';
|
||||
iframe.style.top = Math.max(0, (ch - sh) / 2) + 'px';
|
||||
}
|
||||
function rescaleAll() {
|
||||
rescaleIframe(iframeCur);
|
||||
rescaleIframe(iframeNxt);
|
||||
}
|
||||
window.addEventListener('resize', rescaleAll);
|
||||
|
||||
/* ===== Drag (move card by header) ===== */
|
||||
document.querySelectorAll('[data-drag]').forEach(function(handle){
|
||||
handle.addEventListener('mousedown', function(e){
|
||||
if (e.button !== 0) return;
|
||||
var card = handle.closest('.pcard');
|
||||
if (!card) return;
|
||||
e.preventDefault();
|
||||
card.classList.add('dragging');
|
||||
document.body.classList.add('is-dragging-card');
|
||||
var startX = e.clientX, startY = e.clientY;
|
||||
var startL = parseInt(card.style.left,10) || 0;
|
||||
var startT = parseInt(card.style.top,10) || 0;
|
||||
function onMove(ev){
|
||||
var nx = Math.max(0, Math.min(window.innerWidth - 100, startL + ev.clientX - startX));
|
||||
var ny = Math.max(0, Math.min(window.innerHeight - 50, startT + ev.clientY - startY));
|
||||
card.style.left = nx + 'px';
|
||||
card.style.top = ny + 'px';
|
||||
}
|
||||
function onUp(){
|
||||
card.classList.remove('dragging');
|
||||
document.body.classList.remove('is-dragging-card');
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
saveLayout();
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
/* ===== Resize (drag bottom-right corner) ===== */
|
||||
document.querySelectorAll('[data-resize]').forEach(function(handle){
|
||||
handle.addEventListener('mousedown', function(e){
|
||||
if (e.button !== 0) return;
|
||||
var card = handle.closest('.pcard');
|
||||
if (!card) return;
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
card.classList.add('resizing');
|
||||
document.body.classList.add('is-dragging-card');
|
||||
var startX = e.clientX, startY = e.clientY;
|
||||
var startW = parseInt(card.style.width,10) || card.offsetWidth;
|
||||
var startH = parseInt(card.style.height,10) || card.offsetHeight;
|
||||
function onMove(ev){
|
||||
var nw = Math.max(180, startW + ev.clientX - startX);
|
||||
var nh = Math.max(100, startH + ev.clientY - startY);
|
||||
card.style.width = nw + 'px';
|
||||
card.style.height = nh + 'px';
|
||||
if (card.querySelector('iframe')) rescaleIframe(card.querySelector('iframe'));
|
||||
}
|
||||
function onUp(){
|
||||
card.classList.remove('resizing');
|
||||
document.body.classList.remove('is-dragging-card');
|
||||
document.removeEventListener('mousemove', onMove);
|
||||
document.removeEventListener('mouseup', onUp);
|
||||
rescaleAll();
|
||||
saveLayout();
|
||||
}
|
||||
document.addEventListener('mousemove', onMove);
|
||||
document.addEventListener('mouseup', onUp);
|
||||
});
|
||||
});
|
||||
|
||||
/* ===== Preview iframe ready tracking =====
|
||||
* Each iframe loads the deck ONCE with ?preview=1 on init. Subsequent
|
||||
* slide changes are sent via postMessage('preview-goto') so the iframe
|
||||
* just toggles visibility of a different .slide — no reload, no flicker.
|
||||
*/
|
||||
var iframeReady = { cur: false, nxt: false };
|
||||
var currentTheme = ${themeJSON};
|
||||
window.addEventListener('message', function(e) {
|
||||
if (!e.data || e.data.type !== 'preview-ready') return;
|
||||
var iframe = null;
|
||||
if (e.source === iframeCur.contentWindow) {
|
||||
iframeReady.cur = true;
|
||||
iframe = iframeCur;
|
||||
postPreviewGoto(iframeCur, idx);
|
||||
} else if (e.source === iframeNxt.contentWindow) {
|
||||
iframeReady.nxt = true;
|
||||
iframe = iframeNxt;
|
||||
postPreviewGoto(iframeNxt, idx + 1 < total ? idx + 1 : idx);
|
||||
}
|
||||
/* Sync current theme to the iframe */
|
||||
if (iframe && currentTheme) {
|
||||
try { iframe.contentWindow.postMessage({ type: 'preview-theme', name: currentTheme }, '*'); } catch(err) {}
|
||||
}
|
||||
if (iframe) rescaleIframe(iframe);
|
||||
});
|
||||
|
||||
function postPreviewGoto(iframe, n) {
|
||||
try {
|
||||
iframe.contentWindow.postMessage({ type: 'preview-goto', idx: n }, '*');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
/* ===== Update content =====
|
||||
* Smooth (no-reload) navigation: send postMessage to iframes instead of
|
||||
* resetting src. Iframes stay loaded, just switch visible .slide.
|
||||
*/
|
||||
function update(n) {
|
||||
n = Math.max(0, Math.min(total - 1, n));
|
||||
idx = n;
|
||||
|
||||
/* Current preview — postMessage (smooth) */
|
||||
if (iframeReady.cur) postPreviewGoto(iframeCur, n);
|
||||
curMeta.textContent = (n + 1) + '/' + total;
|
||||
|
||||
/* Next preview */
|
||||
if (n + 1 < total) {
|
||||
iframeNxt.style.display = '';
|
||||
var endEl = document.querySelector('#card-nxt .preview-end');
|
||||
if (endEl) endEl.remove();
|
||||
if (iframeReady.nxt) postPreviewGoto(iframeNxt, n + 1);
|
||||
nxtMeta.textContent = (n + 2) + '/' + total;
|
||||
} else {
|
||||
iframeNxt.style.display = 'none';
|
||||
var body = document.querySelector('#card-nxt .pcard-body');
|
||||
if (body && !body.querySelector('.preview-end')) {
|
||||
var end = document.createElement('div');
|
||||
end.className = 'preview-end';
|
||||
end.textContent = '— END OF DECK —';
|
||||
body.appendChild(end);
|
||||
}
|
||||
nxtMeta.textContent = 'END';
|
||||
}
|
||||
|
||||
/* Notes */
|
||||
var note = slideMeta[n].notes;
|
||||
notesBody.innerHTML = note || '<span class="empty">(这一页还没有逐字稿)</span>';
|
||||
|
||||
/* Timer count */
|
||||
timerCount.textContent = (n + 1) + ' / ' + total;
|
||||
}
|
||||
|
||||
/* ===== Timer ===== */
|
||||
var tStart = Date.now();
|
||||
setInterval(function(){
|
||||
var s = Math.floor((Date.now() - tStart) / 1000);
|
||||
var mm = String(Math.floor(s/60)).padStart(2,'0');
|
||||
var ss = String(s%60).padStart(2,'0');
|
||||
timerDisplay.textContent = mm + ':' + ss;
|
||||
}, 1000);
|
||||
function resetTimer(){ tStart = Date.now(); timerDisplay.textContent = '00:00'; }
|
||||
|
||||
/* ===== BroadcastChannel sync ===== */
|
||||
if (bc) {
|
||||
bc.onmessage = function(e){
|
||||
if (!e.data) return;
|
||||
if (e.data.type === 'go') update(e.data.idx);
|
||||
else if (e.data.type === 'theme' && e.data.name) {
|
||||
currentTheme = e.data.name;
|
||||
/* Forward theme change to preview iframes */
|
||||
[iframeCur, iframeNxt].forEach(function(iframe){
|
||||
try {
|
||||
iframe.contentWindow.postMessage({ type: 'preview-theme', name: e.data.name }, '*');
|
||||
} catch(err) {}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
function go(n) {
|
||||
update(n);
|
||||
if (bc) bc.postMessage({ type: 'go', idx: idx });
|
||||
}
|
||||
|
||||
/* ===== Buttons ===== */
|
||||
document.getElementById('btn-prev').addEventListener('click', function(){ go(idx - 1); });
|
||||
document.getElementById('btn-next').addEventListener('click', function(){ go(idx + 1); });
|
||||
document.getElementById('btn-reset').addEventListener('click', resetTimer);
|
||||
document.getElementById('reset-layout').addEventListener('click', function(){
|
||||
if (confirm('恢复默认卡片布局?')) {
|
||||
try { localStorage.removeItem(STORAGE_KEY); } catch(e){}
|
||||
applyLayout(defaultLayout());
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Keyboard ===== */
|
||||
document.addEventListener('keydown', function(e){
|
||||
if (e.metaKey || e.ctrlKey || e.altKey) return;
|
||||
switch(e.key) {
|
||||
case 'ArrowRight': case ' ': case 'PageDown': go(idx + 1); e.preventDefault(); break;
|
||||
case 'ArrowLeft': case 'PageUp': go(idx - 1); e.preventDefault(); break;
|
||||
case 'Home': go(0); break;
|
||||
case 'End': go(total - 1); break;
|
||||
case 'r': case 'R': resetTimer(); break;
|
||||
case 'Escape': window.close(); break;
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Iframe load → rescale (catches initial size) ===== */
|
||||
iframeCur.addEventListener('load', function(){ rescaleIframe(iframeCur); });
|
||||
iframeNxt.addEventListener('load', function(){ rescaleIframe(iframeNxt); });
|
||||
|
||||
/* ===== Init =====
|
||||
* Load each iframe ONCE with the deck file. After they post
|
||||
* 'preview-ready', all subsequent navigation is via postMessage
|
||||
* (smooth, no reload, no flicker).
|
||||
*/
|
||||
applyLayout(readLayout());
|
||||
iframeCur.src = deckUrl + '?preview=' + (idx + 1);
|
||||
if (idx + 1 < total) iframeNxt.src = deckUrl + '?preview=' + (idx + 2);
|
||||
/* Initialize notes/timer/count without touching iframes */
|
||||
notesBody.innerHTML = slideMeta[idx].notes || '<span class="empty">(这一页还没有逐字稿)</span>';
|
||||
curMeta.textContent = (idx + 1) + '/' + total;
|
||||
nxtMeta.textContent = (idx + 2) + '/' + total;
|
||||
timerCount.textContent = (idx + 1) + ' / ' + total;
|
||||
})();
|
||||
</` + `script>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function fullscreen(){ const el=document.documentElement;
|
||||
if (!document.fullscreenElement) el.requestFullscreen&&el.requestFullscreen();
|
||||
else document.exitFullscreen&&document.exitFullscreen();
|
||||
}
|
||||
|
||||
// theme cycling
|
||||
const root = document.documentElement;
|
||||
const themesAttr = root.getAttribute('data-themes') || document.body.getAttribute('data-themes');
|
||||
const themes = themesAttr ? themesAttr.split(',').map(s=>s.trim()).filter(Boolean) : [];
|
||||
let themeIdx = 0;
|
||||
|
||||
// Auto-detect theme base path from existing <link id="theme-link">
|
||||
let themeBase = root.getAttribute('data-theme-base');
|
||||
if (!themeBase) {
|
||||
const existingLink = document.getElementById('theme-link');
|
||||
if (existingLink) {
|
||||
// el.getAttribute('href') gives the raw relative path written in HTML
|
||||
const rawHref = existingLink.getAttribute('href') || '';
|
||||
const lastSlash = rawHref.lastIndexOf('/');
|
||||
themeBase = lastSlash >= 0 ? rawHref.substring(0, lastSlash + 1) : 'assets/themes/';
|
||||
} else {
|
||||
themeBase = 'assets/themes/';
|
||||
}
|
||||
}
|
||||
|
||||
function applyTheme(name) {
|
||||
let link = document.getElementById('theme-link');
|
||||
if (!link) {
|
||||
link = document.createElement('link');
|
||||
link.rel = 'stylesheet';
|
||||
link.id = 'theme-link';
|
||||
document.head.appendChild(link);
|
||||
}
|
||||
link.href = themeBase + name + '.css';
|
||||
root.setAttribute('data-theme', name);
|
||||
const ind = document.querySelector('.theme-indicator');
|
||||
if (ind) ind.textContent = name;
|
||||
}
|
||||
function cycleTheme(fromRemote){
|
||||
if (!themes.length) return;
|
||||
themeIdx = (themeIdx+1) % themes.length;
|
||||
const name = themes[themeIdx];
|
||||
applyTheme(name);
|
||||
/* Broadcast to other window (audience ↔ presenter) */
|
||||
if (!fromRemote && bc) bc.postMessage({ type: 'theme', name: name });
|
||||
}
|
||||
|
||||
// animation cycling on current slide
|
||||
let animIdx = 0;
|
||||
function cycleAnim(){
|
||||
animIdx = (animIdx+1) % ANIMS.length;
|
||||
const a = ANIMS[animIdx];
|
||||
const target = slides[idx].querySelector('[data-anim-target]') || slides[idx];
|
||||
ANIMS.forEach(x => target.classList.remove('anim-'+x));
|
||||
void target.offsetWidth;
|
||||
target.classList.add('anim-'+a);
|
||||
target.setAttribute('data-anim', a);
|
||||
const ind = document.querySelector('.anim-indicator');
|
||||
if (ind) ind.textContent = a;
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function (e) {
|
||||
if (e.metaKey||e.ctrlKey||e.altKey) return;
|
||||
switch (e.key) {
|
||||
case 'ArrowRight': case ' ': case 'PageDown': case 'Enter': go(idx+1); e.preventDefault(); break;
|
||||
case 'ArrowLeft': case 'PageUp': case 'Backspace': go(idx-1); e.preventDefault(); break;
|
||||
case 'Home': go(0); break;
|
||||
case 'End': go(total-1); break;
|
||||
case 'f': case 'F': fullscreen(); break;
|
||||
case 's': case 'S': openPresenterWindow(); break;
|
||||
case 'n': case 'N': toggleNotes(); break;
|
||||
case 'o': case 'O': toggleOverview(); break;
|
||||
case 't': case 'T': cycleTheme(); break;
|
||||
case 'a': case 'A': cycleAnim(); break;
|
||||
case 'Escape': toggleOverview(false); toggleNotes(false); break;
|
||||
}
|
||||
});
|
||||
|
||||
// hash deep-link
|
||||
function fromHash(){
|
||||
const m = /^#\/(\d+)/.exec(location.hash||'');
|
||||
if (m) go(Math.max(0, parseInt(m[1],10)-1));
|
||||
}
|
||||
window.addEventListener('hashchange', fromHash);
|
||||
fromHash();
|
||||
go(idx);
|
||||
});
|
||||
})();
|
||||
@@ -0,0 +1,23 @@
|
||||
/* theme: academic-paper — 学术论文 */
|
||||
:root{
|
||||
--bg:#fdfcf8;--bg-soft:#f7f5ed;--surface:#ffffff;--surface-2:#f5f3ea;
|
||||
--border:rgba(20,20,20,.14);--border-strong:rgba(20,20,20,.35);
|
||||
--text-1:#0a0a0a;--text-2:#333333;--text-3:#707070;
|
||||
--accent:#1a3a7a;--accent-2:#0a0a0a;--accent-3:#8a1a1a;
|
||||
--good:#1a5a2a;--warn:#8a6a1a;--bad:#8a1a1a;
|
||||
--grad:linear-gradient(135deg,#1a3a7a,#0a0a0a);
|
||||
--grad-soft:linear-gradient(135deg,#e8edf8,#f5f3ea);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:0px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 1px 2px rgba(0,0,0,.1);
|
||||
--font-sans:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-serif:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-display:'Latin Modern Roman','Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
}
|
||||
body{font-family:var(--font-serif)}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;font-family:var(--font-serif)}
|
||||
.card{border:1px solid var(--border);box-shadow:none}
|
||||
.divider{background:var(--text-1);height:1px}
|
||||
.divider-accent{background:var(--text-1);height:2px;width:100%}
|
||||
a{color:var(--accent);text-decoration:underline}
|
||||
.kicker{color:var(--accent);font-style:italic;text-transform:none;letter-spacing:0;font-weight:400}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: arctic-cool — 冷色调 蓝/青/石板灰 */
|
||||
:root{
|
||||
--bg:#f2f6fb;--bg-soft:#e7eef7;--surface:#ffffff;--surface-2:#edf3fa;
|
||||
--border:rgba(40,70,110,.12);--border-strong:rgba(40,70,110,.24);
|
||||
--text-1:#0e1f33;--text-2:#3a5778;--text-3:#6b819b;
|
||||
--accent:#1e6fb0;--accent-2:#17b1b1;--accent-3:#6f8aa6;
|
||||
--good:#1aaf84;--warn:#d19030;--bad:#c5485a;
|
||||
--grad:linear-gradient(135deg,#1e6fb0,#17b1b1 60%,#5fb9d6);
|
||||
--grad-soft:linear-gradient(135deg,#e7eef7,#dff3f3);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 28px rgba(40,70,110,.12);
|
||||
--shadow-lg:0 24px 60px rgba(40,70,110,.18);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* theme: aurora — 极光渐变 */
|
||||
:root{
|
||||
--bg:#06091c;--bg-soft:#0a1130;--surface:rgba(255,255,255,.05);--surface-2:rgba(255,255,255,.08);
|
||||
--border:rgba(180,220,255,.14);--border-strong:rgba(180,220,255,.28);
|
||||
--text-1:#e8f0ff;--text-2:#b4c4e4;--text-3:#6a7a9e;
|
||||
--accent:#5ef2c6;--accent-2:#7aa2ff;--accent-3:#c984ff;
|
||||
--good:#5ef2c6;--warn:#ffd27a;--bad:#ff8ab0;
|
||||
--grad:linear-gradient(135deg,#5ef2c6,#7aa2ff 50%,#c984ff);
|
||||
--grad-soft:linear-gradient(135deg,rgba(94,242,198,.2),rgba(201,132,255,.2));
|
||||
--radius:20px;--radius-sm:14px;--radius-lg:28px;
|
||||
--shadow:0 20px 60px rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.08);
|
||||
--shadow-lg:0 30px 80px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(60% 50% at 20% 10%,rgba(94,242,198,.35),transparent 70%),
|
||||
radial-gradient(55% 50% at 80% 20%,rgba(122,162,255,.32),transparent 70%),
|
||||
radial-gradient(70% 60% at 50% 100%,rgba(201,132,255,.3),transparent 70%),
|
||||
#06091c}
|
||||
.card{backdrop-filter:blur(24px) saturate(160%);-webkit-backdrop-filter:blur(24px) saturate(160%)}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* theme: bauhaus — 几何+原色 */
|
||||
:root{
|
||||
--bg:#f4efe3;--bg-soft:#e8e2d1;--surface:#ffffff;--surface-2:#f4efe3;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#333333;--text-3:#666666;
|
||||
--accent:#e03c27;--accent-2:#f4c430;--accent-3:#1d4eaf;
|
||||
--good:#1b8c3c;--warn:#f4c430;--bad:#e03c27;
|
||||
--grad:linear-gradient(135deg,#e03c27 0 33%,#f4c430 33% 66%,#1d4eaf 66% 100%);
|
||||
--grad-soft:linear-gradient(135deg,#f4efe3,#e8e2d1);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:4px 4px 0 #111;--shadow-lg:8px 8px 0 #111;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
--letter-tight:-.03em;
|
||||
}
|
||||
.card{border:2px solid #111}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* theme: blueprint — 蓝图工程 */
|
||||
:root{
|
||||
--bg:#0b3a6f;--bg-soft:#0a3260;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||
--border:rgba(190,220,255,.3);--border-strong:rgba(190,220,255,.55);
|
||||
--text-1:#e8f3ff;--text-2:#b8d4f0;--text-3:#7da8cf;
|
||||
--accent:#ffffff;--accent-2:#aee1ff;--accent-3:#ffd27a;
|
||||
--good:#8ef0a6;--warn:#ffd27a;--bad:#ff8a96;
|
||||
--grad:linear-gradient(135deg,#ffffff,#aee1ff);
|
||||
--grad-soft:linear-gradient(135deg,#0a3260,#0b3a6f);
|
||||
--radius:2px;--radius-sm:2px;--radius-lg:4px;
|
||||
--shadow:none;--shadow-lg:0 16px 40px rgba(0,0,0,.3);
|
||||
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono',monospace;
|
||||
}
|
||||
body{background:
|
||||
linear-gradient(rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
|
||||
linear-gradient(90deg,rgba(255,255,255,.06) 1px,transparent 1px) 0 0/40px 40px,
|
||||
#0b3a6f}
|
||||
.card{border:1px dashed rgba(190,220,255,.45);background:rgba(255,255,255,.04)}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: catppuccin-latte — catppuccin 浅 */
|
||||
:root{
|
||||
--bg:#eff1f5;--bg-soft:#e6e9ef;--surface:#ffffff;--surface-2:#eef0f4;
|
||||
--border:rgba(76,79,105,.14);--border-strong:rgba(76,79,105,.28);
|
||||
--text-1:#4c4f69;--text-2:#6c6f85;--text-3:#9ca0b0;
|
||||
--accent:#8839ef;--accent-2:#1e66f5;--accent-3:#ea76cb;
|
||||
--good:#40a02b;--warn:#df8e1d;--bad:#d20f39;
|
||||
--grad:linear-gradient(135deg,#8839ef,#1e66f5 50%,#04a5e5);
|
||||
--grad-soft:linear-gradient(135deg,#eff1f5,#e6e9ef);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 8px 24px rgba(76,79,105,.1);
|
||||
--shadow-lg:0 20px 56px rgba(76,79,105,.16);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: catppuccin-mocha — catppuccin 深 */
|
||||
:root{
|
||||
--bg:#1e1e2e;--bg-soft:#181825;--surface:#313244;--surface-2:#45475a;
|
||||
--border:rgba(205,214,244,.12);--border-strong:rgba(205,214,244,.24);
|
||||
--text-1:#cdd6f4;--text-2:#a6adc8;--text-3:#7f849c;
|
||||
--accent:#cba6f7;--accent-2:#89b4fa;--accent-3:#f5c2e7;
|
||||
--good:#a6e3a1;--warn:#f9e2af;--bad:#f38ba8;
|
||||
--grad:linear-gradient(135deg,#cba6f7,#89b4fa 50%,#94e2d5);
|
||||
--grad-soft:linear-gradient(135deg,#313244,#45475a);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.35);
|
||||
--shadow-lg:0 24px 60px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* theme: corporate-clean — 企业商务 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f5f7fa;--surface:#ffffff;--surface-2:#f0f3f7;
|
||||
--border:rgba(10,37,64,.12);--border-strong:rgba(10,37,64,.28);
|
||||
--text-1:#0a2540;--text-2:#425466;--text-3:#8898aa;
|
||||
--accent:#0a2540;--accent-2:#1d4ed8;--accent-3:#64748b;
|
||||
--good:#0e9f6e;--warn:#d97706;--bad:#dc2626;
|
||||
--grad:linear-gradient(135deg,#0a2540,#1d4ed8);
|
||||
--grad-soft:linear-gradient(135deg,#f0f4fb,#e4ecf7);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:10px;
|
||||
--shadow:0 1px 3px rgba(10,37,64,.08),0 4px 12px rgba(10,37,64,.05);
|
||||
--shadow-lg:0 4px 12px rgba(10,37,64,.1),0 16px 40px rgba(10,37,64,.08);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.card{border:1px solid var(--border)}
|
||||
.divider-accent{background:var(--accent);height:3px;width:56px}
|
||||
.kicker{color:var(--accent-2)}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;color:var(--accent)}
|
||||
@@ -0,0 +1,23 @@
|
||||
/* theme: cyberpunk-neon — 赛博朋克霓虹 */
|
||||
:root{
|
||||
--bg:#000000;--bg-soft:#0a0a12;--surface:#0f0f1a;--surface-2:#14141f;
|
||||
--border:rgba(255,0,170,.25);--border-strong:rgba(0,240,255,.55);
|
||||
--text-1:#f5f7ff;--text-2:#b4b8d4;--text-3:#6b6e8a;
|
||||
--accent:#ff2bd6;--accent-2:#00f0ff;--accent-3:#f9f871;
|
||||
--good:#39ff14;--warn:#f9f871;--bad:#ff2bd6;
|
||||
--grad:linear-gradient(135deg,#ff2bd6,#7a00ff 50%,#00f0ff);
|
||||
--grad-soft:linear-gradient(135deg,rgba(255,43,214,.18),rgba(0,240,255,.18));
|
||||
--radius:6px;--radius-sm:3px;--radius-lg:10px;
|
||||
--shadow:0 0 0 1px rgba(255,43,214,.35),0 0 24px rgba(255,43,214,.35),0 0 48px rgba(0,240,255,.18);
|
||||
--shadow-lg:0 0 0 1px rgba(0,240,255,.5),0 0 40px rgba(0,240,255,.45),0 0 80px rgba(255,43,214,.3);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(ellipse at 15% 0%,rgba(255,43,214,.22),transparent 60%),
|
||||
radial-gradient(ellipse at 85% 100%,rgba(0,240,255,.2),transparent 60%),
|
||||
#000}
|
||||
h1.title,h2.title,.h1,.h2{text-shadow:0 0 12px rgba(255,43,214,.6),0 0 30px rgba(0,240,255,.35)}
|
||||
.kicker{color:var(--accent-2);text-shadow:0 0 8px rgba(0,240,255,.6)}
|
||||
.card{background:rgba(15,15,26,.72);backdrop-filter:blur(8px)}
|
||||
.divider-accent{background:var(--grad);box-shadow:0 0 12px var(--accent)}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: dracula — dracula 深色 */
|
||||
:root{
|
||||
--bg:#282a36;--bg-soft:#21222c;--surface:#343746;--surface-2:#44475a;
|
||||
--border:rgba(248,248,242,.12);--border-strong:rgba(248,248,242,.24);
|
||||
--text-1:#f8f8f2;--text-2:#bdbde0;--text-3:#6272a4;
|
||||
--accent:#bd93f9;--accent-2:#ff79c6;--accent-3:#8be9fd;
|
||||
--good:#50fa7b;--warn:#f1fa8c;--bad:#ff5555;
|
||||
--grad:linear-gradient(135deg,#bd93f9,#ff79c6 55%,#8be9fd);
|
||||
--grad-soft:linear-gradient(135deg,#343746,#44475a);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:18px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.4);
|
||||
--shadow-lg:0 22px 60px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* theme: editorial-serif — 杂志风衬线,高级 */
|
||||
:root{
|
||||
--bg:#faf7f2;--bg-soft:#f3efe6;--surface:#ffffff;--surface-2:#f7f2e8;
|
||||
--border:rgba(40,28,18,.12);--border-strong:rgba(40,28,18,.24);
|
||||
--text-1:#1b1410;--text-2:#5c4a3e;--text-3:#8a7868;
|
||||
--accent:#8a2a1c;--accent-2:#c97a4a;--accent-3:#1b1410;
|
||||
--good:#3f7d4f;--warn:#b07a1f;--bad:#8a2a1c;
|
||||
--grad:linear-gradient(135deg,#8a2a1c,#c97a4a);
|
||||
--grad-soft:linear-gradient(135deg,#faf7f2,#f3efe6);
|
||||
--radius:4px;--radius-sm:2px;--radius-lg:8px;
|
||||
--shadow:0 2px 12px rgba(40,28,18,.06);
|
||||
--shadow-lg:0 20px 50px rgba(40,28,18,.14);
|
||||
--font-sans:'Playfair Display','Noto Serif SC',serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
--font-serif:'Playfair Display','Noto Serif SC',serif;
|
||||
--letter-tight:-.02em;
|
||||
}
|
||||
.h1,.h2,h1.title,h2.title{font-style:italic;font-weight:600}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* theme: engineering-whiteprint — 工程白图 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f8fafc;--surface:#ffffff;--surface-2:#f4f7fb;
|
||||
--border:rgba(10,30,70,.22);--border-strong:#0a1e46;
|
||||
--text-1:#0a1e46;--text-2:#3a4a6a;--text-3:#8090a8;
|
||||
--accent:#0a1e46;--accent-2:#1e5ac4;--accent-3:#c42a10;
|
||||
--good:#1a6a3a;--warn:#c47a10;--bad:#c42a10;
|
||||
--grad:linear-gradient(135deg,#0a1e46,#1e5ac4);
|
||||
--grad-soft:linear-gradient(135deg,#eaf0fb,#f4f7fb);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:0px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 0 0 1px var(--border-strong);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-mono:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono','Inter',monospace;
|
||||
}
|
||||
body{background:
|
||||
repeating-linear-gradient(0deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
|
||||
repeating-linear-gradient(90deg,rgba(10,30,70,.07) 0 1px,transparent 1px 40px),
|
||||
#ffffff}
|
||||
.card{border:1px solid var(--border-strong);box-shadow:none;background:rgba(255,255,255,.85)}
|
||||
.divider{background:var(--border-strong);height:1px}
|
||||
.divider-accent{background:var(--border-strong);height:1px;width:100%}
|
||||
.kicker{font-family:var(--font-mono);color:var(--accent-2);letter-spacing:.18em}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:600}
|
||||
.pill{font-family:var(--font-mono);border:1px solid var(--border-strong);border-radius:0}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* theme: glassmorphism — 毛玻璃 */
|
||||
:root{
|
||||
--bg:#0b1024;--bg-soft:#0e1530;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||
--border:rgba(255,255,255,.14);--border-strong:rgba(255,255,255,.28);
|
||||
--text-1:#f2f4ff;--text-2:#c3c8e6;--text-3:#8287a8;
|
||||
--accent:#7dd3fc;--accent-2:#c084fc;--accent-3:#f0abfc;
|
||||
--good:#86efac;--warn:#fde68a;--bad:#fca5a5;
|
||||
--grad:linear-gradient(135deg,#7dd3fc,#c084fc 55%,#f0abfc);
|
||||
--grad-soft:linear-gradient(135deg,rgba(125,211,252,.18),rgba(192,132,252,.18));
|
||||
--radius:22px;--radius-sm:14px;--radius-lg:30px;
|
||||
--shadow:0 20px 60px rgba(0,0,0,.35),inset 0 1px 0 rgba(255,255,255,.12);
|
||||
--shadow-lg:0 30px 80px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
radial-gradient(60% 60% at 20% 20%,rgba(125,211,252,.3),transparent 60%),
|
||||
radial-gradient(50% 50% at 80% 30%,rgba(192,132,252,.28),transparent 60%),
|
||||
radial-gradient(60% 60% at 60% 90%,rgba(240,171,252,.25),transparent 60%),
|
||||
#0b1024}
|
||||
.card{backdrop-filter:blur(28px) saturate(180%);-webkit-backdrop-filter:blur(28px) saturate(180%);
|
||||
background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.18)}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: gruvbox-dark */
|
||||
:root{
|
||||
--bg:#282828;--bg-soft:#1d2021;--surface:#3c3836;--surface-2:#504945;
|
||||
--border:rgba(235,219,178,.14);--border-strong:rgba(235,219,178,.28);
|
||||
--text-1:#ebdbb2;--text-2:#d5c4a1;--text-3:#928374;
|
||||
--accent:#fabd2f;--accent-2:#fe8019;--accent-3:#b8bb26;
|
||||
--good:#b8bb26;--warn:#fabd2f;--bad:#fb4934;
|
||||
--grad:linear-gradient(135deg,#fe8019,#fabd2f 55%,#b8bb26);
|
||||
--grad-soft:linear-gradient(135deg,#3c3836,#504945);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:12px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.5);
|
||||
--shadow-lg:0 24px 60px rgba(0,0,0,.65);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* theme: japanese-minimal — 和风极简 */
|
||||
:root{
|
||||
--bg:#fafaf5;--bg-soft:#f2f0e6;--surface:#ffffff;--surface-2:#f5f3ea;
|
||||
--border:rgba(40,30,20,.1);--border-strong:rgba(40,30,20,.3);
|
||||
--text-1:#1a1a18;--text-2:#5c564c;--text-3:#9c958a;
|
||||
--accent:#d93a2a;--accent-2:#1a1a18;--accent-3:#c9a961;
|
||||
--good:#4a6b3e;--warn:#c9a961;--bad:#d93a2a;
|
||||
--grad:linear-gradient(135deg,#d93a2a,#1a1a18);
|
||||
--grad-soft:linear-gradient(135deg,#faeae6,#f5f3ea);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 1px 0 rgba(40,30,20,.12);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-serif:'Noto Serif SC','Playfair Display',serif;
|
||||
--font-display:'Noto Serif SC','Playfair Display',serif;
|
||||
}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:500;letter-spacing:.04em}
|
||||
.card{border:1px solid var(--border);box-shadow:none;padding:36px 40px}
|
||||
.divider-accent{background:var(--accent);height:2px;width:48px}
|
||||
.kicker{color:var(--accent);letter-spacing:.2em}
|
||||
.slide{padding:96px 128px}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* theme: magazine-bold — 杂志大标题 */
|
||||
:root{
|
||||
--bg:#f5efe2;--bg-soft:#ebe4d2;--surface:#fbf6e8;--surface-2:#ede5d0;
|
||||
--border:rgba(10,10,10,.16);--border-strong:#0a0a0a;
|
||||
--text-1:#0a0a0a;--text-2:#2a2a2a;--text-3:#6a6458;
|
||||
--accent:#ea5a1a;--accent-2:#0a0a0a;--accent-3:#c42a10;
|
||||
--good:#2a6a2a;--warn:#ea5a1a;--bad:#c42a10;
|
||||
--grad:linear-gradient(135deg,#ea5a1a,#c42a10);
|
||||
--grad-soft:linear-gradient(135deg,#fbe4d0,#f5d6c0);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:6px 6px 0 var(--accent);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-serif:'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',Georgia,serif;
|
||||
}
|
||||
h1.title,.h1{font-size:120px;line-height:.92;font-weight:900;letter-spacing:-.04em;font-family:var(--font-serif)}
|
||||
h2.title,.h2{font-size:72px;font-weight:800;font-family:var(--font-serif)}
|
||||
.card{border:1.5px solid var(--text-1)}
|
||||
.divider-accent{background:var(--accent);height:6px;width:90px}
|
||||
.kicker{color:var(--accent);text-transform:uppercase;font-weight:700;letter-spacing:.25em}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* theme: memphis-pop — 孟菲斯波普 */
|
||||
:root{
|
||||
--bg:#fef6e8;--bg-soft:#fdebc7;--surface:#ffffff;--surface-2:#fff1d1;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#333333;--text-3:#666666;
|
||||
--accent:#ff3d8b;--accent-2:#37c2d7;--accent-3:#ffcc00;
|
||||
--good:#6ac04c;--warn:#ffcc00;--bad:#ff3d8b;
|
||||
--grad:linear-gradient(135deg,#ff3d8b,#ffcc00 50%,#37c2d7);
|
||||
--grad-soft:linear-gradient(135deg,#fdebc7,#fff1d1);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:18px;
|
||||
--shadow:5px 5px 0 #111;--shadow-lg:9px 9px 0 #111;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
}
|
||||
.card{border:2.5px solid #111}
|
||||
body{background-image:
|
||||
radial-gradient(circle at 10% 20%,#ff3d8b 3px,transparent 4px),
|
||||
radial-gradient(circle at 80% 40%,#37c2d7 3px,transparent 4px),
|
||||
radial-gradient(circle at 30% 80%,#ffcc00 3px,transparent 4px);
|
||||
background-size:200px 200px,220px 220px,260px 260px}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* theme: midcentury — 世纪中期现代 */
|
||||
:root{
|
||||
--bg:#f3ead8;--bg-soft:#ebdfc4;--surface:#f9f2e0;--surface-2:#e8dcbe;
|
||||
--border:rgba(60,40,20,.18);--border-strong:rgba(60,40,20,.4);
|
||||
--text-1:#201810;--text-2:#5a4830;--text-3:#9a8868;
|
||||
--accent:#d4902a;--accent-2:#2a7a7f;--accent-3:#c7502a;
|
||||
--good:#5a7a3a;--warn:#d4902a;--bad:#c7502a;
|
||||
--grad:linear-gradient(135deg,#d4902a,#c7502a 55%,#2a7a7f);
|
||||
--grad-soft:linear-gradient(135deg,#f4e0b6,#eac7a8);
|
||||
--radius:2px;--radius-sm:0px;--radius-lg:4px;
|
||||
--shadow:4px 4px 0 rgba(40,25,10,.12);
|
||||
--shadow-lg:6px 6px 0 rgba(40,25,10,.2),0 10px 24px rgba(40,25,10,.14);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
}
|
||||
.card{border:1.5px solid var(--border-strong)}
|
||||
.divider-accent{background:var(--accent-3);height:4px;width:80px}
|
||||
.kicker{color:var(--accent-2)}
|
||||
h1.title,.h1{color:var(--accent-3)}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* theme: minimal-white — 极简白,克制高级 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#fafafa;--surface:#ffffff;--surface-2:#f5f5f6;
|
||||
--border:rgba(17,18,22,.08);--border-strong:rgba(17,18,22,.16);
|
||||
--text-1:#0c0d10;--text-2:#55596a;--text-3:#9ca1b0;
|
||||
--accent:#111216;--accent-2:#3b3f4a;--accent-3:#6b6f7a;
|
||||
--good:#1aaf6c;--warn:#c98500;--bad:#c13a3a;
|
||||
--grad:linear-gradient(135deg,#111216,#3b3f4a);
|
||||
--grad-soft:linear-gradient(135deg,#f5f5f6,#ffffff);
|
||||
--radius:14px;--radius-sm:8px;--radius-lg:22px;
|
||||
--shadow:0 1px 2px rgba(17,18,22,.04),0 8px 24px rgba(17,18,22,.06);
|
||||
--shadow-lg:0 20px 60px rgba(17,18,22,.1);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
--letter-tight:-.035em;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* theme: neo-brutalism — 厚描边、硬阴影、明黄 */
|
||||
:root{
|
||||
--bg:#fffef0;--bg-soft:#fffbd0;--surface:#ffffff;--surface-2:#fff38a;
|
||||
--border:#000000;--border-strong:#000000;
|
||||
--text-1:#000000;--text-2:#222222;--text-3:#555555;
|
||||
--accent:#ffd400;--accent-2:#ff5ca8;--accent-3:#3a7cff;
|
||||
--good:#00b36b;--warn:#ff9900;--bad:#ff3a30;
|
||||
--grad:linear-gradient(135deg,#ffd400,#ff5ca8);
|
||||
--grad-soft:linear-gradient(135deg,#fffbd0,#fff);
|
||||
--radius:6px;--radius-sm:4px;--radius-lg:10px;
|
||||
--shadow:6px 6px 0 #000;--shadow-lg:10px 10px 0 #000;
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black','Space Grotesk',sans-serif;
|
||||
--letter-tight:-.03em;
|
||||
}
|
||||
.card{border:3px solid #000}
|
||||
.pill{border:2px solid #000;background:#ffd400;color:#000}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* theme: news-broadcast — 新闻播报 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#ececec;
|
||||
--border:rgba(0,0,0,.14);--border-strong:#0a0a0a;
|
||||
--text-1:#0a0a0a;--text-2:#3a3a3a;--text-3:#7a7a7a;
|
||||
--accent:#e11d2d;--accent-2:#0a0a0a;--accent-3:#ffd100;
|
||||
--good:#0e7c3a;--warn:#ffd100;--bad:#e11d2d;
|
||||
--grad:linear-gradient(90deg,#e11d2d 0%,#e11d2d 100%);
|
||||
--grad-soft:linear-gradient(135deg,#fde5e7,#f4f4f4);
|
||||
--radius:0px;--radius-sm:0px;--radius-lg:2px;
|
||||
--shadow:none;
|
||||
--shadow-lg:0 4px 0 var(--accent);
|
||||
--font-sans:'Oswald','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Oswald','Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
h1.title,h2.title,.h1,.h2{font-weight:700;text-transform:uppercase;letter-spacing:-.01em}
|
||||
.card{border:2px solid var(--text-1);box-shadow:6px 6px 0 var(--accent)}
|
||||
.divider-accent{background:var(--accent);height:6px;width:100%}
|
||||
.kicker{background:var(--accent);color:#fff;padding:4px 12px;display:inline-block;letter-spacing:.15em}
|
||||
.slide::before{content:"";position:absolute;left:0;top:0;bottom:0;width:8px;background:var(--accent);z-index:3}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: nord */
|
||||
:root{
|
||||
--bg:#2e3440;--bg-soft:#272b35;--surface:#3b4252;--surface-2:#434c5e;
|
||||
--border:rgba(236,239,244,.12);--border-strong:rgba(236,239,244,.24);
|
||||
--text-1:#eceff4;--text-2:#d8dee9;--text-3:#7b8394;
|
||||
--accent:#88c0d0;--accent-2:#81a1c1;--accent-3:#b48ead;
|
||||
--good:#a3be8c;--warn:#ebcb8b;--bad:#bf616a;
|
||||
--grad:linear-gradient(135deg,#88c0d0,#81a1c1 50%,#b48ead);
|
||||
--grad-soft:linear-gradient(135deg,#3b4252,#434c5e);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:20px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.35);
|
||||
--shadow-lg:0 22px 60px rgba(0,0,0,.5);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* theme: pitch-deck-vc — YC 风融资 pitch */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#fafbfc;--surface:#ffffff;--surface-2:#f5f7fa;
|
||||
--border:rgba(20,30,50,.1);--border-strong:rgba(20,30,50,.22);
|
||||
--text-1:#0b0d12;--text-2:#4a5270;--text-3:#8b93a8;
|
||||
--accent:#0070f3;--accent-2:#7928ca;--accent-3:#ff4ecb;
|
||||
--good:#0cce6b;--warn:#f5a524;--bad:#ee0000;
|
||||
--grad:linear-gradient(135deg,#0070f3,#7928ca);
|
||||
--grad-soft:linear-gradient(135deg,#e8f0ff,#f3e8ff);
|
||||
--radius:14px;--radius-sm:8px;--radius-lg:22px;
|
||||
--shadow:0 2px 8px rgba(20,30,50,.06),0 12px 32px rgba(20,30,50,.06);
|
||||
--shadow-lg:0 8px 24px rgba(20,30,50,.1),0 30px 80px rgba(20,30,50,.1);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.slide{padding:88px 120px}
|
||||
h1.title,.h1{font-weight:800;letter-spacing:-.035em}
|
||||
h1.title .gradient-text,.h1 .gradient-text{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.card{border:1px solid var(--border)}
|
||||
.divider-accent{background:var(--grad);height:4px;width:64px;border-radius:2px}
|
||||
.kicker{background:var(--grad);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* theme: rainbow-gradient — 彩虹渐变点缀(白底) */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f8f8fb;--surface:#ffffff;--surface-2:#f4f4f8;
|
||||
--border:rgba(20,20,40,.08);--border-strong:rgba(20,20,40,.2);
|
||||
--text-1:#0c0d10;--text-2:#4d5162;--text-3:#9096a8;
|
||||
--accent:#ff4d8b;--accent-2:#7a5cff;--accent-3:#36b6ff;
|
||||
--good:#1aaf6c;--warn:#f5a524;--bad:#e0445a;
|
||||
--grad:linear-gradient(90deg,#ff0080,#ff4d00,#ff9900,#ffe600,#00c853,#0091ea,#6200ea,#ff0080);
|
||||
--grad-soft:linear-gradient(135deg,#fff,#f8f8fb);
|
||||
--radius:16px;--radius-sm:10px;--radius-lg:24px;
|
||||
--shadow:0 12px 32px rgba(124,92,255,.1);
|
||||
--shadow-lg:0 24px 60px rgba(124,92,255,.18);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
.gradient-text{background-size:200% auto;animation:rbflow 6s linear infinite}
|
||||
@keyframes rbflow{to{background-position:200% 0}}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* theme: retro-tv — 复古显像管 */
|
||||
:root{
|
||||
--bg:#f5ecd7;--bg-soft:#efe4c6;--surface:#fbf5e2;--surface-2:#efe3c2;
|
||||
--border:rgba(120,70,20,.22);--border-strong:rgba(120,70,20,.45);
|
||||
--text-1:#2a1a08;--text-2:#6b4a22;--text-3:#a68656;
|
||||
--accent:#e67e14;--accent-2:#c73a1f;--accent-3:#f2b544;
|
||||
--good:#3e8940;--warn:#e67e14;--bad:#c73a1f;
|
||||
--grad:linear-gradient(135deg,#c73a1f,#e67e14 55%,#f2b544);
|
||||
--grad-soft:linear-gradient(135deg,#fde6c4,#fbd9a0);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:16px;
|
||||
--shadow:0 6px 0 rgba(80,40,0,.12),0 12px 28px rgba(80,40,0,.15);
|
||||
--shadow-lg:0 10px 0 rgba(80,40,0,.15),0 24px 50px rgba(80,40,0,.2);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Playfair Display','Noto Serif SC',serif;
|
||||
}
|
||||
body{background:
|
||||
repeating-linear-gradient(0deg,rgba(80,40,0,.06) 0 2px,transparent 2px 4px),
|
||||
radial-gradient(ellipse at center,#f7ecd0 0%,#e8d9b0 85%,#c9b888 100%)}
|
||||
.slide::before{content:"";position:absolute;inset:0;pointer-events:none;
|
||||
background:repeating-linear-gradient(0deg,rgba(0,0,0,.035) 0 2px,transparent 2px 4px);z-index:1}
|
||||
.slide > *{position:relative;z-index:2}
|
||||
h1.title,.h1{color:var(--accent-2)}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: rose-pine */
|
||||
:root{
|
||||
--bg:#191724;--bg-soft:#1f1d2e;--surface:#26233a;--surface-2:#2a2740;
|
||||
--border:rgba(224,222,244,.12);--border-strong:rgba(224,222,244,.24);
|
||||
--text-1:#e0def4;--text-2:#c4b8d8;--text-3:#6e6a86;
|
||||
--accent:#ebbcba;--accent-2:#c4a7e7;--accent-3:#9ccfd8;
|
||||
--good:#31748f;--warn:#f6c177;--bad:#eb6f92;
|
||||
--grad:linear-gradient(135deg,#ebbcba,#c4a7e7 55%,#9ccfd8);
|
||||
--grad-soft:linear-gradient(135deg,#26233a,#2a2740);
|
||||
--radius:14px;--radius-sm:10px;--radius-lg:22px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.4);
|
||||
--shadow-lg:0 22px 58px rgba(0,0,0,.55);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* theme: sharp-mono — 锐利黑白高对比 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#ffffff;--surface:#ffffff;--surface-2:#000000;
|
||||
--border:#000000;--border-strong:#000000;
|
||||
--text-1:#000000;--text-2:#1a1a1a;--text-3:#4a4a4a;
|
||||
--accent:#000000;--accent-2:#000000;--accent-3:#ff2200;
|
||||
--good:#008800;--warn:#ff9900;--bad:#ff0000;
|
||||
--grad:linear-gradient(135deg,#000,#222);
|
||||
--grad-soft:linear-gradient(135deg,#fff,#eee);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:4px 4px 0 #000;--shadow-lg:8px 8px 0 #000;
|
||||
--font-sans:'Archivo Black','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Archivo Black',sans-serif;
|
||||
--letter-tight:-.04em;
|
||||
}
|
||||
.h1,.h2,h1.title,h2.title{text-transform:uppercase}
|
||||
.card{border:2px solid #000}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: soft-pastel — 柔和马卡龙 */
|
||||
:root{
|
||||
--bg:#fdf7fb;--bg-soft:#fbeef3;--surface:#ffffff;--surface-2:#fdf0f5;
|
||||
--border:rgba(120,70,110,.12);--border-strong:rgba(120,70,110,.22);
|
||||
--text-1:#3a1f33;--text-2:#6b4d62;--text-3:#a28a99;
|
||||
--accent:#f49bb8;--accent-2:#b5d5f0;--accent-3:#f7d08a;
|
||||
--good:#9dd9a3;--warn:#f7d08a;--bad:#ef9a9a;
|
||||
--grad:linear-gradient(135deg,#f49bb8,#b5d5f0 55%,#c4a0e8);
|
||||
--grad-soft:linear-gradient(135deg,#fbeef3,#eaf4fc);
|
||||
--radius:24px;--radius-sm:16px;--radius-lg:32px;
|
||||
--shadow:0 8px 28px rgba(244,155,184,.18);
|
||||
--shadow-lg:0 24px 70px rgba(181,213,240,.3);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: solarized-light */
|
||||
:root{
|
||||
--bg:#fdf6e3;--bg-soft:#eee8d5;--surface:#ffffff;--surface-2:#f5efd7;
|
||||
--border:rgba(88,110,117,.2);--border-strong:rgba(88,110,117,.4);
|
||||
--text-1:#073642;--text-2:#586e75;--text-3:#93a1a1;
|
||||
--accent:#268bd2;--accent-2:#2aa198;--accent-3:#d33682;
|
||||
--good:#859900;--warn:#b58900;--bad:#dc322f;
|
||||
--grad:linear-gradient(135deg,#268bd2,#2aa198 50%,#859900);
|
||||
--grad-soft:linear-gradient(135deg,#fdf6e3,#eee8d5);
|
||||
--radius:10px;--radius-sm:6px;--radius-lg:16px;
|
||||
--shadow:0 6px 20px rgba(88,110,117,.14);
|
||||
--shadow-lg:0 18px 50px rgba(88,110,117,.24);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: sunset-warm — 暖色调 橘/珊瑚/琥珀 */
|
||||
:root{
|
||||
--bg:#fff7ef;--bg-soft:#ffeedc;--surface:#ffffff;--surface-2:#fff2e0;
|
||||
--border:rgba(120,60,20,.12);--border-strong:rgba(120,60,20,.22);
|
||||
--text-1:#2a160a;--text-2:#6b4630;--text-3:#a28572;
|
||||
--accent:#e36a2d;--accent-2:#f2a341;--accent-3:#d94860;
|
||||
--good:#5ea35a;--warn:#f2a341;--bad:#d94860;
|
||||
--grad:linear-gradient(135deg,#d94860,#e36a2d 50%,#f2a341);
|
||||
--grad-soft:linear-gradient(135deg,#ffeedc,#ffe0d0);
|
||||
--radius:18px;--radius-sm:12px;--radius-lg:28px;
|
||||
--shadow:0 12px 32px rgba(227,106,45,.16);
|
||||
--shadow-lg:0 24px 64px rgba(227,106,45,.22);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/* theme: swiss-grid — 瑞士网格,Helvetica 感 */
|
||||
:root{
|
||||
--bg:#ffffff;--bg-soft:#f4f4f4;--surface:#ffffff;--surface-2:#f4f4f4;
|
||||
--border:#111111;--border-strong:#111111;
|
||||
--text-1:#111111;--text-2:#444444;--text-3:#888888;
|
||||
--accent:#d6001c;--accent-2:#111111;--accent-3:#888888;
|
||||
--good:#0f8a2f;--warn:#d38a00;--bad:#d6001c;
|
||||
--grad:linear-gradient(135deg,#d6001c,#111);
|
||||
--grad-soft:linear-gradient(135deg,#f4f4f4,#fff);
|
||||
--radius:0;--radius-sm:0;--radius-lg:0;
|
||||
--shadow:none;--shadow-lg:none;
|
||||
--font-sans:'Inter','Helvetica Neue',Helvetica,'Noto Sans SC',sans-serif;
|
||||
--font-display:'Inter','Helvetica Neue',Helvetica,sans-serif;
|
||||
--letter-tight:-.04em;
|
||||
}
|
||||
.card{border-top:2px solid #111;border-bottom:1px solid #111;border-left:none;border-right:none;box-shadow:none;background:#fff}
|
||||
.slide{background-image:linear-gradient(90deg,rgba(0,0,0,.04) 1px,transparent 1px);background-size:calc(100%/12) 100%}
|
||||
@@ -0,0 +1,18 @@
|
||||
/* theme: terminal-green — 绿屏终端 */
|
||||
:root{
|
||||
--bg:#030a04;--bg-soft:#041308;--surface:#0a1b10;--surface-2:#0d2614;
|
||||
--border:rgba(0,255,120,.22);--border-strong:rgba(0,255,120,.42);
|
||||
--text-1:#8cff9a;--text-2:#4bd17a;--text-3:#2f8a4d;
|
||||
--accent:#00ff88;--accent-2:#67ffd0;--accent-3:#b6ff6b;
|
||||
--good:#00ff88;--warn:#ffe066;--bad:#ff6464;
|
||||
--grad:linear-gradient(135deg,#00ff88,#67ffd0);
|
||||
--grad-soft:linear-gradient(135deg,#0a1b10,#0d2614);
|
||||
--radius:4px;--radius-sm:2px;--radius-lg:8px;
|
||||
--shadow:0 0 30px rgba(0,255,136,.15);
|
||||
--shadow-lg:0 0 60px rgba(0,255,136,.28);
|
||||
--font-sans:'JetBrains Mono','IBM Plex Mono',monospace;
|
||||
--font-display:'JetBrains Mono',monospace;
|
||||
--letter-tight:-.01em;
|
||||
}
|
||||
body{text-shadow:0 0 2px rgba(0,255,136,.5)}
|
||||
.card{border:1px solid rgba(0,255,120,.3);background:rgba(10,27,16,.6)}
|
||||
@@ -0,0 +1,14 @@
|
||||
/* theme: tokyo-night */
|
||||
:root{
|
||||
--bg:#1a1b26;--bg-soft:#16161e;--surface:#24283b;--surface-2:#2f334d;
|
||||
--border:rgba(192,202,245,.12);--border-strong:rgba(192,202,245,.24);
|
||||
--text-1:#c0caf5;--text-2:#a9b1d6;--text-3:#565f89;
|
||||
--accent:#7aa2f7;--accent-2:#bb9af7;--accent-3:#7dcfff;
|
||||
--good:#9ece6a;--warn:#e0af68;--bad:#f7768e;
|
||||
--grad:linear-gradient(135deg,#7aa2f7,#bb9af7 55%,#f7768e);
|
||||
--grad-soft:linear-gradient(135deg,#24283b,#2f334d);
|
||||
--radius:12px;--radius-sm:8px;--radius-lg:20px;
|
||||
--shadow:0 10px 30px rgba(0,0,0,.45);
|
||||
--shadow-lg:0 24px 62px rgba(0,0,0,.6);
|
||||
--font-sans:'Inter','Noto Sans SC',sans-serif;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* theme: vaporwave — 蒸汽波 */
|
||||
:root{
|
||||
--bg:#1a0938;--bg-soft:#261050;--surface:rgba(255,255,255,.06);--surface-2:rgba(255,255,255,.1);
|
||||
--border:rgba(255,110,199,.28);--border-strong:rgba(0,245,255,.5);
|
||||
--text-1:#fdf0ff;--text-2:#d4a8e8;--text-3:#8a6ba8;
|
||||
--accent:#ff6ec7;--accent-2:#00f5ff;--accent-3:#ffd166;
|
||||
--grad:linear-gradient(135deg,#ff6ec7 0%,#c94fff 35%,#00f5ff 100%);
|
||||
--grad-soft:linear-gradient(135deg,rgba(255,110,199,.25),rgba(0,245,255,.25));
|
||||
--radius:18px;--radius-sm:10px;--radius-lg:28px;
|
||||
--shadow:0 20px 60px rgba(255,110,199,.2),0 0 1px rgba(0,245,255,.6);
|
||||
--shadow-lg:0 30px 80px rgba(255,110,199,.3),0 0 2px rgba(0,245,255,.8);
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Space Grotesk','Inter',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
linear-gradient(180deg,#1a0938 0%,#3a0f5c 45%,#7a1f6b 85%,#e85d9c 100%),
|
||||
radial-gradient(ellipse at 50% 80%,rgba(0,245,255,.3),transparent 60%)}
|
||||
h1.title,.h1{background:var(--grad);-webkit-background-clip:text;background-clip:text;
|
||||
-webkit-text-fill-color:transparent;color:transparent}
|
||||
.card{backdrop-filter:blur(18px)}
|
||||
.divider-accent{background:var(--grad);height:4px;width:120px;box-shadow:0 0 20px var(--accent)}
|
||||
@@ -0,0 +1,16 @@
|
||||
/* theme: xiaohongshu-white — 小红书白底高级感 */
|
||||
:root{
|
||||
--bg:#fffdfb;--bg-soft:#fff6f1;--surface:#ffffff;--surface-2:#fff1ea;
|
||||
--border:rgba(60,30,20,.1);--border-strong:rgba(60,30,20,.22);
|
||||
--text-1:#1a1210;--text-2:#4f3a32;--text-3:#a08d85;
|
||||
--accent:#ff2742;--accent-2:#ff7a90;--accent-3:#ffb38a;
|
||||
--good:#3ba55c;--warn:#f5a524;--bad:#ff2742;
|
||||
--grad:linear-gradient(135deg,#ff2742,#ff7a90 55%,#ffb38a);
|
||||
--grad-soft:linear-gradient(135deg,#fff6f1,#ffeae0);
|
||||
--radius:20px;--radius-sm:14px;--radius-lg:28px;
|
||||
--shadow:0 12px 30px rgba(255,39,66,.08);
|
||||
--shadow-lg:0 24px 60px rgba(255,39,66,.14);
|
||||
--font-sans:'Noto Sans SC','Inter',sans-serif;
|
||||
--font-display:'Noto Serif SC','Playfair Display',serif;
|
||||
--letter-tight:-.02em;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/* theme: y2k-chrome — 千禧银色铬金属 */
|
||||
:root{
|
||||
--bg:#dfe4ec;--bg-soft:#eef1f6;--surface:rgba(255,255,255,.72);--surface-2:rgba(255,255,255,.5);
|
||||
--border:rgba(120,135,170,.32);--border-strong:rgba(80,100,140,.55);
|
||||
--text-1:#1a1f2e;--text-2:#4a536a;--text-3:#8590a6;
|
||||
--accent:#8a5cff;--accent-2:#3ccfd8;--accent-3:#ff84c4;
|
||||
--grad:linear-gradient(135deg,#b8c4d8 0%,#f5f7fb 30%,#8a9ab8 55%,#e8ecf4 80%,#6b7a95 100%);
|
||||
--grad-soft:linear-gradient(135deg,#c9e4ff,#f5d6ff 50%,#d6fffa);
|
||||
--radius:26px;--radius-sm:16px;--radius-lg:36px;
|
||||
--shadow:0 12px 30px rgba(70,90,130,.22),inset 0 1px 0 rgba(255,255,255,.9),inset 0 -1px 0 rgba(80,100,140,.2);
|
||||
--shadow-lg:0 24px 60px rgba(70,90,130,.35),inset 0 2px 0 rgba(255,255,255,.95);
|
||||
--font-sans:'Space Grotesk','Inter','Noto Sans SC',sans-serif;
|
||||
--font-display:'Space Grotesk','Inter',sans-serif;
|
||||
}
|
||||
body{background:
|
||||
linear-gradient(135deg,#c4cfe0 0%,#f0f3f8 25%,#aab8d0 50%,#f5f7fb 75%,#b8c4d8 100%)}
|
||||
h1.title,.h1{background:linear-gradient(180deg,#f8faff 0%,#9aa8c4 50%,#4a5670 100%);
|
||||
-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent}
|
||||
.card{backdrop-filter:blur(16px) saturate(140%);-webkit-backdrop-filter:blur(16px) saturate(140%)}
|
||||
.pill{background:linear-gradient(180deg,#fff,#d4dcec);border-color:rgba(120,135,170,.4)}
|
||||
Reference in New Issue
Block a user