diff --git a/web/chat.html b/web/chat.html
index f23c42f..b564d93 100644
--- a/web/chat.html
+++ b/web/chat.html
@@ -454,7 +454,12 @@ var QRCode;(function(){function QR8bitByte(data){this.mode=QRMode.MODE_8BIT_BYTE
* ============================================================== */
const ZEBRA_VOL_MARK = 0.80;
const ZEBRA_VOL_SPACE = 0.20;
-const ZEBRA_BAUD_HANDSHAKE = 50;
+/* Link baud for the whole audio modem (handshake + data). Tunable via ?baud=N
+ * because the WebRTC Opus path encodes in 20ms frames and smears bit edges:
+ * lower baud = more audio quanta per symbol = survives the smear, but slower.
+ * Both peers must use the same value (same default, or same ?baud=). */
+const _BAUD_PARAM = parseInt(new URLSearchParams(location.search).get('baud'), 10);
+const ZEBRA_BAUD_HANDSHAKE = (_BAUD_PARAM >= 1 && _BAUD_PARAM <= 1000) ? _BAUD_PARAM : 20;
const ZEBRA_BAUD_MIN = 1;
const ZEBRA_BAUD_MAX = 100000;
const ZEBRA_BAUD_DEFAULT = 10;
@@ -1130,7 +1135,7 @@ async function announceHello() {
if (!myMaxBaud) myMaxBaud = ZEBRA_BAUD_DEFAULT;
const hello = await buildHelloFrame(handle, myMaxBaud);
await txFrame(hello, ZEBRA_BAUD_HANDSHAKE);
- logLine('sys', `tx: HELLO as ${escapeHtml(handle)} (${myMaxBaud} baud)`);
+ logLine('sys', `tx: HELLO as ${escapeHtml(handle)} at ${ZEBRA_BAUD_HANDSHAKE} baud (link)`);
}
/* ============================================================== *