fix(web-ui): orange pulse logo (bigger, no canvas), fix library missing expandedBins state
This commit is contained in:
parent
02d502baaf
commit
4e6142f455
3 changed files with 31 additions and 159 deletions
|
|
@ -17,146 +17,6 @@
|
||||||
//
|
//
|
||||||
// Anything that would just say "all clear" is hidden, not rendered.
|
// Anything that would just say "all clear" is hidden, not rendered.
|
||||||
|
|
||||||
// screens-home.jsx
|
|
||||||
//
|
|
||||||
// Two routes share this file:
|
|
||||||
//
|
|
||||||
// • Home - the launcher. Big-button entry into each section of the MAM.
|
|
||||||
//
|
|
||||||
// • Dashboard - the operations view. Rebuilt as a control-room status board.
|
|
||||||
|
|
||||||
// ─── DragonFlame ─────────────────────────────────────────────
|
|
||||||
// Canvas-based particle flame rendered behind the logo. Each particle rises
|
|
||||||
// from the base, fades as it climbs, and shifts hue from deep orange → yellow.
|
|
||||||
// Spectral shimmer is added via a secondary "spark" layer with lighter colors.
|
|
||||||
function DragonFlame() {
|
|
||||||
const canvasRef = React.useRef(null);
|
|
||||||
const rafRef = React.useRef(null);
|
|
||||||
|
|
||||||
React.useEffect(function() {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
if (!canvas) return;
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Particle pool
|
|
||||||
const W = 160, H = 200;
|
|
||||||
canvas.width = W;
|
|
||||||
canvas.height = H;
|
|
||||||
|
|
||||||
// Particle factory
|
|
||||||
function mkParticle(isSpark) {
|
|
||||||
const x = W * 0.5 + (Math.random() - 0.5) * (isSpark ? 30 : 50);
|
|
||||||
return {
|
|
||||||
x,
|
|
||||||
y: H - 10 - Math.random() * 20,
|
|
||||||
vx: (Math.random() - 0.5) * (isSpark ? 0.6 : 0.9),
|
|
||||||
vy: -(0.8 + Math.random() * (isSpark ? 2.2 : 1.6)),
|
|
||||||
life: 0,
|
|
||||||
maxLife: 50 + Math.random() * (isSpark ? 30 : 50),
|
|
||||||
size: isSpark ? 1 + Math.random() * 2 : 3 + Math.random() * 5,
|
|
||||||
spark: isSpark,
|
|
||||||
wobble: (Math.random() - 0.5) * 0.04,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const COUNT = 90, SPARK_COUNT = 30;
|
|
||||||
const particles = Array.from({ length: COUNT }, function() { return mkParticle(false); });
|
|
||||||
const sparks = Array.from({ length: SPARK_COUNT }, function() { return mkParticle(true); });
|
|
||||||
|
|
||||||
function reset(p) {
|
|
||||||
var n = mkParticle(p.spark);
|
|
||||||
Object.assign(p, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
function draw() {
|
|
||||||
ctx.clearRect(0, 0, W, H);
|
|
||||||
|
|
||||||
// Draw glow base
|
|
||||||
const grad = ctx.createRadialGradient(W * 0.5, H - 5, 0, W * 0.5, H - 5, 55);
|
|
||||||
grad.addColorStop(0, 'rgba(255,120,0,0.18)');
|
|
||||||
grad.addColorStop(0.5, 'rgba(255,70,0,0.07)');
|
|
||||||
grad.addColorStop(1, 'transparent');
|
|
||||||
ctx.fillStyle = grad;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(W * 0.5, H - 5, 55, 30, 0, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
// Update + draw each particle
|
|
||||||
var all = particles.concat(sparks);
|
|
||||||
all.forEach(function(p) {
|
|
||||||
p.life += 1;
|
|
||||||
if (p.life >= p.maxLife) { reset(p); return; }
|
|
||||||
|
|
||||||
var t = p.life / p.maxLife; // 0 → 1
|
|
||||||
// Gentle horizontal drift (wobble)
|
|
||||||
p.vx += p.wobble;
|
|
||||||
p.vx *= 0.98;
|
|
||||||
p.x += p.vx;
|
|
||||||
p.y += p.vy;
|
|
||||||
// Decelerate rise near end
|
|
||||||
p.vy *= 0.99;
|
|
||||||
|
|
||||||
var alpha = p.spark
|
|
||||||
? Math.sin(t * Math.PI) * 0.85
|
|
||||||
: (t < 0.2 ? t / 0.2 : 1 - (t - 0.2) / 0.8) * 0.7;
|
|
||||||
|
|
||||||
// Colour: deep orange (0°) → orange (20°) → yellow (50°) as t rises
|
|
||||||
var hue = p.spark
|
|
||||||
? 40 + t * 20 // sparks: golden yellow
|
|
||||||
: 10 + t * 45; // flame: orange→yellow
|
|
||||||
var sat = 100;
|
|
||||||
var lgt = p.spark ? 70 + t * 20 : 50 + t * 20;
|
|
||||||
|
|
||||||
ctx.save();
|
|
||||||
ctx.globalAlpha = alpha;
|
|
||||||
if (p.spark) {
|
|
||||||
// Sparks: small bright dots
|
|
||||||
ctx.fillStyle = 'hsl(' + hue + ',' + sat + '%,' + lgt + '%)';
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(p.x, p.y, p.size * (1 - t * 0.5), 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
} else {
|
|
||||||
// Flame particles: soft blurred ellipses
|
|
||||||
var size = p.size * (1 - t * 0.6);
|
|
||||||
var g2 = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, size * 2.2);
|
|
||||||
g2.addColorStop(0, 'hsla(' + hue + ',' + sat + '%,' + lgt + '%,1)');
|
|
||||||
g2.addColorStop(0.4, 'hsla(' + (hue - 8) + ',' + sat + '%,' + (lgt - 10) + '%,0.5)');
|
|
||||||
g2.addColorStop(1, 'transparent');
|
|
||||||
ctx.fillStyle = g2;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.ellipse(p.x, p.y, size * 2.2, size * 3.5, 0, 0, Math.PI * 2);
|
|
||||||
ctx.fill();
|
|
||||||
}
|
|
||||||
ctx.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
rafRef.current = requestAnimationFrame(draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check reduced-motion preference
|
|
||||||
var mq = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
||||||
if (mq && mq.matches) {
|
|
||||||
// Static glow only
|
|
||||||
var sg = ctx.createRadialGradient(W * 0.5, H * 0.65, 0, W * 0.5, H * 0.65, 80);
|
|
||||||
sg.addColorStop(0, 'rgba(255,130,0,0.22)');
|
|
||||||
sg.addColorStop(0.6, 'rgba(255,70,0,0.08)');
|
|
||||||
sg.addColorStop(1, 'transparent');
|
|
||||||
ctx.fillStyle = sg;
|
|
||||||
ctx.fillRect(0, 0, W, H);
|
|
||||||
} else {
|
|
||||||
draw();
|
|
||||||
}
|
|
||||||
|
|
||||||
return function() {
|
|
||||||
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return React.createElement('span', { className: 'launcher-logo-pulse' },
|
|
||||||
React.createElement('canvas', { ref: canvasRef })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Home({ navigate }) {
|
function Home({ navigate }) {
|
||||||
const [showDownloads, setShowDownloads] = React.useState(false);
|
const [showDownloads, setShowDownloads] = React.useState(false);
|
||||||
|
|
||||||
|
|
@ -274,7 +134,7 @@ function Home({ navigate }) {
|
||||||
<div className="launcher-inner">
|
<div className="launcher-inner">
|
||||||
<div className="launcher-hero">
|
<div className="launcher-hero">
|
||||||
<span className="launcher-logo-wrap">
|
<span className="launcher-logo-wrap">
|
||||||
<DragonFlame />
|
<span className="launcher-logo-pulse" aria-hidden="true" />
|
||||||
<img
|
<img
|
||||||
className="launcher-logo"
|
className="launcher-logo"
|
||||||
src="img/dragon-logo.png"
|
src="img/dragon-logo.png"
|
||||||
|
|
|
||||||
|
|
@ -28,20 +28,31 @@ function Library({ navigate, onOpenAsset, openProject, onClearProject }) {
|
||||||
return function() { window.removeEventListener('df:bins-changed', onBinsChanged); };
|
return function() { window.removeEventListener('df:bins-changed', onBinsChanged); };
|
||||||
}, [refreshBins]);
|
}, [refreshBins]);
|
||||||
|
|
||||||
|
const [creatingChildOf, setCreatingChildOf] = React.useState(null);
|
||||||
|
const [expandedBins, setExpandedBins] = React.useState(new Set());
|
||||||
|
|
||||||
const createBin = () => {
|
const createBin = () => {
|
||||||
if (!openProject) { window.alert('Open a project first (Projects → click a project), then create a bin inside it.'); return; }
|
if (!openProject) { window.alert('Open a project first (Projects → click a project), then create a bin inside it.'); return; }
|
||||||
setNewBinName(''); setCreatingBin(true);
|
setCreatingChildOf(null); setNewBinName(''); setCreatingBin(true);
|
||||||
|
};
|
||||||
|
const createSubBin = (parentId) => {
|
||||||
|
if (!openProject) return;
|
||||||
|
setCreatingChildOf(parentId); setNewBinName(''); setCreatingBin(true);
|
||||||
|
};
|
||||||
|
const toggleBinExpanded = (binId) => {
|
||||||
|
setExpandedBins(prev => { const s = new Set(prev); s.has(binId) ? s.delete(binId) : s.add(binId); return s; });
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitBin = (name) => {
|
const submitBin = (name) => {
|
||||||
if (!name || !name.trim()) { setCreatingBin(false); return; }
|
if (!name || !name.trim()) { setCreatingBin(false); setCreatingChildOf(null); return; }
|
||||||
setCreatingBin(false);
|
setCreatingBin(false);
|
||||||
|
const parentId = creatingChildOf;
|
||||||
|
setCreatingChildOf(null);
|
||||||
window.ZAMPP_API.fetch('/bins', {
|
window.ZAMPP_API.fetch('/bins', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ project_id: openProject.id, name: name.trim() }),
|
body: JSON.stringify({ project_id: openProject.id, name: name.trim(), parent_id: parentId || null }),
|
||||||
})
|
})
|
||||||
.then(() => window.ZAMPP_API.fetch('/bins?project_id=' + openProject.id))
|
.then(() => window.ZAMPP_API.fetch('/bins?project_id=' + openProject.id))
|
||||||
.then(list => setBins((list || []).map(b => ({ ...b, count: b.asset_count || 0, icon: b.type || 'grid' }))))
|
.then(list => { const n = (list||[]).map(b=>({...b,count:b.asset_count||0,icon:b.type||'grid'})); setBins(n); if (parentId) setExpandedBins(prev => { const s=new Set(prev); s.add(parentId); return s; }); })
|
||||||
.catch(e => window.alert('Could not create bin: ' + e.message));
|
.catch(e => window.alert('Could not create bin: ' + e.message));
|
||||||
};
|
};
|
||||||
const [view, setView] = React.useState('grid');
|
const [view, setView] = React.useState('grid');
|
||||||
|
|
|
||||||
|
|
@ -292,37 +292,38 @@
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
/* Logo wrapper holds the animated pulse halo behind the image. */
|
/* Logo wrapper — large hero with orange pulse halo. */
|
||||||
.launcher-logo-wrap {
|
.launcher-logo-wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-grid;
|
display: inline-grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
width: 52px;
|
width: 120px;
|
||||||
height: 52px;
|
height: 120px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.launcher-logo-pulse {
|
.launcher-logo-pulse {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
width: 80px;
|
width: 180px;
|
||||||
height: 80px;
|
height: 180px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: radial-gradient(circle, var(--accent-soft) 0%, transparent 70%);
|
background: radial-gradient(circle, rgba(232, 130, 28, 0.35) 0%, rgba(232, 130, 28, 0.08) 55%, transparent 70%);
|
||||||
animation: logoPulse 3s ease-in-out infinite;
|
animation: logoPulse 2.8s ease-in-out infinite;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
@keyframes logoPulse {
|
@keyframes logoPulse {
|
||||||
0%, 100% { transform: scale(1); opacity: 0.6; }
|
0%, 100% { transform: scale(1); opacity: 0.7; }
|
||||||
50% { transform: scale(1.15); opacity: 1; }
|
50% { transform: scale(1.18); opacity: 1; }
|
||||||
}
|
}
|
||||||
.launcher-logo {
|
.launcher-logo {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
width: 52px;
|
width: 110px;
|
||||||
height: 52px;
|
height: 110px;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
filter:
|
filter:
|
||||||
brightness(0) invert(1)
|
brightness(0) invert(1)
|
||||||
drop-shadow(0 0 8px rgba(232, 130, 28, 0.35));
|
drop-shadow(0 0 14px rgba(232, 130, 28, 0.6))
|
||||||
|
drop-shadow(0 0 4px rgba(255, 180, 60, 0.4));
|
||||||
animation: launcherLogoIn 400ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
animation: launcherLogoIn 400ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
||||||
}
|
}
|
||||||
@keyframes launcherLogoIn {
|
@keyframes launcherLogoIn {
|
||||||
|
|
@ -330,7 +331,7 @@
|
||||||
to { opacity: 1; transform: scale(1); }
|
to { opacity: 1; transform: scale(1); }
|
||||||
}
|
}
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.launcher-logo-pulse { animation: none; opacity: 0.5; }
|
.launcher-logo-pulse { animation: none; opacity: 0.6; }
|
||||||
.launcher-logo { animation: none; }
|
.launcher-logo { animation: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue