Berikut adalah login page untuk Hotspot Mikrotik sederhana, pembuatannya dibantu AI, khususnya untuk gambar dan efek bola. Simpan sebagai login.html menggantikan login.html halaman default.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SoccerNet</title>
<style>
*{margin:0;padding:0;box-sizing:border-box}
body{
min-height:100vh;
display:flex;flex-direction:column;align-items:center;justify-content:center;
background:#080c0a;
background:radial-gradient(ellipse at 50% 100%,#101e16 0%,#080c0a 55%);
font-family:-apple-system,'Segoe UI',Helvetica,sans-serif;
color:rgba(255,255,255,.85);overflow:hidden;user-select:none;-webkit-user-select:none;
}
.brand{display:flex;flex-direction:column;align-items:center;margin-bottom:1.6rem}
#ball{
width:120px;height:120px;cursor:grab;
filter:drop-shadow(0 6px 18px rgba(0,0,0,.5));
margin-bottom:1rem;
}
#ball:active{cursor:grabbing}
.title{font-size:1.65rem;font-weight:700;letter-spacing:.05em;color:#fff}
.title b{color:#2d8a4e;font-weight:700}
.card{
width:100%;max-width:330px;
padding:1.8rem 2rem 1.4rem;
background:rgba(255,255,255,.025);
border:1px solid rgba(255,255,255,.06);
border-radius:14px;
backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
}
.err{
display:none;
background:rgba(220,60,50,.08);
border:1px solid rgba(220,60,50,.22);
color:#d9604f;font-size:.8rem;
padding:.55rem .85rem;border-radius:8px;
margin-bottom:1.1rem;text-align:center;line-height:1.4;
}
.field{margin-bottom:.95rem}
.field label{
display:block;font-size:.7rem;font-weight:600;
color:rgba(255,255,255,.38);text-transform:uppercase;
letter-spacing:.12em;margin-bottom:.35rem;
}
.field input{
width:100%;padding:.65rem 0;
background:transparent;border:none;
border-bottom:1.5px solid rgba(255,255,255,.1);
color:#fff;font-size:.92rem;font-family:inherit;outline:none;
transition:border-color .25s;
}
.field input:focus{border-bottom-color:#2d8a4e}
.field input::placeholder{color:rgba(255,255,255,.18)}
.btn{
width:100%;padding:.72rem;margin-top:.5rem;
background:#2d8a4e;color:#fff;border:none;border-radius:9px;
font-size:.88rem;font-weight:600;font-family:inherit;
letter-spacing:.04em;cursor:pointer;
transition:background .2s,transform .12s;
}
.btn:hover{background:#35a15b}
.btn:active{transform:scale(.98)}
.foot{
margin-top:1.8rem;text-align:center;
font-size:.65rem;color:rgba(255,255,255,.16);line-height:1.7;
}
/* Subtle ground line */
.gnd{
width:100px;height:1px;margin-top:-4px;
background:linear-gradient(90deg,transparent,rgba(255,255,255,.04),transparent);
}
</style>
</head>
<body>
<div class="brand">
<svg id="ball" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg"></svg>
<div class="gnd"></div>
<h1 class="title">Soccer<b>Net</b></h1>
</div>
<form name="sendin" action="$(link-login-only)" method="post" onsubmit="return doLogin()">
<input type="hidden" name="dst" value="$(link-orig)" />
<input type="hidden" name="popup" value="true" />
<div class="card">
<div class="err" $(if error)style="display:block"$(endif)>$(error)</div>
<div class="field">
<label for="un">Username</label>
<input type="text" id="un" name="username" value="$(username)" placeholder="Enter username" autofocus />
</div>
<div class="field">
<label for="pw">Password</label>
<input type="password" id="pw" name="password" placeholder="Enter password" />
</div>
<button type="submit" class="btn">Login</button>
</div>
</form>
<div class="foot">
MAC: $(mac) · IP: $(ip)
</div>
<script>
/* ===== Mikrotik hotspot submit ===== */
function doLogin(){document.sendin.submit();return false}
/* ===== Truncated Icosahedron Soccer Ball ===== */
(function(){
var PHI=(1+Math.sqrt(5))/2, CX=200,CY=200,RAD=147;
var raw=[];
var a,b,c,i;
for(a=-1;a<=1;a+=2)for(b=-1;b<=1;b+=2){
raw.push([0,a,b*3*PHI]);
raw.push([a,b*3*PHI,0]);
raw.push([b*3*PHI,0,a]);
}
for(a=-1;a<=1;a+=2)for(b=-1;b<=1;b+=2)for(c=-1;c<=1;c+=2){
raw.push([a*2,b*(1+2*PHI),c*PHI]);
raw.push([b*(1+2*PHI),c*PHI,a*2]);
raw.push([c*PHI,a*2,b*(1+2*PHI)]);
}
for(a=-1;a<=1;a+=2)for(b=-1;b<=1;b+=2)for(c=-1;c<=1;c+=2){
raw.push([a,b*(2+PHI),c*2*PHI]);
raw.push([b*(2+PHI),c*2*PHI,a]);
raw.push([c*2*PHI,a,b*(2+PHI)]);
}
var NS=Math.sqrt(10+9*PHI);
var V=raw.map(function(v){return[v[0]/NS,v[1]/NS,v[2]/NS]});
/* Edges */
var EL2=4/(NS*NS), NB=[];
for(i=0;i<60;i++) NB[i]=[];
for(i=0;i<60;i++)for(var j=i+1;j<60;j++){
var dx=V[i][0]-V[j][0],dy=V[i][1]-V[j][1],dz=V[i][2]-V[j][2];
if(Math.abs(dx*dx+dy*dy+dz*dz-EL2)<0.001){NB[i].push(j);NB[j].push(i)}
}
/* Helpers */
function cr(a,b,c){
var e1x=V[b][0]-V[a][0],e1y=V[b][1]-V[a][1],e1z=V[b][2]-V[a][2];
var e2x=V[c][0]-V[b][0],e2y=V[c][1]-V[b][1],e2z=V[c][2]-V[b][2];
return[e1y*e2z-e1z*e2y,e1z*e2x-e1x*e2z,e1x*e2y-e1y*e2x];
}
function dt(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]}
function cn(a,b,c){return[(V[a][0]+V[b][0]+V[c][0])/3,(V[a][1]+V[b][1]+V[c][1])/3,(V[a][2]+V[b][2]+V[c][2])/3]}
/* Face tracing */
var seen={}, faces=[];
for(i=0;i<60;i++)for(var _j=0;_j<NB[i].length;_j++){
var w=NB[i][_j];
if(seen[i+'-'+w])continue;
var face=[i],p=i,cc=w;
while(true){
seen[p+'-'+cc]=true;
face.push(cc);
var cands=NB[cc].filter(function(n){return n!==p});
var best=-1,bestD=-1e9;
for(var k=0;k<cands.length;k++){
var d=dt(cr(p,cc,cands[k]),cn(p,cc,cands[k]));
if(d>bestD){bestD=d;best=cands[k]}
}
if(best<0)break;
p=cc;cc=best;
if(cc===i)break;
if(face.length>10)break;
}
if(face.length===5||face.length===6)faces.push(face);
}
/* Initial rotation: put a pentagon facing viewer */
var topZ=-1e9,topC=null;
for(i=0;i<faces.length;i++){
var f=faces[i]; if(f.length!==5)continue;
var cz=0;for(j=0;j<5;j++)cz+=V[f[j]][2]; cz/=5;
if(cz>topZ){
topZ=cz;
topC=[0,0,0];
for(j=0;j<5;j++){topC[0]+=V[f[j]][0];topC[1]+=V[f[j]][1];topC[2]+=V[f[j]][2]}
topC[0]/=5;topC[1]/=5;topC[2]/=5;
}
}
var iRY=Math.atan2(-topC[0],topC[2]);
var iRX=Math.atan2(topC[1],Math.sqrt(topC[0]*topC[0]+topC[2]*topC[2]));
function rot(x,y,z,rx,ry){
var cY=Math.cos(ry),sY=Math.sin(ry);
var x1=x*cY+z*sY,z1=-x*sY+z*cY;
var cX=Math.cos(rx),sX=Math.sin(rx);
return[x1,y*cX-z1*sX,y*sX+z1*cX];
}
var svg=document.getElementById('ball');
function draw(rx,ry){
var P=V.map(function(v){var t=rot(v[0],v[1],v[2],rx,ry);
return{sx:CX+t[0]*RAD,sy:CY-t[1]*RAD,z:t[2]}});
var rf=faces.map(function(f){
var pts=f.map(function(ii){return P[ii]});
var az=0;for(var k=0;k<pts.length;k++)az+=pts[k].z;az/=pts.length;
return{pts:pts,az:az,pent:f.length===5};
});
rf.sort(function(a,b){return a.az-b.az});
var s='<defs>';
s+='<clipPath id="bc"><circle cx="'+CX+'" cy="'+CY+'" r="'+RAD+'"/></clipPath>';
s+='<radialGradient id="sh" cx="34%" cy="28%" r="66%">';
s+='<stop offset="0%" stop-color="rgba(255,255,255,0.12)"/>';
s+='<stop offset="45%" stop-color="rgba(0,0,0,0)"/>';
s+='<stop offset="100%" stop-color="rgba(0,0,0,0.55)"/>';
s+='</radialGradient>';
s+='<radialGradient id="sp" cx="35%" cy="26%" r="16%">';
s+='<stop offset="0%" stop-color="rgba(255,255,255,0.5)"/>';
s+='<stop offset="100%" stop-color="rgba(255,255,255,0)"/>';
s+='</radialGradient></defs>';
s+='<circle cx="'+CX+'" cy="'+CY+'" r="'+RAD+'" fill="#edecea"/>';
s+='<g clip-path="url(#bc)">';
for(i=0;i<rf.length;i++){
var ff=rf[i],d='';
for(var k=0;k<ff.pts.length;k++){
d+=(k?'L':'M')+ff.pts[k].sx.toFixed(1)+','+ff.pts[k].sy.toFixed(1);
}
d+='Z';
if(ff.pent){
s+='<path d="'+d+'" fill="#1a1a1c" stroke="#0e0e10" stroke-width="0.8" stroke-linejoin="round"/>';
}else{
s+='<path d="'+d+'" fill="#edecea" stroke="#c8c5b9" stroke-width="0.5" stroke-linejoin="round"/>';
}
}
s+='</g>';
s+='<circle cx="'+CX+'" cy="'+CY+'" r="'+RAD+'" fill="url(#sh)"/>';
s+='<circle cx="'+CX+'" cy="'+CY+'" r="'+RAD+'" fill="url(#sp)"/>';
svg.innerHTML=s;
}
var rx=iRX,ry=iRY,drag=false,lx=0,ly=0,lastT=0,idleT=0,lastPh=-1;
function pD(x,y){drag=true;lx=x;ly=y;lastT=performance.now()}
function pM(x,y){if(!drag)return;ry+=(x-lx)*.007;rx+=(y-ly)*.007;
rx=Math.max(-1.3,Math.min(1.3,rx));lx=x;ly=y;lastT=performance.now();draw(rx,ry)}
function pU(){drag=false}
svg.addEventListener('mousedown',function(e){e.preventDefault();pD(e.clientX,e.clientY)});
window.addEventListener('mousemove',function(e){pM(e.clientX,e.clientY)});
window.addEventListener('mouseup',pU);
svg.addEventListener('touchstart',function(e){e.preventDefault();var t=e.touches[0];pD(t.clientX,t.clientY)},{passive:false});
window.addEventListener('touchmove',function(e){if(!drag)return;var t=e.touches[0];pM(t.clientX,t.clientY)},{passive:true});
window.addEventListener('touchend',pU);
function loop(t){
requestAnimationFrame(loop);
if(drag)return;
if(t-lastT<2500)return;
idleT+=0.003;
var ph=Math.round(idleT*60);
if(ph===lastPh)return;lastPh=ph;
draw(rx+Math.sin(idleT*.5)*.04, ry+idleT*.09);
}
draw(rx,ry);
requestAnimationFrame(loop);
})();
</script>
</body>
</html>
Berikut adalah preview-nya: