
void handleRoot() {
  String s = pageStart(txt("status"));
  s.reserve(6500);
  s += F("<h1 class='title'>⌂ "); s += txt("status"); s += F("</h1>");
  s += F("<div class='cards'>");

  s += F("<div class='card metric'><h3>"); s += txt("temperature"); s += F("</h3><div class='val red'>");
  s += isnan(lastTemp) ? "--" : String(lastTemp,1) + " °C";
  s += F("</div><p class='small'>"); s += txt("latest"); s += F("</p></div>");

  s += F("<div class='card metric'><h3>"); s += txt("humidity"); s += F("</h3><div class='val blue'>");
  s += isnan(lastHum) ? "--" : String(lastHum,1) + " %";
  s += F("</div><p class='small'>"); s += txt("latest"); s += F("</p></div>");

  s += F("<div class='card metric'><h3>"); s += txt("led"); s += F("</h3><div class='val green'>");
  s += ledState ? txt("on") : txt("off");
  s += F("</div><a class='btn' href='/led?set=1'>"); s += txt("turnOn"); s += F("</a> <a class='btn redbtn' href='/led?set=0'>"); s += txt("turnOff"); s += F("</a></div>");

  s += F("<div class='card metric'><h3>"); s += txt("mqtt"); s += F("</h3><div class='val purple'>");
  s += mqtt.connected() ? txt("ok") : "--";
  s += F("</div><p class='small'>"); s += mqtt.connected() ? txt("connected") : txt("notConnected"); s += F("</p></div>");
  s += F("</div>");

  s += F("<div class='grid2'><div class='card'><h2>"); s += txt("connections"); s += F("</h2>");
  s += F("<span class='pill "); s += WiFi.isConnected() ? "ok" : "bad"; s += F("'>WiFi: "); s += WiFi.isConnected() ? txt("ok") : txt("notOk"); s += F("</span>");
  s += F("<span class='pill "); s += mqtt.connected() ? "ok" : "bad"; s += F("'>MQTT: "); s += mqtt.connected() ? txt("ok") : txt("notOk"); s += F("</span>");
  s += F("<span class='pill info'>Uptime: "); s += fmtUptime(); s += F("</span>");
  s += F("<table class='tbl'><tr><th>"); s += txt("location"); s += F("</th><td>"); s += htmlEscape(cfg.sensorName); s += F("</td></tr>");
  s += F("<tr><th>SSID</th><td>"); s += WiFi.isConnected() ? htmlEscape(WiFi.SSID()) : String("-"); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("ip"); s += F("</th><td>"); s += WiFi.isConnected() ? WiFi.localIP().toString() : (portalMode ? WiFi.softAPIP().toString() : String("-")); s += F("</td></tr>");
  s += F("<tr><th>mDNS</th><td>http://"); s += htmlEscape(cfg.hostname); s += F(".local/</td></tr>");
  s += F("<tr><th>RSSI</th><td>"); s += WiFi.isConnected() ? String(WiFi.RSSI()) + " dBm <span class='green'>" + rssiBars() + "</span>" : String("-"); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("freeheap"); s += F("</th><td>"); s += String(ESP.getFreeHeap()); s += F(" bytes</td></tr>");
  s += F("<tr><th>Min. heap</th><td>"); s += String(minFreeHeap == 0xFFFFFFFF ? ESP.getFreeHeap() : minFreeHeap); s += F(" bytes</td></tr>");
  s += F("<tr><th>Max block</th><td>"); s += String(ESP.getMaxFreeBlockSize()); s += F(" bytes</td></tr>");
  s += F("<tr><th>DHT status</th><td>"); s += htmlEscape(lastDhtStatus); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("sensor"); s += F("</th><td>DHT"); s += String(cfg.dhtType); s += F(" / GPIO"); s += String(cfg.dhtPin); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("sendInterval"); s += F("</th><td>"); s += String(cfg.publishIntervalMs/1000); s += F(" "); s += txt("seconds"); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("firmware"); s += F("</th><td>v"); s += APP_VERSION; s += F("</td></tr></table></div>");

  s += F("<div class='card'><h2>MQTT topics</h2><table class='tbl'>");
  s += F("<tr><th>"); s += txt("temperature"); s += F("</th><td>"); s += htmlEscape(cfg.topicTemp); s += F("</td></tr>");
  s += F("<tr><th>"); s += txt("humidity"); s += F("</th><td>"); s += htmlEscape(cfg.topicHum); s += F("</td></tr>");
  s += F("<tr><th>LED<br><span class='small'>(subscribe)</span></th><td>"); s += htmlEscape(cfg.topicLed); s += F("</td></tr></table></div></div>");

  s += F("<div class='card'><a class='btn bluebtn' href='/'>↻ "); s += txt("updateStatus"); s += F("</a> <a class='btn gray' href='/debug'>Debug</a></div>");
  s += pageEnd();
  server.send(200, "text/html", s);
}

void handleLed() {
  if (server.hasArg("set")) {
    ledState = server.arg("set") == "1";
    digitalWrite(LED_PIN, ledState ? HIGH : LOW);
    if (mqtt.connected()) mqtt.publish(cfg.topicLed, ledState ? "1" : "0", cfg.retained);
  }
  server.sendHeader("Location", "/"); server.send(303);
}

void handleConfig() {
  if (server.hasArg("lang") && server.method() == HTTP_GET) { strlcpySafe(cfg.language, server.arg("lang"), sizeof(cfg.language)); saveConfig(); server.sendHeader("Location", "/"); server.send(303); return; }
  if (server.method() == HTTP_POST) {
    strlcpySafe(cfg.hostname, server.arg("hostname"), sizeof(cfg.hostname));
    strlcpySafe(cfg.language, server.arg("language"), sizeof(cfg.language));
    cfg.publishIntervalMs = max(1000UL, server.arg("interval").toInt() * 1000UL);
    cfg.retained = server.hasArg("retained");
    saveConfig();
    server.sendHeader("Location", "/config?saved=1"); server.send(303); return;
  }
  String s = pageStart(txt("system"));
  if (server.hasArg("saved")) { s += F("<div class='card ok'>"); s += txt("saved"); s += F("</div>"); }
  s += F("<h1 class='title'>⚙ "); s += txt("system"); s += F("</h1><div class='card'><h2>"); s += txt("system"); s += F("</h2><p class='small'>"); s += txt("systemHelp"); s += F("</p><form method='post'>");
  s += F("<label>Hostname</label><input name='hostname' value='"); s += htmlEscape(cfg.hostname); s += F("'>");
  s += F("<label>"); s += txt("languageCode"); s += F("</label><select name='language'>");
  for (String l : {String("DK"), String("GB"), String("D")}) { s += F("<option"); if (l == cfg.language) s += F(" selected"); s += F(">"); s += l; s += F("</option>"); }
  s += F("</select><label>"); s += txt("sendInterval"); s += F(" i "); s += txt("seconds"); s += F("</label><input name='interval' type='number' min='1' value='"); s += String(cfg.publishIntervalMs/1000); s += F("'>");
  s += F("<label><input style='width:auto' type='checkbox' name='retained' "); if(cfg.retained) s+=F("checked"); s += F("> "); s += txt("retained"); s += F("</label>");
  s += F("<button>"); s += txt("save"); s += F("</button></form><p><a class='btn redbtn' href='/resetconfig'>"); s += txt("resetConfig"); s += F("</a></p></div>");
  s += pageEnd(); server.send(200,"text/html",s);
}

void restartDht() {
  dht.setup(cfg.dhtPin, cfg.dhtType == 11 ? DHTesp::DHT11 : DHTesp::DHT22);
}

String pinOption(uint8_t pin, const String& label) {
  String s = F("<option value='"); s += String(pin); s += F("'");
  if (cfg.dhtPin == pin) s += F(" selected");
  s += F(">"); s += label; s += F("</option>");
  return s;
}

void handleSensor() {
  if (server.method() == HTTP_POST) {
    strlcpySafe(cfg.sensorName, server.arg("sensorName"), sizeof(cfg.sensorName));
    cfg.dhtType = (uint8_t)server.arg("dhtType").toInt();
    if (cfg.dhtType != 11 && cfg.dhtType != 22) cfg.dhtType = 22;
    cfg.dhtPin = (uint8_t)server.arg("dhtPin").toInt();
    saveConfig();
    restartDht();
    server.sendHeader("Location", "/sensor?saved=1"); server.send(303); return;
  }
  String s = pageStart(txt("sensor"));
  if (server.hasArg("saved")) { s += F("<div class='card ok'>"); s += txt("sensorSaved"); s += F("</div>"); }
  s += F("<h1 class='title'>♨ "); s += txt("sensor"); s += F("</h1><div class='card'><h2>"); s += txt("sensor"); s += F("</h2><form method='post'>");
  s += F("<label>"); s += txt("locationName"); s += F("</label><input name='sensorName' value='"); s += htmlEscape(cfg.sensorName); s += F("'>");
  s += F("<label>"); s += txt("sensorType"); s += F("</label><select name='dhtType'>");
  s += F("<option value='11'"); if (cfg.dhtType == 11) s += F(" selected"); s += F(">"); s += txt("blueSensor"); s += F("</option>");
  s += F("<option value='22'"); if (cfg.dhtType == 22) s += F(" selected"); s += F(">"); s += txt("whiteSensor"); s += F("</option>");
  s += F("</select>");
  s += F("<label>"); s += txt("dhtPin"); s += F("</label><select name='dhtPin'>");
  s += pinOption(D1, "D1 / GPIO5");
  s += pinOption(D2, "D2 / GPIO4");
  s += pinOption(D5, "D5 / GPIO14");
  s += pinOption(D6, "D6 / GPIO12");
  s += pinOption(D7, "D7 / GPIO13");
  s += F("</select><p>"); s += txt("normalPin"); s += F("</p>");
  s += F("<button>"); s += txt("save"); s += F("</button></form></div>");
  s += pageEnd(); server.send(200,"text/html",s);
}

void handleWifi() {
  if (server.method() == HTTP_POST) {
    strlcpySafe(cfg.wifiSsid, server.arg("ssid"), sizeof(cfg.wifiSsid));
    strlcpySafe(cfg.wifiPassword, server.arg("pass"), sizeof(cfg.wifiPassword));
    saveConfig();
    server.send(200,"text/html", pageStart(txt("saved")) + "<div class='card'>WiFi er gemt. Genstarter...</div>" + pageEnd());
    delay(1000); ESP.restart(); return;
  }
  int n = WiFi.scanNetworks();
  String s = pageStart("WiFi");
  s += F("<h1 class='title'>≋ WiFi</h1><div class='card'><h2>WiFi</h2><form method='post'><label>SSID</label><select name='ssid'>");
  s += F("<option value='"); s += htmlEscape(cfg.wifiSsid); s += F("'>"); s += htmlEscape(cfg.wifiSsid); s += F("</option>");
  for (int i=0;i<n;i++){ s += F("<option value='"); s += htmlEscape(WiFi.SSID(i)); s += F("'>"); s += htmlEscape(WiFi.SSID(i)); s += F(" ("); s += WiFi.RSSI(i); s += F(" dBm)</option>"); }
  s += F("</select><label>"); s += txt("password"); s += F("</label><input name='pass' type='password' value='"); s += htmlEscape(cfg.wifiPassword); s += F("'><button>"); s += txt("saveRestart"); s += F("</button></form></div>");
  s += pageEnd(); server.send(200,"text/html",s);
}

void handleMqtt() {
  if (server.method() == HTTP_POST) {
    strlcpySafe(cfg.mqttHost, server.arg("host"), sizeof(cfg.mqttHost));
    cfg.mqttPort = server.arg("port").toInt();
    strlcpySafe(cfg.mqttUser, server.arg("user"), sizeof(cfg.mqttUser));
    strlcpySafe(cfg.mqttPassword, server.arg("pass"), sizeof(cfg.mqttPassword));
    cfg.mqttTls = server.hasArg("tls");
    strlcpySafe(cfg.topicTemp, server.arg("temp"), sizeof(cfg.topicTemp));
    strlcpySafe(cfg.topicHum, server.arg("hum"), sizeof(cfg.topicHum));
    strlcpySafe(cfg.topicLed, server.arg("led"), sizeof(cfg.topicLed));
    saveConfig();
    setupMqtt();
    server.sendHeader("Location", "/mqtt?saved=1"); server.send(303); return;
  }
  String s = pageStart("MQTT");
  if (server.hasArg("saved")) { s += F("<div class='card ok'>"); s += txt("mqttSaved"); s += F("</div>"); }
  s += F("<h1 class='title'>◇ MQTT</h1><div class='card'><h2>MQTT</h2><form method='post'>");
  s += F("<label>"); s += txt("brokerHost"); s += F("</label><input name='host' value='"); s += htmlEscape(cfg.mqttHost); s += F("'>");
  s += F("<label>Port</label><input name='port' type='number' value='"); s += cfg.mqttPort; s += F("'>");
  s += F("<label>"); s += txt("user"); s += F("</label><input name='user' value='"); s += htmlEscape(cfg.mqttUser); s += F("'>");
  s += F("<label>"); s += txt("password"); s += F("</label><input name='pass' type='password' value='"); s += htmlEscape(cfg.mqttPassword); s += F("'>");
  s += F("<label><input style='width:auto' type='checkbox' name='tls' "); if(cfg.mqttTls) s+=F("checked"); s += F("> "); s += txt("useTls"); s += F("</label>");
  s += F("<label>"); s += txt("topicTemp"); s += F("</label><input name='temp' value='"); s += htmlEscape(cfg.topicTemp); s += F("'>");
  s += F("<label>"); s += txt("topicHum"); s += F("</label><input name='hum' value='"); s += htmlEscape(cfg.topicHum); s += F("'>");
  s += F("<label>"); s += txt("topicLed"); s += F("</label><input name='led' value='"); s += htmlEscape(cfg.topicLed); s += F("'>");
  s += F("<button>"); s += txt("save"); s += F("</button> <a class='btn bluebtn' href='/mqtttest'>"); s += txt("testMqtt"); s += F("</a></form>");
  if (server.hasArg("test")) { s += F("<p><b>"); s += txt("latestTest"); s += F(":</b> "); s += mqtt.connected() ? String("<span class='ok'>") + txt("mqttConnected") + "</span>" : String("<span class='bad'>") + txt("mqttNotConnected") + "</span>"; s += F("</p>"); }
  s += F("</div>");
  s += pageEnd(); server.send(200,"text/html",s);
}

void startWeb() {
  server.on("/icon.svg", [](){ server.send_P(200, "image/svg+xml", ICON_TEMP_MQTT); });
  server.on("/", handleRoot);
  server.on("/led", handleLed);
  server.on("/config", handleConfig);
  server.on("/sensor", handleSensor);
  server.on("/wifi", handleWifi);
  server.on("/mqtt", handleMqtt);
  server.on("/mqtttest", [](){ connectMqttNonBlocking(); delay(100); if (mqtt.connected()) { mqtt.publish(cfg.topicTemp, isnan(lastTemp) ? "0" : String(lastTemp,1).c_str(), cfg.retained); mqtt.publish(cfg.topicHum, isnan(lastHum) ? "0" : String(lastHum,1).c_str(), cfg.retained); } server.sendHeader("Location", "/mqtt?test=1"); server.send(303); });
  server.on("/restart", [](){ server.send(200,"text/html", pageStart(txt("restart")) + "<div class='card'>Genstarter...</div>" + pageEnd()); delay(1000); ESP.restart(); });
  server.on("/resetconfig", [](){ setDefaults(); saveConfig(); server.send(200,"text/html", pageStart("Reset") + "<div class='card'>Konfiguration nulstillet. Genstarter...</div>" + pageEnd()); delay(1000); ESP.restart(); });
  server.on("/debug", [](){ updateMinHeap(); String s = pageStart("Debug"); s += F("<h1 class='title'>Debug</h1><div class='card'><h2>Heap / status</h2><table class='tbl'>"); s += F("<tr><th>Free heap</th><td>"); s += String(ESP.getFreeHeap()); s += F(" bytes</td></tr><tr><th>Min heap</th><td>"); s += String(minFreeHeap == 0xFFFFFFFF ? ESP.getFreeHeap() : minFreeHeap); s += F(" bytes</td></tr><tr><th>Max free block</th><td>"); s += String(ESP.getMaxFreeBlockSize()); s += F(" bytes</td></tr><tr><th>Fragmentation</th><td>"); s += String(ESP.getHeapFragmentation()); s += F(" %</td></tr><tr><th>WiFi</th><td>"); s += WiFi.isConnected() ? "OK" : "--"; s += F("</td></tr><tr><th>MQTT</th><td>"); s += mqtt.connected() ? "OK" : "--"; s += F("</td></tr><tr><th>DHT</th><td>"); s += htmlEscape(lastDhtStatus); s += F("</td></tr><tr><th>Uptime</th><td>"); s += fmtUptime(); s += F("</td></tr></table><p>Serial Monitor viser også status hvert 30. sekund og ved MQTT/publish.</p></div>"); s += pageEnd(); server.send(200,"text/html",s); });
  server.on("/info", [](){ String s = pageStart(txt("info")); s += F("<h1 class='title'>ⓘ "); s += txt("info"); s += F("</h1><div class='card'><h2>NodeMCU TempMQTT</h2><p>"); s += txt("about"); s += F("</p><table class='tbl'><tr><th>Version</th><td>v"); s += APP_VERSION; s += F("</td></tr><tr><th>Jens</th><td>Jens Krogsgaard / Krogsgaards Modelbane</td></tr><tr><th>Platform</th><td>NodeMCU ESP8266</td></tr><tr><th>Sensorer</th><td>DHT11 og DHT22 / AM2302</td></tr></table></div>"); s += pageEnd(); server.send(200,"text/html",s); });
  server.on("/firmware", HTTP_GET, [](){ String s = pageStart(txt("firmware")); s += F("<h1 class='title'>↥ "); s += txt("firmware"); s += F("</h1><div class='card'><h2>"); s += txt("firmwareUpload"); s += F("</h2><p>"); s += txt("firmwareHelp"); s += F("</p><form method='POST' action='/firmware' enctype='multipart/form-data'><input type='file' name='update'><button>Update Firmware</button></form></div>"); s += pageEnd(); server.send(200,"text/html",s); });
  server.on("/firmware", HTTP_POST, [](){ server.sendHeader("Connection", "close"); server.send(200, "text/html", pageStart(txt("firmware")) + String(Update.hasError() ? "<div class='card bad'>Firmware update fejlede.</div>" : "<div class='card ok'>Firmware opdateret. Genstarter...</div>") + pageEnd()); delay(1000); ESP.restart(); }, [](){ HTTPUpload& upload = server.upload(); if(upload.status == UPLOAD_FILE_START){ WiFiUDP::stopAll(); Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000); } else if(upload.status == UPLOAD_FILE_WRITE){ Update.write(upload.buf, upload.currentSize); } else if(upload.status == UPLOAD_FILE_END){ Update.end(true); } });
  server.onNotFound([](){ if (portalMode) { server.sendHeader("Location", String("http://") + WiFi.softAPIP().toString(), true); server.send(302, "text/plain", ""); } else handleRoot(); });
  server.begin();
}
