Ambient Private Tool Ambient Live Revision View
Ambient website markup

Pin the issue. Add context. Send the view.

Load an Ambient page, save the current screen view, then add pins or circles with comments, section names, and optional reference images.

1 Choose action
Browse lets you move around. Pin and Circle let you mark the issue.
2 Optional recording
Optional: record your screen if you want to explain the revisions out loud.
Start by loading an Ambient page. Empty saved views stay hidden until a pin or circle is added.

Add revision comment

Add the page/section location and write the comment or feedback needed. You can also upload a reference image.

Use this if you want to attach an example image to this pin/circle.

Revision detail

Saved view marker details.

OPEN
Ready.
`; }function exportRecordedLiveView() { if (!countMarks()) { showToast("Add at least one revision comment first."); return; }saveSession();const html = buildRecordedLiveViewHtml(); const blob = new Blob([html], { type: "text/html" });if (lastOpenedBlobUrl) { URL.revokeObjectURL(lastOpenedBlobUrl); }const blobUrl = URL.createObjectURL(blob); lastOpenedBlobUrl = blobUrl;window.open(blobUrl, "_blank");const link = document.createElement("a"); link.download = "ambient-recorded-live-revision-view-v9.html"; link.href = blobUrl; document.body.appendChild(link); link.click(); link.remove();showToast("Recorded Live View opened and downloaded."); setStatus("Recorded Live View opened and downloaded. Send the HTML file to the developer."); }function countMarks() { let total = 0; Object.keys(session.pages).forEach(function (pageUrl) { total += session.pages[pageUrl].marks.length; }); return total; }async function startRecording() { if (!navigator.mediaDevices || !navigator.mediaDevices.getDisplayMedia) { showToast("Screen recording is not supported on this browser."); return; }try { recordedChunks = []; recordingStream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: false });mediaRecorder = new MediaRecorder(recordingStream);mediaRecorder.ondataavailable = function (event) { if (event.data && event.data.size > 0) { recordedChunks.push(event.data); } };mediaRecorder.onstop = function () { const blob = new Blob(recordedChunks, { type: "video/webm" }); const link = document.createElement("a"); link.download = "ambient-live-revision-recording.webm"; link.href = URL.createObjectURL(blob); link.click();if (recordingStream) { recordingStream.getTracks().forEach(function (track) { track.stop(); }); }isRecording = false; recordToggleBtn.textContent = "Start Recording"; recordToggleBtn.classList.remove("ambient-live-btn-main"); recordToggleBtn.classList.add("ambient-live-btn-soft");setStatus("Recording downloaded."); showToast("Recording downloaded."); };mediaRecorder.start();isRecording = true; recordToggleBtn.textContent = "Stop + Download Recording"; recordToggleBtn.classList.remove("ambient-live-btn-soft"); recordToggleBtn.classList.add("ambient-live-btn-main");setStatus("Recording started. Choose this browser tab/window when prompted."); showToast("Recording started."); } catch (error) { isRecording = false; recordToggleBtn.textContent = "Start Recording"; showToast("Recording was cancelled or blocked."); } }function stopRecording() { if (mediaRecorder && mediaRecorder.state !== "inactive") { mediaRecorder.stop(); } }function toggleRecording() { if (isRecording) { stopRecording(); } else { startRecording(); } }function clearProject() { const confirmed = confirm("Clear all saved revision comments from this browser?"); if (!confirmed) return;session = { project: "Ambient Recorded Live Revision View", createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), senderEmail: "", developerEmail: "", pages: {} };currentUrl = ""; activeViewId = ""; urlInput.value = ""; currentUrlInput.value = ""; senderEmailInput.value = ""; developerEmailInput.value = ""; frame.src = "about:blank";localStorage.removeItem(STORAGE_KEY);renderMarkers(); renderComments(); renderPageHistory(); renderActiveViewLabel();setStatus("Project cleared."); showToast("Project cleared."); }function readReferenceFile(file) { return new Promise(function (resolve, reject) { if (!file || !file.type.startsWith("image/")) { reject(new Error("Please upload an image file.")); return; }const reader = new FileReader();reader.onload = function (event) { resolve({ name: file.name, type: file.type, size: file.size, dataUrl: event.target.result }); };reader.onerror = function () { reject(new Error("Could not read the image.")); };reader.readAsDataURL(file); }); }referenceUpload.addEventListener("change", async function () { const file = referenceUpload.files && referenceUpload.files[0];if (!file) { selectedReference = null; renderReferencePreview(null); return; }if (file.size > 2.5 * 1024 * 1024) { referenceUpload.value = ""; showToast("Please use an image under 2.5MB."); return; }try { selectedReference = await readReferenceFile(file); renderReferencePreview(selectedReference); showToast("Reference image attached."); } catch (error) { selectedReference = null; renderReferencePreview(null); showToast(error.message || "Could not attach image."); } });removeReferenceBtn.addEventListener("click", function () { selectedReference = null; referenceUpload.value = ""; renderReferencePreview(null); showToast("Reference image removed."); });deleteFromEditBtn.addEventListener("click", function () { if (editingMarkId) { deleteMark(editingMarkId); } });loadSession(); renderComments(); renderPageHistory(); renderActiveViewLabel();const savedTheme = localStorage.getItem("ambientLiveRevisionThemeV9"); if (savedTheme === "light" || savedTheme === "dark") { root.setAttribute("data-theme", savedTheme); }root.querySelector("[data-theme-toggle]").addEventListener("click", function () { const currentTheme = root.getAttribute("data-theme"); const nextTheme = currentTheme === "dark" ? "light" : "dark"; root.setAttribute("data-theme", nextTheme); localStorage.setItem("ambientLiveRevisionThemeV9", nextTheme); });root.querySelector("[data-load-site]").addEventListener("click", function () { loadWebsite(urlInput.value); });urlInput.addEventListener("keydown", function (event) { if (event.key === "Enter") { event.preventDefault(); loadWebsite(urlInput.value); } });root.querySelector("[data-save-view]").addEventListener("click", saveCurrentView);frame.addEventListener("load", function () { trySyncUrlFromFrame(); });root.querySelectorAll("[data-mode]").forEach(function (button) { button.addEventListener("click", function () { setMode(button.getAttribute("data-mode")); }); });layer.addEventListener("pointerdown", function (event) { if (mode === "browse") return;event.preventDefault();if (!currentUrl) { showToast("Load a page first."); return; }ensureActiveView();const point = getLayerPoint(event);if (mode === "pin") { openCommentModal({ type: "pin", viewportX: point.xp, viewportY: point.yp, viewportXPx: point.x, viewportYPx: point.y, viewportW: 0, viewportH: 0 }); return; }if (mode === "circle") { isDrawing = true; startPoint = point; createPreviewCircle(point.xp, point.yp, 0, 0); layer.setPointerCapture(event.pointerId); } }, { passive: false });layer.addEventListener("pointermove", function (event) { if (!isDrawing || mode !== "circle" || !startPoint) return;event.preventDefault();const point = getLayerPoint(event);const x = Math.min(startPoint.xp, point.xp); const y = Math.min(startPoint.yp, point.yp); const w = Math.abs(point.xp - startPoint.xp); const h = Math.abs(point.yp - startPoint.yp);createPreviewCircle(x, y, w, h); }, { passive: false });layer.addEventListener("pointerup", function (event) { if (!isDrawing || mode !== "circle" || !startPoint) return;event.preventDefault();const point = getLayerPoint(event);const x = Math.min(startPoint.xp, point.xp); const y = Math.min(startPoint.yp, point.yp); const w = Math.abs(point.xp - startPoint.xp); const h = Math.abs(point.yp - startPoint.yp);const pxX = Math.min(startPoint.x, point.x); const pxY = Math.min(startPoint.y, point.y); const pxW = Math.abs(point.x - startPoint.x); const pxH = Math.abs(point.y - startPoint.y);isDrawing = false; startPoint = null;if (w < 2 || h < 2) { if (previewEl) previewEl.remove(); previewEl = null; showToast("Circle was too small. Drag a little bigger."); return; }openCommentModal({ type: "circle", viewportX: x, viewportY: y, viewportW: w, viewportH: h, viewportXPx: pxX, viewportYPx: pxY, viewportWPx: pxW, viewportHPx: pxH }); }, { passive: false });layer.addEventListener("pointercancel", function () { isDrawing = false; startPoint = null;if (previewEl) { previewEl.remove(); previewEl = null; } });root.querySelector("[data-save-comment]").addEventListener("click", saveComment); root.querySelector("[data-cancel-comment]").addEventListener("click", closeCommentModal);detailEditBtn.addEventListener("click", function () { if (!activeDetailMarkId) return; closeDetailModal(); openCommentModal(null, activeDetailMarkId); });detailDeleteBtn.addEventListener("click", function () { if (activeDetailMarkId) { deleteMark(activeDetailMarkId); } });detailDoneBtn.addEventListener("click", function () { const found = findMark(activeDetailMarkId); if (!found) return;found.mark.status = found.mark.status === "done" ? "open" : "done"; found.mark.updatedAt = new Date().toISOString(); saveSession(); renderMarkers(); renderComments(); openDetailModal(activeDetailMarkId); });root.querySelector("[data-detail-close]").addEventListener("click", closeDetailModal);commentModal.addEventListener("click", function (event) { if (event.target === commentModal) { closeCommentModal(); } });detailModal.addEventListener("click", function (event) { if (event.target === detailModal) { closeDetailModal(); } });root.querySelector("[data-export-live-side]").addEventListener("click", exportRecordedLiveView); root.querySelector("[data-email-developer]").addEventListener("click", prepareEmailToDeveloper); root.querySelector("[data-clear-project]").addEventListener("click", clearProject); root.querySelector("[data-record-toggle]").addEventListener("click", toggleRecording);setMode("browse"); })();