The HTML is designed in a futuristic red-and-white style with a virtual robotic hand illustration, giving the project a modern dashboard look
Below is the full code for the project.
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
// Hotspot credentials
const char* ssid = "RoboHand_ESP32";
const char* password = "12345678";
WebServer server(80);
// Servo objects
Servo servo1, servo2, servo3, servo4, servo5;
// GPIO pins
int s1 = 13;
int s2 = 12;
int s3 = 14;
int s4 = 27;
int s5 = 26;
// ================= HTML PAGE =================
String webpage = R"====(
<!DOCTYPE html>
<html>
<head>
<title>NeuroGrip Hand Control</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
:root{
--bg:#070b14;
--bg2:#0d1424;
--card:#0f1728cc;
--card2:#111b31;
--line:rgba(255,255,255,.08);
--text:#f4f7ff;
--muted:#9aa7c7;
--primary:#ff3b5c;
--primary2:#ff6b81;
--accent:#ff9aa8;
--good:#55f2b6;
--shadow:0 24px 80px rgba(0,0,0,.45);
}
*{box-sizing:border-box;margin:0;padding:0}
body{
font-family:Inter,Segoe UI,Arial,sans-serif;
min-height:100vh;
color:var(--text);
background:
radial-gradient(circle at 15% 15%, rgba(255,59,92,.18), transparent 24%),
radial-gradient(circle at 85% 20%, rgba(255,107,129,.14), transparent 20%),
radial-gradient(circle at 50% 100%, rgba(255,255,255,.05), transparent 35%),
linear-gradient(160deg, var(--bg) 0%, #090f1b 45%, #05070d 100%);
display:flex;
justify-content:center;
padding:24px 14px;
}
.shell{
width:100%;
max-width:1180px;
display:grid;
gap:18px;
grid-template-columns:1.05fr .95fr;
align-items:stretch;
}
.hero,
.panel{
background:linear-gradient(180deg, rgba(17,27,49,.88), rgba(8,12,22,.88));
border:1px solid var(--line);
border-radius:28px;
box-shadow:var(--shadow);
backdrop-filter:blur(18px);
overflow:hidden;
position:relative;
}
.hero::before,
.panel::before{
content:"";
position:absolute;
inset:0;
background:linear-gradient(135deg, rgba(255,59,92,.12), transparent 30%, rgba(255,255,255,.03));
pointer-events:none;
}
.hero{
padding:26px;
display:flex;
flex-direction:column;
gap:18px;
min-height:680px;
}
.panel{
padding:22px;
min-height:680px;
}
.topline{
display:flex;
justify-content:space-between;
align-items:center;
gap:12px;
flex-wrap:wrap;
}
.brand{
display:flex;
flex-direction:column;
gap:6px;
}
.brand h1{
font-size:30px;
line-height:1.05;
letter-spacing:.2px;
}
.brand p{
color:var(--muted);
font-size:14px;
}
.chiprow{
display:flex;
gap:10px;
flex-wrap:wrap;
}
.chip{
padding:10px 14px;
border-radius:999px;
background:rgba(255,255,255,.04);
border:1px solid var(--line);
color:#dce5ff;
font-size:12px;
letter-spacing:.3px;
}
.statusdot{
display:inline-flex;
align-items:center;
gap:8px;
}
.dot{
width:10px;
height:10px;
border-radius:50%;
background:var(--good);
box-shadow:0 0 0 6px rgba(85,242,182,.12), 0 0 22px rgba(85,242,182,.55);
}
.scene{
flex:1;
display:grid;
grid-template-columns:1fr;
gap:16px;
}
.handcard{
position:relative;
border-radius:26px;
background:
radial-gradient(circle at 30% 20%, rgba(255,255,255,.10), transparent 18%),
linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.02));
border:1px solid var(--line);
min-height:390px;
overflow:hidden;
display:flex;
align-items:center;
justify-content:center;
}
.gridbg{
position:absolute;
inset:0;
background-image:
linear-gradient(rgba(255,255,255,.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,.04) 1px, transparent 1px);
background-size:34px 34px;
mask-image: radial-gradient(circle at center, black 28%, transparent 78%);
opacity:.42;
}
.glow{
position:absolute;
width:340px;
height:340px;
border-radius:50%;
background:radial-gradient(circle, rgba(255,59,92,.22), transparent 68%);
filter:blur(12px);
}
.glow.one{top:-60px;left:-40px}
.glow.two{bottom:-100px;right:-40px;background:radial-gradient(circle, rgba(255,255,255,.09), transparent 68%)}
svg{
width:min(92%, 600px);
height:auto;
position:relative;
z-index:2;
overflow:visible;
filter:drop-shadow(0 18px 40px rgba(0,0,0,.45));
}
.finger,
.palm{
fill:url(#handGrad);
stroke:rgba(255,255,255,.42);
stroke-width:2;
}
.joint{
fill:#ff7d92;
stroke:#fff;
stroke-width:2;
filter:drop-shadow(0 0 8px rgba(255,59,92,.6));
}
.ui-card{
border:1px solid var(--line);
border-radius:22px;
background:rgba(255,255,255,.04);
padding:16px;
}
.metrics{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:12px;
}
.metric .k{
color:var(--muted);
font-size:12px;
margin-bottom:6px;
}
.metric .v{
font-size:20px;
font-weight:800;
letter-spacing:.2px;
}
.section-title{
display:flex;
justify-content:space-between;
align-items:center;
gap:10px;
margin-bottom:14px;
}
.section-title h2{
font-size:18px;
}
.section-title span{
color:var(--muted);
font-size:12px;
}
.controls{
display:grid;
gap:12px;
}
.control{
padding:14px;
border-radius:18px;
border:1px solid var(--line);
background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,255,255,.03));
}
.head{
display:flex;
justify-content:space-between;
align-items:center;
margin-bottom:10px;
gap:12px;
}
.label{
font-size:14px;
font-weight:700;
}
.value{
font-size:13px;
font-weight:800;
color:var(--primary2);
min-width:54px;
text-align:right;
}
input[type=range]{
width:100%;
-webkit-appearance:none;
appearance:none;
height:10px;
border-radius:999px;
background:linear-gradient(to right, rgba(255,59,92,.35), rgba(255,255,255,.14));
outline:none;
cursor:pointer;
}
input[type=range]::-webkit-slider-thumb{
-webkit-appearance:none;
appearance:none;
width:24px;
height:24px;
border-radius:50%;
border:2px solid #fff;
background:linear-gradient(180deg, #ff8d9f, #ff3b5c);
box-shadow:0 0 0 8px rgba(255,59,92,.12);
}
.presets{
display:grid;
grid-template-columns:repeat(2,1fr);
gap:10px;
margin-top:14px;
}
.presets button{
border:none;
border-radius:16px;
padding:13px 14px;
font-size:13px;
font-weight:800;
color:#fff;
background:linear-gradient(135deg, var(--primary), #ff5f79);
box-shadow:0 12px 24px rgba(255,59,92,.18);
cursor:pointer;
transition:.18s ease;
}
.presets button:hover{transform:translateY(-1px)}
.presets button:active{transform:scale(.98)}
.status{
margin-top:14px;
padding:14px 16px;
border-radius:16px;
border:1px solid var(--line);
background:rgba(255,255,255,.04);
color:#d9e5ff;
text-align:center;
font-size:13px;
min-height:48px;
display:flex;
align-items:center;
justify-content:center;
}
.footer{
margin-top:12px;
color:var(--muted);
font-size:12px;
text-align:center;
}
@media (max-width: 980px){
.shell{grid-template-columns:1fr}
.hero,.panel{min-height:auto}
.metrics{grid-template-columns:1fr}
.presets{grid-template-columns:1fr 1fr}
}
@media (max-width: 560px){
.hero,.panel{padding:18px}
.brand h1{font-size:24px}
.presets{grid-template-columns:1fr}
}
</style>
</head>
<body>
<div class="shell">
<section class="hero">
<div class="topline">
<div class="brand">
<h1>NeuroGrip</h1>
<p>Futuristic robotic hand dashboard for ESP32 servo control.</p>
</div>
<div class="chiprow">
<div class="chip statusdot"><span class="dot"></span> LIVE</div>
<div class="chip">ESP32 Hotspot</div>
<div class="chip">5-DOF Hand</div>
</div>
</div>
<div class="scene">
<div class="handcard">
<div class="gridbg"></div>
<div class="glow one"></div>
<div class="glow two"></div>
<svg viewBox="0 0 820 540" aria-label="Robotic hand illustration">
<defs>
<linearGradient id="handGrad" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#ffe4ea"/>
<stop offset="45%" stop-color="#ff7a92"/>
<stop offset="100%" stop-color="#d90429"/>
</linearGradient>
</defs>
<ellipse cx="410" cy="430" rx="170" ry="55" fill="rgba(255,255,255,.08)"/>
<g transform="translate(165,45)">
<rect class="palm" x="180" y="190" rx="48" ry="48" width="210" height="190"/>
<rect class="finger" x="190" y="55" rx="24" ry="24" width="48" height="155" transform="rotate(-8 190 55)"/>
<circle class="joint" cx="214" cy="82" r="9"/>
<circle class="joint" cx="210" cy="138" r="8"/>
<rect class="finger" x="252" y="28" rx="24" ry="24" width="52" height="182"/>
<circle class="joint" cx="278" cy="60" r="9"/>
<circle class="joint" cx="278" cy="124" r="8"/>
<rect class="finger" x="322" y="52" rx="24" ry="24" width="50" height="158"/>
<circle class="joint" cx="347" cy="82" r="9"/>
<circle class="joint" cx="347" cy="138" r="8"/>
<rect class="finger" x="386" y="92" rx="22" ry="22" width="42" height="120" transform="rotate(6 386 92)"/>
<circle class="joint" cx="407" cy="116" r="8"/>
<circle class="joint" cx="410" cy="158" r="7"/>
<path class="finger" d="M170 270 C130 282, 102 310, 98 346 C96 362, 108 374, 126 376 C150 378, 171 360, 186 336 C201 311, 206 287, 205 255 Z"/>
<circle class="joint" cx="152" cy="306" r="9"/>
<circle class="joint" cx="128" cy="338" r="8"/>
</g>
</svg>
</div>
<div class="ui-card">
<div class="metrics">
<div class="metric">
<div class="k">Mode</div>
<div class="v">Manual</div>
</div>
<div class="metric">
<div class="k">Latency</div>
<div class="v">Low</div>
</div>
<div class="metric">
<div class="k">Power</div>
<div class="v">Stable</div>
</div>
</div>
</div>
</div>
</section>
<section class="panel">
<div class="section-title">
<h2>Servo Control</h2>
<span>Tap presets or drag sliders</span>
</div>
<div class="controls">
<div class="control">
<div class="head"><span class="label">Thumb</span><span class="value" id="v1">90°</span></div>
<input type="range" min="0" max="180" value="90" id="s1" oninput="send(1,this.value)">
</div>
<div class="control">
<div class="head"><span class="label">Index</span><span class="value" id="v2">90°</span></div>
<input type="range" min="0" max="180" value="90" id="s2" oninput="send(2,this.value)">
</div>
<div class="control">
<div class="head"><span class="label">Middle</span><span class="value" id="v3">90°</span></div>
<input type="range" min="0" max="180" value="90" id="s3" oninput="send(3,this.value)">
</div>
<div class="control">
<div class="head"><span class="label">Ring</span><span class="value" id="v4">90°</span></div>
<input type="range" min="0" max="180" value="90" id="s4" oninput="send(4,this.value)">
</div>
<div class="control">
<div class="head"><span class="label">Little</span><span class="value" id="v5">90°</span></div>
<input type="range" min="0" max="180" value="90" id="s5" oninput="send(5,this.value)">
</div>
</div>
<div class="presets">
<button onclick="preset([90,90,90,90,90])">Open Hand</button>
<button onclick="preset([0,0,0,0,0])">Fist</button>
<button onclick="preset([180,0,0,0,0])">Thumbs Up</button>
<button onclick="preset([0,180,0,0,0])">Point</button>
<button onclick="preset([180,180,0,0,0])">Peace</button>
<button onclick="preset([150,150,150,150,150])">Half Close</button>
</div>
<div class="status" id="status">System ready — move a slider to control the hand.</div>
<div class="footer">Designed for a futuristic robotic hand control experience.</div>
</section>
</div>
<script>
function send(servo, value) {
document.getElementById('v' + servo).textContent = value + '°';
const st = document.getElementById('status');
st.textContent = 'Sending Servo ' + servo + ' → ' + value + '°';
const xhr = new XMLHttpRequest();
xhr.open('GET', '/set?servo=' + servo + '&value=' + value, true);
xhr.onload = function() {
st.textContent = '✓ Servo ' + servo + ' set to ' + value + '°';
};
xhr.onerror = function() {
st.textContent = '⚠ Connection error';
};
xhr.send();
}
function preset(vals) {
vals.forEach(function(v, i) {
document.getElementById('s' + (i + 1)).value = v;
document.getElementById('v' + (i + 1)).textContent = v + '°';
send(i + 1, v);
});
}
</script>
</body>
</html>
)====";
// ===========================================
void handleRoot() {
server.send(200, "text/html", webpage);
}
void handleServo() {
int servo = server.arg("servo").toInt();
int value = constrain(server.arg("value").toInt(), 0, 180);
if (servo == 1) servo1.write(value);
if (servo == 2) servo2.write(value);
if (servo == 3) servo3.write(value);
if (servo == 4) servo4.write(value);
if (servo == 5) servo5.write(value);
Serial.print("Servo ");
Serial.print(servo);
Serial.print(" -> ");
Serial.println(value);
server.send(200, "text/plain", "OK");
}
void setup() {
Serial.begin(115200);
servo1.attach(s1);
servo2.attach(s2);
servo3.attach(s3);
servo4.attach(s4);
servo5.attach(s5);
// Initial position
servo1.write(90);
servo2.write(90);
servo3.write(90);
servo4.write(90);
servo5.write(90);
// Start Hotspot
WiFi.softAP(ssid, password);
Serial.println("\nHotspot Started!");
Serial.print("SSID: ");
Serial.println(ssid);
Serial.print("IP Address: ");
Serial.println(WiFi.softAPIP());
server.on("/", handleRoot);
server.on("/set", handleServo);
server.begin();
Serial.println("Web server started.");
}
void loop() {
server.handleClient();
The interface is clean, responsive, and more professional than a basic control page.
This ESP32 robotic hand control project is a simple but powerful example of combining embedded systems, servo motion control, and web-based UI design. With the futuristic dashboard and live finger visualization, the project looks more attractive and professional for presentation or blog publishing.
Comments
Post a Comment