gbp-snapshot-form v1.2 — surface, datetime, NOT_PRESENT/NOT_OBSERVED, category split, posts count, claimed inference options

This commit is contained in:
Ty
2026-08-03 19:09:36 +00:00
parent a8afdac7dd
commit aa0ab33289
+162 -432
View File
@@ -3,260 +3,105 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>DOP — GBP Snapshot Intake v1.1</title> <title>DOP — GBP Snapshot Intake v1.2</title>
<style> <style>
:root { :root { --bg:#0f1419; --panel:#1a2332; --border:#2d3a4f; --text:#e7ecf3; --muted:#8b9bb4; --accent:#3b82f6; --input:#0d1219; --ok:#22c55e; }
--bg: #0f1419;
--panel: #1a2332;
--border: #2d3a4f;
--text: #e7ecf3;
--muted: #8b9bb4;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #22c55e;
--input-bg: #0d1219;
}
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body { margin:0; font-family: system-ui, sans-serif; background:var(--bg); color:var(--text); line-height:1.45; }
margin: 0; .wrap { max-width:900px; margin:0 auto; padding:24px 16px 48px; }
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif; h1 { font-size:1.3rem; margin:0 0 4px; }
background: var(--bg); .sub { color:var(--muted); font-size:0.9rem; margin-bottom:18px; }
color: var(--text); .card { background:var(--panel); border:1px solid var(--border); border-radius:12px; padding:16px 16px 8px; margin-bottom:14px; }
line-height: 1.45; .card h2 { font-size:0.8rem; text-transform:uppercase; letter-spacing:0.04em; color:var(--muted); margin:0 0 12px; }
} .grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; }
.wrap { @media (max-width:640px){ .grid { grid-template-columns:1fr; } }
max-width: 860px; .full { grid-column:1 / -1; }
margin: 0 auto; label { display:block; font-size:0.8rem; color:var(--muted); margin-bottom:4px; }
padding: 24px 16px 48px; input, select, textarea { width:100%; background:var(--input); 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); }
h1 { textarea { min-height:64px; resize:vertical; }
font-size: 1.35rem; .actions { display:flex; flex-wrap:wrap; gap:10px; margin:18px 0 10px; }
margin: 0 0 4px; button { border:none; border-radius:8px; padding:11px 16px; font-size:0.95rem; font-weight:600; cursor:pointer; }
font-weight: 650; .btn-primary { background:var(--accent); color:#fff; }
} .btn-secondary { background:transparent; color:var(--text); border:1px solid var(--border); }
.sub { .status { font-size:0.9rem; color:var(--ok); min-height:1.2rem; }
color: var(--muted); #output { width:100%; min-height:280px; font-family:ui-monospace,monospace; font-size:0.82rem; background:var(--input); border:1px solid var(--border); border-radius:10px; color:var(--text); padding:12px; white-space:pre; }
font-size: 0.92rem; .hint { font-size:0.82rem; color:var(--muted); margin-top:8px; }
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> </style>
</head> </head>
<body> <body>
<div class="wrap"> <div class="wrap">
<h1>DOP — GBP Snapshot Intake v1.1</h1> <h1>DOP — GBP Snapshot Intake v1.2</h1>
<p class="sub">Improved cold-audit form. Fill once per client → Generate → paste to Leonard. Follows execution card rules only.</p> <p class="sub">NOT_PRESENT = looked, confirmed absent · NOT_OBSERVED = did not look / could not see. One primary category only. Paste output to Leonard (execution card v1.2).</p>
<form id="gbpForm" autocomplete="off"> <form id="gbpForm" autocomplete="off">
<div class="card"> <div class="card">
<h2>Snapshot Metadata</h2> <h2>Snapshot Metadata</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="client">Client name</label><input id="client" type="text" /></div>
<label for="client">Client name</label> <div><label for="snapshot_dt">Snapshot datetime + TZ (ISO)</label><input id="snapshot_dt" type="text" placeholder="2026-08-03T14:30:00-07:00" /></div>
<input id="client" type="text" placeholder="Client name" /> <div><label for="source">Source</label>
</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"> <select id="source">
<option value="Human eye on Maps">Human eye on Maps</option> <option>Human eye on Maps</option>
<option value="Places API">Places API</option> <option>Places API</option>
<option value="Owner dashboard">Owner dashboard</option> <option>Owner dashboard</option>
</select> </select>
</div> </div>
<div> <div><label for="surface">Observation surface</label>
<label for="place_id">Place ID (if known)</label> <select id="surface">
<input id="place_id" type="text" placeholder="Optional" /> <option value="desktop_maps">desktop_maps</option>
</div> <option value="mobile_maps">mobile_maps</option>
<div class="full"> <option value="knowledge_panel">knowledge_panel</option>
<label for="maps_url">Maps or Place URL</label> <option value="other">other</option>
<input id="maps_url" type="url" placeholder="https://www.google.com/maps/place/..." /> </select>
</div> </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><label for="place_id">Place ID (blank = agent computes from URL)</label><input id="place_id" type="text" /></div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Core Fields</h2> <h2>Core</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="business_name">Business name shown</label><input id="business_name" type="text" /></div>
<label for="business_name">Business name shown</label> <div><label for="address">Address shown (exact)</label><input id="address" type="text" /></div>
<input id="business_name" type="text" /> <div><label for="address_canonical">Address canonical (optional)</label><input id="address_canonical" type="text" placeholder="Suite not #" /></div>
</div> <div><label for="phone">Phone (full digits, no mask)</label><input id="phone" type="text" placeholder="+1 530-929-7120" /></div>
<div> <div><label for="website">Website shown</label><input id="website" type="text" /></div>
<label for="address">Address shown</label> <div><label for="primary_category">Primary category (ONE only)</label><input id="primary_category" type="text" placeholder="e.g. Gym" /></div>
<input id="address" type="text" /> <div class="full"><label for="additional_categories">Additional categories (list)</label><input id="additional_categories" type="text" /></div>
</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> </div>
<div class="card"> <div class="card">
<h2>Reputation</h2> <h2>Reputation</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="star_rating">Star rating</label><input id="star_rating" type="text" /></div>
<label for="star_rating">Star rating</label> <div><label for="review_count">Review count</label><input id="review_count" type="text" /></div>
<input id="star_rating" type="text" placeholder="e.g. 4.8" /> <div><label for="review_responses">Review responses observed</label>
</div> <select id="review_responses"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
<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><label for="review_responses_detail">If Yes: N of total with replies + most recent age</label><input id="review_responses_detail" type="text" /></div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Ownership / Claim Signals</h2> <h2>Ownership / Claim</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="claim_prompt">Own this business? prompt</label>
<label for="claim_prompt">“Own this business?” prompt visible</label> <select id="claim_prompt"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
<select id="claim_prompt">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div> </div>
<div> <div><label for="claimed">Claimed status</label>
<label for="claimed">Claimed status (your best judgment)</label>
<select id="claimed"> <select id="claimed">
<option value="Unknown">Unknown</option> <option>Unknown</option>
<option value="Yes">Yes</option> <option>Claimed</option>
<option value="No">No</option> <option>Unclaimed</option>
<option>Likely Claimed — inferred</option>
</select> </select>
</div> </div>
<div> <div class="full"><label for="claimed_signals">If inferred — signals used</label><input id="claimed_signals" type="text" /></div>
<label for="verified">Verified badge or equivalent observed</label> <div><label for="verified">Verified badge</label>
<select id="verified"> <select id="verified"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div> </div>
</div> </div>
</div> </div>
@@ -264,263 +109,148 @@
<div class="card"> <div class="card">
<h2>Operations</h2> <h2>Operations</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="hours_listed">Hours listed</label>
<label for="hours_listed">Hours listed</label> <select id="hours_listed"><option>No</option><option>Yes</option></select>
<select id="hours_listed">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
</div> </div>
<div> <div><label for="open_now">Open now (requires datetime+TZ)</label>
<label for="open_now">Open now indicator observed</label> <select id="open_now"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
<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 class="full"><label for="hours_text">Hours text</label><input id="hours_text" type="text" /></div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Conversion</h2> <h2>Conversion (complete whole row if any button observed)</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="booking_button">Booking button on GBP</label>
<label for="booking_button">Booking button present <strong>on the GBP</strong> (not the website)</label> <select id="booking_button"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
<select id="booking_button">
<option value="Unknown">Unknown</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div> </div>
<div> <div><label for="booking_label">Booking label</label><input id="booking_label" type="text" /></div>
<label for="booking_label">Booking button label (if any)</label> <div><label for="messaging_button">Messaging button</label>
<input id="booking_label" type="text" /> <select id="messaging_button"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
</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 class="full"><label for="other_buttons">Other action buttons</label><input id="other_buttons" type="text" /></div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Content Surface</h2> <h2>Content</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="photos_present">Photos present</label>
<label for="photos_present">Photos present</label> <select id="photos_present"><option>No</option><option>Yes</option></select>
<select id="photos_present">
<option value="No">No</option>
<option value="Yes">Yes</option>
</select>
</div> </div>
<div> <div><label for="photo_count">Approx photo count</label><input id="photo_count" type="text" /></div>
<label for="photo_count">Approx photo count</label> <div class="full"><label for="services">Services listed (exact)</label><input id="services" type="text" /></div>
<input id="photo_count" type="text" /> <div><label for="posts_section">Posts section present</label>
<select id="posts_section"><option>NOT_OBSERVED</option><option>Yes</option><option>No</option></select>
</div> </div>
<div class="full"> <div><label for="post_count">Post count (0 if empty)</label><input id="post_count" type="text" placeholder="0" /></div>
<label for="services">Services listed</label> <div><label for="recent_post">Most recent post date</label><input id="recent_post" type="text" /></div>
<input id="services" type="text" placeholder="e.g. strength training only" /> <div><label for="qa_status">Q&A status</label>
</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"> <select id="qa_status">
<option value="Not visible">Not visible</option> <option>NOT_OBSERVED</option>
<option value="Visible but empty">Visible but empty</option> <option>NOT_PRESENT</option>
<option value="Visible + has answers">Visible + has answers</option> <option>empty</option>
<option>answers present</option>
</select> </select>
</div> </div>
<div class="full"> <div class="full"><label for="attributes">Attributes / highlights</label><input id="attributes" type="text" /></div>
<label for="attributes">Attributes / highlights visible (optional)</label>
<input id="attributes" type="text" placeholder="e.g. women-owned, LGBTQ-friendly" />
</div>
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<h2>Related Listings</h2> <h2>Related listings</h2>
<div class="grid"> <div class="grid">
<div> <div><label for="second_listing">Second related listing nearby</label>
<label for="second_listing">Second related listing nearby</label> <select id="second_listing"><option>Unknown</option><option>Yes</option><option>No</option></select>
<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><label for="second_names">Related listing name(s)</label><input id="second_names" type="text" /></div>
<div class="full"><label for="second_notes">Related notes (factual)</label><textarea id="second_notes"></textarea></div>
<div class="full"><label for="raw_notes">Raw notes</label><textarea id="raw_notes"></textarea></div>
</div> </div>
</div> </div>
</form> </form>
<div class="actions"> <div class="actions">
<button type="button" class="btn-primary" id="btnGenerate">Generate for Leonard</button> <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="btnCopy">Copy</button>
<button type="button" class="btn-secondary" id="btnDownload">Download .txt</button> <button type="button" class="btn-secondary" id="btnClear">Clear</button>
<button type="button" class="btn-secondary" id="btnClear">Clear form</button>
</div> </div>
<div class="status" id="status"></div> <div class="status" id="status"></div>
<label for="output">Leonard-ready output</label> <label for="output">Leonard-ready output</label>
<textarea id="output" readonly placeholder="Click Generate for Leonard…"></textarea> <textarea id="output" readonly></textarea>
<p class="hint">Paste the output block directly to Leonard. He should follow the execution card and only fire threat rules R1R3.</p> <p class="hint">Follows execution card v1.2 (R1R6). PLACE_ID computed from URL by agent if blank.</p>
</div> </div>
<script>
<script> const $ = (id) => document.getElementById(id);
const $ = (id) => document.getElementById(id); function val(id) {
const v = ($(id).value || '').trim();
function val(id) { return v === '' ? 'NOT_OBSERVED' : v;
const el = $(id); }
const v = (el.value || "").trim(); function buildOutput() {
return v === "" ? "Not observed" : v; return `Follow docs/agents/leonard-gbp-execution-card.md exactly (v1.2).
} Map intake into client Data Inventory GBP row.
Apply only threat trigger rules R1R6.
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. 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. Return: updated GBP section, any threat changes, Changes Made list (with object IDs), Rules Fired list (rule ID, version, inputs, values, output).
--- GBP SNAPSHOT INTAKE --- --- GBP SNAPSHOT INTAKE ---
CLIENT: ${val("client")} CLIENT: ${val('client')}
SNAPSHOT_DATE: ${val("snapshot_date")} SNAPSHOT_DATETIME: ${val('snapshot_dt')}
SOURCE: ${val("source")} SOURCE: ${val('source')}
MAPS_URL: ${val("maps_url")} OBSERVATION_SURFACE: ${val('surface')}
PLACE_ID: ${val("place_id")} MAPS_URL: ${val('maps_url')}
PLACE_ID: ${($('place_id').value || '').trim() || 'COMPUTE_FROM_URL'}
BUSINESS_NAME: ${val("business_name")} BUSINESS_NAME: ${val('business_name')}
ADDRESS: ${val("address")} ADDRESS: ${val('address')}
PHONE: ${val("phone")} ADDRESS_CANONICAL: ${val('address_canonical')}
WEBSITE: ${val("website")} PHONE: ${val('phone')}
PRIMARY_CATEGORY: ${val("primary_category")} WEBSITE: ${val('website')}
ADDITIONAL_CATEGORIES: ${val("additional_categories")} PRIMARY_CATEGORY: ${val('primary_category')}
ADDITIONAL_CATEGORIES: ${val('additional_categories')}
STAR_RATING: ${val("star_rating")} STAR_RATING: ${val('star_rating')}
REVIEW_COUNT: ${val("review_count")} REVIEW_COUNT: ${val('review_count')}
REVIEW_RESPONSES_OBSERVED: ${val("review_responses")} REVIEW_RESPONSES_OBSERVED: ${val('review_responses')}
REVIEW_RESPONSES_DETAIL: ${val("review_responses_detail")} REVIEW_RESPONSES_DETAIL: ${val('review_responses_detail')}
CLAIM_PROMPT_VISIBLE: ${val("claim_prompt")} CLAIM_PROMPT_VISIBLE: ${val('claim_prompt')}
CLAIMED_STATUS: ${val("claimed")} CLAIMED_STATUS: ${val('claimed')}
VERIFIED_BADGE_VISIBLE: ${val("verified")} CLAIMED_SIGNALS: ${val('claimed_signals')}
VERIFIED_BADGE_VISIBLE: ${val('verified')}
HOURS_LISTED: ${val("hours_listed")} HOURS_LISTED: ${val('hours_listed')}
HOURS_TEXT: ${val("hours_text")} HOURS_TEXT: ${val('hours_text')}
OPEN_NOW_OBSERVED: ${val("open_now")} OPEN_NOW_OBSERVED: ${val('open_now')}
BOOKING_BUTTON_PRESENT_ON_GBP: ${val("booking_button")} BOOKING_BUTTON_PRESENT_ON_GBP: ${val('booking_button')}
BOOKING_BUTTON_LABEL: ${val("booking_label")} BOOKING_BUTTON_LABEL: ${val('booking_label')}
MESSAGING_BUTTON_PRESENT: ${val("messaging_button")} MESSAGING_BUTTON_PRESENT: ${val('messaging_button')}
OTHER_ACTION_BUTTONS: ${val("other_buttons")} OTHER_ACTION_BUTTONS: ${val('other_buttons')}
PHOTOS_PRESENT: ${val("photos_present")} PHOTOS_PRESENT: ${val('photos_present')}
PHOTO_COUNT_APPROX: ${val("photo_count")} PHOTO_COUNT_APPROX: ${val('photo_count')}
SERVICES_LISTED: ${val("services")} SERVICES_LISTED: ${val('services')}
UPDATES_POSTS_STATUS: ${val("posts_status")} POSTS_SECTION_PRESENT: ${val('posts_section')}
MOST_RECENT_POST_DATE: ${val("recent_post")} POST_COUNT: ${val('post_count')}
QA_STATUS: ${val("qa_status")} MOST_RECENT_POST_DATE: ${val('recent_post')}
ATTRIBUTES_HIGHLIGHTS: ${val("attributes")} QA_STATUS: ${val('qa_status')}
ATTRIBUTES_HIGHLIGHTS: ${val('attributes')}
SECOND_LISTING_NEARBY: ${val("second_listing")} SECOND_LISTING_NEARBY: ${val('second_listing')}
SECOND_LISTING_NAMES: ${val("second_listing_names")} SECOND_LISTING_NAMES: ${val('second_names')}
SECOND_LISTING_NOTES: ${val("second_listing_notes")} SECOND_LISTING_NOTES: ${val('second_notes')}
RAW_NOTES: ${val('raw_notes')}
RAW_NOTES: ${val("raw_notes")}
`; `;
} }
$('btnGenerate').onclick = () => { $('output').value = buildOutput(); $('status').textContent = 'Generated.'; };
function setStatus(msg) { $('btnCopy').onclick = async () => {
$("status").textContent = msg; const t = $('output').value || buildOutput();
if (msg) setTimeout(() => { $('output').value = t;
if ($("status").textContent === msg) $("status").textContent = ""; try { await navigator.clipboard.writeText(t); $('status').textContent = 'Copied.'; } catch { $('status').textContent = 'Select-all + copy.'; }
}, 2500); };
} $('btnClear').onclick = () => { $('gbpForm').reset(); $('output').value = ''; $('status').textContent = 'Cleared.'; };
</script>
$("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> </body>
</html> </html>