diff --git a/panther2d.js b/panther2d.js
index b856d8fc507499b7f13c6b9f1786615bd40e40ab..9183d135e0a2bf82679e6d9719d2c4bee4165e53 100644
--- a/panther2d.js
+++ b/panther2d.js
@@ -24,6 +24,7 @@
 	const state = document.location.search.indexOf('ps')>-1;
 	const vlv = document.location.search.indexOf('vlv')>-1;
 	const vlvs = [];
+	const speechToken = Math.floor(Math.random() * 1000000000000);
 	for (let i=0; i<pa.length; i++) {const p = pa[i].split('='); parameters[p[0]] = p[1];}
 	const machineCaseSensitive = document.location.search.indexOf('machine=')>-1? document.location.search.split('machine=')[1].split('&')[0]: 'elettra';
 	const machine = machineCaseSensitive.toLowerCase();
@@ -34,6 +35,9 @@
 			fel2 = fel[6] == 1;
 		});
 	}
+	function resizeIframe(obj) {
+		obj.style.height = obj.contentWindow.document.documentElement.scrollHeight + 'px';
+	}
 	const latticeFile = document.location.href.split('?')[0].split('/').slice(0,-1).join('/')+'/'+machine+'_lattice.json';
 	const params = {machine: machineCaseSensitive.toLowerCase(), search: '', backgroundColor: '#333333'};
 	gui.title('PAnTHer - controls');
@@ -43,6 +47,9 @@
 		$("#sname").on("keydown", searchText);
 		$('#starter').hide();
 	}
+	// Polyfill replaceAll()
+	if (typeof String.prototype.replaceAll !== 'function') {
+	}
 	// Polyfill for parentNode.replaceChildren()
 	if (typeof Element.prototype.replaceChildren !== 'function') {
 		Object.defineProperty(Element.prototype, 'replaceChildren', {
@@ -78,6 +85,7 @@
 	// if (navigator.userAgent.indexOf('Firefox/63')==-1) {gui.add(params, 'machine', conf.machineList).onChange(function() {toggleMachine(params.machine);});}
 	gui.add(params, 'machine', conf.machineList).onChange(function() {toggleMachine(params.machine);});
 	gui.add(params, 'search');
+	$('.controller.string:last').parent().append('<iframe id="talk" src="./speech/talk.php?background=black&token='+speechToken+'" frameborder="0" scrolling="no" onload="resizeIframe(this)"></iframe>');
 	gui.addColor(params, 'backgroundColor').onChange(function() {toggleParam('backgroundColor');});
 	params.vlv = document.location.search.indexOf('vlv')>-1;
 	gui.add(params, 'vlv').name('vlv & bst').onChange(function() {toggleParam('vlv');});
@@ -105,12 +113,13 @@
 		if (name==null) name = document.getElementById('sname').value;
 		for (let i=0; i<alias.length; i++) if (alias[i][1]==name) name = alias[i][0];
 		document.getElementById('sname').value = name;
-		if (typeof $('#'+name)[0] == 'undefined') return;
+		if (typeof $('#'+name)[0] == 'undefined') return name+' Not found';
 		console.log(name, window.innerWidth/2, $('#'+name)[0].getCTM().e, $('#'+name)[0].getCTM().f);
 		// panZoomPanther.zoomAtPoint(10, {x: window.innerWidth/2 - $('#'+name)[0].getCTM().e, y: window.innerHeight/2 - $('#'+name)[0].getCTM().f})
 		panZoomPanther.zoom(10);
 		// leave a delay between zoom and pan
 		setTimeout(mypan, 1200, name);
+		return 'OK';
 	}
 	function mypan(name) {
 		$('.label').show(); 
@@ -139,30 +148,33 @@
 		const res = search.join('&');
 		document.location = document.location.href.split('?')[0]+(res.length? '?'+res: '');
 	}
-	async function subscribe() {
-		let response = await fetch("./misc/talk.php?read");
+	async function subscribeSpeech() {
+		mylog('subscribeSpeech()', 1, 2);
+		let response = await fetch("./speech/talk.php?read&token="+speechToken);
 		if (response.status == 502) {
-			await subscribe();
+			await subscribeSpeech();
 		} else if (response.status != 200) {
 			// An error - let's show it
-			mylog('subscribe() ERROR ', response.statusText);
+			mylog('subscribeSpeech() ERROR ', response.statusText);
 			// Reconnect in one second
 			await new Promise(resolve => setTimeout(resolve, 1000));
-			await subscribe();
+			await subscribeSpeech();
 		} else {
 			// Get and show the message
 			let message = await response.text();
-			mylog('subscribe()', message);
-			highlightobjects(message);
-			// Call subscribe() again to get the next message
-			await subscribe();
+			mylog('subscribeSpeech()', message);
+			const msg = findComponent(message);
+			// return feedback
+			fetch("./speech/talk.php?readresult="+msg+"&token="+speechToken);
+			// Call subscribeSpeech() again to get the next message
+			await subscribeSpeech();
 		}
 	}
-	// subscribe();
 
 	function mylog(...args) {
-		if (document.location.search.indexOf('debug')>-1) $('#debug').html($('#debug').html()+JSON.stringify(args).replaceAll(':',': ').replaceAll(',',', ')+'\n');
-		else {console.trace.apply(null, args);}
+		if (document.location.search.indexOf('debug')>-1) $('#debug').val($('#debug').val()+JSON.stringify(args)+'\n');
+		console.trace.apply(null);
+		console.log(args, JSON.stringify(args), $('#debug').val());
 	}
 	function initIndex(lattice) {
 		const index = [];
@@ -225,6 +237,7 @@
 				$('#tooltip').css('width', '100%');
 				$('#tooltip').html('<textarea id="debug" style="height: 100px; width:100%;"></textarea>');
 			}
+			subscribeSpeech();
 			let pan = [window.innerWidth/2, window.innerHeight/2];
 			if (document.location.search.indexOf('pan=')>-1) pan = document.location.search.split('pan=')[1].split('&')[0].split(',');
 			if (document.location.search.indexOf('search=')>-1) findComponent(document.location.search.split('search=')[1].split('&')[0]);