Build an Interactive Rust Tutorial

Build a Rust learning site with hover tooltips and live code execution.

Introduction

30 Second Summary

Think about the last time you stared at code in an unfamiliar programming language. Every symbol looks cryptic until someone explains what each piece means and why it is there.

In this project, you will build an interactive Rust tutorial website where hovering over any token in a code example reveals its type, scope depth, and a plain-English description. You will also wire up a "Run" button that compiles and executes real Rust code from the browser.

What You'll Build

Picture hovering over fn in a code example and instantly seeing a tooltip that says "Declares a function", then clicking Run and watching that code compile and execute on real Rust Playground servers with the output appearing right on the page.

By the end of this project, you'll have:

  • A hover tooltip system that reveals type information, scope depth, and descriptions for every code token in your tutorial examples.
  • Keyword documentation cards that teach Rust concepts like fn, let, and mut in context as they appear for the first time.
  • A working Run button that sends code to the Rust Playground API, compiles it on real servers, and displays the output (or errors) directly on the page.
  • A Dockerized deployment live on GitHub Pages so anyone can visit your tutorial from a public URL.
  • Secret Mission: Add a Pattern Matching lesson with match and enum variants.

Are there any prerequisites?

None. This guide includes setup instructions for all required tools. You do not need any prior experience with Rust, web development, or Docker.

Before We Start

Before We Start...

Before we dive in, let's lock in what you're about to build and why it matters. This is where you commit to the goal so every step that follows has a clear purpose.

Set Up the Project Files

Before tooltips, code execution, or containers can work, you need the raw file structure in place. Every feature you build later depends on three files talking to each other through the browser.

In this step, you will create the project folder, add three files, and write a base HTML skeleton that wires them together.

In this step, get ready to:
  • Create the project folder with three files.
  • Write a base HTML skeleton that connects the stylesheet and script.
  • Verify the page loads in the browser.
Create the project folder and files

You need a dedicated folder on your Desktop to hold the project. Three files make up the entire site: one for structure, one for styling, and one for behaviour.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open the integrated terminal by pressing Ctrl+` (Windows) or Cmd+` (macOS).

Windows users: switch to Git Bash

If your terminal shows PowerShell or Command Prompt, click the dropdown arrow next to the + icon in the terminal panel and select Git Bash. This ensures all the commands in this project work the same across operating systems.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, move into it, and create the three files by running these commands:
mkdir rust-tutorial
cd rust-tutorial
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js listed in the output.

Don't see all three files?

  • Make sure you ran cd rust-tutorial before touch. If you skipped it, the files landed one folder up.
  • Check for typos in the filenames. Run ls again after fixing.
  • help me troubleshoot missing files in my terminal
Add the base HTML skeleton

The HTML file is the entry point for the entire project. It needs to reference the stylesheet and the script so the browser loads all three files together.

  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the rust-tutorial folder, and click Open.
  • In the Explorer sidebar on the left, click index.html to open it in the editor.
  • Paste the following HTML into index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust Interactive Tutorial</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The <title> tag sets the text that appears in the browser tab.
  • The <link> tag tells the browser to load style.css for styling rules.
  • The <script> tag tells the browser to load script.js for JavaScript behaviour.
  • All three files are now connected through this single HTML entry point.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).

Before you open the file in your browser, what do you expect to see on the page?

  • Open index.html in your browser by running this in the terminal:

macOS

open index.html

Windows

start index.html

You should see a blank white page with the text Rust Interactive Tutorial displayed in the browser tab at the top.

Don't see the page title in the tab?

  • Make sure you saved index.html before opening it. Unsaved changes won't appear.
  • Check that the file contains <title>Rust Interactive Tutorial</title> inside the <head> tag.
  • If the browser shows raw HTML text instead of a page, make sure the file is saved with the .html extension.
  • help me get my HTML page to load correctly in the browser

✔️ Awesome, I've got everything!

Great. Make sure you have saved index.html and confirmed the title appears in your browser tab.

ⓧ I'd like to double check the full code

Here is the complete index.html file so far. Make sure yours matches exactly:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust Interactive Tutorial</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="script.js"></script>
</body>
</html>

Your file structure is in place and the browser can load your page. Next up, you will fill the page with structured lessons, styled code blocks, and keyword documentation cards.

Build the Tutorial Page

Your HTML skeleton proves the browser can find the file, but right now the page is empty. Without visible content, there is nothing to make interactive later.

In this step you will fill the page with three Rust lessons, each built from annotated <span> elements that carry metadata about every token. Then you will style everything with a dark theme so the lessons look like a real code editor.

In this step, get ready to:
  • Build the HTML structure for three annotated lessons with keyword documentation cards.
  • Style the page layout, code blocks, and keyword cards with a dark theme.
  • Verify all three lessons render correctly in the browser.
Write the HTML structure for three lessons

Each lesson section contains a title, an explanation paragraph, keyword documentation cards, and a code block made entirely of <span> elements. Every span carries three data- attributes: data-type, data-scope, and data-desc.

What are data attributes?

HTML data-* attributes let you attach custom metadata to any element. JavaScript can read them later to power features like tooltips. The browser ignores them for rendering, so they are invisible until you write code to use them.

  • In index.html, select everything between the opening <body> tag and the closing </body> tag (including the existing <script> tag).
  • Replace that selection with the following code to add the page header and the first lesson:
    <header>
        <h1>Rust Interactive Tutorial</h1>
        <p class="subtitle">Hover any token for details. Click Run to execute.</p>
    </header>

    <main>
        <section class="lesson" id="lesson-hello">
            <h2>1. Hello, World!</h2>
            <p>Every Rust program starts with a <code>main</code> function. The <code>println!</code> macro prints text to the console.</p>

            <div class="keyword-doc">
                <h4>fn</h4>
                <p>Declares a function. Syntax: <code>fn name(params) -&gt; ReturnType { body }</code></p>
            </div>
            <div class="keyword-doc">
                <h4>println!</h4>
                <p>A macro that prints formatted text to standard output, followed by a newline.</p>
            </div>

            <pre class="code-block"><code><span class="token" data-type="keyword" data-scope="0" data-desc="Declares a function">fn</span> <span class="token" data-type="function" data-scope="0" data-desc="Program entry point - execution starts here">main</span><span class="token" data-type="punctuation" data-scope="0" data-desc="Empty parameter list">()</span> <span class="token" data-type="block" data-scope="0" data-desc="Opens function body (entering scope 1)">{</span>
    <span class="token" data-type="macro" data-scope="1" data-desc="Prints to stdout with a newline">println!</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Macro argument delimiter">(</span><span class="token" data-type="&amp;str" data-scope="1" data-desc="String literal - a reference to text data">"Hello, world!"</span><span class="token" data-type="punctuation" data-scope="1" data-desc="End of macro arguments">)</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
<span class="token" data-type="block" data-scope="1" data-desc="Closes function body (back to scope 0)">}</span></code></pre>

            <button class="run-btn" onclick="runCode(this)">Run</button>
            <div class="output"></div>
        </section>
    </main>

    <div id="tooltip" class="tooltip hidden"></div>
    <script src="script.js"></script>

What does this code do?

The <header> gives the page a title and subtitle. The <main> element wraps all lesson content.

Inside the lesson section, keyword-doc divs explain new Rust keywords as they appear. The <pre> block contains <span class="token"> elements where each span represents one token of Rust code, annotated with type, scope depth, and a description.

The tooltip div at the bottom is hidden for now. It will display metadata when you hover tokens in the next step.

  • Save index.html and refresh your browser.
  • You should see the heading "Rust Interactive Tutorial" and the Hello World lesson with unstyled text, a visible Run button, and two keyword documentation boxes.

Don't see any content?

Make sure you deleted the old <script src="script.js"></script> line that was between the original <body> tags. The new code already includes that script tag at the bottom.

Check that you still have the <body> and </body> wrapper around the pasted content.

help me debug my index.html structure

  • Now add the second lesson. In index.html, find the closing </section> tag of the Hello World lesson (just above </main>).
  • Paste the following code between that </section> and </main>:

        <section class="lesson" id="lesson-variables">
            <h2>2. Variables and Mutability</h2>
            <p>Variables in Rust are immutable by default. Use <code>mut</code> to allow changes after declaration.</p>

            <div class="keyword-doc">
                <h4>let</h4>
                <p>Binds a value to a variable name. Syntax: <code>let name: Type = value;</code></p>
            </div>
            <div class="keyword-doc">
                <h4>mut</h4>
                <p>Marks a variable as mutable, allowing its value to be changed after initial assignment.</p>
            </div>

            <pre class="code-block"><code><span class="token" data-type="keyword" data-scope="0" data-desc="Declares a function">fn</span> <span class="token" data-type="function" data-scope="0" data-desc="Program entry point">main</span><span class="token" data-type="punctuation" data-scope="0" data-desc="Empty parameter list">()</span> <span class="token" data-type="block" data-scope="0" data-desc="Opens function body (entering scope 1)">{</span>
    <span class="token" data-type="keyword" data-scope="1" data-desc="Binds a value to a variable">let</span> <span class="token" data-type="i32" data-scope="1" data-desc="Immutable variable holding a 32-bit integer">x</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Type annotation separator">:</span> <span class="token" data-type="type" data-scope="1" data-desc="32-bit signed integer type">i32</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Assignment operator">=</span> <span class="token" data-type="i32" data-scope="1" data-desc="Integer literal with value 5">5</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
    <span class="token" data-type="keyword" data-scope="1" data-desc="Binds a value to a variable">let</span> <span class="token" data-type="keyword" data-scope="1" data-desc="Makes this variable mutable">mut</span> <span class="token" data-type="i32" data-scope="1" data-desc="Mutable variable - its value can change">y</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Assignment operator">=</span> <span class="token" data-type="i32" data-scope="1" data-desc="Integer literal with value 10">10</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
    <span class="token" data-type="i32" data-scope="1" data-desc="Reassigning y (allowed because it is mut)">y</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Assignment operator">=</span> <span class="token" data-type="i32" data-scope="1" data-desc="Current value of y">y</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Addition operator">+</span> <span class="token" data-type="i32" data-scope="1" data-desc="Value of x (5)">x</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
    <span class="token" data-type="macro" data-scope="1" data-desc="Prints to stdout with a newline">println!</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Macro argument delimiter">(</span><span class="token" data-type="&amp;str" data-scope="1" data-desc="Format string - {} is replaced by y's value">"y = {}"</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Argument separator">,</span> <span class="token" data-type="i32" data-scope="1" data-desc="Value of y (15) inserted into format string">y</span><span class="token" data-type="punctuation" data-scope="1" data-desc="End of macro arguments">)</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
<span class="token" data-type="block" data-scope="1" data-desc="Closes function body (back to scope 0)">}</span></code></pre>

            <button class="run-btn" onclick="runCode(this)">Run</button>
            <div class="output"></div>
        </section>

What does this code do?

This lesson demonstrates Rust's immutability default. The data-scope="1" on the inner tokens means they live one level deep (inside the fn main body), while the function declaration itself sits at scope 0.

The keyword-doc cards for let and mut explain these keywords the first time they appear in the tutorial.

  • Now add the third lesson. Find the closing </section> tag of the Variables lesson (just above </main>) and paste the following code between that </section> and </main>:

        <section class="lesson" id="lesson-ownership">
            <h2>3. Ownership and Borrowing</h2>
            <p>Rust's ownership system ensures memory safety without a garbage collector. Each value has one owner. You can lend access with references (<code>&amp;</code>) without transferring ownership.</p>

            <div class="keyword-doc">
                <h4>&amp; (reference)</h4>
                <p>Creates a reference that borrows a value without taking ownership. The original variable remains valid.</p>
            </div>
            <div class="keyword-doc">
                <h4>String</h4>
                <p>A growable, heap-allocated string type. Unlike <code>&amp;str</code>, a <code>String</code> owns its data and can be modified.</p>
            </div>

            <pre class="code-block"><code><span class="token" data-type="keyword" data-scope="0" data-desc="Declares a function">fn</span> <span class="token" data-type="function" data-scope="0" data-desc="Program entry point">main</span><span class="token" data-type="punctuation" data-scope="0" data-desc="Empty parameter list">()</span> <span class="token" data-type="block" data-scope="0" data-desc="Opens function body (entering scope 1)">{</span>
    <span class="token" data-type="keyword" data-scope="1" data-desc="Binds a value to a variable">let</span> <span class="token" data-type="String" data-scope="1" data-desc="Owns the heap-allocated string data">s1</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Assignment operator">=</span> <span class="token" data-type="String" data-scope="1" data-desc="Creates a new String from a string literal">String::from</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Function call delimiter">(</span><span class="token" data-type="&amp;str" data-scope="1" data-desc="String literal passed to String::from">"hello"</span><span class="token" data-type="punctuation" data-scope="1" data-desc="End of function call">)</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
    <span class="token" data-type="keyword" data-scope="1" data-desc="Binds a value to a variable">let</span> <span class="token" data-type="&amp;String" data-scope="1" data-desc="Borrows s1 - does not take ownership">s2</span> <span class="token" data-type="punctuation" data-scope="1" data-desc="Assignment operator">=</span> <span class="token" data-type="operator" data-scope="1" data-desc="Reference operator - borrows without taking ownership">&amp;</span><span class="token" data-type="String" data-scope="1" data-desc="The value being borrowed">s1</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
    <span class="token" data-type="macro" data-scope="1" data-desc="Prints to stdout with a newline">println!</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Macro argument delimiter">(</span><span class="token" data-type="&amp;str" data-scope="1" data-desc="Format string with two placeholders">"{}  and {}"</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Argument separator">,</span> <span class="token" data-type="String" data-scope="1" data-desc="s1 is still valid because s2 only borrowed it">s1</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Argument separator">,</span> <span class="token" data-type="&amp;String" data-scope="1" data-desc="The borrowed reference to s1">s2</span><span class="token" data-type="punctuation" data-scope="1" data-desc="End of macro arguments">)</span><span class="token" data-type="punctuation" data-scope="1" data-desc="Statement terminator">;</span>
<span class="token" data-type="block" data-scope="1" data-desc="Closes function body (back to scope 0)">}</span></code></pre>

            <button class="run-btn" onclick="runCode(this)">Run</button>
            <div class="output"></div>
        </section>

What does this code do?

This lesson introduces Rust's ownership model. The & operator creates a reference that borrows a value without taking ownership. The keyword-doc cards explain & (reference) and the String type.

Notice that s1 remains valid after s2 borrows it. This is the key insight the lesson teaches about borrowing versus moving ownership.

  • Save index.html and refresh your browser.
  • You should see three unstyled lesson sections stacked vertically: Hello World, Variables and Mutability, and Ownership and Borrowing. Each has a heading, paragraph text, keyword boxes, a block of code text, and a Run button.

Only see one or two lessons?

Check that each new <section> was pasted between the previous </section> and </main>. If a section ended up outside <main>, it may still render but will be positioned oddly once styles are added.

help me find where my second and third lessons should go in index.html

Style the page layout and code blocks

The content is in place, but everything looks like unstyled browser defaults. Now you will add CSS to give the page a dark editor theme with styled lesson cards and code blocks.

  • Open style.css in VS Code and paste the following base layout styles:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
    line-height: 1.6;
    color: #e0e0e0;
    background: #1a1a2e;
    padding: 2rem;
    max-width: 800px;
    margin: 0 auto;
}

header {
    text-align: center;
    margin-bottom: 3rem;
}

header h1 {
    color: #ff6b35;
    font-size: 2rem;
    margin-bottom: 0.5rem;
}

.subtitle {
    color: #888;
    font-size: 0.9rem;
}

What does this code do?

The universal selector * resets margins and padding across all elements. The body gets a dark navy background (#1a1a2e), light text, and a centered column capped at 800px.

The header is centered with the title in an orange accent color (#ff6b35) that will be used throughout for emphasis.

  • Below the subtitle rule, add the lesson card and keyword documentation styles:

.lesson {
    margin-bottom: 3rem;
    padding: 1.5rem;
    background: #16213e;
    border-radius: 8px;
    border: 1px solid #0f3460;
}

.lesson h2 {
    color: #ff6b35;
    margin-bottom: 1rem;
}

.lesson p {
    margin-bottom: 1rem;
    color: #ccc;
}

.lesson p code {
    background: #0f3460;
    padding: 0.15rem 0.4rem;
    border-radius: 3px;
    color: #ff6b35;
    font-size: 0.9em;
}

.keyword-doc {
    background: #0f3460;
    border-left: 4px solid #ff6b35;
    padding: 0.75rem 1rem;
    margin-bottom: 0.75rem;
    border-radius: 0 4px 4px 0;
}

What does this code do?

Each .lesson becomes a rounded dark card. The .keyword-doc cards get a left orange border to visually separate them as reference material within the lesson flow.

  • Save style.css and refresh your browser.
  • You should see dark rounded cards for each lesson, orange headings, and keyword documentation boxes with a distinct left border.
  • Return to style.css and add the keyword-doc typography and code block styles below the existing rules:

.keyword-doc h4 {
    color: #ff6b35;
    font-family: "Courier New", monospace;
    margin-bottom: 0.25rem;
}

.keyword-doc p {
    margin: 0;
    font-size: 0.85rem;
    color: #aaa;
}

.keyword-doc p code {
    background: #1a1a2e;
    padding: 0.1rem 0.3rem;
    border-radius: 3px;
    color: #7fdbca;
    font-size: 0.9em;
}

.code-block {
    background: #0d1117;
    border: 1px solid #30363d;
    border-radius: 6px;
    padding: 1rem;
    overflow-x: auto;
    margin-top: 1rem;
    font-family: "Courier New", Consolas, monospace;
    font-size: 0.9rem;
    line-height: 1.5;
}

.code-block code {
    white-space: pre;
}

What does this code do?

The keyword-doc headings use a monospace font to look like code references. The .code-block gets a very dark background (#0d1117), a subtle border, and monospace font to mimic a code editor. Setting white-space: pre preserves the indentation of the Rust code.

  • Continue adding the token hover and tooltip styles below:

.token {
    cursor: help;
    border-radius: 2px;
    transition: background 0.2s;
}

.token:hover {
    background: #2d4a7a;
}

.tooltip {
    position: fixed;
    background: #1a1a2e;
    border: 1px solid #ff6b35;
    color: #e0e0e0;
    padding: 0.5rem 0.75rem;
    border-radius: 6px;
    font-size: 0.8rem;
    font-family: "Courier New", monospace;
    z-index: 1000;
    pointer-events: none;
    max-width: 300px;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
}

.tooltip.hidden {
    display: none;
}

.tooltip .tip-type {
    color: #7fdbca;
}

.tooltip .tip-scope {
    color: #ff6b35;
}

What does this code do?

The .token rule gives every code token a help cursor and a blue highlight on hover. The .tooltip is styled as a fixed-position popup with an orange border that appears near the hovered token. It is hidden by default via the .hidden class.

  • Save style.css and refresh your browser.
  • The code blocks now have a dark background with monospace text. Hover your mouse over any token in the code and you should see the cursor change to a help icon and the background highlight blue.
  • Add the final styles for the tooltip description, Run button, and output area:

.tooltip .tip-desc {
    color: #ccc;
    display: block;
    margin-top: 0.25rem;
}

.run-btn {
    margin-top: 0.75rem;
    padding: 0.5rem 1.5rem;
    background: #ff6b35;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9rem;
    font-weight: bold;
    transition: background 0.2s;
}

.run-btn:hover {
    background: #e55a2b;
}

.run-btn:disabled {
    background: #555;
    cursor: wait;
}

What does this code do?

The .run-btn gets the same orange accent as headings, with a darker shade on hover and a grey disabled state for when code is running. The .tip-desc displays as a block so the description drops to its own line inside the tooltip.

  • Add the output area styles at the very end of the file:

.output {
    margin-top: 0.5rem;
    padding: 0.75rem;
    background: #0d1117;
    border-radius: 4px;
    font-family: "Courier New", monospace;
    font-size: 0.85rem;
    white-space: pre-wrap;
    min-height: 1.5rem;
    display: none;
}

.output.visible {
    display: block;
}

.output.success {
    border-left: 3px solid #3fb950;
    color: #3fb950;
}

.output.error {
    border-left: 3px solid #f85149;
    color: #f85149;
}

What does this code do?

The output area is hidden by default (display: none). When code runs later, JavaScript will add the visible class to show it. Successful output gets a green left border, and errors get a red one.

  • Save style.css.

✔️ Awesome, I've got everything!

Great. Make sure both index.html and style.css are saved before continuing.

ⓧ I'd like to double check the full code

Your index.html should contain, in order:

  • The DOCTYPE and head section (unchanged from Step 1).
  • A <header> with the page title and subtitle.
  • A <main> element containing three <section class="lesson"> blocks: Hello World, Variables and Mutability, and Ownership and Borrowing.
  • A hidden <div id="tooltip"> and the <script> tag at the bottom.

Your style.css should contain, in order:

  • The reset (*), body, header, and subtitle rules.
  • The .lesson, .lesson h2, .lesson p, and .lesson p code rules.
  • The .keyword-doc rules (base, h4, p, p code).
  • The .code-block and .token rules.
  • The .tooltip rules (base, hidden, tip-type, tip-scope, tip-desc).
  • The .run-btn rules (base, hover, disabled).
  • The .output rules (base, visible, success, error).

Scroll up and confirm each chunk is present in order. If anything is missing, copy the relevant chunk from above.

Verify the styled lessons in the browser

Before you run this final check, what do you expect the page to look like? Think about what three lessons with dark cards, orange headings, monospace code, and bordered keyword boxes would look like together.

  • Refresh your browser with index.html open.

You should see three dark-themed lesson cards stacked vertically. Each card has an orange heading, a paragraph of explanation text, keyword documentation boxes with an orange left border, and a dark code block with monospace Rust code. An orange "Run" button sits below each code block.

Styles not applying?

Make sure your <link rel="stylesheet" href="style.css"> is inside the <head> section of index.html. It was already there from Step 1, but confirm it was not accidentally deleted.

Try a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to clear any cached version of the stylesheet.

help me figure out why my CSS styles are not showing up

Your tutorial page is taking shape. Next up, you will bring those annotated tokens to life by building the hover tooltip system that reads the data attributes and displays them on screen.

Add Hover Tooltips

Your tutorial page has three styled lessons with dark-themed code blocks. Each <span> element inside those code blocks carries hidden metadata in its data-type, data-scope, and data-desc attributes.

Right now, that metadata is invisible. The code blocks look like ordinary formatted text. In this step, you will write JavaScript that reveals this information through hover tooltips, making every token in the tutorial alive with documentation.

In this step, get ready to:
  • Write event listeners that read token metadata on hover.
  • Display a positioned tooltip showing type, scope depth, and description.
  • Verify tooltips appear when hovering over code tokens in the browser.
Write the tooltip event listeners

The tooltip's styling is already in style.css from the previous step. Now you need the JavaScript that actually populates and positions it when a user hovers over a token.

  • Switch back to VS Code and open script.js in the rust-tutorial folder.
  • Add the tooltip logic by pasting the following code:
// Grab the tooltip element from the page
const tooltip = document.getElementById("tooltip");

// Attach hover listeners to every token span
document.querySelectorAll(".token").forEach(function(token) {
    token.addEventListener("mouseenter", function(e) {
        var type = token.getAttribute("data-type");
        var scope = token.getAttribute("data-scope");
        var desc = token.getAttribute("data-desc");

        // Skip tokens with no metadata
        if (!type && !desc) return;

        // Build the tooltip content
        tooltip.innerHTML =
            '<span class="tip-type">Type: ' + type + '</span> | ' +
            '<span class="tip-scope">Scope: ' + scope + '</span>' +
            '<span class="tip-desc">' + desc + '</span>';

        // Show the tooltip
        tooltip.classList.remove("hidden");

        // Position it just below the hovered token
        var rect = token.getBoundingClientRect();
        tooltip.style.left = rect.left + "px";
        tooltip.style.top = (rect.bottom + 8) + "px";
    });

    token.addEventListener("mouseleave", function() {
        tooltip.classList.add("hidden");
    });
});

What does this code do?

This script runs once when the page loads. It finds every element with the class .token and attaches two event listeners to each one.

  • On mouseenter, it reads the three data- attributes from the hovered span, builds an HTML string showing the type, scope, and description, then removes the hidden class to make the tooltip visible.
  • It uses getBoundingClientRect() to measure where the token sits on screen, then places the tooltip 8 pixels below it.
  • On mouseleave, it adds the hidden class back, making the tooltip disappear.
  • Save script.js.

File not saving?

Make sure you are editing the script.js file inside the rust-tutorial folder, not a different file. Press Cmd+S (macOS) or Ctrl+S (Windows) to save.

help me debug my script.js file not saving in VS Code

Test the hover tooltips in your browser

Before you test, think about what you expect to see. When you hover over the fn keyword in the Hello World lesson, what should the tooltip display?

  • Return to your browser and refresh the page.
  • Hover your mouse over the fn keyword in the first lesson's code block.

You should see a tooltip appear just below the token showing: Type: keyword | Scope: 0 on the first line, and Declares a function on the second line. Move your mouse away and the tooltip disappears.

  • Try hovering over other tokens too. Hover over "Hello, world!" and confirm you see Type: &str | Scope: 1 with the description String literal - a reference to text data.

Tooltip not appearing?

  • Open DevTools (Cmd+Option+I on macOS, Ctrl+Shift+I on Windows) and check the Console tab for JavaScript errors. A typo in a class name or missing quote will show up here.
  • Confirm the <script src="script.js"> tag is at the bottom of your index.html body, after the #tooltip div. If the script loads before the tooltip div exists, getElementById returns null.
  • Make sure the token spans in your HTML have the class token (not tokens or Token). Class names are case-sensitive.

Help me debug why my hover tooltip is not showing up

Why fixed positioning?

The tooltip uses position: fixed and getBoundingClientRect() instead of position: absolute. Fixed positioning places the tooltip relative to the browser viewport, which means it always appears near the token regardless of how far down the page the learner has scrolled.

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js and your tooltips are working on all three lessons.

ⓧ I'd like to double check the full code

Here is the complete script.js at this point:

const tooltip = document.getElementById("tooltip");

document.querySelectorAll(".token").forEach(function(token) {
    token.addEventListener("mouseenter", function(e) {
        var type = token.getAttribute("data-type");
        var scope = token.getAttribute("data-scope");
        var desc = token.getAttribute("data-desc");

        if (!type && !desc) return;

        tooltip.innerHTML =
            '<span class="tip-type">Type: ' + type + '</span> | ' +
            '<span class="tip-scope">Scope: ' + scope + '</span>' +
            '<span class="tip-desc">' + desc + '</span>';

        tooltip.classList.remove("hidden");

        var rect = token.getBoundingClientRect();
        tooltip.style.left = rect.left + "px";
        tooltip.style.top = (rect.bottom + 8) + "px";
    });

    token.addEventListener("mouseleave", function() {
        tooltip.classList.add("hidden");
    });
});

Your code blocks are now alive with documentation. Every token reveals its type, scope depth, and a plain-English description on hover. Next up, you will wire up the Run buttons so learners can actually execute the Rust code and see real output.

Wire Up the Rust Playground API

Your tooltip system reveals hidden metadata about every token. But the code examples themselves are static text. You can read about Rust, but you cannot verify what the code actually does until it runs on a real compiler.

In this step, you will connect your tutorial to the Rust Playground, a public service that compiles and executes Rust code. Each lesson's Run button will send its code to the Playground and display the result directly on the page.

In this step, get ready to:
  • Write a function that sends code to the Rust Playground API.
  • Handle the API response to display output in green or errors in red.
  • Test error handling by introducing a deliberate typo.
Send code to the Rust Playground

The Rust Playground exposes an endpoint that accepts Rust source code and returns the compilation result. Your JavaScript function needs to extract the text from a code block, send it as a POST request, and handle the response.

  • In script.js, add the following function below the existing tooltip code:
function runCode(button) {
    // Find the lesson container, code block, and output area
    var section = button.closest(".lesson");
    var codeBlock = section.querySelector(".code-block code");
    var output = section.querySelector(".output");
    var code = codeBlock.textContent;

    // Show loading state while code compiles
    button.disabled = true;
    button.textContent = "Running...";
    output.classList.remove("visible", "success", "error");
    output.textContent = "";

    // Send the code to Rust Playground for compilation
    fetch("https://play.rust-lang.org/execute", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
            channel: "stable",
            mode: "debug",
            edition: "2021",
            crateType: "bin",
            tests: false,
            code: code,
            backtrace: false
        })
    })
    .then(function(response) {
        return response.json();
    })

What does this code do?

This is the first half of the runCode function. It handles everything up to receiving the response.

  • button.closest(".lesson") walks up the DOM from the clicked Run button until it finds the parent lesson section.
  • codeBlock.textContent extracts the plain text from all the <span> tokens, giving you raw Rust source code.
  • The button is disabled and its text changes to "Running..." so the learner knows something is happening.
  • The fetch call POSTs the code to the Rust Playground with settings for stable Rust, debug mode, and the 2021 edition.
Display the compilation result

The Playground returns a JSON object with a success boolean, a stdout field for program output, and a stderr field for compiler errors. Your response handler needs to show the right one in the right colour.

  • Directly below the .then(function(response) { ... }) block you just wrote, add the response handlers:
    .then(function(data) {
        // Show the output area
        output.classList.add("visible");
        if (data.success) {
            // Green text for successful compilation
            output.classList.add("success");
            output.textContent = data.stdout || "(no output)";
        } else {
            // Red text for compilation errors
            output.classList.add("error");
            output.textContent = data.stderr || "Compilation failed.";
        }
    })
    .catch(function(err) {
        // Handle network failures
        output.classList.add("visible", "error");
        output.textContent = "Network error: " + err.message;
    })
    .finally(function() {
        // Re-enable the Run button regardless of outcome
        button.disabled = false;
        button.textContent = "Run";
    });
}

What does this code do?

  • The second .then checks data.success. If true, it shows stdout in green. If false, it shows stderr in red.
  • .catch handles network failures (no internet, API down) by showing the error message in red.
  • .finally runs no matter what happened. It re-enables the button and resets its text back to "Run".
  • Save script.js.
  • Refresh your browser.

Before you click Run, what do you think will appear in the output area below the Hello World code block?

  • Click the Run button below the Hello World code block.

The button briefly shows "Running..." while the code compiles on Rust's servers. After a moment, you should see "Hello, world!" appear in green text below the code block.

Not seeing any output?

  • Check that you saved script.js after adding both code chunks. The function must be complete (opening brace to closing brace) to work.
  • Open your browser's Developer Tools console (press F12) and look for red error messages. A syntax error means the two chunks are not joined correctly.
  • If you see "Network error", check that you are connected to the internet. The Rust Playground is an external server.

My Run button isn't producing any output. How do I debug this?

Test error handling

You have confirmed the green success path. Now verify that compilation errors display correctly in red.

  • In index.html, find the Hello World code block inside the lesson-hello section.
  • Find the span that contains the text main (the function name):
<span class="token" data-type="function" data-scope="0" data-desc="Program entry point - execution starts here">main</span>
  • Change the text main to mai (delete the trailing n) so the span reads:
<span class="token" data-type="function" data-scope="0" data-desc="Program entry point - execution starts here">mai</span>
  • Save index.html and refresh your browser.
  • Click Run on the Hello World example.

This time the output area shows a red error message from the Rust compiler. The function name is no longer valid, so the Playground reports the error back to your page.

  • Change mai back to main in that same span.
  • Save index.html.

Why test both paths?

A tutorial that only shows success is misleading. When learners experiment with their own Rust code in the future, they will hit compiler errors. Seeing those errors rendered clearly in red (instead of a silent failure or cryptic console message) makes the tutorial feel like a real development tool.

  • Refresh your browser and click Run on the Variables and Mutability lesson to confirm it outputs y = 15 in green.

✔️ Awesome, I've got everything!

Great. Double check you have saved script.js and index.html (with main restored).

ⓧ I'd like to double check the full code

Your script.js should contain two sections in this order:

  • The tooltip logic (the const tooltip block and the forEach loop with mouseenter and mouseleave listeners) from the previous step.
  • The runCode(button) function you just added, starting with function runCode(button) { and ending with the closing } after .finally.

Scroll up and confirm both sections are present in your file, in order. The index.html file is unchanged from the previous step (with main restored after your typo test).

Every code example in your tutorial can now compile and run on real Rust servers. Next up, you will containerize this entire site with Docker and deploy it live to GitHub Pages.

Dockerize and Deploy

Your interactive Rust tutorial works perfectly on your machine. Tooltips appear on hover, and the Run buttons compile real Rust code.

The problem is that nobody else can see it. In this step, you will package the site into a Docker container to prove it runs anywhere, then deploy it to GitHub Pages so anyone with the URL can use your tutorial.

In this step, get ready to:
  • Create a Dockerfile and run your site in a container.
  • Push your code to GitHub and deploy with GitHub Pages.
  • Verify the live site works with tooltips and code execution.
Create the Dockerfile and run the container

A Dockerfile tells Docker how to package your files into an image. You will use the nginx:alpine base image, which includes a web server that can serve your HTML, CSS, and JavaScript files.

  • In VS Code, create a new file called Dockerfile (no file extension) inside your rust-tutorial folder.
  • Add the following content to your Dockerfile:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
COPY style.css /usr/share/nginx/html/style.css
COPY script.js /usr/share/nginx/html/script.js
EXPOSE 80

What does this Dockerfile do?

  • FROM nginx:alpine uses a lightweight web server image as the starting point.
  • Each COPY line places your project files into the directory nginx serves from (/usr/share/nginx/html).
  • EXPOSE 80 documents that the container listens on port 80.
  • Save Dockerfile.
  • Confirm the file exists by running:
ls

You should see Dockerfile, index.html, style.css, and script.js listed in the output.

✔️ Awesome, I've got everything!

Great. Make sure you have saved Dockerfile before building the image.

ⓧ I'd like to double check the full code

Your Dockerfile should look exactly like this:

FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
COPY style.css /usr/share/nginx/html/style.css
COPY script.js /usr/share/nginx/html/script.js
EXPOSE 80

Now build the Docker image from your Dockerfile.

  • Build the Docker image by running this command:
docker build -t rust-tutorial .

You should see Docker pull the nginx:alpine layers and finish with a success message.

Seeing a build error?

  • Make sure Docker Desktop is running. The Docker daemon must be active for builds to work.
  • Check that your terminal is inside the rust-tutorial folder where the Dockerfile lives.
  • Verify the file is named exactly Dockerfile with a capital D and no file extension.

Why is my Docker build failing?

Now run the container and map its internal port 80 to your machine's port 8080.

  • Start the container by running:
docker run -p 8080:80 rust-tutorial

What does this command do?

The -p 8080:80 flag maps port 8080 on your machine to port 80 inside the container. Nginx serves your files on port 80 inside the container, so you access them at http://localhost:8080 in your browser.

This command keeps the terminal occupied while the container runs. You will stop it with Ctrl+C after verifying.

Before you check the browser, what do you expect to see at http://localhost:8080? Will it look exactly the same as your local file, or could something be different?

  • Open your browser and navigate to http://localhost:8080.

You should see the full Rust Interactive Tutorial page. Hover over a token to confirm tooltips work. Click a Run button to confirm the Playground API still responds.

Page not loading at localhost:8080?

  • Make sure you did not close the terminal running the container. The docker run process must stay active.
  • Check that nothing else is already using port 8080 on your machine.
  • If you see a blank page, open DevTools and check for 404 errors on style.css or script.js. Verify your COPY lines match the exact filenames.

Why is my Docker container not serving my site at localhost:8080?

  • After confirming the site works, return to the terminal running the container and press Ctrl+C to stop it.
Push to GitHub and enable GitHub Pages

Your site runs in a container, proving it works outside your local file system. Now you will push the code to GitHub and turn on GitHub Pages so anyone can visit the tutorial from a public URL.

  • Create an empty .nojekyll file to prevent GitHub from processing your files with Jekyll:
touch .nojekyll

Why create a .nojekyll file?

GitHub Pages runs Jekyll by default, which can interfere with plain HTML files. An empty .nojekyll file in the root of your publishing source disables Jekyll processing so your files are served exactly as they are.

  • Initialize a Git repository and make your first commit by running these commands:
git init
git add .
git commit -m "Initial commit"

You should see output listing the files being committed, including index.html, style.css, script.js, Dockerfile, and .nojekyll.

Next, create a new repository on GitHub to hold this project.

  • Open your browser and go to https://github.com/new.
  • Enter rust-tutorial as the repository name.
  • Set the visibility to Public (required for free GitHub Pages hosting).
  • Do NOT initialize with a README, .gitignore, or license (you already have local files).
  • Click Create repository.

GitHub shows a Quick Setup page with your repository URL. Copy the HTTPS URL shown at the top.

  • Back in your terminal, connect your local repository to GitHub and push by running these commands:
git branch -M main
git remote add origin [[REPO_URL="https://github.com/YOUR-USERNAME/rust-tutorial.git"]]
git push -u origin main

Git will prompt you to authenticate. Enter your GitHub username and a personal access token when prompted. After pushing, you should see output confirming the branch was pushed to origin.

Push rejected or authentication failed?

  • GitHub no longer accepts passwords. You need a personal access token. Generate one at Settings > Developer settings > Personal access tokens on GitHub.
  • If you see "remote origin already exists", run git remote set-url origin followed by your URL to update it.

Why can't I push my code to GitHub?

Now enable GitHub Pages so the repository is served as a live website.

  • On GitHub, navigate to your rust-tutorial repository page.
  • Click Settings in the top navigation bar.
  • In the left sidebar, under Code and automation, click Pages.
  • Under Build and deployment, set Source to Deploy from a branch.
  • In the branch dropdown, select main and leave the folder as / (root).
  • Click Save.

The Pages settings now show that your site will be published from the main branch.

Verify the live deployment

How long does publishing take?

GitHub Pages can take up to 10 minutes to publish after you push changes or update the settings. If your site is not live yet, wait a few minutes and refresh.

Before you visit your live URL, do you think the tooltips and Run buttons will work on GitHub Pages? Remember that the tooltips are pure JavaScript and the Run button calls an external API.

  • Open your browser and visit your GitHub Pages URL:
https://[[GITHUB_USERNAME="your-github-username"]].github.io/rust-tutorial

You should see the full Rust Interactive Tutorial page live on the internet. Hover over any token to confirm tooltips appear. Click Run on the Hello World example to confirm the Playground API responds with output.

Site not loading or showing a 404?

  • Double-check that Pages is enabled under Settings > Pages and the source branch is main.
  • Make sure your repository is public. GitHub Pages requires a public repo on free plans.
  • Wait at least 10 minutes after enabling Pages. Deployments are not instant.
  • Confirm your repository contains index.html at the root level (not inside a subfolder).

Why is my GitHub Pages site showing a 404 error?

Your Rust tutorial is live. Anyone with the URL can hover over tokens, read keyword documentation cards, and compile real Rust code from their browser.

Secret mission

Add a Pattern Matching Lesson

Your tutorial covers variables, mutability, and ownership. But Rust's pattern matching system is one of its most powerful features. Can you extend the tutorial with a fourth lesson that teaches enum and match, complete with your own hover annotations?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally and uses free services (GitHub Pages), so there are no ongoing costs.

Resources you used:

  • Local project folder rust-tutorial on your Desktop (contains index.html, style.css, script.js, Dockerfile, .nojekyll).
  • Docker image rust-tutorial built locally.
  • GitHub repository with GitHub Pages enabled.

Keep everything running

No action needed. Choose this if you are still actively building or want to continue experimenting with the tutorial site.

  • Your local rust-tutorial folder remains on your Desktop.
  • Your Docker image stays cached locally for quick container rebuilds.
  • Your GitHub Pages site stays live at your repository URL.

Pause - I'll come back to this later

Stop running containers to free up system resources, but keep your files and repository intact for later.

  • Stop any running Docker container by running:
docker ps -q --filter ancestor=rust-tutorial | xargs -r docker stop
  • Your local files and GitHub repository remain unchanged and ready to pick up again.

Delete - I don't want to use this again

Remove all project resources from your machine and GitHub account.

  • Stop and remove any running containers, then remove the Docker image by running:
docker ps -q --filter ancestor=rust-tutorial | xargs -r docker stop
docker ps -aq --filter ancestor=rust-tutorial | xargs -r docker rm
docker rmi rust-tutorial
  • Delete the local project folder by running:

macOS

cd ~/Desktop
rm -rf rust-tutorial

Windows

cd ~\Desktop
rmdir /s /q rust-tutorial
  • Delete the GitHub repository by going to your repository page on GitHub.
  • Click Settings in the top navigation bar.
  • Scroll to the bottom of the page to the Danger Zone section.
  • Click Delete this repository and follow the confirmation prompts.

Nice Work!

Nice Work!

You just built a fully interactive Rust tutorial website from scratch. Every code token is alive with hover documentation, lessons teach real Rust concepts in context, and the Run button compiles actual Rust on demand.

You've learned how to:

  • Built a hover tooltip system that reads data-type, data-scope, and data-desc attributes from DOM elements and displays them in a positioned tooltip on hover.
  • Integrated the Rust Playground API to compile and execute real Rust code from the browser, handling both successful output and compilation errors.
  • Containerized the site with Docker using nginx:alpine and deployed it live to GitHub Pages as a publicly accessible website.
  • Secret Mission: Added a Pattern Matching lesson with custom annotations for match and enum, designing the type metadata and keyword documentation cards yourself.

Ready to quiz yourself?