UI polish: CRT scanlines, glow effects, boot animation
Pure CSS visual upgrades — zero JS overhead, no feature changes: - CRT scanline overlay via body::after repeating gradient - Boot-in animation (brightness flash + blur fade) - Title flicker on load, persistent text-shadow glow - Pulsing glow on active radio dots and connection indicator - Shimmer gradient on progress bar - Canvas sweep cursor and sparkline glow via shadowBlur - Gradient fills on sparkline charts - Hover states on cards, metric tiles, buttons (glow + scale) - Custom thin scrollbar on log panel - Tabular-nums for jitter-free metric updates - Version badge in header Made-with: Cursor
This commit is contained in:
80
src/main.cpp
80
src/main.cpp
@@ -978,25 +978,30 @@ const char kHtml[] = R"HTML(
|
|||||||
<title>CC1101 JAMMER</title><style>
|
<title>CC1101 JAMMER</title><style>
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
html,body{background:#020504;color:#86f28a;font-family:'Courier New',monospace;font-size:12px;line-height:1.4}
|
html,body{background:#020504;color:#86f28a;font-family:'Courier New',monospace;font-size:12px;line-height:1.4}
|
||||||
header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #1a3a1e;background:#030705}
|
body::after{content:'';position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:9999;background:repeating-linear-gradient(0deg,transparent,transparent 2px,rgba(0,0,0,.08) 2px,rgba(0,0,0,.08) 4px);opacity:.6}
|
||||||
h1{font-size:17px;letter-spacing:.12em;color:#a0f5a4}
|
header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #1a3a1e;background:linear-gradient(180deg,#040a06 0%,#030705 100%)}
|
||||||
|
h1{font-size:17px;letter-spacing:.12em;color:#a0f5a4;text-shadow:0 0 8px rgba(134,242,138,.4)}
|
||||||
.sub{font-size:10px;color:#4fbf59;margin-top:2px}
|
.sub{font-size:10px;color:#4fbf59;margin-top:2px}
|
||||||
#progWrap{height:3px;background:#060e07}
|
#progWrap{height:3px;background:#060e07}
|
||||||
#progBar{height:3px;background:#4fbf59;width:0;transition:width .8s linear}
|
#progBar{height:3px;background:linear-gradient(90deg,#2a8a2e,#86f28a,#2a8a2e);background-size:200% 100%;width:0;transition:width .8s linear;animation:progShimmer 3s linear infinite}
|
||||||
|
@keyframes progShimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
|
||||||
main{padding:10px;max-width:920px;margin:0 auto;display:flex;flex-direction:column;gap:8px}
|
main{padding:10px;max-width:920px;margin:0 auto;display:flex;flex-direction:column;gap:8px}
|
||||||
.card{background:#030705;border:1px solid #1a3a1e;padding:10px}
|
.card{background:#030705;border:1px solid #1a3a1e;padding:10px;transition:border-color .3s}
|
||||||
|
.card:hover{border-color:#2a5a2e}
|
||||||
.card h2{font-size:10px;color:#4fbf59;letter-spacing:.12em;text-transform:uppercase;border-bottom:1px solid #122814;padding-bottom:5px;margin-bottom:8px}
|
.card h2{font-size:10px;color:#4fbf59;letter-spacing:.12em;text-transform:uppercase;border-bottom:1px solid #122814;padding-bottom:5px;margin-bottom:8px}
|
||||||
.banner{padding:14px;text-align:center;border:2px solid #1a3a1e}
|
.banner{padding:14px;text-align:center;border:2px solid #1a3a1e;transition:all .4s ease}
|
||||||
.bt{font-size:22px;font-weight:bold;letter-spacing:.18em}
|
.bt{font-size:22px;font-weight:bold;letter-spacing:.18em}
|
||||||
.bs{font-size:11px;margin-top:5px}
|
.bs{font-size:11px;margin-top:5px}
|
||||||
.ban-on{border-color:#4fbf59;background:#060f07}
|
.ban-on{border-color:#4fbf59;background:radial-gradient(ellipse at center,#0a1a0c 0%,#060f07 70%)}
|
||||||
.ban-off{border-color:#3a1218;background:#030504}
|
.ban-off{border-color:#3a1218;background:#030504}
|
||||||
@keyframes pulse{0%,100%{box-shadow:0 0 6px rgba(134,242,138,.15)}50%{box-shadow:0 0 20px rgba(134,242,138,.45)}}
|
@keyframes pulse{0%,100%{box-shadow:0 0 6px rgba(134,242,138,.15)}50%{box-shadow:0 0 24px rgba(134,242,138,.5),0 0 60px rgba(134,242,138,.1)}}
|
||||||
.ban-on{animation:pulse 2.2s ease-in-out infinite}
|
.ban-on{animation:pulse 2.2s ease-in-out infinite}
|
||||||
|
.ban-on .bt{text-shadow:0 0 12px rgba(134,242,138,.6)}
|
||||||
.sg{display:grid;grid-template-columns:repeat(auto-fill,minmax(105px,1fr));gap:5px}
|
.sg{display:grid;grid-template-columns:repeat(auto-fill,minmax(105px,1fr));gap:5px}
|
||||||
.s{background:#020504;border:1px solid #1a3a1e;padding:6px 8px}
|
.s{background:#020504;border:1px solid #1a3a1e;padding:6px 8px;transition:border-color .2s,background .2s}
|
||||||
|
.s:hover{border-color:#2a5a2e;background:#040a06}
|
||||||
.sl{font-size:8px;color:#4fbf59;text-transform:uppercase;letter-spacing:.1em;white-space:nowrap}
|
.sl{font-size:8px;color:#4fbf59;text-transform:uppercase;letter-spacing:.1em;white-space:nowrap}
|
||||||
.sv{font-size:15px;font-weight:bold;margin-top:2px}
|
.sv{font-size:15px;font-weight:bold;margin-top:2px;transition:color .3s}
|
||||||
.su{font-size:8px;color:#4fbf59;margin-left:1px}
|
.su{font-size:8px;color:#4fbf59;margin-left:1px}
|
||||||
.warm{color:#f5d87c!important}.hot{color:#f28a86!important}.lo{color:#f5d87c!important}.crit{color:#f28a86!important}
|
.warm{color:#f5d87c!important}.hot{color:#f28a86!important}.lo{color:#f5d87c!important}.crit{color:#f28a86!important}
|
||||||
.band{margin-bottom:10px}
|
.band{margin-bottom:10px}
|
||||||
@@ -1007,25 +1012,42 @@ canvas.sp{height:44px;border:1px solid #122814;background:#020504}
|
|||||||
.row{display:flex;flex-wrap:wrap;gap:10px}
|
.row{display:flex;flex-wrap:wrap;gap:10px}
|
||||||
.col{flex:1;min-width:140px}
|
.col{flex:1;min-width:140px}
|
||||||
.rrow{display:flex;gap:7px;align-items:center;margin-bottom:4px}
|
.rrow{display:flex;gap:7px;align-items:center;margin-bottom:4px}
|
||||||
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex-shrink:0}
|
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex-shrink:0;transition:all .3s}
|
||||||
.on{background:#86f28a}.off{background:#f28a86}
|
.on{background:#86f28a;box-shadow:0 0 6px rgba(134,242,138,.7)}
|
||||||
|
@keyframes dotPulse{0%,100%{box-shadow:0 0 4px rgba(134,242,138,.5)}50%{box-shadow:0 0 10px rgba(134,242,138,.9)}}
|
||||||
|
.on{animation:dotPulse 1.5s ease-in-out infinite}
|
||||||
|
.off{background:#f28a86;box-shadow:0 0 4px rgba(242,138,134,.4)}
|
||||||
hr{border:none;border-top:1px solid #122814;margin:8px 0}
|
hr{border:none;border-top:1px solid #122814;margin:8px 0}
|
||||||
label{font-size:10px;color:#4fbf59;display:block;margin-bottom:3px}
|
label{font-size:10px;color:#4fbf59;display:block;margin-bottom:3px}
|
||||||
input[type=range]{width:100%;accent-color:#86f28a;margin:2px 0}
|
input[type=range]{width:100%;accent-color:#86f28a;margin:2px 0}
|
||||||
input[type=number]{background:#020504;border:1px solid #1a3a1e;color:#86f28a;padding:3px 6px;font-family:inherit;font-size:11px;width:100%}
|
input[type=number]{background:#020504;border:1px solid #1a3a1e;color:#86f28a;padding:3px 6px;font-family:inherit;font-size:11px;width:100%;transition:border-color .2s}
|
||||||
button{padding:6px 11px;background:#0a1e0c;color:#86f28a;border:1px solid #2a5a2e;cursor:pointer;font-family:inherit;font-size:11px;letter-spacing:.04em}
|
input[type=number]:focus{border-color:#4fbf59;outline:none}
|
||||||
button:hover{background:#142a16}
|
select:focus{border-color:#4fbf59;outline:none}
|
||||||
|
button{padding:6px 11px;background:#0a1e0c;color:#86f28a;border:1px solid #2a5a2e;cursor:pointer;font-family:inherit;font-size:11px;letter-spacing:.04em;transition:all .15s}
|
||||||
|
button:hover{background:#142a16;box-shadow:0 0 8px rgba(134,242,138,.15)}
|
||||||
|
button:active{transform:scale(.97)}
|
||||||
button.d{background:#140608;border-color:#4a1820;color:#f28a86}
|
button.d{background:#140608;border-color:#4a1820;color:#f28a86}
|
||||||
button.d:hover{background:#200a10}
|
button.d:hover{background:#200a10;box-shadow:0 0 8px rgba(242,138,134,.15)}
|
||||||
.err{color:#f28a86;font-size:10px;margin-top:2px}
|
.err{color:#f28a86;font-size:10px;margin-top:2px}
|
||||||
pre{margin:0;padding:8px;background:#020504;border:1px solid #122814;height:28vh;overflow-y:auto;font-size:10px;line-height:1.6;color:#5fbf69}
|
pre{margin:0;padding:8px;background:#020504;border:1px solid #122814;height:28vh;overflow-y:auto;font-size:10px;line-height:1.6;color:#5fbf69}
|
||||||
#connDot{width:9px;height:9px;border-radius:50%;background:#f28a86}
|
pre::-webkit-scrollbar{width:4px}
|
||||||
|
pre::-webkit-scrollbar-track{background:#020504}
|
||||||
|
pre::-webkit-scrollbar-thumb{background:#1a3a1e;border-radius:2px}
|
||||||
|
#connDot{width:9px;height:9px;border-radius:50%;background:#f28a86;transition:background .3s,box-shadow .3s}
|
||||||
|
@keyframes bootIn{0%{opacity:0;filter:brightness(2) blur(2px)}100%{opacity:1;filter:brightness(1) blur(0)}}
|
||||||
|
body{animation:bootIn .6s ease-out}
|
||||||
|
@keyframes flicker{0%{opacity:1}3%{opacity:.4}6%{opacity:1}7%{opacity:.6}9%{opacity:1}100%{opacity:1}}
|
||||||
|
h1{animation:flicker .4s ease-out}
|
||||||
|
.sv{font-variant-numeric:tabular-nums}
|
||||||
</style></head><body>
|
</style></head><body>
|
||||||
<header>
|
<header>
|
||||||
<div><h1>CC1101 JAMMER</h1>
|
<div><h1>CC1101 JAMMER</h1>
|
||||||
<div class="sub">ESP32-S3 • 300–320 MHz + 390–436 MHz • Dual-band FM noise sweep</div></div>
|
<div class="sub">ESP32-S3 • 300–320 MHz + 390–436 MHz • Dual-band FM noise sweep</div></div>
|
||||||
<div style="text-align:right">
|
<div style="text-align:right;display:flex;align-items:center;gap:10px">
|
||||||
<div id="mission" style="font-size:9px;color:#4fbf59;margin-bottom:5px">—</div>
|
<div>
|
||||||
|
<div id="mission" style="font-size:9px;color:#4fbf59;margin-bottom:3px">—</div>
|
||||||
|
<div style="font-size:8px;color:#1a3a1e;letter-spacing:.08em">v2.0 // LFSR+VCO</div>
|
||||||
|
</div>
|
||||||
<div id="connDot"></div>
|
<div id="connDot"></div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -1239,13 +1261,17 @@ function drawSw(id,freq,ctr,span,active,trail,marks){
|
|||||||
const g=ctx.createLinearGradient(cx-80,0,cx+80,0);
|
const g=ctx.createLinearGradient(cx-80,0,cx+80,0);
|
||||||
g.addColorStop(0,'rgba(134,242,138,0)');g.addColorStop(.5,'rgba(134,242,138,0.22)');g.addColorStop(1,'rgba(134,242,138,0)');
|
g.addColorStop(0,'rgba(134,242,138,0)');g.addColorStop(.5,'rgba(134,242,138,0.22)');g.addColorStop(1,'rgba(134,242,138,0)');
|
||||||
ctx.fillStyle=g;ctx.fillRect(cx-80,0,160,H-14);
|
ctx.fillStyle=g;ctx.fillRect(cx-80,0,160,H-14);
|
||||||
// Cursor line
|
// Cursor line with glow
|
||||||
|
ctx.save();ctx.shadowColor='rgba(134,242,138,.6)';ctx.shadowBlur=6;
|
||||||
ctx.strokeStyle='#86f28a';ctx.lineWidth=2;
|
ctx.strokeStyle='#86f28a';ctx.lineWidth=2;
|
||||||
ctx.beginPath();ctx.moveTo(cx,0);ctx.lineTo(cx,H-14);ctx.stroke();
|
ctx.beginPath();ctx.moveTo(cx,0);ctx.lineTo(cx,H-14);ctx.stroke();
|
||||||
// Cursor dot
|
ctx.restore();
|
||||||
|
// Cursor dot with glow
|
||||||
|
ctx.save();ctx.shadowColor='rgba(134,242,138,.8)';ctx.shadowBlur=10;
|
||||||
ctx.fillStyle='#86f28a';ctx.beginPath();ctx.arc(cx,(H-14)/2,4,0,Math.PI*2);ctx.fill();
|
ctx.fillStyle='#86f28a';ctx.beginPath();ctx.arc(cx,(H-14)/2,4,0,Math.PI*2);ctx.fill();
|
||||||
|
ctx.restore();
|
||||||
// Top notch
|
// Top notch
|
||||||
ctx.fillRect(Math.max(0,cx-2),0,4,4);
|
ctx.fillStyle='#86f28a';ctx.fillRect(Math.max(0,cx-2),0,4,4);
|
||||||
} else if(!active){
|
} else if(!active){
|
||||||
ctx.strokeStyle='#2a1216';ctx.lineWidth=1;
|
ctx.strokeStyle='#2a1216';ctx.lineWidth=1;
|
||||||
ctx.beginPath();ctx.moveTo(W/2,0);ctx.lineTo(W/2,H-14);ctx.stroke();
|
ctx.beginPath();ctx.moveTo(W/2,0);ctx.lineTo(W/2,H-14);ctx.stroke();
|
||||||
@@ -1270,11 +1296,13 @@ function drawSp(id,data,color,minH,maxH){
|
|||||||
ctx.beginPath();let fs=true;
|
ctx.beginPath();let fs=true;
|
||||||
data.forEach((v,i)=>{if(v===null){fs=true;return;}const x=i/(HS-1)*W,y=toY(v);if(fs){ctx.moveTo(x,H-4);ctx.lineTo(x,y);fs=false;}else ctx.lineTo(x,y);});
|
data.forEach((v,i)=>{if(v===null){fs=true;return;}const x=i/(HS-1)*W,y=toY(v);if(fs){ctx.moveTo(x,H-4);ctx.lineTo(x,y);fs=false;}else ctx.lineTo(x,y);});
|
||||||
ctx.lineTo(W,H-4);ctx.closePath();
|
ctx.lineTo(W,H-4);ctx.closePath();
|
||||||
ctx.fillStyle=color+'28';ctx.fill();
|
const gf=ctx.createLinearGradient(0,0,0,H);gf.addColorStop(0,color+'30');gf.addColorStop(1,color+'05');
|
||||||
// Line
|
ctx.fillStyle=gf;ctx.fill();
|
||||||
|
// Line with glow
|
||||||
|
ctx.save();ctx.shadowColor=color;ctx.shadowBlur=4;
|
||||||
ctx.strokeStyle=color;ctx.lineWidth=1.5;ctx.beginPath();fs=true;
|
ctx.strokeStyle=color;ctx.lineWidth=1.5;ctx.beginPath();fs=true;
|
||||||
data.forEach((v,i)=>{if(v===null){fs=true;return;}const x=i/(HS-1)*W,y=toY(v);if(fs){ctx.moveTo(x,y);fs=false;}else ctx.lineTo(x,y);});
|
data.forEach((v,i)=>{if(v===null){fs=true;return;}const x=i/(HS-1)*W,y=toY(v);if(fs){ctx.moveTo(x,y);fs=false;}else ctx.lineTo(x,y);});
|
||||||
ctx.stroke();
|
ctx.stroke();ctx.restore();
|
||||||
// Range labels
|
// Range labels
|
||||||
ctx.fillStyle=color+'90';ctx.font='8px monospace';
|
ctx.fillStyle=color+'90';ctx.font='8px monospace';
|
||||||
ctx.textAlign='left';ctx.fillText(mn.toFixed(0),2,H-3);
|
ctx.textAlign='left';ctx.fillText(mn.toFixed(0),2,H-3);
|
||||||
@@ -1349,7 +1377,7 @@ function applyTelemetry(t){
|
|||||||
document.getElementById('hNow').className=ch(t.free_heap/1024);
|
document.getElementById('hNow').className=ch(t.free_heap/1024);
|
||||||
drawSp('cT',hT,'#86f28a',20,90);
|
drawSp('cT',hT,'#86f28a',20,90);
|
||||||
drawSp('cH',hH,'#4fbf59',0,320);
|
drawSp('cH',hH,'#4fbf59',0,320);
|
||||||
document.getElementById('connDot').style.background='#86f28a';
|
const cd=document.getElementById('connDot');cd.style.background='#86f28a';cd.style.boxShadow='0 0 8px rgba(134,242,138,.7)';
|
||||||
}
|
}
|
||||||
|
|
||||||
let logTick=0;
|
let logTick=0;
|
||||||
@@ -1364,7 +1392,7 @@ async function poll(){
|
|||||||
if(atBot)log.scrollTop=log.scrollHeight;
|
if(atBot)log.scrollTop=log.scrollHeight;
|
||||||
}
|
}
|
||||||
}catch(e){
|
}catch(e){
|
||||||
document.getElementById('connDot').style.background='#f28a86';
|
const cd=document.getElementById('connDot');cd.style.background='#f28a86';cd.style.boxShadow='0 0 8px rgba(242,138,134,.6)';
|
||||||
document.getElementById('bt').textContent='OFFLINE';
|
document.getElementById('bt').textContent='OFFLINE';
|
||||||
}
|
}
|
||||||
setTimeout(poll, 1000);
|
setTimeout(poll, 1000);
|
||||||
|
|||||||
Reference in New Issue
Block a user