Vibe-check your app: a tiny JS validator for vibe-coded pages
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 = "×";
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
patternsarray indetectTrackers()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.