Build a Linear-Style ASCII Diagram

Create a dark-themed portfolio page with a terminal-style architecture diagram.

Introduction

30 Second Summary

You know those slick architecture diagrams that make a developer's portfolio stop you mid-scroll? The ones that look like they belong in a terminal from the future? Linear nails this aesthetic on their website, using monospaced box-drawing characters to map out system architecture in a way that feels both retro and polished.

In this project, you will build your own version of that effect. You'll create a dark-themed portfolio page featuring an ASCII architecture diagram made entirely with HTML and CSS.

What You'll Build

A dark-themed single-page website that visualizes your project's code architecture using Unicode box-drawing characters, colored background layers, and professional typography.

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

  • A complete index.html page with an ASCII architecture diagram built from Unicode box-drawing characters, nested boxes, labels, and connection arrows.
  • Working knowledge of CSS ch and lh units for positioning colored background layers with character-level precision behind monospaced text.
  • A dark-themed portfolio page styled with Inter and JetBrains Mono, inspired by Linear's Nordic Gray palette.
  • A breathing glow animation that pulses the diagram's box-drawing characters with a subtle neon-terminal effect.

Are there any prerequisites?

None. This project uses only a code editor (VS Code or Cursor) and a web browser. No frameworks, no build tools, no installations needed.

Before We Start

Before We Start...

Set Up Your Project Files

Before you can build your diagram, you need a project folder and an index.html file to work in.

In this step, you'll create your project, write the HTML boilerplate, load two fonts from Google Fonts, and set up the dark background that gives the page its Linear-inspired look.

In this step, get ready to:
  • Create a project folder and HTML file.
  • Add the HTML boilerplate with Google Fonts and a UTF-8 charset.
  • Style the page with a dark background and light text.
Create your project folder and HTML file

Every web project starts with a folder to hold your files and an index.html file that the browser loads.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or PowerShell (Windows) 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 linear-diagram
cd linear-diagram
  • Create the index.html file by running:
touch index.html
  • Confirm the file exists by running:
ls

You should see index.html in the output.

Don't see index.html?

Make sure you ran cd linear-diagram before running touch index.html. If you skipped that step, the file may have been created in the wrong folder.

On Windows, touch may not be available. Use New-Item index.html in PowerShell instead.

Help me create my project folder and index.html file.

Now open the folder in your code editor.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code (or Cursor if you use Cursor) and press Enter to open it.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the linear-diagram folder, then click Open.
  • In the Explorer sidebar on the left, click index.html to open it in the editor.
Add the HTML boilerplate

Your HTML file needs a few things before you start building the diagram. The charset meta tag tells the browser to use UTF-8 encoding, which is what makes the box-drawing characters (like ┌ and ─) render correctly.

  • Copy the following boilerplate into your index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
  <!-- UTF-8 is critical for Unicode box-drawing characters -->
  <meta charset="UTF-8">

  <!-- Makes the page scale correctly on mobile devices -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Architecture Diagram</title>

  <!-- Load Inter (body font) and JetBrains Mono (diagram font) from Google Fonts -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap">
</head>
<body>
  <h1>Hello, diagram!</h1>
</body>
</html>

What does this code do?

  • <meta charset="UTF-8"> enables Unicode support. Without this, your box-drawing characters could show up as garbled symbols.
  • The viewport meta tag ensures the page scales properly on different screen sizes.
  • The Google Fonts <link> tag loads two fonts: Inter for body text and JetBrains Mono for the monospace diagram. These are the same style of fonts that Linear uses on their site.

💡 Why JetBrains Mono instead of Berkeley Mono?

Linear actually uses Berkeley Mono for their monospace elements. Berkeley Mono is a paid, commercially licensed font. JetBrains Mono is the closest free alternative (85% similarity) and has excellent support for box-drawing characters. It is available on Google Fonts under an open license.

Now open this file in your browser to make sure it loads.

  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open Finder (macOS) or File Explorer (Windows) and navigate to the linear-diagram folder on your Desktop.
  • Double-click index.html to open it in your default browser.

You should see the text "Hello, diagram!" on a white page. The white background is the browser's default. You'll change that next.

Seeing a blank page or weird characters?

  • If the page is completely blank, make sure you saved the file after pasting the code.
  • If you see garbled symbols instead of normal text, double-check that the <meta charset="UTF-8"> tag is present inside the <head> section.

Help me figure out why my HTML page isn't displaying correctly.

Add the dark theme styles

Linear's pages use a dark background with light text. You'll replicate this look using a CSS reset and a handful of body styles.

  • In index.html, find the closing </title> tag. Add the following <style> block directly below it (before the Google Fonts <link> tag):
  <style>
    /* Reset default browser margins and padding */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    /* Dark background inspired by Linear's Nordic Gray palette */
    body {
      background-color: #222326;
      color: #f7f8f8;
      font-family: 'Inter', sans-serif;
      padding: 80px 40px;
    }
  </style>

What does this code do?

  • The * selector resets all elements to zero margin and padding, so the browser's defaults don't interfere with your layout.
  • box-sizing: border-box makes width and height calculations include padding and borders. This prevents elements from being unexpectedly larger than you sized them.
  • #222326 is Linear's Nordic Gray. #f7f8f8 is a near-white text color that's easy to read on dark backgrounds.
  • font-family: 'Inter' sets Inter as the default body font, matching Linear's typography.
  • Save index.html.
  • Switch to your browser and refresh the page.

You should see "Hello, diagram!" in light text on a dark gray background. The page should feel noticeably darker and cleaner than before.

Still seeing a white background?

  • Make sure the <style> block is inside the <head> section, not inside <body>.
  • Check that the background-color value is #222326 with a # at the start. Without the hash symbol, the browser ignores the color.
  • Make sure you saved the file before refreshing.

Help me debug why my dark background isn't showing up.

Awesome, I've got everything!

Double-check that you've saved your file before moving on.

I'd like to double check the full code

Here's what your complete index.html should look like at this point.

<!DOCTYPE html>
<html lang="en">
<head>
  <!-- UTF-8 is critical for Unicode box-drawing characters -->
  <meta charset="UTF-8">

  <!-- Makes the page scale correctly on mobile devices -->
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>Architecture Diagram</title>

  <style>
    /* Reset default browser margins and padding */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    /* Dark background inspired by Linear's Nordic Gray palette */
    body {
      background-color: #222326;
      color: #f7f8f8;
      font-family: 'Inter', sans-serif;
      padding: 80px 40px;
    }
  </style>

  <!-- Load Inter (body font) and JetBrains Mono (diagram font) from Google Fonts -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap">
</head>
<body>
  <h1>Hello, diagram!</h1>
</body>
</html>

Your dark-themed canvas is ready. Next up, you'll learn how Unicode box-drawing characters work and use them to build the actual architecture diagram.

Build the ASCII Diagram with Box-Drawing Characters

Your project is set up and your dark background is live in the browser. Now it's time to build the centerpiece of the page: the ASCII architecture diagram.

Unicode includes a set of special characters designed specifically for drawing boxes and lines in text. You'll combine these characters inside a <pre> tag to create a nested diagram that looks like it was rendered by a terminal.

In this step, get ready to:
  • Learn the key Unicode box-drawing characters and how they connect.
  • Build a full nested architecture diagram with labeled sections and arrows.
  • Style the diagram container so every line connects seamlessly.
Learn the box-drawing character set

Unicode defines a block of characters (U+2500 to U+253C) specifically for drawing boxes and lines in plain text. Each character is designed to connect seamlessly with its neighbors when rendered in a monospace font.

Here are the characters you'll use in this project:

  • Corners: ┌ (top-left), ┐ (top-right), └ (bottom-left), ┘ (bottom-right).
  • Lines: ─ (horizontal), │ (vertical).
  • Tees: ├ (left), ┤ (right), ┬ (top), ┴ (bottom).
  • Arrows: ◀ (left), ▶ (right).

What are box-drawing characters?

Box-drawing characters are a set of Unicode symbols originally used in text-based terminals to create tables, borders, and diagrams. Each character occupies exactly one character cell in a monospace font.

The lines in each character start from the center of the cell. That's what lets a corner like ┌ connect perfectly with a horizontal line ─ to its right and a vertical line │ below it.

Let's start with a simple single-box diagram to see how these characters connect. You'll type these characters directly into your HTML.

  • In index.html, add the following <pre> block inside your <body> tag, below the closing </style> tag:
<!-- Simple test box to see how box-drawing characters connect -->
<pre>
┌──────────────────┐
│   My Project     │
└──────────────────┘
</pre>

What does this code do?

  • The <pre> tag preserves all whitespace and line breaks exactly as you type them. Normal HTML collapses multiple spaces into one, but <pre> keeps every space and newline intact.
  • The corner characters (┌ and ┐) connect with the horizontal lines (─) to form the top edge of the box.
  • The vertical lines (│) on the left and right form the sides, with your label text in between.
  • Save index.html.
  • Refresh your browser. You should see a small text box with "My Project" inside it, drawn entirely with line characters.

Seeing broken characters or question marks?

  • Make sure your <meta charset="UTF-8"> tag is in the <head> of your HTML. Without it, the browser may not know how to decode Unicode characters.
  • Check that your file is saved with UTF-8 encoding. In VS Code, look at the bottom-right status bar. It should say UTF-8.
  • If the lines still look broken, try copying the box-drawing characters directly from this page rather than typing them manually.

Why are my box-drawing characters showing as broken symbols?

Build the full nested architecture diagram

A single box is nice, but the real power of box-drawing characters comes from nesting. You can place boxes inside boxes, add labels to sections, and draw arrows to show how components connect.

The diagram below represents a simple web application architecture. It has an outer system boundary, three inner component boxes, and arrows showing data flow between them.

  • In index.html, replace the entire <pre> block you just created with this full diagram:
<!-- Architecture diagram using box-drawing characters -->
<div class="diagram">
  <pre>
┌─────────────────────────────────────────────────┐
│  System Architecture                            │
│                                                 │
│  ┌─────────────┐   ┌─────────────┐              │
│  │  Frontend   │   │     API     │              │
│  │             │   │             │              │
│  │  React App  │   │  Express.js │              │
│  │  Port 3000  │   │  Port 8080  │              │
│  └──────┬──────┘   └──────┬──────┘              │
│         │                 │                     │
│         └────────┬────────┘                     │
│                  │                              │
│            ◀─────┼─────▶                        │
│                  │                              │
│         ┌────────┴────────┐                     │
│         │    Database     │                     │
│         │                 │                     │
│         │   PostgreSQL    │                     │
│         │   Port 5432     │                     │
│         └─────────────────┘                     │
│                                                 │
└─────────────────────────────────────────────────┘
  </pre>
</div>

What does this diagram do?

  • The outer box (┌...┐...└...┘) represents the entire system boundary. Everything inside it is part of your application.
  • The two inner boxes at the top (Frontend and API) are nested inside the outer box. They use the same corner and line characters, just indented with spaces.
  • The tee characters (┬ and ┴) create junction points where vertical lines meet horizontal lines. They show how the Frontend and API both connect down to the Database.
  • The arrow line (◀─────┼─────▶) uses the cross character ┼ where the horizontal arrow intersects the vertical connection line. This shows bidirectional data flow.

💡 Pro tip: customize the labels

This diagram uses "Frontend", "API", and "Database" as example components. Replace these labels with your own project's architecture. Just make sure each label fits inside its box and you keep the same number of characters per line so the right edges stay aligned.

  • Save index.html.
  • Refresh your browser. You should see the full nested diagram with three component boxes, connecting lines, and arrows. The text will appear in your browser's default monospace font for now.

Lines not connecting or boxes look misaligned?

  • Count the characters on each line carefully. Every line inside the outer box must have the same total width. If one line is shorter or longer, the right edge of the outer box will shift.
  • Make sure you're using the box-drawing characters (─) and not regular hyphens (-). Regular hyphens are shorter and won't connect with the corner characters.
  • Check that no tabs snuck into your <pre> block. Tabs render at different widths in different browsers. Use only spaces for indentation inside the diagram.

My box-drawing lines aren't connecting properly in the browser. Help me debug.

Style the diagram container for seamless lines

The diagram is showing up, but the lines probably have small gaps between them. This happens because browsers add default spacing between lines and characters. Three CSS properties fix this completely.

  • In your <style> block in index.html, add the following CSS rules for the .diagram pre selector:
/* Style the diagram for seamless box-drawing lines */
.diagram pre {
  font-family: 'JetBrains Mono', monospace;
  font-size: 14px;
  line-height: 1;       /* Remove extra vertical space between lines */
  letter-spacing: 0;    /* Remove extra horizontal space between characters */
  white-space: pre;     /* Preserve all whitespace exactly as typed */
}

Why do these three properties matter?

  • line-height: 1 removes the extra vertical space browsers add between lines. Without it, vertical lines (│) have visible gaps between rows.
  • letter-spacing: 0 ensures horizontal lines (─) connect edge to edge with no gaps between characters.
  • white-space: pre tells the browser to preserve every space and newline. This is technically the default for <pre> tags, but setting it explicitly prevents any CSS reset from overriding it.

The font is set to JetBrains Mono, which has excellent box-drawing character support. It renders clean, connected lines at line-height: 1 without the choppy rendering some other monospace fonts produce.

  • Save index.html.
  • Refresh your browser. The diagram should now render in JetBrains Mono with every horizontal and vertical line connecting seamlessly. No gaps between characters or between rows.

Still seeing gaps between lines?

  • Open your browser's DevTools (press F12 or right-click and select Inspect). Click on the <pre> element and check its computed line-height. It should match the font-size value exactly (e.g., both 14px).
  • If the font hasn't loaded from Google Fonts, the browser falls back to a generic monospace font. Check that your Google Fonts <link> tag from Step 1 is still in the <head> and that you're connected to the internet.
  • Make sure the CSS selector is .diagram pre (with a space between .diagram and pre). This targets the <pre> inside the .diagram div.

My box-drawing lines have gaps even after setting line-height and letter-spacing. Help me fix it.

Awesome, I've got everything!

Double check you've saved index.html and that your diagram lines connect seamlessly in the browser.

I'd like to double check the full code

Here is the full index.html file at the end of this step. Compare it with yours to make sure everything matches.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Architecture Diagram</title>

  <!-- Google Fonts: Inter for body text, JetBrains Mono for the diagram -->
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">

  <style>
    /* CSS reset and dark theme body styles */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: #222326;
      color: #f7f8f8;
      font-family: 'Inter', sans-serif;
    }

    /* Style the diagram for seamless box-drawing lines */
    .diagram pre {
      font-family: 'JetBrains Mono', monospace;
      font-size: 14px;
      line-height: 1;
      letter-spacing: 0;
      white-space: pre;
    }
  </style>
</head>
<body>

  <!-- Architecture diagram using box-drawing characters -->
  <div class="diagram">
    <pre>
┌─────────────────────────────────────────────────┐
│  System Architecture                            │
│                                                 │
│  ┌─────────────┐   ┌─────────────┐              │
│  │  Frontend   │   │     API     │              │
│  │             │   │             │              │
│  │  React App  │   │  Express.js │              │
│  │  Port 3000  │   │  Port 8080  │              │
│  └──────┬──────┘   └──────┬──────┘              │
│         │                 │                     │
│         └────────┬────────┘                     │
│                  │                              │
│            ◀─────┼─────▶                        │
│                  │                              │
│         ┌────────┴────────┐                     │
│         │    Database     │                     │
│         │                 │                     │
│         │   PostgreSQL    │                     │
│         │   Port 5432     │                     │
│         └─────────────────┘                     │
│                                                 │
└─────────────────────────────────────────────────┘
    </pre>
  </div>

</body>
</html>

Your ASCII diagram is built and every line connects cleanly. Next up, you'll add colored background layers behind the diagram sections to make each component visually distinct.

Add Colored Background Layers

Add a Glow Animation

Your diagram has colored background layers and clean lines, but it still looks static. The diagrams on Linear's site feel alive because they use subtle animation to draw your eye.

In this step, you'll add a breathing glow effect to the box-drawing characters using CSS @keyframes animation. The glow pulses the diagram's text-shadow between dim and bright, giving the whole thing a futuristic terminal feel.

In this step, get ready to:
  • Add a static text-shadow glow to the diagram's box-drawing characters.
  • Create a keyframes animation that pulses the glow for a breathing effect.
Add a static glow effect

The text-shadow CSS property adds a colored blur behind text. When applied to box-drawing characters on a dark background, it creates a neon glow that makes the diagram look like it's rendered on a terminal screen.

  • In your <style> block in index.html, find your .diagram pre rule and add a text-shadow line so it looks like this:
/* The pre inherits font settings from the container */
.diagram pre {
  font: inherit;
  position: relative;
  text-shadow: 0 0 8px rgba(255, 255, 255, 1); /* White neon glow */
}

What does this code do?

  • The text-shadow values are: 0 horizontal offset, 0 vertical offset, 8px blur radius, and a fully opaque white color.
  • The white glow creates a bright halo around every box-drawing character. Against the dark #222326 background, this makes the lines look like they're emitting light.
  • Save index.html.
  • Refresh your browser. You should see a bright white haze around every box-drawing character. The corners, lines, and arrows all glow against the dark background.

Don't see the glow?

  • Make sure the text-shadow line is inside your existing .diagram pre rule, not in a duplicate selector. If you have two .diagram pre { } blocks, the second one might override the first.
  • Check that the blur radius is 8px, not 0px. A blur of 0 creates a hard duplicate of the text instead of a soft glow.
  • Help me debug my text-shadow glow effect.
Animate the glow with a breathing pulse

A static glow is nice. A glow that slowly breathes in and out is better. You'll define a @keyframes animation that pulses the glow opacity between dim and bright, then apply it to the pre element.

  • In your <style> block, add this @keyframes rule above your .diagram pre selector:
/* Define a breathing glow animation */
@keyframes glow {
  from {
    text-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
  }
  to {
    text-shadow: 0 0 8px rgba(255, 255, 255, 1);
  }
}

What does this code do?

  • The @keyframes glow rule defines two states: from (a dim glow at 20% opacity) and to (the full bright glow at 100% opacity).
  • The browser smoothly interpolates between these two states. Combined with the alternate direction (which you'll add next), it plays forward then backward, creating a breathing loop.
  • Now update your .diagram pre rule. Remove the static text-shadow line and replace it with the animation shorthand, so the rule looks like this:
/* The pre inherits font settings from the container */
.diagram pre {
  font: inherit;
  position: relative;
  animation: glow 1s ease-in-out infinite alternate; /* Breathing glow */
}

What does the animation shorthand mean?

  • glow is the name of the @keyframes rule you just defined.
  • 1s sets each pulse cycle to 1 second.
  • ease-in-out makes the transition smooth at both ends, avoiding a mechanical feel.
  • infinite keeps the animation looping forever.
  • alternate reverses the animation on every other cycle, creating the breathing in-and-out effect.
  • Save index.html and refresh your browser.
  • Watch the diagram for a few seconds. You should see the white glow slowly brighten and dim in a continuous loop. The corners and lines of the box-drawing characters pulse like a terminal screen.

Glow not animating?

  • Make sure you removed the old static text-shadow line and replaced it with the animation line. Having both can cause conflicts.
  • Check that the @keyframes glow block is placed outside of any selector. It should be at the top level of your CSS, not nested inside .diagram pre { }.
  • Help me debug my glow animation.

💡 Pro tip: experiment with glow colors

Try swapping the white (255, 255, 255) for Linear's accent indigo (94, 106, 210) or a green (0, 255, 100) for a classic terminal feel. You can also increase the blur radius from 8px to 16px for a more dramatic effect.

Awesome, I've got everything!

Great. Double-check you have saved index.html and that the glow is pulsing smoothly before moving on.

I'd like to double check the full code

Here is the complete index.html file with all changes from this step included.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Architecture Diagram</title>

  <!-- Google Fonts: Inter for body text, JetBrains Mono for the diagram -->
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">

  <style>
    /* CSS reset and dark theme body styles */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: #222326;
      color: #f7f8f8;
      font-family: 'Inter', sans-serif;
    }

    /* Diagram container: sets the positioning context */
    .diagram {
      font: 1em 'JetBrains Mono', monospace;
      position: relative;
      width: min-content;
      line-height: 1;
    }

    /* Define a breathing glow animation */
    @keyframes glow {
      from {
        text-shadow: 0 0 8px rgba(255, 255, 255, 0.2);
      }
      to {
        text-shadow: 0 0 8px rgba(255, 255, 255, 1);
      }
    }

    /* The pre inherits font settings from the container */
    .diagram pre {
      font: inherit;
      position: relative;
      animation: glow 1s ease-in-out infinite alternate;
    }

    /* Reusable background layer class */
    .bg {
      position: absolute;
      margin: 0.5lh 0.5ch;
      left: calc(var(--x) * 1ch);
      top: calc(var(--y) * 1lh);
      width: calc(var(--w) * 1ch);
      height: calc(var(--h) * 1lh);
      background-color: var(--bg);
      border-radius: 4px;
    }
  </style>
</head>
<body>

  <!-- Diagram container with positioning context -->
  <div class="diagram">

    <!-- Background layers (positioned behind the text) -->
    <div class="bg" style="--bg: rgba(94, 106, 210, 0.12); --x:0; --y:0; --w:51; --h:22;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:3; --y:3; --w:15; --h:6;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:21; --y:3; --w:15; --h:6;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:9; --y:14; --w:19; --h:6;"></div>

    <!-- ASCII diagram -->
    <pre>
┌─────────────────────────────────────────────────┐
│  System Architecture                            │
│                                                 │
│  ┌─────────────┐   ┌─────────────┐              │
│  │  Frontend   │   │     API     │              │
│  │             │   │             │              │
│  │  React App  │   │  Express.js │              │
│  │  Port 3000  │   │  Port 8080  │              │
│  └──────┬──────┘   └──────┬──────┘              │
│         │                 │                     │
│         └────────┬────────┘                     │
│                  │                              │
│            ◀─────┼─────▶                        │
│                  │                              │
│         ┌────────┴────────┐                     │
│         │    Database     │                     │
│         │                 │                     │
│         │   PostgreSQL    │                     │
│         │   Port 5432     │                     │
│         └─────────────────┘                     │
│                                                 │
└─────────────────────────────────────────────────┘
    </pre>
  </div>

</body>
</html>

Your diagram now pulses with a breathing glow effect. Next up, you'll style the surrounding page with a header, descriptive text, and a footer to turn this into a polished portfolio piece.

Style the Surrounding Page

Your diagram is looking sharp. The box-drawing characters connect seamlessly, and the colored background layers highlight each component. But right now, the diagram is floating alone on a dark page with no context.

In this step, you'll build the surrounding page. A header introduces your project, descriptive text explains each component, and a footer ties it all together. By the end, this goes from a cool experiment to a polished portfolio piece.

In this step, get ready to:
  • Add a page header with your project title, date, and description.
  • Add descriptive text below the diagram explaining each component.
  • Add a footer and apply final layout polish to the full page.
Add a page header above the diagram

Every portfolio piece needs context. A header tells visitors what they're looking at before they dive into the diagram itself.

  • In index.html, find the opening <body> tag.
  • Add this header section above your diagram container (above the <div> that wraps your <pre> element):
<!-- Page header -->
<header class="page-header">
  <p class="date">April 2026</p>
  <h1>System Architecture</h1>
  <p class="subtitle">A visual map of how the pieces connect.</p>
</header>

What does this code do?

  • The <header> element groups the page title, date, and subtitle into a semantic block that sits above the diagram.
  • The date class marks the small date line. The subtitle class marks the description underneath the heading.

The header HTML is in place, but it won't look right until you add the CSS. Time to style it.

  • Add these styles inside your <style> tag, below your existing CSS rules:
/* Page header styles */
.page-header {
  max-width: 80ch;
  margin: 0 auto;
  padding: 80px 20px 40px;
}

/* Small date label above the heading */
.page-header .date {
  font-size: 14px;
  color: #8a8f98;
  margin-bottom: 12px;
  letter-spacing: 0.5px;
}

/* Large project title */
.page-header h1 {
  font-family: 'Inter', sans-serif;
  font-size: 48px;
  font-weight: 600;
  letter-spacing: -1px;
  line-height: 1.1;
  margin: 0 0 16px;
}

/* One-line description */
.page-header .subtitle {
  font-size: 18px;
  color: #8a8f98;
  margin: 0;
}

What does this code do?

  • max-width: 80ch caps the header width at 80 characters. This keeps text comfortable to read and aligns with the monospace grid of your diagram.
  • The h1 uses Inter at 48px with tight letter-spacing (-1px). This creates the dense, confident heading style that Linear uses across their pages.
  • The muted color #8a8f98 on the date and subtitle pushes secondary text into the background so the heading dominates.
  • Save index.html.
  • Refresh your browser. You should see a large "System Architecture" heading at the top of the page, with a small date above it and a muted subtitle below it.

Header text not showing up?

  • Make sure the <header> block is inside <body> but above your diagram container <div>.
  • If the heading looks like default browser styling (serif font, no spacing), check that your .page-header h1 CSS rule is inside the <style> tag and has no typos in the selector.
  • Still stuck? Help me debug why my page header styles aren't applying.
Add descriptive text below the diagram

The diagram shows how your components connect, but visitors need a written explanation of what each piece does. A short description section below the diagram completes the story.

  • In index.html, add this section below your diagram container (after the closing </div> of the diagram wrapper):
<!-- Description section -->
<section class="description">
  <h2>How It Works</h2>
  <p>
    The <strong>Frontend</strong> sends user requests to the
    <strong>API</strong> layer, which handles authentication
    and business logic.
  </p>
  <p>
    The API reads from and writes to the <strong>Database</strong>,
    then returns structured responses back to the Frontend
    for rendering.
  </p>
  <p class="muted">
    Each component runs independently and communicates
    over HTTP. This separation keeps the system modular
    and easier to maintain.
  </p>
</section>

What does this code do?

  • The <section> groups the description into its own semantic block, separate from the header and diagram.
  • The <strong> tags highlight component names (Frontend, API, Database) so they stand out in the paragraph text.
  • The muted class on the last paragraph marks it as secondary information. You'll style it with a dimmer color next.
  • Add these styles inside your <style> tag, below the page header styles:
/* Description section */
.description {
  max-width: 80ch;
  margin: 0 auto;
  padding: 40px 20px;
}

/* Section heading */
.description h2 {
  font-family: 'Inter', sans-serif;
  font-size: 24px;
  font-weight: 600;
  margin: 0 0 16px;
}

/* Body paragraphs */
.description p {
  font-size: 16px;
  line-height: 1.6;
  color: #f7f8f8;
  margin: 0 0 12px;
}

/* Muted secondary text */
.description .muted {
  color: #8a8f98;
}

What does this code do?

  • The max-width: 80ch matches the header width, keeping all page content aligned to the same column.
  • Primary text uses #f7f8f8 (near-white) for readability. The .muted class drops to #8a8f98 for secondary information, creating a clear visual hierarchy.
  • Save index.html.
  • Refresh your browser. You should see a "How It Works" heading below the diagram, followed by two white paragraphs and one dimmer paragraph at the bottom.

Description text looks the same color as the muted text?

  • Check that the .description p rule sets color: #f7f8f8 (near-white). If your body CSS from Step 1 already sets a muted text color, the paragraph rule needs to override it.
  • Make sure the class="muted" attribute is only on the last <p> tag, not on all three paragraphs.
  • Need help? Help me figure out why my text colors aren't showing the right contrast.
Add a footer and polish the layout

The content is all there. Now you'll add a footer, center the diagram, and add spacing so the page feels balanced and spacious.

  • In index.html, add this footer below your description section (before the closing </body> tag):
<!-- Page footer -->
<footer class="page-footer">
  <p>Built by <a href="https://github.com">Your Name</a></p>
</footer>

What does this code do?

The footer adds a simple credit line with a link to your GitHub profile. Replace "Your Name" and the href URL with your own details.

  • Save index.html.

Now add the CSS for the footer and the final layout polish. This is the last chunk of CSS for the page.

  • Add these styles inside your <style> tag, below the description styles:
/* Footer */
.page-footer {
  max-width: 80ch;
  margin: 0 auto;
  padding: 40px 20px 80px;
  font-size: 14px;
  color: #8a8f98;
}

/* Footer link */
.page-footer a {
  color: #8a8f98;
  text-decoration: underline;
  text-underline-offset: 3px;
}

.page-footer a:hover {
  color: #f7f8f8;
}

/* Center the diagram on the page */
.diagram {
  max-width: fit-content;
  margin: 40px auto;
  padding: 0 20px;
}

What does this code do?

  • The footer matches the same max-width: 80ch and margin: 0 auto pattern as the header and description, keeping everything on the same center column.
  • The link starts muted and brightens to white on hover. The text-underline-offset: 3px pushes the underline slightly away from the text for a cleaner look.
  • The .diagram centering rule uses margin: 40px auto to center the diagram on the page. The fit-content value makes the container shrink-wrap to the diagram's width instead of stretching full-width.
  • Save index.html.
  • Refresh your browser. Your diagram should now be centered on the page with breathing room above and below it.
  • Scroll to the bottom. You should see your name as a subtle, muted link. Hover over it to confirm it brightens to white.

Diagram not centered?

  • Make sure the .diagram CSS rule includes max-width: fit-content and margin: 40px auto. Both are needed for centering.
  • If the diagram is centered but the header and description are not aligned with it, that is expected. The diagram uses fit-content (sized to its content) while the text sections use 80ch (sized to character count). They center independently.
  • Still not working? Help me center my diagram on the page.

Awesome, I've got everything!

Double-check that you've saved index.html and that your page shows the header, diagram, description, and footer all styled and centered.

I'd like to double check the full code

Here is the complete index.html file with all the HTML and CSS from Steps 1 through 4. Your diagram content will differ, but the structure should match.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>System Architecture</title>

  <!-- Google Fonts: Inter for body, JetBrains Mono for diagram -->
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
  <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">

  <style>
    /* CSS reset and dark theme */
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background: #222326;
      color: #f7f8f8;
      font-family: 'Inter', sans-serif;
    }

    /* Diagram container: positioning context and centering */
    .diagram {
      font: 1em 'JetBrains Mono', monospace;
      position: relative;
      width: min-content;
      line-height: 1;
      max-width: fit-content;
      margin: 40px auto;
      padding: 0 20px;
    }

    /* The pre inherits font settings from the container */
    .diagram pre {
      font: inherit;
      position: relative;
    }

    /* Reusable background layer class */
    .bg {
      position: absolute;
      margin: 0.5lh 0.5ch;
      left: calc(var(--x) * 1ch);
      top: calc(var(--y) * 1lh);
      width: calc(var(--w) * 1ch);
      height: calc(var(--h) * 1lh);
      background-color: var(--bg);
      border-radius: 4px;
    }

    /* Page header styles */
    .page-header {
      max-width: 80ch;
      margin: 0 auto;
      padding: 80px 20px 40px;
    }

    /* Small date label above the heading */
    .page-header .date {
      font-size: 14px;
      color: #8a8f98;
      margin-bottom: 12px;
      letter-spacing: 0.5px;
    }

    /* Large project title */
    .page-header h1 {
      font-family: 'Inter', sans-serif;
      font-size: 48px;
      font-weight: 600;
      letter-spacing: -1px;
      line-height: 1.1;
      margin: 0 0 16px;
    }

    /* One-line description */
    .page-header .subtitle {
      font-size: 18px;
      color: #8a8f98;
      margin: 0;
    }

    /* Description section */
    .description {
      max-width: 80ch;
      margin: 0 auto;
      padding: 40px 20px;
    }

    /* Section heading */
    .description h2 {
      font-family: 'Inter', sans-serif;
      font-size: 24px;
      font-weight: 600;
      margin: 0 0 16px;
    }

    /* Body paragraphs */
    .description p {
      font-size: 16px;
      line-height: 1.6;
      color: #f7f8f8;
      margin: 0 0 12px;
    }

    /* Muted secondary text */
    .description .muted {
      color: #8a8f98;
    }

    /* Footer */
    .page-footer {
      max-width: 80ch;
      margin: 0 auto;
      padding: 40px 20px 80px;
      font-size: 14px;
      color: #8a8f98;
    }

    /* Footer link */
    .page-footer a {
      color: #8a8f98;
      text-decoration: underline;
      text-underline-offset: 3px;
    }

    .page-footer a:hover {
      color: #f7f8f8;
    }
  </style>
</head>
<body>

  <!-- Page header -->
  <header class="page-header">
    <p class="date">April 2026</p>
    <h1>System Architecture</h1>
    <p class="subtitle">A visual map of how the pieces connect.</p>
  </header>

  <!-- Diagram -->
  <div class="diagram">
    <!-- Background layers -->
    <div class="bg" style="--bg: rgba(94, 106, 210, 0.12); --x:0; --y:0; --w:51; --h:22;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:3; --y:3; --w:15; --h:6;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:21; --y:3; --w:15; --h:6;"></div>
    <div class="bg" style="--bg: rgba(113, 112, 255, 0.08); --x:9; --y:14; --w:19; --h:6;"></div>

    <!-- ASCII diagram -->
    <pre>
┌─────────────────────────────────────────────────┐
│  System Architecture                            │
│                                                 │
│  ┌─────────────┐   ┌─────────────┐              │
│  │  Frontend   │   │     API     │              │
│  │             │   │             │              │
│  │  React App  │   │  Express.js │              │
│  │  Port 3000  │   │  Port 8080  │              │
│  └──────┬──────┘   └──────┬──────┘              │
│         │                 │                     │
│         └────────┬────────┘                     │
│                  │                              │
│            ◀─────┼─────▶                        │
│                  │                              │
│         ┌────────┴────────┐                     │
│         │    Database     │                     │
│         │                 │                     │
│         │   PostgreSQL    │                     │
│         │   Port 5432     │                     │
│         └─────────────────┘                     │
│                                                 │
└─────────────────────────────────────────────────┘
    </pre>
  </div>

  <!-- Description section -->
  <section class="description">
    <h2>How It Works</h2>
    <p>
      The <strong>Frontend</strong> sends user requests to the
      <strong>API</strong> layer, which handles authentication
      and business logic.
    </p>
    <p>
      The API reads from and writes to the <strong>Database</strong>,
      then returns structured responses back to the Frontend
      for rendering.
    </p>
    <p class="muted">
      Each component runs independently and communicates
      over HTTP. This separation keeps the system modular
      and easier to maintain.
    </p>
  </section>

  <!-- Page footer -->
  <footer class="page-footer">
    <p>Built by <a href="https://github.com">Your Name</a></p>
  </footer>

</body>
</html>

Your page is complete. You have a header, a centered ASCII diagram with colored layers, a description section, and a footer. Next up, the Secret Mission challenges you to make it responsive and add a glow animation.

Secret mission

Add Flowing Data Lines

Your diagram glows, but the connection lines are still static. What if you could make the lines look like data is actually flowing between components? In this secret mission, you'll add animated flowing dashes to the connection lines using CSS gradients.

Before you go

This project runs entirely locally with a single HTML file opened in your browser. There are no cloud resources, servers, or ongoing costs to worry about.

Decide whether to keep your project files, or delete them entirely.

Resources you used:

  • Your project folder containing index.html.
  • Google Fonts loaded via external links (JetBrains Mono and Inter). These are free and require no account.
  • Your browser tab with the local file open.

Keep everything running

No action needed. Choose this if you want to keep iterating on your diagram or use it as a starting point for future projects.

  • Your project folder and index.html file stay right where they are.
  • You can reopen the file in your browser anytime by double-clicking it or dragging it into a browser window.
  • Consider pushing the project to a GitHub repository so you have a backup and can share it as a portfolio piece.

Pause - I'll come back to this later

Since this is a local project with no running processes, there is nothing to pause. Your files will be waiting for you whenever you come back.

  • Close the browser tab showing your diagram.
  • Close the project folder in VS Code or Cursor.
  • Your project folder remains on your computer. Reopen it anytime to pick up where you left off.

Delete - I don't want to use this again

Remove all project files from your computer. This cannot be undone.

  • Close the browser tab showing your diagram.
  • Close the project folder in VS Code or Cursor.
  • Delete the entire project folder from your file system.
  • That's it. There are no cloud resources, accounts, or services to clean up.

Mission accomplished

Nice work! You just built a Linear-style ASCII architecture diagram page from scratch using nothing but HTML and CSS.

Here's what you learned:

  • Used Unicode box-drawing characters to build nested architecture diagrams with corners, lines, tees, and arrows inside a <pre> element.
  • Added a breathing glow animation using @keyframes and text-shadow to give the diagram a futuristic terminal feel.
  • Built a complete portfolio page with a header, description section, and footer styled with Linear's Nordic Gray palette.
  • Secret Mission: Added animated flowing data lines to the diagram's connection paths using CSS gradient overlays.

Ready to quiz yourself?