design inspiration for the concepts site
For the full content of this gist, refer to https://gist.github.com/bentossell/dbb69235536b1df15ddd57340f4ef582
For the full content of this gist, refer to https://gist.github.com/bentossell/dbb69235536b1df15ddd57340f4ef582
This file is the visual rulebook for this project. Future design changes should stay inside these boundaries unless explicitly changed here first.
The site is a single-page concepts explainer. It should feel clear, structured, thoughtful, and intentional.
Do not use these unless specifically requested:
680px to 760px1100pxUse this spacing scale consistently:
4px8px12px16px24px32px48px72pxRules:
16px32px to 48px72pxUse at most two families:
Rules:
Recommended hierarchy:
40px to 64px24px to 36px16px to 20px16px to 18px12px to 13pxUse a restrained palette:
Rules:
Suggested token structure:
--bg--bg-subtle--surface--surface-2--text--text-muted--line--accent--accent-strong--success8px12px16px to 24px120ms to 180msAll components should follow these rules:
Interactive demo12pxUnless the user asks otherwise, use this baseline:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>How Local and Remote Works</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
color-scheme: dark;
--bg: #0d0d0d;
--bg2: #161616;
--bg3: #1e1e1e;
--bg4: #282828;
--text: #e0e0e0;
--text2: #999;
--text3: #666;
--accent: #7f77dd;
--accent2: #534ab7;
--accent-bg: #1a1840;
--teal: #1d9e75;
--teal-bg: #0a2a20;
--coral: #d85a30;
--coral-bg: #2a1510;
--border: #2a2a2a;
--border2: #333;
--radius: 8px;
--radius-lg: 12px;
}
html { font-size: 16px; }
body {
background: var(--bg);
color: var(--text);
font-family: "SF Mono", "Fira Code", "Cascadia Code", "JetBrains Mono", monospace;
line-height: 1.7;
-webkit-font-smoothing: antialiased;
}
.container { max-width: 720px; margin: 0 auto; padding: 3rem 1.5rem 6rem; }
h1 { font-size: 1.75rem; font-weight: 500; margin-bottom: 0.5rem; letter-spacing: -0.5px; }
.subtitle { font-size: 0.875rem; color: var(--text2); margin-bottom: 4rem; }
h2 { font-size: 1.125rem; font-weight: 500; margin-bottom: 0.75rem; color: var(--accent); letter-spacing: -0.3px; }
h3 { font-size: 0.9375rem; font-weight: 500; margin-bottom: 0.5rem; }
p { font-size: 0.875rem; color: var(--text2); margin-bottom: 1.5rem; }
.section { margin-bottom: 4rem; }
.divider { height: 1px; background: var(--border); margin: 4rem 0; }
code {
background: var(--bg3);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.8125rem;
color: var(--text);
}
.widget {
background: var(--bg2);
border: 0.5px solid var(--border);
border-radius: var(--radius-lg);
padding: 1.5rem;
margin-bottom: 2rem;
}
.widget-label {
font-size: 0.75rem;
color: var(--text3);
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 1rem;
}
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.file-tree { list-style: none; padding: 0; }
.tree-row {
padding: 6px 0;
font-size: 0.8125rem;
color: var(--text2);
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
transition: color 0.15s ease;
}
.tree-row:hover { color: var(--text); }
.tree-row.active { color: var(--accent); }
.tree-row.folder { cursor: default; }
.tree-row svg { width: 16px; height: 16px; flex-shrink: 0; }
.tree-row.folder svg { color: var(--accent); }
.tree-row.file svg { color: var(--text3); }
.tree-children { padding-left: 20px; }
.file-preview {
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
min-height: 160px;
}
.path { font-size: 0.75rem; color: var(--text3); margin-bottom: 0.5rem; }
.content {
font-size: 0.8125rem;
color: var(--teal);
white-space: pre-wrap;
min-height: 92px;
}
.file-location {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--bg3);
border-radius: 999px;
padding: 4px 12px;
font-size: 0.75rem;
color: var(--text2);
margin-top: 10px;
}
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--teal); }
.server-sim { display: flex; flex-direction: column; gap: 12px; }
.server-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
button {
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius);
padding: 8px 16px;
color: var(--text);
font-family: inherit;
font-size: 0.8125rem;
cursor: pointer;
transition: border-color 0.2s ease, color 0.2s ease, background 0.2s ease;
}
button:hover { border-color: var(--accent); color: var(--accent); }
.server-btn.running { border-color: var(--teal); color: var(--teal); }
.server-status {
font-size: 0.8125rem;
color: var(--text3);
display: flex;
align-items: center;
gap: 8px;
}
.server-status .status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text3);
transition: background 0.3s ease;
}
.server-status .status-dot.on { background: var(--teal); }
.server-log {
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
font-size: 0.75rem;
color: var(--text3);
min-height: 110px;
max-height: 170px;
overflow-y: auto;
}
.log-line { margin-bottom: 2px; }
.log-line.info { color: var(--teal); }
.log-line.req { color: var(--accent); }
.log-line.warn { color: var(--coral); }
.browser-bar {
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius) var(--radius) 0 0;
padding: 8px 12px;
display: flex;
align-items: center;
gap: 8px;
}
.browser-dots { display: flex; gap: 4px; }
.browser-dots span { width: 8px; height: 8px; border-radius: 50%; background: var(--bg4); }
.browser-url {
flex: 1;
background: var(--bg3);
border-radius: 4px;
padding: 4px 8px;
font-size: 0.75rem;
color: var(--text3);
}
.browser-body {
background: var(--bg3);
border: 0.5px solid var(--border);
border-top: none;
border-radius: 0 0 var(--radius) var(--radius);
padding: 1.5rem;
min-height: 84px;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.875rem;
color: var(--text3);
text-align: center;
}
.browser-body.active { color: var(--text); }
.compare-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.compare-card {
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius);
padding: 1rem;
}
.card-title { font-size: 0.8125rem; font-weight: 500; margin-bottom: 8px; }
.card-title.local { color: var(--teal); }
.card-title.remote { color: var(--accent); }
.card-row {
font-size: 0.75rem;
color: var(--text3);
padding: 4px 0;
display: flex;
justify-content: space-between;
gap: 8px;
}
.card-row span:last-child { color: var(--text2); text-align: right; }
.deploy-pipeline { display: flex; flex-direction: column; gap: 0; }
.deploy-step { display: flex; align-items: stretch; gap: 12px; padding: 12px 0; }
.deploy-line {
width: 2px;
background: var(--border);
position: relative;
flex-shrink: 0;
margin-left: 11px;
}
.deploy-line::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--bg2);
border: 2px solid var(--border);
transition: all 0.3s ease;
}
.deploy-step.done .deploy-line::before { background: var(--teal); border-color: var(--teal); }
.deploy-step.active .deploy-line::before { background: var(--accent); border-color: var(--accent); }
.deploy-info { flex: 1; }
.step-name { font-size: 0.8125rem; color: var(--text); margin-bottom: 2px; }
.step-desc { font-size: 0.75rem; color: var(--text3); }
.step-time { font-size: 0.6875rem; color: var(--text3); margin-top: 2px; }
.deploy-controls { display: flex; gap: 8px; margin-top: 12px; }
.deploy-btn {
background: var(--accent-bg);
border-color: var(--accent2);
color: var(--accent);
padding: 8px 20px;
}
.deploy-btn:hover { background: var(--accent2); color: #fff; }
.deploy-btn.reset {
background: var(--bg);
border-color: var(--border);
color: var(--text3);
}
.deploy-btn.reset:hover { border-color: var(--text3); color: var(--text2); }
.deploy-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.env-toggle {
display: flex;
background: var(--bg);
border: 0.5px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
margin-bottom: 1rem;
}
.env-toggle button {
flex: 1;
background: none;
border: none;
padding: 8px 16px;
color: var(--text3);
border-radius: 0;
}
.env-toggle button.active {
background: var(--bg3);
color: var(--text);
}
.closing {
margin-top: 2rem;
color: var(--text3);
font-size: 0.8125rem;
border-top: 0.5px solid var(--border);
padding-top: 2rem;
}
@media (max-width: 700px) {
.two-col, .compare-grid { grid-template-columns: 1fr; }
.container { padding: 2rem 1rem 4rem; }
}
</style>
</head>
<body>
<div class="container">
<h1>How local and remote works</h1>
<p class="subtitle">A visual guide for non-technical people</p>
<div class="section">
<h2>01 -- Files</h2>
<p>Every website or app starts as a collection of files on someone's computer. These files contain the code, images, and content that make up what you see on screen. Think of them like documents in folders, just organized in a way that browsers and servers understand.</p>
<div class="widget">
<div class="widget-label">Interactive: explore a project</div>
<div class="two-col">
<div><ul class="file-tree" id="fileTree"></ul></div>
<div>
<div class="file-preview">
<div class="path" id="filePath">Click a file to preview</div>
<div class="content" id="fileContent"></div>
</div>
<div class="file-location" id="fileLocation">
<span class="dot" id="fileDot"></span>
<span id="fileLocationLabel">Stored on your computer</span>
</div>
</div>
</div>
</div>
<p>When files only live on your computer, only you can work with them. To let other people use them, those files need to be served by a machine they can reach.</p>
</div>
<div class="divider"></div>
<div class="section">
<h2>02 -- Local servers</h2>
<p>A local server is a program running on your computer that acts like a small private website. It reads your files and serves them to your browser, so you can test a real page before anything is public.</p>
<div class="widget">
<div class="widget-label">Interactive: run a local server</div>
<div class="server-sim">
<div class="server-bar">
<button class="server-btn" id="serverBtn" type="button">Start server</button>
<div class="server-status">
<span class="status-dot" id="serverDot"></span>
<span id="serverLabel">Stopped</span>
</div>
</div>
<div class="server-log" id="serverLog"><div class="log-line">$ _</div></div>
<div>
<div class="browser-bar">
<div class="browser-dots"><span></span><span></span><span></span></div>
<div class="browser-url">localhost:3000</div>
</div>
<div class="browser-body" id="browserBody">No server running</div>
</div>
<div style="margin-top:4px">
<button id="editBtn" type="button" style="font-size:0.75rem;padding:6px 12px" disabled>Simulate a file edit</button>
</div>
</div>
</div>
<p>The address <code>localhost</code> means โthis computer.โ When you visit <code>localhost:3000</code>, your browser is talking to your own machine, not to the public internet.</p>
<div class="widget" style="margin-top:1.5rem">
<div class="widget-label">Compare: local vs remote</div>
<div class="compare-grid">
<div class="compare-card">
<div class="card-title local">Local</div>
<div class="card-row"><span>Audience</span><span>Only you</span></div>
<div class="card-row"><span>Address</span><span>localhost</span></div>
<div class="card-row"><span>Speed</span><span>Instant updates</span></div>
<div class="card-row"><span>Purpose</span><span>Development</span></div>
</div>
<div class="compare-card">
<div class="card-title remote">Remote</div>
<div class="card-row"><span>Audience</span><span>Other people</span></div>
<div class="card-row"><span>Address</span><span>your-site.com</span></div>
<div class="card-row"><span>Speed</span><span>Needs deployment</span></div>
<div class="card-row"><span>Purpose</span><span>Sharing and production</span></div>
</div>
</div>
</div>
</div>
<div class="divider"></div>
<div class="section">
<h2>03 -- Deployments</h2>
<p>Deploying means copying your files from your computer to a remote server, a machine on the internet that stays online so other people can reach it. The public version changes only after that handoff finishes.</p>
<div class="widget">
<div class="widget-label">Interactive: deploy a site</div>
<div class="deploy-pipeline" id="pipeline"></div>
<div class="deploy-controls">
<button class="deploy-btn" id="deployBtn" type="button">Deploy</button>
<button class="deploy-btn reset" id="resetBtn" type="button" style="display:none">Reset</button>
</div>
</div>
<p>Deployment usually runs through a short pipeline so broken changes do not go live immediately. That extra delay is what makes remote systems safer than just saving a file.</p>
<div class="widget" style="margin-top:1.5rem">
<div class="widget-label">Toggle: where are the files now?</div>
<div class="env-toggle" id="envToggle">
<button class="active" type="button" data-env="local">Your computer</button>
<button type="button" data-env="remote">Deploy server</button>
<button type="button" data-env="user">User browser</button>
</div>
<div id="envDesc" style="font-size:0.8125rem;color:var(--text2);line-height:1.6"></div>
</div>
<p class="closing">That is the full journey: files on your computer, previewed by a local server, then deployed to a remote server for other people to use.</p>
</div>
</div>
<script>
const tree = [
{
name: "my-site",
type: "folder",
children: [
{ name: "index.html", type: "file", location: "local", content: "<!DOCTYPE html>\n<html>\n <body>\n <h1>Hello world</h1>\n </body>\n</html>" },
{ name: "styles.css", type: "file", location: "local", content: "body {\n font-family: sans-serif;\n margin: 2rem;\n color: #333;\n}" },
{ name: "app.js", type: "file", location: "local", content: "const button = document.querySelector('#signup');\nbutton.addEventListener('click', () => {\n alert('Welcome');\n});" },
{
name: "images",
type: "folder",
children: [
{ name: "logo.svg", type: "file", location: "remote", content: "<svg width='80' height='80'>\n <circle cx='40' cy='40' r='36' fill='#7f77dd' />\n</svg>" },
{ name: "hero.jpg", type: "file", location: "remote", content: "[binary image data]\n780 x 440 px\n124 KB" }
]
}
]
}
];
function icon(type) {
if (type === "folder") {
return "<svg viewBox='0 0 16 16' fill='currentColor'><path d='M1 3.5A1.5 1.5 0 0 1 2.5 2h3.379a1.5 1.5 0 0 1 1.06.44l.662.661A.5.5 0 0 0 7.955 3.3H13.5A1.5 1.5 0 0 1 15 4.8v7.7a1.5 1.5 0 0 1-1.5 1.5h-11A1.5 1.5 0 0 1 1 12.5v-9z'/></svg>";
}
return "<svg viewBox='0 0 16 16' fill='currentColor'><path d='M4 1.5A1.5 1.5 0 0 1 5.5 0h4.879a1.5 1.5 0 0 1 1.06.44l2.122 2.12A1.5 1.5 0 0 1 14 3.622V14.5a1.5 1.5 0 0 1-1.5 1.5h-7A1.5 1.5 0 0 1 4 14.5v-13z'/></svg>";
}
function renderTree(items, container, prefix = "") {
items.forEach((item) => {
const li = document.createElement("li");
li.className = "file-tree-item";
const row = document.createElement("div");
row.className = `tree-row ${item.type}`;
row.innerHTML = `${icon(item.type)}<span>${item.name}</span>`;
li.appendChild(row);
if (item.type === "file") {
row.addEventListener("click", () => {
document.querySelectorAll(".tree-row.file").forEach((node) => node.classList.remove("active"));
row.classList.add("active");
document.getElementById("filePath").textContent = prefix ? `${prefix}/${item.name}` : item.name;
document.getElementById("fileContent").textContent = item.content;
document.getElementById("fileLocationLabel").textContent = item.location === "local" ? "Stored on your computer" : "Copied to a remote server";
document.getElementById("fileDot").style.background = item.location === "local" ? "var(--teal)" : "var(--accent)";
});
}
container.appendChild(li);
if (item.children) {
const child = document.createElement("ul");
child.className = "file-tree tree-children";
li.appendChild(child);
renderTree(item.children, child, prefix ? `${prefix}/${item.name}` : item.name);
}
});
}
renderTree(tree, document.getElementById("fileTree"));
let serverRunning = false;
function addLog(text, cls = "") {
const line = document.createElement("div");
line.className = `log-line${cls ? ` ${cls}` : ""}`;
line.textContent = text;
const log = document.getElementById("serverLog");
log.appendChild(line);
log.scrollTop = log.scrollHeight;
}
document.getElementById("serverBtn").addEventListener("click", () => {
const btn = document.getElementById("serverBtn");
const dot = document.getElementById("serverDot");
const label = document.getElementById("serverLabel");
const body = document.getElementById("browserBody");
const editBtn = document.getElementById("editBtn");
if (!serverRunning) {
serverRunning = true;
btn.textContent = "Stop server";
btn.classList.add("running");
dot.classList.add("on");
label.textContent = "Running";
label.style.color = "var(--teal)";
document.getElementById("serverLog").innerHTML = "";
addLog("$ npm run dev");
setTimeout(() => addLog("Starting development server..."), 250);
setTimeout(() => addLog("Compiled successfully.", "info"), 700);
setTimeout(() => addLog("Server running at http://localhost:3000", "info"), 1000);
setTimeout(() => {
body.textContent = "Hello world";
body.classList.add("active");
addLog("GET / 200 -- 3ms", "req");
}, 1300);
setTimeout(() => addLog("GET /styles.css 200 -- 1ms", "req"), 1500);
editBtn.disabled = false;
return;
}
serverRunning = false;
btn.textContent = "Start server";
btn.classList.remove("running");
dot.classList.remove("on");
label.textContent = "Stopped";
label.style.color = "";
body.textContent = "No server running";
body.classList.remove("active");
editBtn.disabled = true;
addLog("Server stopped.", "warn");
});
document.getElementById("editBtn").addEventListener("click", () => {
if (!serverRunning) return;
addLog("File changed: index.html", "warn");
addLog("Recompiling...");
setTimeout(() => {
addLog("Compiled successfully.", "info");
addLog("GET / 200 -- 2ms", "req");
document.getElementById("browserBody").textContent = "Hello world (updated)";
}, 500);
});
const steps = [
{ name: "Upload files", desc: "Send your project to the deploy server", time: "2s" },
{ name: "Install dependencies", desc: "Fetch the pieces your project depends on", time: "8s" },
{ name: "Run tests", desc: "Check that nothing obvious is broken", time: "4s" },
{ name: "Build", desc: "Prepare optimized files for the live site", time: "6s" },
{ name: "Go live", desc: "Serve the new version to visitors", time: "1s" }
];
function renderPipeline() {
document.getElementById("pipeline").innerHTML = steps.map((step, index) => `
<div class="deploy-step" id="step-${index}">
<div class="deploy-line"></div>
<div class="deploy-info">
<div class="step-name">${step.name}</div>
<div class="step-desc">${step.desc}</div>
<div class="step-time" id="step-time-${index}"></div>
</div>
</div>
`).join("");
}
renderPipeline();
let deployRunning = false;
document.getElementById("deployBtn").addEventListener("click", () => {
if (deployRunning) return;
deployRunning = true;
document.getElementById("deployBtn").disabled = true;
renderPipeline();
let index = 0;
function runStep() {
if (index >= steps.length) {
deployRunning = false;
document.getElementById("resetBtn").style.display = "";
return;
}
const el = document.getElementById(`step-${index}`);
document.getElementById(`step-time-${index}`).textContent = "running...";
el.classList.add("active");
setTimeout(() => {
el.classList.remove("active");
el.classList.add("done");
document.getElementById(`step-time-${index}`).textContent = steps[index].time;
index += 1;
runStep();
}, parseInt(steps[index].time, 10) * 220);
}
runStep();
});
document.getElementById("resetBtn").addEventListener("click", () => {
deployRunning = false;
document.getElementById("deployBtn").disabled = false;
document.getElementById("resetBtn").style.display = "none";
renderPipeline();
});
const envData = {
local: "Your original files live here. You edit them in a code editor, and the changes stay private until you deploy.",
remote: "A remote deploy server receives a copy of those files and prepares them to be served on the internet.",
user: "When someone visits your URL, their browser downloads copies of those files and assembles the page they see."
};
const envButtons = [...document.querySelectorAll("#envToggle button")];
const envDesc = document.getElementById("envDesc");
function showEnv(key) {
envDesc.textContent = envData[key];
envButtons.forEach((button) => button.classList.toggle("active", button.dataset.env === key));
}
envButtons.forEach((button) => {
button.addEventListener("click", () => showEnv(button.dataset.env));
});
showEnv("local");
</script>
</body>
</html>