Vibe-check your app: a tiny JS validator for vibe-coded pages

December 4, 2025

This vibe-check tool runs a quick validation pass on the current page and tells you how vibe-coded it looks. It doesn’t try to be a full audit – just a fast gut-check for:

  • Heavy front-end frameworks (React, Angular, Vue, jQuery)
  • Script bloat and external script count
  • DOM size (too many nodes = likely bloat)
  • Common third-party trackers (GA, GTM, Meta, DoubleClick, etc.)

When you drop it in, it adds a small overlay in the corner with a score (0–5) and a checklist. It’s meant as a quick “does this feel like a vibe-coded app?” validator, not a performance lab.

<style>
  /* Vibe-check overlay: tiny validation panel in the corner */

  .vibe-check-panel {
    position: fixed;
    inset-block-start: 12px;
    inset-inline-end: 12px;
    z-index: 99999;
    max-width: 280px;
    background: #111;
    color: #f8f8f8;
    font: 12px/1.5 system-ui, -apple-system, BlinkMacSystemFont,
          "Segoe UI", sans-serif;
    border-radius: 8px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
    padding: 10px 12px 10px;
  }

  .vibe-check-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 6px;
  }

  .vibe-check-title {
    font-weight: 600;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    opacity: 0.9;
  }

  .vibe-check-score {
    font-size: 11px;
    padding: 2px 6px;
    border-radius: 999px;
    background: #1b4332;
    color: #b7ffcf;
    font-weight: 600;
    white-space: nowrap;
  }

  .vibe-check-score--ok {
    background: #1b4332;
    color: #b7ffcf;
  }

  .vibe-check-score--warn {
    background: #5c3c05;
    color: #ffe5ad;
  }

  .vibe-check-score--bad {
    background: #5b0505;
    color: #ffd6d6;
  }

  .vibe-check-close {
    background: none;
    border: 0;
    color: #aaa;
    cursor: pointer;
    padding: 0 0 0 6px;
    font-size: 14px;
    line-height: 1;
  }

  .vibe-check-close:hover {
    color: #fff;
  }

  .vibe-check-list {
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .vibe-check-item {
    display: flex;
    align-items: flex-start;
    gap: 5px;
    margin: 2px 0;
  }

  .vibe-check-icon {
    font-size: 11px;
    line-height: 1.4;
    flex: 0 0 auto;
  }

  .vibe-check-text {
    font-size: 11px;
    opacity: 0.9;
  }

  .vibe-check-footnote {
    margin-top: 4px;
    font-size: 10px;
    opacity: 0.7;
  }

  @media (max-width: 480px) {
    .vibe-check-panel {
      inset-block-start: auto;
      inset-block-end: 10px;
      inset-inline: 10px;
      max-width: none;
    }
  }
</style>

<script>
  // Vibe-check validator for “vibe-coded” apps
  // Drop this near the end of your <body> in a dev/staging build.
  // It rates the page on a quick checklist and shows a tiny overlay.

  (function () {
    if (document.querySelector(".vibe-check-panel")) return;

    function detectFrameworks() {
      var found = [];

      // Rough, intentionally simple signals
      if (window.React || window.ReactDOM) found.push("React");
      if (window.angular || window.ng) found.push("Angular");
      if (window.Vue) found.push("Vue");
      if (window.jQuery || window.$ && window.$.fn && window.$.fn.jquery) {
        found.push("jQuery");
      }

      // Look at script srcs as a fallback
      var scripts = document.scripts || [];
      var reMap = [
        [/react/i, "React"],
        [/angular/i, "Angular"],
        [/vue/i, "Vue"],
        [/jquery/i, "jQuery"]
      ];

      for (var i = 0; i < scripts.length; i++) {
        var src = scripts[i].src || "";
        if (!src) continue;
        reMap.forEach(function (pair) {
          if (pair[0].test(src) && found.indexOf(pair[1]) === -1) {
            found.push(pair[1]);
          }
        });
      }

      return found;
    }

    function getScriptMetrics() {
      var out = {
        count: (document.scripts || []).length,
        bytes: null
      };

      if (
        typeof performance === "undefined" ||
        typeof performance.getEntriesByType !== "function"
      ) {
        return out;
      }

      try {
        var entries = performance.getEntriesByType("resource") || [];
        var sum = 0;

        for (var i = 0; i < entries.length; i++) {
          var e = entries[i];
          if (e.initiatorType !== "script") continue;

          var size =
            (typeof e.transferSize === "number" && e.transferSize) ||
            (typeof e.encodedBodySize === "number" && e.encodedBodySize) ||
            0;

          sum += size;
        }

        out.bytes = sum || null;
      } catch (e) {
        // ignore
      }

      return out;
    }

    function getDomSize() {
      try {
        return document.getElementsByTagName("*").length;
      } catch (e) {
        return null;
      }
    }

    function detectTrackers() {
      var hosts = [];
      var patterns = [
        /google-analytics\.com/i,
        /googletagmanager\.com/i,
        /gtag\/js/i,
        /facebook\.net/i,
        /connect\.facebook\.net/i,
        /doubleclick\.net/i,
        /hotjar\.com/i,
        /segment\.com/i
      ];

      if (
        typeof performance === "undefined" ||
        typeof performance.getEntriesByType !== "function"
      ) {
        return hosts;
      }

      try {
        var entries = performance.getEntriesByType("resource") || [];
        for (var i = 0; i < entries.length; i++) {
          var name = entries[i].name || "";
          if (!name) continue;
          patterns.forEach(function (re) {
            if (re.test(name)) {
              if (hosts.indexOf(re.source) === -1) {
                hosts.push(re.source);
              }
            }
          });
        }
      } catch (e) {
        // ignore
      }

      return hosts;
    }

    function runChecks() {
      var frameworks = detectFrameworks();
      var scripts = getScriptMetrics();
      var domSize = getDomSize();
      var trackers = detectTrackers();

      var score = 5;
      var checks = [];

      // 1) Frameworks
      if (frameworks.length === 0) {
        checks.push({
          ok: true,
          label: "No big front-end framework detected."
        });
      } else {
        score -= 1;
        checks.push({
          ok: false,
          label: "Frameworks in use: " + frameworks.join(", ") + "."
        });
      }

      // 2) Script count
      var scriptCount = scripts.count;
      if (scriptCount <= 20) {
        checks.push({
          ok: true,
          label: "Script tags look reasonable (" + scriptCount + " total)."
        });
      } else {
        score -= 1;
        checks.push({
          ok: false,
          label: "High number of script tags (" + scriptCount + ")."
        });
      }

      // 3) Script weight (if we could measure it)
      if (scripts.bytes == null) {
        checks.push({
          ok: true,
          label: "Could not measure JS weight (no resource timing)."
        });
      } else {
        var kb = Math.round(scripts.bytes / 1024);
        if (kb <= 200) {
          checks.push({
            ok: true,
            label: "Total JS transfer ~" + kb + " KB (nice and light)."
          });
        } else {
          score -= 1;
          checks.push({
            ok: false,
            label: "Total JS transfer ~" + kb + " KB (getting heavy)."
          });
        }
      }

      // 4) DOM size
      if (domSize == null) {
        checks.push({
          ok: true,
          label: "DOM size not measured."
        });
      } else if (domSize <= 1000) {
        checks.push({
          ok: true,
          label: "DOM nodes: " + domSize + " (looks lean)."
        });
      } else {
        score -= 1;
        checks.push({
          ok: false,
          label: "DOM nodes: " + domSize + " (could be bloated)."
        });
      }

      // 5) Trackers
      if (trackers.length === 0) {
        checks.push({
          ok: true,
          label: "No common tracker domains spotted."
        });
      } else {
        score -= 1;
        checks.push({
          ok: false,
          label: "Trackers detected (rough): " + trackers.join(", ") + "."
        });
      }

      // Clamp score
      if (score < 0) score = 0;
      if (score > 5) score = 5;

      return {
        score: score,
        maxScore: 5,
        checks: checks
      };
    }

    function renderPanel(result) {
      var panel = document.createElement("aside");
      panel.className = "vibe-check-panel";

      var header = document.createElement("div");
      header.className = "vibe-check-header";

      var title = document.createElement("div");
      title.className = "vibe-check-title";
      title.textContent = "Vibe-check validator";

      var score = document.createElement("div");
      var scoreClass = "vibe-check-score";
      if (result.score >= 4) {
        scoreClass += " vibe-check-score--ok";
      } else if (result.score >= 2) {
        scoreClass += " vibe-check-score--warn";
      } else {
        scoreClass += " vibe-check-score--bad";
      }
      score.className = scoreClass;
      score.textContent =
        "Score: " + result.score + "/" + result.maxScore;

      var close = document.createElement("button");
      close.type = "button";
      close.className = "vibe-check-close";
      close.innerHTML = "&times;";
      close.setAttribute("aria-label", "Close vibe-check panel");
      close.addEventListener("click", function () {
        panel.remove();
      });

      header.appendChild(title);
      header.appendChild(score);
      header.appendChild(close);

      var list = document.createElement("ul");
      list.className = "vibe-check-list";

      result.checks.forEach(function (item) {
        var li = document.createElement("li");
        li.className = "vibe-check-item";

        var icon = document.createElement("span");
        icon.className = "vibe-check-icon";
        icon.textContent = item.ok ? "✔" : "⚠";

        var text = document.createElement("span");
        text.className = "vibe-check-text";
        text.textContent = item.label;

        li.appendChild(icon);
        li.appendChild(text);
        list.appendChild(li);
      });

      var foot = document.createElement("div");
      foot.className = "vibe-check-footnote";
      foot.textContent =
        "Rough heuristic only. Use this as a gut-check, not a full audit.";

      panel.appendChild(header);
      panel.appendChild(list);
      panel.appendChild(foot);

      document.body.appendChild(panel);
    }

    function attach() {
      if (!document.body) {
        setTimeout(attach, 40);
        return;
      }
      var result = runChecks();
      renderPanel(result);
    }

    if (document.readyState === "complete" || document.readyState === "interactive") {
      attach();
    } else {
      document.addEventListener("DOMContentLoaded", attach);
    }
  })();
</script>

How to use it

Include the whole snippet (the <style> and <script> block) near the end of your <body> on a dev or staging build of your app. Reload the page and you’ll see a small “Vibe-check validator” panel in the corner with:

  • A 0–5 score
  • Whether big frameworks are present
  • Script tag count and approximate JS weight (if available)
  • DOM node count
  • A rough pass at common tracking domains

What “vibe-coded” means here

This tool assumes a “vibe-coded” app tries to stay:

  • Small: not shipping hundreds of KB of JS just to render a simple page
  • Focused: only uses big frameworks when they’re actually needed
  • Light: DOM isn’t overflowing with unnecessary wrappers and divs
  • Respectful: not packed with third-party trackers on every view

You can absolutely ignore a “fail” if you know you’re making a deliberate trade-off – this panel is meant as a conversation starter, not a guilt trip.

Customizing the checks

Inside the script, tweak:

  • The patterns array in detectTrackers() to match or ignore services you care about.
  • Thresholds for DOM size, script count, and JS KB if your projects naturally run a bit heavier or lighter than the defaults.
  • The scoring rules if you want different weights (for example, subtract 2 points when trackers are present).

Drop it into your next side-project or refactor as a tiny “is this still vibe-coded?” sanity check whenever you feel the UI starting to creak under extra dependencies.

Comments (0)

No comments yet — be the first.

← Back to all scripts