Popup generator · single file HTML

Popups that ship themselves.

Shape a popup on the canvas, watch it behave, then copy one self-contained HTML block. No libraries, no accounts, no tracking baked in.

What you export

<div class="pf-overlay">
<style> … </style>
<script> … </script>
6.5 kB output0 deps4 triggers
01

Start from

02

Content

03

Look

Palette
Position
Animation
04

Behavior

Trigger
Show again

Keeps storage keys unique per popup.

Esc closes · focus returns

your-site.com
live
Northwind
ShopStoryJournal

Get the good stuff first

Join 4,200 readers who get one short email each Friday with the best finds, tools and ideas.

Join the list

opens after 4s · once per session

Built into the snippet

  • Esc key and overlay dismissal
  • Focus moved into the dialog
  • Scroll lock while open
  • Reduced-motion respect
  • Session or permanent memory
  • Mobile breakpoint included

05 Embed

Copy once, paste before </body>.

The block carries its own styles and script. It inherits your page font and stays out of the way of everything else.

html · css · js6.5 kB
<!-- Popup embed generated with Popforge. Paste this before </body>. -->
<div class="pf-overlay" id="pf-popforge" data-position="center" data-animation="scale" aria-hidden="true">
  <div class="pf-card" role="dialog" aria-modal="true" aria-labelledby="pf-popforge-title" aria-describedby="pf-popforge-text">
    <button class="pf-close" type="button" aria-label="Close popup">
      <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true"><path d="M1 1l12 12M13 1L1 13" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
    </button>
    <div class="pf-body">
      <h2 class="pf-title" id="pf-popforge-title">Get the good stuff first</h2>
      <p class="pf-text" id="pf-popforge-text">Join 4,200 readers who get one short email each Friday with the best finds, tools and ideas.</p>
    </div>
    <a class="pf-cta" href="https://example.com/newsletter">Join the list</a>
  </div>
</div>

<style>
  #pf-popforge {
    position: fixed;
    inset: 0;
    z-index: 99999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(8, 7, 6, 0.62);
    -webkit-backdrop-filter: blur(3px);
    backdrop-filter: blur(3px);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.25s ease, visibility 0.25s ease;
    font-family: inherit;
  }
  #pf-popforge.is-open {
    opacity: 1;
    visibility: visible;
  }
  #pf-popforge .pf-card {
    position: relative;
    box-sizing: border-box;
    width: 100%;
    max-width: 420px;
    background: #16130F;
    color: #F6F1E7;
    border-radius: 20px;
    padding: 32px;
    box-shadow: 0 30px 70px -20px rgba(0, 0, 0, 0.55);
    overflow: hidden;
    opacity: 0;
    transform: translateY(0) scale(1);
    transition: transform 0.35s cubic-bezier(0.2, 0.9, 0.2, 1), opacity 0.3s ease;
  }
  #pf-popforge[data-animation='scale'] .pf-card { transform: scale(0.94); }
  #pf-popforge[data-animation='slide'] .pf-card { transform: translateY(32px); }
  #pf-popforge.is-open .pf-card {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
  #pf-popforge .pf-body { min-width: 0; }
  #pf-popforge .pf-media {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    object-fit: cover;
    border-radius: 12px;
    margin-bottom: 20px;
  }
  #pf-popforge .pf-title {
    margin: 0 0 10px;
    font-size: 26px;
    line-height: 1.15;
    font-weight: 700;
    letter-spacing: -0.02em;
  }
  #pf-popforge .pf-text {
    margin: 0 0 22px;
    font-size: 15px;
    line-height: 1.6;
    opacity: 0.78;
  }
  #pf-popforge .pf-cta {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 13px 22px;
    border-radius: 14px;
    background: #D9F45C;
    color: #111111;
    font-size: 15px;
    font-weight: 600;
    text-decoration: none;
    transition: transform 0.15s ease, box-shadow 0.15s ease;
  }
  #pf-popforge .pf-cta:hover {
    transform: translateY(-1px);
    box-shadow: 0 10px 24px -12px rgba(0, 0, 0, 0.6);
  }
  #pf-popforge .pf-close {
    position: absolute;
    top: 14px;
    right: 14px;
    display: grid;
    place-items: center;
    width: 32px;
    height: 32px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgba(127, 127, 127, 0.14);
    color: inherit;
    cursor: pointer;
    opacity: 0.7;
    transition: opacity 0.15s ease;
  }
  #pf-popforge .pf-close:hover { opacity: 1; }
  #pf-popforge[data-position='bottom-right'] { align-items: flex-end; justify-content: flex-end; }
  #pf-popforge[data-position='bottom-center'] { align-items: flex-end; justify-content: center; }
  #pf-popforge[data-position='top-bar'] { align-items: flex-start; padding: 0; }
  #pf-popforge[data-position='top-bar'] .pf-card {
    max-width: 100%;
    display: flex;
    align-items: center;
    gap: 20px;
    border-radius: 0 0 20px 20px;
    padding: 18px 56px 18px 24px;
  }
  #pf-popforge[data-position='top-bar'] .pf-body { flex: 1; }
  #pf-popforge[data-position='top-bar'] .pf-media { display: none; }
  #pf-popforge[data-position='top-bar'] .pf-title { font-size: 18px; margin-bottom: 4px; }
  #pf-popforge[data-position='top-bar'] .pf-text { margin: 0; font-size: 14px; }
  #pf-popforge[data-position='top-bar'] .pf-cta { flex: none; padding: 11px 18px; }
  @media (max-width: 520px) {
    #pf-popforge { padding: 16px; }
    #pf-popforge .pf-card { padding: 24px; }
    #pf-popforge .pf-title { font-size: 22px; }
    #pf-popforge[data-position='top-bar'] .pf-card { flex-wrap: wrap; gap: 12px; }
  }
  @media (prefers-reduced-motion: reduce) {
    #pf-popforge, #pf-popforge .pf-card { transition: none; }
  }
</style>

<script>
(function () {
  var overlay = document.getElementById('pf-popforge');
  if (!overlay) return;

  var storageKey = 'pf-popforge-seen';
  var frequency = 'session';
  var lockScroll = true;
  var closeOnOverlay = true;
  var lastFocus = null;

  function alreadySeen() {
    if (frequency === 'always') return false;
    try {
      if (frequency === 'once') return !!localStorage.getItem(storageKey);
      return !!sessionStorage.getItem(storageKey);
    } catch (error) {
      return false;
    }
  }

  function markSeen() {
    try {
      if (frequency === 'once') localStorage.setItem(storageKey, '1');
      if (frequency === 'session') sessionStorage.setItem(storageKey, '1');
    } catch (error) {
      /* storage can be blocked, the popup still works */
    }
  }

  function openPopup() {
    if (alreadySeen() || overlay.classList.contains('is-open')) return;
    lastFocus = document.activeElement;
    overlay.classList.add('is-open');
    overlay.setAttribute('aria-hidden', 'false');
    if (lockScroll) document.body.style.overflow = 'hidden';
    markSeen();
    var focusable = overlay.querySelector('.pf-cta, .pf-close');
    if (focusable) focusable.focus({ preventScroll: true });
  }

  function closePopup() {
    overlay.classList.remove('is-open');
    overlay.setAttribute('aria-hidden', 'true');
    if (lockScroll) document.body.style.overflow = '';
    if (lastFocus && lastFocus.focus) lastFocus.focus({ preventScroll: true });
  }

  overlay.querySelectorAll('.pf-close').forEach(function (button) {
    button.addEventListener('click', closePopup);
  });

  if (closeOnOverlay) {
    overlay.addEventListener('click', function (event) {
      if (event.target === overlay) closePopup();
    });
  }

  document.addEventListener('keydown', function (event) {
    if (event.key === 'Escape') closePopup();
  });

  if (alreadySeen()) return;

  window.setTimeout(openPopup, 4000);
})();
</script>