Build an Impact Dashboard

Build an animated impact dashboard with HTML, CSS, and JavaScript.

Introduction

30 Second Summary

You have led training programmes that reached thousands of farmers and students across Ghana through the GIZ ABF Matching Fund. A visual, interactive dashboard captures the scale of that impact far more powerfully than a written report.

In this project, you will build a professional impact dashboard from scratch using HTML, CSS, and JavaScript. The finished page runs right in Google Chrome from your Desktop, featuring animated counters, a delivery timeline, and a photo gallery ready for your own field images.

What You'll Build

Open the finished file in Chrome and watch animated counters tick up to your real impact numbers while scroll animations reveal your timeline, partners, and field photos one section at a time.

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

  • Animated impact counters that tick up from zero to your real numbers, covering 10,000+ farmers trained, 60,000+ SHS students, 50+ projects coordinated, and more.
  • A delivery timeline and four workstream cards tracing your programme journey from initiation through stakeholder engagement.
  • A partner showcase, photo gallery, and scroll-reveal animations that bring each section to life as visitors scroll down the page.
  • Secret Mission: Add a dark mode toggle that transforms the entire dashboard with one click.

Are there any prerequisites?

No coding experience or additional tools needed. You will use Notepad and Google Chrome, both already on your Windows PC.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Project Folder and Files

Every web page is built from three types of files working together. HTML defines the content and structure you see on the page. CSS controls how that content looks, from colours to layout.

JavaScript adds movement and interactivity. In this step, you'll create a project folder on your Desktop and save three starter files inside it so Chrome can display your first page.

In this step, get ready to:
  • Create a giz-dashboard project folder on your Desktop.
  • Save three starter files: index.html, style.css, and script.js.
  • Open the dashboard in Chrome to see your first result.
Create the project folder and HTML file

Your dashboard needs a dedicated folder so all three files can find each other. The HTML file is the main file Chrome opens, and it links to the CSS and JavaScript files so they work together as one page.

  • Right-click on an empty area of your Desktop.
  • Click New.
  • Click Folder.
  • Type giz-dashboard as the folder name and press Enter.

You should see the giz-dashboard folder appear on your Desktop.

  • Press the Windows key on your keyboard to open the search bar.
  • Type Notepad and press Enter to open it.
  • Copy and paste the following HTML code into the empty Notepad window:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GIZ ABF Matching Fund Impact Dashboard</title>
    <link rel="stylesheet" href="style.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body>
    <p>GIZ ABF Matching Fund Impact Dashboard</p>
    <script src="script.js"></script>
</body>
</html>

What does this HTML do?

  • The <head> section sets the page title and loads two resources: your style.css stylesheet and the Inter font from Google Fonts.
  • The <body> holds a single paragraph with the dashboard title. You'll replace this with full dashboard content in later steps.
  • The <script> tag at the bottom links to script.js so JavaScript loads after the page content appears.
  • Click File in the Notepad menu bar.
  • Click Save As.
  • Navigate to the giz-dashboard folder on your Desktop.
  • Type index.html in the File name field.
  • Click the Save as type dropdown.
  • Select All Files (*.*).
  • Click Save.

Why change Save as type?

Notepad defaults to saving files as .txt. Changing the type to All Files (*.*) lets you save with the .html extension that Chrome needs to recognise the file as a web page.

You'll need to do this every time you save a new file in this project.

  • Open the giz-dashboard folder on your Desktop by double-clicking it.

You should see index.html listed inside the folder. If you see index.html.txt instead, delete that file and save again with Save as type set to All Files (*.*).

Seeing index.html.txt instead?

This means Notepad added its default .txt extension. Delete the incorrect file from the folder, go back to Notepad, paste the code again, and repeat the Save As steps. Make sure you select All Files (*.*) before clicking Save.

Still stuck?

Help me fix a .txt extension problem when saving HTML in Notepad.

✔️ Awesome, I've got everything!

Great. Double check you've saved index.html in the giz-dashboard folder, then move on to the next file.

ⓧ I'd like to double check the full code

Here is the complete index.html file. Compare it with what you pasted into Notepad.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>GIZ ABF Matching Fund Impact Dashboard</title>
    <link rel="stylesheet" href="style.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet">
</head>
<body>
    <p>GIZ ABF Matching Fund Impact Dashboard</p>
    <script src="script.js"></script>
</body>
</html>
Create the CSS and JavaScript files

The CSS file stores all the colour and layout rules your dashboard will use. You'll start with colour variables and base styles that every section builds on in later steps.

  • Press the Windows key to open your search bar.
  • Type Notepad and press Enter to open a new Notepad window.
  • Copy and paste the following CSS code into the new Notepad window:
:root {
    --primary: #00796B;
    --primary-dark: #004D40;
    --primary-light: #B2DFDB;
    --accent: #FFB300;
    --accent-dark: #FF8F00;
    --bg-main: #F5F7FA;
    --bg-card: #FFFFFF;
    --bg-section-alt: #EDF2F7;
    --text-primary: #1A202C;
    --text-secondary: #5A6474;
    --text-light: #FFFFFF;
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
    --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08);
    --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
    --radius: 12px;
    --radius-sm: 8px;
    --transition: all 0.3s ease;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: var(--bg-main);
    color: var(--text-primary);
    line-height: 1.6;
    overflow-x: hidden;
}
.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }

What does this CSS do?

  • The :root section defines colour variables like --primary (teal) and --accent (amber). Using variables means you change a colour once and it updates everywhere on the page.
  • The * selector resets all elements to have zero margins and padding, giving you a clean starting point.
  • The body rule sets the Inter font, a light grey background, and dark text colour for the entire page.
  • The .container class centres content with a max width of 1200 pixels so the dashboard does not stretch too wide on large screens.
  • Click File in the Notepad menu bar.
  • Click Save As.
  • Navigate to the giz-dashboard folder on your Desktop.
  • Type style.css in the File name field.
  • Set Save as type to All Files (*.*).
  • Click Save.
  • Open the giz-dashboard folder on your Desktop.

You should now see both index.html and style.css listed inside the folder.

One more file to go. The JavaScript file will hold all your animation code later. For now it just needs a placeholder comment.

  • Press the Windows key to open your search bar.
  • Type Notepad and press Enter to open another new Notepad window.
  • Copy and paste the following single line into the new Notepad window:
// GIZ ABF Matching Fund Impact Dashboard - JavaScript
  • Click File in the Notepad menu bar.
  • Click Save As.
  • Navigate to the giz-dashboard folder on your Desktop.
  • Type script.js in the File name field.
  • Set Save as type to All Files (*.*).
  • Click Save.
  • Open the giz-dashboard folder on your Desktop.

You should see all three files listed: index.html, style.css, and script.js.

Missing a file or seeing .txt extensions?

If any file has a .txt extension (like style.css.txt), delete it, reopen Notepad, paste the code again, and save with Save as type set to All Files (*.*).

If you only see two files, repeat the steps above for the missing file. All three must be in the same giz-dashboard folder for the dashboard to work.

Help me fix file extension or missing file issues in my giz-dashboard folder.

✔️ Awesome, I've got everything!

Great. Double check you've saved all three files in the giz-dashboard folder, then move on to open the dashboard in Chrome.

ⓧ I'd like to double check the full code

Here is the complete style.css file. Compare it with what you pasted into Notepad.

:root {
    --primary: #00796B;
    --primary-dark: #004D40;
    --primary-light: #B2DFDB;
    --accent: #FFB300;
    --accent-dark: #FF8F00;
    --bg-main: #F5F7FA;
    --bg-card: #FFFFFF;
    --bg-section-alt: #EDF2F7;
    --text-primary: #1A202C;
    --text-secondary: #5A6474;
    --text-light: #FFFFFF;
    --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.06);
    --shadow-md: 0 4px 20px rgba(0, 0, 0, 0.08);
    --shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
    --radius: 12px;
    --radius-sm: 8px;
    --transition: all 0.3s ease;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background: var(--bg-main);
    color: var(--text-primary);
    line-height: 1.6;
    overflow-x: hidden;
}
.container { max-width: 1200px; margin: 0 auto; padding: 0 24px; }

Your script.js should contain just this single line:

// GIZ ABF Matching Fund Impact Dashboard - JavaScript
Open the dashboard in Chrome

All three files are in place. Before you open the page, what do you think you'll see? A fully styled dashboard, or something simpler?

  • Open the giz-dashboard folder on your Desktop.
  • Double-click index.html.

Chrome opens and displays a plain white page with the text "GIZ ABF Matching Fund Impact Dashboard" in small, unstyled font near the top-left corner. The page looks bare and that is expected.

Why does it look so plain?

Right now the HTML only contains a single paragraph of text. The CSS file has base styles and colour variables but no rules that target this paragraph. In the next step you'll add the hero banner, counter cards, and the CSS that brings them to life.

Page not opening in Chrome?

If double-clicking index.html opens a different browser or Notepad, right-click the file instead. Select Open with and choose Google Chrome.

If you see a blank page with no text at all, check that your index.html file was saved without the .txt extension and that the code inside matches the code block above.

Help me get my HTML file to open correctly in Chrome on Windows.

Your project files are set up and connected. Next up, you'll build the hero banner with your programme details and add seven impact counter cards.

Build the Hero Banner and Impact Counters

Your project folder is ready and Chrome is showing the raw dashboard title. Now it is time to build what visitors see first: the hero banner that introduces the GIZ ABF Matching Fund programme and your role as Project Lead.

Below the hero, seven impact counter cards display your results at a glance. You will build the HTML structure for both sections, then bring them to life with CSS styling.

In this step, get ready to:
  • Add the navigation bar, hero banner, and seven impact counter cards to index.html.
  • Style the hero gradient background, text layout, and counter card grid in style.css.
Add the hero section and counter cards to the HTML

The current index.html file has a single placeholder paragraph. You will replace it with three new sections: a navigation bar, the hero banner, and the impact counter grid.

  • Switch back to the Notepad window that has index.html open.
  • Find the line that says:
<p>GIZ ABF Matching Fund Impact Dashboard</p>
  • Select that entire line and delete it.
  • Place your cursor on the blank line between <body> and <script src="script.js"></script>.
  • Add the navigation bar by pasting the following code at your cursor:

    <nav class="navbar" id="navbar">
        <div class="nav-container">
            <a href="#hero" class="nav-brand">ABF Impact</a>
            <ul class="nav-links">
                <li><a href="#impact">Impact</a></li>
                <li><a href="#timeline">Timeline</a></li>
                <li><a href="#workstreams">Delivery</a></li>
                <li><a href="#partners">Partners</a></li>
                <li><a href="#gallery">Gallery</a></li>
            </ul>
        </div>
    </nav>

What does this code do?

The <nav> element creates a fixed navigation bar at the top of the page. Each link points to a section that you will build in later steps (Impact, Timeline, Delivery, Partners, Gallery).

  • Place your cursor on a new line directly below the closing </nav> tag you just pasted.
  • Add the hero banner section by pasting this code:

    <section class="hero" id="hero">
        <div class="hero-content">
            <span class="hero-badge">Donor-Funded Programme | BMZ + EU + OACPS</span>
            <h1>GIZ ABF Matching Fund</h1>
            <h2>Impact Dashboard</h2>
            <div class="hero-divider"></div>
            <p class="hero-role">Project Lead | Farming in Africa</p>
            <p class="hero-desc">Leading strategic partnerships with the GIZ Matching Grant Fund, successfully training 74,000+ individuals in climate-resilient sustainable agriculture across 16 regions of Ghana and parts of Nigeria.</p>
            <div class="hero-tags">
                <span class="tag">Cocoa</span>
                <span class="tag">Maize</span>
                <span class="tag">Livestock</span>
                <span class="tag">Climate Resilience</span>
            </div>
        </div>
        <div class="hero-scroll">
            <span>Scroll to explore</span>
            <div class="scroll-arrow"></div>
        </div>
    </section>

What does this code do?

This section creates the full-screen hero banner. It includes the programme badge, the main title, your role, a description of the programme's reach across 16 regions, and four value chain tags (Cocoa, Maize, Livestock, Climate Resilience).

The hero-scroll div at the bottom adds a "Scroll to explore" prompt with an animated arrow.

  • Save index.html by pressing Ctrl+S.
  • Switch to Chrome and refresh the page.

You should see the navigation link text (Impact, Timeline, Delivery, Partners, Gallery) at the top of the page. Below it, the hero content appears as plain unstyled text including the programme title, your role, and the value chain tags.

Don't see the new text?

  • Make sure you deleted the original <p>GIZ ABF Matching Fund Impact Dashboard</p> line. If it is still there, the old text will show alongside the new content.
  • Confirm that the <script src="script.js"></script> line is still present near the bottom of the file. If it was accidentally deleted, add it back before </body>.
  • Check that you saved the file as index.html with All Files (*.*) selected in the "Save as type" dropdown.

Still stuck? Help me figure out why my HTML changes are not showing in Chrome.

Now you will add the impact counter section below the hero. This section holds seven cards that display your key programme numbers.

  • Switch back to index.html in Notepad.
  • Place your cursor on a new line directly below the closing </section> tag of the hero section.
  • Add the impact section header and first two counter cards by pasting this code:

    <section class="impact" id="impact">
        <div class="container">
            <div class="section-header">
                <span class="section-label">Results</span>
                <h2>Impact at a Glance</h2>
                <p>Measurable outcomes delivered across Ghana and Nigeria</p>
            </div>
            <div class="counter-grid">
                <div class="counter-card featured">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#127806;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="10000">0</span><span class="counter-plus">+</span>
                    </div>
                    <h3>Farmers Trained</h3>
                    <p>In climate-resilient sustainable agriculture across Ghana and Nigeria</p>
                </div>
                <div class="counter-card featured">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#127891;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="60000">0</span><span class="counter-plus">+</span>
                    </div>
                    <h3>SHS Students Trained</h3>
                    <p>Across senior high schools in Ghana in under 1 year</p>
                </div>

What do the counter cards contain?

Each counter card has an icon, a number with a data-target attribute storing the real value, and a description. The number starts at 0 because the JavaScript animation that counts up has not been added yet.

The featured class on the first two cards marks them for a highlighted border. The first card tracks 10,000+ farmers trained and the second tracks 60,000+ SHS students trained.

  • Place your cursor directly after the last line you just pasted (the closing </div> of the SHS Students card).
  • Add the next three counter cards by pasting this code:
                <div class="counter-card">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#127979;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="4000">0</span><span class="counter-plus">+</span>
                    </div>
                    <h3>University Students</h3>
                    <p>KNUST, Tamale Agric College, Navrongo Agric College, UENR</p>
                </div>
                <div class="counter-card">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#128203;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="50">0</span><span class="counter-plus">+</span>
                    </div>
                    <h3>Projects Coordinated</h3>
                    <p>Across 16 regions of Ghana and parts of Nigeria</p>
                </div>
                <div class="counter-card">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#127942;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="40">0</span><span class="counter-plus">+</span>
                    </div>
                    <h3>Milestones Achieved</h3>
                    <p>Strengthening execution discipline across workstreams</p>
                </div>
  • Save index.html and refresh Chrome.

You should see five counter card entries appearing as plain text below the hero content, each showing "0" as the number.

  • Switch back to index.html in Notepad.
  • Place your cursor directly after the last line you pasted (the closing </div> of the Milestones card).
  • Add the final two counter cards and the closing tags by pasting this code:
                <div class="counter-card">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#128176;</span></div>
                    <div class="counter-value">
                        <span class="counter-prefix">&#8364;</span><span class="counter-number" data-target="500">0</span><span class="counter-plus">K+</span>
                    </div>
                    <h3>Budget Managed</h3>
                    <p>In project budget commitments strengthening sustainable agri-value chains</p>
                </div>
                <div class="counter-card">
                    <div class="counter-icon-wrap"><span class="counter-icon">&#127758;</span></div>
                    <div class="counter-value">
                        <span class="counter-number" data-target="16">0</span>
                    </div>
                    <h3>Regions Covered</h3>
                    <p>All 16 regions of Ghana plus parts of Nigeria</p>
                </div>
            </div>
        </div>
    </section>

What about the Budget Managed card?

The Budget Managed card includes a counter-prefix span that displays the Euro symbol before the number, and K+ after it. This formats the value as "EUR 500K+" once the animation runs.

  • Save index.html and refresh Chrome.

All seven counter card entries now appear as unstyled text. Each number displays "0" for now. The HTML structure is complete.

Seeing jumbled or missing text?

  • Check that the closing tags </div> at the bottom of the impact section are present. You need three closing </div> tags followed by </section> to close the counter-grid, container, and impact section.
  • Make sure each code block was pasted directly below the previous one with no text accidentally deleted between them.

Need a hand? Help me figure out why my counter cards HTML looks wrong in Chrome.

✔️ Awesome, I've got everything!

Great. Make sure you have saved index.html before moving on to the CSS.

ⓧ I'd like to double check the full code

Your index.html file is longer than a single block, so scroll up through this step and confirm your file contains each piece in this order:

  • The original <head> section (unchanged from Step 1) with the stylesheet and font links.
  • The <nav> navigation bar with five links.
  • The <section class="hero"> with the badge, titles, role, description, tags, and scroll prompt.
  • The <section class="impact"> containing the section header and all seven counter cards (Farmers Trained, SHS Students Trained, University Students, Projects Coordinated, Milestones Achieved, Budget Managed, Regions Covered).
  • The <script src="script.js"></script> line before </body>.
Style the hero and counters

The HTML is in place but everything looks like plain text. CSS will transform the page with a teal gradient hero background, styled typography, and a card grid for the counters.

  • Switch to the Notepad window that has style.css open.
  • Place your cursor at the very end of the file, after the closing } of the .container rule.
  • Add the navigation bar styles by pasting this code:

/* Navbar */
.navbar {
    position: fixed; top: 0; left: 0; right: 0; z-index: 1000;
    padding: 16px 0; transition: var(--transition);
}
.navbar.scrolled {
    background: rgba(0, 77, 64, 0.95); padding: 10px 0;
    box-shadow: var(--shadow-md); backdrop-filter: blur(10px);
}
.nav-container {
    max-width: 1200px; margin: 0 auto; padding: 0 24px;
    display: flex; justify-content: space-between; align-items: center;
}
.nav-brand { font-size: 1.2rem; font-weight: 700; color: var(--text-light); text-decoration: none; letter-spacing: -0.5px; }
.nav-links { display: flex; list-style: none; gap: 32px; }
.nav-links a { color: rgba(255,255,255,0.85); text-decoration: none; font-size: 0.9rem; font-weight: 500; transition: var(--transition); }
.nav-links a:hover { color: var(--accent); }

What does this CSS do?

The position: fixed rule pins the navbar to the top of the screen so it stays visible as the visitor scrolls. The .scrolled class adds a dark background when JavaScript activates it later.

  • Place your cursor directly below the navbar code you just pasted.
  • Add the hero section styles by pasting this code:

/* Hero Section */
.hero {
    min-height: 100vh;
    background: linear-gradient(135deg, var(--primary-dark) 0%, var(--primary) 50%, #00897B 100%);
    display: flex; flex-direction: column; justify-content: center; align-items: center;
    text-align: center; padding: 120px 24px 60px; position: relative;
}
.hero::before {
    content: ''; position: absolute; top: 0; left: 0; right: 0; bottom: 0;
    background: radial-gradient(circle at 30% 70%, rgba(255,179,0,0.1) 0%, transparent 60%),
                radial-gradient(circle at 70% 30%, rgba(255,255,255,0.05) 0%, transparent 50%);
    pointer-events: none;
}
.hero-content { position: relative; z-index: 1; max-width: 800px; }
.hero-badge {
    display: inline-block; background: rgba(255,255,255,0.15); color: var(--text-light);
    padding: 8px 20px; border-radius: 50px; font-size: 0.8rem; font-weight: 600;
    letter-spacing: 1px; text-transform: uppercase; margin-bottom: 24px;
    border: 1px solid rgba(255,255,255,0.2);
}
.hero h1 { font-size: 3.5rem; font-weight: 800; color: var(--text-light); line-height: 1.1; letter-spacing: -1.5px; margin-bottom: 8px; }
.hero h2 { font-size: 2rem; font-weight: 300; color: var(--accent); margin-bottom: 24px; }
.hero-divider { width: 60px; height: 3px; background: var(--accent); margin: 0 auto 24px; border-radius: 2px; }
.hero-role { font-size: 1.1rem; font-weight: 600; color: var(--text-light); margin-bottom: 16px; }
.hero-desc { font-size: 1.05rem; color: rgba(255,255,255,0.85); max-width: 650px; margin: 0 auto 32px; line-height: 1.7; }
.hero-tags { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px; }
.tag { background: rgba(255,179,0,0.2); color: var(--accent); padding: 6px 16px; border-radius: 50px; font-size: 0.85rem; font-weight: 500; border: 1px solid rgba(255,179,0,0.3); }
.hero-scroll { position: absolute; bottom: 30px; left: 50%; transform: translateX(-50%); text-align: center; color: rgba(255,255,255,0.6); font-size: 0.8rem; }
.scroll-arrow { width: 20px; height: 20px; border-right: 2px solid rgba(255,255,255,0.6); border-bottom: 2px solid rgba(255,255,255,0.6); transform: rotate(45deg); margin: 8px auto 0; animation: scrollBounce 2s infinite; }
@keyframes scrollBounce { 0%, 100% { transform: rotate(45deg) translateY(0); } 50% { transform: rotate(45deg) translateY(8px); } }

What creates the teal gradient?

The linear-gradient property blends three shades of teal at a 135-degree angle, creating the banner's smooth colour transition. The ::before pseudo-element adds a subtle golden radial glow for depth.

  • Save style.css and refresh Chrome.

A full-screen teal gradient hero banner now fills the page. You should see "GIZ ABF Matching Fund" as the large heading, "Impact Dashboard" in gold below it, your role as Project Lead, and the four value chain tags (Cocoa, Maize, Livestock, Climate Resilience). A bouncing scroll arrow sits at the bottom.

Hero still looks like plain text?

  • Confirm that you saved style.css (not just index.html). The CSS file must be saved for the browser to pick up the new styles.
  • Check that the <link rel="stylesheet" href="style.css"> line is still in the <head> of your index.html file.
  • Make sure style.css is in the same giz-dashboard folder as index.html.

Still stuck? Help me figure out why my CSS styles are not applying to the hero section.

The hero looks great. Now add the styles that transform the plain counter text into a polished card grid.

  • Switch back to style.css in Notepad.
  • Place your cursor at the very end of the file, after the closing } of the scrollBounce animation you just pasted.
  • Add the section header, counter card, and responsive styles by pasting this code:

/* Section Headers */
.section-header { text-align: center; margin-bottom: 48px; }
.section-label { display: inline-block; background: var(--primary-light); color: var(--primary-dark); padding: 4px 14px; border-radius: 50px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1.5px; margin-bottom: 12px; }
.section-header h2 { font-size: 2.2rem; font-weight: 800; color: var(--text-primary); letter-spacing: -1px; margin-bottom: 8px; }
.section-header p { font-size: 1.05rem; color: var(--text-secondary); }

/* Impact Section */
.impact { padding: 80px 0; background: var(--bg-main); }
.counter-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 24px; }
.counter-card { background: var(--bg-card); border-radius: var(--radius); padding: 32px 24px; text-align: center; box-shadow: var(--shadow-sm); transition: var(--transition); border: 1px solid rgba(0,0,0,0.04); }
.counter-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.counter-card.featured { border-top: 3px solid var(--primary); }
.counter-icon-wrap { margin-bottom: 16px; }
.counter-icon { font-size: 2.5rem; }
.counter-value { display: flex; align-items: baseline; justify-content: center; gap: 2px; margin-bottom: 8px; }
.counter-number { font-size: 2.8rem; font-weight: 800; color: var(--primary-dark); letter-spacing: -1px; }
.counter-prefix { font-size: 2rem; font-weight: 700; color: var(--primary-dark); }
.counter-plus { font-size: 1.5rem; font-weight: 700; color: var(--primary); }
.counter-card h3 { font-size: 1rem; font-weight: 700; color: var(--text-primary); margin-bottom: 6px; }
.counter-card p { font-size: 0.85rem; color: var(--text-secondary); line-height: 1.5; }

@media (max-width: 768px) {
    .hero h1 { font-size: 2.2rem; }
    .hero h2 { font-size: 1.4rem; }
    .nav-links { display: none; }
    .section-header h2 { font-size: 1.7rem; }
    .counter-grid { grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 16px; }
    .counter-number { font-size: 2rem; }
}

What does this CSS do?

The counter-grid uses CSS Grid to automatically arrange the seven cards in responsive columns. Each card gets rounded corners, a subtle shadow, and a hover effect that lifts it upward.

The @media block at the bottom adjusts font sizes and column widths for smaller screens so the dashboard looks good on mobile.

Before you refresh, think about what the counter card numbers will display. Will they show the real impact numbers or something else?

  • Save style.css and refresh Chrome.
  • Scroll down past the hero banner to see the counter cards.

Seven white counter cards are arranged in a grid below the "Impact at a Glance" heading. Each card shows an icon, a large "0" as the number, a title, and a description. The first two cards (Farmers Trained and SHS Students Trained) have a teal top border marking them as featured.

Every counter displays "0" right now. That is intentional. The JavaScript animation code in Step 5 will make these numbers count up from 0 to their real values.

Cards look squished or overlapping?

  • Make sure the counter-grid CSS rule was pasted correctly. The grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)) value controls the card layout.
  • Verify that each counter card <div> in your HTML has a matching closing </div>. A missing closing tag can break the grid layout.

Need help? Help me figure out why my counter card grid layout is broken.

✔️ Awesome, I've got everything!

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

ⓧ I'd like to double check the full code

Your style.css file is longer than a single block. Scroll up through this step and confirm your file contains each piece in this order:

  • The original :root variables, reset, and .container rule (unchanged from Step 1).
  • The Navbar styles (.navbar through .nav-links a:hover).
  • The Hero Section styles (.hero through the scrollBounce animation).
  • The Section Headers styles (.section-header through .section-header p).
  • The Impact Section styles (.impact through .counter-card p).
  • The @media (max-width: 768px) responsive block at the end.

Your dashboard already makes a strong first impression with the branded hero banner and seven impact cards. Next up, you will add a project delivery timeline and workstream cards that show how you delivered those results.

Add the Project Timeline and Delivery Workstreams

Your hero banner and impact counters give visitors the headline numbers. But numbers alone don't tell the full story.

Stakeholders and employers want to see how you delivered those results. In this step, you will add a project delivery timeline that traces your five programme phases and four workstream cards that explain your core delivery areas.

In this step, get ready to:
  • Add a five-phase project timeline tracing the programme journey from initiation to stakeholder engagement.
  • Add four workstream cards covering coordination, training, engagement, and grant writing.
  • Style both sections with a vertical timeline track, phase markers, and card layouts.
Add the timeline and workstream HTML

The timeline section tells the story of your programme delivery across five phases. Each phase sits inside a card connected by a vertical track.

  • Switch back to index.html in Notepad.
  • Find the closing </section> tag of the impact section (the one right above <script src="script.js"></script>).
  • Click at the end of that </section> line and press Enter to create a blank line.
  • Add the first part of the timeline section by pasting this code on the new blank line:
    <!-- Timeline section - five delivery phases -->
    <section class="timeline" id="timeline">
        <div class="container">
            <div class="section-header">
                <span class="section-label">Journey</span>
                <h2>Project Delivery Timeline</h2>
                <p>Key phases in the GIZ ABF Matching Fund implementation</p>
            </div>
            <div class="timeline-track">
                <div class="timeline-line"></div>
                <!-- Phase 1 -->
                <div class="timeline-item reveal-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-phase">Phase 1</span>
                        <h3>Programme Initiation</h3>
                        <p>Secured strategic partnership with GIZ ABF Funding Team. Established programme governance, stakeholder mapping, and delivery framework for the Matching Grant Fund initiative.</p>
                    </div>
                </div>
                <!-- Phase 2 -->
                <div class="timeline-item reveal-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-phase">Phase 2</span>
                        <h3>Grant Writing and Funding</h3>
                        <p>Led grant writing and proposal development. Secured EUR 500,000+ in budget commitments to strengthen sustainable agri-value chains across cocoa, maize, and livestock sectors.</p>
                    </div>
                </div>

What does this code do?

This creates the timeline section header and the first two delivery phases. Each phase lives inside a timeline-item div with a marker dot and a content card.

The reveal-item class on each item will be used later for scroll animations. For now it has no effect.

  • Immediately below the code you just pasted, add the remaining three timeline phases and the section closing tags:
                <!-- Phase 3 -->
                <div class="timeline-item reveal-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-phase">Phase 3</span>
                        <h3>Farmer Training Rollout</h3>
                        <p>Coordinated 50+ projects to train 10,000+ farmer groups in climate-resilient sustainable agriculture. Deployed Train of Trainer programmes for Veterinary Officers across all 16 regions of Ghana.</p>
                    </div>
                </div>
                <!-- Phase 4 -->
                <div class="timeline-item reveal-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-phase">Phase 4</span>
                        <h3>Education and Scale</h3>
                        <p>Expanded impact to 60,000+ senior high school students and 4,000 university students at KNUST, Tamale Agric College, Navrongo Agric College, and UENR.</p>
                    </div>
                </div>
                <!-- Phase 5 -->
                <div class="timeline-item reveal-item">
                    <div class="timeline-marker"></div>
                    <div class="timeline-content">
                        <span class="timeline-phase">Phase 5</span>
                        <h3>Stakeholder Engagement and Partnerships</h3>
                        <p>Strengthened partnerships with MOFA, Youth Employment Agency, Women in Agric Nkoranza, and community leaders. Facilitated 40+ milestones and drove sustainable impact across regions.</p>
                    </div>
                </div>
            </div>
        </div>
    </section>

What does this code do?

Phases 3 through 5 cover your farmer training rollout, education scale-up, and stakeholder partnerships. The closing tags at the bottom seal the timeline-track, container, and timeline section.

  • Save index.html by pressing Ctrl+S.
  • Switch to Chrome and refresh the page.
  • Scroll past the counter cards. You should see raw, unstyled text for each of the five phases (Programme Initiation through Stakeholder Engagement and Partnerships).

Don't see any timeline text?

  • Make sure you pasted both code blocks one after the other, with the second block directly below the first. There should be no gap or missing closing tags between them.
  • Check that the new code sits between the impact section's closing </section> tag and the <script src="script.js"></script> line.
  • Confirm you saved the file (Ctrl+S) before refreshing Chrome.

Still stuck? Why isn't my timeline HTML showing up in the browser?

Now add the four workstream cards that describe your core delivery areas.

  • In index.html, place your cursor right after the timeline section's closing </section> tag (the one you just added).
  • Press Enter and paste the workstreams section:
    <!-- Workstreams section - four delivery area cards -->
    <section class="workstreams" id="workstreams">
        <div class="container">
            <div class="section-header">
                <span class="section-label">Delivery</span>
                <h2>Core Workstreams</h2>
                <p>Strategic delivery areas driving programme success</p>
            </div>
            <div class="workstream-grid">
                <div class="workstream-card reveal-item">
                    <div class="workstream-number">01</div>
                    <h3>Project Coordination</h3>
                    <p>End-to-end coordination of 50+ projects across 16 regions. Managing workstreams, tracking deliverables, and resolving issues to maintain programme momentum.</p>
                </div>
                <div class="workstream-card reveal-item">
                    <div class="workstream-number">02</div>
                    <h3>Training and Capacity Building</h3>
                    <p>Designed and facilitated training programmes for 74,000+ individuals including farmers, students, and veterinary officers in climate-resilient agricultural practices.</p>
                </div>
                <div class="workstream-card reveal-item">
                    <div class="workstream-number">03</div>
                    <h3>Stakeholder Engagement</h3>
                    <p>Built and maintained strategic relationships with GIZ, MOFA, community leaders, Youth Employment Agency, and Women in Agric Nkoranza to drive programme alignment.</p>
                </div>
                <div class="workstream-card reveal-item">
                    <div class="workstream-number">04</div>
                    <h3>Grant Writing and Funding</h3>
                    <p>Led partnership development and grant writing efforts, driving EUR 500,000+ in funding commitments to strengthen sustainable agri-value chains.</p>
                </div>
            </div>
        </div>
    </section>

What does this code do?

This creates four workstream cards inside a responsive grid. Each card has a large number overlay (workstream-number), a heading, and a description of that delivery area.

The four workstreams cover Project Coordination, Training and Capacity Building, Stakeholder Engagement, and Grant Writing and Funding.

  • Save index.html and refresh Chrome.
  • Scroll below the timeline text. You should see raw text for all four workstreams ("01", "Project Coordination", "02", "Training and Capacity Building", and so on).

Only see the timeline text but not the workstreams?

  • Make sure you pasted the workstreams code after the timeline section's closing </section> tag, not inside it.
  • Check that the workstreams code appears before the <script src="script.js"></script> line near the bottom of the file.

Need help? Why aren't my workstream cards showing up below the timeline?

✔️ Awesome, I've got everything!

Great. Make sure you have saved the file before moving on to the styling.

ⓧ I'd like to double check the full code

Your index.html is now quite long. Scroll through the file and confirm these sections appear in this order:

  • The <nav> bar at the top.
  • The hero section (id="hero").
  • The impact section (id="impact") with seven counter cards.
  • The timeline section (id="timeline") with five timeline-item divs.
  • The workstreams section (id="workstreams") with four workstream-card divs.
  • The <script src="script.js"></script> line just before </body>.
Style the timeline and workstream cards

The timeline and workstream text is on the page, but it looks like a plain list. Now you will add the CSS that turns it into a vertical track with markers and styled cards.

  • Switch to style.css in Notepad.
  • Scroll to the very bottom of the file (below the @media block's closing }).
  • Add the timeline styles by pasting this code at the end of the file:
/* Timeline Section - vertical track with connected phases */
.timeline { padding: 80px 0; background: var(--bg-section-alt); }
.timeline-track { position: relative; max-width: 700px; margin: 0 auto; padding-left: 40px; }
/* Gradient line connecting all timeline phases */
.timeline-line { position: absolute; left: 15px; top: 0; bottom: 0; width: 3px; background: linear-gradient(to bottom, var(--primary), var(--accent)); border-radius: 2px; }
.timeline-item { position: relative; padding: 0 0 40px 30px; }
.timeline-item:last-child { padding-bottom: 0; }
/* Circular marker dot on each phase */
.timeline-marker { position: absolute; left: -32px; top: 4px; width: 16px; height: 16px; background: var(--primary); border-radius: 50%; border: 3px solid var(--bg-section-alt); box-shadow: 0 0 0 3px var(--primary); }
/* Card styling for each phase description */
.timeline-content { background: var(--bg-card); border-radius: var(--radius); padding: 24px; box-shadow: var(--shadow-sm); transition: var(--transition); }
.timeline-content:hover { box-shadow: var(--shadow-md); }
/* Phase label badge (e.g. "Phase 1") */
.timeline-phase { display: inline-block; background: var(--primary-light); color: var(--primary-dark); padding: 3px 10px; border-radius: 4px; font-size: 0.75rem; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 8px; }
.timeline-content h3 { font-size: 1.15rem; font-weight: 700; color: var(--text-primary); margin-bottom: 8px; }
.timeline-content p { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.6; }

What does this CSS do?

  • timeline-line draws a thin vertical gradient line from teal to gold that connects all five phases.
  • timeline-marker creates a small teal circle on each phase, positioned on top of the line.
  • timeline-content turns each phase description into a white card with rounded corners and a hover shadow.
  • Save style.css and refresh Chrome.
  • Scroll past the counter cards. You should see a vertical timeline with a teal-to-gold gradient line, five circular markers, and white cards for each phase.

Timeline still looks like plain text?

  • Make sure you pasted the CSS at the very end of style.css, after the closing } of the @media block. Pasting inside the media block would scope these styles to small screens only.
  • Check for missing or extra curly braces. Each CSS rule must have an opening { and a closing }.

Need help? Why isn't my timeline CSS applying in the browser?

Now add the styles that turn the raw workstream text into polished cards with number overlays.

  • In style.css, paste the workstream styles directly below the timeline CSS you just added:
/* Workstreams Section - four delivery area cards */
.workstreams { padding: 80px 0; background: var(--bg-main); }
.workstream-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 24px; }
.workstream-card { background: var(--bg-card); border-radius: var(--radius); padding: 32px 24px; box-shadow: var(--shadow-sm); transition: var(--transition); position: relative; overflow: hidden; border: 1px solid rgba(0,0,0,0.04); }
.workstream-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); border-color: var(--primary-light); }
/* Large semi-transparent number overlay in each card corner */
.workstream-number { font-size: 4rem; font-weight: 800; color: rgba(0,121,107,0.08); position: absolute; top: 10px; right: 16px; line-height: 1; }
.workstream-card h3 { font-size: 1.1rem; font-weight: 700; color: var(--primary-dark); margin-bottom: 12px; }
.workstream-card p { font-size: 0.9rem; color: var(--text-secondary); line-height: 1.6; }

What does this CSS do?

  • workstream-grid arranges the four cards in a responsive grid that wraps on smaller screens.
  • workstream-number positions the large "01", "02", "03", "04" text in the top-right corner of each card with very low opacity, creating a subtle decorative element.
  • The :hover rule lifts each card slightly and adds a deeper shadow when you move your mouse over it.

One more small addition. The existing @media block at the top of style.css handles small screens. You need to add one line inside it so the timeline track adjusts on mobile.

  • Scroll up in style.css and find the @media (max-width: 768px) block.
  • Find this line inside it:
    .counter-number { font-size: 2rem; }
  • Place your cursor at the end of that line and press Enter. Add this line below it, still inside the @media block:
    .timeline-track { padding-left: 30px; }

Before you refresh, think about what you expect to see. What should the workstream section look like now that you have added the card grid and number overlay styles?

  • Save style.css and refresh Chrome.
  • Scroll past the counter cards and the timeline. You should see four workstream cards arranged in a grid. Each card has a large, faint number ("01" through "04") in the top-right corner.
  • Hover your mouse over a workstream card. It should lift slightly and cast a deeper shadow.

Cards look flat or numbers are missing?

  • The numbers use a very low opacity (rgba(0,121,107,0.08)), so they are intentionally faint. Look closely at the top-right corner of each card.
  • If the cards are not in a grid, check that .workstream-grid includes display: grid in your CSS.

Still stuck? Why aren't my workstream cards styled correctly?

✔️ Awesome, I've got everything!

Great. Double check you have saved both files before moving on.

ⓧ I'd like to double check the full code

Your style.css is getting long. Scroll through the file and confirm these CSS sections appear in this order:

  • The :root variables, reset, and body styles at the top.
  • Navbar styles (.navbar, .nav-container, etc.).
  • Hero section styles (.hero, .hero-content, etc.).
  • Section header styles (.section-header).
  • Impact and counter card styles (.impact, .counter-grid, etc.).
  • The @media (max-width: 768px) block, now including .timeline-track { padding-left: 30px; }.
  • Timeline section styles (.timeline, .timeline-track, .timeline-marker, etc.).
  • Workstream section styles (.workstreams, .workstream-card, .workstream-number, etc.).

Your dashboard now shows the full delivery story. The timeline traces your programme journey from initiation through stakeholder engagement, and the workstream cards highlight your four core delivery areas. Next up, you will add partner cards, a photo gallery, and the page footer to complete the dashboard content.

Add Partners, Photo Gallery, and Footer

Your timeline and workstream cards now show how you delivered results across Ghana and Nigeria. Stakeholders and employers want one more thing: proof that real partners stood behind this work.

Your partnerships with GIZ, MOFA, and community leaders validate every number on the dashboard. In this step, you will add partner cards, a photo gallery for your field images, and a professional footer that closes the page with programme attribution.

In this step, get ready to:
  • Add five partner cards, four academic institution chips, a six-image photo gallery, and a footer to your HTML.
  • Style each section with coloured badges, grid layouts, and a dark-teal footer.
  • Swap gallery placeholders for your own project photos.
Add the partners section HTML

Each partner gets a card with a coloured circle showing their abbreviation, their full name, and a short description of their role in the programme.

  • Switch back to index.html in Notepad.
  • Find the closing </section> tag of the workstreams section (right after the last workstream card).
  • Add the partners section below it by pasting this code:
    <section class="partners" id="partners">
        <div class="container">
            <div class="section-header">
                <span class="section-label">Partnerships</span>
                <h2>Key Partners and Collaborators</h2>
                <p>Strategic alliances that powered programme delivery</p>
            </div>
            <div class="partner-grid">
                <div class="partner-card reveal-item"><div class="partner-initial" style="background: #00796B;">GIZ</div><h3>GIZ ABF Funding Team</h3><p>Deutsche Gesellschaft fuer Internationale Zusammenarbeit. Primary funding partner and programme sponsor.</p></div>
                <div class="partner-card reveal-item"><div class="partner-initial" style="background: #1565C0;">MOFA</div><h3>Ministry of Food and Agriculture</h3><p>Government of Ghana. Policy alignment and agricultural extension support.</p></div>
                <div class="partner-card reveal-item"><div class="partner-initial" style="background: #E65100;">YEA</div><h3>Youth Employment Agency</h3><p>Youth mobilisation and employment linkages for agricultural programmes.</p></div>
                <div class="partner-card reveal-item"><div class="partner-initial" style="background: #AD1457;">WIA</div><h3>Women in Agric Nkoranza</h3><p>Women-led agricultural initiative driving gender-inclusive value chain development.</p></div>
                <div class="partner-card reveal-item"><div class="partner-initial" style="background: #6A1B9A;">ADC</div><h3>Chiefs and People of Abisim Dawa</h3><p>Community leadership in the Eastern Region of Ghana. Local governance and community mobilisation.</p></div>
            </div>
            <div class="institution-section reveal-item">
                <h3>Academic Institutions</h3>
                <div class="institution-grid">
                    <div class="institution-chip">Kwame Nkrumah University of Science and Technology (KNUST)</div>
                    <div class="institution-chip">Tamale Agric College</div>
                    <div class="institution-chip">Navrongo Agric College</div>
                    <div class="institution-chip">University of Energy and Natural Resources (UENR)</div>
                </div>
            </div>
        </div>
    </section>

What does this code do?

The partner-grid holds five cards, one for each key partner. Each card has a partner-initial div with a coloured background set by an inline style attribute so each badge has a unique colour.

Below the partner cards, the institution-section lists the four academic institutions as rounded chips. The reveal-item class on each card will power scroll-reveal animations once you add JavaScript in the next step.

  • Save index.html (Ctrl+S).
  • Refresh Chrome. Scroll past the workstream cards. You should see plain text listing each partner name and the four institution names.

Don't see the partner names?

  • Make sure the new <section> block sits after the workstreams </section> closing tag, not inside it.
  • Check that the closing </section> tag is present at the very end of the code you pasted.
  • Still stuck? Help me find where to paste the partners section in my index.html file.
Add the gallery and footer HTML

The photo gallery uses placeholder boxes that you will swap for your own images later. The footer credits the programme and its funding partners.

  • In index.html, find the closing </section> tag of the partners section you just added.
  • Add the gallery section and footer below it by pasting this code (before the <script src="script.js"></script> line):
    <section class="gallery" id="gallery">
        <div class="container">
            <div class="section-header">
                <span class="section-label">In the Field</span>
                <h2>Project Gallery</h2>
                <p>Highlights from training sessions, stakeholder meetings, and field activities</p>
            </div>
            <div class="gallery-grid">
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 1</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Farmer Training Session"&gt;</p></div><div class="gallery-caption">Farmer Training Session</div></div>
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 2</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Stakeholder Engagement"&gt;</p></div><div class="gallery-caption">Stakeholder Engagement</div></div>
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 3</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Field Activities"&gt;</p></div><div class="gallery-caption">Field Activities</div></div>
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 4</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Community Outreach"&gt;</p></div><div class="gallery-caption">Community Outreach</div></div>
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 5</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Training of Trainers"&gt;</p></div><div class="gallery-caption">Training of Trainers</div></div>
                <div class="gallery-item reveal-item"><div class="gallery-placeholder"><span>Photo 6</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="University Programme"&gt;</p></div><div class="gallery-caption">University Programme</div></div>
            </div>
        </div>
    </section>

    <footer class="footer">
        <div class="container">
            <div class="footer-content">
                <div class="footer-brand"><h3>GIZ ABF Matching Fund</h3><p>Impact Dashboard</p></div>
                <div class="footer-info">
                    <p>Project Lead | Farming in Africa</p>
                    <p>Co-funded by BMZ and the European Union under the Samoa Agreement with OACPS</p>
                    <p>Implemented by GIZ (Deutsche Gesellschaft fuer Internationale Zusammenarbeit)</p>
                </div>
            </div>
            <div class="footer-bottom"><p>Built to showcase project impact and delivery excellence</p></div>
        </div>
    </footer>

What does this code do?

The gallery section creates a grid of six placeholder cards. Each placeholder has built-in instructions telling you how to replace it with a real image later.

The <footer> element uses a semantic HTML tag instead of a <section>. This tells browsers and screen readers that this is the page's closing content. It credits BMZ, the European Union, OACPS, and GIZ as the implementing organisation.

  • Save index.html (Ctrl+S).
  • Refresh Chrome and scroll to the very bottom. You should see "Photo 1" through "Photo 6" placeholder text followed by footer text including "GIZ ABF Matching Fund" and the funding attribution.

Footer text missing or overlapping the gallery?

  • Check that the gallery </section> closing tag appears before the <footer> opening tag. Missing it will nest the footer inside the gallery.
  • Make sure the footer code sits before the <script src="script.js"></script> line, not after it.
  • Need more help? Help me fix the gallery and footer placement in my index.html.
Style the partners and institutions

The partner cards need a grid layout, coloured circle badges for each abbreviation, and rounded chips for the academic institutions.

  • Switch back to style.css in Notepad.
  • Add the partners and institutions CSS at the end of the file, before the @media block, by pasting this code:
/* Partners Section */
.partners { padding: 80px 0; background: var(--bg-section-alt); }
.partner-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 24px; margin-bottom: 40px; }
.partner-card { background: var(--bg-card); border-radius: var(--radius); padding: 28px 20px; text-align: center; box-shadow: var(--shadow-sm); transition: var(--transition); border: 1px solid rgba(0,0,0,0.04); }
.partner-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.partner-initial { display: inline-flex; align-items: center; justify-content: center; width: 56px; height: 56px; border-radius: 50%; color: var(--text-light); font-size: 1rem; font-weight: 800; margin-bottom: 16px; letter-spacing: 0.5px; }
.partner-card h3 { font-size: 0.95rem; font-weight: 700; color: var(--text-primary); margin-bottom: 8px; }
.partner-card p { font-size: 0.82rem; color: var(--text-secondary); line-height: 1.5; }
.institution-section { background: var(--bg-card); border-radius: var(--radius); padding: 32px; box-shadow: var(--shadow-sm); }
.institution-section h3 { font-size: 1.1rem; font-weight: 700; color: var(--text-primary); margin-bottom: 16px; text-align: center; }
.institution-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 12px; }
.institution-chip { background: var(--primary-light); color: var(--primary-dark); padding: 8px 18px; border-radius: 50px; font-size: 0.85rem; font-weight: 500; }

What does this code do?

  • partner-grid arranges the five cards in a responsive grid that wraps to fit smaller screens.
  • partner-initial creates the 56px coloured circle that displays each partner's abbreviation (GIZ, MOFA, YEA, WIA, ADC).
  • institution-chip styles each university name as a teal rounded pill, giving the academic partners a distinct look from the main partner cards.
  • Save style.css (Ctrl+S) and refresh Chrome.
  • Scroll to the partners section. You should see five white cards arranged in a grid. Each card has a coloured circle badge (teal for GIZ, blue for MOFA, orange for YEA, pink for WIA, purple for ADC). Below the cards, four institution names appear as rounded teal chips.

Circles not showing their colours?

  • The colour comes from the inline style="background: #00796B;" on each partner-initial div in your HTML. Check that you have the style attribute with a hex colour on each one.
  • Make sure the CSS for .partner-initial includes border-radius: 50% to create the circle shape.
  • Still stuck? Help me troubleshoot why my partner badge circles are not showing their colours.
Style the gallery and footer
  • Still in style.css, add the gallery and footer CSS below the partners styles you just added (still before the @media block) by pasting this code:
/* Gallery Section */
.gallery { padding: 80px 0; background: var(--bg-main); }
.gallery-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; }
.gallery-item { border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow-sm); transition: var(--transition); background: var(--bg-card); }
.gallery-item:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
.gallery-placeholder { height: 220px; background: linear-gradient(135deg, #E0E0E0, #BDBDBD); display: flex; flex-direction: column; align-items: center; justify-content: center; color: #757575; }
.gallery-placeholder span { font-size: 1.4rem; font-weight: 700; margin-bottom: 8px; }
.gallery-placeholder p { font-size: 0.7rem; text-align: center; padding: 0 20px; line-height: 1.4; }
.gallery-item img { width: 100%; height: 220px; object-fit: cover; }
.gallery-caption { padding: 14px 18px; font-size: 0.9rem; font-weight: 600; color: var(--text-primary); background: var(--bg-card); }

/* Footer */
.footer { background: var(--primary-dark); color: var(--text-light); padding: 48px 0 24px; }
.footer-content { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 32px; margin-bottom: 32px; padding-bottom: 24px; border-bottom: 1px solid rgba(255,255,255,0.15); }
.footer-brand h3 { font-size: 1.3rem; font-weight: 700; margin-bottom: 4px; }
.footer-brand p { color: var(--accent); font-weight: 500; }
.footer-info p { font-size: 0.85rem; color: rgba(255,255,255,0.7); margin-bottom: 4px; }
.footer-bottom { text-align: center; }
.footer-bottom p { font-size: 0.8rem; color: rgba(255,255,255,0.5); }

What does this code do?

  • gallery-placeholder creates grey gradient boxes at 220px tall. When you add real photos later, the .gallery-item img rule uses object-fit: cover to crop photos to fill the space without stretching.
  • The footer uses var(--primary-dark) for a deep teal background that matches the hero banner. The footer-content uses flexbox to put the programme name on the left and the funding attribution on the right.

The dashboard also needs two responsive rules so the footer and gallery stack neatly on smaller screens.

  • Scroll down to the @media (max-width: 768px) block at the end of your file.
  • Find this line inside the block:
.timeline-track { padding-left: 30px; }
  • Add these two lines right after it, still inside the @media block's closing }:
    .footer-content { flex-direction: column; }
    .gallery-grid { grid-template-columns: 1fr; }
  • Save style.css (Ctrl+S) and refresh Chrome.
  • Scroll to the bottom of the page. You should see six grey placeholder boxes arranged in a grid, each labelled "Photo 1" through "Photo 6" with a caption underneath. Below the gallery, a dark teal footer displays "GIZ ABF Matching Fund" on the left and the funding attribution on the right.

Footer background not dark teal?

  • Make sure you used <footer class="footer"> in your HTML. The CSS targets the .footer class, not the <footer> element directly.
  • Check that the .footer CSS rule is outside the @media block, not accidentally pasted inside it.
  • Still stuck? Help me figure out why my dashboard footer is not styled correctly.

✔️ Awesome, I've got everything!

Great. Double check you have saved both index.html and style.css.

ⓧ I'd like to double check the full code

Your index.html and style.css files are long at this point. Scroll through each file and confirm these sections are present in order:

In index.html:

  • Navbar with nav links for Impact, Timeline, Delivery, Partners, Gallery.
  • Hero section with badge, title, role, description, and tags.
  • Impact section with seven counter cards.
  • Timeline section with five phases.
  • Workstreams section with four cards (01 through 04).
  • Partners section with five partner cards (GIZ, MOFA, YEA, WIA, ADC) and four institution chips.
  • Gallery section with six gallery items (Photo 1 through Photo 6).
  • Footer with programme name and funding attribution.
  • The <script src="script.js"></script> line right before </body>.

In style.css:

  • CSS variables, base styles, navbar, hero, section headers, impact/counter styles.
  • Timeline and workstreams styles.
  • Partners section styles (partner-grid, partner-card, partner-initial, institution-section, institution-chip).
  • Gallery section styles (gallery-grid, gallery-item, gallery-placeholder, gallery-caption).
  • Footer styles (footer, footer-content, footer-brand, footer-info, footer-bottom).
  • The @media block at the very end containing responsive rules including .footer-content and .gallery-grid.
Add your own photos

The grey placeholder boxes are ready to be replaced with your actual field photos. Each placeholder tells you exactly what to do.

  • Copy any project photos (JPG or PNG files) into your giz-dashboard folder on the Desktop. The photos must be in the same folder as index.html.
  • In index.html, scroll to the gallery section and find the first placeholder. It looks like this:
<div class="gallery-placeholder"><span>Photo 1</span><p>Save your image in the giz-dashboard folder then replace this div with: &lt;img src="your-photo-name.jpg" alt="Farmer Training Session"&gt;</p></div>
  • Replace that entire <div class="gallery-placeholder">...</div> with an <img> tag pointing to your photo filename, so the line now looks like this:
<img src="[[PHOTO_FILENAME="your-photo-name.jpg"]]" alt="Farmer Training Session">
  • Repeat for as many gallery items as you have photos.
  • Save index.html and refresh Chrome.
  • Your photos now appear inside the gallery cards, cropped to fill the 220px height. Any slots you did not replace still show the grey placeholder boxes.

No photos yet?

That is fine. The grey placeholders look tidy and you can swap them for real photos any time. The dashboard is fully functional either way.

Photo not showing up?

  • The filename in your src attribute must match the actual file exactly, including capitalisation and extension. If your file is called Training.JPG, the src must also say Training.JPG, not training.jpg.
  • Make sure the photo file is directly inside the giz-dashboard folder, not inside a subfolder.
  • Need help? Help me figure out why my photo is not displaying in my HTML gallery.

Before you move on, take a moment to predict what you expect to see when you scroll through the entire dashboard from top to bottom.

  • Refresh Chrome and scroll from the hero banner all the way to the footer.

You should see five partner cards with coloured initial badges (GIZ, MOFA, YEA, WIA, ADC). Below them, four academic institution chips appear in a row. The photo gallery shows either your uploaded images or grey placeholder boxes. The footer displays the programme name on the left and funding attribution on the right against a dark teal background.

The page now has all its content, but notice that the counter cards still show "0" and nothing animates as you scroll. The page feels static. That changes in the next step.

Your dashboard now tells the full story from hero to footer. Next up, you will bring it all to life with animated counters that tick up from zero and scroll-reveal effects that make each section appear as you scroll down.

Bring the Dashboard to Life with Animations

Your dashboard has every section in place, from the hero banner down to the footer. But scroll through it right now and you will notice something missing. The counter cards still display "0," nothing responds to your scrolling, and the page feels completely flat.

JavaScript is what transforms a static page into an interactive experience. In this step, you will add code that makes your impact numbers count up from zero, reveals sections as visitors scroll, and enables smooth navigation.

In this step, get ready to:
  • Add JavaScript that animates counter cards, reveals sections on scroll, and enables smooth navigation.
  • Add CSS animation classes that work with the JavaScript to create fade-and-slide effects.
Add the animation JavaScript

The script.js file currently has a single comment line. You will replace it with four blocks of code, pasting them one after another. Each block adds a specific animation feature.

  • Open script.js in Notepad.
  • Select all the text by pressing Ctrl+A, then delete it.
  • Paste the following code, which defines the counter animation function:
document.addEventListener('DOMContentLoaded', function () {

    // Counter Animation
    var countersAnimated = false;

    function animateCounters() {
        if (countersAnimated) return;
        countersAnimated = true;
        var counters = document.querySelectorAll('.counter-number');
        counters.forEach(function (counter) {
            var target = parseInt(counter.getAttribute('data-target'));
            var duration = 2000;
            var startTime = null;

            function updateCounter(timestamp) {
                if (!startTime) startTime = timestamp;
                var progress = Math.min((timestamp - startTime) / duration, 1);
                // Cubic ease-out makes the counting slow down smoothly
                var eased = 1 - Math.pow(1 - progress, 3);
                var current = Math.floor(eased * target);
                counter.textContent = current.toLocaleString();
                if (progress < 1) {
                    requestAnimationFrame(updateCounter);
                } else {
                    counter.textContent = target.toLocaleString();
                }
            }

            requestAnimationFrame(updateCounter);
        });
    }

What does this code do?

document.addEventListener('DOMContentLoaded', ...) wraps all your code so it only runs after the page finishes loading. Everything else you paste goes inside this wrapper.

The animateCounters function finds every element with the class counter-number and counts it up from 0 to the number stored in its data-target attribute over 2 seconds. The cubic ease-out formula makes the numbers start fast and slow down near the end, which feels satisfying to watch.

  • Directly below the code you just pasted (after the closing } of the animateCounters function), paste this scroll-reveal observer code:

    // Intersection Observer for scroll-reveal and counter trigger
    var observerOptions = {
        threshold: 0.15,
        rootMargin: '0px 0px -40px 0px'
    };

    var revealObserver = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
            if (entry.isIntersecting) {
                entry.target.classList.add('revealed');
                revealObserver.unobserve(entry.target);
            }
        });
    }, observerOptions);

    document.querySelectorAll('.reveal-item').forEach(function (el) {
        revealObserver.observe(el);
    });

What is an Intersection Observer?

An Intersection Observer watches elements on the page and fires a callback when they scroll into view. Here, revealObserver watches every element with the class reveal-item. When one enters the viewport, it adds the class revealed. You will add CSS rules for that class in the next substep.

  • Directly below the reveal observer code, paste this counter observer code:

    // Counter cards: staggered reveal and trigger animation
    var counterSection = document.getElementById('impact');
    var counterCards = document.querySelectorAll('.counter-card');

    var counterObserver = new IntersectionObserver(function (entries) {
        entries.forEach(function (entry) {
            if (entry.isIntersecting) {
                counterCards.forEach(function (card, index) {
                    setTimeout(function () {
                        card.classList.add('revealed');
                    }, index * 100);
                });
                animateCounters();
                counterObserver.unobserve(entry.target);
            }
        });
    }, { threshold: 0.2 });

    if (counterSection) {
        counterObserver.observe(counterSection);
    }

What does this code do?

This is a second Intersection Observer that specifically watches the impact section. When you scroll it into view, two things happen.

First, each counter card gets the revealed class with a 100ms stagger between cards, creating a cascading appearance effect. Second, it calls animateCounters() to start the numbers ticking up from zero.

Those three blocks handle the counter animation and scroll-reveal. Time to test before adding the last piece.

  • Save script.js by pressing Ctrl+S.

Before you refresh Chrome, think about what you expect to see. Those counter cards have been showing zeros since Step 2. What do you think happens now that the JavaScript is in place?

  • Switch to Chrome and refresh the page by pressing Ctrl+R.
  • Scroll down to the "Impact at a Glance" section.

The counter numbers animate from 0 up to their real values: 10,000 farmers, 60,000 SHS students, 4,000 university students, and so on. The zeros are gone.

Counters still showing zero?

  • Make sure you saved script.js and refreshed the page.
  • Check that your three code blocks are pasted in order, one after the other, inside the DOMContentLoaded wrapper. If any block is outside the opening function () {, the code will not run.
  • Try scrolling the impact section into view slowly. The animation triggers when the section enters the viewport.

Why aren't my counter animations working?

Now add the final JavaScript block for navbar effects and smooth scrolling.

  • Switch back to script.js in Notepad.
  • Place your cursor at the very end of the file, after the last line you pasted.
  • Paste this final block of code, which adds the navbar scroll effect, smooth scrolling, and closes the DOMContentLoaded wrapper:

    // Navbar scroll effect
    var navbar = document.getElementById('navbar');
    window.addEventListener('scroll', function () {
        if (window.scrollY > 80) {
            navbar.classList.add('scrolled');
        } else {
            navbar.classList.remove('scrolled');
        }
    });

    // Smooth scroll for nav links
    document.querySelectorAll('a[href^="#"]').forEach(function (anchor) {
        anchor.addEventListener('click', function (e) {
            e.preventDefault();
            var targetEl = document.querySelector(this.getAttribute('href'));
            if (targetEl) {
                targetEl.scrollIntoView({ behavior: 'smooth', block: 'start' });
            }
        });
    });

});

What does this code do?

The navbar scroll effect listens for scroll events. When you scroll more than 80 pixels past the top, it adds the scrolled class to the navbar, which triggers the darker background style already in your CSS.

The smooth scroll section intercepts clicks on navigation links (like "Impact" and "Timeline" in the navbar) and scrolls the page smoothly to that section instead of jumping instantly. The closing }); at the bottom closes the DOMContentLoaded wrapper you opened in the first block.

  • Save script.js by pressing Ctrl+S.
  • Switch to Chrome and refresh the page by pressing Ctrl+R.
  • Scroll down past the hero banner slowly. The navbar background should change from transparent to a solid dark teal.
  • Click any navigation link in the navbar (for example, Gallery). The page should smoothly scroll to that section instead of jumping.

Navbar not changing or links not scrolling?

  • Check that the very last line of script.js is }); which closes the DOMContentLoaded wrapper. A missing closing bracket will break all the code.
  • Make sure the four code blocks are pasted in order with no extra text between them.

Help me fix my navbar scroll effect and smooth scrolling JavaScript.

✔️ Awesome, I've got everything!

Great. Make sure you have saved script.js before moving on.

ⓧ I'd like to double check the full code

Your script.js should contain four blocks of code, all inside the DOMContentLoaded wrapper, in this order:

  • Counter animation (the animateCounters function with countersAnimated, requestAnimationFrame, and cubic easing).
  • Scroll-reveal observer (revealObserver that adds the revealed class to reveal-item elements).
  • Counter observer (counterObserver that staggers counter card reveals at 100ms intervals and calls animateCounters()).
  • Navbar + smooth scroll (adds scrolled class to the navbar and enables smooth scrolling for nav links, followed by the closing });).

Scroll up through the four code blocks above and confirm each one is present in your file, in order.

Add the animation CSS classes

The JavaScript is adding and removing CSS classes, but those classes do not exist in your stylesheet yet. Without them, the scroll-reveal has no visible effect on your timeline items, workstream cards, partner cards, and gallery items.

  • Open style.css in Notepad.
  • Scroll to the very bottom of the file.
  • Paste the following CSS at the end, below the responsive media query:
/* Reveal Animation Classes */
.reveal-item {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal-item.revealed {
    opacity: 1;
    transform: translateY(0);
}

.counter-card {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.counter-card.revealed {
    opacity: 1;
    transform: translateY(0);
}

What does this CSS do?

The .reveal-item class starts elements invisible (opacity: 0) and shifted 30 pixels down (translateY(30px)). When your JavaScript adds the revealed class on scroll, the element fades in and slides up over 0.6 seconds.

The .counter-card rules do the same thing for the impact counter cards. This overrides the earlier .counter-card transition so the cards fade and slide in with the staggered timing your JavaScript set up.

  • Save style.css by pressing Ctrl+S.

Before you refresh, think about what you expect to see now. The JavaScript is adding the revealed class on scroll, and the CSS now defines what that class looks like. What will happen to the timeline items and workstream cards as you scroll?

  • Switch to Chrome and refresh the page by pressing Ctrl+R.
  • Scroll down slowly from the top of the page.

The counter numbers animate from 0 up to their real values (10,000, 60,000, 4,000, 50, 40, 500, 16). The counter cards cascade in one by one with a staggered delay. Continue scrolling. Timeline items, workstream cards, partner cards, and gallery items fade and slide into view as they enter the screen. The navbar background changes from transparent to dark teal as you scroll past the hero. Click any navigation link and the page smoothly scrolls to that section.

Sections not fading in on scroll?

  • Make sure you saved style.css and refreshed the page. The new CSS classes must be loaded for the animations to work.
  • Check that the CSS was pasted after the responsive media query block, not inside it. If the reveal styles are inside the @media block, they will only apply on small screens.
  • If sections appear blank (completely invisible), the CSS is working but the JavaScript may not be adding the revealed class. Double check that script.js is saved with all four blocks of code.

My dashboard sections are invisible or not animating on scroll. Help me debug.

✔️ Awesome, I've got everything!

Great. Double check you have saved both script.js and style.css.

ⓧ I'd like to double check the CSS

The new CSS you added at the very bottom of style.css should look exactly like this:

/* Reveal Animation Classes */
.reveal-item {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.reveal-item.revealed {
    opacity: 1;
    transform: translateY(0);
}

.counter-card {
    opacity: 0;
    transform: translateY(30px);
    transition: opacity 0.6s ease, transform 0.6s ease;
}

.counter-card.revealed {
    opacity: 1;
    transform: translateY(0);
}

Make sure this block appears after the @media responsive query, not inside it.

Your dashboard is now fully alive. Counters tick up, sections reveal themselves as visitors scroll, and the navbar responds to your position on the page. This is a portfolio-ready project. Next up, take on the Secret Mission and add a dark mode toggle that transforms the entire colour scheme with one click.

Secret mission

Add a Dark Mode Toggle

Your dashboard looks polished with animations and scroll effects. What if visitors could switch to a sleek dark theme with one click? Add a dark mode toggle that transforms every section of the page and remembers the preference across refreshes.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally in your browser. There are no cloud accounts, subscriptions, or ongoing costs to worry about.

Decide whether to keep your project files on your Desktop, or delete them if you no longer need them.

Resources you used:

  • The giz-dashboard folder on your Desktop containing index.html, style.css, script.js, and any photos you added.

Keep everything running

No action needed. Choose this if you want to keep your dashboard as a portfolio piece or continue customising it.

  • Your giz-dashboard folder stays on your Desktop. There are no ongoing costs.
  • To view the dashboard again at any time, double-click index.html inside the folder.
  • To share the dashboard online in the future, you could upload the folder to a free hosting service like GitHub Pages or Netlify.
  • Consider copying the giz-dashboard folder to a USB drive or cloud storage as a backup.

Pause - I'll come back to this later

Your project is made up of static files on your Desktop. There are no running processes or services to stop.

  • The giz-dashboard folder takes up minimal disk space and can stay on your Desktop indefinitely.
  • If you want to free up Desktop space, move the giz-dashboard folder to a different location like your Documents folder or a USB drive.
  • When you are ready to resume, open the folder and double-click index.html to pick up where you left off.

Delete - I don't want to use this again

Remove your project files entirely. This cannot be undone.

  • Open File Explorer and navigate to your Desktop.
  • Right-click the giz-dashboard folder and select Delete.
  • Empty your Recycle Bin if you want to permanently remove the files.

Alternatively, delete the folder from the command line by pressing the Windows key, typing cmd, pressing Enter, and running this command:

rmdir /s /q "%USERPROFILE%\Desktop\giz-dashboard"

After deleting, confirm the folder is gone by checking your Desktop.

Nice Work!

Nice Work!

Nice work! You just built a professional, interactive impact dashboard for the GIZ ABF Matching Fund project from scratch using HTML, CSS, and JavaScript. The dashboard runs right from your Desktop in Chrome with no hosting, no accounts, and no ongoing costs.

You've learned how to:

  • Built a portfolio-ready impact dashboard with a teal-gradient hero banner, seven animated impact counters that tick up from zero, a five-phase delivery timeline, and four workstream cards.
  • Connected three files (index.html, style.css, script.js) to create a structured web page with scroll-reveal animations, smooth navigation, and a partner showcase featuring GIZ ABF, MOFA, and community leaders.
  • Used Intersection Observer in JavaScript to trigger counter animations and fade-in effects as visitors scroll, turning a static page into an engaging, interactive experience with a photo gallery ready for your own field images.
  • Secret Mission: Added a dark mode toggle that transforms the entire dashboard with one click and persists the preference using localStorage.

Ready to quiz yourself?