diff --git a/mobile/android/app/src/main/assets/capacitor.config.json b/mobile/android/app/src/main/assets/capacitor.config.json
index 75bcc7c..813d996 100644
--- a/mobile/android/app/src/main/assets/capacitor.config.json
+++ b/mobile/android/app/src/main/assets/capacitor.config.json
@@ -28,7 +28,9 @@
},
"android": {
"allowMixedContent": true,
- "backgroundColor": "#2563eb"
+ "backgroundColor": "#2563eb",
+ "webContentsDebuggingEnabled": true,
+ "appendUserAgent": "PedScribe-Android"
},
"ios": {
"backgroundColor": "#2563eb",
diff --git a/mobile/android/app/src/main/assets/public/index.html b/mobile/android/app/src/main/assets/public/index.html
index 2936fd2..ae6cb0c 100644
--- a/mobile/android/app/src/main/assets/public/index.html
+++ b/mobile/android/app/src/main/assets/public/index.html
@@ -23,22 +23,6 @@
-
-
-
-
PedScribe
-
Unlock to continue
-
-
-
diff --git a/mobile/android/app/src/main/assets/public/launcher.js b/mobile/android/app/src/main/assets/public/launcher.js
index 1c5e663..8ce1804 100644
--- a/mobile/android/app/src/main/assets/public/launcher.js
+++ b/mobile/android/app/src/main/assets/public/launcher.js
@@ -1,36 +1,26 @@
// PedScribe Mobile Launcher
-// Handles server URL config, biometric auth, and native features
+// Handles configurable server URL and auto-redirect
(function() {
var STORAGE_KEY = 'pedscribe_server_url';
- var BIOMETRIC_KEY = 'pedscribe_biometric_enabled';
var DEFAULT_URL = 'https://app.pedshub.com';
var setupScreen = document.getElementById('setup-screen');
var connectingScreen = document.getElementById('connecting-screen');
- var biometricScreen = document.getElementById('biometric-screen');
var urlInput = document.getElementById('server-url');
var connectBtn = document.getElementById('btn-connect');
var changeBtn = document.getElementById('btn-change-server');
- var unlockBtn = document.getElementById('btn-unlock');
- var skipBioBtn = document.getElementById('btn-skip-biometric');
var savedUrl = localStorage.getItem(STORAGE_KEY);
- var biometricEnabled = localStorage.getItem(BIOMETRIC_KEY) === 'true';
- // ── Launch flow ──
if (savedUrl) {
- if (biometricEnabled && isCapacitor()) {
- showBiometricScreen(savedUrl);
- } else {
- showConnecting(savedUrl);
- }
+ showConnecting(savedUrl);
} else {
urlInput.value = DEFAULT_URL;
showScreen('setup');
}
- // ── Connect button ──
+ // Connect button
connectBtn.addEventListener('click', function() {
var url = (urlInput.value || DEFAULT_URL).trim().replace(/\/+$/, '');
if (!url.startsWith('http')) url = 'https://' + url;
@@ -39,27 +29,15 @@
connectBtn.textContent = 'Connecting...';
haptic();
- testServer(url, function(ok) {
- if (ok) {
- localStorage.setItem(STORAGE_KEY, url);
- // Enable biometric on first connect if available
- checkBiometricAvailable(function(available) {
- if (available) localStorage.setItem(BIOMETRIC_KEY, 'true');
- });
- navigateToServer(url);
- } else {
- connectBtn.disabled = false;
- connectBtn.textContent = 'Connect';
- showError('Could not reach server. Check the URL and try again.');
- }
- });
+ localStorage.setItem(STORAGE_KEY, url);
+ navigateToServer(url);
});
urlInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') connectBtn.click();
});
- // ── Change server ──
+ // Change server
changeBtn.addEventListener('click', function() {
localStorage.removeItem(STORAGE_KEY);
urlInput.value = savedUrl || DEFAULT_URL;
@@ -67,114 +45,21 @@
urlInput.focus();
});
- // ── Biometric unlock ──
- if (unlockBtn) {
- unlockBtn.addEventListener('click', function() {
- doBiometricAuth(function(ok) {
- if (ok) {
- showConnecting(savedUrl);
- } else {
- showError('Authentication failed. Try again.');
- }
- });
- });
- }
-
- if (skipBioBtn) {
- skipBioBtn.addEventListener('click', function() {
- showConnecting(savedUrl);
- });
- }
-
- // ── Screen management ──
+ // Screen management
function showScreen(which) {
setupScreen.style.display = which === 'setup' ? '' : 'none';
connectingScreen.style.display = which === 'connecting' ? '' : 'none';
- biometricScreen.style.display = which === 'biometric' ? '' : 'none';
- }
-
- function showBiometricScreen(url) {
- showScreen('biometric');
- // Auto-trigger biometric prompt
- setTimeout(function() {
- doBiometricAuth(function(ok) {
- if (ok) showConnecting(url);
- // If fails, user sees the manual Unlock button
- });
- }, 300);
}
function showConnecting(url) {
showScreen('connecting');
- setTimeout(function() {
- testServer(url, function(ok) {
- if (ok) {
- navigateToServer(url);
- } else {
- urlInput.value = url;
- showScreen('setup');
- showError('Server not reachable. Check your connection or change the URL.');
- }
- });
- }, 500);
- }
-
- // ── Server check ──
- function testServer(url, callback) {
- // In native WebView, no-cors fetch and image probes are unreliable.
- // Just try a normal fetch to the health endpoint.
- fetch(url + '/api/health', { signal: AbortSignal.timeout(8000) })
- .then(function(r) { callback(true); })
- .catch(function() {
- // If CORS blocks it, that still means the server is reachable
- // (CORS error = server responded but blocked the origin).
- // Just navigate — the WebView will handle it.
- callback(true);
- });
+ setTimeout(function() { navigateToServer(url); }, 800);
}
function navigateToServer(url) {
window.location.href = url;
}
- // ── Biometric auth ──
- function isCapacitor() {
- return !!(window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform());
- }
-
- function checkBiometricAvailable(callback) {
- if (!isCapacitor()) { callback(false); return; }
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(false); return; }
- BiometricAuth.checkBiometry().then(function(result) {
- callback(result.isAvailable);
- }).catch(function() { callback(false); });
- } catch(e) { callback(false); }
- }
-
- function doBiometricAuth(callback) {
- if (!isCapacitor()) { callback(true); return; } // Skip on web
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(true); return; }
- BiometricAuth.authenticate({
- reason: 'Unlock PedScribe',
- cancelTitle: 'Cancel',
- allowDeviceCredential: true, // Allow PIN/password fallback
- iosFallbackTitle: 'Use Passcode',
- androidTitle: 'PedScribe',
- androidSubtitle: 'Verify your identity to continue',
- androidConfirmationRequired: false
- }).then(function() {
- callback(true);
- }).catch(function() {
- callback(false);
- });
- } catch(e) { callback(true); } // Fail open if plugin not available
- }
-
- // ── Helpers ──
function haptic() {
try {
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics) {
@@ -182,16 +67,4 @@
}
} catch(e) {}
}
-
- function showError(msg) {
- var existing = document.querySelector('.error-msg');
- if (existing) existing.remove();
- var div = document.createElement('div');
- div.className = 'error-msg';
- div.style.display = 'block';
- div.textContent = msg;
- var parent = connectBtn ? connectBtn.parentNode : document.querySelector('.launcher');
- if (parent) parent.appendChild(div);
- setTimeout(function() { if (div.parentNode) div.remove(); }, 8000);
- }
})();
diff --git a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
index e3c1876..aa5b1d7 100644
--- a/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
+++ b/mobile/android/app/src/main/java/com/pedshub/scribe/MainActivity.java
@@ -1,24 +1,34 @@
package com.pedshub.scribe;
+import android.Manifest;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.webkit.PermissionRequest;
-import android.webkit.WebChromeClient;
+
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
+ private static final int MIC_PERMISSION_CODE = 1001;
+
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- // Auto-grant WebView permissions (microphone, camera)
- // The Android runtime permission dialog still shows on first use
- this.bridge.getWebView().setWebChromeClient(new WebChromeClient() {
- @Override
- public void onPermissionRequest(PermissionRequest request) {
- request.grant(request.getResources());
- }
- });
+ // Request microphone permission at app start (not just when WebView asks)
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
+ != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this,
+ new String[]{ Manifest.permission.RECORD_AUDIO }, MIC_PERMISSION_CODE);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ // Capacitor handles the result via its bridge
}
}
diff --git a/mobile/capacitor.config.json b/mobile/capacitor.config.json
index 5d2360e..4524111 100644
--- a/mobile/capacitor.config.json
+++ b/mobile/capacitor.config.json
@@ -22,7 +22,9 @@
},
"android": {
"allowMixedContent": true,
- "backgroundColor": "#2563eb"
+ "backgroundColor": "#2563eb",
+ "webContentsDebuggingEnabled": true,
+ "appendUserAgent": "PedScribe-Android"
},
"ios": {
"backgroundColor": "#2563eb",
diff --git a/mobile/ios/App/App/capacitor.config.json b/mobile/ios/App/App/capacitor.config.json
index 583c488..293c1a2 100644
--- a/mobile/ios/App/App/capacitor.config.json
+++ b/mobile/ios/App/App/capacitor.config.json
@@ -28,7 +28,9 @@
},
"android": {
"allowMixedContent": true,
- "backgroundColor": "#2563eb"
+ "backgroundColor": "#2563eb",
+ "webContentsDebuggingEnabled": true,
+ "appendUserAgent": "PedScribe-Android"
},
"ios": {
"backgroundColor": "#2563eb",
diff --git a/mobile/ios/App/App/public/index.html b/mobile/ios/App/App/public/index.html
index 2936fd2..ae6cb0c 100644
--- a/mobile/ios/App/App/public/index.html
+++ b/mobile/ios/App/App/public/index.html
@@ -23,22 +23,6 @@
-
-
-
-
PedScribe
-
Unlock to continue
-
-
-
diff --git a/mobile/ios/App/App/public/launcher.js b/mobile/ios/App/App/public/launcher.js
index 1c5e663..8ce1804 100644
--- a/mobile/ios/App/App/public/launcher.js
+++ b/mobile/ios/App/App/public/launcher.js
@@ -1,36 +1,26 @@
// PedScribe Mobile Launcher
-// Handles server URL config, biometric auth, and native features
+// Handles configurable server URL and auto-redirect
(function() {
var STORAGE_KEY = 'pedscribe_server_url';
- var BIOMETRIC_KEY = 'pedscribe_biometric_enabled';
var DEFAULT_URL = 'https://app.pedshub.com';
var setupScreen = document.getElementById('setup-screen');
var connectingScreen = document.getElementById('connecting-screen');
- var biometricScreen = document.getElementById('biometric-screen');
var urlInput = document.getElementById('server-url');
var connectBtn = document.getElementById('btn-connect');
var changeBtn = document.getElementById('btn-change-server');
- var unlockBtn = document.getElementById('btn-unlock');
- var skipBioBtn = document.getElementById('btn-skip-biometric');
var savedUrl = localStorage.getItem(STORAGE_KEY);
- var biometricEnabled = localStorage.getItem(BIOMETRIC_KEY) === 'true';
- // ── Launch flow ──
if (savedUrl) {
- if (biometricEnabled && isCapacitor()) {
- showBiometricScreen(savedUrl);
- } else {
- showConnecting(savedUrl);
- }
+ showConnecting(savedUrl);
} else {
urlInput.value = DEFAULT_URL;
showScreen('setup');
}
- // ── Connect button ──
+ // Connect button
connectBtn.addEventListener('click', function() {
var url = (urlInput.value || DEFAULT_URL).trim().replace(/\/+$/, '');
if (!url.startsWith('http')) url = 'https://' + url;
@@ -39,27 +29,15 @@
connectBtn.textContent = 'Connecting...';
haptic();
- testServer(url, function(ok) {
- if (ok) {
- localStorage.setItem(STORAGE_KEY, url);
- // Enable biometric on first connect if available
- checkBiometricAvailable(function(available) {
- if (available) localStorage.setItem(BIOMETRIC_KEY, 'true');
- });
- navigateToServer(url);
- } else {
- connectBtn.disabled = false;
- connectBtn.textContent = 'Connect';
- showError('Could not reach server. Check the URL and try again.');
- }
- });
+ localStorage.setItem(STORAGE_KEY, url);
+ navigateToServer(url);
});
urlInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') connectBtn.click();
});
- // ── Change server ──
+ // Change server
changeBtn.addEventListener('click', function() {
localStorage.removeItem(STORAGE_KEY);
urlInput.value = savedUrl || DEFAULT_URL;
@@ -67,114 +45,21 @@
urlInput.focus();
});
- // ── Biometric unlock ──
- if (unlockBtn) {
- unlockBtn.addEventListener('click', function() {
- doBiometricAuth(function(ok) {
- if (ok) {
- showConnecting(savedUrl);
- } else {
- showError('Authentication failed. Try again.');
- }
- });
- });
- }
-
- if (skipBioBtn) {
- skipBioBtn.addEventListener('click', function() {
- showConnecting(savedUrl);
- });
- }
-
- // ── Screen management ──
+ // Screen management
function showScreen(which) {
setupScreen.style.display = which === 'setup' ? '' : 'none';
connectingScreen.style.display = which === 'connecting' ? '' : 'none';
- biometricScreen.style.display = which === 'biometric' ? '' : 'none';
- }
-
- function showBiometricScreen(url) {
- showScreen('biometric');
- // Auto-trigger biometric prompt
- setTimeout(function() {
- doBiometricAuth(function(ok) {
- if (ok) showConnecting(url);
- // If fails, user sees the manual Unlock button
- });
- }, 300);
}
function showConnecting(url) {
showScreen('connecting');
- setTimeout(function() {
- testServer(url, function(ok) {
- if (ok) {
- navigateToServer(url);
- } else {
- urlInput.value = url;
- showScreen('setup');
- showError('Server not reachable. Check your connection or change the URL.');
- }
- });
- }, 500);
- }
-
- // ── Server check ──
- function testServer(url, callback) {
- // In native WebView, no-cors fetch and image probes are unreliable.
- // Just try a normal fetch to the health endpoint.
- fetch(url + '/api/health', { signal: AbortSignal.timeout(8000) })
- .then(function(r) { callback(true); })
- .catch(function() {
- // If CORS blocks it, that still means the server is reachable
- // (CORS error = server responded but blocked the origin).
- // Just navigate — the WebView will handle it.
- callback(true);
- });
+ setTimeout(function() { navigateToServer(url); }, 800);
}
function navigateToServer(url) {
window.location.href = url;
}
- // ── Biometric auth ──
- function isCapacitor() {
- return !!(window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform());
- }
-
- function checkBiometricAvailable(callback) {
- if (!isCapacitor()) { callback(false); return; }
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(false); return; }
- BiometricAuth.checkBiometry().then(function(result) {
- callback(result.isAvailable);
- }).catch(function() { callback(false); });
- } catch(e) { callback(false); }
- }
-
- function doBiometricAuth(callback) {
- if (!isCapacitor()) { callback(true); return; } // Skip on web
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(true); return; }
- BiometricAuth.authenticate({
- reason: 'Unlock PedScribe',
- cancelTitle: 'Cancel',
- allowDeviceCredential: true, // Allow PIN/password fallback
- iosFallbackTitle: 'Use Passcode',
- androidTitle: 'PedScribe',
- androidSubtitle: 'Verify your identity to continue',
- androidConfirmationRequired: false
- }).then(function() {
- callback(true);
- }).catch(function() {
- callback(false);
- });
- } catch(e) { callback(true); } // Fail open if plugin not available
- }
-
- // ── Helpers ──
function haptic() {
try {
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics) {
@@ -182,16 +67,4 @@
}
} catch(e) {}
}
-
- function showError(msg) {
- var existing = document.querySelector('.error-msg');
- if (existing) existing.remove();
- var div = document.createElement('div');
- div.className = 'error-msg';
- div.style.display = 'block';
- div.textContent = msg;
- var parent = connectBtn ? connectBtn.parentNode : document.querySelector('.launcher');
- if (parent) parent.appendChild(div);
- setTimeout(function() { if (div.parentNode) div.remove(); }, 8000);
- }
})();
diff --git a/mobile/src/index.html b/mobile/src/index.html
index 2936fd2..ae6cb0c 100644
--- a/mobile/src/index.html
+++ b/mobile/src/index.html
@@ -23,22 +23,6 @@
-
-
-
-
PedScribe
-
Unlock to continue
-
-
-
diff --git a/mobile/src/launcher.js b/mobile/src/launcher.js
index 1c5e663..8ce1804 100644
--- a/mobile/src/launcher.js
+++ b/mobile/src/launcher.js
@@ -1,36 +1,26 @@
// PedScribe Mobile Launcher
-// Handles server URL config, biometric auth, and native features
+// Handles configurable server URL and auto-redirect
(function() {
var STORAGE_KEY = 'pedscribe_server_url';
- var BIOMETRIC_KEY = 'pedscribe_biometric_enabled';
var DEFAULT_URL = 'https://app.pedshub.com';
var setupScreen = document.getElementById('setup-screen');
var connectingScreen = document.getElementById('connecting-screen');
- var biometricScreen = document.getElementById('biometric-screen');
var urlInput = document.getElementById('server-url');
var connectBtn = document.getElementById('btn-connect');
var changeBtn = document.getElementById('btn-change-server');
- var unlockBtn = document.getElementById('btn-unlock');
- var skipBioBtn = document.getElementById('btn-skip-biometric');
var savedUrl = localStorage.getItem(STORAGE_KEY);
- var biometricEnabled = localStorage.getItem(BIOMETRIC_KEY) === 'true';
- // ── Launch flow ──
if (savedUrl) {
- if (biometricEnabled && isCapacitor()) {
- showBiometricScreen(savedUrl);
- } else {
- showConnecting(savedUrl);
- }
+ showConnecting(savedUrl);
} else {
urlInput.value = DEFAULT_URL;
showScreen('setup');
}
- // ── Connect button ──
+ // Connect button
connectBtn.addEventListener('click', function() {
var url = (urlInput.value || DEFAULT_URL).trim().replace(/\/+$/, '');
if (!url.startsWith('http')) url = 'https://' + url;
@@ -39,27 +29,15 @@
connectBtn.textContent = 'Connecting...';
haptic();
- testServer(url, function(ok) {
- if (ok) {
- localStorage.setItem(STORAGE_KEY, url);
- // Enable biometric on first connect if available
- checkBiometricAvailable(function(available) {
- if (available) localStorage.setItem(BIOMETRIC_KEY, 'true');
- });
- navigateToServer(url);
- } else {
- connectBtn.disabled = false;
- connectBtn.textContent = 'Connect';
- showError('Could not reach server. Check the URL and try again.');
- }
- });
+ localStorage.setItem(STORAGE_KEY, url);
+ navigateToServer(url);
});
urlInput.addEventListener('keydown', function(e) {
if (e.key === 'Enter') connectBtn.click();
});
- // ── Change server ──
+ // Change server
changeBtn.addEventListener('click', function() {
localStorage.removeItem(STORAGE_KEY);
urlInput.value = savedUrl || DEFAULT_URL;
@@ -67,114 +45,21 @@
urlInput.focus();
});
- // ── Biometric unlock ──
- if (unlockBtn) {
- unlockBtn.addEventListener('click', function() {
- doBiometricAuth(function(ok) {
- if (ok) {
- showConnecting(savedUrl);
- } else {
- showError('Authentication failed. Try again.');
- }
- });
- });
- }
-
- if (skipBioBtn) {
- skipBioBtn.addEventListener('click', function() {
- showConnecting(savedUrl);
- });
- }
-
- // ── Screen management ──
+ // Screen management
function showScreen(which) {
setupScreen.style.display = which === 'setup' ? '' : 'none';
connectingScreen.style.display = which === 'connecting' ? '' : 'none';
- biometricScreen.style.display = which === 'biometric' ? '' : 'none';
- }
-
- function showBiometricScreen(url) {
- showScreen('biometric');
- // Auto-trigger biometric prompt
- setTimeout(function() {
- doBiometricAuth(function(ok) {
- if (ok) showConnecting(url);
- // If fails, user sees the manual Unlock button
- });
- }, 300);
}
function showConnecting(url) {
showScreen('connecting');
- setTimeout(function() {
- testServer(url, function(ok) {
- if (ok) {
- navigateToServer(url);
- } else {
- urlInput.value = url;
- showScreen('setup');
- showError('Server not reachable. Check your connection or change the URL.');
- }
- });
- }, 500);
- }
-
- // ── Server check ──
- function testServer(url, callback) {
- // In native WebView, no-cors fetch and image probes are unreliable.
- // Just try a normal fetch to the health endpoint.
- fetch(url + '/api/health', { signal: AbortSignal.timeout(8000) })
- .then(function(r) { callback(true); })
- .catch(function() {
- // If CORS blocks it, that still means the server is reachable
- // (CORS error = server responded but blocked the origin).
- // Just navigate — the WebView will handle it.
- callback(true);
- });
+ setTimeout(function() { navigateToServer(url); }, 800);
}
function navigateToServer(url) {
window.location.href = url;
}
- // ── Biometric auth ──
- function isCapacitor() {
- return !!(window.Capacitor && window.Capacitor.isNativePlatform && window.Capacitor.isNativePlatform());
- }
-
- function checkBiometricAvailable(callback) {
- if (!isCapacitor()) { callback(false); return; }
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(false); return; }
- BiometricAuth.checkBiometry().then(function(result) {
- callback(result.isAvailable);
- }).catch(function() { callback(false); });
- } catch(e) { callback(false); }
- }
-
- function doBiometricAuth(callback) {
- if (!isCapacitor()) { callback(true); return; } // Skip on web
- try {
- var BiometricAuth = window.Capacitor.Plugins.BiometricAuth;
- if (!BiometricAuth) { callback(true); return; }
- BiometricAuth.authenticate({
- reason: 'Unlock PedScribe',
- cancelTitle: 'Cancel',
- allowDeviceCredential: true, // Allow PIN/password fallback
- iosFallbackTitle: 'Use Passcode',
- androidTitle: 'PedScribe',
- androidSubtitle: 'Verify your identity to continue',
- androidConfirmationRequired: false
- }).then(function() {
- callback(true);
- }).catch(function() {
- callback(false);
- });
- } catch(e) { callback(true); } // Fail open if plugin not available
- }
-
- // ── Helpers ──
function haptic() {
try {
if (window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.Haptics) {
@@ -182,16 +67,4 @@
}
} catch(e) {}
}
-
- function showError(msg) {
- var existing = document.querySelector('.error-msg');
- if (existing) existing.remove();
- var div = document.createElement('div');
- div.className = 'error-msg';
- div.style.display = 'block';
- div.textContent = msg;
- var parent = connectBtn ? connectBtn.parentNode : document.querySelector('.launcher');
- if (parent) parent.appendChild(div);
- setTimeout(function() { if (div.parentNode) div.remove(); }, 8000);
- }
})();