Build a Calorie Tracker in JavaScript

Build a browser calorie tracker powered by real USDA nutrition data.

Introduction

30 Second Summary

You probably know how many calories are in a banana. But what about a bowl of pad thai or a handful of trail mix?

In this project, you will build a browser-based calorie tracker that searches a real government nutrition database for any food and logs it to your daily intake. A color-coded progress bar shows how close you are to your daily goal, and everything persists across page refreshes.

What You'll Build

Type a food name, pick from real search results, and watch your daily progress bar fill from green to orange to red as you close in on your calorie goal.

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

  • Search the USDA FoodData Central database for any food and see its real calorie count per 100g.
  • Log foods to a daily meal list with a running calorie total that updates live.
  • Set a daily calorie goal and watch a color-coded progress bar track your intake, with all data persisting in the browser through localStorage.
  • Secret Mission: Add a delete button to each log entry so you can remove mistakes with one click.

Are there any prerequisites?

No prior experience is needed. This guide walks you through setting up every tool from scratch, including your code editor and project files.

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 Files

Every web app starts with three foundation files. index.html provides the page structure, style.css handles the visual design, and app.js powers the behavior.

In this step, you'll create the project folder and all three files, then build the HTML structure and CSS styling so the tracker layout is visible in your browser.

In this step, get ready to:
  • Create the calorie-tracker folder and three starter files.
  • Write the HTML structure and CSS styling for the tracker.
  • Open the page in your browser and confirm it renders correctly.
Create project folder and files

Your calorie tracker needs a dedicated folder and three files before you can see anything in the browser.

  • Return to Cursor and open the integrated terminal by pressing Cmd+` (macOS) or Ctrl+` (Windows).
  • Create the project folder on your Desktop by running these commands:

macOS

cd ~/Desktop
mkdir calorie-tracker
cd calorie-tracker
touch index.html style.css app.js

Windows

cd ~/Desktop
mkdir calorie-tracker
cd calorie-tracker
New-Item index.html, style.css, app.js -ItemType File
  • Confirm the files exist by running:
ls

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

Not seeing all three files?

Make sure you ran all four commands in order. If a file is missing, re-run the touch or New-Item command for the missing file name.

Help me figure out why my files aren't showing up.

  • In Cursor, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop and select the calorie-tracker folder, then click Open.

Cursor reloads with the folder open. You should see all three files listed in the Explorer sidebar on the left.

Write the HTML shell and CSS foundation

The HTML file defines what appears on the page. You'll start with the outer document shell and header, then add the form and log sections.

  • Build the HTML skeleton by clicking index.html in the Explorer sidebar, then pasting this code:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Calorie Tracker</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="container">
    <header>
      <h1>Calorie Tracker</h1>
      <p class="date" id="currentDate"></p>
    </header>
  </div>
  <script src="app.js"></script>
</body>
</html>

What does this code do?

  • The <head> links style.css for styling and sets the page title to "Calorie Tracker".
  • The <header> holds the app title and a placeholder for today's date.
  • The <script> tag at the bottom loads app.js after the page content has loaded.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open the file in your browser by right-clicking index.html in the Explorer sidebar and selecting Reveal in Finder (macOS) or Reveal in File Explorer (Windows), then double-clicking index.html.

You should see the text "Calorie Tracker" displayed in your browser. It looks plain for now because there is no CSS yet.

  • Back in Cursor, add the form and log sections. In index.html, find the closing </header> tag and paste the following code below it (above </div>):
    <section class="add-section">
      <h2>Add Food Manually</h2>
      <form id="addForm">
        <input type="text" id="foodName" placeholder="Food name" required>
        <input type="number" id="foodCalories" placeholder="Calories" min="0" required>
        <button type="submit">Add</button>
      </form>
    </section>

    <section class="log-section">
      <h2>Today's Log</h2>
      <ul class="food-log" id="foodLog"></ul>
      <p class="empty-state" id="emptyState">No foods logged yet. Add something above!</p>
    </section>

    <footer>
      <p>Nutrition data from <a href="https://fdc.nal.usda.gov/" target="_blank">USDA FoodData Central</a></p>
    </footer>

What does this code do?

  • The add-section contains a form with two inputs (food name and calories) and an Add button.
  • The log-section holds an empty <ul> where food entries will appear, plus a message shown when the log is empty.
  • The footer credits the USDA FoodData Central database that will power the food search later.
  • Save index.html and refresh your browser.

You should now see "Add Food Manually" with two input fields, an Add button, "Today's Log", the empty state message, and the USDA footer link.

✔️ Awesome, I've got everything!

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

ⓧ I'd like to double check the full code

Your index.html has two code blocks you built above. Scroll up and confirm both are present in this order:

  • The HTML skeleton (<!DOCTYPE html> through </html>) with the header, container wrapper, and script tag.
  • The form and log sections (<section class="add-section"> through </footer>) pasted between </header> and </div>.

Now let's style the page. The CSS will transform the plain text into a clean, card-based layout.

  • Start the global styles by clicking style.css in the Explorer sidebar, then pasting this code:
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background-color: #f0f2f5;
  color: #1a1a2e;
  line-height: 1.6;
}

.container {
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
}

header {
  text-align: center;
  margin-bottom: 24px;
}

header h1 {
  font-size: 1.8rem;
  color: #16213e;
}

What do these styles do?

  • The * selector resets all margins and padding so every browser starts from the same baseline.
  • The .container centers the layout at 500px wide. This keeps the tracker compact and mobile-friendly.
  • Save style.css and refresh your browser.

The heading is now centered on a light grey page. The form fields and text are still unstyled, but the layout is already tighter.

  • Add the date and section card styles below your existing CSS:
.date {
  color: #666;
  font-size: 0.9rem;
}

.add-section,
.log-section {
  background: white;
  border-radius: 12px;
  padding: 16px;
  margin-bottom: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.add-section h2,
.log-section h2 {
  font-size: 1rem;
  margin-bottom: 10px;
}

What do the card styles do?

The .add-section and .log-section rules give each section a white card look with rounded corners and a subtle shadow. This separates them visually from the grey background.

  • Save style.css and refresh your browser.

The form and log sections now appear as distinct white cards with rounded edges floating on the grey page.

  • Add the form layout and input styles below your existing CSS:
#addForm {
  display: flex;
  gap: 8px;
}

#addForm input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-size: 0.95rem;
}

#addForm input[type="number"] {
  max-width: 100px;
}

#addForm button {
  padding: 8px 16px;
  background: #e76f51;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 600;
  font-size: 0.85rem;
}

What do the form styles do?

  • The display: flex on #addForm lines up the inputs and button side by side in a single row.
  • The #addForm button styles create a coral-colored Add button that stands out from the neutral inputs.
  • Save style.css and refresh your browser.

The form inputs now sit side by side with a coral-colored Add button on the right.

  • Add the food log entry styles below your existing CSS:
.food-log {
  list-style: none;
}

.log-entry {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 0;
  border-bottom: 1px solid #f0f0f0;
}

.log-entry:last-child {
  border-bottom: none;
}

.entry-name {
  font-size: 0.95rem;
}

.entry-calories {
  font-weight: 600;
  color: #e76f51;
  font-size: 0.9rem;
}
  • Finish the styling by adding the empty state and footer styles below your existing CSS:
.empty-state {
  text-align: center;
  color: #999;
  font-size: 0.9rem;
  padding: 20px 0;
}

footer {
  text-align: center;
  font-size: 0.75rem;
  color: #999;
  padding: 16px 0;
}

footer a {
  color: #0a9396;
}

What do the log and footer styles do?

  • The .log-entry rules lay out each food entry as a row with the name on the left and calories on the right, separated by light divider lines.
  • The .empty-state centers the placeholder message in muted grey so it's visible but unobtrusive.
  • The footer a colors the USDA link in teal to make it stand out as clickable.
  • Save style.css and refresh your browser.

The empty state message is centered in muted grey, and the USDA footer link is teal. Your full layout is styled.

✔️ Awesome, I've got everything!

Great. Make sure style.css is saved before moving on.

ⓧ I'd like to double check the full code

Your style.css has five code blocks you built above. Scroll up and confirm all five are present in this order:

  • Global reset, body, container, and header styles (* through header h1).
  • Date, section cards, and heading styles (.date through .log-section h2).
  • Form layout, input, and button styles (#addForm through #addForm button).
  • Food log entry styles (.food-log through .entry-calories).
  • Empty state and footer styles (.empty-state through footer a).

Page still looks unstyled?

Check that the <link> tag in index.html says href="style.css" exactly. A typo like styles.css will silently fail.

Also confirm both files are in the same calorie-tracker folder. If style.css is outside the folder, the browser cannot find it.

Help me figure out why my styles aren't loading.

Verify in the browser

Before you move on, take a moment to predict what you expect to see. Does your page have all the sections the tracker needs?

  • Refresh your browser one more time and confirm you see all of the following:
  • A centered "Calorie Tracker" heading at the top.
  • A white card with the title "Add Food Manually" containing a food name input, a calories input, and a coral Add button.
  • A white card with "Today's Log" and the message "No foods logged yet. Add something above!"
  • A small footer with a teal link to USDA FoodData Central.

Your foundation is set. Next up, you'll bring this page to life by wiring up the form so you can add food entries and watch a running calorie total build up.

Build the Food Log

Your tracker has a face: a clean form and an empty log section waiting for data. But clicking Add right now does nothing because app.js is empty.

In this step, you'll write the JavaScript that brings the form to life. Each food you add will appear in the log with its calorie count, and the app will track a running total behind the scenes.

In this step, get ready to:
  • Handle the form submit event and store food entries.
  • Render logged entries and compute a running calorie total.
  • Discover what happens when data only lives in memory.
Handle the form submission

Before the form can do anything, the app needs a place to store entries and a function to create them. Each entry is an object with a unique ID, the food name, its calorie count, and today's date.

  • Switch back to Cursor and open app.js.
  • Add the following code to create the entries storage and the function that adds new foods:
// Array to hold all food entries in memory
let entries = [];

// Returns today's date as a YYYY-MM-DD string for filtering
function getTodayString() {
  return new Date().toISOString().split('T')[0];
}

// Creates a new entry object and adds it to the entries array
function addEntry(name, calories) {
  const entry = {
    id: Date.now(),
    name: name,
    calories: Number(calories),
    date: getTodayString()
  };
  entries.push(entry);
  renderLog();
}

What does this code do?

entries is a plain JavaScript array that holds every food the learner logs. All data lives here in memory.

getTodayString() returns today's date in YYYY-MM-DD format so the app can filter entries to just today.

addEntry() builds an entry object using Date.now() as a unique ID, then pushes it into the array and calls renderLog() to refresh the display. You'll write renderLog in the next chunk.

  • Save app.js.
  • Refresh your browser, then right-click anywhere on the page and click Inspect.
  • Click the Console tab.
  • Type getTodayString() and press Enter. You should see today's date in YYYY-MM-DD format.

Seeing an error instead of the date?

  • Make sure you saved app.js and refreshed the browser before opening the console.
  • Check that you typed getTodayString() with exact capitalization and the parentheses at the end.
  • My date helper function isn't returning a value in the console.
Render the log and compute the total

The addEntry function stores data, but nothing shows on screen yet. The renderLog function takes the entries array, filters it to today's date, and builds the visible list in the DOM.

  • In app.js, add the renderLog function below addEntry by copying the following code:
// Updates the food log display with today's entries
function renderLog() {
  const logEl = document.getElementById('foodLog');
  const emptyEl = document.getElementById('emptyState');

  const today = getTodayString();
  const todayEntries = entries.filter(entry => entry.date === today);

  // Show empty state or build the entry list
  if (todayEntries.length === 0) {
    logEl.innerHTML = '';
    emptyEl.style.display = 'block';
  } else {
    emptyEl.style.display = 'none';
    logEl.innerHTML = todayEntries.map(entry => `
      <li class="log-entry" data-id="${entry.id}">
        <span class="entry-name">${entry.name}</span>
        <span class="entry-calories">${entry.calories} cal</span>
      </li>
    `).join('');
  }

  // Calculate the running total of today's calories
  const total = todayEntries.reduce((sum, entry) => sum + entry.calories, 0);
  document.getElementById('consumedDisplay') && (document.getElementById('consumedDisplay').textContent = total);
}

What does this code do?

  • filter() narrows the entries array down to only those with today's date. Entries from other days are ignored.
  • If there are no entries for today, the function shows the "No foods logged yet" empty state. Otherwise, it builds an HTML list item for each entry using map().
  • reduce() walks through today's entries and sums up all calorie values into a single total. This total will power the summary display you add in a later step.
  • Save app.js. The renderLog function exists now but nothing calls it yet. The next chunk wires everything together.

The last piece is the event listener that runs when the page loads. It displays today's date, renders the log, and listens for the form's submit event.

  • Add the following code at the bottom of app.js, below the renderLog function:
// Initialize the app when the page finishes loading
document.addEventListener('DOMContentLoaded', () => {
  // Display today's formatted date in the header
  document.getElementById('currentDate').textContent = new Date().toLocaleDateString('en-US', {
    weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
  });

  renderLog();

  // Handle form submission to add a new food entry
  document.getElementById('addForm').addEventListener('submit', (e) => {
    e.preventDefault();
    const name = document.getElementById('foodName').value.trim();
    const calories = document.getElementById('foodCalories').value;
    if (name && calories) {
      addEntry(name, Number(calories));
      e.target.reset();
    }
  });
});

What does this code do?

  • DOMContentLoaded fires once the browser has finished reading all the HTML. This guarantees elements like addForm and foodLog exist before the code tries to access them.
  • e.preventDefault() stops the form from reloading the page when the learner clicks Add. Without it, the browser submits the form and resets everything.
  • e.target.reset() clears the form inputs after a successful add so the learner can immediately type the next food.
  • Save app.js and refresh your browser.
  • You should see today's date displayed under the "Calorie Tracker" heading.
  • Type Banana in the food name field.
  • Type 105 in the calories field.
  • Click Add. The entry should appear in the "Today's Log" section below with "Banana" on the left and "105 cal" on the right.

Entry not showing up?

  • Make sure you saved app.js and refreshed the browser after saving.
  • Open the browser console (Cmd+Option+J on macOS, Ctrl+Shift+J on Windows) and check for red error messages. A typo in an element ID like foodlog instead of foodLog will cause a silent failure.
  • If the page reloads when you click Add, check that e.preventDefault() is the first line inside your submit event handler.
  • My food entry isn't appearing in the log after I click Add.

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js before moving on.

ⓧ I'd like to double check the full code

Scroll up and confirm your app.js contains these sections in order:

  • The entries array and getTodayString() function.
  • The addEntry() function that pushes an entry and calls renderLog().
  • The renderLog() function that filters to today, builds list items, and computes the total with reduce().
  • The DOMContentLoaded listener that sets the date, calls renderLog(), and wires the form submit handler.
Test it out

The log is working. Time to put it through its paces and see if anything breaks.

  • Add a second entry: type Coffee with 5 calories, then click Add.
  • Add a third entry: type Sandwich with 350 calories, then click Add.

You should see all three entries listed under "Today's Log". The form clears after each add, ready for the next one.

Before you refresh the page, what do you think will happen to your three logged entries?

  • Refresh the page now.

Every entry is gone. The log is back to "No foods logged yet" as if nothing happened.

Why did the entries vanish?

The entries array lives in JavaScript's memory. When you refresh the page, the browser throws away all JavaScript state and starts fresh. The variable is recreated as an empty array every time.

A calorie tracker that forgets everything on refresh is not very useful. The next step fixes this by saving entries to the browser's localStorage so they survive refreshes, tab closures, and even restarts.

Your food log works. You can add entries and see them listed in real time. The one problem is that data disappears the moment you refresh. Next up, you'll save entries to the browser so they stick around.

Persist Data with localStorage

Your food log works. You can add entries and see them listed. But you hit a problem at the end of the last step: refreshing the page wipes out every single entry.

That happens because the data only lives in a JavaScript variable, which resets every time the browser reloads. In this step, you'll use localStorage to save entries directly in the browser so they survive refreshes, tab closures, and even restarts.

In this step, get ready to:
  • Save food entries to the browser's localStorage.
  • Load saved entries automatically when the page opens.
  • Verify entries survive a page refresh.
Write the save function

localStorage is a built-in browser API that stores key-value pairs as strings. To store a JavaScript array, you first convert it to a JSON string.

  • In app.js, add these two constants at the very top of the file, above the existing let entries = []; line:
// Keys used to store and retrieve data from localStorage
const ENTRIES_KEY = 'calorie-tracker-entries';
const GOAL_KEY = 'calorie-tracker-goal';
  • Now add the saveEntries function right below let entries = [];:
// Save the current entries array to the browser
function saveEntries() {
  localStorage.setItem(ENTRIES_KEY, JSON.stringify(entries));
}

What does this code do?

  • ENTRIES_KEY and GOAL_KEY are string constants that name the localStorage slots your app uses. Using constants avoids typos when you reference these keys in multiple places.
  • GOAL_KEY isn't used yet. You'll wire it up in a later step when you add a daily calorie goal.
  • JSON.stringify(entries) converts the JavaScript array into a JSON string, because localStorage can only store strings.

Right now, saveEntries exists but nothing calls it yet. You need to call it every time a new entry is added.

  • In app.js, scroll down to the addEntry function.
  • Find this line:
  entries.push(entry);
  renderLog();
  • Add a saveEntries(); call between those two lines, so it looks like this:
  entries.push(entry);
  saveEntries();
  renderLog();
  • Save app.js.
  • Switch to your browser and refresh the calorie tracker page.
  • Type Banana in the food name field and 105 in the calories field.
  • Click Add.
  • Open DevTools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows).
  • Click the Application tab at the top of DevTools.
  • In the left sidebar, expand Local Storage and click your page's origin.

You should see a row with the key calorie-tracker-entries and a JSON string containing your Banana entry as its value.

Don't see the key in Local Storage?

  • Make sure you saved app.js and refreshed the page before adding the entry.
  • Check that saveEntries(); is inside the addEntry function, between entries.push(entry); and renderLog();.
  • If the Application tab isn't visible, click the >> arrows in the DevTools toolbar to find it.

Why isn't my entry showing up in Local Storage?

Write the load function

Saving data is only half the puzzle. When the page loads, you need to read that saved data back from localStorage and restore it into the entries array.

  • In app.js, add the loadEntries function right below saveEntries:
function loadEntries() {
  try {
    // Read the stored JSON string from localStorage
    const raw = localStorage.getItem(ENTRIES_KEY);
    if (!raw) return [];
    // Parse the JSON string back into a JavaScript array
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed : [];
  } catch (e) {
    // If the stored data is corrupt, start fresh
    console.warn('Could not load entries:', e.message);
    return [];
  }
}

Why all the error handling?

localStorage stores raw strings. If the stored value is missing, corrupted, or not valid JSON, calling JSON.parse would throw an error and crash your app.

The try/catch block catches that error gracefully and returns an empty array instead. The Array.isArray check ensures someone hasn't manually edited localStorage to contain something other than an array.

Now you need to call loadEntries when the page first loads, so saved data appears immediately.

  • Scroll down to the DOMContentLoaded event listener.
  • Find this line:
  renderLog();
  • Add entries = loadEntries(); directly above it, so the two lines look like this:
  entries = loadEntries();
  renderLog();

One last cleanup. The renderLog function currently has two lines at the bottom that try to update a summary element that doesn't exist in your HTML yet. You'll build a proper summary display in Step 5.

  • In the renderLog function, find these two lines at the very end (right before the closing }):
  const total = todayEntries.reduce((sum, entry) => sum + entry.calories, 0);
  document.getElementById('consumedDisplay') && (document.getElementById('consumedDisplay').textContent = total);
  • Delete both of those lines.
  • Save app.js.
Test that entries survive a refresh

Before you refresh, think about what you expect to happen. You added a Banana entry earlier. Will it still be there after a refresh?

  • Switch to your browser and refresh the calorie tracker page.

Your Banana entry should still be in the log. The data survived the refresh because loadEntries read it back from localStorage before renderLog built the list.

  • Add two more entries to confirm everything still works end to end (e.g. Coffee at 5 calories and Rice at 200 calories).
  • Refresh the page again.

All three entries should appear in the log. Close the tab entirely, reopen it, and they'll still be there.

Entries disappearing after refresh?

  • Check that entries = loadEntries(); is above renderLog(); inside the DOMContentLoaded listener. If the order is reversed, the log renders before the data loads.
  • Make sure saveEntries(); is being called inside addEntry. Without it, new entries are never written to localStorage.
  • Open DevTools and check Application > Local Storage. If the key calorie-tracker-entries is missing, the save step isn't running.

Help me debug why my entries disappear on refresh.

✔️ Awesome, I've got everything!

Great. Double check you've saved app.js.

ⓧ I'd like to double check the full code

Scroll up through your app.js and confirm each of these sections is present, in this order:

  • ENTRIES_KEY and GOAL_KEY constants at the very top.
  • let entries = []; declaration.
  • saveEntries() function that calls localStorage.setItem.
  • loadEntries() function with try/catch and JSON.parse.
  • getTodayString() function (unchanged from Step 2).
  • addEntry() function with saveEntries(); between entries.push(entry); and renderLog();.
  • renderLog() function with no total calculation lines at the end.
  • DOMContentLoaded listener that calls entries = loadEntries(); before renderLog();.

Your tracker now remembers everything. Next up, you'll connect to a real food database so you can search for foods and get accurate calorie counts instead of guessing.

Search Real Foods with the USDA API

Right now your calorie tracker relies on the learner typing in calorie counts by hand. That means guessing, Googling, or just making numbers up.

The USDA FoodData Central API has nutrition data for millions of foods. In this step, you will connect your app to that database so you can search for any food and get its real calorie count with one click.

In this step, get ready to:
  • Add a search bar to your HTML and style it.
  • Fetch real nutrition data from the USDA FoodData Central API.
  • Wire up click-to-add so a search result goes straight into your food log.
Add the search UI

Before any API calls can happen, the page needs a place for the learner to type a food name and see results.

  • In Cursor, open index.html.
  • Find the opening <section class="add-section"> tag.
  • Add this new search section directly above it:
    <section class="search-section">
      <h2>Search USDA Foods</h2>
      <div class="input-group">
        <input type="text" id="searchInput" placeholder="Search for a food (e.g. chicken breast)">
        <button id="searchBtn">Search</button>
      </div>
      <ul class="search-results" id="searchResults"></ul>
    </section>

What does this HTML do?

The searchInput text field is where the learner types a food name. The searchBtn button triggers the API call. The empty searchResults list will be filled with clickable results by JavaScript later.

  • Save index.html.
  • Refresh your browser. You should see a "Search USDA Foods" heading with a text input and a Search button above the manual add form.

Don't see the search section?

Make sure the new <section> block sits directly above the <section class="add-section"> line, not inside it. Check that your tags are properly closed.

Still stuck? Help me figure out why my search section isn't showing up in index.html.

The search box is on the page but it looks unstyled. Time to fix that.

  • Switch to style.css in Cursor.
  • Find this line:
.add-section,
.log-section {
  • Add .search-section, on a new line above .add-section, so the selector now looks like this:
.search-section,
.add-section,
.log-section {
  • Next, find this line:
.add-section h2,
.log-section h2 {
  • Add .search-section h2, on a new line above .add-section h2, so the selector now looks like this:
.search-section h2,
.add-section h2,
.log-section h2 {

Those two changes give the search section the same white card and heading size as the existing sections.

  • Now add the input-group and search button styles. Below the closing } of the .search-section h2, .add-section h2, .log-section h2 rule, paste the following:
/* Layout for search input and button side by side */
.input-group {
  display: flex;
  gap: 8px;
}

.input-group input {
  flex: 1;
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 8px;
  font-size: 0.95rem;
}

.input-group button {
  padding: 8px 16px;
  background: #16213e;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 0.85rem;
}

/* Override the dark button color for search */
.search-section button {
  background: #0a9396;
}

What do these styles do?

The .input-group class uses flexbox to place the text input and button side by side. The .search-section button override gives the Search button a teal color so it looks distinct from the orange Add button.

  • Save style.css and refresh your browser.
  • The search input and teal Search button should now sit side by side inside a white card.

Search button not teal?

Make sure the .search-section button rule appears after the .input-group button rule in your CSS. CSS applies rules in order, so the teal override must come second.

Need more help? Help me debug why my search button is not teal in my calorie tracker.

The search results will need their own styles too. These styles will activate once the JavaScript renders actual results.

  • Still in style.css, add these styles below the .search-section button rule:
/* Container for the search results list */
.search-results {
  list-style: none;
  margin-top: 8px;
}

/* Each individual search result row */
.search-result {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 10px 12px;
  border-bottom: 1px solid #f0f0f0;
  cursor: pointer;
  transition: background 0.15s;
}

.search-result:hover {
  background: #f0faf8;
}

What do these styles do?

Each .search-result is a flexbox row with the food name on the left and calories on the right. The cursor: pointer and hover effect signal that each result is clickable.

  • Save style.css.
  • You won't see any change in the browser yet because no search results exist. These styles take effect once the JavaScript renders results.
  • Add the final batch of search styles below the .search-result:hover rule:
/* Food name and calorie labels inside each result */
.result-name {
  font-size: 0.9rem;
  flex: 1;
  margin-right: 8px;
}

.result-calories {
  font-size: 0.8rem;
  color: #0a9396;
  font-weight: 600;
  white-space: nowrap;
}

/* Message shown when a search returns nothing */
.no-results {
  padding: 10px;
  color: #999;
  font-size: 0.9rem;
}

What do these styles do?

The .result-name takes up available space with flex: 1. The .result-calories displays the calorie count in teal. The .no-results class styles the "No foods found" message for empty searches.

  • Save style.css. The search UI is fully styled and ready for JavaScript.
Fetch from the USDA API

The USDA FoodData Central API is a free, public database maintained by the US government. It holds nutrition data for millions of foods. Your app will send a search query to this API and get back real calorie counts.

What is USDA FoodData Central?

FoodData Central is the USDA's primary food composition database. It covers branded products, raw ingredients, and restaurant items. The data is public domain (CC0 license), meaning you can use it freely with no permission needed.

The API uses a special key called DEMO_KEY that lets you make requests without signing up. It is limited to 30 requests per hour and 50 per day, which is plenty for this project.

  • Switch to app.js in Cursor.
  • Find the very first line:
const ENTRIES_KEY = 'calorie-tracker-entries';
  • Add these two constants on new lines above it, so they become the first two lines of the file:
const API_BASE = 'https://api.nal.usda.gov/fdc/v1';
const API_KEY = 'DEMO_KEY';

Now add the function that calls the API.

  • Scroll down to the closing } of the renderLog function.
  • Add the searchFoods function below it:
async function searchFoods(query) {
  // Build the API URL with the search query and a limit of 5 results
  const url = `${API_BASE}/foods/search?api_key=${API_KEY}&query=${encodeURIComponent(query)}&pageSize=5`;

  try {
    const response = await fetch(url);
    if (!response.ok) {
      console.error('API error:', response.status);
      return [];
    }
    const data = await response.json();
    return data.foods || [];
  } catch (e) {
    console.error('Search failed:', e.message);
    return [];
  }
}

What does this function do?

The searchFoods function sends a GET request to the USDA's /foods/search endpoint. It passes the DEMO_KEY for authentication, the user's search query, and pageSize=5 to limit results.

The response comes back as JSON with a foods array. Each food object includes a description (the food name) and a foodNutrients array with nutrient details.

  • Save app.js and refresh your browser.

Before you test, what do you think will happen if you call searchFoods('banana') from the browser console? Will it return data from the USDA, or will it fail?

  • Open your browser's DevTools by pressing F12 (or Cmd+Option+I on macOS).
  • Click the Console tab.
  • Test the API by running this command in the console:
searchFoods('banana').then(foods => console.log(foods))

You should see an array of food objects logged to the console, each with fields like description and foodNutrients. The API is working.

Seeing an error in the console?

If you see a network or CORS error, your browser may be blocking the request because the page is loaded via the file:// protocol. Try opening the file through a local server instead. In your terminal, navigate to the calorie-tracker folder and run:

python3 -m http.server 8000

Then open http://localhost:8000 in your browser and try the console test again.

Still stuck? Help me fix a CORS or network error when calling the USDA API from my calorie tracker.

The API returns raw data. Now you need a function to turn that data into clickable HTML.

  • In app.js, add the renderSearchResults function directly below the searchFoods function:
function renderSearchResults(foods) {
  const resultsEl = document.getElementById('searchResults');

  if (foods.length === 0) {
    resultsEl.innerHTML = '<li class="no-results">No foods found.</li>';
    return;
  }

  // Build a clickable list item for each food
  resultsEl.innerHTML = foods.map(food => {
    const energyNutrient = food.foodNutrients.find(
      n => n.name === 'Energy' && n.unitName === 'KCAL'
    );
    const calories = energyNutrient ? Math.round(energyNutrient.amount) : 0;

    return `
      <li class="search-result" data-name="${food.description}" data-calories="${calories}">
        <span class="result-name">${food.description}</span>
        <span class="result-calories">${calories} cal/100g</span>
      </li>
    `;
  }).join('');
}

How does the calorie extraction work?

Each food in the API response has a foodNutrients array containing objects with name, amount, and unitName fields. The code uses .find() to locate the nutrient where name is "Energy" and unitName is "KCAL".

The calorie count and food name are stored in data- attributes on each list item. This makes it easy for a click handler to read them later without re-parsing the data.

  • Save app.js. The rendering function is ready. The next chunk connects the Search button to call both functions.
Wire up click-to-add

The API and rendering functions exist, but nothing calls them yet. You need two event listeners: one for the Search button, and one for clicking a search result.

  • In app.js, scroll down to the DOMContentLoaded callback.
  • Find the closing }); of the addForm submit listener.
  • Add the search button handler below it:
  document.getElementById('searchBtn').addEventListener('click', async () => {
    const query = document.getElementById('searchInput').value.trim();
    if (query.length < 2) return;

    // Show a loading state on the button
    const btn = document.getElementById('searchBtn');
    btn.textContent = 'Searching...';
    btn.disabled = true;

    const foods = await searchFoods(query);
    renderSearchResults(foods);

    btn.textContent = 'Search';
    btn.disabled = false;
  });

What does this handler do?

When the learner clicks Search, the handler grabs the query from the input, skips searches shorter than 2 characters, and swaps the button text to "Searching..." while the API call runs. Once results arrive, it calls renderSearchResults and resets the button.

  • Save app.js and refresh your browser.
  • Type banana in the search input and click Search.

You should see a list of USDA food results appear below the search box, each showing a food name and its calorie count per 100g. The button flashes "Searching..." while the request is in flight.

No results appearing?

Open the DevTools console and check for errors. If you see a network error, try serving the page from a local server (see the troubleshooting tip above). If you see no errors but still no results, make sure you typed at least 2 characters in the search input.

Need help? Help me figure out why my USDA food search isn't showing any results.

The results appear, but clicking one does nothing yet. The last piece is an event listener on the results list.

  • In app.js, add the search results click handler directly below the searchBtn handler you just added (still inside the DOMContentLoaded callback):
  document.getElementById('searchResults').addEventListener('click', (e) => {
    const result = e.target.closest('.search-result');
    if (result) {
      // Add the clicked food to the log using its data attributes
      addEntry(result.dataset.name, Number(result.dataset.calories));
      document.getElementById('searchResults').innerHTML = '';
      document.getElementById('searchInput').value = '';
    }
  });

What is event delegation?

Instead of attaching a click listener to every .search-result item (which are created dynamically), the listener sits on the parent searchResults list. When a click happens anywhere inside it, .closest('.search-result') checks if the clicked element is inside a result row.

This pattern is called event delegation. It works even for elements that did not exist when the page loaded.

  • Save app.js.

Before you test, what do you think will happen when you click one of the search results?

  • Refresh your browser.
  • Type apple in the search input and click Search.
  • Click any result from the list.

The clicked food appears in your Today's Log with its real calorie count. The search results clear, and the daily total updates. Refresh the page. The entry persists thanks to localStorage from the previous step.

Click not adding the food?

Make sure the searchResults listener is inside the DOMContentLoaded callback, not outside it. Also check that the data-name and data-calories attributes in renderSearchResults match result.dataset.name and result.dataset.calories in the click handler.

Still stuck? Help me debug why clicking a search result doesn't add it to my food log.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files: index.html, style.css, and app.js.

ⓧ I'd like to double check the full code

All three files have changed in this step. Scroll up and confirm each piece is present, in order:

  • index.html: The new search-section sits above the add-section.
  • style.css: Both group selectors include .search-section. The .input-group, .search-section button, .search-results, .search-result, .result-name, .result-calories, and .no-results rules all appear between the h2 group rule and the #addForm rule.
  • app.js: API_BASE and API_KEY constants sit at the top. The searchFoods and renderSearchResults functions sit after renderLog. The searchBtn and searchResults listeners sit inside DOMContentLoaded after the addForm listener.

Your tracker can now pull real nutrition data from the USDA's database and log it in one click. Next up, you will add a daily calorie goal and a progress bar that fills up and changes color as you eat.

Add a Daily Goal with Progress Bar

Your calorie tracker can search real foods from the USDA database and log entries that survive page refreshes. There is one piece missing: a goal to track against.

A calorie log without a target is just a list of numbers. In this step, you will add a daily calorie goal and a color-coded progress bar that turns from green to orange to red as you approach your daily allowance.

In this step, get ready to:
  • Add a summary dashboard and goal input to the HTML and CSS.
  • Wire up goal saving, loading, and progress bar calculation in JavaScript.
  • Test the full tracker from goal setting to progress bar color changes.
Add the goal and summary UI

The tracker needs a summary dashboard at the top of the page showing three values: your goal, how many calories you have consumed, and how many remain. Below that, a progress bar fills as you eat through your allowance.

  • In Cursor, open index.html.
  • Find the closing </header> tag. Paste this code between </header> and the <section class="search-section"> line:
    <!-- Summary dashboard showing goal, consumed, and remaining -->
    <section class="summary">
      <div class="summary-item">
        <span class="summary-label">Goal</span>
        <span class="summary-value" id="goalDisplay">--</span>
      </div>
      <div class="summary-item">
        <span class="summary-label">Consumed</span>
        <span class="summary-value" id="consumedDisplay">0</span>
      </div>
      <div class="summary-item">
        <span class="summary-label">Remaining</span>
        <span class="summary-value" id="remainingDisplay">--</span>
      </div>
      <!-- Progress bar fills as you eat -->
      <div class="progress-container">
        <div class="progress-bar" id="progressBar"></div>
      </div>
    </section>

    <!-- Goal input to set your daily target -->
    <section class="goal-section">
      <label for="goalInput">Daily Calorie Goal:</label>
      <div class="input-group">
        <input type="number" id="goalInput" placeholder="e.g. 2000" min="0">
        <button id="setGoalBtn">Set Goal</button>
      </div>
    </section>

What does this code do?

The summary section creates a dashboard with three values: Goal, Consumed, and Remaining. Each has an id so JavaScript can update its text dynamically.

The progress-bar div starts at 0% width. JavaScript will stretch it as a percentage of your goal. The goal-section provides an input and button to set your daily calorie target.

  • Save index.html.
  • Refresh your browser. You should see a new row of text reading Goal, Consumed, and Remaining above the search section, plus a Daily Calorie Goal input below it.

Don't see the new sections?

Make sure you pasted the code between </header> and <section class="search-section">. If the page looks broken, check for missing closing tags.

Help me fix my index.html layout

The new sections look plain because they have no CSS yet. Time to style them.

  • Add the summary card styles to style.css. Find the closing } of the .date rule and paste this code below it:
/* Summary dashboard card */
.summary {
  background: white;
  border-radius: 12px;
  padding: 20px;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 20px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.summary-item {
  flex: 1;
  text-align: center;
  min-width: 80px;
}

.summary-label {
  display: block;
  font-size: 0.75rem;
  text-transform: uppercase;
  color: #888;
  letter-spacing: 0.5px;
}

.summary-value {
  display: block;
  font-size: 1.5rem;
  font-weight: 700;
  color: #16213e;
}

What do these styles do?

The .summary rule creates a white card with flexbox layout so the three values sit side by side. Each .summary-item takes equal width.

The .summary-label uses small uppercase text for the category names. The .summary-value uses large bold numbers so the calorie counts are easy to read at a glance.

  • Save style.css and refresh your browser.
  • The summary section should now appear as a white card with three large numbers (--, 0, --) and small uppercase labels.

Summary card not styled?

Check that the code is pasted after the .date rule's closing brace, not inside it. Each CSS rule must have its own opening and closing {} pair.

Help me debug my summary card CSS

  • Add the progress bar styles. In style.css, find the closing } of the .summary-value rule you just added and paste this code below it:
/* Progress bar track and fill */
.progress-container {
  width: 100%;
  height: 8px;
  background: #e9ecef;
  border-radius: 4px;
  overflow: hidden;
}

.progress-bar {
  height: 100%;
  background: #4caf50;
  border-radius: 4px;
  width: 0%;
  transition: width 0.3s ease;
}

/* Color changes at 80% and 100% */
.progress-bar.warning {
  background: #ff9800;
}

.progress-bar.over {
  background: #f44336;
}

The goal section also needs three small CSS updates to match the other cards.

  • Scroll down in style.css to find the line that says .search-section,. Add .goal-section, on a new line directly above it.
  • Right after the closing } of that same grouped rule block, paste this new rule:
/* Goal section label styling */
.goal-section label {
  display: block;
  font-size: 0.85rem;
  font-weight: 600;
  margin-bottom: 8px;
}
  • Find the line that says .input-group button { and change it to two lines:
.input-group button,
.goal-section button {

What do these CSS changes do?

The progress bar starts at 0% width (invisible) and has a smooth transition so it animates when JavaScript changes the width. The .warning class turns the bar orange at 80%. The .over class turns it red at 100%.

Adding .goal-section, to the grouped selector gives the goal section the same white card styling as the other sections. The label rule styles the Daily Calorie Goal: text above the input.

  • Save style.css and refresh your browser.
  • You should see a thin gray progress bar strip under the summary card and the goal section styled as a white card matching the search and log sections.

Goal section not showing a white card?

Make sure .goal-section, is on its own line directly above .search-section, with a comma at the end. A missing comma breaks the entire selector group.

Help me fix my goal section CSS

✔️ 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

Both files are too long to show here in full. Scroll through and confirm these pieces are present:

  • In index.html: the summary section (with goalDisplay, consumedDisplay, remainingDisplay, and progressBar) and the goal-section (with goalInput and setGoalBtn) are between </header> and <section class="search-section">.
  • In style.css: the .summary, .summary-item, .summary-label, and .summary-value rules are after .date.
  • In style.css: the .progress-container, .progress-bar, .progress-bar.warning, and .progress-bar.over rules are after .summary-value.
  • In style.css: the grouped section selector starts with .goal-section, and the button selector reads .input-group button, .goal-section button {.
Wire up goal logic

The UI is in place, but the summary numbers are static and the progress bar stays empty. You need JavaScript to calculate consumed calories, track a goal, and drive the progress bar.

  • Switch to app.js in Cursor.
  • Find the line let entries = []; and add this line directly below it:
let dailyGoal = null;
  • Now scroll down past the closing } of the renderLog function. Paste this new function below it:
function updateSummary() {
  // Calculate today's total consumed calories
  const today = getTodayString();
  const consumed = entries
    .filter(entry => entry.date === today)
    .reduce((sum, entry) => sum + entry.calories, 0);

  // Always show consumed count
  document.getElementById('consumedDisplay').textContent = consumed;

  // Update goal-dependent displays only if a goal is set
  if (dailyGoal) {
    document.getElementById('goalDisplay').textContent = dailyGoal;
    const remaining = dailyGoal - consumed;
    document.getElementById('remainingDisplay').textContent = remaining;

    // Set progress bar width (capped at 100%)
    const percent = Math.min((consumed / dailyGoal) * 100, 100);
    const bar = document.getElementById('progressBar');
    bar.style.width = percent + '%';
    bar.classList.remove('warning', 'over');

    // Apply color class based on intake level
    if (percent >= 100) {
      bar.classList.add('over');
    } else if (percent >= 80) {
      bar.classList.add('warning');
    }
  }
}
  • Scroll back up to the renderLog function. Find its last closing } and add updateSummary(); on the line just above it, so the end of renderLog looks like this:
  updateSummary();
}

What does updateSummary do?

Every time renderLog runs, it now calls updateSummary. This function uses reduce to sum today's calories and writes the total into the consumedDisplay element.

When a goal is set, it also calculates the remaining calories and the progress bar width as a percentage. The function caps the bar at 100% using Math.min and swaps CSS classes to change the color: green under 80%, orange from 80% to 99%, and red at 100% or above.

  • Save app.js and refresh your browser.
  • The Consumed value in the summary card should now show the total calories from your existing log entries (or 0 if the log is empty).

Seeing an error in the console?

If you see a ReferenceError mentioning dailyGoal, check that let dailyGoal = null; is on the line directly after let entries = []; at the top of the file.

If the consumed count does not appear, open DevTools (F12) and check the Console tab for errors. Make sure updateSummary(); is the last line inside renderLog before its closing brace.

Help me debug my updateSummary function

The consumed total now updates, but you cannot set a goal yet. You need functions to save and load the goal from localStorage, and a button handler to wire it all together.

  • Find the closing } of the loadEntries function. Paste these two functions below it:
function saveGoal(goal) {
  // Persist the goal number to localStorage
  localStorage.setItem(GOAL_KEY, JSON.stringify(goal));
}

function loadGoal() {
  // Read the saved goal from localStorage
  try {
    const raw = localStorage.getItem(GOAL_KEY);
    if (!raw) return null;
    return JSON.parse(raw);
  } catch (e) {
    console.warn('Could not load goal:', e.message);
    return null;
  }
}

Now connect these functions inside the DOMContentLoaded handler.

  • Scroll down to the DOMContentLoaded handler. Find the line entries = loadEntries(); and add this line directly below it:
  dailyGoal = loadGoal();
  • Find the closing }); of the addForm submit handler. Paste this event listener below it:
  document.getElementById('setGoalBtn').addEventListener('click', () => {
    // Read the goal input and save if it is a valid number
    const goal = Number(document.getElementById('goalInput').value);
    if (goal > 0) {
      dailyGoal = goal;
      saveGoal(goal);
      updateSummary();
    }
  });

What does this handler do?

When the learner clicks Set Goal, the handler reads the number from the input, stores it in both the dailyGoal variable and localStorage, then immediately calls updateSummary to refresh the dashboard.

On page load, dailyGoal = loadGoal() pulls the saved goal from localStorage so the dashboard restores itself after a refresh. If no goal has been set yet, loadGoal returns null and the progress bar stays hidden.

  • Save app.js and refresh your browser.
  • Type 2000 into the Daily Calorie Goal input and click Set Goal.
  • The Goal value should update to 2000 and the Remaining value should show the difference between 2000 and your consumed calories.

Clicking Set Goal does nothing?

Open DevTools and check the Console tab. If you see an error about saveGoal or loadGoal not being defined, make sure you pasted both functions after the closing } of loadEntries and before getTodayString.

Help me fix my Set Goal button

✔️ Awesome, I've got everything!

Great. Double check you have saved app.js before moving on.

ⓧ I'd like to double check the full code

The file is too long to show in full. Scroll through app.js and confirm these pieces are present, in order:

  • let dailyGoal = null; right after let entries = [];.
  • saveGoal and loadGoal functions after loadEntries.
  • updateSummary function after renderLog.
  • updateSummary(); as the last line inside renderLog before its closing }.
  • dailyGoal = loadGoal(); after entries = loadEntries(); in DOMContentLoaded.
  • The setGoalBtn click handler after the addForm submit handler.
Test the full flow

Before you run through the final test, think about what you expect to happen. If you set a goal of 2000 calories and add foods that total around 1600 calories, what percentage should the bar fill to? What color should it be?

  • Refresh your browser to start with a clean state.
  • Type 2000 in the Daily Calorie Goal input and click Set Goal.
  • Search for banana using the USDA search and click a result to add it.
  • Add a few more foods until your consumed count approaches 1600 calories.
  • Watch the progress bar. It should be green and about 80% full.
  • Add more food to push past 1600 calories. The bar should turn orange as you cross 80% of your goal.
  • Keep adding until you pass 2000 calories total. The bar should turn red and fill completely.

At 1600 out of 2000 calories the bar is at 80%, exactly where it switches from green to orange. Push past the goal and the bar turns red to signal you have hit your limit.

  • Refresh the page one more time. Your goal, entries, and progress bar should all reappear exactly as you left them.

Why does persistence still work?

Both the goal and entries are saved to localStorage. On page load, loadEntries and loadGoal restore the data. Then renderLog calls updateSummary, which recalculates everything from the restored data.

Secret mission

Delete Entries from Your Log

What happens when you accidentally log the wrong food? Right now there is no way to fix it. In this mission, you add a delete button to each log entry that removes it from the list, updates the calorie total, and persists the change in localStorage.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine with no cloud resources or paid services. There are no ongoing costs.

Decide whether to keep your files and data as they are, clear your tracked data, or delete everything entirely.

Resources you used:

  • The calorie-tracker folder on your Desktop containing index.html, style.css, and app.js.
  • Browser localStorage data stored under the keys calorie-tracker-entries and calorie-tracker-goal.

Keep everything running

No action needed. Choose this if you want to keep using your calorie tracker.

  • Your tracker lives as local files on your Desktop. Open index.html in your browser anytime to use it.
  • Your logged foods and daily goal persist in your browser's localStorage automatically.

Upgrade your API key

The DEMO_KEY has low rate limits of 30 requests per hour and 50 per day. If you plan to keep using your tracker, sign up for a free API key at fdc.nal.usda.gov/api-key-signup. A free key gives you 1,000 requests per hour.

Pause - I'll come back to this later

There are no running processes to stop. Your files and localStorage data will be waiting when you come back.

  • Close Cursor if you are done for now.
  • Your food log and goal are saved in localStorage. They will still be there when you reopen index.html.

Delete - I don't want to use this again

Remove all project files and clear your saved data to start completely fresh.

First, clear your localStorage data:

  • Open your browser and navigate to your calorie tracker page (or any page on the same origin).
  • Open DevTools by pressing Cmd+Option+I (macOS) or Ctrl+Shift+I (Windows).
  • Click the Application tab.
  • In the left sidebar, expand Local Storage and click on your site's origin.
  • Find the calorie-tracker-entries key, right-click it, and select Delete.
  • Find the calorie-tracker-goal key, right-click it, and select Delete.

Next, delete the project folder:

macOS

  • Delete the project folder by running this command in your terminal:
rm -rf ~/Desktop/calorie-tracker

Windows

  • Delete the project folder by running this command in your terminal:
rmdir /s /q %USERPROFILE%\Desktop\calorie-tracker

Nice Work!

Nice Work!

Nice work! You just built a fully functional calorie tracker powered by real USDA nutrition data.

You've learned how to:

  • Built a browser-based calorie tracker from scratch with vanilla HTML, CSS, and JavaScript that searches the USDA FoodData Central API for real nutrition data and logs meals with a running total.
  • Used localStorage to persist food entries and goal settings across page refreshes and tab closures, with error handling for missing or corrupt data.
  • Built a color-coded progress bar that tracks daily calorie intake against a goal, shifting from green to orange to red as you approach your target.
  • Secret Mission: Completed an optional challenge to extend your calorie tracker with new functionality.

Ready to quiz yourself?