new-site/site/public/portal/esign/index.html
justin f481a1d13c analytics: filter email-scanner / headless traffic out of Umami stats
Email security gateways (Microsoft Defender Safe Links / ATP, Proofpoint,
Mimecast, Barracuda, etc.) auto-fetch and often render every link in a
campaign email to scan for malware. The advanced ones drive a real headless
browser, execute JS, and fire Umami pageviews/clicks that masquerade as human
visits -- inflating campaign click-through.

New site/public/js/pw-bot-filter.js queries multiple real-browser signals and
gates Umami via its official data-before-send hook (umamiBeforeSend), dropping
all events when the visitor is a bot. Signals (from empirical chromium probing):
  decisive: navigator.webdriver, HeadlessChrome UA, known scanner UAs, zero/
            collapsed screen|viewport|outer geometry, window LARGER than the
            physical screen (impossible on real HW; uses outerW/H so page zoom
            does not false-positive), software GPU rasterizer (SwiftShader/
            llvmpipe/swrast via WebGL UNMASKED_RENDERER), zero logical CPUs.
  soft (>=2 to trip): tiny screen, inner>screen, low color depth, empty
            navigator.languages, no input device (no fine/coarse pointer + no
            hover + 0 touch), no WebGL on a desktop UA.
Designed to FAIL OPEN: only strong/corroborated evidence suppresses, so real
visitors (incl. zoomed, privacy-tooled, remote-desktop, kiosk) still count.

Wired before the Umami tag in Base.astro (Astro pages) and all 86 static
public/**/*.html pages; both load with defer so order is guaranteed and the
hook is defined before Umami reads it.

Tested end-to-end with chromium (site/tests/bot-filter.test.sh, 4/4):
default headless-new, spoofed-Windows-UA + normal 1366x768 window, and
spoofed-UA + 1x1 window are all caught; hook returns null to drop the event.
2026-06-18 02:02:34 -05:00

452 lines
39 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Review & Sign — Performance West Inc.</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<script>
window.__PW_API = (function() {
var h = window.location.hostname;
if (h === "localhost" || h === "127.0.0.1") return "http://" + h + ":3001";
if (h === "dev.performancewest.net") return "https://api.dev.performancewest.net";
return "https://api.performancewest.net";
})();
</script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',system-ui,sans-serif;color:#1f2937;background:#f1f5f9;line-height:1.6}
.wrap{max-width:720px;margin:0 auto;padding:2rem 1rem 4rem}
.header{background:#1e3a5f;color:#fff;border-radius:12px;padding:1.75rem 1.5rem;margin-bottom:1.5rem}
.header h1{margin:0 0 .3rem;font-size:1.35rem;font-weight:700}
.header p{margin:0;opacity:.8;font-size:.875rem}
.card{background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:1.5rem;margin-bottom:1.25rem;box-shadow:0 1px 4px rgba(0,0,0,.05)}
.card h2{font-size:1rem;font-weight:700;color:#1e3a5f;margin:0 0 .75rem}
.hint{font-size:.875rem;color:#475569;margin:0 0 .75rem}
.pdf-frame{width:100%;height:520px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc}
#sig-canvas{width:100%;height:160px;border:2px solid #cbd5e1;border-radius:8px;cursor:crosshair;touch-action:none;background:#fafafa;display:block}
#sig-canvas.has-sig{border-color:#1e3a5f}
.sig-actions{display:flex;align-items:center;justify-content:space-between;margin-top:.5rem;font-size:.8rem;color:#64748b}
.sig-clear{background:none;border:1px solid #e2e8f0;color:#475569;padding:.3rem .75rem;border-radius:6px;cursor:pointer;font-size:.8rem}
.sig-clear:hover{background:#f1f5f9}
/* Typed signature */
.sig-tabs{display:flex;gap:.5rem;margin-bottom:.75rem}
.sig-tab{flex:1;padding:.5rem;border:2px solid #e2e8f0;border-radius:8px;background:#fff;cursor:pointer;font-weight:600;font-size:.85rem;text-align:center;color:#475569;font-family:inherit}
.sig-tab.active{border-color:#1e3a5f;color:#1e3a5f;background:#eff6ff}
#typed-sig{width:100%;padding:.75rem;border:2px solid #cbd5e1;border-radius:8px;font-size:1.5rem;font-family:'Brush Script MT','Segoe Script','Comic Sans MS',cursive;text-align:center;color:#1e3a5f}
#typed-sig:focus{outline:none;border-color:#1e3a5f}
.typed-preview{text-align:center;font-family:'Brush Script MT','Segoe Script','Comic Sans MS',cursive;font-size:2rem;color:#1e3a5f;padding:1rem;min-height:80px;border:1px dashed #cbd5e1;border-radius:8px;margin-top:.5rem}
.confirm-row{display:flex;gap:.75rem;align-items:flex-start;padding:1rem;background:#f8fafc;border:1px solid #e2e8f0;border-radius:8px;font-size:.85rem;color:#374151;margin-bottom:.75rem}
.confirm-row input[type=checkbox]{margin-top:2px;flex-shrink:0;width:16px;height:16px;accent-color:#1e3a5f}
.perjury{font-size:.8rem;color:#6b7280;font-style:italic;padding:.75rem;background:#fefce8;border:1px solid #fde68a;border-radius:8px;margin-bottom:.75rem}
.submit-btn{width:100%;background:#1e3a5f;color:#fff;border:none;border-radius:10px;padding:.9rem;font-size:1rem;font-weight:700;cursor:pointer;font-family:inherit}
.submit-btn:hover:not(:disabled){background:#162e4d}
.submit-btn:disabled{opacity:.5;cursor:not-allowed}
.status{font-size:.875rem;margin-top:.75rem;min-height:1.25rem;text-align:center}
.err{color:#dc2626}.ok{color:#16a34a}
#success{display:none;text-align:center;padding:3rem 1rem}
#success .check{width:56px;height:56px;background:#dcfce7;border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 1rem}
#success h2{color:#1e3a5f;margin:0 0 .5rem}
#success p{color:#475569;font-size:.9rem;max-width:400px;margin:0 auto}
#loading{text-align:center;padding:4rem 1rem;color:#64748b}
#error-screen{display:none;text-align:center;padding:3rem 1rem}
.hidden{display:none}
/* Signature block locked until signing authorization is given */
#sig-block.locked{opacity:.45;pointer-events:none;filter:grayscale(.3)}
</style>
<script defer src="/js/pw-bot-filter.js"></script><script defer src="https://analytics.performancewest.net/script.js" data-website-id="55250014-ee15-44ac-a1f6-81dabad3fe0f" data-before-send="umamiBeforeSend"></script><script defer src="/js/pw-analytics.js"></script></head>
<body>
<div class="wrap">
<div id="loading"><p>Loading your document...</p><p style="font-size:.8rem;color:#94a3b8">Verifying your link</p></div>
<div id="main-ui" class="hidden">
<div class="header">
<h1 id="hdr-title">Review & Sign</h1>
<p id="hdr-subtitle"></p>
</div>
<!-- Step 1: Review -->
<div class="card">
<h2>Step 1 — Review Your Document</h2>
<p class="hint" id="review-hint">Please read the full document before signing.</p>
<div id="pdf-container">
<div style="display:flex;align-items:center;justify-content:center;height:200px;color:#94a3b8;font-size:.875rem;border:1px dashed #cbd5e1;border-radius:8px">Loading document preview...</div>
</div>
</div>
<!-- Step 2: Sign -->
<div class="card">
<h2>Step 2 — Your Signature</h2>
<!-- Signing-authorization gate (shown only when the document requires it).
The signer must authorize use of their signature to complete the filing
BEFORE they can draw it. -->
<div id="sign-consent-box" class="hidden" style="padding:1rem;background:#eff6ff;border:1px solid #bfdbfe;border-radius:8px;margin-bottom:1rem">
<p style="font-size:.85rem;color:#1f2937;margin:0 0 .75rem" id="sign-consent-text"></p>
<label style="display:flex;gap:.6rem;align-items:flex-start;font-size:.85rem;color:#1f2937;cursor:pointer">
<input type="checkbox" id="sign-consent-chk" style="margin-top:2px;flex-shrink:0;width:16px;height:16px;accent-color:#1e3a5f">
<span>I authorize this and confirm the signature I draw is my own.</span>
</label>
</div>
<div id="sig-block">
<div class="sig-tabs">
<button type="button" class="sig-tab active" data-mode="draw">Draw</button>
<button type="button" class="sig-tab" data-mode="type">Type</button>
</div>
<div id="draw-mode">
<p class="hint">Sign below using your mouse, trackpad, or finger.</p>
<canvas id="sig-canvas"></canvas>
<div class="sig-actions">
<span id="sig-hint" style="font-style:italic">Draw your signature above</span>
<button type="button" class="sig-clear" id="sig-clear">Clear</button>
</div>
</div>
<div id="type-mode" class="hidden">
<p class="hint">Type your full legal name below.</p>
<input type="text" id="typed-sig" placeholder="Your full name" autocomplete="name">
<div class="typed-preview" id="typed-preview"></div>
</div>
</div>
</div>
<!-- Step 3: Confirm -->
<div class="card">
<h2>Step 3 — Confirm & Submit</h2>
<div id="attestation-box" class="perjury hidden" style="font-style:normal;color:#1f2937;background:#eff6ff;border-color:#bfdbfe"></div>
<div id="perjury-box" class="perjury hidden">
I declare under penalty of perjury under the laws of the United States of America that the foregoing is true and correct. Executed on <span id="perjury-date"></span>.
</div>
<div class="confirm-row">
<input type="checkbox" id="agree-chk">
<label for="agree-chk" id="agree-label">
I confirm that I have reviewed the document above and that my signature constitutes
my legal electronic signature. I authorize Performance West Inc. to submit this
document on behalf of <strong id="entity-confirm"></strong>.
</label>
</div>
<button class="submit-btn" id="submit-btn" disabled>Submit Signed Document</button>
<p class="status" id="status-msg"></p>
</div>
</div>
<div id="success" class="hidden">
<div class="check">
<svg width="28" height="28" fill="none" stroke="#16a34a" stroke-width="3" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4.5 12.75l6 6 9-13.5"/></svg>
</div>
<h2>Document Signed Successfully</h2>
<p id="success-msg">Your signed document has been submitted. You'll receive a confirmation email shortly.</p>
</div>
<div id="error-screen" class="hidden">
<div class="card" style="border-color:#fca5a5;background:#fef2f2">
<h2 style="color:#991b1b">Error</h2>
<p style="color:#7f1d1d" id="error-msg"></p>
</div>
</div>
</div>
<script>
(function() {
var API = window.__PW_API;
var params = new URLSearchParams(window.location.search);
var token = params.get("token");
if (!token) {
document.getElementById("loading").style.display = "none";
document.getElementById("error-screen").style.display = "block";
document.getElementById("error-msg").textContent = "No signing token provided. Please use the link from your email.";
return;
}
// Load document info
fetch(API + "/api/v1/portal/esign?token=" + encodeURIComponent(token))
.then(function(r) { return r.json(); })
.then(function(data) {
document.getElementById("loading").style.display = "none";
if (data.error) {
document.getElementById("error-screen").style.display = "block";
document.getElementById("error-msg").textContent = data.error;
return;
}
if (data.already_signed) {
document.getElementById("success").style.display = "block";
var signedDate = data.signed_at ? new Date(data.signed_at).toLocaleDateString("en-US", {year:"numeric",month:"long",day:"numeric",hour:"numeric",minute:"2-digit"}) : "";
document.getElementById("success-msg").textContent = "This document was already signed" + (signedDate ? " on " + signedDate : "") + ". No further action needed.";
return;
}
// Populate UI
document.getElementById("main-ui").classList.remove("hidden");
document.getElementById("hdr-title").textContent = data.document_title || "Review & Sign";
document.getElementById("hdr-subtitle").textContent = data.entity_name + (data.order_number ? " — " + data.order_number : "");
document.getElementById("entity-confirm").textContent = data.entity_name;
document.getElementById("perjury-date").textContent = new Date().toLocaleDateString("en-US", {year:"numeric",month:"long",day:"numeric"});
// Show the specific certification/attestation language for this document
// (e.g. the dissolution "no outstanding lawsuits, liens, or judgments"
// attestation, or the OP-1 / MCS-150 certification).
var attestText = data.metadata && data.metadata.perjury_text;
if (attestText) {
var ab = document.getElementById("attestation-box");
ab.textContent = attestText;
ab.classList.remove("hidden");
}
if (data.requires_perjury) {
document.getElementById("perjury-box").classList.remove("hidden");
}
// Signing-authorization gate: for documents that require it, show an
// explicit per-document authorization that must be accepted BEFORE the
// signer can draw. Lock the signature block until the box is checked.
if (data.require_sign_consent && data.sign_consent_text) {
document.getElementById("sign-consent-text").textContent = data.sign_consent_text;
document.getElementById("sign-consent-box").classList.remove("hidden");
document.getElementById("sig-block").classList.add("locked");
}
// PDF preview
if (data.document_url) {
document.getElementById("pdf-container").innerHTML =
'<iframe src="' + data.document_url + '" class="pdf-frame" title="Document preview"></iframe>';
} else {
document.getElementById("pdf-container").innerHTML =
'<div style="display:flex;align-items:center;justify-content:center;height:120px;color:#64748b;font-size:.875rem;border:1px dashed #cbd5e1;border-radius:8px;background:#f8fafc">' +
'Document preview not available. Please contact us if you need a copy before signing.</div>';
}
// Store for submit
window._esignData = data;
})
.catch(function() {
document.getElementById("loading").style.display = "none";
document.getElementById("error-screen").style.display = "block";
document.getElementById("error-msg").textContent = "Could not load document. The link may have expired.";
});
// ── Signature canvas ──
var canvas = document.getElementById("sig-canvas");
var ctx = canvas.getContext("2d");
var drawing = false;
var hasSig = false;
// Vector capture: stroke paths normalized to the capture box (0..1, origin
// top-left), resolution-independent.
var sigStrokes = []; // array of strokes; each stroke = array of {x,y,t}
var curStroke = null;
var strokeStart = 0;
var captureW = 0, captureH = 0;
function resizeCanvas() {
var rect = canvas.getBoundingClientRect();
captureW = rect.width;
captureH = rect.height;
canvas.width = rect.width * 2;
canvas.height = rect.height * 2;
ctx.scale(2, 2);
ctx.strokeStyle = "#1e3a5f";
ctx.lineWidth = 2;
ctx.lineCap = "round";
ctx.lineJoin = "round";
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);
function getPos(e) {
var rect = canvas.getBoundingClientRect();
var t = e.touches ? e.touches[0] : e;
return { x: t.clientX - rect.left, y: t.clientY - rect.top };
}
function beginStroke(p) {
drawing = true;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
curStroke = [];
strokeStart = Date.now();
pushPoint(p);
}
function pushPoint(p) {
if (!curStroke) return;
var w = captureW || 1, h = captureH || 1;
curStroke.push({
x: Math.max(0, Math.min(1, p.x / w)),
y: Math.max(0, Math.min(1, p.y / h)),
t: Date.now() - strokeStart,
});
}
function endStroke() {
drawing = false;
if (curStroke && curStroke.length) sigStrokes.push(curStroke);
curStroke = null;
}
function markSigged() {
hasSig = true;
canvas.classList.add("has-sig");
document.getElementById("sig-hint").textContent = "Signature captured";
updateSubmit();
}
canvas.addEventListener("mousedown", function(e) { beginStroke(getPos(e)); });
canvas.addEventListener("mousemove", function(e) { if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); pushPoint(p); markSigged(); });
canvas.addEventListener("mouseup", endStroke);
canvas.addEventListener("mouseleave", endStroke);
canvas.addEventListener("touchstart", function(e) { e.preventDefault(); beginStroke(getPos(e)); }, {passive:false});
canvas.addEventListener("touchmove", function(e) { e.preventDefault(); if (!drawing) return; var p = getPos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); pushPoint(p); markSigged(); }, {passive:false});
canvas.addEventListener("touchend", endStroke);
document.getElementById("sig-clear").addEventListener("click", function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
hasSig = false;
sigStrokes = [];
curStroke = null;
canvas.classList.remove("has-sig");
document.getElementById("sig-hint").textContent = "Draw your signature above";
updateSubmit();
});
// ── Typed signature ──
var typedInput = document.getElementById("typed-sig");
var typedPreview = document.getElementById("typed-preview");
typedInput.addEventListener("input", function() {
typedPreview.textContent = this.value;
updateSubmit();
});
// ── Tab switching ──
var sigMode = "draw";
document.querySelectorAll(".sig-tab").forEach(function(tab) {
tab.addEventListener("click", function() {
document.querySelectorAll(".sig-tab").forEach(function(t) { t.classList.remove("active"); });
this.classList.add("active");
sigMode = this.dataset.mode;
document.getElementById("draw-mode").classList.toggle("hidden", sigMode !== "draw");
document.getElementById("type-mode").classList.toggle("hidden", sigMode !== "type");
updateSubmit();
});
});
// ── Submit logic ──
var agreeChk = document.getElementById("agree-chk");
agreeChk.addEventListener("change", updateSubmit);
// ── Signing authorization: unlock the signature block when authorized ──
var signConsentChk = document.getElementById("sign-consent-chk");
signConsentChk.addEventListener("change", function() {
var data = window._esignData || {};
var needsConsent = !!(data.require_sign_consent && data.sign_consent_text);
if (needsConsent) {
document.getElementById("sig-block").classList.toggle("locked", !this.checked);
// If they un-check after drawing, clear the now-unauthorized capture.
if (!this.checked) document.getElementById("sig-clear").click();
}
updateSubmit();
});
function signConsentOk() {
var data = window._esignData || {};
var needsConsent = !!(data.require_sign_consent && data.sign_consent_text);
// Authorization is only required for a DRAWN signature on a consent-required doc.
if (!needsConsent || sigMode !== "draw") return true;
return signConsentChk.checked;
}
function updateSubmit() {
var hasSignature = sigMode === "draw" ? hasSig : typedInput.value.trim().length >= 2;
document.getElementById("submit-btn").disabled =
!(hasSignature && agreeChk.checked && signConsentOk());
}
document.getElementById("submit-btn").addEventListener("click", async function() {
var btn = this;
var statusEl = document.getElementById("status-msg");
btn.disabled = true;
btn.textContent = "Submitting...";
statusEl.textContent = "";
var signatureData;
if (sigMode === "draw") {
signatureData = { type: "drawn", image_b64: canvas.toDataURL("image/png") };
// Attach the vector strokes (resolution-independent) so the same signing
// event resumes the filing pipeline.
if (sigStrokes.length) {
signatureData.vector = { v: 1, w: captureW, h: captureH, strokes: sigStrokes };
}
} else {
signatureData = { type: "typed", name: typedInput.value.trim() };
}
try {
var resp = await fetch(API + "/api/v1/portal/esign", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + token,
},
body: JSON.stringify({
signature: signatureData,
agreed_at: new Date().toISOString(),
user_agent: navigator.userAgent,
sign_consent: signConsentOk() && sigMode === "draw" && signConsentChk.checked,
}),
});
var result = await resp.json();
if (!resp.ok) throw new Error(result.error || "Submission failed");
document.getElementById("main-ui").classList.add("hidden");
document.getElementById("success").style.display = "block";
} catch (err) {
statusEl.textContent = err.message;
statusEl.className = "status err";
btn.disabled = false;
btn.textContent = "Submit Signed Document";
}
});
})();
</script>
<script defer src="/js/pw-cookie-consent.js"></script><!-- Floating help button --><button type="button" id="support-fab" aria-label="Open support" class="fixed bottom-6 left-6 z-[9999] w-14 h-14 rounded-full bg-pw-700 text-white shadow-lg hover:bg-pw-800 transition-all hover:scale-105 flex items-center justify-center"> <svg id="support-fab-icon-open" class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> </svg> <svg id="support-fab-icon-close" class="w-6 h-6 hidden" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Slide-out panel --> <div id="support-panel" class="fixed bottom-24 left-6 z-[9998] w-[380px] max-w-[calc(100vw-2rem)] bg-white rounded-xl shadow-2xl border border-gray-200 transform translate-y-4 opacity-0 pointer-events-none transition-all duration-200 ease-out"> <div class="px-5 py-4 border-b border-gray-100 bg-gray-50 rounded-t-xl"> <h3 class="text-base font-semibold text-gray-900">How can we help?</h3> <p class="text-xs text-gray-500 mt-0.5">Choose a category and tell us what you need.</p> </div> <!-- Category selector (step 1) --> <div id="support-step-categories" class="p-4 space-y-2"> <button type="button" data-category="question" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-blue-100 text-blue-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Ask a Question</span> <span class="block text-xs text-gray-500">About our compliance services or process</span> </div> </div> </button> <button type="button" data-category="support" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-green-100 text-green-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M18.364 5.636l-3.536 3.536m0 5.656l3.536 3.536M9.172 9.172L5.636 5.636m3.536 9.192l-3.536 3.536M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-5 0a4 4 0 11-8 0 4 4 0 018 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Get Support</span> <span class="block text-xs text-gray-500">Help with an ongoing engagement</span> </div> </div> </button> <button type="button" data-category="issue" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-red-100 text-red-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Report an Issue</span> <span class="block text-xs text-gray-500">Something isn't right with a deliverable</span> </div> </div> </button> <button type="button" data-category="service_request" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-purple-100 text-purple-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Service</span> <span class="block text-xs text-gray-500">Start a new compliance engagement</span> </div> </div> </button> <button type="button" data-category="quote" class="support-cat-btn w-full text-left px-4 py-3 rounded-lg border border-gray-200 hover:border-pw-300 hover:bg-pw-50 transition-colors group"> <div class="flex items-center gap-3"> <span class="flex-shrink-0 w-8 h-8 rounded-full bg-amber-100 text-amber-600 flex items-center justify-center"> <svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg> </span> <div> <span class="text-sm font-medium text-gray-900 group-hover:text-pw-700">Request a Quote</span> <span class="block text-xs text-gray-500">Get pricing for a complex engagement</span> </div> </div> </button> <!-- Sign-in link (hidden when logged in) --> <div id="support-signin-link" class="pt-2 border-t border-gray-100 mt-2"> <button type="button" id="support-signin-btn" class="w-full flex items-center justify-center gap-2 px-4 py-2 text-xs text-gray-500 hover:text-pw-700 transition-colors"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg>
Already a client? <span class="font-medium text-pw-600">Sign in</span> </button> </div> </div> <!-- Ticket form — Report an Issue + guest fallback (step 2a) --> <form id="support-step-form" class="hidden p-4"> <button type="button" id="support-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div id="support-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="support-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-gray-400">(optional)</span></label> <input type="text" id="support-name" name="name" placeholder="Your name" maxlength="100" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-gray-400">(for follow-up)</span></label> <input type="email" id="support-email" name="email" placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-subject" class="block text-xs font-medium text-gray-700 mb-1">Subject <span class="text-red-400">*</span></label> <input type="text" id="support-subject" name="subject" required minlength="3" maxlength="200" placeholder="Brief summary" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="support-message" class="block text-xs font-medium text-gray-700 mb-1">Message <span class="text-red-400">*</span></label> <textarea id="support-message" name="message" required minlength="10" maxlength="5000" rows="4" placeholder="Describe your question, issue, or request..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> <p class="text-[10px] text-gray-400 mt-0.5 text-right"><span id="support-char-count">0</span> / 5000</p> </div> <button type="submit" id="support-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Submit
</button> </div> </form> <!-- Opportunity form — Request a Service / Request a Quote (step 2b) --> <form id="support-step-opportunity" class="hidden p-4"> <button type="button" id="opportunity-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-3"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div id="opportunity-category-badge" class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-medium bg-pw-100 text-pw-700 mb-3"></div> <div class="space-y-3"> <div> <label for="opp-name" class="block text-xs font-medium text-gray-700 mb-1">Name <span class="text-red-400">*</span></label> <input type="text" id="opp-name" name="name" required minlength="2" maxlength="100" placeholder="Your name" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-email" class="block text-xs font-medium text-gray-700 mb-1">Email <span class="text-red-400">*</span></label> <input type="email" id="opp-email" name="email" required placeholder="you@company.com" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-company" class="block text-xs font-medium text-gray-700 mb-1">Company <span class="text-gray-400">(optional)</span></label> <input type="text" id="opp-company" name="company" placeholder="Company name" maxlength="200" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-phone" class="block text-xs font-medium text-gray-700 mb-1">Phone <span class="text-gray-400">(optional)</span></label> <input type="tel" id="opp-phone" name="phone" placeholder="(555) 555-1234" maxlength="30" class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow"> </div> <div> <label for="opp-service" class="block text-xs font-medium text-gray-700 mb-1">Service interest <span class="text-red-400">*</span></label> <select id="opp-service" name="service_slug" required class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow bg-white"> <option value="" disabled selected>Select a service...</option> <option value="canada-crtc">Canada CRTC Carrier Package</option> <option value="llc-formation">LLC Formation</option> <option value="corporation-formation">Corporation Formation</option> <option value="registered-agent">Registered Agent</option> <option value="compliance-audit">Compliance Audit</option> <option value="other">Other / Not sure</option> </select> </div> <div> <label for="opp-details" class="block text-xs font-medium text-gray-700 mb-1">Details <span class="text-gray-400">(optional)</span></label> <textarea id="opp-details" name="details" maxlength="5000" rows="3" placeholder="Tell us about your project or what you need..." class="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:ring-2 focus:ring-pw-500 focus:border-pw-500 outline-none transition-shadow resize-y"></textarea> </div> <button type="submit" id="opp-submit-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Submit Request
</button> </div> </form> <!-- Sign-in prompt — Ask a Question / Get Support when not logged in (step 2c) --> <div id="support-step-signin" class="hidden p-6"> <button type="button" id="signin-back-btn" class="inline-flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"></path></svg>
Back
</button> <div class="text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-blue-100 flex items-center justify-center"> <svg class="w-6 h-6 text-blue-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Sign in for support</h4> <p class="text-xs text-gray-500 mb-5">Sign in to access your client portal where you can view your orders, submit support requests, and manage your services.</p> <button type="button" id="signin-auth-btn" class="w-full py-2.5 px-4 bg-pw-700 text-white text-sm font-medium rounded-lg hover:bg-pw-800 transition-colors mb-3">
Sign In
</button> <button type="button" id="signin-guest-btn" class="text-xs text-gray-500 hover:text-gray-700 transition-colors">
Continue as guest &rarr;
</button> </div> </div> <!-- Success state (step 3) --> <div id="support-step-success" class="hidden p-6 text-center"> <div class="w-12 h-12 mx-auto mb-3 rounded-full bg-green-100 flex items-center justify-center"> <svg class="w-6 h-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"></path></svg> </div> <h4 class="text-sm font-semibold text-gray-900 mb-1">Request received</h4> <p id="support-success-message" class="text-xs text-gray-500 mb-4">We'll get back to you within one business day.</p> <p id="support-ticket-id" class="text-xs text-gray-400 mb-4 hidden">Ref: <span></span></p> <button type="button" id="support-new-ticket-btn" class="text-xs text-pw-600 hover:text-pw-700 font-medium">Submit another request</button> </div> </div> <!-- Modal backdrop --><div id="auth-modal-backdrop" class="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm hidden items-center justify-center p-4" role="dialog" aria-modal="true" aria-labelledby="auth-modal-title"> <div id="auth-modal" class="relative w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden"> <!-- Close --> <button id="auth-modal-close" type="button" aria-label="Close" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600 transition-colors z-10"> <svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"></path> </svg> </button> <!-- Tab bar --> <div id="auth-tabs" class="flex border-b border-gray-100"> <button type="button" id="auth-tab-login" class="auth-tab flex-1 py-4 text-sm font-semibold text-pw-700 border-b-2 border-pw-600 transition-colors">
Sign In
</button> <button type="button" id="auth-tab-register" class="auth-tab flex-1 py-4 text-sm font-semibold text-gray-400 border-b-2 border-transparent hover:text-gray-600 transition-colors">
Create Account
</button> </div> <div class="px-6 py-6"> <!-- ── Login form ──────────────────────────────────────────────────── --> <form id="auth-login-form" class="space-y-4" novalidate> <div> <label for="auth-login-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-login-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <div class="flex items-center justify-between mb-1"> <label for="auth-login-password" class="text-sm font-medium text-gray-700">Password</label> <button type="button" id="auth-forgot-link" class="text-xs text-pw-600 hover:text-pw-800 underline underline-offset-2">
Forgot password?
</button> </div> <input type="password" id="auth-login-password" autocomplete="current-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="••••••••"> </div> <p id="auth-login-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-login-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Sign In
</button> </form> <!-- ── Register form ──────────────────────────────────────────────── --> <form id="auth-register-form" class="space-y-4 hidden" novalidate> <div> <label for="auth-reg-name" class="block text-sm font-medium text-gray-700 mb-1">Your Name</label> <input type="text" id="auth-reg-name" autocomplete="name" class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="Full name"> </div> <div> <label for="auth-reg-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-reg-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <div> <label for="auth-reg-password" class="block text-sm font-medium text-gray-700 mb-1">Password</label> <input type="password" id="auth-reg-password" autocomplete="new-password" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="At least 8 characters"> </div> <p id="auth-reg-err" class="hidden text-xs text-red-600 font-medium"></p> <button type="submit" id="auth-reg-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Create Account
</button> </form> <!-- ── Forgot password form ────────────────────────────────────────── --> <div id="auth-forgot-form" class="hidden"> <button type="button" id="auth-back-to-login" class="flex items-center gap-1 text-xs text-gray-500 hover:text-gray-700 mb-4"> <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"></path></svg>
Back to sign in
</button> <h3 class="text-base font-semibold text-gray-900 mb-1">Reset your password</h3> <p class="text-sm text-gray-500 mb-4">Enter your email and we'll send you a reset link.</p> <form id="auth-forgot-email-form" class="space-y-4" novalidate> <div> <label for="auth-forgot-email" class="block text-sm font-medium text-gray-700 mb-1">Email</label> <input type="email" id="auth-forgot-email" autocomplete="email" required class="w-full rounded-lg border border-gray-300 px-3 py-2.5 text-sm focus:outline-none focus:ring-2 focus:ring-pw-500 focus:border-pw-500 transition-colors" placeholder="you@company.com"> </div> <p id="auth-forgot-err" class="hidden text-xs text-red-600 font-medium"></p> <p id="auth-forgot-ok" class="hidden text-xs text-green-700 font-medium"></p> <button type="submit" id="auth-forgot-btn" class="w-full py-2.5 rounded-lg bg-pw-700 text-white text-sm font-semibold hover:bg-pw-800 transition-colors disabled:opacity-50">
Send reset link
</button> </form> </div> </div> </div> </div>
<script type="module" src="/_astro/hoisted.yFz1BYXO.js"></script>
</body>
</html>