Hand decorate your website

Place stuff anywhere

try now

Let the editor see your site's height

The editor draws inside a frame over your site. If your site lives on a different domain, the browser won't let the editor measure it directly — that's a security rule, not a bug. Paste this snippet into your site and it reports its own height instead, so the drawing canvas always matches your real page.

<script>
(function () {
  var REF = 900;
  var framed = window.parent !== window;
  function rewrite(t) {
    return t.replace(/(-?\d*\.?\d+)(dvh|svh|lvh|vh)\b/gi, function (_, n) {
      return (parseFloat(n) * REF / 100) + "px";
    });
  }
  function walk(rules) {
    for (var i = 0; i < rules.length; i++) {
      var r = rules[i];
      if (r.style && r.style.cssText && /vh\b/i.test(r.style.cssText)) {
        try { r.style.cssText = rewrite(r.style.cssText); } catch (e) {}
      }
      if (r.cssRules) walk(r.cssRules);
    }
  }
  // Only when framed (i.e. inside the npression editor): pin viewport-height
  // units to a fixed reference so the page's height stops depending on the
  // frame size and can be measured without feeding back. A normal visitor
  // viewing the site directly is never framed, so their layout is untouched.
  function neutralize() {
    try {
      for (var i = 0; i < document.styleSheets.length; i++) {
        var rules;
        try { rules = document.styleSheets[i].cssRules; } catch (e) { continue; }
        if (rules) walk(rules);
      }
    } catch (e) {}
    try {
      var els = document.querySelectorAll('[style*="vh"]');
      for (var j = 0; j < els.length; j++) {
        var s = els[j].getAttribute("style");
        if (s) els[j].setAttribute("style", rewrite(s));
      }
    } catch (e) {}
  }
  function reportHeight() {
    if (framed) neutralize();
    var height = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
    window.parent.postMessage({ source: "npres-viewpoint", height: height }, "*");
  }
  reportHeight();
  new ResizeObserver(reportHeight).observe(document.documentElement);
})();
</script>

Using an AI coding assistant

Paste the snippet above into your assistant's chat along with a short prompt, and let it place the code for you:

Add this script to my site (in <head>, or just before </body>) so it reports the page's height to whatever page has framed it:

<script>
(function () {
  var REF = 900;
  var framed = window.parent !== window;
  function rewrite(t) {
    return t.replace(/(-?\d*\.?\d+)(dvh|svh|lvh|vh)\b/gi, function (_, n) {
      return (parseFloat(n) * REF / 100) + "px";
    });
  }
  function walk(rules) {
    for (var i = 0; i < rules.length; i++) {
      var r = rules[i];
      if (r.style && r.style.cssText && /vh\b/i.test(r.style.cssText)) {
        try { r.style.cssText = rewrite(r.style.cssText); } catch (e) {}
      }
      if (r.cssRules) walk(r.cssRules);
    }
  }
  // Only when framed (i.e. inside the npression editor): pin viewport-height
  // units to a fixed reference so the page's height stops depending on the
  // frame size and can be measured without feeding back. A normal visitor
  // viewing the site directly is never framed, so their layout is untouched.
  function neutralize() {
    try {
      for (var i = 0; i < document.styleSheets.length; i++) {
        var rules;
        try { rules = document.styleSheets[i].cssRules; } catch (e) { continue; }
        if (rules) walk(rules);
      }
    } catch (e) {}
    try {
      var els = document.querySelectorAll('[style*="vh"]');
      for (var j = 0; j < els.length; j++) {
        var s = els[j].getAttribute("style");
        if (s) els[j].setAttribute("style", rewrite(s));
      }
    } catch (e) {}
  }
  function reportHeight() {
    if (framed) neutralize();
    var height = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight);
    window.parent.postMessage({ source: "npres-viewpoint", height: height }, "*");
  }
  reportHeight();
  new ResizeObserver(reportHeight).observe(document.documentElement);
})();
</script>

Editing the code yourself

Paste the snippet into every page you want to preview in the editor — right before the closing </body> tag is simplest, though it also works in <head>. Save and deploy as usual; there's no build step beyond that, and nothing else on the page needs to change.

Why overlays can drift on full-height layouts

This very landing page is a live example of the problem. Pages built with full-viewport sections — a hero using min-height: 100vh — place everything below that section at a spot that depends on the visitor's screen height. A hero that's one screen tall is 900px on your monitor but 850px on a smaller laptop, which shifts everything under it by that same 50px.

Because your drawings are pinned to exact positions, that shift makes them land in the wrong place for anyone whose screen isn't the same height as yours — the bigger the difference, the bigger the drift. It only affects content sitting below a viewport-height section; ordinary pages are unaffected.

I'm already working on solving this automatically, by anchoring drawings to the page's own elements rather than to fixed positions. Until that ships, you can make any page overlay-stable today by giving its full-viewport sections a fixed height instead.

Using an AI coding assistant

Paste this into your assistant's chat:

Find every section on my site that sizes itself to the viewport height (height: 100vh, min-height: 100vh, or the dvh/svh/lvh variants) and change it so its height no longer depends on the screen size. The only hard rule is that the result must not use any viewport-height unit — anything else is fine, so use your judgment on what preserves the design best: a fixed pixel height, letting the content define the height, or whatever fits each section. The goal is that content below these sections lands at the same position on every screen, so drawn overlays stay put.

Editing the code yourself

Find the CSS on your full-height sections and swap the viewport height for a fixed one:

.hero {
  min-height: 900px; /* was: min-height: 100vh */
}

The selector depends on your own markup — apply the same change to whichever sections stretch to the full screen height.