patx/miadruck
Add editor-controlled property spotlight popup
Commit c5edf2c · patx · 2026-08-27T20:20:13-04:00
Comments
No comments yet.
Diff
diff --git a/docs/404.html b/docs/404.html
index b0bb494..219fa9e 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -10,8 +10,8 @@
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
<a class="skip" href="#main">Skip to content</a>
diff --git a/docs/assets/site.css b/docs/assets/site.css
index aec4538..25076fb 100644
--- a/docs/assets/site.css
+++ b/docs/assets/site.css
@@ -944,6 +944,132 @@ footer a:hover{ color:#fff; text-decoration:underline; text-underline-offset:3px
.copy-toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
@media (max-width:860px){ .copy-toast{ bottom:74px; } }
+/* Property spotlight popup ----------------------------------------------- */
+body.property-promo-open{ overflow:hidden; }
+.property-promo-layer{
+ position:fixed;
+ inset:0;
+ z-index:1000;
+ display:grid;
+ place-items:center;
+ overflow-y:auto;
+ padding:clamp(16px,4vw,48px);
+ background:rgba(15,36,54,.76);
+ opacity:0;
+ pointer-events:none;
+ transition:opacity .22s ease;
+}
+.property-promo-layer.is-visible{ opacity:1; pointer-events:auto; }
+.property-promo{
+ display:grid;
+ width:min(920px,100%);
+ max-height:calc(100vh - clamp(32px,8vw,96px));
+ max-height:calc(100dvh - clamp(32px,8vw,96px));
+ grid-template-columns:minmax(300px,.86fr) minmax(0,1.14fr);
+ overflow:auto;
+ border:1px solid rgba(255,255,255,.28);
+ border-radius:2px;
+ background:var(--paper);
+ box-shadow:0 28px 80px rgba(8,22,33,.36);
+ transform:translateY(18px) scale(.985);
+ transition:transform .32s cubic-bezier(.22,.61,.36,1);
+}
+.property-promo-layer.is-visible .property-promo{ transform:none; }
+.property-promo-media{
+ position:relative;
+ min-height:480px;
+ overflow:hidden;
+ background:var(--sand);
+ padding-right:38px;
+}
+.property-promo-media img{
+ width:100%;
+ height:100%;
+ object-fit:cover;
+}
+.property-promo-spine{
+ position:absolute;
+ top:0;
+ right:0;
+ bottom:0;
+ display:flex;
+ width:38px;
+ align-items:center;
+ justify-content:center;
+ background:var(--brass);
+ color:#fff;
+ font-family:var(--sans);
+ font-size:10px;
+ font-weight:600;
+ letter-spacing:.2em;
+ text-transform:uppercase;
+ writing-mode:vertical-rl;
+ transform:rotate(180deg);
+}
+.property-promo-body{
+ align-self:center;
+ padding:clamp(30px,5vw,58px);
+}
+.property-promo-body .eyebrow{ margin-bottom:14px; }
+.property-promo-body h2{
+ max-width:11ch;
+ font-size:clamp(38px,5.2vw,64px);
+ line-height:.95;
+}
+.property-promo-rule{
+ width:54px;
+ height:1px;
+ margin:26px 0 24px;
+ background:var(--brass);
+}
+.property-promo-price{
+ margin:0;
+ font-family:var(--serif);
+ font-variation-settings:'opsz' 144;
+ font-size:clamp(25px,3vw,32px);
+ font-weight:600;
+ letter-spacing:-.02em;
+ line-height:1.1;
+}
+.property-promo-address{ margin:12px 0 2px; font-size:clamp(20px,2vw,25px); }
+.property-promo-location{
+ margin:0;
+ color:var(--ink-60);
+ font-family:var(--serif);
+ font-size:17px;
+ font-style:italic;
+}
+.property-promo-specs{
+ margin:20px 0 0;
+ color:var(--ink-80);
+ font-size:12px;
+ font-weight:600;
+ letter-spacing:.07em;
+ line-height:1.7;
+ text-transform:uppercase;
+}
+.property-promo-actions{
+ display:flex;
+ gap:12px;
+ margin-top:30px;
+}
+.property-promo-actions .btn{ min-width:0; padding-inline:20px; white-space:nowrap; }
+
+@media (max-width:720px){
+ .property-promo{ width:min(540px,100%); grid-template-columns:1fr; }
+ .property-promo-media{ min-height:0; height:clamp(190px,34vh,270px); }
+ .property-promo-body{ padding:28px clamp(22px,7vw,38px) 32px; }
+ .property-promo-body h2{ max-width:14ch; font-size:clamp(34px,10vw,48px); }
+ .property-promo-rule{ margin:20px 0; }
+}
+@media (max-width:440px){
+ .property-promo-layer{ align-items:end; padding:10px; }
+ .property-promo{ max-height:calc(100dvh - 20px); }
+ .property-promo-media{ height:180px; }
+ .property-promo-actions{ align-items:stretch; flex-direction:column; }
+ .property-promo-actions .btn{ width:100%; }
+}
+
/* Utility ----------------------------------------------------------------- */
.hide{ display:none !important; }
diff --git a/docs/assets/site.js b/docs/assets/site.js
index 392ca87..1e47b0b 100644
--- a/docs/assets/site.js
+++ b/docs/assets/site.js
@@ -184,6 +184,199 @@
var deepLink = new URLSearchParams(location.search).get('listing');
if (deepLink) setTimeout(function () { highlight(deepLink); }, 300);
+ /* -- Property spotlight popup ----------------------------------------- */
+ (function () {
+ // Someone following a listing deep link is already headed to a property.
+ if (deepLink || !window.fetch) return;
+
+ var manifestRequest = fetch('/properties/properties.json', {
+ credentials: 'same-origin',
+ cache: 'no-cache'
+ }).then(function (response) {
+ if (!response.ok) throw new Error('Property manifest unavailable');
+ return response.json();
+ });
+
+ function popupSelection(manifest) {
+ var settings = manifest && manifest.popup;
+ if (!settings || settings.enabled !== true ||
+ typeof settings.title !== 'string' || !settings.title.trim() ||
+ typeof settings.listing_id !== 'string') return null;
+ var listings = Array.isArray(manifest.listings) ? manifest.listings : [];
+ var listing = listings.find(function (item) {
+ return item && item.id === settings.listing_id;
+ });
+ return listing ? { settings: settings, listing: listing } : null;
+ }
+
+ function sessionKey(selection) {
+ return 'miadruck:property-popup:' + selection.listing.id + ':' +
+ selection.settings.title.trim();
+ }
+
+ function wasDismissed(key) {
+ try { return sessionStorage.getItem(key) === 'dismissed'; }
+ catch (err) { return false; }
+ }
+
+ function markDismissed(key) {
+ try { sessionStorage.setItem(key, 'dismissed'); }
+ catch (err) { /* Storage can be unavailable in privacy modes. */ }
+ }
+
+ function assetUrl(path) {
+ return '/' + String(path || '').replace(/^\/+/, '');
+ }
+
+ function detailsUrl(listing) {
+ if (listing.details_url) {
+ try {
+ var external = new URL(listing.details_url);
+ if (external.protocol === 'https:') return external.toString();
+ } catch (err) { /* Fall through to the on-site listing. */ }
+ }
+ return '/?listing=' + encodeURIComponent(listing.id);
+ }
+
+ function money(value) {
+ var number = Number(value);
+ if (!Number.isFinite(number)) return '';
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ maximumFractionDigits: 0
+ }).format(number);
+ }
+
+ function showPopup(manifest) {
+ var selection = popupSelection(manifest);
+ if (!selection) return;
+ var key = sessionKey(selection);
+ if (wasDismissed(key)) return;
+
+ var listing = selection.listing;
+ var address = listing.address || {};
+ var image = listing.image || {};
+ var statusLabels = {
+ for_sale: 'For sale',
+ for_rent: 'For rent',
+ off_market: 'Off market'
+ };
+ var price = money(listing.price);
+ if (listing.price_period === 'month') price += ' / month';
+
+ var layer = document.createElement('div');
+ layer.className = 'property-promo-layer';
+ layer.innerHTML =
+ '<section class="property-promo" role="dialog" aria-modal="true" ' +
+ 'aria-labelledby="propertyPromoTitle" aria-describedby="propertyPromoDetails" tabindex="-1">' +
+ '<div class="property-promo-media">' +
+ '<img width="560" height="420">' +
+ '<span class="property-promo-spine" aria-hidden="true">Mia\'s spotlight</span>' +
+ '</div>' +
+ '<div class="property-promo-body">' +
+ '<p class="eyebrow property-promo-status"></p>' +
+ '<h2 id="propertyPromoTitle"></h2>' +
+ '<div class="property-promo-rule"></div>' +
+ '<p class="property-promo-price"></p>' +
+ '<h3 class="property-promo-address"></h3>' +
+ '<p class="property-promo-location"></p>' +
+ '<p class="property-promo-specs" id="propertyPromoDetails"></p>' +
+ '<div class="property-promo-actions">' +
+ '<a class="btn property-promo-view">View listing</a>' +
+ '<button class="btn btn-ghost property-promo-continue" type="button">Continue browsing</button>' +
+ '</div>' +
+ '</div>' +
+ '</section>';
+
+ var dialog = layer.querySelector('.property-promo');
+ var promoImage = layer.querySelector('img');
+ promoImage.src = assetUrl(image.thumb || image.src);
+ promoImage.alt = image.alt || (address.street ? 'Home at ' + address.street : 'Featured home');
+ layer.querySelector('.property-promo-status').textContent =
+ statusLabels[listing.status] || 'Featured property';
+ layer.querySelector('#propertyPromoTitle').textContent = selection.settings.title.trim();
+ layer.querySelector('.property-promo-price').textContent = price;
+ layer.querySelector('.property-promo-address').textContent = address.street || '';
+ layer.querySelector('.property-promo-location').textContent =
+ [address.city, address.state].filter(Boolean).join(', ');
+ layer.querySelector('.property-promo-specs').textContent = [
+ listing.bedrooms + ' bed',
+ listing.bathrooms + ' bath',
+ Number(listing.square_feet).toLocaleString('en-US') + ' sq ft',
+ listing.property_type
+ ].filter(Boolean).join(' · ');
+
+ var view = layer.querySelector('.property-promo-view');
+ var target = detailsUrl(listing);
+ view.href = target;
+ if (/^https:\/\//i.test(target)) {
+ view.target = '_blank';
+ view.rel = 'noopener';
+ }
+
+ var previousFocus = document.activeElement;
+ var closed = false;
+
+ function closePopup() {
+ if (closed) return;
+ closed = true;
+ markDismissed(key);
+ layer.classList.remove('is-visible');
+ document.body.classList.remove('property-promo-open');
+ document.removeEventListener('keydown', onKeydown);
+ setTimeout(function () {
+ layer.remove();
+ if (previousFocus && previousFocus.focus) previousFocus.focus();
+ }, reduceMotion ? 0 : 220);
+ }
+
+ function onKeydown(event) {
+ if (event.key === 'Escape') {
+ event.preventDefault();
+ closePopup();
+ return;
+ }
+ if (event.key !== 'Tab') return;
+ var focusable = Array.prototype.slice.call(
+ dialog.querySelectorAll('a[href],button:not([disabled])')
+ );
+ if (!focusable.length) return;
+ var first = focusable[0];
+ var last = focusable[focusable.length - 1];
+ if (event.shiftKey && document.activeElement === first) {
+ event.preventDefault(); last.focus();
+ } else if (!event.shiftKey && document.activeElement === last) {
+ event.preventDefault(); first.focus();
+ }
+ }
+
+ layer.querySelector('.property-promo-continue').addEventListener('click', closePopup);
+ view.addEventListener('click', closePopup);
+ layer.addEventListener('click', function (event) {
+ if (event.target === layer) closePopup();
+ });
+ document.addEventListener('keydown', onKeydown);
+ document.body.appendChild(layer);
+ document.body.classList.add('property-promo-open');
+ requestAnimationFrame(function () {
+ layer.classList.add('is-visible');
+ view.focus();
+ });
+ }
+
+ function schedulePopup() {
+ setTimeout(function () {
+ manifestRequest.then(showPopup).catch(function () {
+ // The promotion is optional; the website remains fully usable.
+ });
+ }, 2000);
+ }
+
+ if (document.readyState === 'complete') schedulePopup();
+ else window.addEventListener('load', schedulePopup, { once: true });
+ })();
+
/* -- Contact form (Formspree, AJAX) ------------------------------------- */
(function () {
var form = document.getElementById('contactForm');
diff --git a/docs/boca-raton-real-estate/index.html b/docs/boca-raton-real-estate/index.html
index 209355b..5f4d11e 100644
--- a/docs/boca-raton-real-estate/index.html
+++ b/docs/boca-raton-real-estate/index.html
@@ -32,8 +32,8 @@
<link rel="manifest" href="/site.webmanifest">
<link rel="preload" href="/assets/fonts/fraunces-latin-var.woff2" as="font" type="font/woff2" crossorigin>
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
diff --git a/docs/fort-lauderdale-real-estate/index.html b/docs/fort-lauderdale-real-estate/index.html
index 711b89e..59e9272 100644
--- a/docs/fort-lauderdale-real-estate/index.html
+++ b/docs/fort-lauderdale-real-estate/index.html
@@ -32,8 +32,8 @@
<link rel="manifest" href="/site.webmanifest">
<link rel="preload" href="/assets/fonts/fraunces-latin-var.woff2" as="font" type="font/woff2" crossorigin>
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
diff --git a/docs/index.html b/docs/index.html
index 247939a..f885194 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -45,8 +45,8 @@
imagesrcset="/assets/img/hero-fort-lauderdale-640w.webp 640w, /assets/img/hero-fort-lauderdale-960w.webp 960w, /assets/img/hero-fort-lauderdale.webp 1280w"
imagesizes="100vw" fetchpriority="high">
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
diff --git a/docs/lighthouse-point-real-estate/index.html b/docs/lighthouse-point-real-estate/index.html
index 9efefc1..4986564 100644
--- a/docs/lighthouse-point-real-estate/index.html
+++ b/docs/lighthouse-point-real-estate/index.html
@@ -32,8 +32,8 @@
<link rel="manifest" href="/site.webmanifest">
<link rel="preload" href="/assets/fonts/fraunces-latin-var.woff2" as="font" type="font/woff2" crossorigin>
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
diff --git a/docs/pompano-beach-real-estate/index.html b/docs/pompano-beach-real-estate/index.html
index c2fe8ac..29ade06 100644
--- a/docs/pompano-beach-real-estate/index.html
+++ b/docs/pompano-beach-real-estate/index.html
@@ -32,8 +32,8 @@
<link rel="manifest" href="/site.webmanifest">
<link rel="preload" href="/assets/fonts/fraunces-latin-var.woff2" as="font" type="font/woff2" crossorigin>
<script>document.documentElement.classList.add('js')</script>
-<link rel="stylesheet" href="/assets/site.css">
-<script src="/assets/site.js" defer></script>
+<link rel="stylesheet" href="/assets/site.css?v=20260827-popup">
+<script src="/assets/site.js?v=20260827-popup" defer></script>
</head>
<body>
diff --git a/docs/properties/properties.json b/docs/properties/properties.json
index 4130dfd..593ec27 100644
--- a/docs/properties/properties.json
+++ b/docs/properties/properties.json
@@ -1,5 +1,10 @@
{
"version": 1,
+ "popup": {
+ "enabled": false,
+ "title": "",
+ "listing_id": null
+ },
"listings": [
{
"id": "boca-600-nw-12",