All projects
Agent-built productSeptember 2026Reported by the owner

The OM, evolved

Built for

Philadelphia retail brokers and sponsors

Project

Slate CRE

A static PDF became a responsive interactive OM with a live engagement dashboard that shows which sections each prospect read.

02

Demo

Slate CRE landing
Slate CRE landing
Mayfair Commons interactive offering memorandum
Mayfair Commons interactive offering memorandum
Mayfair Commons engagement dashboard
Mayfair Commons engagement dashboard
Slate refuses embedding, so open the live OM in a new tab.Open live ↗
03

How it works

Architecture diagram
  1. 01

    Each OM is a self-contained page with its own design system, so a broker can send one link.

  2. 02

    Engagement dashboard reads section-level view events from the OM.

  3. 03

    Admin page gated by a Vercel middleware login; no framework runtime.

  4. 04

    Static hosting on Vercel keeps cost near zero per deal.

05

Stack and code

middleware.js
// Vercel Edge Middleware - gates the demo OMs and dashboard behind an admin cookie.
// Verifies an HMAC-SHA256 signed token. The matching login endpoint lives at /api/login.

export const config = {
  matcher: [
    '/fort-williams-square-om',
    '/fort-williams-square-om.html',
  ],
};

const COOKIE_NAME = 'slate_admin';

export default async function middleware(request) {
  const cookieHeader = request.headers.get('cookie') || '';
  const token = readCookie(cookieHeader, COOKIE_NAME);
  const secret = process.env.AUTH_SECRET;

  if (token && secret && (await verifyToken(token, secret))) {
    return; // authenticated, continue to static file
  }

  const url = new URL(request.url);
  const target = new URL('/admin', url.origin);
  target.searchParams.set('next', url.pathname + url.search);
  return Response.redirect(target.toString(), 302);
}

function readCookie(header, name) {
  const parts = header.split(/;\s*/);
  for (const p of parts) {
    const eq = p.indexOf('=');
    if (eq < 0) continue;
    if (p.slice(0, eq).trim() === name) return p.slice(eq + 1);
  }
  return null;
}

// Token format: base64url(JSON({exp})).base64url(HMAC-SHA256(payload, secret))
async function verifyToken(token, secret) {
  const dot = token.indexOf('.');
  if (dot < 1) return false;
  const payload = token.slice(0, dot);
  const sigGiven = token.slice(dot + 1);

  let expectedSig;
  try {
    const key = await crypto.subtle.importKey(
      'raw',
      new TextEncoder().encode(secret),
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign'],
    );
    const sigBuf = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(payload));
    expectedSig = base64url(sigBuf);
  } catch {
    return false;
  }

  if (!timingSafeEqual(sigGiven, expectedSig)) return false;

  let parsed;
  try {
    parsed = JSON.parse(b64urlDecode(payload));
  } catch {
    return false;
  }
  if (typeof parsed.exp !== 'number') return false;
  if (Date.now() / 1000 > parsed.exp) return false;

  return true;
}

function base64url(buf) {
  const bytes = new Uint8Array(buf);
  let str = '';
  for (let i = 0; i < bytes.length; i++) str += String.fromCharCode(bytes[i]);
  return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}

function b64urlDecode(s) {
  const pad = s.length % 4 === 0 ? 0 : 4 - (s.length % 4);
  const b64 = (s + '='.repeat(pad)).replace(/-/g, '+').replace(/_/g, '/');
  return atob(b64);
}

function timingSafeEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
  return diff === 0;
}

~/Documents/Slate/middleware.js

mayfair-commons-om.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Mayfair Commons · Offering Memorandum · Slate CRE</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { background: #faf9f5; display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
    #__bundler_loading { position: fixed; bottom: 20px; right: 20px; font: 13px/1.4 -apple-system, BlinkMacSystemFont, sans-serif; color: #666; background: #fff; padding: 8px 14px; border-radius: 8px; box-shadow: 0 1px 4px rgba(0,0,0,0.12); z-index: 10000; }
    #__bundler_thumbnail { position: fixed; inset: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background: #faf9f5; z-index: 9999; }
    #__bundler_thumbnail svg { width: 100%; height: 100%; object-fit: contain; }
    #__bundler_placeholder { color: #999; font-size: 14px; }
  </style>
  <noscript>
    <style>#__bundler_loading { display: none; }</style>
    <div style="position:fixed;bottom:12px;left:12px;font:13px/1.4 -apple-system,BlinkMacSystemFont,sans-serif;color:#999;background:rgba(255,255,255,0.9);padding:6px 12px;border-radius:6px;box-shadow:0 1px 4px rgba(0,0,0,0.08);z-index:10000;">
      This page requires JavaScript to display.
    </div>
  </noscript>
</head>
<body>
  <div id="__bundler_thumbnail">
<svg viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
  <rect width="400" height="300" fill="#1B2942"></rect>
  <rect x="120" y="90" width="160" height="120" fill="#E6CFA8"></rect>
  <rect x="120" y="90" width="160" height="16" fill="#B5523A"></rect>
  <rect x="138" y="128" width="52" height="64" fill="#4A6B7D"></rect>
  <rect x="200" y="128" width="30" height="64" fill="#8FA9B8"></rect>
  <rect x="238" y="128" width="24" height="64" fill="#8FA9B8"></rect>
  <text x="200" y="245" fill="#E6CFA8" font-family="Georgia,serif" font-size="26" font-style="italic" text-anchor="middle">Mayfair Commons</text>
</svg>
</div>
  <div id="__bundler_loading">Unpacking...</div>

  <script>
    
document.addEventListener('DOMContentLoaded', async function() {
  const loading = document.getElementById('__bundler_loading');
  function setStatus(msg) { if (loading) loading.textContent = msg; }

  const FONT_MIME = /^(font[/]|application[/](x-)?font-|application[/]vnd\.ms-fontobject)/i;
  const MIME_TOKEN = /^[\w.+-]+[/][\w.+-]+$/;
  function toBase64(bytes) {
    let bin = '';
    for (let i = 0; i < bytes.length; i += 0x8000) {
      bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
    }
    return btoa(bin);
  }

  // Error sink persists across replaceWith since it's on window, not the DOM.
  window.addEventListener('error', function(e) {
    var p = document.body || document.documentElement;
    var d = document.getElementById('__bundler_err') || p.appendChild(document.createElement('div'));
    d.id = '__bundler_err';
    d.style.cssText = 'position:fixed;bottom:12px;left:12px;right:12px;font:12px/1.4 ui-monospace,monospace;background:#2a1215;color:#ff8a80;padding:10px 14px;border-radius:8px;border:1px solid #5c2b2e;z-index:99999;white-space:pre-wrap;max-height:40vh;overflow:auto';
    d.textContent = (d.textContent ? d.textContent + String.fromCharCode(10) : '') +
      '[bundle] ' + (e.message || e.type) +
      (e.filename ? ' (' + e.filename.slice(0, 60) + ':' + e.lineno + ')' : '');
  }, true);

  try {
    const manifestEl = document.querySelector('script[type="__bundler/manifest"]');
    const templateEl = document.querySelector('script[type="__bundler/template"]');
    if (!manifestEl || !templateEl) {
      setStatus('Error: missing bundle data');
      console.error('[bundler] Missing script tags — manifestEl:', !!manifestEl, 'templateEl:', !!templateEl);
      return;
    }

    const manifest = JSON.parse(manifestEl.textContent);
    let template = JSON.parse(templateEl.textContent);

    const uuids = Object.keys(manifest);
    setStatus('Unpacking ' + uuids.length + ' assets...');

    const blobUrls = {};
    await Promise.all(uuids.map(async (uuid) => {
      const entry = manifest[uuid];
      try {

~/Documents/Slate/mayfair-commons-om.html