You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

905 lines
28 KiB

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>System Design Game</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@300;400;500;700&display=swap');
/* === CSS VARIABLES === */
:root {
/* Colors */
--color-bg-body: #161b22;
--color-bg-dark: #121212;
--color-bg-sidebar: #111;
--color-bg-component: #1e1e1e;
--color-bg-hover: #2a2a2a;
--color-bg-accent: #005f87;
--color-bg-tab-active: #1a3d2a;
--color-border: #444;
--color-border-accent: #00ff88;
--color-border-panel: #30363d;
--color-text-primary: #ccc;
--color-text-muted: #888;
--color-text-accent: #00ff88;
--color-text-white: #fff;
--color-text-dark: #333;
--color-button: #238636;
--color-button-disabled: #555;
--color-connection: #333;
--color-connection-selected: #007bff;
--color-tooltip-bg: #333;
--color-tooltip-text: #fff;
/* Sizes */
--radius-small: 4px;
--radius-medium: 6px;
--radius-large: 8px;
--font-family-mono: 'JetBrains Mono', monospace;
--font-family-code: 'Fira Code', monospace;
--component-padding: 8px;
--component-gap: 12px;
}
/* === RESET === */
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: var(--font-family-mono);
background-color: var(--color-bg-body);
color: var(--color-text-primary);
display: flex;
flex-direction: row;
height: 100vh;
}
/* === LAYOUT === */
#page-container {
display: flex;
flex-direction: column;
width: 100%;
}
#sd-header {
width: 100%;
background: none;
padding: 12px 24px;
font-size: 24px;
font-weight: bold;
color: var(--color-text-accent);
border-bottom: 1px solid var(--color-text-dark);
}
#main-content {
display: flex;
flex-direction: row;
height: 100%;
background: radial-gradient(circle at 30% 50%, rgba(0, 255, 136, 0.1), transparent 50%),
radial-gradient(circle at 70% 80%, rgba(255, 107, 53, 0.1), transparent 50%);
}
/* === SIDEBAR === */
#sidebar {
width: 100%;
background-color: var(--color-bg-sidebar);
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: var(--component-gap);
}
/* === COMPONENT ICONS === */
.component-icon,
#arrow-tool {
position: relative;
padding: var(--component-padding) 12px;
background-color: var(--color-bg-component);
border: 1px solid var(--color-border);
border-radius: var(--radius-medium);
text-align: center;
cursor: grab;
user-select: none;
font-size: 16px;
color: var(--color-text-primary);
transition: background-color 0.1s ease;
}
.component-icon:hover,
#arrow-tool:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-border-accent);
}
.component-icon:active,
#arrow-tool:active {
cursor: grabbing;
}
#arrow-tool.active {
background-color: var(--color-bg-accent);
color: var(--color-text-white);
border-color: var(--color-button);
}
/* === TOOLTIP === */
.tooltip {
visibility: hidden;
opacity: 0;
position: absolute;
top: 100%;
left: 0;
z-index: 10;
background: var(--color-tooltip-bg);
color: var(--color-tooltip-text);
padding: 6px 8px;
border-radius: var(--radius-small);
white-space: nowrap;
font-size: 14px;
line-height: 1.4;
margin-top: 4px;
transition: opacity 0.2s;
}
.component-icon:hover .tooltip {
visibility: visible;
opacity: 1;
z-index: 1000;
}
.component-icon.dragging .tooltip {
display: none;
}
/* === CANVAS === */
#canvas-wrapper {
flex: 1;
display: flex;
flex-direction: column;
border-radius: var(--radius-large);
border: 2px solid var(--color-border-panel);
overflow: hidden;
background: var(--color-bg-dark);
margin: 12px 12px 12px 0;
padding: 16px;
}
#canvas-container {
flex: 1;
position: relative;
background: var(--color-bg-dark);
height: 100%;
margin-top: 16px;
}
#canvas {
width: 100%;
height: 90%;
background: var(--color-bg-dark);
border: 2px dashed var(--color-border-panel);
border-radius: var(--radius-large);
}
.dropped {
cursor: move;
}
.dropped.selected rect {
stroke: #00bcd4;
stroke-width: 2;
}
#canvas-toolbar {
position: absolute;
top: 12px;
left: 12px;
z-index: 20;
display: flex;
gap: 8px;
background: var(--color-bg-component);
border: 1px solid var(--color-border);
border-radius: var(--radius-small);
padding: 6px;
box-shadow: 0 0 8px rgba(0, 0, 0, 0.4);
}
.toolbar-btn {
background: none;
border: 1px solid var(--color-border);
color: var(--color-text-primary);
padding: 6px 10px;
border-radius: var(--radius-small);
font-size: 14px;
cursor: pointer;
font-family: var(--font-family-mono);
}
.toolbar-btn:hover {
background-color: var(--color-bg-hover);
border-color: var(--color-border-accent);
}
.toolbar-btn.active {
background-color: var(--color-bg-accent);
color: var(--color-text-white);
border-color: var(--color-button);
}
/* === PANELS === */
#info-panel {
position: absolute;
top: 12px;
right: 12px;
background: var(--color-bg-dark);
color: var(--color-text-primary);
padding: 1rem;
border-radius: var(--radius-large);
font-family: monospace;
font-size: 14px;
min-width: 220px;
z-index: 10;
border: 1px solid var(--color-text-dark);
box-shadow: 0 0 8px rgba(0, 0, 0, 0.3);
}
#node-props-panel {
position: absolute;
width: 220px;
background-color: var(--color-bg-sidebar);
border: 1px solid var(--color-border);
border-radius: var(--radius-small);
padding: 12px;
color: var(--color-text-white);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.6);
display: none;
z-index: 10;
}
#node-props-panel h3 {
margin-top: 0;
font-size: 15px;
color: var(--color-text-primary);
}
#node-props-save {
margin-top: 8px;
padding: 10px;
background-color: var(--color-button);
color: var(--color-text-white);
border: none;
border-radius: var(--radius-small);
cursor: pointer;
font-size: 14px;
}
.prop-group {
display: none;
margin-bottom: 12px;
}
.prop-group label,
.prop-group input {
display: block;
width: 100%;
margin-top: 6px;
font-size: 13px;
}
input[type="text"],
input[type="number"] {
padding: 6px;
background-color: #222;
border: 1px solid var(--color-border);
color: var(--color-text-white);
border-radius: var(--radius-small);
font-family: var(--font-family-code);
}
/* === BUTTONS === */
#run-button {
margin-top: auto;
padding: 10px;
background-color: var(--color-button);
color: var(--color-text-white);
border: none;
border-radius: var(--radius-small);
cursor: pointer;
font-size: 14px;
}
#run-button:disabled,
#node-props-panel button:disabled {
background-color: var(--color-button-disabled);
cursor: not-allowed;
}
/* === TABS === */
.tabs {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.tab-labels {
display: flex;
cursor: pointer;
}
.tab-labels label {
padding: 10px 20px;
background: var(--color-bg-body);
margin-right: 4px;
margin-bottom: 20px;
border-radius: var(--radius-small);
}
.tab-content {
border-top: 1px solid var(--color-border-panel);
padding: 20px 0 0;
display: none;
height: 100%;
}
input[name="tab"] {
display: none;
}
#tab1:checked ~ .tabs .tab-labels label[for="tab1"],
#tab2:checked ~ .tabs .tab-labels label[for="tab2"],
#tab3:checked ~ .tabs .tab-labels label[for="tab3"] {
background: var(--color-bg-tab-active);
font-weight: bold;
color: var(--color-text-accent);
}
#tab1:checked ~ .tabs #content1,
#tab2:checked ~ .tabs #content2,
#tab3:checked ~ .tabs #content3 {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
/* === CHALLENGE PANEL === */
#challenge-container {
width: 15%;
background: var(--color-bg-dark);
margin: 12px 12px;
border: 2px solid var(--color-border-panel);
border-radius: var(--radius-large);
padding: 0 12px;
}
.challenge-list {
list-style: none;
margin: 0;
padding: 0;
}
.challenge-name {
font-weight: 500;
margin-bottom: 5px;
}
.challenge-item {
padding: 10px;
margin: 5px 0;
background: #21262d;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s ease;
border-left: 3px solid transparent;
list-style: none;
}
.challenge-difficulty {
font-size: 0.8rem;
color: #0b949e;
}
.challenge-difficulty.easy {
color: #3fb950;
}
.challenge-difficulty.medium {
color: #d29922;
}
.challenge-difficulty.hard {
color: #f85149
}
.challenge-item:hover {
background: #30363d;
}
.challenge-item.active {
background: #1a3d2a;
border-left-color: #00ff88;
}
/* === PANEL METRICS === */
.panel-title {
font-weight: bold;
color: var(--color-text-white);
font-size: 15px;
margin-bottom: 0.5rem;
}
.panel-metric {
margin-bottom: 0.4rem;
}
.panel-metric .label {
display: inline-block;
width: 140px;
color: var(--color-text-muted);
}
.sidebar-title {
color: #8b949e;
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 15px;
padding-bottom: 8px;
padding-left: 8px;
border-bottom: 1px solid #303638;
}
.requirements-section {
background: #161b22;
border: 1px solid #30363d;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.requirements-list {
margin: 0;
padding: 0;
list-style: none;
}
.requirement-item {
position: relative;
padding: 8px 0 8px 25px;
margin: 0;
border-bottom: 1px solid #30363d;;
}
.requirement-item:before {
content: "✓";
color: #00ff88;
position: absolute;
left: 0;
}
.modal {
position: absolute;
top: 30%;
left: 50%;
transform: translate(-50%, -30%);
background: #121212;
padding: 20px;
border-radius: 8px;
border: 1px solid #444;
z-index: 999;
color: #ccc;
}
.modal-content label {
display: block;
margin: 10px 0;
}
.modal-actions {
margin-top: 10px;
text-align: right;
}
.modal input {
width: 100%;
padding: 6px;
margin-top: 4px;
background: #222;
border: 1px solid #444;
color: #fff;
border-radius: 4px;
}
.modal select {
width: 100%;
padding: 6px;
margin-top: 4px;
background: #222;
border: 1px solid #444;
color: #fff;
border-radius: 4px;
}
#node-props-panel .form-group {
margin-bottom: 10px;
}
#node-props-panel label {
display: block;
font-weight: bold;
margin-bottom: 4px;
}
#node-props-panel select {
width: 100%;
padding: 4px;
font-size: 14px;
}
</style>
</head>
<body>
<div id="page-container">
<div id="sd-header">System Design Game</div>
<div id="main-content">
<div id="challenge-container">
<h2 class="sidebar-title">Challenges</h2>
<ul class="challenge-list">
{{range .Levels}}
<li class="challenge-item {{if and (eq .Name $.Level.Name) (eq .Difficulty $.Level.Difficulty)}}active{{end}}">
<div class="challenge-name">{{.Name}}</div>
<div class="challenge-difficulty {{.Difficulty}}">{{.Difficulty}}</div>
</li>
{{end}}
</ul>
</div>
<div id="canvas-wrapper">
<input type="radio" id="tab1" name="tab" checked>
<input type="radio" id="tab2" name="tab">
<input type="radio" id="tab3" name="tab">
<div class="tabs">
<div class="tab-labels">
<label for="tab1">Requirements</label>
<label for="tab2">Design</label>
<label for="tab3">Metrics</label>
</div>
<!-- Requirements -->
<div id="content1" class="tab-content">
{{ if .Level.InterviewerRequirements }}
<div class="requirements-section">
<h3>Interviewer Requirements</h3>
<ul class="requirements-list">
{{ range .Level.InterviewerRequirements }}
<li class="requirement-item">{{ . }}</li>
{{ end }}
</ul>
</div>
{{ end }}
{{ if .Level.FunctionalRequirements }}
<div class="requirements-section">
<h3>Functional Requirements</h3>
<ul class="requirements-list">
{{ range .Level.FunctionalRequirements }}
<li class="requirement-item">{{ . }}</li>
{{ end }}
</ul>
</div>
{{ end }}
{{ if .Level.NonFunctionalRequirements }}
<div class="requirements-section">
<h3>Non-Functional Requirements</h3>
<ul class="requirements-list">
{{ range .Level.NonFunctionalRequirements }}
<li class="requirement-item">{{ . }}</li>
{{ end }}
</ul>
</div>
{{ end }}
</div>
<!-- Design-->
<div id="content2" class="tab-content">
<div id="sidebar">
<div class="component-icon" draggable="true" data-type="user">
user
</div>
<div class="component-icon" draggable="true" data-type="loadBalancer">
load balancer
</div>
<div class="component-icon" draggable="true" data-type="webserver">
webserver
</div>
<div class="component-icon" draggable="true" data-type="database">
database
</div>
<div class="component-icon" draggable="true" data-type="cache">
cache
</div>
<div class="component-icon" draggable="true" data-type="messageQueue">
message queue
</div>
<div class="component-icon" draggable="true" data-type="cdn">
CDN
</div>
<div class="component-icon" draggable="true" data-type="microservice">
microservice node
</div>
<div class="component-icon" draggable="true" data-type="data pipeline">
data pipeline
</div>
<div class="component-icon" draggable="true" data-type="monitoring/alerting">
monitoring/alerting
</div>
<div class="component-icon" draggable="true" data-type="third party service">
third-party service
</div>
</div>
<div id="canvas-container">
<div id="connection-modal" style="display: none;" class="modal">
<div class="modal-content">
<h3>Create Connection</h3>
<label>
Label:
<input type="text" id="connection-label" value="Read traffic">
</label>
<label>
Protocol:
<select id="connection-protocol">
<option>HTTP</option>
<option>HTTPS</option>
<option>gRPC</option>
<option>WebSocket</option>
<option>GraphQL</option>
<option>Kafka</option>
<option>AMQP</option>
<option>MQTT</option>
<option>SQL</option>
<option>NoSQL</option>
<option>Redis</option>
<option>TLS</option>
</select>
</label>
<label style="margin-top: 10px;">
<input type="checkbox" id="connection-tls">
Enable TLS (encryption)
</label>
<label for="connection-capacity">Capacity Limit (RPS):</label>
<input type="number" id="connection-capacity" value="1000" min="1" />
<div class="modal-actions">
<button id="connection-save">Save</button>
<button id="connection-cancel">Cancel</button>
</div>
</div>
</div>
<div id="canvas-toolbar">
<button id="arrow-tool-btn" class="toolbar-btn">Arrow Tool</button>
</div>
<div id="info-panel">
<div id="constraints-panel">
<div class="panel-title">level constraints</div>
<div class="panel-metric"><span class="label">🎯 target rps:</span> <span id="constraint-rps"></span></div>
<div class="panel-metric"><span class="label"> max p95 latency:</span> <span id="constraint-latency"></span></div>
<div class="panel-metric"><span class="label">💸 max cost:</span> <span id="constraint-cost"></span></div>
<div class="panel-metric"><span class="label">🔒 availability:</span> <span id="constraint-availability"></span></div>
</div>
<div id="score-panel">
<div class="panel-title">simulation results</div>
<div class="panel-metric"><span class="label">✅ cost:</span> <span id="score-cost"></span></div>
<div class="panel-metric"><span class="label">⚡ p95 latency:</span> <span id="score-p95"></span></div>
<div class="panel-metric"><span class="label">📈 achieved rps:</span> <span id="score-rps"></span></div>
<div class="panel-metric"><span class="label">🛡 availability:</span> <span id="score-availability"></span></div>
</div>
</div>
<svg id="canvas">
<defs>
<marker id="arrowhead-start" markerWidth="10" markerHeight="7" refX="0" refY="3.5"
orient="auto" markerUnits="strokeWidth">
<path d="M10 0 L0 3.5 L10 7" fill="#ccc" />
</marker>
<marker id="arrowhead-end" markerWidth="10" markerHeight="7" refX="10" refY="3.5"
orient="auto" markerUnits="strokeWidth">
<path d="M0 0 L10 3.5 L0 7" fill="#ccc" />
</marker>
</defs>
</svg>
<div id="node-props-panel">
<h3>node properties</h3>
<div id="label-group" data-group="label-group">
<label>label:</label>
<input type="text" name="label" />
</div>
<div id="db-group" class="prop-group" data-group="db-group">
<label>replication factor:<input type="number" name="replication" min="1" step="1" /></label>
</div>
<div id="cache-group" class="prop-group" data-group="cache-group">
<label>cache ttl (secs):<input type="number" name="cacheTTL" min="0" step="60" /></label>
<label>Max Entries: <input name="maxEntries" type="number" /></label>
<label>Eviction Policy:
<select name="evictionPolicy">
<option value="LRU">LRU</option>
<option value="LFU">LFU</option>
<option value="Random">Random</option>
</select>
</label>
</div>
<div id="compute-group" data-group="compute-group" class="prop-group">
<label>CPU Cores:</label>
<input type="number" name="cpu" min="1" />
<label>RAM (GB):</label>
<input type="number" name="ramGb" min="1" />
<label>RPS Capacity:</label>
<input type="number" name="rpsCapacity" min="1" />
<label>Monthly Cost (USD):</label>
<input type="number" name="monthlyCostUsd" min="0" />
</div>
<div id="lb-group" data-group="lb-group" class="prop-group">
<label>Algorithm</label>
<select name="algorithm">
<option value="round-robin">Round Robin</option>
<option value="least-connections">Least Connections</option>
</select>
</div>
<div id="mq-group" data-group="mq-group" class="prop-group">
<label>Queue Capacity (,ax Messages that can be held in que)</label>
<input type="number" name="queueCapacity" min="1" />
<label>Retention Time (seconds)</label>
<input type="number" name="retentionSeconds" min="1" />
</div>
<div id="cdn-group" data-group="cdn-group" class="prop-group">
<label>TTL (seconds)</label>
<input type="number" name="ttl" min="1" />
<label>Geo Replication</label>
<select name="geoReplication">
<option value="global">Global</option>
<option value="regional">Regional</option>
<option value="custom">Custom</option>
</select>
<label>Caching Strategy</label>
<select name="cachingStrategy">
<option value="cache-first">Cache First</option>
<option value="network-first">Network First</option>
<option value="stale-while-revalidate">Stale While Revalidate</option>
</select>
<label>Compression</label>
<select name="compression">
<option value="brotli">Brotli</option>
<option value="gzip">Gzip</option>
<option value="none">None</option>
</select>
<label>HTTP/2 Support</label>
<select name="http2">
<option value="enabled">Enabled</option>
<option value="disabled">Disabled</option>
</select>
</div>
<div id="microservice-group" data-group="microservice-group" class="prop-group">
<label>
Instance Count:
<input type="number" name="instanceCount" value="3" min="1" />
</label>
<label>
CPU (vCPUs):
<input type="number" name="cpu" value="2" min="1" />
</label>
<label>
RAM (GB):
<input type="number" name="ramGb" value="4" min="1" />
</label>
<label>
RPS Capacity:
<input type="number" name="rpsCapacity" value="150" min="1" />
</label>
<label>
Monthly Cost (USD):
<input type="number" name="monthlyUsd" value="18" min="0" step="1" />
</label>
<label>
Scaling Strategy:
<select name="scalingStrategy">
<option value="auto" selected>Auto</option>
<option value="manual">Manual</option>
</select>
</label>
<label>
API Version:
<input type="text" name="apiVersion" value="v1" />
</label>
</div>
<div id="datapipeline-group" data-group="pipeline-group" class="prop-group">
<label>Batch Size</label>
<input type="number" name="batchSize" min="1" />
<label>Schedule</label>
<select name="schedule">
<option value="realtime">Real-time</option>
<option value="hourly">Hourly</option>
<option value="daily">Daily</option>
<option value="weekly">Weekly</option>
</select>
<label>Transformations</label>
<select name="transformations">
<option value="normalize">Normalize</option>
<option value="dedupe">Dedupe</option>
<option value="filter">Filter</option>
<option value="enrich">Enrich</option>
<option value="aggregate">Aggregate</option>
</select>
<label>Destination</label>
<input type="text" name="destination" placeholder="e.g. data warehouse" />
</div>
<div id="monitor-group" data-group="monitor-group" class="prop-group">
<label>Monitoring Tool</label>
<select name="tool">
<option value="Prometheus">Prometheus</option>
<option value="Datadog">Datadog</option>
<option value="New Relic">New Relic</option>
<option value="Grafana Cloud">Grafana Cloud</option>
</select>
<label>Alert Threshold (%)</label>
<input type="number" name="alertThreshold" min="0" max="100" />
</div>
<div id="third-party-group" data-group="third-party-group" class="prop-group">
<label>Provider</label>
<input type="text" name="provider" />
<label>Latency (ms)</label>
<input type="number" name="latency" min="0" />
</div>
<!-- PUT NEW COMPONENTS BEFORE THIS BUTTON -->
<button id="node-props-save" disabled>save</button>
</div>
<div id="bottom-panel">
<button id="run-button" disabled>Test Design</button>
</div>
</div>
</div>
<!-- Metrics-->
<div id="content3" class="tab-content">This is Tab 3 content.</div>
</div>
</div>
</div>
</div>
<script type="module" src="/static/index.js"></script>
</body>
</html>