Files
veripath/tools/gbp-snapshot-form.html
T

526 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DOP — GBP Snapshot Intake v1.1</title>
<style>
:root {
--bg: #0f1419;
--panel: #1a2332;
--border: #2d3a4f;
--text: #e7ecf3;
--muted: #8b9bb4;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #22c55e;
--input-bg: #0d1219;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.45;
}
.wrap {
max-width: 860px;
margin: 0 auto;
padding: 24px 16px 48px;
}
h1 {
font-size: 1.35rem;
margin: 0 0 4px;
font-weight: 650;
}
.sub {
color: var(--muted);
font-size: 0.92rem;
margin-bottom: 20px;
}
.card {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 12px;
padding: 16px 16px 8px;
margin-bottom: 14px;
}
.card h2 {
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--muted);
margin: 0 0 12px;
font-weight: 600;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px 14px;
}
@media (max-width: 640px) {
.grid { grid-template-columns: 1fr; }
}
.full { grid-column: 1 / -1; }
label {
display: block;
font-size: 0.8rem;
color: var(--muted);
margin-bottom: 4px;
}
input[type="text"],
input[type="date"],
input[type="url"],
input[type="number"],
select,
textarea {
width: 100%;
background: var(--input-bg);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
padding: 9px 11px;
font-size: 0.95rem;
outline: none;
}
input:focus, select:focus, textarea:focus {
border-color: var(--accent);
}
textarea { min-height: 72px; resize: vertical; }
.actions {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin: 18px 0 10px;
}
button {
border: none;
border-radius: 8px;
padding: 11px 16px;
font-size: 0.95rem;
font-weight: 600;
cursor: pointer;
}
.btn-primary {
background: var(--accent);
color: white;
}
.btn-primary:hover { background: var(--accent-hover); }
.btn-secondary {
background: transparent;
color: var(--text);
border: 1px solid var(--border);
}
.btn-secondary:hover { border-color: var(--muted); }
.status {
font-size: 0.9rem;
color: var(--success);
min-height: 1.2em;
margin-bottom: 8px;
}
#output {
width: 100%;
min-height: 320px;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.82rem;
line-height: 1.4;
background: var(--input-bg);
border: 1px solid var(--border);
border-radius: 10px;
color: var(--text);
padding: 12px;
white-space: pre;
}
.hint {
font-size: 0.82rem;
color: var(--muted);
margin-top: 8px;
}
</style>
</head>
<body>
<div class="wrap">
<h1>DOP — GBP Snapshot Intake v1.1</h1>
<p class="sub">Improved cold-audit form. Fill once per client → Generate → paste to Leonard. Follows execution card rules only.</p>
<form id="gbpForm" autocomplete="off">
<div class="card">
<h2>Snapshot Metadata</h2>
<div class="grid">
<div>
<label for="client">Client name</label>
<input id="client" type="text" placeholder="Client name" />
</div>
<div>
<label for="snapshot_date">Snapshot date</label>
<input id="snapshot_date" type="date" />
</div>
<div>
<label for="source">Source</label>
<select id="source">
<option value="Human eye on Maps">Human eye on Maps</option>
<option value="Places API">Places API</option>
<option value="Owner dashboard">Owner dashboard</option>
</select>
</div>
<div>
<label for="place_id">Place ID (if known)</label>
<input id="place_id" type="text" placeholder="Optional" />
</div>
<div class="full">
<label for="maps_url">Maps or Place URL</label>
<input id="maps_url" type="url" placeholder="https://www.google.com/maps/place/..." />
</div>
</div>
</div>
<div class="card">
<h2>Core Fields</h2>
<div class="grid">
<div>
<label for="business_name">Business name shown</label>
<input id="business_name" type="text" />
</div>
<div>
<label for="address">Address shown</label>
<input id="address" type="text" />
</div>
<div>
<label for="phone">Phone shown</label>
<input id="phone" type="text" />
</div>
<div>
<label for="website">Website shown</label>
<input id="website" type="text" />
</div>
<div>
<label for="primary_category">Primary category</label>
<input id="primary_category" type="text" placeholder="Copy exactly as shown" />
</div>
<div>
<label for="additional_categories">Additional categories</label>
<input id="additional_categories" type="text" />
</div>
</div>
</div>
<div class="card">
<h2>Reputation</h2>
<div class="grid">
<div>
<label for="star_rating">Star rating</label>
<input id="star_rating" type="text" placeholder="e.g. 4.8" />
</div>
<div>
<label for="review_count">Review count</label>
<input id="review_count" type="text" placeholder="e.g. 42" />
</div>
<div>
<label for="review_responses">Review responses observed</label>
<select id="review_responses">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="review_responses_detail">Approx how many / how recent (optional)</label>
<input id="review_responses_detail" type="text" />
</div>
</div>
</div>
<div class="card">
<h2>Ownership / Claim Signals</h2>
<div class="grid">
<div>
<label for="claim_prompt">“Own this business?” prompt visible</label>
<select id="claim_prompt">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="claimed">Claimed status (your best judgment)</label>
<select id="claimed">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="verified">Verified badge or equivalent observed</label>
<select id="verified">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
</div>
</div>
<div class="card">
<h2>Operations</h2>
<div class="grid">
<div>
<label for="hours_listed">Hours listed</label>
<select id="hours_listed">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
</div>
<div>
<label for="open_now">Open now indicator observed</label>
<select id="open_now">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div class="full">
<label for="hours_text">Hours text (paste if short)</label>
<input id="hours_text" type="text" />
</div>
</div>
</div>
<div class="card">
<h2>Conversion</h2>
<div class="grid">
<div>
<label for="booking_button">Booking button present <strong>on the GBP</strong> (not the website)</label>
<select id="booking_button">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="booking_label">Booking button label (if any)</label>
<input id="booking_label" type="text" />
</div>
<div>
<label for="messaging_button">Messaging button present</label>
<select id="messaging_button">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div class="full">
<label for="other_buttons">Other action buttons observed</label>
<input id="other_buttons" type="text" />
</div>
</div>
</div>
<div class="card">
<h2>Content Surface</h2>
<div class="grid">
<div>
<label for="photos_present">Photos present</label>
<select id="photos_present">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
</div>
<div>
<label for="photo_count">Approx photo count</label>
<input id="photo_count" type="text" />
</div>
<div class="full">
<label for="services">Services listed</label>
<input id="services" type="text" placeholder="e.g. strength training only" />
</div>
<div>
<label for="posts_status">Updates / Posts status</label>
<select id="posts_status">
<option value="Not visible">Not visible</option>
<option value="Present but empty">Present but empty</option>
<option value="Present with content">Present with content</option>
</select>
</div>
<div>
<label for="recent_post">Most recent post date (approx, optional)</label>
<input id="recent_post" type="text" />
</div>
<div>
<label for="qa_status">Q&A status</label>
<select id="qa_status">
<option value="Not visible">Not visible</option>
<option value="Visible but empty">Visible but empty</option>
<option value="Visible + has answers">Visible + has answers</option>
</select>
</div>
<div class="full">
<label for="attributes">Attributes / highlights visible (optional)</label>
<input id="attributes" type="text" placeholder="e.g. women-owned, LGBTQ-friendly" />
</div>
</div>
</div>
<div class="card">
<h2>Related Listings</h2>
<div class="grid">
<div>
<label for="second_listing">Second related listing nearby</label>
<select id="second_listing">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="second_listing_names">Related listing name(s)</label>
<input id="second_listing_names" type="text" />
</div>
<div class="full">
<label for="second_listing_notes">Related listing notes (factual only)</label>
<textarea id="second_listing_notes"></textarea>
</div>
<div class="full">
<label for="raw_notes">Raw notes (optional, factual only)</label>
<textarea id="raw_notes"></textarea>
</div>
</div>
</div>
</form>
<div class="actions">
<button type="button" class="btn-primary" id="btnGenerate">Generate for Leonard</button>
<button type="button" class="btn-secondary" id="btnCopy">Copy to clipboard</button>
<button type="button" class="btn-secondary" id="btnDownload">Download .txt</button>
<button type="button" class="btn-secondary" id="btnClear">Clear form</button>
</div>
<div class="status" id="status"></div>
<label for="output">Leonard-ready output</label>
<textarea id="output" readonly placeholder="Click Generate for Leonard…"></textarea>
<p class="hint">Paste the output block directly to Leonard. He should follow the execution card and only fire threat rules R1R3.</p>
</div>
<script>
const $ = (id) => document.getElementById(id);
function val(id) {
const el = $(id);
const v = (el.value || "").trim();
return v === "" ? "Not observed" : v;
}
function todayISO() {
const d = new Date();
const m = String(d.getMonth() + 1).padStart(2, "0");
const day = String(d.getDate()).padStart(2, "0");
return `${d.getFullYear()}-${m}-${day}`;
}
$("snapshot_date").value = todayISO();
function buildOutput() {
return `Follow docs/agents/leonard-gbp-execution-card.md exactly.
Map this intake into the client Data Inventory GBP row.
Apply only threat trigger rules R1R3.
Do not invent fields. Do not add solutions. Do not reason beyond the execution card.
Return: updated GBP section, any threat changes, Changes Made list, Rules Fired list.
--- GBP SNAPSHOT INTAKE ---
CLIENT: ${val("client")}
SNAPSHOT_DATE: ${val("snapshot_date")}
SOURCE: ${val("source")}
MAPS_URL: ${val("maps_url")}
PLACE_ID: ${val("place_id")}
BUSINESS_NAME: ${val("business_name")}
ADDRESS: ${val("address")}
PHONE: ${val("phone")}
WEBSITE: ${val("website")}
PRIMARY_CATEGORY: ${val("primary_category")}
ADDITIONAL_CATEGORIES: ${val("additional_categories")}
STAR_RATING: ${val("star_rating")}
REVIEW_COUNT: ${val("review_count")}
REVIEW_RESPONSES_OBSERVED: ${val("review_responses")}
REVIEW_RESPONSES_DETAIL: ${val("review_responses_detail")}
CLAIM_PROMPT_VISIBLE: ${val("claim_prompt")}
CLAIMED_STATUS: ${val("claimed")}
VERIFIED_BADGE_VISIBLE: ${val("verified")}
HOURS_LISTED: ${val("hours_listed")}
HOURS_TEXT: ${val("hours_text")}
OPEN_NOW_OBSERVED: ${val("open_now")}
BOOKING_BUTTON_PRESENT_ON_GBP: ${val("booking_button")}
BOOKING_BUTTON_LABEL: ${val("booking_label")}
MESSAGING_BUTTON_PRESENT: ${val("messaging_button")}
OTHER_ACTION_BUTTONS: ${val("other_buttons")}
PHOTOS_PRESENT: ${val("photos_present")}
PHOTO_COUNT_APPROX: ${val("photo_count")}
SERVICES_LISTED: ${val("services")}
UPDATES_POSTS_STATUS: ${val("posts_status")}
MOST_RECENT_POST_DATE: ${val("recent_post")}
QA_STATUS: ${val("qa_status")}
ATTRIBUTES_HIGHLIGHTS: ${val("attributes")}
SECOND_LISTING_NEARBY: ${val("second_listing")}
SECOND_LISTING_NAMES: ${val("second_listing_names")}
SECOND_LISTING_NOTES: ${val("second_listing_notes")}
RAW_NOTES: ${val("raw_notes")}
`;
}
function setStatus(msg) {
$("status").textContent = msg;
if (msg) setTimeout(() => {
if ($("status").textContent === msg) $("status").textContent = "";
}, 2500);
}
$("btnGenerate").addEventListener("click", () => {
$("output").value = buildOutput();
setStatus("Generated.");
});
$("btnCopy").addEventListener("click", async () => {
const text = $("output").value || buildOutput();
$("output").value = text;
try {
await navigator.clipboard.writeText(text);
setStatus("Copied to clipboard.");
} catch (e) {
$("output").focus();
$("output").select();
setStatus("Select-all ready — press Ctrl/Cmd+C.");
}
});
$("btnDownload").addEventListener("click", () => {
const text = $("output").value || buildOutput();
$("output").value = text;
const client = ($("client").value || "client").trim().replace(/[^\w\-]+/g, "_") || "client";
const date = $("snapshot_date").value || todayISO();
const blob = new Blob([text], { type: "text/plain;charset=utf-8" });
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = `gbp-intake-${client}-${date}.txt`;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(a.href);
setStatus("Download started.");
});
$("btnClear").addEventListener("click", () => {
$("gbpForm").reset();
$("snapshot_date").value = todayISO();
$("output").value = "";
setStatus("Form cleared.");
});
</script>
</body>
</html>