← quik.run
NP@naman-parashar-s-team / Startup idea validatorPublic

Startup idea validator

A JS snippet on QuikRun, callable at quik.run/r/idea-001.

JS· node:20· 3 runs· 0 forks· updated 13h ago
Endpointquik.run/r/idea-001RunFork
Startup idea validator.js
// idea-001 — Startup Idea Validator (pure SSR prototype, no client JS, no fetch)
 
const BRAND = {
bg: "#0b0b0c",
surface: "#111214",
border: "#23252a",
text: "#eaeaea",
muted: "#6b6f76",
accent: "#E6FF55",
};
 
const DIMENSIONS = [
{ key: "market", label: "Market Pull", blurb: "How badly does the world already want this?" },
{ key: "moat", label: "Defensibility", blurb: "What stops a weekend clone from eating you?" },
{ key: "clarity", label: "Pitch Clarity", blurb: "Can a stranger repeat it back in one breath?" },
{ key: "timing", label: "Timing", blurb: "Why now and not in 2019 or 2031?" },
{ key: "wedge", label: "Wedge", blurb: "The tiny painful thing you win first." },
{ key: "gtm", label: "Go-To-Market", blurb: "A channel that scales without a sales army." },
];
 
const VERDICTS = [
{ min: 88, tag: "FUNDABLE", line: "This one has legs. Ship the waitlist tonight." },
{ min: 74, tag: "PROMISING", line: "Sharp idea. Cut one feature and it sings." },
{ min: 58, tag: "NEEDS A WEDGE", line: "Good bones, fuzzy entry point. Narrow it." },
{ min: 40, tag: "TOO EARLY", line: "Interesting, but the market is still asleep." },
{ min: 0, tag: "BACK TO THE WHITEBOARD", line: "Charming. Also currently a hobby, not a company." },
];
 
const SAMPLE_IDEAS = [
"AI that turns your gym selfies into a personalized meal plan",
"A marketplace for renting houseplants by the season",
"Carbon-negative concrete for backyard patios",
"Slack but every message self-destructs after you read it",
"On-demand notary that shows up on a scooter",
"A dating app that only matches people during power outages",
];
 
// deterministic hash so the same idea always scores the same
function hashString(str) {
let h = 2166136261;
for (let i = 0; i < str.length; i++) {
h ^= str.charCodeAt(i);
h = Math.imul(h, 16777619);
}
return h >>> 0;
}
 
function seeded(seed) {
let s = seed >>> 0 || 1;
return function () {
s ^= s << 13; s >>>= 0;
s ^= s >> 17;
s ^= s << 5; s >>>= 0;
return s / 4294967296;
};
}
 
function esc(v) {
return String(v ?? "")
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
 
function clampScore(n) {
return Math.max(28, Math.min(97, Math.round(n)));
}
 
function scoreIdea(idea) {
const seed = hashString(idea.toLowerCase().trim() || "empty");
const rand = seeded(seed);
const words = idea.trim().split(/\s+/).filter(Boolean);
const hasAI = /\b(ai|ml|agent|gpt|llm|model)\b/i.test(idea);
const hasMarket = /\b(market|marketplace|platform|network|community)\b/i.test(idea);
const tooLong = words.length > 14;
const tooShort = words.length < 3;
 
const dims = DIMENSIONS.map((d) => {
let base = 42 + rand() * 52;
if (d.key === "market" && hasMarket) base += 10;
if (d.key === "moat" && hasAI) base -= 8;
if (d.key === "clarity" && tooLong) base -= 14;
if (d.key === "clarity" && tooShort) base -= 10;
if (d.key === "timing" && hasAI) base += 9;
return { ...d, score: clampScore(base) };
});
 
const overall = Math.round(dims.reduce((a, d) => a + d.score, 0) / dims.length);
const verdict = VERDICTS.find((v) => overall >= v.min) || VERDICTS[VERDICTS.length - 1];
 
const waitlist = 300 + (seed % 8700);
const investors = 3 + (seed % 14);
const runwayWeeks = 6 + (seed % 20);
 
return { dims, overall, verdict, waitlist, investors, runwayWeeks };
}
 
function barColor(score) {
if (score >= 75) return "#E6FF55";
if (score >= 55) return "#c5df6a";
return "#7d8270";
}
 
function ring(overall) {
const r = 54;
const c = 2 * Math.PI * r;
const filled = c * (overall / 100);
return { r, c, filled };
}
 
function page(idea, data) {
const { dims, overall, verdict, waitlist, investors, runwayWeeks } = data;
const ringData = ring(overall);
const safeIdea = esc(idea);
 
const rows = dims
.map((d, i) => {
const delay = 0.2 + i * 0.08;
const color = barColor(d.score);
const idx = String(i + 1).padStart(2, "0");
return `
<div class="row">
<div class="row-head">
<span class="row-label"><em class="row-idx">${idx}</em>${esc(d.label)}</span>
<span class="row-score" style="color:${color}">${d.score}</span>
</div>
<div class="track">
<div class="fill" style="--w:${d.score}%;--c:${color};--d:${delay}s"></div>
</div>
<div class="row-blurb">${esc(d.blurb)}</div>
</div>`;
})
.join("");
 
const chips = SAMPLE_IDEAS.map(
(s) => `<a class="chip" href="/?idea=${encodeURIComponent(s)}">${esc(s)}</a>`
).join("");
 
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark" />
<title>Idea Validator · ${safeIdea}</title>
<style>
:root{
--bg:${BRAND.bg}; --surface:${BRAND.surface}; --border:${BRAND.border};
--text:${BRAND.text}; --muted:${BRAND.muted}; --accent:${BRAND.accent};
--mono:ui-monospace,"SF Mono",Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
margin:0;
background:
radial-gradient(1100px 620px at 82% -12%, rgba(230,255,85,.10), transparent 58%),
radial-gradient(820px 560px at -8% 108%, rgba(230,255,85,.05), transparent 56%),
var(--bg);
color:var(--text);
font-family:system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
line-height:1.5;min-height:100vh;
}
.wrap{max-width:940px;margin:0 auto;padding:clamp(40px,7vw,72px) 22px 88px}
 
/* ---- hero ---- */
.kicker{
font-family:var(--mono);font-size:11px;letter-spacing:.24em;text-transform:uppercase;
color:var(--accent);display:inline-flex;align-items:center;gap:9px;
border:1px solid rgba(230,255,85,.24);background:rgba(230,255,85,.05);
padding:6px 12px;border-radius:999px;
}
.kicker .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);
box-shadow:0 0 10px 1px rgba(230,255,85,.75)}
h1{font-size:clamp(32px,6vw,52px);line-height:1.02;margin:20px 0 12px;
font-weight:700;letter-spacing:-.03em}
h1 .hl{color:var(--accent);position:relative}
.sub{color:var(--muted);font-size:clamp(14px,1.6vw,16px);max-width:580px;margin:0}
 
/* ---- form ---- */
form{margin:30px 0 0}
.field{
display:flex;gap:10px;background:var(--surface);border:1px solid var(--border);
border-radius:16px;padding:8px 8px 8px 18px;align-items:center;
box-shadow:0 1px 0 rgba(255,255,255,.03) inset, 0 20px 50px -34px rgba(0,0,0,.9);
transition:border-color .18s ease, box-shadow .18s ease;
}
.field:focus-within{border-color:rgba(230,255,85,.55);
box-shadow:0 0 0 3px rgba(230,255,85,.12), 0 20px 50px -34px rgba(0,0,0,.9)}
.field .glyph{color:var(--muted);flex:0 0 auto;display:flex}
.field input{
flex:1;background:transparent;border:0;outline:0;color:var(--text);
font-size:15px;font-family:inherit;min-width:0;padding:6px 0;
}
.field input::placeholder{color:#4c5057}
.field button{
border:0;border-radius:11px;padding:12px 20px;cursor:pointer;
background:var(--accent);color:#0b0b0c;font-weight:650;font-size:14px;
font-family:inherit;white-space:nowrap;letter-spacing:.01em;
transition:transform .08s ease, box-shadow .18s ease;
box-shadow:0 0 0 0 rgba(230,255,85,0);
}
.field button:hover{box-shadow:0 8px 24px -10px rgba(230,255,85,.55)}
.field button:active{transform:translateY(1px)}
 
.chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:16px}
.chips-label{font-family:var(--mono);font-size:10px;letter-spacing:.18em;
text-transform:uppercase;color:#4c5057;align-self:center;margin-right:2px}
.chip{
font-size:12px;color:var(--muted);text-decoration:none;
border:1px solid var(--border);background:rgba(255,255,255,.018);
padding:7px 12px;border-radius:999px;transition:all .15s ease;
}
.chip:hover{color:var(--text);border-color:rgba(230,255,85,.4);
background:rgba(230,255,85,.06)}
 
/* ---- result card ---- */
.card{
margin-top:38px;background:
linear-gradient(180deg, rgba(255,255,255,.02), transparent 30%),
var(--surface);
border:1px solid var(--border);border-radius:24px;padding:clamp(20px,3.4vw,30px);
box-shadow:0 30px 80px -40px rgba(0,0,0,.95);position:relative;overflow:hidden;
}
.card::before{content:"";position:absolute;inset:0;pointer-events:none;
background:radial-gradient(420px 220px at 88% 0%, rgba(230,255,85,.06), transparent 70%)}
 
.verdict{
display:flex;gap:clamp(20px,4vw,32px);align-items:center;flex-wrap:wrap;
padding-bottom:26px;border-bottom:1px solid var(--border);position:relative;
}
.gauge{position:relative;width:140px;height:140px;flex:0 0 auto}
.gauge svg{transform:rotate(-90deg)}
.gauge .num{position:absolute;inset:0;display:flex;flex-direction:column;
align-items:center;justify-content:center}
.gauge .num b{font-size:42px;font-weight:700;font-family:var(--mono);
font-variant-numeric:tabular-nums;letter-spacing:-.03em;line-height:1;color:var(--text)}
.gauge .num small{font-size:10px;color:var(--muted);letter-spacing:.2em;
text-transform:uppercase;margin-top:7px}
.verdict-text{flex:1;min-width:230px}
.tag{display:inline-flex;align-items:center;gap:7px;font-family:var(--mono);
font-size:11px;letter-spacing:.18em;font-weight:600;color:var(--accent);
border:1px solid rgba(230,255,85,.42);background:rgba(230,255,85,.08);
padding:6px 11px;border-radius:8px}
.tag .spark{width:5px;height:5px;border-radius:50%;background:var(--accent);
box-shadow:0 0 8px 1px rgba(230,255,85,.7)}
.verdict-text p{margin:15px 0 0;font-size:clamp(18px,2.4vw,21px);line-height:1.32;
letter-spacing:-.015em;color:var(--text)}
.idea-echo{margin-top:12px;color:var(--muted);font-size:13px;font-style:italic;
padding-left:12px;border-left:2px solid var(--border);line-height:1.45}
 
/* ---- dimension rows ---- */
.rows{margin-top:28px;display:grid;gap:20px}
.row-head{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:9px}
.row-label{font-size:14px;font-weight:560;display:flex;align-items:baseline;gap:10px}
.row-idx{font-family:var(--mono);font-style:normal;font-size:11px;color:#4c5057;
letter-spacing:.05em}
.row-score{font-family:var(--mono);font-variant-numeric:tabular-nums;
font-size:15px;font-weight:600}
.track{height:9px;border-radius:999px;background:#17181b;overflow:hidden;
border:1px solid rgba(255,255,255,.035)}
.fill{height:100%;width:0;border-radius:999px;background:var(--c);
box-shadow:0 0 14px -2px var(--c);
animation:grow 1.1s cubic-bezier(.16,.84,.3,1) forwards;animation-delay:var(--d)}
@keyframes grow{to{width:var(--w)}}
.row-blurb{margin-top:8px;font-size:12.5px;color:var(--muted);line-height:1.45}
 
/* ---- stats ---- */
.stats{margin-top:30px;display:grid;gap:12px;grid-template-columns:repeat(3,1fr)}
.stat{background:rgba(255,255,255,.018);border:1px solid var(--border);
border-radius:15px;padding:16px 17px}
.stat b{display:block;font-size:24px;font-weight:700;font-family:var(--mono);
font-variant-numeric:tabular-nums;letter-spacing:-.02em;line-height:1}
.stat span{font-size:10px;color:var(--muted);letter-spacing:.14em;
text-transform:uppercase;margin-top:9px;display:block}
.stat.live b{color:var(--accent)}
.live-dot{display:inline-block;width:7px;height:7px;border-radius:50%;
background:var(--accent);margin-right:8px;vertical-align:middle;
animation:pulse 1.6s ease-in-out infinite;box-shadow:0 0 8px 1px rgba(230,255,85,.7)}
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.32}}
 
footer{margin-top:44px;color:var(--muted);font-size:12px;text-align:center;
font-family:var(--mono);letter-spacing:.05em}
footer .q{color:var(--accent);font-weight:700}
footer .sep{opacity:.4;margin:0 8px}
 
@media (prefers-reduced-motion: reduce){
.fill{animation:none;width:var(--w)}
.live-dot{animation:none}
circle[stroke-dashoffset]{animation:none !important}
}
@media(max-width:560px){
.stats{grid-template-columns:1fr}
.field{flex-direction:column;align-items:stretch;padding:14px}
.field .glyph{display:none}
.field input{padding:2px 0}
.field button{width:100%;padding:14px}
.verdict{justify-content:center;text-align:center;flex-direction:column}
.idea-echo{text-align:left}
}
</style>
</head>
<body>
<div class="wrap">
<span class="kicker"><span class="dot"></span> Prototype · Idea Validator</span>
<h1>Prove the idea<br/>before you open an <span class="hl">IDE</span>.</h1>
<p class="sub">Paste a startup idea. Get a brutally quick read across six dimensions,
an investor-flavored verdict, and pretend momentum you have not earned yet.</p>
 
<form method="get" action="/">
<div class="field">
<span class="glyph" aria-hidden="true">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 3a5 5 0 0 0-3 9c.6.5 1 1.2 1 2v1h4v-1c0-.8.4-1.5 1-2a5 5 0 0 0-3-9Z"/>
<path d="M10 19h4M10.5 22h3"/>
</svg>
</span>
<input name="idea" value="${safeIdea}" placeholder="e.g. AI that turns whiteboard photos into working prototypes" autofocus autocomplete="off" />
<button type="submit">Validate</button>
</div>
</form>
<div class="chips">
<span class="chips-label">try</span>
${chips}
</div>
 
<div class="card">
<div class="verdict">
<div class="gauge">
<svg width="140" height="140" viewBox="0 0 140 140">
<circle cx="70" cy="70" r="${ringData.r}" fill="none" stroke="#1c1d21" stroke-width="11" />
<circle cx="70" cy="70" r="${ringData.r}" fill="none"
stroke="${BRAND.accent}" stroke-width="11" stroke-linecap="round"
stroke-dasharray="${ringData.c.toFixed(1)}"
stroke-dashoffset="${ringData.c.toFixed(1)}"
style="filter:drop-shadow(0 0 7px rgba(230,255,85,.55));animation:ringfill 1.4s cubic-bezier(.16,.84,.3,1) .25s forwards" />
</svg>
<div class="num"><b>${overall}</b><small>Score</small></div>
</div>
<div class="verdict-text">
<span class="tag"><span class="spark"></span>${esc(verdict.tag)}</span>
<p>${esc(verdict.line)}</p>
<div class="idea-echo">&ldquo;${safeIdea}&rdquo;</div>
</div>
</div>
 
<div class="rows">${rows}</div>
 
<div class="stats">
<div class="stat live">
<b><span class="live-dot"></span>${waitlist.toLocaleString("en-US")}</b>
<span>Waitlist (imaginary)</span>
</div>
<div class="stat">
<b>${investors}</b>
<span>Warm intros</span>
</div>
<div class="stat">
<b>${runwayWeeks}w</b>
<span>Vibe runway</span>
</div>
</div>
</div>
 
<footer>
Rendered on the edge by <span class="q">q.</span><span class="sep">·</span>QuikRun<span class="sep">·</span>no real VCs were harmed
</footer>
</div>
<style>@keyframes ringfill{to{stroke-dashoffset:${(ringData.c - ringData.filled).toFixed(1)}}}</style>
</body>
</html>`;
}
 
export default async function handler(req, env) {
const raw = (req.query && req.query.idea) || "";
const idea =
String(raw).trim() ||
SAMPLE_IDEAS[Math.floor(Date.now() / 3600000) % SAMPLE_IDEAS.length];
const data = scoreIdea(idea);
const html = page(idea, data);
return new Response(html, {
status: 200,
headers: { "content-type": "text/html; charset=utf-8" },
});
}
Fork this and make it yours

Describe a change in plain English and QuikRun deploys it to your own URL.

Fork snippet