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>
|
||||
*{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}
|
||||
header{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid #1a3a1e;background:#030705}
|
||||
h1{font-size:17px;letter-spacing:.12em;color:#a0f5a4}
|
||||
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}
|
||||
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}
|
||||
#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}
|
||||
.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}
|
||||
.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}
|
||||
.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}
|
||||
@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 .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}
|
||||
.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}
|
||||
.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}
|
||||
.warm{color:#f5d87c!important}.hot{color:#f28a86!important}.lo{color:#f5d87c!important}.crit{color:#f28a86!important}
|
||||
.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}
|
||||
.col{flex:1;min-width:140px}
|
||||
.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}
|
||||
.on{background:#86f28a}.off{background:#f28a86}
|
||||
.dot{width:7px;height:7px;border-radius:50%;display:inline-block;flex-shrink:0;transition:all .3s}
|
||||
.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}
|
||||
label{font-size:10px;color:#4fbf59;display:block;margin-bottom:3px}
|
||||
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%}
|
||||
button{padding:6px 11px;background:#0a1e0c;color:#86f28a;border:1px solid #2a5a2e;cursor:pointer;font-family:inherit;font-size:11px;letter-spacing:.04em}
|
||||
button:hover{background:#142a16}
|
||||
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}
|
||||
input[type=number]:focus{border-color:#4fbf59;outline:none}
|
||||
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: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}
|
||||
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>
|
||||
<header>
|
||||
<div><h1>CC1101 JAMMER</h1>
|
||||
<div class="sub">ESP32-S3 • 300–320 MHz + 390–436 MHz • Dual-band FM noise sweep</div></div>
|
||||
<div style="text-align:right">
|
||||
<div id="mission" style="font-size:9px;color:#4fbf59;margin-bottom:5px">—</div>
|
||||
<div style="text-align:right;display:flex;align-items:center;gap:10px">
|
||||
<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>
|
||||
</header>
|
||||
@@ -1239,13 +1261,17 @@ function drawSw(id,freq,ctr,span,active,trail,marks){
|
||||
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)');
|
||||
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.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.restore();
|
||||
// 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){
|
||||
ctx.strokeStyle='#2a1216';ctx.lineWidth=1;
|
||||
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;
|
||||
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.fillStyle=color+'28';ctx.fill();
|
||||
// Line
|
||||
const gf=ctx.createLinearGradient(0,0,0,H);gf.addColorStop(0,color+'30');gf.addColorStop(1,color+'05');
|
||||
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;
|
||||
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
|
||||
ctx.fillStyle=color+'90';ctx.font='8px monospace';
|
||||
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);
|
||||
drawSp('cT',hT,'#86f28a',20,90);
|
||||
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;
|
||||
@@ -1364,7 +1392,7 @@ async function poll(){
|
||||
if(atBot)log.scrollTop=log.scrollHeight;
|
||||
}
|
||||
}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';
|
||||
}
|
||||
setTimeout(poll, 1000);
|
||||
|
||||
Reference in New Issue
Block a user