← Computer Science
1 / 1
← → move  F fullscreen  Esc read

Capstone · Software route

Capstone: Build It for the Web

Learn to build and host a real site, then use it to ship the thing that's actually yours

Everybody in this class does a capstone. This page is for the ones who want theirs to run in a browser: a game, an app, a tool, a site. You do not need to have built a full website before. The first four class periods teach you how, using your own idea from day one. After that, nobody writes the steps: you build it out, cut scope when you need to, and ship it at a real address.

Is this your capstone?

Your capstone can go several directions. Robotics and competition. Product and fabrication, on the laser cutter or the printers. Media and documentation. Or this one, software, where the thing you build runs in a browser.

Pick this one if you liked restyling your dashboard more than you expected to, or you already have something in your head that you wish existed and it is a game, a tool, a page or an app.

Do not pick this one because it sounds easier. It is not. Ask anybody who has tried to make Chess work.

You are not locked in until your proposal is approved, so if you are torn, talk to Mr. Krigger before you commit.

Start with the Capstone Proposal and the Capstone lesson. This page is the extra guidance for the software route.

What you'll learn

What you need

The AI rule

Same as every assignment in this class, and it matters more here because the project is bigger.

Build your first working version without AI. No ChatGPT, no Claude, no Gemini, no Copilot writing your code. Watching somebody else solve a problem does not teach you to solve it. The fifteen minutes you spend staring at a broken layout before you work out why is where the learning actually happens.

Where to look: W3Schools for a quick example, MDN for the real explanation, freeCodeCamp for a full course, YouTube tutorials, Stack Overflow, your classmates, and Mr. Krigger.

Once your first working version exists and Mr. Krigger has seen it, you can use AI to push further: better CSS, animations, a feature you could not figure out. Two conditions. You have to say what you used it for, and you have to be able to explain every line of it on demand. If you cannot walk through a section of your own code, that section does not count toward your grade.

One more thing worth saying plainly: a large share of the capstone rubric is explaining your work, and another chunk is creativity and ambition. Neither of those can be generated. A project that was mostly typed by a machine scores badly before anybody even looks at whether it runs.

What counts

Anything that runs in a browser and does something.

Something for an audience. A page that runs at a booth or an event: spin-the-wheel, a trivia round, a live high score board, a costume vote, a photo wall.

Something the lab or the school actually needs. A printer queue for the lab. A tool checkout board. A RoboMaster battery tracker so nobody grabs a dead one again. A bell schedule that is readable from across the room.

Something about here. A hurricane prep checklist your family would really use. A carnival countdown. A page for a local business you like that does not have one. Tide and beach conditions. A food map of St. Croix.

What counts, continued

Games. Students in this class have built Chess, Tetris, Snake, Flappy Bird and Candy Crush. Go look at the projects page. Those were built by people sitting where you are sitting.

Sites with something behind them. A page that talks to your Pi. A scoreboard that survives a refresh. A page that pulls live data from somewhere.

The bar for a capstone: it does something real for somebody who is not you, and it is a genuine step past what you could already do in September. A page with your name and three photos on it is a fine assignment and not a capstone.

Two routes, both legitimate

From scratch. You know roughly how to build it and you want to work it out yourself. Slower at the start, and you will understand every line.

Following a tutorial. You want to build Chess, so you find a Chess tutorial and follow it. This is a real way to learn and professionals do it constantly.

If you take the tutorial route, two conditions.

  1. Finish somewhere the tutorial did not. Add a feature, change how it works, restyle it completely, make it do something the video never mentioned. A capstone that ends exactly where a YouTube video ends is a transcription, not a project.
  2. You can explain what you typed. Mr. Krigger will point at parts of your code and ask what they do, in the Week 4 walkthrough.

Tell Mr. Krigger which tutorial you are following before you start, so he can tell you if it is teaching you something from 2013.

Week 1

Step 1: Sketch it

Close the laptop. On paper, write:

  1. One sentence: what is this and who is it for?
  2. What are the three or four main chunks of the page, top to bottom?
  3. What is the one thing on it that moves, changes, or responds when you click?

Show Mr. Krigger your sketch before you type anything. This takes five minutes and saves you two periods.

Checkpoint: a sentence, and a stack of sections, on paper.

Step 2: Name your vertical slice and your fallback

Name your vertical slice: one thin piece of the finished thing, working end to end. Not the easy part, the whole path, made narrow: something on screen, something you can do to it, something that happens back.

ProjectThe sliceNot the slice
ChessA board that draws, and one pawn you can move one squareAll the pieces with legal move rules
A study timerA countdown that starts, runs and stopsPresets, history, sound, settings
A printer queueA list you can add one job toLogin, editing, deleting, dates
A trivia gameOne question, one answer, one right or wrongCategories, scores, a leaderboard

Then write down your fallback: the version of your project you would be satisfied with if everything took twice as long as you think it will. You are writing this now, while you are calm, because the day you need it you will not want to.

Checkpoint: your slice and your fallback, both approved by Mr. Krigger.

Step 3: Name it after what it is

Make your folder on the Desktop, and name it after your project. Not mysite, not project, not website.

If you are buildingCall the folder
Chesschess
A tide and conditions pagetide-check
A printer queueprint-queue

Lowercase, hyphens instead of spaces. Inside it, make three empty files and one folder:

chess/
├── index.html      ← the structure. What is on the page
├── styles.css      ← the look. Colors, fonts, spacing
├── script.js       ← the behavior. What happens when you click
└── assets/         ← images and anything else you use

Two rules about filenames, and they matter more than they look. All lowercase, no spaces: beach-photo.jpg, never Beach Photo.jpg. Your Pi runs Linux, and Linux thinks Photo.jpg and photo.jpg are two different files. And everything you use lives inside your project folder: a file pulled straight from your Desktop will be found on this computer and nowhere else.

Step 4: Open it in VS Code

File → Open Folder, pick your project folder, and the whole thing appears in the sidebar.

Turn on two things:

Checkpoint: your three files are in the VS Code sidebar and clicking one opens it.

Step 5: Write the skeleton

Put this in index.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Change This Title</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>

  <header>
    <h1>Change this to what your project is</h1>
    <p>One sentence saying what it does.</p>
  </header>

  <main>
    <section>
      <h2>Section one</h2>
      <p>Replace all of this.</p>
    </section>
  </main>

  <footer>
    <p>Built by YOUR NAME, served from a Raspberry Pi.</p>
  </footer>

  <script src="script.js"></script>
</body>
</html>

Keep the <head> exactly as it is. Those five lines set the character encoding, make the page scale correctly on phones, and connect your CSS file.

Put this in styles.css to prove the connection works:

body {
  background: #111;
  color: #eee;
  font-family: system-ui, sans-serif;
}

Double-click index.html. It opens in your browser straight off the disk, no server needed yet.

Checkpoint: dark background, light text, your heading. White background means your CSS is not connected: check the filename and that it is in the same folder as index.html.
Where students get stuck: a student who opens the wrong editor. VS Code only, it colors the code and underlines an unclosed tag. An image dragged in straight from the Desktop, with the src pointing at the Desktop: works on this computer, breaks on the Pi.

Step 6: Fill in your sections

Build out the chunks from your sketch. The tags that do most of the work:

TagWhat it does
<h1> to <h6>Headings, h1 is biggest. One h1 per page
<p>A paragraph
<img>An image. The alt text is what a screen reader says
<a>A link
<ul> with <li>A bullet list
<div>A box you can style. The class is the name you use in CSS
<button>Something to click
<section>A chunk of the page
Do not know how to do something? Search it the way a developer does: put the language first. "css center a div", "html add a background image", "javascript change text on click". W3Schools first, MDN when you want the real answer.

Day 2

Step 1: Pick your colors and font on purpose

Generate a palette at coolors.co and pick a font at fonts.google.com. Then put your colors at the top of styles.css with names, so you can change your whole site in one place:

:root {
  --bg: #0f1020;
  --text: #f2f2f2;
  --accent: #ff5d73;
}

body {
  margin: 0;
  padding: 24px;
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, sans-serif;
  line-height: 1.6;
}

h1 { color: var(--accent); }

--bg is a variable. var(--bg) means "whatever that is set to." Change it once at the top and every place that uses it changes with it.

Step 2: Space things out

Beginner sites almost always look cramped for the same three reasons. Fix all three:

body { line-height: 1.6; }          /* space between lines of text */

section { margin-bottom: 48px; }    /* space between chunks */

main {
  max-width: 800px;                 /* stop text running the full screen width */
  margin: 0 auto;                   /* and center it */
}

That last one does more for how a page reads than any color choice. Text running 1400 pixels wide is exhausting and nobody can say why. max-width stops it, and margin: 0 auto centers it.

Step 3: Make it work on a phone

Squeeze your browser window as narrow as it goes. Whatever breaks, fix now. The usual culprit:

img { max-width: 100%; height: auto; }   /* images that stay inside the screen */

If you have a row of boxes, this one line handles every screen size:

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
}

"As many columns as fit, each at least 220px wide." Three columns on a laptop, one on a phone, and you did not write a rule for either.

Checkpoint: drag your window from full width down to as narrow as it goes. Nothing runs off the side. No sideways scrollbar at any width.

Day 3

Step 1: Wire up a button

In index.html:

<button id="go">Click me</button>
<p id="output">Nothing yet.</p>

In script.js:

const button = document.getElementById("go");
const output = document.getElementById("output");

button.addEventListener("click", function () {
  output.textContent = "You clicked it.";
});

Three lines, three ideas. getElementById finds a thing on the page by its id. addEventListener("click", ...) says "when this gets clicked, run this." .textContent = ... changes what it says. Almost everything interactive on the web is some version of those three.

Step 2: Type into the console

The console is not just for when something breaks. It is how you find out what broke and where. Open it now: F12 (or right-click the page → Inspect → Console tab).

Type this directly into the console and press Enter:

document.getElementById("go")

It prints the button element back to you. The console is a live line into your page. You can ask it questions any time, not only when something crashes.

Now break something on purpose. In script.js, misspell the id: change "go" to "gox", and click the button. The console shows something like:

Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
    at script.js:4

Step 3: Read the error

Read it in this order:

  1. script.js:4 — which file, which line. Click it and the browser jumps you straight there.
  2. TypeError — the kind of mistake. This one means "I tried to use something that is not the type I expected."
  3. Cannot read properties of null — getElementById came back empty-handed. No element had that id, so it handed back null, and null has no .addEventListener to call.

Wrong id → null → crash on the next line. That chain is the single most common bug in this unit. Once you can read it, you fix it in ten seconds instead of guessing for ten minutes.

Step 4: What the other errors mean

You'll seeIt usually means
ReferenceError: x is not definedYou used a variable before creating it, or typo'd its name
Uncaught SyntaxError: ...A typo in the code itself: a missing } or ), usually just above the line it names
Cannot read properties of undefinedSame idea as null, one step removed: you are reaching into something that does not exist yet
Yellow text, not redA warning. The page still runs, but something is off
Plain black text, no file:lineYour own console.log(...) output, not an error

Fix your id back to "go" before you move on.

Checkpoint: click the button, the text changes. If nothing happens, open the console, find the red line, and read it top to bottom using the steps above before you ask anyone for help.

Step 5: Build your actual slice

Now build the interactive piece of your actual slice, the one you named on Day 1. Some shapes and what to search for:

IdeaThe trick to search for
A piece that moves on click or keypressjavascript move element on click
Random pick (quote, fact, dare, name)javascript random array item
Countdown to a datejavascript countdown timer
Score or tally that goes upjavascript increment variable on click
Filter a list of cardsjavascript filter list buttons
Quiz with a score at the endjavascript quiz score

Break your own slice before it goes further: click everything twice fast, click things out of order, make the window tiny, open it on another computer, hand it to the person next to you and say nothing. Fix what they find.

Checkpoint: your vertical slice does the one thing it is supposed to do, and someone else has used it and you fixed what broke.

Day 4

Step 1: Copy it over

Jump drive into your Pi, drag the whole folder into your YOURNAME folder (the one you made in the dashboard lesson), or from the Terminal:

cp -r /media/$USER/YOUR-DRIVE-NAME/chess ~/YOURNAME/chess

Check it arrived:

ls ~/YOURNAME/chess
Checkpoint: ls lists index.html, styles.css, script.js and assets. If the folder is missing or empty, the drag did not finish. Do it again and wait for it.

Step 2: Write the server

nano ~/YOURNAME/site_server.py
from flask import Flask, send_from_directory
import os

SITE = os.path.expanduser("~/YOURNAME/chess")

app = Flask(__name__)


@app.route("/")
def home():
    return send_from_directory(SITE, "index.html")


@app.route("/<path:filename>")
def other_files(filename):
    return send_from_directory(SITE, filename)


app.run(host="0.0.0.0", port=8000)

On the SITE line, change YOURNAME to your folder name and chess to your project's folder name. Everything else is the same for everybody. Save: Ctrl+O, Enter. Exit: Ctrl+X.

Thonny works for this file too. Open site_server.py with File, Open, click the green Run button, and use the red Stop button to stop it. Set Thonny's Interpreter to your pihealth toolbox first. The steps are on Day 1 of the dashboard lesson.
Notice the port is 8000, not 5000. Your dashboard owns 5000. Two servers cannot share a door, and now you can run both at once.

Steps 3 and 4: Run it, then hand somebody the address

source ~/YOURNAME/pihealth/bin/activate
python3 ~/YOURNAME/site_server.py

Check http://localhost:8000 on the Pi first, then get your Pi's address:

hostname -I

Give someone http://YOUR-PI-IP:8000 and watch them load it on their computer. A phone works if you are testing at home.

Checkpoint, end of Week 1: your slice, running, loaded by somebody else on a device that is not the Pi.
If it is not running today, that is information, not failure. Go to your fallback today, while there is still time for the fallback to be good.

Check on that other computer, not on the Pi:

Why images break on the Pi and nowhere else

Your Mac or Windows computer is case-insensitive. It treats Sunset.JPG and sunset.jpg as the same file, so a typo in your src never shows up.

Linux is case-sensitive. To your Pi, those are two unrelated files, and one of them does not exist. Your site worked for three days and broke the minute it arrived, and you did nothing wrong. Every web developer gets bitten by this exactly once. Now you have had your once.

The fix and the habit: all lowercase, hyphens instead of spaces.

Weeks 2 and 3

Build it out

No steps here. It is your project and nobody knows what it needs except you. What follows is how to work, not what to work on.

Add one thing at a time, and keep it running. Working, change one thing, working, change one thing. If you change five things and it breaks you have five suspects. Keep a copy of the last version that worked, on your jump drive, every single period.

Read the console every time something is wrong. F12, Console tab. It tells you what went wrong and roughly where. Guessing is slower than reading.

Build it out, continued

Get somebody to use it every week. Not at the end. Hand them the laptop and say nothing, and write down where they get stuck. You cannot see your own project the way a stranger does, and by week three you will not even remember trying.

Check phone width every period. Drag the window narrow before you leave. A layout that collapses at 390px is cheap to fix today and expensive to fix the week of the showcase.

Keep a short log. Two lines a period: what you did, what broke. When you write the explanation for the capstone, this log is where that writing comes from.

Checkpoint, mid Block 3: somebody else has used it and you have fixed what they found.

If you took the tutorial route: by now you should be past where the tutorial left off. If you are still typing what the video says with nothing of your own added, stop and add something before you go further.

Week 4

Step 1: Check it the way a stranger would

Step 2: Write the explanation

The capstone asks for a written document. Cover:

Step 3: The walkthrough

Three minutes with your code open. Mr. Krigger points, you explain. Be ready to open any file and explain any line.

You cannot revise for this. It is a conversation about a thing you built, so if you built it you will be fine.

Base and Stretch

Base · every capstone on this route

Your slice grew into your project. It runs, does what you pitched or what your fallback pitched, is hosted on your Pi, and loads on a device that is not the Pi. Your own colors and structure, not a template with the words changed. No horizontal scrolling at phone width. You wrote the explanation and can walk through your own code.

Stretch · required for high school, bonus for middle school

Something the browser cannot do on its own. Pick one: your own API route, state that persists after a reload, real data pulled from somewhere else, or your Pi's own hardware.

The Stretch, step by step

The most common pick is your own API route. Add this above the last line of site_server.py:

import random

ITEMS = ["Put your own list here.", "At least three items.", "They can be anything."]


@app.route("/api/random")
def random_item():
    return {"item": random.choice(ITEMS)}

Then in script.js:

async function loadItem() {
  const response = await fetch("/api/random");
  const data = await response.json();
  document.getElementById("output").textContent = data.item;
}
Restart the server and check http://YOUR-PI-IP:8000/api/random in a browser first. If the JSON is not there, fix that before you touch the page.

A random list is the worked example, so make it something your site actually needs: a score that survives a refresh, a visit counter, live data from the Pi. Choosing state that persists (localStorage or your server), real data from a public API (pick it with Mr. Krigger, not all of them are appropriate or free), or your Pi's own hardware works just as well. Pick whichever one your project actually needs.

Taking your screenshots

On the Pi. Press Print Screen. If nothing happens, open Menu → Accessories and look for Screenshot. Rename it yourname-chess-pi.png and drag it into your YOURNAME folder so it travels with your project.

On a lab computer. Load your site from the Pi's address. Mac is Cmd+Shift+4, then drag a box. Windows is Windows+Shift+S, then drag a box and paste it into any app to save. Name it yourname-chess-on-lab-computer.png.

Make sure the address bar is in the shot. Your screenshot has to prove where the page was loaded from, not just that a page loaded.

Pack your project into one file

A project has many files. Pack them into one zip so you only send one thing. Run this in the Terminal. Change yourname and chess to yours.

cd ~/YOURNAME && python3 -m zipfile -c yourname-chess.zip chess site_server.py

Check it is there, along with your Pi screenshot:

ls ~/YOURNAME
Checkpoint: yourname-chess.zip and yourname-chess-pi.png are both in the list.

Packed again after you change anything? Run the same command again. It replaces the old zip.

Getting it to Schoology: pick a route

The zip and your Pi screenshot are on the Pi. Your lab-computer screenshot is on the lab computer. Try Route 1 first.

RouteWhat happensLogins
1. Schoology on the PiLog in to Schoology on the Pi and upload from there.Two: the Pi, then the lab computer
2. Email yourselfEmail the two files to yourself from the Pi, then open that email on a lab computer.Email on the Pi, then Schoology on the lab computer
3. Download from the PiYour Pi shares your folder for a minute. You save the two files on a lab computer.One: Schoology on the lab computer
Upload, never Create. In Schoology, choose Submit Assignment, then Upload. Create only takes typed text.

Last resort: copy the zip to a jump drive and submit from a lab computer. Name your files so they are readable: yourname-chess.zip.

Route 1: Schoology on the Pi

  1. Open Chromium on the Pi and press Ctrl+Shift+N. That opens a Private window (Chromium calls it Incognito). It forgets your login when you close it.
  2. Log in to Schoology. If Chromium offers to save the password, click Never. Do not tick any box that says remember me.
  3. Open the assignment, click Submit Assignment, then Upload.
  4. Pick yourname-chess.zip and yourname-chess-pi.png from Home → YOURNAME.
  5. Check both names are in the list, then submit.
Before you walk away: click your name at the top right of Schoology and choose Log out. Then close the Private window with the X. The next student uses this Pi.

Then log in on a lab computer and upload your lab-computer screenshot there.

Route 2: Email yourself

  1. On the Pi, press Ctrl+Shift+N in Chromium for a Private window.
  2. Log in to your email. If Chromium offers to save the password, click Never.
  3. Write a new message to yourself. Click the paperclip and attach yourname-chess.zip and yourname-chess-pi.png from your YOURNAME folder.
  4. Send it. Wait until your email says the message was sent.
  5. Log out of your email, then close the Private window. Do this before you do anything else.
  6. On a lab computer, open the email, download both files, and upload them to Schoology with your lab-computer screenshot.
Never leave your email open on a shared Pi. Anyone who sits down next could read it. Many email systems refuse a zip that contains .js files. If yours does, use Route 1 or Route 3.

Route 3: Download from the Pi

Your Pi can hand out your files the same way it hands out your site. Nothing to log in to on the Pi.

On the Pi, in the Terminal:

cd ~/YOURNAME && python3 -m http.server 8001

It prints a line like this, then sits there listening:

Serving HTTP on 0.0.0.0 port 8001 (http://0.0.0.0:8001/) ...

On the lab computer, go to this address with your Pi's IP:

http://YOUR-PI-IP:8001

Right-click yourname-chess.zip and choose Save link as. Do the same for yourname-chess-pi.png.

Now stop it. Click the Terminal on the Pi and press Ctrl+C. While it runs, anyone on the school network can read that folder. We use port 8001 because your dashboard owns 5000 and your site owns 8000.

Upload the files and your lab-computer screenshot to Schoology from that same computer. One login covers everything.

What you hand in

What you hand in, continued

Submissions with a written explanation but no working code, or code but no explanation, are incomplete. Both parts are required.

Words worth knowing

TermWhat it means
Vertical sliceOne thin piece of the finished thing, working end to end
FallbackThe smaller version of your project you agree to build if the real one runs long
Static fileA file the server hands over unchanged: an HTML page, a stylesheet, an image
PortThe door number on a computer. Your dashboard uses 5000, this project uses 8000
RouteA line saying which request a function answers
Relative pathA path starting from where the file is, like assets/photo.jpg. Works anywhere the folder goes
Case sensitiveLinux treats Photo.jpg and photo.jpg as different files. Mac and Windows do not
CSS variableA named color or size defined once in :root and reused with var(--name)
Event listenerCode that waits for something to happen, like a click, and then runs
ConsoleThe browser panel that shows your JavaScript errors. F12
ResponsiveA page that works at any screen width without a separate phone version

Where to look things up

W3SchoolsStart here. Try It Yourself box on almost every page
MDN Web DocsThe real explanation, when W3Schools is not enough
freeCodeCampFree, self paced, from zero
CoolorsColor palettes
Google FontsFree fonts
UnsplashFree photos you are allowed to use
Student projectsWhat your classmates built

When it goes wrong

My CSS is not doing anything.
Is styles.css in the same folder as index.html? Is the <link> line in the <head> spelled exactly right? Did the file save as styles.css and not styles.css.txt?
My browser shows the code as text instead of the page.
The file is not really an .html file. Look at the tab name in VS Code. If it says index.html.txt, rename it in the sidebar.
My images worked on the laptop and not on the Pi.
A capital letter. Check the filename in your src matches the actual file exactly, letter for letter. Rename everything to lowercase.
Address already in use.
Something is already on that port. Your dashboard is probably on 5000, this project is on 8000. If 8000 is also taken, pkill -f YOURNAME/site_server.py and start again.
My JavaScript does nothing.
Press F12 and read the Console, top to bottom, the way Day 3 walks through. "null is not an object" means getElementById could not find that id, so the name in your HTML and the name in your JS do not match. Also check <script src="script.js"></script> is the last thing before </body>.
My email refuses the zip.
Some email systems block zips that contain .js files. Use Route 1 or Route 3 in the Turn in tab instead.
The page loads but it is the old version.
Hard refresh: Ctrl+Shift+R.
Nothing loads from the other computer.
Is that computer on the school network? Is the server still running on the Pi? Did you type :8000 after the IP?

When it goes wrong, continued

I cannot find my jump drive on the Pi.
ls /media/$USER/ lists what is plugged in. If it is empty, unplug and replug, then wait a few seconds.
I deleted something and now it is all broken.
Your jump drive has yesterday's copy. This is exactly why you save to it at the end of every period.
I have no idea what to build.
Go through the projects page and find the one you wish you had made. Build a different version of that. Nobody starts from nothing.
My project is too big and I know it.
Good, that is the useful realization and most people have it too late. Go to your fallback. Today, not next week.
The tutorial I am following does something I do not understand.
Stop and look up that one thing before you type more. A tutorial you are copying without understanding is building you a project you cannot defend.
It worked yesterday and now it does not.
What changed? If you cannot say, that is the actual problem. Copy back the working version from your jump drive and redo the change one piece at a time.