488 lines
15 KiB
HTML
488 lines
15 KiB
HTML
<!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 R1–R3.</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 R1–R3.
|
||
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>
|