Client Blueprint Generator MVP
Build a browser app that generates blueprint floor plans with structural load calculations.
Introduction
30 Second Summary
Every construction project starts with raw data. Room dimensions, floor types, client addresses. Turning that information into a professional floor plan usually requires expensive CAD software or hours of manual drawing.
In this project, you will build Blueprint Studio, a zero-dependency browser application that stores client building data and generates blueprint-style floor plans with structural load calculations at the click of a button. No frameworks, no APIs, no paid tools. Just three files: index.html, styles.css, and app.js.
What You'll Build
Select a client from the sidebar, click Generate Blueprint, and watch a dark-blue construction-style floor plan fill the canvas with rooms drawn to scale, dimension annotations in feet, and a full table of structural load calculations underneath.
By the end of this project, you'll have:
- A client management sidebar where you browse three pre-loaded building profiles, click one, and instantly see the project details highlighted and ready to generate.
- A one-click blueprint generator that renders a professional floor plan on HTML Canvas with room outlines, dimension labels in feet, a measured grid, and a title block identifying the project.
- An auto-computed calculations panel showing dead load, live load, and total building load for every room based on IBC occupancy-type values.
- Secret Mission: a one-click PNG export that composites the blueprint and calculations into a single downloadable image you can email to a client.
Are there any prerequisites?
Basic familiarity with HTML, CSS, and JavaScript syntax is all you need. This project uses no frameworks, no build tools, and no package managers.
Before We Start
Before We Start...
Before we dive in, let's lock in what you're about to build and why it matters. You're creating a browser app called Blueprint Studio that turns structured client data into blueprint-style floor plans with structural load calculations.
Set Up the Project Files
Every construction project starts with a foundation, and so does every web application. Before you can render blueprints or compute structural loads, you need the three files that make up Blueprint Studio: an HTML page for the layout, a CSS stylesheet for the dark engineering theme, and a JavaScript file to hold client data.
In this step, you will create the project folder, build the page structure, style the dark blueprint UI, and load three client profiles with room data. By the end, you will have a styled shell ready for the rendering logic that comes in Step 2.
In this step, get ready to:
- Create a blueprint-studio folder with three files: index.html, styles.css, and app.js.
- Build the HTML structure with a sidebar and a workspace area containing a canvas and calculations panel.
- Add the complete dark blueprint CSS theme and load three client data profiles with room definitions.
Create the project folder and files
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir blueprint-studio
cd blueprint-studio
- Create three empty files and confirm they exist by running these commands:
touch index.html styles.css app.js
ls
You should see index.html, styles.css, and app.js listed in the output.
Don't see all three files?
Make sure you ran the cd blueprint-studio command before running touch. If you skipped it, the files were created one level up on your Desktop instead of inside the project folder.
help me find where my project files were created
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type VS Code and press Enter to open it.
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Navigate to your Desktop, select the blueprint-studio folder, and click Open.
You should see all three files listed in the Explorer sidebar on the left side of VS Code.
Build the HTML shell
The HTML file defines the page layout: a sidebar on the left for client selection and a workspace on the right for the blueprint canvas and calculations panel.
- Click index.html in the Explorer sidebar to open it.
- Copy the following HTML structure into the file:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blueprint Studio</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="app">
<aside class="sidebar">
<h1 class="logo">Blueprint Studio</h1>
<div id="client-list"></div>
<button id="generate-btn" disabled>Generate Blueprint</button>
</aside>
<main class="workspace">
<div class="canvas-container">
<canvas id="blueprint-canvas"></canvas>
</div>
<div id="calc-panel" class="calc-panel hidden">
<h2>Structural Calculations</h2>
<div id="calc-results"></div>
</div>
</main>
</div>
<script src="app.js"></script>
</body>
</html>
What does this HTML do?
- The <aside class="sidebar"> holds the client list and the generate button. The button starts disabled because no client is selected yet.
- The <main class="workspace"> contains a <canvas> element for drawing blueprints and a hidden calc-panel for structural calculations.
- The <link> tag connects the CSS file, and the <script> tag at the bottom loads the JavaScript after the page structure is ready.
- Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
- Open the blueprint-studio folder on your Desktop in Finder (macOS) or File Explorer (Windows).
- Double-click index.html to open it in Chrome.
You should see unstyled text on a white background: "Blueprint Studio" as a heading, a "Generate Blueprint" button, and "Structural Calculations" below it. It looks bare, but the HTML skeleton is in place.
Seeing a blank page or raw HTML code?
If you see a blank page, confirm you saved the file in VS Code. A white dot on the file tab means unsaved changes.
If your browser shows raw HTML code instead of a rendered page, the file may have opened as a text file. Right-click index.html and choose Open With then select Google Chrome.
help me get index.html to display in Chrome
✔️ Awesome, I've got everything!
Great. Make sure you have saved index.html and keep the Chrome tab open. You will refresh it as you add styles next.
ⓧ I'd like to double check the full code
Here is the complete index.html file. Compare it against yours character by character.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Blueprint Studio</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="app">
<aside class="sidebar">
<h1 class="logo">Blueprint Studio</h1>
<div id="client-list"></div>
<button id="generate-btn" disabled>Generate Blueprint</button>
</aside>
<main class="workspace">
<div class="canvas-container">
<canvas id="blueprint-canvas"></canvas>
</div>
<div id="calc-panel" class="calc-panel hidden">
<h2>Structural Calculations</h2>
<div id="calc-results"></div>
</div>
</main>
</div>
<script src="app.js"></script>
</body>
</html>
Style the dark blueprint theme and add client data
The page is unstyled right now. You will build up the styles.css file in sections, refreshing Chrome after each one so you can watch the dark engineering theme take shape.
- In VS Code, click styles.css in the Explorer sidebar to open it.
- Add the page foundation styles by pasting the following into styles.css:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #0d1b2a;
color: #c8d6e5;
height: 100vh;
overflow: hidden;
}
.app {
display: flex;
height: 100vh;
}
.sidebar {
width: 280px;
background: #121e2e;
border-right: 1px solid #1e3a5f;
padding: 20px;
display: flex;
flex-direction: column;
gap: 12px;
}
What does this CSS do?
The * reset strips default margins and padding from every element. The dark background: #0d1b2a on the body sets the navy base color for the entire app.
The .app container uses display: flex to place the sidebar and workspace side by side. The sidebar gets a fixed 280px width and a slightly lighter background to stand out from the workspace.
- Save styles.css and refresh Chrome.
The page is now dark navy. You should see a slightly lighter panel on the left side where the sidebar lives.
- Add the logo and client card styles below the sidebar block by pasting this code at the bottom of styles.css:
.logo {
font-size: 18px;
font-weight: 700;
color: #4a9ece;
letter-spacing: 1px;
text-transform: uppercase;
padding-bottom: 12px;
border-bottom: 1px solid #1e3a5f;
}
.client-card {
padding: 12px;
background: #162232;
border: 1px solid #1e3a5f;
border-radius: 6px;
cursor: pointer;
transition: all 0.2s;
}
.client-card:hover {
border-color: #4a9ece;
}
.client-card.selected {
border-color: #4a9ece;
background: #1a3050;
}
- Save styles.css and refresh Chrome.
"Blueprint Studio" now appears in blue uppercase at the top of the sidebar, with a thin border line underneath. The client card styles are loaded, but no cards are visible yet because the JavaScript that creates them comes in Step 2.
- Add the card typography and generate button styles at the bottom of styles.css:
.client-card h3 {
font-size: 14px;
color: #e0e8f0;
margin-bottom: 4px;
}
.client-card p {
font-size: 12px;
color: #7eb8da;
}
.client-card .address {
font-size: 11px;
color: #5a7a8f;
margin-top: 2px;
}
#generate-btn {
margin-top: auto;
padding: 12px;
background: #1a5a8a;
color: #e0e8f0;
border: 1px solid #4a9ece;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
transition: all 0.2s;
}
What does margin-top: auto do?
The sidebar uses display: flex with flex-direction: column. Setting margin-top: auto on the button pushes it to the very bottom of the sidebar, no matter how many client cards sit above it.
- Save styles.css and refresh Chrome.
The "Generate Blueprint" button now sits at the bottom of the sidebar in a styled blue box. It appears grayed out because of the disabled attribute in the HTML.
- Add the button state styles and workspace layout at the bottom of styles.css:
#generate-btn:hover:not(:disabled) {
background: #2070a8;
}
#generate-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
.workspace {
flex: 1;
display: flex;
flex-direction: column;
padding: 20px;
gap: 16px;
overflow-y: auto;
}
.canvas-container {
background: #0a1628;
border: 1px solid #1e3a5f;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
padding: 12px;
}
canvas {
border-radius: 4px;
}
- Save styles.css and refresh Chrome.
The workspace area to the right of the sidebar now shows a dark rounded container where the blueprint canvas will render. The button fades to 40% opacity because of the disabled style.
- Add the calculations panel styles at the bottom of styles.css:
.calc-panel {
background: #121e2e;
border: 1px solid #1e3a5f;
border-radius: 8px;
padding: 20px;
}
.calc-panel.hidden {
display: none;
}
.calc-panel h2 {
font-size: 14px;
color: #4a9ece;
text-transform: uppercase;
letter-spacing: 1px;
margin-bottom: 12px;
}
.calc-note {
font-size: 12px;
color: #7eb8da;
margin-bottom: 12px;
font-style: italic;
}
- Add the data table styles at the bottom of styles.css:
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th,
td {
padding: 8px 12px;
text-align: left;
border-bottom: 1px solid #1e3a5f;
}
th {
color: #4a9ece;
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
color: #c8d6e5;
}
tfoot td {
font-weight: 700;
color: #e0e8f0;
border-top: 2px solid #4a9ece;
}
Why style a panel and table that aren't visible yet?
The calculations panel starts with a hidden class that sets display: none. The table does not exist in the DOM until JavaScript creates it. These styles are ready and waiting so the panel looks correct the moment it appears in Step 4.
- Save styles.css and refresh Chrome to confirm the page still renders correctly.
The page should look the same as before: dark sidebar with the heading and grayed-out button on the left, and the dark canvas container on the right. The new panel and table styles will become visible when those elements are populated later.
Sidebar or button looks broken?
Check that each CSS block has matching opening { and closing } braces. A missing brace causes every style below it to break.
Confirm the <link> tag in index.html says href="styles.css" and that the file is named exactly styles.css (not style.css or Styles.css).
help me fix my Blueprint Studio CSS styling
✔️ Awesome, I've got everything!
Great. Make sure styles.css is saved and you see the dark theme in Chrome.
ⓧ I'd like to double check the full code
Your styles.css should contain these sections in order. Scroll up through the chunks above to compare each one:
- Page foundation: *, body, .app, .sidebar.
- Sidebar components: .logo, .client-card (including hover and selected states).
- Card typography and button: .client-card h3, p, .address, #generate-btn.
- Button states and workspace: #generate-btn:hover, #generate-btn:disabled, .workspace, .canvas-container, canvas.
- Calculations panel: .calc-panel, .calc-panel.hidden, .calc-panel h2, .calc-note.
- Table styles: table, th/td, tfoot td.
Now you will add the client data that drives the entire application. Each client has a name, a building, an address, project notes, and an array of rooms with positions, dimensions, occupancy types, and floor construction types.
- Click app.js in the Explorer sidebar to open it.
- Start building the client data array by pasting the first client into app.js:
// ---- Client Data ----
var clients = [
{
id: 1,
name: "Heritage Row Partners",
building: "Victorian Townhouse",
address: "142 Elm St, Hartford, CT",
notes: "Historical preservation - load-bearing masonry walls, original hardwood floors",
rooms: [
{ name: "Parlor", x: 0, y: 0, w: 16, h: 14, use: "residential", floor: "hardwood" },
{ name: "Dining Room", x: 16, y: 0, w: 14, h: 14, use: "residential", floor: "hardwood" },
{ name: "Kitchen", x: 16, y: 14, w: 14, h: 10, use: "residential", floor: "tile" },
{ name: "Hallway", x: 12, y: 14, w: 4, h: 10, use: "residential", floor: "hardwood" },
{ name: "Study", x: 0, y: 14, w: 12, h: 10, use: "residential", floor: "hardwood" }
]
},
];
What does each room property mean?
- x and y set where the room sits on the floor plan (in feet from the top-left corner).
- w and h define the room's width and height in feet.
- use is the occupancy type (residential, office, retail, or storage). This determines the live load applied to that room in the structural calculations.
- floor is the construction type (hardwood, tile, or concrete). This determines the dead load of the floor itself.
- Save app.js.
- In Chrome, open the browser console by pressing Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
- Type clients.length and press Enter.
You should see 1 in the console. That confirms the JavaScript file is loading and the first client object is valid.
- Back in VS Code, find the line ]; at the bottom of app.js.
- Add the remaining two clients above that ]; line by pasting this code:
{
id: 2,
name: "Coastal Living LLC",
building: "Seaside Residence",
address: "88 Ocean Dr, Myrtle Beach, SC",
notes: "Waterproofing assessment - elevated foundation, coastal exposure category D",
rooms: [
{ name: "Great Room", x: 0, y: 0, w: 20, h: 16, use: "residential", floor: "concrete" },
{ name: "Kitchen", x: 20, y: 0, w: 14, h: 12, use: "residential", floor: "tile" },
{ name: "Master Bed", x: 20, y: 12, w: 14, h: 12, use: "residential", floor: "hardwood" },
{ name: "Bathroom", x: 0, y: 16, w: 10, h: 8, use: "residential", floor: "tile" },
{ name: "Porch", x: 10, y: 16, w: 24, h: 8, use: "residential", floor: "concrete" }
]
},
{
id: 3,
name: "Mason & Stone Group",
building: "Commerce Hall",
address: "310 Market St, Charleston, SC",
notes: "Masonry restoration (1924) - evaluate mortar joint shear strength per ASTM C1531",
rooms: [
{ name: "Showroom", x: 0, y: 0, w: 24, h: 18, use: "retail", floor: "concrete" },
{ name: "Office", x: 24, y: 0, w: 12, h: 10, use: "office", floor: "hardwood" },
{ name: "Storage", x: 24, y: 10, w: 12, h: 14, use: "storage", floor: "concrete" },
{ name: "Workshop", x: 0, y: 18, w: 16, h: 14, use: "storage", floor: "concrete" },
{ name: "Restroom", x: 16, y: 18, w: 8, h: 8, use: "residential", floor: "tile" }
]
}
Why three different clients?
Each client represents a different construction domain. Heritage Row Partners is a historical preservation project with hardwood floors. Coastal Living LLC is a waterproofing assessment for a seaside home on a concrete slab. Mason & Stone Group is a commercial masonry restoration with retail, office, and storage spaces.
The mix of occupancy types (residential, retail, office, storage) and floor types (hardwood, tile, concrete) ensures the structural load calculations in Step 4 produce different values for each building.
- Save app.js.
- Refresh Chrome and type clients.length in the console again.
The console should now return 3. All three client profiles are loaded and ready.
Console shows an error or clients is not defined?
Check the console for a red error message. A syntax error usually means a missing comma between client objects or a misplaced bracket. Each client object ends with }, except the last one, which ends with just }.
If the console says clients is not defined, confirm that index.html contains <script src="app.js"></script> and that the file is named exactly app.js.
help me debug my Blueprint Studio client data
✔️ Awesome, I've got everything!
Great. Confirm app.js is saved and the console shows 3 for clients.length.
ⓧ I'd like to double check the full code
Your app.js should contain the full clients array with all three client objects in order. Scroll up through the two JavaScript chunks above and confirm:
- Client 1: Heritage Row Partners (Victorian Townhouse) with 5 rooms: Parlor, Dining Room, Kitchen, Hallway, Study.
- Client 2: Coastal Living LLC (Seaside Residence) with 5 rooms: Great Room, Kitchen, Master Bed, Bathroom, Porch.
- Client 3: Mason & Stone Group (Commerce Hall) with 5 rooms: Showroom, Office, Storage, Workshop, Restroom.
- The array opens with var clients = [ and closes with ];.
Before you look at the final result, what do you expect the page to look like right now? You have the HTML structure, a full dark theme, and client data loaded in memory. But you have not written any rendering logic yet.
- Close the console panel and look at the page in Chrome.
You should see a dark-themed page with "Blueprint Studio" in blue uppercase in the sidebar and a grayed-out "Generate Blueprint" button at the bottom. The workspace area on the right shows the dark canvas container. No client cards appear yet because the rendering function that turns the data array into clickable cards comes in the next step.
Your project skeleton is complete. Three files, a dark theme, and structured data for three real clients. Next up, you will bring the sidebar to life by rendering client cards and drawing your first floor plan on the canvas.
Build the Client Selector and Basic Floor Plan
Your three files are in place and the dark theme is rendering, but the sidebar is empty and the canvas is blank. The client data exists in app.js, but nothing reads it or displays it yet.
This step connects the data to the page. You will populate the sidebar with clickable client cards and draw each client's rooms on the canvas as a first proof that structured data can drive a visual floor plan.
In this step, get ready to:
- Render client cards in the sidebar from the data array.
- Draw a basic floor plan on the canvas when the generate button is clicked.
Render the client cards
The clients array already holds three clients with their building info and room data. You need a function that reads this array and creates a clickable card for each client in the sidebar.
- In app.js, add this code below the closing ]; of the clients array:
// ---- State ----
var selectedClient = null;
// ---- Client List ----
function renderClientList() {
var list = document.getElementById("client-list");
// Clear existing cards so the list redraws fresh
list.innerHTML = "";
clients.forEach(function (client) {
var card = document.createElement("div");
// Highlight the selected card with the 'selected' class
card.className = "client-card" + (selectedClient === client ? " selected" : "");
card.innerHTML =
"<h3>" + client.name + "</h3>" +
"<p>" + client.building + "</p>" +
'<p class="address">' + client.address + "</p>";
// When clicked, mark this client as selected and re-render
card.addEventListener("click", function () {
selectedClient = client;
document.getElementById("generate-btn").disabled = false;
renderClientList();
});
list.appendChild(card);
});
}
What does this code do?
- selectedClient tracks which client the user has clicked. It starts as null because no card is selected yet.
- renderClientList() loops through the clients array and creates a div.client-card for each one, showing the name, building, and address.
- Clicking a card sets selectedClient, enables the generate button, and re-renders the list so the clicked card gets the selected CSS class.
- Add the initialization code at the very bottom of app.js, below renderClientList():
// ---- Init ----
function init() {
// Set the canvas drawing area to 800x600 pixels
var canvas = document.getElementById("blueprint-canvas");
canvas.width = 800;
canvas.height = 600;
// Populate the sidebar with client cards
renderClientList();
}
document.addEventListener("DOMContentLoaded", init);
What does this code do?
- init() sizes the canvas to 800 by 600 pixels, then calls renderClientList() to populate the sidebar.
- DOMContentLoaded fires after the HTML is fully parsed, so the canvas and sidebar elements are guaranteed to exist before init() tries to find them.
- Save app.js.
- Refresh index.html in Chrome. Three client cards now appear in the sidebar: Heritage Row Partners, Coastal Living LLC, and Mason & Stone Group.
- Click any card. It highlights with a blue border and the Generate Blueprint button activates (no longer faded out).
No cards in the sidebar?
- Make sure you saved app.js after adding both code blocks. Unsaved changes will not appear.
- Open DevTools with Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows) and check the Console tab for red error messages. A typo in the code will show the line number.
- Verify that the renderClientList() function sits below the clients array, and the init() function is at the very bottom of the file.
Help me debug why my client cards are not showing up in the sidebar.
Draw rooms on the canvas
Clicking a card selects it, but clicking the generate button does nothing yet. You need a function that reads the selected client's room data and draws each room on the HTML Canvas as a scaled rectangle.
- In app.js, add the generateBlueprint() function between renderClientList() and init():
// ---- Blueprint Rendering ----
function generateBlueprint() {
if (!selectedClient) return;
var canvas = document.getElementById("blueprint-canvas");
var ctx = canvas.getContext("2d");
var rooms = selectedClient.rooms;
// Find the building's total width and height from room data
var maxX = 0;
var maxY = 0;
rooms.forEach(function (r) {
if (r.x + r.w > maxX) maxX = r.x + r.w;
if (r.y + r.h > maxY) maxY = r.y + r.h;
});
// Scale the floor plan to fit the canvas with 80px padding
var pad = 80;
var scaleX = (canvas.width - pad * 2) / maxX;
var scaleY = (canvas.height - pad * 2) / maxY;
var scale = Math.min(scaleX, scaleY);
var ox = (canvas.width - maxX * scale) / 2;
var oy = pad;
// Clear the canvas with a white background
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
How does the scaling work?
Each client's rooms are defined in feet (e.g. a 16 x 14 Parlor). The canvas is 800 x 600 pixels. The function finds the building's total footprint (maxX by maxY), then calculates a scale factor that fits the entire footprint inside the canvas with 80 pixels of padding on each side.
Using Math.min(scaleX, scaleY) ensures the floor plan fits both horizontally and vertically without stretching. The ox offset centers the plan horizontally on the canvas.
- Now add the room drawing loop. In generateBlueprint(), find the closing } of the function and paste this code just above it:
// Draw each room as a labeled rectangle
rooms.forEach(function (room) {
var x = room.x * scale + ox;
var y = room.y * scale + oy;
var w = room.w * scale;
var h = room.h * scale;
// Light blue fill for each room
ctx.fillStyle = "rgba(173, 216, 230, 0.5)";
ctx.fillRect(x, y, w, h);
// Dark border around each room
ctx.strokeStyle = "#333";
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
// Room name centered inside the rectangle
ctx.fillStyle = "#000";
ctx.font = "bold 12px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(room.name, x + w / 2, y + h / 2);
});
What does this code do?
- For each room, it converts the room's position and size from feet into pixel coordinates using the scale factor and offsets calculated above.
- fillRect() draws a light blue rectangle for the room area. strokeRect() draws a dark border around it.
- fillText() places the room name at the exact center of the rectangle.
The drawing function is ready. Now you need to connect the generate button to it.
- In init(), find this line:
renderClientList();
- Add this line below it to wire the button:
document.getElementById("generate-btn").addEventListener("click", generateBlueprint);
Your init() function should now look like this:
function init() {
var canvas = document.getElementById("blueprint-canvas");
canvas.width = 800;
canvas.height = 600;
renderClientList();
document.getElementById("generate-btn").addEventListener("click", generateBlueprint);
}
- Save app.js.
Before you refresh the page, think about what you expect to happen when you select a client and click Generate Blueprint. How many rooms should appear for Coastal Living LLC?
- Refresh index.html in Chrome.
- Click Coastal Living LLC in the sidebar.
- Click Generate Blueprint.
Five rooms appear on a white canvas: Great Room, Kitchen, Master Bed, Bathroom, and Porch, each labeled with its name. The rooms are light blue rectangles with dark borders.
- Click Mason & Stone Group and click Generate Blueprint again. The layout changes to show Showroom, Office, Storage, Workshop, and Restroom.
Canvas stays blank after clicking generate?
- Open the Console with Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows). If you see a red error mentioning generateBlueprint, confirm the function is defined between renderClientList() and init() in app.js.
- Make sure the addEventListener line inside init() references generateBlueprint without parentheses. Adding () would call the function immediately instead of waiting for a click.
- Check that the room drawing loop (rooms.forEach) is inside generateBlueprint(), not after its closing brace.
Help me figure out why clicking Generate Blueprint does not draw anything on the canvas.
✔️ Awesome, I've got everything!
Double check you have saved app.js.
ⓧ I'd like to double check the full code
Your app.js is longer than a quick glance, so scroll through and confirm these sections appear in order:
- // ---- Client Data ---- with the full clients array (three clients, unchanged from Step 1).
- // ---- State ---- with var selectedClient = null;.
- // ---- Client List ---- with the renderClientList() function.
- // ---- Blueprint Rendering ---- with the generateBlueprint() function, ending with the rooms.forEach loop that draws rectangles.
- // ---- Init ---- with init() containing canvas sizing, renderClientList(), and the addEventListener line for the generate button.
- document.addEventListener("DOMContentLoaded", init); as the very last line.
index.html and styles.css are unchanged from Step 1.
The floor plan works, but it looks like colored rectangles on a whiteboard. Next, you will transform it into a professional blueprint with a dark background, a measured grid, dimension annotations, and a title block.
Transform into a Blueprint with Dimensions
Your floor plan works. Clicking a client and generating draws their rooms on the canvas. But right now it looks like colored rectangles on a whiteboard, not something you would hand to a construction professional.
Construction drawings use a specific visual language: dark backgrounds, measured grids, dimension annotations, and project identification blocks. In this step, you will transform the basic floor plan into a drawing that reads like an engineering document.
In this step, get ready to:
- Replace the white canvas with a dark blueprint background and measured grid.
- Restyle each room with blueprint colors, dimension annotations, and area labels.
- Add overall dimension lines with tick marks and a project title block.
Switch to the dark blueprint background and grid
Real blueprints use a dark background so that white outlines and cyan annotations stand out. The white canvas from Step 2 needs to go.
- In app.js, find these two lines inside generateBlueprint():
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, canvas.width, canvas.height);
- Replace those two lines with the following, which changes the background to dark navy and adds a grid overlay call:
ctx.fillStyle = "#0a1628";
ctx.fillRect(0, 0, canvas.width, canvas.height);
drawGrid(ctx, canvas.width, canvas.height);
- Add the drawGrid() function below generateBlueprint() by pasting this code:
function drawGrid(ctx, w, h) {
ctx.strokeStyle = "rgba(50, 100, 150, 0.12)";
ctx.lineWidth = 0.5;
// Draw vertical grid lines every 20px
for (var x = 0; x < w; x += 20) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
ctx.stroke();
}
// Draw horizontal grid lines every 20px
for (var y = 0; y < h; y += 20) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(w, y);
ctx.stroke();
}
}
What does this code do?
The background fill uses #0a1628, a deep navy that matches professional blueprint paper.
The drawGrid() function loops across the canvas in 20-pixel increments, drawing faint lines at very low opacity. The grid gives the blueprint a measured, engineered feel without competing with the room outlines.
- Save app.js and refresh your browser.
- Click any client, then click Generate Blueprint. You should see a dark navy canvas with subtle grid lines. The rooms still appear in the old light-blue-on-dark style.
Canvas still white?
Make sure you replaced #ffffff with #0a1628 in the ctx.fillStyle line inside generateBlueprint(). Check that you saved the file after editing.
If you see no rooms at all, open the browser console (Cmd+Option+J on macOS, Ctrl+Shift+J on Windows) and look for an error. A typo in the drawGrid call or function will stop the rest of the code from running.
Help me debug my blueprint background
Restyle rooms with blueprint colors and dimensions
The rooms are still drawn inline with the old colors and have no dimension labels. You will extract the room drawing into its own function that uses blueprint-style colors and adds measurements.
- Add the drawRoom() function below drawGrid() by pasting this code:
function drawRoom(ctx, room, scale, ox, oy) {
var x = room.x * scale + ox;
var y = room.y * scale + oy;
var w = room.w * scale;
var h = room.h * scale;
var area = room.w * room.h;
ctx.fillStyle = "rgba(30, 60, 100, 0.25)"; // Translucent blue fill
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = "#c8dce8"; // Light gray wall outlines
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
// Room name in white
ctx.fillStyle = "#e0e8f0";
ctx.font = "bold 12px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(room.name, x + w / 2, y + h / 2 - 14);
// Cyan dimension text (e.g. "16' x 14'")
ctx.font = "11px monospace";
ctx.fillStyle = "#4a9ece";
ctx.fillText(room.w + "' x " + room.h + "'", x + w / 2, y + h / 2 + 2);
// Muted blue area text
ctx.font = "10px monospace";
ctx.fillStyle = "#5a8aaa";
ctx.fillText(area + " sq ft", x + w / 2, y + h / 2 + 16);
}
What does this code do?
- Each room gets a translucent blue fill so you can see the grid behind it, with light gray wall outlines that pop against the dark background.
- Three lines of text stack vertically inside each room: the name in white, dimensions in cyan (like 16' x 14'), and area in muted blue.
- The offset values (- 14, + 2, + 16) spread the text so the three labels don't overlap.
Now you need to update generateBlueprint() to use this new function instead of the old inline drawing code.
- In generateBlueprint(), find the rooms.forEach block that starts with:
rooms.forEach(function (room) {
var x = room.x * scale + ox;
var y = room.y * scale + oy;
var w = room.w * scale;
var h = room.h * scale;
ctx.fillStyle = "rgba(173, 216, 230, 0.5)";
ctx.fillRect(x, y, w, h);
ctx.strokeStyle = "#333";
ctx.lineWidth = 2;
ctx.strokeRect(x, y, w, h);
ctx.fillStyle = "#000";
ctx.font = "bold 12px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(room.name, x + w / 2, y + h / 2);
});
- Replace that entire block with this three-line version that calls your new drawRoom() function:
rooms.forEach(function (room) {
drawRoom(ctx, room, scale, ox, oy);
});
- Save app.js and refresh your browser.
- Select a client and click Generate Blueprint. Each room now shows a translucent blue fill with light gray walls, a white name label, cyan dimensions, and a muted blue area readout.
Rooms disappeared?
Make sure you deleted the entire old rooms.forEach block (from rooms.forEach(function (room) { through its closing });) and replaced it with the new three-line version. Having both the old and new code will cause duplicated or broken drawing.
Check the browser console for errors. A mismatched parenthesis or brace in the replacement is the most common cause.
Help me fix my drawRoom function
Add dimension annotations along the edges
Each room shows its own dimensions, but a construction drawing also needs the total building footprint annotated along the edges. This tells the reader the overall width and height at a glance.
- Start the drawOverallDimensions() function below drawRoom() by pasting this code, which draws the bottom dimension line showing total building width:
function drawOverallDimensions(ctx, maxX, maxY, scale, ox, oy) {
var totalW = maxX * scale;
var totalH = maxY * scale;
var d = 30; // Offset distance from the floor plan
// Dimension line styling
ctx.strokeStyle = "#4a9ece";
ctx.fillStyle = "#4a9ece";
ctx.lineWidth = 1;
ctx.font = "11px monospace";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// Bottom dimension line showing total building width
var by = oy + totalH + d;
ctx.beginPath();
ctx.moveTo(ox, by);
ctx.lineTo(ox + totalW, by);
ctx.stroke();
// Left tick mark
ctx.beginPath();
ctx.moveTo(ox, by - 5);
ctx.lineTo(ox, by + 5);
ctx.stroke();
// Right tick mark
ctx.beginPath();
ctx.moveTo(ox + totalW, by - 5);
ctx.lineTo(ox + totalW, by + 5);
ctx.stroke();
ctx.fillText(maxX + "'-0\"", ox + totalW / 2, by + 14);
}
What does this code do?
- The function calculates pixel positions from the building's foot dimensions using the same scale factor that generateBlueprint() computed.
- A horizontal line runs 30 pixels below the floor plan with small tick marks at each end, mimicking an architectural dimension line.
- The label uses architectural notation like 30'-0" (30 feet, zero inches).
- Now complete the function by adding the right-side dimension line. Find the closing } of drawOverallDimensions() and paste this code directly above it:
// Right dimension line showing total building height
var rx = ox + totalW + d;
ctx.beginPath();
ctx.moveTo(rx, oy);
ctx.lineTo(rx, oy + totalH);
ctx.stroke();
// Top tick mark
ctx.beginPath();
ctx.moveTo(rx - 5, oy);
ctx.lineTo(rx + 5, oy);
ctx.stroke();
// Bottom tick mark
ctx.beginPath();
ctx.moveTo(rx - 5, oy + totalH);
ctx.lineTo(rx + 5, oy + totalH);
ctx.stroke();
// Rotate text vertically for the height label
ctx.save();
ctx.translate(rx + 14, oy + totalH / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText(maxY + "'-0\"", 0, 0);
ctx.restore();
Why rotate the text?
The HTML Canvas API draws text horizontally by default. For the right-side height label, ctx.save() preserves the current drawing state, ctx.rotate(-Math.PI / 2) turns the coordinate system 90 degrees counterclockwise, and ctx.restore() resets everything so later drawing calls are unaffected.
- Wire the dimension function into generateBlueprint(). Find the rooms.forEach block you updated earlier, and add this line directly below its closing });:
drawOverallDimensions(ctx, maxX, maxY, scale, ox, oy);
- Save app.js and refresh your browser.
- Select Heritage Row Partners and click Generate Blueprint. You should see a dimension line along the bottom edge reading 30'-0" and one along the right edge reading 24'-0", each with small tick marks at the ends.
Dimension lines not showing?
Make sure the drawOverallDimensions call is inside generateBlueprint(), after the rooms.forEach block but before the function's closing }. Placing it outside the function means it runs before a client is selected.
Check that you pasted the right-side dimension code above the closing } of drawOverallDimensions, not below it. The code needs to be inside the function body.
Help me debug my dimension lines
Add the title block
Every engineering drawing has a title block in the corner identifying the project, the client, and the date. This is the last piece that makes your canvas output look like a real blueprint.
- Add the drawTitleBlock() function below drawOverallDimensions() by pasting this code:
function drawTitleBlock(ctx, w, h) {
var bw = 250;
var bh = 75;
var bx = w - bw - 12; // 12px from right edge
var by = h - bh - 12; // 12px from bottom edge
// Semi-transparent dark background
ctx.fillStyle = "rgba(10, 22, 40, 0.92)";
ctx.fillRect(bx, by, bw, bh);
ctx.strokeStyle = "#c8dce8";
ctx.lineWidth = 1.5;
ctx.strokeRect(bx, by, bw, bh);
// Building name in uppercase
ctx.fillStyle = "#e0e8f0";
ctx.font = "bold 12px monospace";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillText(selectedClient.building.toUpperCase(), bx + 10, by + 8);
// Client details
ctx.font = "10px monospace";
ctx.fillStyle = "#7eb8da";
ctx.fillText("Client: " + selectedClient.name, bx + 10, by + 25);
ctx.fillText(selectedClient.address, bx + 10, by + 39);
// Today's date
var today = new Date();
var dateStr =
(today.getMonth() + 1) + "/" + today.getDate() + "/" + today.getFullYear();
ctx.fillText("Date: " + dateStr, bx + 10, by + 53);
}
What does this code do?
- A 250x75 pixel box is positioned 12 pixels from the bottom-right corner of the canvas, matching where title blocks sit on real engineering drawings.
- The building name is pulled from selectedClient.building and converted to uppercase with .toUpperCase(), which is the convention for drawing titles.
- The date is generated live from new Date() so the blueprint always shows the current date.
- Wire the title block into generateBlueprint(). Find the drawOverallDimensions call you just added, and add this line directly below it:
drawTitleBlock(ctx, canvas.width, canvas.height);
Before you test, think about what you expect to see. You have a dark background, a grid, styled rooms with dimensions, edge-to-edge measurement lines, and now a title block. What will the lower-right corner of the canvas look like?
- Save app.js and refresh your browser.
- Select Heritage Row Partners and click Generate Blueprint.
The canvas shows a dark blue background with a fine grid, five rooms outlined in light gray with cyan dimension labels inside each room, overall dimension lines along the bottom and right edges reading 30'-0" and 24'-0", and a title block in the lower-right corner showing VICTORIAN TOWNHOUSE with the client details and today's date.
- Try switching to Mason & Stone Group and generating again to confirm the layout, dimensions, and title block all update for the new client.
Title block missing or in the wrong place?
Check that drawTitleBlock(ctx, canvas.width, canvas.height) is inside generateBlueprint(), right after the drawOverallDimensions call. If you placed it outside the function, it will run on page load before any client is selected and cause an error.
Help me fix my title block
✔️ Awesome, I've got everything!
Great. Double check you have saved app.js.
ⓧ I'd like to double check the full code
Your app.js is too long to display in one block. Scroll up through your file and confirm each section is present in this order:
- The clients array with three client objects (Heritage Row Partners, Coastal Living LLC, Mason & Stone Group).
- The selectedClient state variable.
- The renderClientList() function.
- The updated generateBlueprint() function with #0a1628 background and calls to drawGrid, drawRoom, drawOverallDimensions, and drawTitleBlock.
- The drawGrid() function with vertical and horizontal grid loops.
- The drawRoom() function with translucent fill, wall strokes, room name, dimensions, and area text.
- The drawOverallDimensions() function with both bottom and right dimension lines, tick marks, and architectural notation labels.
- The drawTitleBlock() function with building name, client, address, and date.
- The init() function and the DOMContentLoaded event listener at the bottom.
The canvas now produces a professional-looking construction drawing. Next up, you will add the engineering value that makes this tool genuinely useful: structural load calculations for every room.
Add Structural Load Calculations
Your blueprint now looks like a proper construction document. Dark canvas, measured grid, dimension annotations, and a title block all contribute to a professional presentation.
A pretty floor plan without load data is just art. Construction professionals need to know how much weight each room's floor must support before they can size beams, specify foundations, or file permits. This step adds structural load calculations that turn the visual blueprint into a genuinely useful engineering tool.
In this step, get ready to:
- Add IBC-based live load and dead load lookup tables to your data layer.
- Build a calculations function that computes per-room and total building loads.
- Wire the calculations to appear automatically whenever a blueprint is generated.
Add the structural load lookup tables
Every room in a building carries two types of structural load. Dead load is the permanent weight of the structure itself: the floor, walls, and finishes. Live load is the variable weight from people, furniture, and equipment.
The IBC (International Building Code) publishes minimum live loads by occupancy type in Table 1607.1. Your app will use these standard values to compute loads automatically based on each room's use and floor properties.
- In app.js, find the // ---- State ---- comment.
- Add the two load lookup objects directly above that comment by pasting this code:
// IBC Table 1607.1 minimum live loads (psf)
var LIVE_LOAD = {
residential: 40,
office: 50,
retail: 100,
storage: 125
};
// Estimated dead loads by floor construction type (psf)
var DEAD_LOAD = {
hardwood: 12,
tile: 16,
concrete: 50
};
What do these values mean?
Live load values come from IBC Table 1607.1. Residential rooms need to support 40 psf (pounds per square foot), offices 50 psf, retail spaces 100 psf, and storage areas 125 psf. Higher-traffic and heavier-use spaces require stronger floors.
Dead load values are engineering estimates based on construction materials. A hardwood floor system weighs about 12 psf, tile on frame about 16 psf, and a concrete slab about 50 psf.
- Save app.js. The next chunk uses these tables to compute per-room loads.
Build the calculations function and wire it up
The HTML already has a hidden calc-panel div waiting to display results. You need a function that unhides it, loops through the selected client's rooms, looks up each room's dead and live loads, and builds an HTML table showing the results.
- In app.js, scroll down to find the // ---- Init ---- comment near the bottom of the file.
- Start building the renderCalculations() function by pasting this code directly above // ---- Init ----:
// ---- Calculations ----
function renderCalculations() {
var panel = document.getElementById("calc-panel");
var results = document.getElementById("calc-results");
panel.classList.remove("hidden");
var html = '<p class="calc-note">' + selectedClient.notes + "</p>";
html +=
"<table><thead><tr>" +
"<th>Room</th><th>Area (sq ft)</th><th>Dead Load (psf)</th>" +
"<th>Live Load (psf)</th><th>Total Load (lbs)</th>" +
"</tr></thead><tbody>";
- Continue building the function by pasting this code directly below the previous block:
var totalLoad = 0;
selectedClient.rooms.forEach(function (room) {
var area = room.w * room.h;
var dead = DEAD_LOAD[room.floor] || 12;
var live = LIVE_LOAD[room.use] || 40;
var load = area * (dead + live);
totalLoad += load;
html +=
"<tr><td>" + room.name + "</td>" +
"<td>" + area + "</td>" +
"<td>" + dead + "</td>" +
"<td>" + live + "</td>" +
"<td>" + load.toLocaleString() + "</td></tr>";
});
html +=
"</tbody><tfoot><tr>" +
'<td colspan="4">Total Building Load</td>' +
"<td>" + totalLoad.toLocaleString() + " lbs</td>" +
"</tr></tfoot></table>";
results.innerHTML = html;
}
What does this function do?
- panel.classList.remove("hidden") reveals the calculations panel that the CSS hides by default.
- The first section builds the table header with column names for Room, Area, Dead Load, Live Load, and Total Load.
- The forEach loop computes each room's area (w * h), looks up the dead load from DEAD_LOAD and live load from LIVE_LOAD, then multiplies area by (dead + live) to get total load in pounds.
- The footer row sums every room's load into a single Total Building Load value.
The function exists, but nothing calls it yet. You need to trigger it at the end of generateBlueprint() so the calculations panel appears every time a blueprint is generated.
- Scroll up to the generateBlueprint() function and find this line near the end:
drawTitleBlock(ctx, canvas.width, canvas.height);
}
- Add renderCalculations(); on a new line between drawTitleBlock(ctx, canvas.width, canvas.height); and the closing }, so it looks like this:
drawTitleBlock(ctx, canvas.width, canvas.height);
renderCalculations();
}
Before you test, take a moment to predict. When you select Mason & Stone Group and generate the blueprint, what do you expect the Showroom's total load to be? It is a 24x18 retail space with a concrete slab.
- Save app.js.
- Refresh index.html in Chrome.
- Click the Mason & Stone Group card in the sidebar.
- Click Generate Blueprint.
Below the blueprint canvas, a calculations table appears. The Showroom row shows 432 sq ft, 50 psf dead load (concrete), 100 psf live load (retail), and 64,800 lbs total. The Storage row shows 168 sq ft, 50 psf dead, 125 psf live, and 29,400 lbs total. The footer displays the total building load for the entire building.
How is the Showroom total calculated?
Area is 24 x 18 = 432 sq ft. The concrete slab dead load is 50 psf. The retail live load is 100 psf. Total load = 432 x (50 + 100) = 64,800 lbs. Every room follows the same formula: area multiplied by the sum of its dead and live loads.
- Try switching to Heritage Row Partners and generating again. The table updates with that client's rooms and the project notes change to mention historical preservation.
Calculations table not appearing?
- Check that renderCalculations(); is inside the generateBlueprint() function, after the drawTitleBlock call and before the closing }.
- Make sure LIVE_LOAD and DEAD_LOAD are defined above the // ---- State ---- comment, not inside a function.
- Open the browser console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for a red error message. A missing bracket or semicolon in the pasted code is the most common cause.
- Why isn't my calculations table showing up after clicking Generate Blueprint?
✔️ Awesome, I've got everything!
Great. Double check you have saved app.js.
ⓧ I'd like to double check the full code
The full app.js file is too long to show in one block. Scroll through your file and confirm each of these sections is present, in order:
- // ---- Client Data ---- with the clients array (three clients, each with 5 rooms).
- LIVE_LOAD object with four occupancy types and DEAD_LOAD object with three floor types, both above // ---- State ----.
- // ---- State ---- with var selectedClient = null;
- // ---- Client List ---- with renderClientList().
- // ---- Blueprint Rendering ---- with generateBlueprint() (ending with renderCalculations();), drawGrid(), drawRoom(), drawOverallDimensions(), and drawTitleBlock().
- // ---- Calculations ---- with renderCalculations().
- // ---- Init ---- with init() and the DOMContentLoaded listener.
index.html and styles.css are unchanged from the previous step.
Your Blueprint Studio now delivers both a visual floor plan and the engineering data to back it up. Next, the Secret Mission takes it one step further: exporting the blueprint and calculations as a single downloadable PNG file.
Secret mission
One-Click PNG Export
Your blueprint and calculations look great in the browser. But clients and engineers need a file they can email, print, or attach to a report. Can you add a download button that composites everything into a single PNG image?
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally with no cloud services, APIs, or paid tools. There are no ongoing costs.
Resources you used:
- The blueprint-studio folder on your Desktop containing index.html, styles.css, and app.js.
Decide whether to keep your project files, or delete them entirely.
Keep everything running
No action needed. Choose this if you want to keep experimenting with Blueprint Studio, add more clients, or extend the project further.
- Your blueprint-studio folder stays on your Desktop, ready to open in VS Code or Chrome anytime.
- Since the project is just three local files with no server or database, there is nothing running in the background.
Pause - I'll come back to this later
There is nothing to pause. The project has no running processes, servers, or background services.
- Your files sit in the blueprint-studio folder on your Desktop. Open index.html in Chrome whenever you want to pick up where you left off.
Delete - I don't want to use this again
Remove the project folder to start fresh. This permanently deletes all three files.
- Delete the project folder by running this command in your terminal:
macOS
rm -rf ~/Desktop/blueprint-studio
Windows
rmdir /s /q %USERPROFILE%\Desktop\blueprint-studio
- Confirm the folder is gone by running:
ls ~/Desktop
You should no longer see blueprint-studio in the output.
Nice Work!
Nice Work!
You just built a fully working construction tech MVP with nothing but three files and a browser. Blueprint Studio turns structured client data into professional floor plans and engineering calculations, all without a single dependency or build step.
You've learned how to:
- Build a zero-dependency browser application with three files (index.html, styles.css, app.js) and a client management sidebar that stores and displays building data for three clients.
- Render blueprint-style floor plans on HTML Canvas with rooms drawn to scale, dimension annotations in feet, a measured grid overlay, and a professional title block.
- Compute structural loads per room using IBC Table 1607.1 live load values and construction-type dead load estimates, with a total building load calculated automatically.
- Secret Mission: Added a one-click PNG export that composites the blueprint and structural load summary into a single downloadable image.
Ready to quiz yourself?