Files
monica_site/proposal/pages/portal-postcard-designer.html
2026-07-16 05:48:12 -05:00

314 lines
14 KiB
HTML
Raw Permalink 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>Postcard designer · Portal · MKDRealtor.com</title>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Work+Sans:300,400,500,700%7CPoppins:400,600,700">
<link rel="stylesheet" href="proposal.css">
</head>
<body class="portal">
<div class="proposal-banner">
<div><span class="tag">Proposal mockup</span> &nbsp; MKDRealtor.com — UX examples (not production)</div>
<div><a href="index.html">All screens</a></div>
</div>
<div class="portal-shell">
<aside class="portal-sidebar">
<a class="portal-brand" href="portal-dashboard.html"><img src="brand/exit_logo.png" alt="EXIT Realty"><span>MKDRealtor.com</span></a>
<nav class="portal-nav">
<div class="nav-section">Overview</div>
<a class="" href="portal-dashboard.html">Dashboard</a>
<a class="" href="portal-leads.html">Leads</a>
<a class="" href="portal-analytics.html">Analytics</a>
<div class="nav-section">Outreach</div>
<a class="" href="portal-contacts.html">Mailing list</a>
<a class="" href="portal-campaign.html">Campaigns</a>
<a class="active" href="portal-postcard-designer.html">Postcard design</a>
<div class="nav-section">Social</div>
<a class="" href="portal-social.html">Compose & preview</a>
<a class="" href="portal-social-accounts.html">Accounts</a>
</nav>
</aside>
<div class="portal-main">
<div class="portal-topbar">
<h1>Postcard designer</h1>
<div class="portal-user">Signed in as <strong>Monica Dhillon</strong> · <a href="portal-login.html" style="color:#00626c">Sign out</a></div>
</div>
<div class="portal-content">
<div class="proposal-note"><strong>UX note:</strong> Pick print size (4×6 / 6×9 / 6×11 / square). Upload images once into a reusable media library, then apply to any design. Save as template → attach to postcard campaign.</div>
<div class="designer-layout">
<div class="designer-controls">
<div class="panel">
<div class="panel-h"><h2>Card size</h2><span class="muted" style="font-size:12px" id="size-label">4×6 in</span></div>
<div class="panel-b">
<div class="size-grid" id="size-grid">
<button type="button" class="size-option active" data-size="4x6" data-label="4×6 in" data-ratio="6/4" onclick="setSize(this)">
<div class="sz-name">4 × 6</div>
<div class="sz-meta">Standard · lowest postage</div>
<span class="sz-badge">Most common</span>
</button>
<button type="button" class="size-option" data-size="6x9" data-label="6×9 in" data-ratio="9/6" onclick="setSize(this)">
<div class="sz-name">6 × 9</div>
<div class="sz-meta">More room for photo + copy</div>
</button>
<button type="button" class="size-option" data-size="6x11" data-label="6×11 in" data-ratio="11/6" onclick="setSize(this)">
<div class="sz-name">6 × 11</div>
<div class="sz-meta">Jumbo · stands out in mail</div>
</button>
<button type="button" class="size-option" data-size="square" data-label="6×6 in square" data-ratio="1/1" onclick="setSize(this)">
<div class="sz-name">6 × 6</div>
<div class="sz-meta">Square · social-ready crop</div>
</button>
</div>
<p class="library-hint">Size locked when campaign is queued. Provider pricing varies by size.</p>
</div>
</div>
<div class="panel">
<div class="panel-h"><h2>Media library</h2><span class="muted" style="font-size:12px">Reusable</span></div>
<div class="panel-b">
<input type="file" id="pc-upload" accept="image/*" multiple hidden onchange="handleUpload(event)">
<div class="dropzone compact" id="pc-drop" onclick="document.getElementById('pc-upload').click()"
ondragover="event.preventDefault();this.style.borderColor='#00626c'"
ondragleave="this.style.borderColor=''"
ondrop="event.preventDefault();this.style.borderColor=''; handleDrop(event)">
Drop images here or <strong>click to upload</strong><br>
<span class="muted">Saved to your library — reuse across postcards &amp; campaigns</span>
</div>
<div class="media-library" id="media-library" style="margin-top:12px"></div>
<p class="library-hint" id="library-count">0 images in library</p>
</div>
</div>
<div class="panel">
<div class="panel-h"><h2>Side</h2></div>
<div class="panel-b">
<div class="channel-tabs" style="margin:0">
<a href="#" class="active" id="tab-front" onclick="showSide('front');return false">Front</a>
<a href="#" id="tab-back" onclick="showSide('back');return false">Back</a>
</div>
</div>
</div>
<div class="panel" id="front-controls">
<div class="panel-h"><h2>Front artwork</h2></div>
<div class="panel-b form-grid">
<div class="field"><label>Headline</label>
<input id="pc-headline" value="JUST LISTED" oninput="syncPostcard()">
</div>
<div class="field"><label>Subhead</label>
<input id="pc-sub" value="412 Willow Lane · Open Sat 111" oninput="syncPostcard()">
</div>
<div class="field"><label>Accent color</label>
<input id="pc-color" type="color" value="#00626c" oninput="syncPostcard()">
</div>
<div class="field"><label>Overlay</label>
<select id="pc-overlay" onchange="syncPostcard()">
<option value="dark">Dark gradient</option>
<option value="light">Light band</option>
<option value="none">None</option>
</select>
</div>
</div>
</div>
<div class="panel" id="back-controls" style="display:none">
<div class="panel-h"><h2>Back copy</h2></div>
<div class="panel-b form-grid">
<div class="field"><label>Body text</label>
<textarea id="pc-body" style="min-height:100px" oninput="syncPostcard()">Quiet street, updated kitchen, walkable to Oakridge schools. Asking $449,000.
Call or text Monica for a private showing.</textarea>
</div>
<div class="field"><label>Agent line</label>
<input id="pc-agent" value="Monica Dhillon · (555) 123-4567 · mkdrealtor.com" oninput="syncPostcard()">
</div>
<div class="field"><label>Include</label>
<label class="check-row"><input type="checkbox" id="pc-qr" checked onchange="syncPostcard()"> QR to listing / contact</label>
<label class="check-row"><input type="checkbox" id="pc-addr" checked onchange="syncPostcard()"> Recipient address block ( Lob )</label>
</div>
</div>
</div>
<div class="panel">
<div class="panel-b" style="display:flex;gap:8px;flex-wrap:wrap">
<button class="btn btn-primary" type="button" onclick="alert('Mockup: save front/back assets + size + library refs → MessageTemplate')">Save design</button>
<a class="btn btn-ghost" href="portal-campaign.html">Use in campaign</a>
</div>
</div>
</div>
<div class="designer-preview-col">
<div class="panel">
<div class="panel-h">
<h2>Live preview · <span id="preview-size">4×6</span></h2>
<span class="muted" style="font-size:12px" id="side-label">Front</span>
</div>
<div class="panel-b" style="background:#e8edf3;display:flex;justify-content:center;padding:28px">
<div class="postcard size-4x6" id="postcard-front">
<div class="postcard-media" id="pc-media"></div>
<div class="postcard-overlay" id="pc-ov"></div>
<div class="postcard-copy">
<div class="postcard-headline" id="pc-h-out">JUST LISTED</div>
<div class="postcard-sub" id="pc-s-out">412 Willow Lane · Open Sat 111</div>
</div>
</div>
<div class="postcard postcard-back size-4x6" id="postcard-back" style="display:none">
<div class="postcard-back-grid">
<div class="postcard-back-msg">
<div class="postcard-back-body" id="pc-b-out"></div>
<div class="postcard-agent" id="pc-a-out"></div>
<div class="postcard-qr" id="pc-qr-out">QR</div>
</div>
<div class="postcard-back-mail" id="pc-mail-out">
<div class="stamp">Stamp</div>
<div class="addr-block">
{{first_name}} {{last_name}}<br>
{{street}}<br>
{{city}}, {{state}} {{zip}}
</div>
</div>
</div>
</div>
</div>
</div>
<p class="muted" style="font-size:13px;margin:0">Print provider receives front + back images at selected size. Address verified before batch send (Lob).</p>
</div>
</div>
</div>
</div>
</div>
<script>
const LIBRARY_KEY = 'monica_postcard_library_v1';
let selectedBg = 'images/grid-layout-1-370x256.jpg';
let currentSize = '4x6';
const SEED_LIBRARY = [
{ id: 'seed-1', name: 'Willow exterior', src: 'images/grid-layout-1-370x256.jpg', reused: 3 },
{ id: 'seed-2', name: 'Kitchen', src: 'images/grid-layout-2-370x256.jpg', reused: 1 },
{ id: 'seed-3', name: 'Living room', src: 'images/grid-layout-3-370x256.jpg', reused: 0 },
{ id: 'seed-4', name: 'Neighborhood', src: 'images/bg-image-1.jpg', reused: 2 },
];
function loadLibrary() {
try {
const raw = localStorage.getItem(LIBRARY_KEY);
if (raw) return JSON.parse(raw);
} catch (e) {}
return SEED_LIBRARY.slice();
}
function saveLibrary(items) {
try { localStorage.setItem(LIBRARY_KEY, JSON.stringify(items)); } catch (e) {}
}
let library = loadLibrary();
function renderLibrary() {
const root = document.getElementById('media-library');
root.innerHTML = '';
const upload = document.createElement('button');
upload.type = 'button';
upload.className = 'media-tile upload-tile';
upload.innerHTML = '<span>+</span>Upload';
upload.onclick = () => document.getElementById('pc-upload').click();
root.appendChild(upload);
library.forEach(item => {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'media-tile' + (item.src === selectedBg ? ' active' : '');
btn.style.backgroundImage = 'url(' + JSON.stringify(item.src).slice(1, -1) + ')';
btn.title = item.name + (item.reused ? ' · used ' + item.reused + '×' : '');
btn.innerHTML = '<span class="media-label">' + escapeHtml(item.name) + '</span>';
btn.onclick = () => selectMedia(item.id);
root.appendChild(btn);
});
document.getElementById('library-count').textContent =
library.length + ' image' + (library.length === 1 ? '' : 's') + ' in library · click to use on this card';
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
}
function selectMedia(id) {
const item = library.find(x => x.id === id);
if (!item) return;
selectedBg = item.src;
item.reused = (item.reused || 0) + 1;
saveLibrary(library);
renderLibrary();
syncPostcard();
}
function addFiles(files) {
Array.from(files || []).forEach(file => {
if (!file.type.startsWith('image/')) return;
const reader = new FileReader();
reader.onload = () => {
const item = {
id: 'up-' + Date.now() + '-' + Math.random().toString(36).slice(2, 7),
name: file.name.replace(/\.[^.]+$/, '') || 'Upload',
src: reader.result,
reused: 0,
};
library.unshift(item);
saveLibrary(library);
selectedBg = item.src;
renderLibrary();
syncPostcard();
};
reader.readAsDataURL(file);
});
}
function handleUpload(e) { addFiles(e.target.files); e.target.value = ''; }
function handleDrop(e) { addFiles(e.dataTransfer.files); }
function setSize(btn) {
currentSize = btn.dataset.size;
document.querySelectorAll('.size-option').forEach(el => el.classList.toggle('active', el === btn));
document.getElementById('size-label').textContent = btn.dataset.label;
document.getElementById('preview-size').textContent = btn.dataset.label.replace(' in', '');
['postcard-front', 'postcard-back'].forEach(id => {
const el = document.getElementById(id);
el.className = el.className.replace(/size-\S+/g, '').trim() + ' size-' + currentSize;
if (id === 'postcard-back') el.classList.add('postcard-back');
});
}
function showSide(side) {
const front = side === 'front';
document.getElementById('postcard-front').style.display = front ? 'block' : 'none';
document.getElementById('postcard-back').style.display = front ? 'none' : 'block';
document.getElementById('front-controls').style.display = front ? 'block' : 'none';
document.getElementById('back-controls').style.display = front ? 'none' : 'block';
document.getElementById('tab-front').classList.toggle('active', front);
document.getElementById('tab-back').classList.toggle('active', !front);
document.getElementById('side-label').textContent = front ? 'Front' : 'Back';
}
function syncPostcard() {
const color = document.getElementById('pc-color').value;
const ov = document.getElementById('pc-overlay').value;
document.getElementById('pc-media').style.backgroundImage = 'url(' + JSON.stringify(selectedBg).slice(1, -1) + ')';
document.getElementById('pc-h-out').textContent = document.getElementById('pc-headline').value;
document.getElementById('pc-s-out').textContent = document.getElementById('pc-sub').value;
const textColor = ov === 'light' ? '#111' : '#fff';
document.getElementById('pc-h-out').style.color = textColor;
document.getElementById('pc-s-out').style.color = textColor;
document.getElementById('pc-h-out').style.borderColor = color;
document.getElementById('pc-ov').className = 'postcard-overlay ov-' + ov;
document.getElementById('pc-b-out').textContent = document.getElementById('pc-body').value;
document.getElementById('pc-a-out').textContent = document.getElementById('pc-agent').value;
document.getElementById('pc-qr-out').style.display = document.getElementById('pc-qr').checked ? 'flex' : 'none';
document.getElementById('pc-mail-out').style.display = document.getElementById('pc-addr').checked ? 'block' : 'none';
}
renderLibrary();
syncPostcard();
</script>
</body>
</html>