0mij's picture
Update index.html
9e0dc04 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CodeSignal Interview Prompt Generator</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0c10;
--surface: #111318;
--surface2: #191d24;
--border: #252b36;
--accent: #f97316;
--accent2: #fb923c;
--green: #22c55e;
--blue: #38bdf8;
--purple: #a78bfa;
--red: #f87171;
--text: #e2e8f0;
--muted: #64748b;
--heading: #f8fafc;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: var(--bg);
color: var(--text);
font-family: 'Syne', sans-serif;
min-height: 100vh;
overflow-x: hidden;
}
/* GRID BACKGROUND */
body::before {
content: '';
position: fixed;
inset: 0;
background-image:
linear-gradient(rgba(249,115,22,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(249,115,22,0.03) 1px, transparent 1px);
background-size: 48px 48px;
pointer-events: none;
z-index: 0;
}
/* NOISE OVERLAY */
body::after {
content: '';
position: fixed;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.03'/%3E%3C/svg%3E");
pointer-events: none;
z-index: 0;
opacity: 0.4;
}
.container {
position: relative;
z-index: 1;
max-width: 1100px;
margin: 0 auto;
padding: 0 24px 80px;
}
/* HEADER */
header {
padding: 48px 0 40px;
border-bottom: 1px solid var(--border);
margin-bottom: 48px;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 24px;
}
.brand {
display: flex;
flex-direction: column;
gap: 6px;
}
.brand-tag {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--accent);
letter-spacing: 0.2em;
text-transform: uppercase;
}
h1 {
font-size: clamp(28px, 5vw, 44px);
font-weight: 800;
color: var(--heading);
line-height: 1.1;
letter-spacing: -0.02em;
}
h1 span {
color: var(--accent);
}
.header-meta {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--muted);
text-align: right;
line-height: 1.8;
}
/* MAIN LAYOUT */
.layout {
display: grid;
grid-template-columns: 340px 1fr;
gap: 32px;
align-items: start;
/* Prevent children from blowing out the grid */
width: 100%;
}
/* Critical: grid children must not exceed their track */
.layout > * {
min-width: 0;
width: 100%;
}
@media (max-width: 768px) {
.layout { grid-template-columns: 1fr; }
header { flex-direction: column; align-items: flex-start; }
.header-meta { text-align: left; }
}
/* SIDEBAR / CONFIG PANEL */
.panel {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
padding: 28px;
position: sticky;
top: 24px;
}
.panel-title {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--accent);
letter-spacing: 0.18em;
text-transform: uppercase;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 8px;
}
.panel-title::before {
content: '//';
opacity: 0.5;
}
.field {
margin-bottom: 20px;
}
label {
display: block;
font-size: 12px;
font-weight: 600;
color: var(--muted);
letter-spacing: 0.08em;
text-transform: uppercase;
margin-bottom: 8px;
}
input, select, textarea {
width: 100%;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
padding: 11px 14px;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
appearance: none;
}
input::placeholder, textarea::placeholder { color: var(--muted); }
input:focus, select:focus, textarea:focus {
border-color: var(--accent);
box-shadow: 0 0 0 3px rgba(249,115,22,0.12);
}
select {
cursor: pointer;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2364748b' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
textarea { resize: vertical; min-height: 80px; line-height: 1.5; }
.api-key-wrap {
position: relative;
}
.api-key-wrap input { padding-right: 44px; }
.toggle-vis {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: var(--muted);
padding: 4px;
display: flex;
align-items: center;
transition: color 0.2s;
}
.toggle-vis:hover { color: var(--text); }
/* GENERATE BUTTON */
.btn-generate {
width: 100%;
background: var(--accent);
color: #fff;
border: none;
border-radius: 8px;
font-family: 'Syne', sans-serif;
font-weight: 700;
font-size: 14px;
letter-spacing: 0.05em;
text-transform: uppercase;
padding: 14px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: background 0.2s, transform 0.1s;
margin-top: 8px;
}
.btn-generate:hover { background: var(--accent2); }
.btn-generate:active { transform: scale(0.98); }
.btn-generate:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
.btn-generate .spinner {
display: none;
width: 16px;
height: 16px;
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
animation: spin 0.7s linear infinite;
margin-right: 8px;
}
.btn-generate.loading .spinner { display: inline-block; }
.btn-generate.loading .btn-text::before { content: 'Generating'; }
.btn-generate:not(.loading) .btn-text::before { content: '⚡ Generate Prompt'; }
@keyframes spin { to { transform: rotate(360deg); } }
.btn-generate-inner {
display: flex;
align-items: center;
justify-content: center;
}
/* DIVIDER */
.divider {
border: none;
border-top: 1px solid var(--border);
margin: 20px 0;
}
.systems-presets {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: -8px;
}
.preset-chip {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
padding: 4px 10px;
border-radius: 20px;
border: 1px solid var(--border);
background: transparent;
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.preset-chip:hover {
border-color: var(--accent);
color: var(--accent);
background: rgba(249,115,22,0.06);
}
/* OUTPUT AREA */
.output-area {
min-height: 500px;
min-width: 0;
width: 100%;
overflow: hidden;
}
.output-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.output-title {
font-size: 18px;
font-weight: 700;
color: var(--heading);
}
.output-actions {
display: flex;
gap: 8px;
}
.btn-sm {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
padding: 7px 14px;
border-radius: 6px;
border: 1px solid var(--border);
background: var(--surface);
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
letter-spacing: 0.05em;
}
.btn-sm:hover { border-color: var(--accent); color: var(--accent); }
/* PLACEHOLDER */
.placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 420px;
border: 1px dashed var(--border);
border-radius: 12px;
gap: 16px;
}
.placeholder-icon {
font-size: 48px;
opacity: 0.3;
}
.placeholder p {
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
color: var(--muted);
text-align: center;
line-height: 1.6;
}
/* LEVEL TABS */
.level-tabs {
display: flex;
gap: 0;
margin-bottom: 24px;
border-bottom: 1px solid var(--border);
}
.level-tab {
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
font-weight: 700;
padding: 10px 20px;
border: none;
background: transparent;
color: var(--muted);
cursor: pointer;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
transition: all 0.15s;
letter-spacing: 0.05em;
}
.level-tab:hover { color: var(--text); }
.level-tab.active {
color: var(--accent);
border-bottom-color: var(--accent);
}
.level-tab .level-badge {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 4px;
font-size: 10px;
margin-right: 6px;
}
.level-tab[data-level="1"] .level-badge { background: rgba(34,197,94,0.15); color: var(--green); }
.level-tab[data-level="2"] .level-badge { background: rgba(56,189,248,0.15); color: var(--blue); }
.level-tab[data-level="3"] .level-badge { background: rgba(167,139,250,0.15); color: var(--purple); }
.level-tab[data-level="4"] .level-badge { background: rgba(249,115,22,0.15); color: var(--accent); }
/* LEVEL CONTENT PANELS */
.level-panel { display: none; }
.level-panel.active { display: block; animation: fadeUp 0.3s ease; }
@keyframes fadeUp {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.level-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
overflow: hidden;
min-width: 0;
width: 100%;
}
.level-card-header {
padding: 20px 24px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: 12px;
min-width: 0;
}
.level-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
[data-level="1"] .level-dot { background: var(--green); box-shadow: 0 0 8px var(--green); }
[data-level="2"] .level-dot { background: var(--blue); box-shadow: 0 0 8px var(--blue); }
[data-level="3"] .level-dot { background: var(--purple); box-shadow: 0 0 8px var(--purple); }
[data-level="4"] .level-dot { background: var(--accent); box-shadow: 0 0 8px var(--accent); }
.level-card-title {
font-size: 15px;
font-weight: 700;
color: var(--heading);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
}
.level-card-subtitle {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--muted);
margin-left: auto;
white-space: nowrap;
flex-shrink: 0;
}
.level-card-body {
padding: 24px;
min-width: 0;
overflow: hidden;
}
/* CONTENT SECTIONS */
.section {
margin-bottom: 24px;
}
.section:last-child { margin-bottom: 0; }
.section-label {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: var(--muted);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 8px;
}
.section-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.section p, .section li {
font-size: 14px;
line-height: 1.75;
color: var(--text);
overflow-wrap: break-word;
word-break: break-word;
}
.section ul {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 6px;
}
.section ul li::before {
content: '→';
color: var(--accent);
margin-right: 10px;
font-family: 'JetBrains Mono', monospace;
}
/* CODE BLOCKS */
pre, .code-block {
background: #060810;
border: 1px solid var(--border);
border-radius: 8px;
padding: 16px 18px;
overflow-x: auto;
font-family: 'JetBrains Mono', monospace;
font-size: 13px;
line-height: 1.65;
color: #c9d1d9;
position: relative;
max-width: 100%;
width: 100%;
box-sizing: border-box;
word-break: normal;
white-space: pre;
}
.code-block-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.code-lang {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--accent);
background: rgba(249,115,22,0.1);
padding: 2px 8px;
border-radius: 4px;
}
.copy-btn {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
padding: 3px 10px;
border-radius: 4px;
border: 1px solid var(--border);
background: var(--surface2);
color: var(--muted);
cursor: pointer;
transition: all 0.15s;
}
.copy-btn:hover { color: var(--green); border-color: var(--green); }
.copy-btn.copied { color: var(--green); border-color: var(--green); }
/* KEYWORD HIGHLIGHTING */
.kw-method { color: #38bdf8; }
.kw-type { color: #a78bfa; }
.kw-string { color: #86efac; }
.kw-comment { color: #475569; font-style: italic; }
.kw-num { color: #fb923c; }
/* SYSTEM INFO BANNER */
.system-banner {
background: linear-gradient(135deg, rgba(249,115,22,0.08), rgba(167,139,250,0.06));
border: 1px solid rgba(249,115,22,0.2);
border-radius: 10px;
padding: 16px 20px;
margin-bottom: 24px;
display: flex;
align-items: center;
gap: 14px;
min-width: 0;
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.system-banner-icon {
font-size: 24px;
}
.system-banner-name {
font-size: 18px;
font-weight: 800;
color: var(--heading);
}
.system-banner-desc {
font-size: 13px;
color: var(--muted);
margin-top: 2px;
}
.difficulty-pills {
margin-left: auto;
display: flex;
gap: 6px;
}
.pill {
font-family: 'JetBrains Mono', monospace;
font-size: 10px;
padding: 3px 10px;
border-radius: 20px;
font-weight: 700;
}
.pill-easy { background: rgba(34,197,94,0.15); color: var(--green); }
.pill-med { background: rgba(56,189,248,0.15); color: var(--blue); }
.pill-hard { background: rgba(167,139,250,0.15); color: var(--purple); }
.pill-expert { background: rgba(249,115,22,0.15); color: var(--accent); }
/* ERROR */
.error-box {
background: rgba(248,113,113,0.08);
border: 1px solid rgba(248,113,113,0.25);
border-radius: 8px;
padding: 14px 18px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
color: var(--red);
margin-bottom: 16px;
}
/* STREAM CURSOR */
.cursor {
display: inline-block;
width: 2px;
height: 1em;
background: var(--accent);
margin-left: 2px;
animation: blink 1s step-end infinite;
vertical-align: text-bottom;
}
@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0} }
#structuredOutput {
min-width: 0;
width: 100%;
overflow: hidden;
}
.level-panel {
min-width: 0;
width: 100%;
}
.raw-view {
display: none;
background: #060810;
border: 1px solid var(--border);
border-radius: 12px;
padding: 24px;
font-family: 'JetBrains Mono', monospace;
font-size: 12px;
line-height: 1.7;
color: #c9d1d9;
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: break-word;
max-height: 600px;
overflow-y: auto;
overflow-x: hidden;
width: 100%;
box-sizing: border-box;
}
.raw-view.visible { display: block; }
/* FOOTER */
footer {
margin-top: 60px;
border-top: 1px solid var(--border);
padding-top: 24px;
display: flex;
align-items: center;
justify-content: space-between;
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
color: var(--muted);
}
footer a { color: var(--accent); text-decoration: none; }
</style>
</head>
<body>
<div class="container">
<header>
<div class="brand">
<div class="brand-tag">// codesignal · oop systems design</div>
<h1>CodeSignal<br><span>Prompt</span> Generator</h1>
</div>
<div class="header-meta">
Powered by GPT-4o<br>
OOP System Design<br>
4-Level Interview Format
</div>
</header>
<div class="layout">
<!-- SIDEBAR -->
<aside>
<div class="panel">
<div class="panel-title">Configuration</div>
<div class="field">
<label>System Name</label>
<input type="text" id="systemName" placeholder="e.g. LRU Cache" />
</div>
<div class="field">
<label>Quick Presets</label>
<div class="systems-presets">
<button class="preset-chip" onclick="setPreset('Time-Based Key-Value Store')">KV Store</button>
<button class="preset-chip" onclick="setPreset('In-Memory Database')">In-Memory DB</button>
<button class="preset-chip" onclick="setPreset('LRU / LFU Cache')">LRU/LFU Cache</button>
<button class="preset-chip" onclick="setPreset('C-Like Memory Allocator')">Allocator</button>
<button class="preset-chip" onclick="setPreset('Type Inference Engine')">Type Inference</button>
<button class="preset-chip" onclick="setPreset('Circuit Breaker')">Circuit Breaker</button>
<button class="preset-chip" onclick="setPreset('API Gateway with Rate Limiting')">API Gateway</button>
<button class="preset-chip" onclick="setPreset('Thread Pool / Task Scheduler')">Thread Pool</button>
<button class="preset-chip" onclick="setPreset('Transaction / Credit System')">Transaction</button>
<button class="preset-chip" onclick="setPreset('Employee Management System')">Employee Mgmt</button>
</div>
</div>
<div class="field">
<label>Brief Description <span style="color:var(--muted);font-weight:400">(optional)</span></label>
<textarea id="systemDesc" placeholder="Any extra context or constraints for this system..."></textarea>
</div>
<hr class="divider">
<div class="field">
<label>OpenAI API Key</label>
<div class="api-key-wrap">
<input type="password" id="apiKey" placeholder="sk-..." autocomplete="off" />
<button class="toggle-vis" onclick="toggleKey()" title="Show/hide key">
<svg id="eyeIcon" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>
</svg>
</button>
</div>
</div>
<div class="field">
<label>Model</label>
<select id="modelSelect">
<option value="gpt-4o">gpt-4o (recommended)</option>
<option value="gpt-4o-mini">gpt-4o-mini (faster)</option>
<option value="gpt-4-turbo">gpt-4-turbo</option>
<option value="gpt-4">gpt-4</option>
</select>
</div>
<button class="btn-generate" id="generateBtn" onclick="generate()">
<div class="btn-generate-inner">
<span class="spinner"></span>
<span class="btn-text"></span>
</div>
</button>
<div style="text-align:center;margin-top:12px">
<button class="btn-sm" style="width:100%;justify-content:center;display:flex" onclick="copyPromptSidebar(this)">⎘ Copy Prompt Only</button>
</div>
</div>
</aside>
<!-- OUTPUT -->
<main class="output-area">
<div class="output-header">
<div class="output-title">Generated Prompt</div>
<div class="output-actions" id="outputActions" style="display:none">
<button class="btn-sm" onclick="copyPrompt(this)">⎘ Copy Prompt</button>
<button class="btn-sm" onclick="toggleRaw()">⊞ Raw</button>
<button class="btn-sm" onclick="copyAll()">⎘ Copy Output</button>
</div>
</div>
<div id="errorBox" class="error-box" style="display:none"></div>
<div id="placeholder" class="placeholder">
<div class="placeholder-icon"></div>
<p>Enter a system name and your OpenAI key,<br>then click Generate to build your interview prompt.</p>
</div>
<!-- STRUCTURED OUTPUT -->
<div id="structuredOutput" style="display:none">
<div class="system-banner" id="systemBanner">
<div class="system-banner-icon" id="bannerIcon">🏗️</div>
<div>
<div class="system-banner-name" id="bannerName"></div>
<div class="system-banner-desc" id="bannerDesc"></div>
</div>
<div class="difficulty-pills">
<span class="pill pill-easy">L1 Easy</span>
<span class="pill pill-med">L2 Medium</span>
<span class="pill pill-hard">L3 Hard</span>
<span class="pill pill-expert">L4 Expert</span>
</div>
</div>
<div class="level-tabs">
<button class="level-tab active" data-level="1" onclick="switchLevel(1)">
<span class="level-badge">1</span>Foundation
</button>
<button class="level-tab" data-level="2" onclick="switchLevel(2)">
<span class="level-badge">2</span>Extended
</button>
<button class="level-tab" data-level="3" onclick="switchLevel(3)">
<span class="level-badge">3</span>Concurrent
</button>
<button class="level-tab" data-level="4" onclick="switchLevel(4)">
<span class="level-badge">4</span>Production
</button>
</div>
<div id="lvl1" class="level-panel active" data-level="1"></div>
<div id="lvl2" class="level-panel" data-level="2"></div>
<div id="lvl3" class="level-panel" data-level="3"></div>
<div id="lvl4" class="level-panel" data-level="4"></div>
</div>
<!-- RAW OUTPUT -->
<div id="rawOutput" class="raw-view"></div>
</main>
</div>
<footer>
<span>CodeSignal-style Interview Prompt Generator · Prompt credits: <a href="https://www.yuan-meng.com/posts/mle_interviews_2.0/" target="_blank">Yuan Meng ↗</a></span>
<span>4-Level OOP Systems Design · <a href="https://platform.openai.com/docs" target="_blank">OpenAI Docs ↗</a></span>
</footer>
</div>
<script>
const LEVEL_DESCRIPTIONS = ['Foundation', 'Extended APIs', 'Concurrency', 'Production-Ready'];
const LEVEL_ICONS = ['🟢', '🔵', '🟣', '🟠'];
const SYSTEM_ICONS = {
'cache': '🗃️', 'store': '📦', 'database': '🗄️', 'memory': '💾',
'allocator': '⚙️', 'type': '🔍', 'circuit': '⚡', 'gateway': '🚦',
'thread': '🧵', 'task': '📋', 'transaction': '💳', 'credit': '💰',
'employee': '👥', 'default': '🏗️'
};
let rawText = '';
let showingRaw = false;
let levels = { 1: '', 2: '', 3: '', 4: '' };
function setPreset(name) {
document.getElementById('systemName').value = name;
}
function toggleKey() {
const inp = document.getElementById('apiKey');
inp.type = inp.type === 'password' ? 'text' : 'password';
}
function getSystemIcon(name) {
const lower = name.toLowerCase();
for (const [k, v] of Object.entries(SYSTEM_ICONS)) {
if (lower.includes(k)) return v;
}
return SYSTEM_ICONS.default;
}
function switchLevel(num) {
document.querySelectorAll('.level-tab').forEach(t => t.classList.toggle('active', parseInt(t.dataset.level) === num));
document.querySelectorAll('.level-panel').forEach(p => p.classList.toggle('active', p.id === `lvl${num}`));
}
function toggleRaw() {
showingRaw = !showingRaw;
document.getElementById('rawOutput').classList.toggle('visible', showingRaw);
document.getElementById('structuredOutput').style.display = showingRaw ? 'none' : 'block';
document.querySelector('[onclick="toggleRaw()"]').textContent = showingRaw ? '⊞ Structured' : '⊞ Raw';
}
async function copyPrompt(btn) {
const systemName = document.getElementById('systemName').value.trim();
const desc = document.getElementById('systemDesc').value.trim();
if (!systemName) {
alert('Enter a system name first.');
return;
}
const prompt = buildPrompt(systemName, desc);
try {
await navigator.clipboard.writeText(prompt);
const label = btn.textContent;
btn.textContent = '✓ Copied!';
setTimeout(() => btn.textContent = label, 2000);
} catch (e) {}
}
function copyPromptSidebar(btn) { copyPrompt(btn); }
async function copyAll() {
try {
await navigator.clipboard.writeText(rawText);
const btn = document.querySelector('[onclick="copyAll()"]');
btn.textContent = '✓ Copied!';
setTimeout(() => btn.textContent = '⎘ Copy Output', 2000);
} catch (e) {}
}
function copyCode(btn, text) {
navigator.clipboard.writeText(text).then(() => {
btn.textContent = '✓ Copied';
btn.classList.add('copied');
setTimeout(() => { btn.textContent = 'Copy'; btn.classList.remove('copied'); }, 2000);
});
}
function buildLevelCard(levelNum, content) {
const descriptions = ['Basic Functionality & Core Classes', 'Constraints & Additional APIs', 'Concurrency & Performance', 'Extensibility & Production Requirements'];
const colors = ['var(--green)', 'var(--blue)', 'var(--purple)', 'var(--accent)'];
const card = document.createElement('div');
card.className = 'level-card';
card.dataset.level = levelNum;
const sections = parseContent(content);
card.innerHTML = `
<div class="level-card-header" data-level="${levelNum}">
<div class="level-dot"></div>
<div class="level-card-title">Level ${levelNum}${descriptions[levelNum - 1]}</div>
<div class="level-card-subtitle">${LEVEL_ICONS[levelNum - 1]} ${LEVEL_DESCRIPTIONS[levelNum - 1]}</div>
</div>
<div class="level-card-body">
${sections}
</div>`;
return card;
}
function parseContent(text) {
// Detect code blocks
const parts = text.split(/(```[\s\S]*?```)/g);
let out = '';
for (const part of parts) {
if (part.startsWith('```')) {
const lines = part.split('\n');
const lang = lines[0].replace('```', '').trim() || 'code';
const code = lines.slice(1, -1).join('\n');
const id = Math.random().toString(36).substr(2, 8);
out += `
<div class="section">
<div class="code-block-header">
<span class="code-lang">${lang}</span>
<button class="copy-btn" onclick="copyCode(this, ${JSON.stringify(code)})">Copy</button>
</div>
<pre>${escapeHtml(code)}</pre>
</div>`;
} else {
out += convertMarkdown(part);
}
}
return out;
}
function convertMarkdown(text) {
const lines = text.split('\n');
let out = '';
let inList = false;
let listItems = [];
const flushList = () => {
if (listItems.length) {
out += `<div class="section"><ul>${listItems.map(li => `<li>${li}</li>`).join('')}</ul></div>`;
listItems = [];
}
inList = false;
};
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (!trimmed) {
if (inList) flushList();
continue;
}
// Section labels (##, ###, ####)
if (/^#{2,4}\s/.test(trimmed)) {
if (inList) flushList();
const label = trimmed.replace(/^#{2,4}\s/, '');
out += `<div class="section"><div class="section-label">${label}</div></div>`;
continue;
}
// Bullet points
if (/^[-*]\s/.test(trimmed)) {
inList = true;
const item = trimmed.replace(/^[-*]\s/, '').replace(/`([^`]+)`/g, '<code style="font-family:JetBrains Mono,monospace;background:#060810;padding:1px 5px;border-radius:3px;font-size:12px;color:#38bdf8">$1</code>').replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
listItems.push(item);
continue;
}
if (inList) flushList();
// Bold **text**
const processed = trimmed
.replace(/`([^`]+)`/g, '<code style="font-family:JetBrains Mono,monospace;background:#060810;padding:1px 5px;border-radius:3px;font-size:12px;color:#38bdf8">$1</code>')
.replace(/\*\*([^*]+)\*\*/g, '<strong style="color:var(--heading)">$1</strong>');
out += `<div class="section"><p>${processed}</p></div>`;
}
if (inList) flushList();
return out;
}
function escapeHtml(s) {
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function parseLevels(text) {
const result = { 1: '', 2: '', 3: '', 4: '' };
// Try to split on Level N headers
const levelRegex = /(?:^|\n)(?:#{1,4}\s*)?Level\s*(\d)[\s\S]*?(?=\n(?:#{1,4}\s*)?Level\s*\d|$)/gi;
let match;
while ((match = levelRegex.exec(text)) !== null) {
const num = parseInt(match[1]);
if (num >= 1 && num <= 4) {
result[num] = match[0].trim();
}
}
// If nothing found, try to split by "---" or lines starting with ## Level
if (!result[1]) {
const chunks = text.split(/\n#{1,3}\s*Level/i);
for (let i = 1; i < chunks.length && i <= 4; i++) {
result[i] = ('Level' + chunks[i]).trim();
}
}
// Fallback: split evenly
if (!result[1]) {
const quarterLen = Math.floor(text.length / 4);
for (let i = 1; i <= 4; i++) {
result[i] = text.slice((i - 1) * quarterLen, i * quarterLen);
}
}
return result;
}
async function generate() {
const systemName = document.getElementById('systemName').value.trim();
const apiKey = document.getElementById('apiKey').value.trim();
const desc = document.getElementById('systemDesc').value.trim();
const model = document.getElementById('modelSelect').value;
const errorBox = document.getElementById('errorBox');
errorBox.style.display = 'none';
if (!systemName) {
errorBox.textContent = '⚠ Please enter a system name.';
errorBox.style.display = 'block';
return;
}
if (!apiKey || !apiKey.startsWith('sk-')) {
errorBox.textContent = '⚠ Please enter a valid OpenAI API key starting with sk-';
errorBox.style.display = 'block';
return;
}
const btn = document.getElementById('generateBtn');
btn.disabled = true;
btn.classList.add('loading');
document.getElementById('placeholder').style.display = 'none';
document.getElementById('structuredOutput').style.display = 'none';
document.getElementById('rawOutput').classList.remove('visible');
document.getElementById('outputActions').style.display = 'none';
rawText = '';
showingRaw = false;
const prompt = buildPrompt(systemName, desc);
try {
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({
model,
messages: [{ role: 'user', content: prompt }],
stream: true,
temperature: 0.7,
max_tokens: 4096
})
});
if (!response.ok) {
const err = await response.json();
throw new Error(err.error?.message || `HTTP ${response.status}`);
}
// Show streaming raw view first
document.getElementById('rawOutput').classList.add('visible');
document.getElementById('rawOutput').textContent = '';
showingRaw = true;
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n').filter(l => l.startsWith('data: ') && l !== 'data: [DONE]');
for (const line of lines) {
try {
const data = JSON.parse(line.slice(6));
const delta = data.choices?.[0]?.delta?.content || '';
rawText += delta;
document.getElementById('rawOutput').textContent = rawText;
} catch {}
}
}
// Parse and render structured output
renderStructured(systemName, desc);
} catch (e) {
errorBox.textContent = `⚠ Error: ${e.message}`;
errorBox.style.display = 'block';
document.getElementById('rawOutput').classList.remove('visible');
document.getElementById('placeholder').style.display = 'flex';
} finally {
btn.disabled = false;
btn.classList.remove('loading');
}
}
function renderStructured(systemName, desc) {
// Switch to structured view
document.getElementById('rawOutput').classList.remove('visible');
showingRaw = false;
// Setup banner
document.getElementById('bannerIcon').textContent = getSystemIcon(systemName);
document.getElementById('bannerName').textContent = systemName;
document.getElementById('bannerDesc').textContent = desc || 'CodeSignal-style 4-level OOP interview prompt';
// Parse levels
const parsedLevels = parseLevels(rawText);
for (let i = 1; i <= 4; i++) {
const container = document.getElementById(`lvl${i}`);
container.innerHTML = '';
const card = buildLevelCard(i, parsedLevels[i]);
container.appendChild(card);
}
switchLevel(1);
document.getElementById('structuredOutput').style.display = 'block';
document.getElementById('outputActions').style.display = 'flex';
document.getElementById('rawOutput').textContent = rawText;
// Update raw ref
document.querySelector('[onclick="toggleRaw()"]').textContent = '⊞ Raw';
}
function buildPrompt(systemName, description) {
const descPart = description ? `\nAdditional context for this system: ${description}\n` : '';
return `You are an interview question designer.
For the system listed below, generate a **CodeSignal-style 4-level coding prompt** that incrementally builds an object-oriented system.
### System
- ${systemName}
${descPart}
### Requirements
- Generate **4 levels**, where:
- **Level 1**: Basic functionality and core classes (single-threaded, minimal features)
- **Level 2**: Adds constraints, edge cases, or additional APIs
- **Level 3**: Introduces concurrency, performance constraints, or correctness guarantees
- **Level 4**: Extensibility or production-like requirements (pluggability, configuration, failure handling)
- Each level must include:
- A clear problem statement
- Required public methods and their signatures (use code blocks with \`\`\`python or \`\`\`java)
- Explicit constraints and assumptions
- Example inputs/outputs or representative test cases (in code blocks)
- Keep the scope realistic for a **45–60 minute interview**.
- Focus on **class design, APIs, and invariants**, not UI or persistence.
- Do **not** include the solution—only the prompt.
### Output Format
- Use clear section headers for each level: ## Level 1, ## Level 2, ## Level 3, ## Level 4
- Under each level use subsections: ### Problem Statement, ### API, ### Constraints, ### Examples
- Keep descriptions concise and interview-realistic.`;
}
</script>
</body>
</html>