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

488 lines
15 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</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</h1>
<p class="sub">Fill once per cold audit. Generate text → copy/paste to Leonard. Uses 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="Overcome Fitness" />
</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" />
</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 response behavior observed</label>
<select id="review_responses">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
</div>
</div>
<div class="card">
<h2>Ownership / Claim Signals</h2>
<div class="grid">
<div>
<label for="claimed">Claimed or “Own this business” visible</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 / appointment button present</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 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>
<label for="posts_visible">Posts visible</label>
<select id="posts_visible">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
<div>
<label for="qa_visible">Q&A visible</label>
<select id="qa_visible">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</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 GMB row.
Apply only threat trigger rules R1R3.
Do not invent fields. Do not add solutions. Do not reason beyond the execution card.
Return: updated GMB 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")}
CLAIMED_VISIBLE: ${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: ${val("booking_button")}
BOOKING_BUTTON_LABEL: ${val("booking_label")}
OTHER_ACTION_BUTTONS: ${val("other_buttons")}
PHOTOS_PRESENT: ${val("photos_present")}
PHOTO_COUNT_APPROX: ${val("photo_count")}
POSTS_VISIBLE: ${val("posts_visible")}
QA_VISIBLE: ${val("qa_visible")}
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>