This commit is contained in:
2026-07-16 05:48:12 -05:00
parent c36d46c3b3
commit e9be65df8c
558 changed files with 74087 additions and 75 deletions
@@ -0,0 +1,313 @@
<!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>