Build a Bee and Honey Landing Page
Build a honey storefront with Bootstrap, SVG art, and a live JavaScript basket.
Introduction
30 Second Summary
A small local business often gets one quick chance to make its products feel worth discovering online. Visitors stay when the page makes the story clear and the next step easy.
In this project, you will build a responsive honey storefront for a fictional local business. Visitors can browse illustrated products, add honey to a live basket, learn how to support pollinators, and prepare a simulated order enquiry.
What You'll Build
Your finished page welcomes visitors with an illustrated bee and honey jar, adapts to mobile screens, and updates the basket the moment they choose a product.
By the end of this project, you'll have:
- A responsive storefront that presents three honey products in a Bootstrap 5.3.8 card grid across mobile and desktop screens.
- An engaging bee education section paired with original illustrations built from inline SVG artwork.
- A live basket and enquiry flow powered by JavaScript that groups repeated products and confirms a valid simulated enquiry.
- Secret Mission: Replace repeated Add clicks with per-product quantity controls and a calculated demo total.
Are there any prerequisites?
You should be comfortable building pages with HTML, CSS, and JavaScript. Visual Studio Code and Safari are the only local tools you need.
Before We Start
Before any hands-on work begins, commit to building Honeybee Harvest, a fictional local honey storefront for demo products. Its bee education belongs beside the catalog because pollinators help plants produce fruit and seeds.
Set Up the Three-File Site
Honeybee Harvest needs a clear home before its storefront can grow. A small folder keeps every part easy to find.
HTML provides the page structure. CSS holds the visual design.
JavaScript handles later interactions. Bootstrap 5.3.8 loads through a content delivery network so the setup stays focused on frontend fundamentals.
In this step, get ready to:
- Create the local folder in Visual Studio Code.
- Add the three site files with Bootstrap 5.3.8 connected.
- Preview the starter page in Safari.
Create the project files
A project folder gives the editor a defined workspace. Each file then appears in one Explorer view.
- Press Command-Space bar to open Spotlight.
- Type Visual Studio Code into Spotlight.
- Press Return to open Visual Studio Code.
- Select File in the menu bar.
- Select Open Folder....
- Select Desktop as the location.
- Select New Folder.
- Enter honeybee-harvest as the folder name.
- Select Open to use the folder as your workspace.
Visual Studio Code may show a Workspace Trust prompt for the new folder. You created every file in this workspace yourself.
- Select Yes, I trust the authors if the Workspace Trust prompt appears.
You will see honeybee-harvest at the top of the Explorer sidebar.
- Select Explorer in the Activity Bar.
- Select New File....
- Enter index.html as the filename.
- Select New File... again.
- Enter styles.css as the filename.
- Select New File... again.
- Enter script.js as the filename.
Good progress. Your Explorer now lists index.html, styles.css, and script.js inside the project folder.
Build the starter document
The starter document gives Safari enough information to interpret the page correctly. It also connects the two local files that you use later.
The verified Bootstrap files come from jsDelivr. The stylesheet loads in the page head while the JavaScript bundle loads near the end of the body.
Why use a CDN here?
A CDN lets this static page load Bootstrap directly in the browser. This keeps the project free from package installation.
A package manager would introduce extra setup before you could work on the storefront. The CDN keeps your attention on the three frontend files.
- Select index.html in the Explorer sidebar.
- Replace its empty contents by pasting this starter document:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Honeybee Harvest</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Honeybee Harvest</h1>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>
What does this code do?
- The <!doctype html> declaration identifies an HTML5 document. The lang="en" attribute identifies its language.
- The character encoding metadata supports UTF-8 text. The viewport metadata lets the layout adapt to the browser width.
- The first CDN tag loads Bootstrap 5.3.8 styling.
- The local tags connect styles.css and script.js.
- The <h1> element gives the page a visible heading for the first browser test.
- The Bootstrap bundle provides the JavaScript used by responsive components later in the project.
- Save index.html by pressing Cmd+S on macOS or Ctrl+S on Windows.
The unsaved dot disappears from the index.html tab. This confirms the starter document is stored on disk.
Still seeing an unsaved dot?
Make sure the index.html editor tab is active when you use the save shortcut. Check that the file remains inside honeybee-harvest in the Explorer sidebar.
Help me check why index.html is not saving in Visual Studio Code.
✔️ Awesome, I've got everything!
Your starter document is saved. Keep styles.css and script.js empty for this first preview.
ⓧ I'd like to double check the full code
- Confirm that styles.css is empty.
- Confirm that script.js is empty.
- Compare index.html with this complete starter document:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Honeybee Harvest</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>Honeybee Harvest</h1>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>
Preview the page in Safari
A local preview tests whether the saved document can render directly from your Desktop. It also exercises both local file paths.
Before you open the page, predict whether Safari can load the heading from the three-file setup.
- Control-click index.html in the Explorer sidebar.
- Select Reveal in Finder.
- Control-click index.html in Finder.
- Select Open With.
- Select Safari.
You will see a large Honeybee Harvest heading on the local page. Safari stays on the page without showing a missing-file warning.
Page blank or showing code?
Confirm that the filename ends with .html. Check that the <h1>Honeybee Harvest</h1> line remains inside the body.
Help me troubleshoot my local Honeybee Harvest page in Safari.
Strong start. Your three-file storefront now opens locally with Bootstrap connected.
Your project foundation is ready. Next, you will replace the starter heading with a responsive navigation bar and an illustrated storefront hero.
Build the Storefront Hero
Your three-file site already opens cleanly in Safari. The starter heading proves the files connect.
The page still needs a product story. It also needs a reason for visitors to keep scrolling.
Bootstrap 5.3.8 gives the navigation a responsive collapse pattern. Semantic HTML gives the page clear landmarks.
An inline SVG creates original bee artwork without external image files. Custom CSS adds the amber storefront identity.
In this step, get ready to:
- Structure the page with semantic landmarks.
- Create a responsive storefront navigation.
- Build and style the illustrated hero.
Build the responsive navigation
A responsive navigation keeps the storefront destinations available at every screen width. The basket chip also reserves a visible home for the interaction you will activate later.
- Switch back to index.html in Visual Studio Code.
- Find the visible Honeybee Harvest starter heading.
- Replace the starter heading with the following navigation markup:
<a class="visually-hidden-focusable" href="#main-content">Skip to main content</a>
<header>
<nav class="navbar navbar-expand-lg honey-nav sticky-top" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand brand-mark" href="#home">Honeybee Harvest</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavigation" aria-controls="mainNavigation" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNavigation">
<ul class="navbar-nav nav-links-push">
<li class="nav-item"><a class="nav-link" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#shop">Shop</a></li>
<li class="nav-item"><a class="nav-link" href="#bees">Bees</a></li>
<li class="nav-item"><a class="nav-link" href="#order">Order</a></li>
</ul>
<span class="basket-chip">Basket <span id="basket-count">0</span></span>
</div>
</div>
</nav>
</header>
What does this markup do?
- The skip link lets keyboard users move directly to the main content.
- The header landmark identifies the introductory area of the page.
- The #mainNavigation target lets the menu collapse at narrower widths.
- The #basket-count element starts at zero. JavaScript will update it in a later step.
- Save index.html.
- Reload the page in Safari.
- Narrow the Safari window until the navigation toggle appears.
You will see the brand name beside a navigation menu. At narrower widths, the links move behind the navigation toggle.
Navigation missing or staying expanded?
Confirm that the new markup sits inside the body element. Keep the Bootstrap bundle script near the end of the body so the collapse behavior can run.
Check that data-bs-target="#mainNavigation" matches id="mainNavigation" exactly.
Ask for help with the responsive menu.
Create the illustrated hero
The hero gives visitors an immediate sense of the fictional product. Its artwork remains part of the document, so the page needs no separate image files.
- Find the closing </header> tag in index.html.
- Add the hero shell below the header by pasting this code:
<main id="main-content">
<section class="hero-section" id="home">
<div class="container hero-grid">
<div>
<p class="eyebrow">Fictional local honey shop</p>
<h1>Golden honey, gathered with care</h1>
<p class="hero-copy">Explore small-batch demo products, build a sample basket, and discover simple ways to support pollinators.</p>
<div class="hero-actions">
<a class="btn btn-honey" href="#shop">Shop the harvest</a>
<a class="text-link" href="#bees">Why bees matter</a>
</div>
</div>
<svg class="hero-art" viewBox="0 0 520 420" role="img" aria-labelledby="heroArtTitle heroArtDescription">
<title id="heroArtTitle">Bee flying beside a honey jar</title>
<desc id="heroArtDescription">An original illustration with a striped bee, flowers, honeycomb cells, and a golden jar.</desc>
<defs>
<linearGradient id="honeyFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffd166"></stop>
<stop offset="1" stop-color="#e99a12"></stop>
</linearGradient>
</defs>
<circle cx="260" cy="210" r="182" fill="#fff3cf"></circle>
</svg>
</div>
</section>
</main>
How does the hero work?
- The main landmark contains the page content that follows the navigation.
- The #home section gives the Home link a destination.
- The two hero links point to sections that you will add later.
- The SVG title and description explain the artwork to assistive technology.
- Save index.html.
- Reload the page in Safari.
You will see the new headline with two links. A pale circular canvas appears where the illustration is taking shape.
Hero copy not appearing?
Check that the complete main block sits after </header>. Confirm that it remains above the script tags.
Ask for help locating the misplaced hero markup.
Simple vector shapes combine into the illustration. Each group below adds a recognizable subject while keeping the artwork editable as text.
- Find the background circle inside the hero-art SVG.
- Add the honeycomb and bee below that circle by pasting this code:
<g fill="none" stroke="#d78908" stroke-width="6" opacity="0.32">
<path d="M62 86l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M106 112l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M62 138l22-13 22 13v26l-22 13-22-13z"></path>
</g>
<g transform="translate(92 154)">
<ellipse cx="48" cy="31" rx="34" ry="24" fill="#ffc43d"></ellipse>
<rect x="32" y="9" width="9" height="44" rx="4" fill="#2f2a24"></rect>
<rect x="53" y="10" width="9" height="42" rx="4" fill="#2f2a24"></rect>
<circle cx="76" cy="28" r="15" fill="#2f2a24"></circle>
<circle cx="81" cy="23" r="2.5" fill="#fffaf0"></circle>
<ellipse cx="38" cy="4" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(-22 38 4)"></ellipse>
<ellipse cx="55" cy="2" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(18 55 2)"></ellipse>
<path d="M18 30H0" stroke="#2f2a24" stroke-width="4" stroke-linecap="round"></path>
</g>
How do these shapes become a bee?
- The path elements draw three connected honeycomb cells.
- The ellipses form the bee body and translucent wings.
- The rectangles create the dark stripes across the bee.
- The translated group keeps the bee shapes positioned as one unit.
- Save index.html.
- Reload the page in Safari.
You will see a striped bee beside three honeycomb cells. Both sit over the pale circular background.
Bee shapes missing?
Place both g groups before the closing </svg> tag. A group placed outside the SVG will not render as part of the illustration.
Ask for help checking the SVG group placement.
- Find the closing bee group immediately below the path containing M18 30H0.
- Add the honey jar and flower below that group by pasting this code:
<g transform="translate(235 120)">
<rect x="42" y="8" width="116" height="28" rx="8" fill="#4b382d"></rect>
<rect x="28" y="32" width="144" height="190" rx="34" fill="#f9e4a6" stroke="#4b382d" stroke-width="7"></rect>
<rect x="42" y="85" width="116" height="120" rx="24" fill="url(#honeyFill)"></rect>
<path d="M42 105c25 18 39-13 61 3 23 17 36-15 55 0v35H42z" fill="#ffd970"></path>
<rect x="50" y="118" width="100" height="56" rx="12" fill="#fff8e6"></rect>
<text x="100" y="142" text-anchor="middle" font-size="17" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">HONEYBEE</text>
<text x="100" y="162" text-anchor="middle" font-size="13" font-family="system-ui, sans-serif" fill="#76513a">HARVEST</text>
</g>
<g transform="translate(390 300)">
<circle cx="0" cy="0" r="13" fill="#ffc43d"></circle>
<circle cx="0" cy="-24" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
</g>
What completes the illustration?
- Layered rectangles create the jar body and label.
- The honeyFill gradient gives the jar its golden depth.
- The final circles form one flower beside the jar.
- Save index.html.
- Reload the page in Safari.
The hero now shows a bee flying beside a labeled honey jar. Honeycomb cells and a flower complete the scene.
Jar or flower not rendering?
Confirm that both new groups remain inside the hero-art SVG. Check every opening g element for a matching closing tag.
Ask for help finding an unmatched SVG tag.
✔️ Awesome, I've got everything!
Great. Save index.html before moving to the visual styling.
ⓧ I'd like to double check the full code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Honeybee Harvest is a fictional honey shop and bee education demo.">
<title>Honeybee Harvest</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="visually-hidden-focusable" href="#main-content">Skip to main content</a>
<header>
<nav class="navbar navbar-expand-lg honey-nav sticky-top" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand brand-mark" href="#home">Honeybee Harvest</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavigation" aria-controls="mainNavigation" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNavigation">
<ul class="navbar-nav nav-links-push">
<li class="nav-item"><a class="nav-link" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#shop">Shop</a></li>
<li class="nav-item"><a class="nav-link" href="#bees">Bees</a></li>
<li class="nav-item"><a class="nav-link" href="#order">Order</a></li>
</ul>
<span class="basket-chip">Basket <span id="basket-count">0</span></span>
</div>
</div>
</nav>
</header>
<main id="main-content">
<section class="hero-section" id="home">
<div class="container hero-grid">
<div>
<p class="eyebrow">Fictional local honey shop</p>
<h1>Golden honey, gathered with care</h1>
<p class="hero-copy">Explore small-batch demo products, build a sample basket, and discover simple ways to support pollinators.</p>
<div class="hero-actions">
<a class="btn btn-honey" href="#shop">Shop the harvest</a>
<a class="text-link" href="#bees">Why bees matter</a>
</div>
</div>
<svg class="hero-art" viewBox="0 0 520 420" role="img" aria-labelledby="heroArtTitle heroArtDescription">
<title id="heroArtTitle">Bee flying beside a honey jar</title>
<desc id="heroArtDescription">An original illustration with a striped bee, flowers, honeycomb cells, and a golden jar.</desc>
<defs>
<linearGradient id="honeyFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffd166"></stop>
<stop offset="1" stop-color="#e99a12"></stop>
</linearGradient>
</defs>
<circle cx="260" cy="210" r="182" fill="#fff3cf"></circle>
<g fill="none" stroke="#d78908" stroke-width="6" opacity="0.32">
<path d="M62 86l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M106 112l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M62 138l22-13 22 13v26l-22 13-22-13z"></path>
</g>
<g transform="translate(92 154)">
<ellipse cx="48" cy="31" rx="34" ry="24" fill="#ffc43d"></ellipse>
<rect x="32" y="9" width="9" height="44" rx="4" fill="#2f2a24"></rect>
<rect x="53" y="10" width="9" height="42" rx="4" fill="#2f2a24"></rect>
<circle cx="76" cy="28" r="15" fill="#2f2a24"></circle>
<circle cx="81" cy="23" r="2.5" fill="#fffaf0"></circle>
<ellipse cx="38" cy="4" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(-22 38 4)"></ellipse>
<ellipse cx="55" cy="2" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(18 55 2)"></ellipse>
<path d="M18 30H0" stroke="#2f2a24" stroke-width="4" stroke-linecap="round"></path>
</g>
<g transform="translate(235 120)">
<rect x="42" y="8" width="116" height="28" rx="8" fill="#4b382d"></rect>
<rect x="28" y="32" width="144" height="190" rx="34" fill="#f9e4a6" stroke="#4b382d" stroke-width="7"></rect>
<rect x="42" y="85" width="116" height="120" rx="24" fill="url(#honeyFill)"></rect>
<path d="M42 105c25 18 39-13 61 3 23 17 36-15 55 0v35H42z" fill="#ffd970"></path>
<rect x="50" y="118" width="100" height="56" rx="12" fill="#fff8e6"></rect>
<text x="100" y="142" text-anchor="middle" font-size="17" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">HONEYBEE</text>
<text x="100" y="162" text-anchor="middle" font-size="13" font-family="system-ui, sans-serif" fill="#76513a">HARVEST</text>
</g>
<g transform="translate(390 300)">
<circle cx="0" cy="0" r="13" fill="#ffc43d"></circle>
<circle cx="0" cy="-24" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
</g>
</svg>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>
Style the responsive hero
The structure now communicates the storefront content. A shared color system turns that structure into a warm honey brand.
- Switch to the existing styles.css file.
- Add the color variables and global page styles by pasting this code:
:root {
--ink: #2f2a24;
--brown: #69452f;
--amber: #e99a12;
--gold: #ffc43d;
--cream: #fffaf0;
--soft-gold: #fff1c7;
--green: #315c45;
--white: #ffffff;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--ink);
background: var(--cream);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
What does the visual system control?
- The custom properties store colors in one reusable palette.
- The body rule applies the cream background and charcoal text.
- The system font stack keeps the local page readable without loading another dependency.
- Save styles.css.
- Reload the page in Safari.
The page background changes to cream. The text changes to charcoal.
Colors not changing?
Confirm that styles.css remains linked in the document head. Check the variable names for matching spelling.
Ask for help checking the stylesheet connection.
- Add the heading scale and navigation surface below the existing styles:
h1,
h2,
h3,
.brand-mark {
line-height: 1.1;
font-weight: 800;
}
h1 {
max-width: 720px;
font-size: clamp(2.8rem, 8vw, 5.8rem);
letter-spacing: -0.055em;
}
h2 {
font-size: clamp(2rem, 5vw, 3.4rem);
letter-spacing: -0.035em;
}
.honey-nav {
background: rgba(255, 250, 240, 0.96);
border-bottom: 1px solid rgba(105, 69, 47, 0.16);
backdrop-filter: blur(12px);
}
.brand-mark {
color: var(--brown);
}
How does the type stay responsive?
- The clamp() values let headings grow within controlled limits.
- The navigation background remains slightly transparent over page content.
- The brand name uses brown to connect the navigation with the honey palette.
- Save styles.css.
- Reload the page in Safari.
The headline becomes larger and tighter. The navigation gains a pale surface with a subtle lower border.
Heading still looks small?
Check that the selector is h1 without a class prefix. Confirm that the heading remains an h1 element in index.html.
Ask for help comparing the heading selector with the markup.
- Add the navigation alignment and basket chip styles below the current CSS:
.nav-links-push {
margin-left: auto;
}
.nav-link {
color: var(--ink);
font-weight: 700;
}
.basket-chip {
display: inline-flex;
gap: 0.45rem;
align-items: center;
margin-left: 1rem;
padding: 0.45rem 0.75rem;
color: var(--white);
background: var(--brown);
border-radius: 999px;
font-weight: 800;
}
.hero-section {
padding: 5.5rem 0;
overflow: hidden;
background: radial-gradient(circle at top right, #ffe399 0, transparent 36%), var(--soft-gold);
}
What changes in the navigation?
- The automatic left margin pushes the navigation links toward the right on wide screens.
- The basket chip uses a rounded brown background for contrast.
- The hero receives generous spacing and a soft radial highlight.
- Save styles.css.
- Reload the page in Safari.
The links align toward the right on a wide screen. The basket count sits inside a dark rounded chip.
Basket chip still looks plain?
Confirm that the HTML uses class="basket-chip". Check that the class selector begins with a period in styles.css.
Ask for help matching the basket class.
- Add the hero grid and supporting text styles below the existing rules:
.hero-grid,
.impact-grid,
.order-grid {
display: grid;
gap: 2.5rem;
align-items: center;
}
.hero-copy {
max-width: 620px;
font-size: 1.2rem;
}
.eyebrow {
margin-bottom: 0.75rem;
color: var(--green);
font-size: 0.78rem;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
}
How is the hero organized?
- The grid creates consistent space between the copy and artwork.
- The hero paragraph has a readable maximum width.
- The eyebrow style creates a small uppercase category label.
- Save styles.css.
- Reload the page in Safari.
The hero content gains more breathing room. The fictional shop label becomes a small green eyebrow above the headline.
Hero spacing unchanged?
Confirm that the hero container uses class="container hero-grid". Check that the hero-grid selector includes its leading period.
Ask for help checking the hero grid class.
- Add the hero action and button styles below the current CSS:
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-top: 1.75rem;
}
.btn-honey {
padding: 0.8rem 1.1rem;
color: var(--ink);
background: var(--gold);
border: 2px solid var(--ink);
border-radius: 0.7rem;
font-weight: 900;
box-shadow: 4px 4px 0 var(--ink);
}
.btn-honey:hover,
.btn-honey:focus {
color: var(--white);
background: var(--brown);
border-color: var(--brown);
box-shadow: none;
}
Why does the button feel interactive?
- The flex container lets the hero links wrap on narrow screens.
- The dark border and offset shadow give the primary link a raised appearance.
- The hover and focus styles provide the same strong visual response for pointer and keyboard interaction.
- Save styles.css.
- Reload the page in Safari.
- Move the pointer over the Shop the harvest button.
The primary link looks like a raised gold button. Its background changes to brown during hover.
Button style not appearing?
Confirm that the primary link includes both btn and btn-honey classes. Check that the custom stylesheet loads after the Bootstrap stylesheet.
Ask for help checking the button classes and stylesheet order.
- Add the secondary link and artwork sizing below the button styles:
.text-link,
.site-footer a {
color: var(--brown);
font-weight: 800;
text-underline-offset: 0.25rem;
}
.hero-art {
width: min(100%, 520px);
justify-self: center;
}
@media (min-width: 768px) {
.hero-grid,
.impact-grid,
.order-grid {
grid-template-columns: 1.1fr 0.9fr;
}
}
How does the desktop layout form?
- The secondary link uses brown text with a clearer underline offset.
- The SVG fills its available space without growing beyond 520px.
- The media rule creates two hero columns once the viewport reaches 768px.
- Save styles.css.
- Reload the page in a wide Safari window.
The hero copy sits in the left column. The illustration sits beside it in the right column.
Illustration still below the copy?
Widen the Safari window beyond the media rule threshold. Confirm that the media rule contains the hero-grid selector.
Ask for help checking the desktop media rule.
- Finish the narrow navigation and reduced-motion behavior with these media rules:
@media (max-width: 991px) {
.nav-links-push {
margin: 1rem 0;
}
.basket-chip {
margin: 0 0 1rem;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
What do the final media rules handle?
- The first rule adjusts menu spacing when the responsive navigation collapses.
- The basket chip loses its desktop left margin inside the collapsed menu.
- The reduced-motion rule removes smooth scrolling when the visitor requests less motion.
- Save styles.css.
Before you reload the page, predict whether the navigation and hero will adapt to a narrow Safari window.
- Reload index.html in Safari.
- Resize the Safari window from wide to narrow.
- Use the navigation toggle at the narrower width.
You will see the navigation collapse at smaller widths. The hero stacks its copy above the titled bee-and-jar illustration while both action links remain visible.
Layout not adapting to the window?
Check that every media rule has matching braces. Confirm that the responsive viewport metadata remains in index.html.
Ask for help diagnosing the responsive layout.
✔️ Awesome, I've got everything!
Your responsive hero styling is complete. Save styles.css one final time.
ⓧ I'd like to double check the full code
:root {
--ink: #2f2a24;
--brown: #69452f;
--amber: #e99a12;
--gold: #ffc43d;
--cream: #fffaf0;
--soft-gold: #fff1c7;
--green: #315c45;
--white: #ffffff;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--ink);
background: var(--cream);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1,
h2,
h3,
.brand-mark {
line-height: 1.1;
font-weight: 800;
}
h1 {
max-width: 720px;
font-size: clamp(2.8rem, 8vw, 5.8rem);
letter-spacing: -0.055em;
}
h2 {
font-size: clamp(2rem, 5vw, 3.4rem);
letter-spacing: -0.035em;
}
.honey-nav {
background: rgba(255, 250, 240, 0.96);
border-bottom: 1px solid rgba(105, 69, 47, 0.16);
backdrop-filter: blur(12px);
}
.brand-mark {
color: var(--brown);
}
.nav-links-push {
margin-left: auto;
}
.nav-link {
color: var(--ink);
font-weight: 700;
}
.basket-chip {
display: inline-flex;
gap: 0.45rem;
align-items: center;
margin-left: 1rem;
padding: 0.45rem 0.75rem;
color: var(--white);
background: var(--brown);
border-radius: 999px;
font-weight: 800;
}
.hero-section {
padding: 5.5rem 0;
overflow: hidden;
background: radial-gradient(circle at top right, #ffe399 0, transparent 36%), var(--soft-gold);
}
.hero-grid,
.impact-grid,
.order-grid {
display: grid;
gap: 2.5rem;
align-items: center;
}
.hero-copy {
max-width: 620px;
font-size: 1.2rem;
}
.eyebrow {
margin-bottom: 0.75rem;
color: var(--green);
font-size: 0.78rem;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-top: 1.75rem;
}
.btn-honey {
padding: 0.8rem 1.1rem;
color: var(--ink);
background: var(--gold);
border: 2px solid var(--ink);
border-radius: 0.7rem;
font-weight: 900;
box-shadow: 4px 4px 0 var(--ink);
}
.btn-honey:hover,
.btn-honey:focus {
color: var(--white);
background: var(--brown);
border-color: var(--brown);
box-shadow: none;
}
.text-link,
.site-footer a {
color: var(--brown);
font-weight: 800;
text-underline-offset: 0.25rem;
}
.hero-art {
width: min(100%, 520px);
justify-self: center;
}
@media (min-width: 768px) {
.hero-grid,
.impact-grid,
.order-grid {
grid-template-columns: 1.1fr 0.9fr;
}
}
@media (max-width: 991px) {
.nav-links-push {
margin: 1rem 0;
}
.basket-chip {
margin: 0 0 1rem;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
That is the first big visual leap complete. Your local page now behaves like a storefront across wide and narrow layouts.
Your storefront now has a brand and a reason to keep scrolling. Next, you will give visitors three honey products to browse.
Add the Honey Catalog
Your hero introduces Honeybee Harvest, but visitors still have no products to browse. A clear catalog turns that promise into something they can see and compare.
You will use Bootstrap cards to give every product a consistent structure. You will also test the finished controls to discover what the storefront still needs.
In this step, get ready to:
- Build a responsive catalog with three illustrated honey products.
- Add a visible basket summary with accessible status feedback.
- Test what the product buttons currently do.
Build the product grid
A product catalog needs a predictable visual hierarchy. Each card will combine an illustration, product name, description, demo price, and action button.
Why Bootstrap cards?
Bootstrap cards provide a familiar structure for grouped content. The responsive grid changes from one column to three columns as the available width grows.
The h-100 class keeps the cards equal in height. Your custom CSS supplies the Honeybee Harvest identity.
- In index.html, locate the closing tag for the existing #home section.
- Create the catalog shell directly after that section by copying this block before </main>:
<section class="page-section" id="shop">
<div class="container">
<div class="section-heading">
<p class="eyebrow">Demo products and prices</p>
<h2>Choose your honey</h2>
<p>These fictional products are for a front-end demonstration only.</p>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
</div>
<aside class="basket-panel" aria-labelledby="basket-title">
<div>
<p class="eyebrow">Live JavaScript demo</p>
<h2 id="basket-title">Your basket</h2>
</div>
<ul id="basket-items">
<li>No honey selected yet.</li>
</ul>
<p id="basket-status" class="status-message" aria-live="polite"></p>
</aside>
</div>
</section>
What does this HTML do?
- The #shop section gives the navigation link a destination.
- The row row-cols-1 row-cols-md-3 g-4 classes create a responsive grid with consistent gaps.
- The #basket-items list provides the empty basket state.
- The #basket-status live region gives future basket updates a place to announce feedback.
- Save index.html.
- Reload index.html in Safari.
You should see the Choose your honey heading followed by an empty basket summary. This confirms that the new section sits inside the page correctly.
Catalog section missing?
Confirm that the new #shop section appears after the complete #home section. It must remain inside <main id="main-content">.
Check that every opening <div> has a matching closing tag.
Help me find why the catalog section is missing from my Honeybee Harvest page.
The grid is ready to receive complete product cards. Each card uses an original inline SVG jar, so the catalog does not depend on external image files.
- Place the Wildflower Honey card inside the empty responsive grid by copying this block between its opening and closing <div> tags:
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of wildflower honey">
<rect width="220" height="150" rx="24" fill="#fff1c7"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#f5bd35" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">WILDFLOWER</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Wildflower Honey</h3>
<p class="card-text">A mellow fictional blend with a floral finish.</p>
<p class="product-price">$12.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Wildflower Honey" data-price="12">Add to basket</button>
</div>
</article>
</div>
How is the first product structured?
- The article element treats the product as a self-contained item.
- The inline SVG draws the Wildflower Honey jar directly in the document.
- The data-product attribute stores the product name for the future basket logic.
- The numeric data-price attribute stores the demo price without currency symbols.
- Save index.html.
- Reload the page in Safari.
You should see a Wildflower Honey card with jar artwork, a demo price, and an Add to basket button.
First card outside the grid?
Make sure the card sits between <div class="row row-cols-1 row-cols-md-3 g-4"> and its closing </div>.
Help me fix the placement of my Wildflower Honey card.
- Add the Citrus Blossom card directly after the Wildflower Honey card by copying this block:
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of citrus blossom honey">
<rect width="220" height="150" rx="24" fill="#fff6d9"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#ffd76a" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">CITRUS</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Citrus Blossom</h3>
<p class="card-text">A bright fictional jar inspired by spring orchards.</p>
<p class="product-price">$14.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Citrus Blossom" data-price="14">Add to basket</button>
</div>
</article>
</div>
What changes in this card?
The card reuses the same accessible structure with product-specific artwork and copy. Its data-price="14" value keeps the machine-readable price separate from the visible demo label.
- Save index.html.
- Reload the page in Safari.
You should see Citrus Blossom beside Wildflower Honey when Safari is wide enough. The cards stack when the window is narrow.
Second card wrapping strangely?
Confirm that Citrus Blossom has its own outer <div class="col"> wrapper. Check that it appears after the first wrapper closes.
Help me fix the responsive placement of my Citrus Blossom card.
- Add the Forest Honey card directly after the Citrus Blossom card by copying this block:
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of forest honey">
<rect width="220" height="150" rx="24" fill="#f1ead8"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#3c3028"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#b87523" stroke="#3c3028" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#3c3028">FOREST</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Forest Honey</h3>
<p class="card-text">A dark, rich fictional variety for the demo catalog.</p>
<p class="product-price">$16.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Forest Honey" data-price="16">Add to basket</button>
</div>
</article>
</div>
How does the full grid work?
Each col wrapper gives Bootstrap one grid item. The three cards share the same content structure while keeping distinct names, artwork, and demo prices.
- Save index.html.
- Reload the page in Safari.
You should now see all three honey products. Their Bootstrap foundations are working before the custom catalog styles arrive.
Missing the Forest Honey card?
Check that the Forest Honey wrapper appears before the responsive grid's closing </div>.
Help me find why the Forest Honey card is missing from my catalog.
Use the second tab if you want to compare your complete index.html file with the catalog included.
✔️ Awesome, I've got everything!
Great. Save index.html before moving on to the catalog styles.
ⓧ I'd like to double check the full code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Honeybee Harvest is a fictional honey shop and bee education demo.">
<title>Honeybee Harvest</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="visually-hidden-focusable" href="#main-content">Skip to main content</a>
<header>
<nav class="navbar navbar-expand-lg honey-nav sticky-top" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand brand-mark" href="#home">Honeybee Harvest</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavigation" aria-controls="mainNavigation" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNavigation">
<ul class="navbar-nav nav-links-push">
<li class="nav-item"><a class="nav-link" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#shop">Shop</a></li>
<li class="nav-item"><a class="nav-link" href="#bees">Bees</a></li>
<li class="nav-item"><a class="nav-link" href="#order">Order</a></li>
</ul>
<span class="basket-chip">Basket <span id="basket-count">0</span></span>
</div>
</div>
</nav>
</header>
<main id="main-content">
<section class="hero-section" id="home">
<div class="container hero-grid">
<div>
<p class="eyebrow">Fictional local honey shop</p>
<h1>Golden honey, gathered with care</h1>
<p class="hero-copy">Explore small-batch demo products, build a sample basket, and discover simple ways to support pollinators.</p>
<div class="hero-actions">
<a class="btn btn-honey" href="#shop">Shop the harvest</a>
<a class="text-link" href="#bees">Why bees matter</a>
</div>
</div>
<svg class="hero-art" viewBox="0 0 520 420" role="img" aria-labelledby="heroArtTitle heroArtDescription">
<title id="heroArtTitle">Bee flying beside a honey jar</title>
<desc id="heroArtDescription">An original illustration with a striped bee, flowers, honeycomb cells, and a golden jar.</desc>
<defs>
<linearGradient id="honeyFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffd166"></stop>
<stop offset="1" stop-color="#e99a12"></stop>
</linearGradient>
</defs>
<circle cx="260" cy="210" r="182" fill="#fff3cf"></circle>
<g fill="none" stroke="#d78908" stroke-width="6" opacity="0.32">
<path d="M62 86l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M106 112l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M62 138l22-13 22 13v26l-22 13-22-13z"></path>
</g>
<g transform="translate(92 154)">
<ellipse cx="48" cy="31" rx="34" ry="24" fill="#ffc43d"></ellipse>
<rect x="32" y="9" width="9" height="44" rx="4" fill="#2f2a24"></rect>
<rect x="53" y="10" width="9" height="42" rx="4" fill="#2f2a24"></rect>
<circle cx="76" cy="28" r="15" fill="#2f2a24"></circle>
<circle cx="81" cy="23" r="2.5" fill="#fffaf0"></circle>
<ellipse cx="38" cy="4" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(-22 38 4)"></ellipse>
<ellipse cx="55" cy="2" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(18 55 2)"></ellipse>
<path d="M18 30H0" stroke="#2f2a24" stroke-width="4" stroke-linecap="round"></path>
</g>
<g transform="translate(235 120)">
<rect x="42" y="8" width="116" height="28" rx="8" fill="#4b382d"></rect>
<rect x="28" y="32" width="144" height="190" rx="34" fill="#f9e4a6" stroke="#4b382d" stroke-width="7"></rect>
<rect x="42" y="85" width="116" height="120" rx="24" fill="url(#honeyFill)"></rect>
<path d="M42 105c25 18 39-13 61 3 23 17 36-15 55 0v35H42z" fill="#ffd970"></path>
<rect x="50" y="118" width="100" height="56" rx="12" fill="#fff8e6"></rect>
<text x="100" y="142" text-anchor="middle" font-size="17" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">HONEYBEE</text>
<text x="100" y="162" text-anchor="middle" font-size="13" font-family="system-ui, sans-serif" fill="#76513a">HARVEST</text>
</g>
<g transform="translate(390 300)">
<circle cx="0" cy="0" r="13" fill="#ffc43d"></circle>
<circle cx="0" cy="-24" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
</g>
</svg>
</div>
</section>
<section class="page-section" id="shop">
<div class="container">
<div class="section-heading">
<p class="eyebrow">Demo products and prices</p>
<h2>Choose your honey</h2>
<p>These fictional products are for a front-end demonstration only.</p>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of wildflower honey">
<rect width="220" height="150" rx="24" fill="#fff1c7"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#f5bd35" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">WILDFLOWER</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Wildflower Honey</h3>
<p class="card-text">A mellow fictional blend with a floral finish.</p>
<p class="product-price">$12.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Wildflower Honey" data-price="12">Add to basket</button>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of citrus blossom honey">
<rect width="220" height="150" rx="24" fill="#fff6d9"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#ffd76a" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">CITRUS</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Citrus Blossom</h3>
<p class="card-text">A bright fictional jar inspired by spring orchards.</p>
<p class="product-price">$14.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Citrus Blossom" data-price="14">Add to basket</button>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of forest honey">
<rect width="220" height="150" rx="24" fill="#f1ead8"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#3c3028"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#b87523" stroke="#3c3028" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#3c3028">FOREST</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Forest Honey</h3>
<p class="card-text">A dark, rich fictional variety for the demo catalog.</p>
<p class="product-price">$16.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Forest Honey" data-price="16">Add to basket</button>
</div>
</article>
</div>
</div>
<aside class="basket-panel" aria-labelledby="basket-title">
<div>
<p class="eyebrow">Live JavaScript demo</p>
<h2 id="basket-title">Your basket</h2>
</div>
<ul id="basket-items">
<li>No honey selected yet.</li>
</ul>
<p id="basket-status" class="status-message" aria-live="polite"></p>
</aside>
</div>
</section>
</main>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>
Style and test the catalog
Bootstrap supplies the responsive structure. Your custom CSS now turns the generic cards into a warm storefront that matches the hero.
- Switch back to styles.css.
- Add the section spacing and product surfaces by copying this block before the existing media rules:
.page-section {
padding: 5rem 0;
}
.section-heading {
max-width: 680px;
margin-bottom: 2rem;
}
.product-card {
overflow: hidden;
background: var(--white);
border: 2px solid rgba(105, 69, 47, 0.18);
border-radius: 1.25rem;
box-shadow: 0 18px 40px rgba(75, 56, 45, 0.08);
}
.product-art {
width: 100%;
border-bottom: 2px solid rgba(105, 69, 47, 0.12);
}
What do these styles change?
- The section receives enough vertical space to feel separate from the hero.
- The heading stays narrow enough for comfortable reading.
- Each product card gains rounded corners, a light border, and a soft shadow.
- Each jar illustration stretches across the full card width.
- Save styles.css.
- Reload the page in Safari.
You should see three rounded white product cards with wide jar illustrations. The shop section now has clear spacing from the hero.
Cards still look plain?
Confirm that styles.css is saved. Check that each article still has the product-card class.
Help me find why my product card styles are not appearing.
The card body needs a flexible layout so every action button settles at the bottom. This keeps the three controls aligned even when the descriptions use different amounts of space.
- Add the product body and demo price styles below the product artwork styles by copying this block:
.product-body {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 1.4rem;
}
.product-body .btn {
margin-top: auto;
}
.product-price {
color: var(--green);
font-weight: 900;
}
Why use a flexible card body?
The vertical flex layout lets margin-top: auto consume the unused space above each button. The buttons stay aligned while every description keeps its natural length.
- Save styles.css.
- Reload the page in Safari.
You should see green demo prices and evenly aligned Add to basket buttons. The cards remain equal in height across the row.
Buttons not aligned?
Confirm that every card body uses both card-body and product-body.
Help me align the Add to basket buttons across my product cards.
The basket panel needs stronger contrast because it represents a changing state. Its dark surface separates selections from the product catalog.
- Add the basket panel styles below the product price styles by copying this block:
.basket-panel {
display: grid;
gap: 1rem;
margin-top: 2.5rem;
padding: 1.5rem;
color: var(--white);
background: var(--brown);
border-radius: 1.25rem;
}
.basket-panel .eyebrow {
color: #ffe399;
}
#basket-items {
margin: 0;
padding-left: 1.25rem;
}
.status-message,
.form-message {
min-height: 1.5rem;
margin: 0;
font-weight: 800;
}
How does the basket stay stable?
- The brown background creates a clear boundary between browsing and basket feedback.
- The empty list keeps its compact spacing inside the panel.
- The status area reserves space for future messages so the panel will not jump when feedback appears.
- Save styles.css.
- Reload the page in Safari.
You should see a dark brown basket panel below the catalog. It should contain the heading Your basket and the message No honey selected yet.
Basket panel has no dark background?
Confirm that the basket uses <aside class="basket-panel"> in index.html.
Help me find why my basket panel styles are not applying.
Use the second tab if you want to compare your complete styles.css file with the catalog styles included.
✔️ Awesome, I've got everything!
Your catalog styling is in place. Save styles.css before testing the page.
ⓧ I'd like to double check the full code
:root {
--ink: #2f2a24;
--brown: #69452f;
--amber: #e99a12;
--gold: #ffc43d;
--cream: #fffaf0;
--soft-gold: #fff1c7;
--green: #315c45;
--white: #ffffff;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--ink);
background: var(--cream);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1,
h2,
h3,
.brand-mark {
line-height: 1.1;
font-weight: 800;
}
h1 {
max-width: 720px;
font-size: clamp(2.8rem, 8vw, 5.8rem);
letter-spacing: -0.055em;
}
h2 {
font-size: clamp(2rem, 5vw, 3.4rem);
letter-spacing: -0.035em;
}
.honey-nav {
background: rgba(255, 250, 240, 0.96);
border-bottom: 1px solid rgba(105, 69, 47, 0.16);
backdrop-filter: blur(12px);
}
.brand-mark {
color: var(--brown);
}
.nav-links-push {
margin-left: auto;
}
.nav-link {
color: var(--ink);
font-weight: 700;
}
.basket-chip {
display: inline-flex;
gap: 0.45rem;
align-items: center;
margin-left: 1rem;
padding: 0.45rem 0.75rem;
color: var(--white);
background: var(--brown);
border-radius: 999px;
font-weight: 800;
}
.hero-section {
padding: 5.5rem 0;
overflow: hidden;
background: radial-gradient(circle at top right, #ffe399 0, transparent 36%), var(--soft-gold);
}
.hero-grid {
display: grid;
gap: 2.5rem;
align-items: center;
}
.hero-copy {
max-width: 620px;
font-size: 1.2rem;
}
.eyebrow {
margin-bottom: 0.75rem;
color: var(--green);
font-size: 0.78rem;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-top: 1.75rem;
}
.btn-honey {
padding: 0.8rem 1.1rem;
color: var(--ink);
background: var(--gold);
border: 2px solid var(--ink);
border-radius: 0.7rem;
font-weight: 900;
box-shadow: 4px 4px 0 var(--ink);
}
.btn-honey:hover,
.btn-honey:focus {
color: var(--white);
background: var(--brown);
border-color: var(--brown);
box-shadow: none;
}
.text-link {
color: var(--brown);
font-weight: 800;
text-underline-offset: 0.25rem;
}
.hero-art {
width: min(100%, 520px);
justify-self: center;
}
.page-section {
padding: 5rem 0;
}
.section-heading {
max-width: 680px;
margin-bottom: 2rem;
}
.product-card {
overflow: hidden;
background: var(--white);
border: 2px solid rgba(105, 69, 47, 0.18);
border-radius: 1.25rem;
box-shadow: 0 18px 40px rgba(75, 56, 45, 0.08);
}
.product-art {
width: 100%;
border-bottom: 2px solid rgba(105, 69, 47, 0.12);
}
.product-body {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 1.4rem;
}
.product-body .btn {
margin-top: auto;
}
.product-price {
color: var(--green);
font-weight: 900;
}
.basket-panel {
display: grid;
gap: 1rem;
margin-top: 2.5rem;
padding: 1.5rem;
color: var(--white);
background: var(--brown);
border-radius: 1.25rem;
}
.basket-panel .eyebrow {
color: #ffe399;
}
#basket-items {
margin: 0;
padding-left: 1.25rem;
}
.status-message,
.form-message {
min-height: 1.5rem;
margin: 0;
font-weight: 800;
}
@media (min-width: 768px) {
.hero-grid {
grid-template-columns: 1.1fr 0.9fr;
}
}
@media (max-width: 991px) {
.nav-links-push {
margin: 1rem 0;
}
.basket-chip {
margin: 0 0 1rem;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
The catalog now looks complete, so it is time to test the experience as a visitor. Start by checking how the cards respond to different screen widths.
- Reload index.html in Safari.
- Resize the Safari window from a wide desktop view to a narrow mobile view.
You should see three equal-height cards across the wide view. The cards should stack into one column in the narrow view.
Before you click a product button, what do you expect to happen to the basket count and summary?
- Click any Add to basket button.
The button receives the click, but the basket count remains at zero. The summary still says No honey selected yet.
Why did nothing change?
HTML gives the button its label and product data. CSS controls how the button looks.
The empty script.js file does not listen for the click yet. This intended shortfall proves that appearance alone does not create behavior.
You have built a responsive catalog and exposed the exact interaction gap the next step needs to solve. The storefront can now show products even though its basket is still waiting for logic.
Next up, you will connect the product buttons to JavaScript state. Each click will update the navigation count, group repeated products, and announce the latest selection.
Activate the Basket
Your three product cards now make the storefront look ready for shoppers. The inert buttons proved that polished markup alone cannot react to a click.
In this step, JavaScript will connect each click to basket state. A render function will turn that state into grouped quantities.
DOM events will update the visible count. A polite live region will name each added product.
In this step, get ready to:
- Select the basket controls and create state in script.js.
- Render grouped product quantities with an updated basket count.
- Connect product clicks to visible basket feedback.
Map the basket's moving parts
Each product button already carries its product name in data-product. The basket outputs already have stable identifiers for JavaScript to select.
- Switch back to Visual Studio Code.
- Select index.html in the Explorer sidebar.
- Find the first .add-to-basket button.
- Confirm its data-product value matches the product card title.
- Find #basket-count, #basket-items, and #basket-status.
These classes and identifiers form the bridge between the existing page and the new basket logic.
Build the basket renderer
The basket array records one product name for every click. renderBasket() converts those selections into the count and grouped list that visitors see.
- Select script.js in the Explorer sidebar.
- Create the basket state and renderer by replacing the empty file with this code:
const addButtons = document.querySelectorAll('.add-to-basket')
const basketCount = document.querySelector('#basket-count')
const basketItems = document.querySelector('#basket-items')
const basketStatus = document.querySelector('#basket-status')
const basket = []
function renderBasket() {
basketCount.textContent = basket.length
if (basket.length === 0) {
basketItems.innerHTML = '<li>No honey selected yet.</li>'
return
}
const quantities = basket.reduce((summary, product) => {
summary[product] = (summary[product] || 0) + 1
return summary
}, {})
basketItems.replaceChildren()
Object.entries(quantities).forEach(([product, quantity]) => {
const item = document.createElement('li')
item.textContent = `${product} x ${quantity}`
basketItems.append(item)
})
}
How Does the Renderer Work?
- The selector constants connect JavaScript to every product button and basket output.
- The basket array preserves one product name for every click.
- The empty-state check keeps the original basket message when the array has no products.
- The reduce() call groups matching product names into quantities.
- The final loop creates one list item for each grouped product.
- Save script.js.
- Return to the open Honeybee Harvest page in Safari.
- Reload index.html.
You should see the catalog load with a basket count of 0 and the original empty-basket message.
Page Not Loading Normally?
Check that every opening brace in renderBasket() has a matching closing brace. Confirm that script.js remains linked near the end of index.html.
Ask for help with the renderer syntax if Safari reports a JavaScript error: Help me compare my basket renderer with the required script structure.
Connect clicks to basket state
The renderer knows how to display basket state. A click listener now needs to read the selected product before asking that renderer to update the page.
- Add the click listeners below renderBasket() by copying this code:
addButtons.forEach((button) => {
button.addEventListener('click', () => {
const product = button.dataset.product
basket.push(product)
renderBasket()
basketStatus.textContent = `${product} added to the basket.`
})
})
What Do the Listeners Do?
- The outer loop attaches the same click behavior to every product button.
- The listener reads the clicked button's data-product value.
- The selected name enters the basket array before renderBasket() redraws the summary.
- The final line gives screen-reader users a polite update that names the added product.
Before you test the finished script, which parts of the page do you expect each click to update?
- Save script.js.
- Return to Safari.
- Reload index.html.
- Click Add to basket twice on the Wildflower Honey card.
- Click Add to basket once on the Citrus Blossom card.
You'll see the navigation basket count reach 3. The summary shows Wildflower Honey x 2 and Citrus Blossom x 1.
The status message names Citrus Blossom as the latest addition. That interaction gap is closed.
Basket Still Staying Empty?
Confirm that the listener block sits below renderBasket() in script.js. Check that the selector uses .add-to-basket exactly.
Make sure each product button still has a data-product value. Help me find why my Add to basket buttons remain inert.
✔️ Awesome, I've got everything!
Your saved basket script now selects the page elements, groups duplicate products, and responds to every product click.
ⓧ I'd like to double check the full code
const addButtons = document.querySelectorAll('.add-to-basket')
const basketCount = document.querySelector('#basket-count')
const basketItems = document.querySelector('#basket-items')
const basketStatus = document.querySelector('#basket-status')
const basket = []
function renderBasket() {
basketCount.textContent = basket.length
if (basket.length === 0) {
basketItems.innerHTML = '<li>No honey selected yet.</li>'
return
}
const quantities = basket.reduce((summary, product) => {
summary[product] = (summary[product] || 0) + 1
return summary
}, {})
basketItems.replaceChildren()
Object.entries(quantities).forEach(([product, quantity]) => {
const item = document.createElement('li')
item.textContent = `${product} x ${quantity}`
basketItems.append(item)
})
}
addButtons.forEach((button) => {
button.addEventListener('click', () => {
const product = button.dataset.product
basket.push(product)
renderBasket()
basketStatus.textContent = `${product} added to the basket.`
})
})
What Should the Full Script Contain?
This reference combines the four page selectors with the basket array. It also includes the grouped renderer and product click listeners.
Your storefront now responds to product choices with visible basket state. Next up, you'll complete the journey with bee education and a validated order enquiry.
Finish the Bee Story and Order Enquiry
Your product buttons now feed a live basket through JavaScript. The storefront can respond to shoppers, but their journey still ends at the basket.
The final sections give visitors context for supporting bees. They also provide a clear next action through an enquiry form that demonstrates validation without sending data or processing payments.
In this step, get ready to:
- Explain why pollinators matter and share practical ways to support them.
- Add a native-validated demo order enquiry form and a footer.
- Connect valid form submissions to an accessible confirmation message.
Add the bee story
Pollinators transfer pollen while visiting flowers for nectar or pollen. This process helps plants produce fruit or seed. The bee section connects that role to practical actions visitors can take.
- In index.html, locate the closing </section> for the #shop section.
- Insert the bee education section immediately below it by pasting this code:
<section class="page-section bee-section" id="bees">
<div class="container impact-grid">
<div>
<p class="eyebrow">More than honey</p>
<h2>Pollinators help plants produce fruit and seeds</h2>
<p>As pollinators move between flowers in search of nectar and pollen, they can transfer pollen that plants use to produce fruit or seed.</p>
</div>
<div class="impact-card">
<h3>Make space for bees</h3>
<ul>
<li>Plant native flowering species suited to your region.</li>
<li>Choose diverse plants that bloom across the growing season.</li>
<li>Limit or eliminate pesticide use where possible.</li>
</ul>
</div>
</div>
</section>
Why these actions support pollinators
The explanation connects flower visits to the transfer of pollen. That transfer can help plants produce fruit or seed.
The recommendations follow USDA guidance. Native plants provide locally suitable food sources. Diverse flowering species extend that support across the growing season. Limiting pesticide use reduces a potential source of harm.
- Save index.html.
- Reload the page in Safari.
- Select the Bees navigation link.
You should reach a new section with a pollinator explanation and three practical actions. The content now gives the product catalog a wider purpose.
Bee section missing?
Confirm that the new section sits inside <main id="main-content">. Check that its opening and closing section tags are both present.
Ask for help checking the bee section markup.
The new content needs the same visual hierarchy as the hero and catalog. A shared grid aligns the story beside its action card on wider screens.
- In styles.css, find the existing .hero-grid rule.
- Replace that entire rule with this shared layout rule:
.hero-grid,
.impact-grid,
.order-grid {
display: grid;
gap: 2.5rem;
align-items: center;
}
What does this layout rule do?
- The three selectors share one grid foundation across the hero, bee story, and enquiry sections.
- The gap keeps each section's columns separated.
- Centered alignment balances the text with the card or form beside it.
- Save styles.css.
- Reload Safari to confirm the bee copy and action card keep a consistent gap.
The bee section now follows the page's existing layout rhythm.
Bee content pressed together?
Check that each selector ends with a comma except .order-grid. Confirm that the declarations remain inside one pair of braces.
Ask for help checking the shared grid selector.
- In styles.css, locate the existing .status-message block.
- Add the bee section styles immediately below that block by pasting this code:
.bee-section {
color: var(--white);
background: var(--green);
}
.bee-section .eyebrow {
color: #ffe399;
}
.impact-card,
.order-form {
padding: 1.6rem;
color: var(--ink);
background: var(--white);
border-radius: 1.25rem;
box-shadow: 8px 8px 0 var(--gold);
}
.impact-card li + li {
margin-top: 0.75rem;
}
How does the bee section get its identity?
- The green section separates the educational story from the cream storefront.
- The white action card keeps the recommendations easy to scan.
- The gold shadow carries the honey palette into the new section.
- Save styles.css.
- Reload Safari to apply the bee section styles.
You should see white story text on a green background beside a raised white action card. The recommendations should have clear vertical spacing.
Bee section still looks plain?
Confirm that the HTML section uses class="page-section bee-section". Check that the recommendation container uses class="impact-card".
Ask for help matching the bee section classes to the stylesheet.
Add the order enquiry and footer
Native browser validation checks required fields before the form's submit event reaches your JavaScript. The form also states its static boundary clearly, so visitors understand that the demonstration does not send data or create an order.
- In index.html, locate the closing </section> for the #bees section.
- Insert the order enquiry immediately below it by pasting this code:
<section class="page-section" id="order">
<div class="container order-grid">
<div>
<p class="eyebrow">Complete the journey</p>
<h2>Send a demo order enquiry</h2>
<p>This static form demonstrates validation and feedback. It does not send data, process a payment, or create a real order.</p>
</div>
<form id="order-form" class="order-form">
<div>
<label class="form-label" for="customer-name">Name</label>
<input class="form-control" id="customer-name" name="name" type="text" autocomplete="name" required>
</div>
<div>
<label class="form-label" for="customer-email">Email</label>
<input class="form-control" id="customer-email" name="email" type="email" autocomplete="email" required>
</div>
<div>
<label class="form-label" for="customer-message">Enquiry</label>
<textarea class="form-control" id="customer-message" name="message" rows="4" required></textarea>
</div>
<button class="btn btn-honey" type="submit">Prepare enquiry</button>
<p id="form-message" class="form-message" aria-live="polite" hidden>Your demo enquiry is ready. A production site would now send it to a secure server.</p>
</form>
</div>
</section>
How does the form stay honest and accessible?
- Each label points to its matching field through the for and id values.
- The required attributes let the browser stop an incomplete submission.
- The hidden live message provides confirmation after a valid demonstration submission.
- The introductory copy makes the static sales boundary explicit.
- Save index.html.
- Reload the page in Safari.
- Select the Order navigation link.
Before you try the empty form, which required field do you think Safari will ask you to complete first?
- Select Prepare enquiry without entering any details.
Safari should keep you on the form and focus the empty name field with native required-field feedback. This confirms that incomplete enquiries cannot reach the submit listener.
Empty form submits anyway?
Confirm that all three controls include the required attribute. Check that the button uses type="submit".
Ask for help checking the form's native validation attributes.
- In index.html, locate the existing </main> line.
- Replace that line with the closing landmark and footer below:
</main>
<footer class="site-footer">
<div class="container footer-row">
<p>Honeybee Harvest, a fictional learning project.</p>
<a href="#home">Back to top</a>
</div>
</footer>
What does the footer complete?
The footer repeats the fictional-project boundary at the end of the page. Its link also gives visitors a direct route back to the hero.
- Save index.html.
- Reload Safari.
- Scroll to the bottom of the page.
You should see the fictional-project notice and a Back to top link below the enquiry form.
Footer inside the order form?
Confirm that the order section closes before </main>. The footer must begin after the main landmark closes.
Ask for help checking the closing tags around the footer.
✔️ Awesome, I've got everything!
Great. Save index.html before styling the form and footer.
ⓧ I'd like to double check the full code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Honeybee Harvest is a fictional honey shop and bee education demo.">
<title>Honeybee Harvest</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-sRIl4kxILFvY47J16cr9ZwB07vP4J8+LH7qKQnuqkuIAvNWLzeN8tE5YBujZqJLB" crossorigin="anonymous">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<a class="visually-hidden-focusable" href="#main-content">Skip to main content</a>
<header>
<nav class="navbar navbar-expand-lg honey-nav sticky-top" aria-label="Primary navigation">
<div class="container">
<a class="navbar-brand brand-mark" href="#home">Honeybee Harvest</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainNavigation" aria-controls="mainNavigation" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="mainNavigation">
<ul class="navbar-nav nav-links-push">
<li class="nav-item"><a class="nav-link" href="#home">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#shop">Shop</a></li>
<li class="nav-item"><a class="nav-link" href="#bees">Bees</a></li>
<li class="nav-item"><a class="nav-link" href="#order">Order</a></li>
</ul>
<span class="basket-chip">Basket <span id="basket-count">0</span></span>
</div>
</div>
</nav>
</header>
<main id="main-content">
<section class="hero-section" id="home">
<div class="container hero-grid">
<div>
<p class="eyebrow">Fictional local honey shop</p>
<h1>Golden honey, gathered with care</h1>
<p class="hero-copy">Explore small-batch demo products, build a sample basket, and discover simple ways to support pollinators.</p>
<div class="hero-actions">
<a class="btn btn-honey" href="#shop">Shop the harvest</a>
<a class="text-link" href="#bees">Why bees matter</a>
</div>
</div>
<svg class="hero-art" viewBox="0 0 520 420" role="img" aria-labelledby="heroArtTitle heroArtDescription">
<title id="heroArtTitle">Bee flying beside a honey jar</title>
<desc id="heroArtDescription">An original illustration with a striped bee, flowers, honeycomb cells, and a golden jar.</desc>
<defs>
<linearGradient id="honeyFill" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#ffd166"></stop>
<stop offset="1" stop-color="#e99a12"></stop>
</linearGradient>
</defs>
<circle cx="260" cy="210" r="182" fill="#fff3cf"></circle>
<g fill="none" stroke="#d78908" stroke-width="6" opacity="0.32">
<path d="M62 86l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M106 112l22-13 22 13v26l-22 13-22-13z"></path>
<path d="M62 138l22-13 22 13v26l-22 13-22-13z"></path>
</g>
<g transform="translate(92 154)">
<ellipse cx="48" cy="31" rx="34" ry="24" fill="#ffc43d"></ellipse>
<rect x="32" y="9" width="9" height="44" rx="4" fill="#2f2a24"></rect>
<rect x="53" y="10" width="9" height="42" rx="4" fill="#2f2a24"></rect>
<circle cx="76" cy="28" r="15" fill="#2f2a24"></circle>
<circle cx="81" cy="23" r="2.5" fill="#fffaf0"></circle>
<ellipse cx="38" cy="4" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(-22 38 4)"></ellipse>
<ellipse cx="55" cy="2" rx="18" ry="11" fill="#dff4ff" stroke="#75a9be" stroke-width="3" transform="rotate(18 55 2)"></ellipse>
<path d="M18 30H0" stroke="#2f2a24" stroke-width="4" stroke-linecap="round"></path>
</g>
<g transform="translate(235 120)">
<rect x="42" y="8" width="116" height="28" rx="8" fill="#4b382d"></rect>
<rect x="28" y="32" width="144" height="190" rx="34" fill="#f9e4a6" stroke="#4b382d" stroke-width="7"></rect>
<rect x="42" y="85" width="116" height="120" rx="24" fill="url(#honeyFill)"></rect>
<path d="M42 105c25 18 39-13 61 3 23 17 36-15 55 0v35H42z" fill="#ffd970"></path>
<rect x="50" y="118" width="100" height="56" rx="12" fill="#fff8e6"></rect>
<text x="100" y="142" text-anchor="middle" font-size="17" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">HONEYBEE</text>
<text x="100" y="162" text-anchor="middle" font-size="13" font-family="system-ui, sans-serif" fill="#76513a">HARVEST</text>
</g>
<g transform="translate(390 300)">
<circle cx="0" cy="0" r="13" fill="#ffc43d"></circle>
<circle cx="0" cy="-24" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-14" cy="20" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
<circle cx="-23" cy="-7" r="15" fill="#fffaf0" stroke="#d78908" stroke-width="3"></circle>
</g>
</svg>
</div>
</section>
<section class="page-section" id="shop">
<div class="container">
<div class="section-heading">
<p class="eyebrow">Demo products and prices</p>
<h2>Choose your honey</h2>
<p>These fictional products are for a front-end demonstration only.</p>
</div>
<div class="row row-cols-1 row-cols-md-3 g-4">
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of wildflower honey">
<rect width="220" height="150" rx="24" fill="#fff1c7"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#f5bd35" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">WILDFLOWER</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Wildflower Honey</h3>
<p class="card-text">A mellow fictional blend with a floral finish.</p>
<p class="product-price">$12.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Wildflower Honey" data-price="12">Add to basket</button>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of citrus blossom honey">
<rect width="220" height="150" rx="24" fill="#fff6d9"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#4b382d"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#ffd76a" stroke="#4b382d" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#4b382d">CITRUS</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Citrus Blossom</h3>
<p class="card-text">A bright fictional jar inspired by spring orchards.</p>
<p class="product-price">$14.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Citrus Blossom" data-price="14">Add to basket</button>
</div>
</article>
</div>
<div class="col">
<article class="card h-100 product-card">
<svg class="product-art" viewBox="0 0 220 150" role="img" aria-label="Illustrated jar of forest honey">
<rect width="220" height="150" rx="24" fill="#f1ead8"></rect>
<rect x="71" y="22" width="78" height="18" rx="5" fill="#3c3028"></rect>
<rect x="61" y="38" width="98" height="92" rx="22" fill="#b87523" stroke="#3c3028" stroke-width="5"></rect>
<rect x="72" y="67" width="76" height="38" rx="8" fill="#fffaf0"></rect>
<text x="110" y="91" text-anchor="middle" font-size="15" font-family="system-ui, sans-serif" font-weight="700" fill="#3c3028">FOREST</text>
</svg>
<div class="card-body product-body">
<h3 class="card-title">Forest Honey</h3>
<p class="card-text">A dark, rich fictional variety for the demo catalog.</p>
<p class="product-price">$16.00 demo price</p>
<button class="btn btn-honey add-to-basket" type="button" data-product="Forest Honey" data-price="16">Add to basket</button>
</div>
</article>
</div>
</div>
<aside class="basket-panel" aria-labelledby="basket-title">
<div>
<p class="eyebrow">Live JavaScript demo</p>
<h2 id="basket-title">Your basket</h2>
</div>
<ul id="basket-items">
<li>No honey selected yet.</li>
</ul>
<p id="basket-status" class="status-message" aria-live="polite"></p>
</aside>
</div>
</section>
<section class="page-section bee-section" id="bees">
<div class="container impact-grid">
<div>
<p class="eyebrow">More than honey</p>
<h2>Pollinators help plants produce fruit and seeds</h2>
<p>As pollinators move between flowers in search of nectar and pollen, they can transfer pollen that plants use to produce fruit or seed.</p>
</div>
<div class="impact-card">
<h3>Make space for bees</h3>
<ul>
<li>Plant native flowering species suited to your region.</li>
<li>Choose diverse plants that bloom across the growing season.</li>
<li>Limit or eliminate pesticide use where possible.</li>
</ul>
</div>
</div>
</section>
<section class="page-section" id="order">
<div class="container order-grid">
<div>
<p class="eyebrow">Complete the journey</p>
<h2>Send a demo order enquiry</h2>
<p>This static form demonstrates validation and feedback. It does not send data, process a payment, or create a real order.</p>
</div>
<form id="order-form" class="order-form">
<div>
<label class="form-label" for="customer-name">Name</label>
<input class="form-control" id="customer-name" name="name" type="text" autocomplete="name" required>
</div>
<div>
<label class="form-label" for="customer-email">Email</label>
<input class="form-control" id="customer-email" name="email" type="email" autocomplete="email" required>
</div>
<div>
<label class="form-label" for="customer-message">Enquiry</label>
<textarea class="form-control" id="customer-message" name="message" rows="4" required></textarea>
</div>
<button class="btn btn-honey" type="submit">Prepare enquiry</button>
<p id="form-message" class="form-message" aria-live="polite" hidden>Your demo enquiry is ready. A production site would now send it to a secure server.</p>
</form>
</div>
</section>
</main>
<footer class="site-footer">
<div class="container footer-row">
<p>Honeybee Harvest, a fictional learning project.</p>
<a href="#home">Back to top</a>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.8/dist/js/bootstrap.bundle.min.js" integrity="sha384-FKyoEForCGlyvwx9Hj09JcYn3nv7wiPVlz7YYwJrWVcXK/BmnVDxM+D2scQbITxI" crossorigin="anonymous"></script>
<script src="script.js"></script>
</body>
</html>
The form needs a clear card treatment and visible focus feedback. These styles make each field readable while preserving the page's amber visual system.
- In styles.css, replace the existing .status-message block with this shared feedback rule:
.status-message,
.form-message {
min-height: 1.5rem;
margin: 0;
font-weight: 800;
}
Why share the feedback rule?
The basket status and form confirmation both reserve space for short updates. Shared typography keeps those messages visually consistent.
- Add the form-specific styles after the .impact-card li + li block by pasting this code:
.order-form {
display: grid;
gap: 1rem;
border: 2px solid rgba(105, 69, 47, 0.2);
}
.form-control {
border: 2px solid rgba(105, 69, 47, 0.35);
}
.form-control:focus {
border-color: var(--amber);
box-shadow: 0 0 0 0.25rem rgba(233, 154, 18, 0.2);
}
.form-message {
padding: 0.8rem;
color: var(--green);
background: #e9f6ee;
border-radius: 0.7rem;
}
What do the form styles change?
- The grid gap separates each label and control group.
- The darker field borders make editable areas visible against the white card.
- The amber focus ring shows which control currently receives keyboard input.
- The confirmation message uses a pale green panel to signal completion.
- Save styles.css.
- Reload Safari to apply the enquiry styles.
- Move through the three form controls with the keyboard.
You should see a white form card with spaced controls. The active field should gain an amber focus ring.
Form styles not appearing?
Confirm that the form uses class="order-form". Check that every input and textarea uses class="form-control".
Ask for help matching the form classes to the CSS.
- Add the footer styles immediately after the .form-message block by pasting this code:
.site-footer {
padding: 1.5rem 0;
background: #f4e6c3;
}
.footer-row {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
}
.footer-row p {
margin: 0;
}
How does the footer stay responsive?
The flex row separates the project notice from the Back to top link. Wrapping lets those elements stack when horizontal space becomes tight.
- Save styles.css.
- Reload Safari to apply the footer styles.
You should see a cream footer with the notice and Back to top link separated across the available width.
Footer content not separating?
Confirm that the inner footer container uses class="container footer-row". Check that the .footer-row rule includes flex display.
Ask for help checking the footer layout.
The completed stylesheet also prepares the basket summary for richer controls. Its final responsive rule gives the hero, bee story, and enquiry matching desktop columns.
- Add the basket control classes immediately below the footer styles by pasting this code:
.basket-item-row {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.quantity-controls {
display: inline-flex;
gap: 0.5rem;
align-items: center;
}
.basket-total {
margin: 0;
font-size: 1.2rem;
font-weight: 900;
}
What are these basket classes for?
The classes define space for a product row, compact quantity controls, and a prominent total. They keep the finished stylesheet ready for the optional project extension.
- Find the existing @media (min-width: 768px) block near the bottom of styles.css.
- Replace that entire media block with this final responsive version:
@media (min-width: 768px) {
.hero-grid,
.impact-grid,
.order-grid {
grid-template-columns: 1.1fr 0.9fr;
}
.basket-panel {
grid-template-columns: 0.8fr 1.2fr;
align-items: start;
}
}
What changes on wider screens?
The three main content grids gain two columns at the same breakpoint. The basket panel also separates its heading from the item summary.
- Save styles.css.
- Reload Safari.
- Resize the browser from a narrow window to a wide window.
You should see the hero, bee story, and enquiry stack in narrow space. Each section should switch to two columns when the browser becomes wider.
Sections stay in one column?
Confirm that the grouped selectors sit inside the @media (min-width: 768px) block. Check that .impact-grid and .order-grid match the HTML classes exactly.
Ask for help checking the responsive grid rules.
✔️ Awesome, I've got everything!
Your finished visual system is in place. Save styles.css before connecting the confirmation message.
ⓧ I'd like to double check the full code
:root {
--ink: #2f2a24;
--brown: #69452f;
--amber: #e99a12;
--gold: #ffc43d;
--cream: #fffaf0;
--soft-gold: #fff1c7;
--green: #315c45;
--white: #ffffff;
}
html {
scroll-behavior: smooth;
}
body {
margin: 0;
color: var(--ink);
background: var(--cream);
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.6;
}
h1,
h2,
h3,
p {
margin-top: 0;
}
h1,
h2,
h3,
.brand-mark {
line-height: 1.1;
font-weight: 800;
}
h1 {
max-width: 720px;
font-size: clamp(2.8rem, 8vw, 5.8rem);
letter-spacing: -0.055em;
}
h2 {
font-size: clamp(2rem, 5vw, 3.4rem);
letter-spacing: -0.035em;
}
.honey-nav {
background: rgba(255, 250, 240, 0.96);
border-bottom: 1px solid rgba(105, 69, 47, 0.16);
backdrop-filter: blur(12px);
}
.brand-mark {
color: var(--brown);
}
.nav-links-push {
margin-left: auto;
}
.nav-link {
color: var(--ink);
font-weight: 700;
}
.basket-chip {
display: inline-flex;
gap: 0.45rem;
align-items: center;
margin-left: 1rem;
padding: 0.45rem 0.75rem;
color: var(--white);
background: var(--brown);
border-radius: 999px;
font-weight: 800;
}
.hero-section {
padding: 5.5rem 0;
overflow: hidden;
background: radial-gradient(circle at top right, #ffe399 0, transparent 36%), var(--soft-gold);
}
.hero-grid,
.impact-grid,
.order-grid {
display: grid;
gap: 2.5rem;
align-items: center;
}
.hero-copy {
max-width: 620px;
font-size: 1.2rem;
}
.eyebrow {
margin-bottom: 0.75rem;
color: var(--green);
font-size: 0.78rem;
font-weight: 900;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.hero-actions {
display: flex;
flex-wrap: wrap;
gap: 1rem;
align-items: center;
margin-top: 1.75rem;
}
.btn-honey {
padding: 0.8rem 1.1rem;
color: var(--ink);
background: var(--gold);
border: 2px solid var(--ink);
border-radius: 0.7rem;
font-weight: 900;
box-shadow: 4px 4px 0 var(--ink);
}
.btn-honey:hover,
.btn-honey:focus {
color: var(--white);
background: var(--brown);
border-color: var(--brown);
box-shadow: none;
}
.text-link,
.site-footer a {
color: var(--brown);
font-weight: 800;
text-underline-offset: 0.25rem;
}
.hero-art {
width: min(100%, 520px);
justify-self: center;
}
.page-section {
padding: 5rem 0;
}
.section-heading {
max-width: 680px;
margin-bottom: 2rem;
}
.product-card {
overflow: hidden;
background: var(--white);
border: 2px solid rgba(105, 69, 47, 0.18);
border-radius: 1.25rem;
box-shadow: 0 18px 40px rgba(75, 56, 45, 0.08);
}
.product-art {
width: 100%;
border-bottom: 2px solid rgba(105, 69, 47, 0.12);
}
.product-body {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 1.4rem;
}
.product-body .btn {
margin-top: auto;
}
.product-price {
color: var(--green);
font-weight: 900;
}
.basket-panel {
display: grid;
gap: 1rem;
margin-top: 2.5rem;
padding: 1.5rem;
color: var(--white);
background: var(--brown);
border-radius: 1.25rem;
}
.basket-panel .eyebrow {
color: #ffe399;
}
#basket-items {
margin: 0;
padding-left: 1.25rem;
}
.status-message,
.form-message {
min-height: 1.5rem;
margin: 0;
font-weight: 800;
}
.bee-section {
color: var(--white);
background: var(--green);
}
.bee-section .eyebrow {
color: #ffe399;
}
.impact-card,
.order-form {
padding: 1.6rem;
color: var(--ink);
background: var(--white);
border-radius: 1.25rem;
box-shadow: 8px 8px 0 var(--gold);
}
.impact-card li + li {
margin-top: 0.75rem;
}
.order-form {
display: grid;
gap: 1rem;
border: 2px solid rgba(105, 69, 47, 0.2);
}
.form-control {
border: 2px solid rgba(105, 69, 47, 0.35);
}
.form-control:focus {
border-color: var(--amber);
box-shadow: 0 0 0 0.25rem rgba(233, 154, 18, 0.2);
}
.form-message {
padding: 0.8rem;
color: var(--green);
background: #e9f6ee;
border-radius: 0.7rem;
}
.site-footer {
padding: 1.5rem 0;
background: #f4e6c3;
}
.footer-row {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: space-between;
}
.footer-row p {
margin: 0;
}
.basket-item-row {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
}
.quantity-controls {
display: inline-flex;
gap: 0.5rem;
align-items: center;
}
.basket-total {
margin: 0;
font-size: 1.2rem;
font-weight: 900;
}
@media (min-width: 768px) {
.hero-grid,
.impact-grid,
.order-grid {
grid-template-columns: 1.1fr 0.9fr;
}
.basket-panel {
grid-template-columns: 0.8fr 1.2fr;
align-items: start;
}
}
@media (max-width: 991px) {
.nav-links-push {
margin: 1rem 0;
}
.basket-chip {
margin: 0 0 1rem;
}
}
@media (prefers-reduced-motion: reduce) {
html {
scroll-behavior: auto;
}
}
Connect the valid submit
The browser already blocks incomplete submissions. Your final JavaScript handles the valid path by preventing navigation, clearing the fields, and revealing the confirmation message.
- In script.js, locate the declarations at the top of the file.
- Replace those declarations with this expanded group:
const addButtons = document.querySelectorAll('.add-to-basket')
const basketCount = document.querySelector('#basket-count')
const basketItems = document.querySelector('#basket-items')
const basketStatus = document.querySelector('#basket-status')
const orderForm = document.querySelector('#order-form')
const formMessage = document.querySelector('#form-message')
What do the new references hold?
- The orderForm reference points to the enquiry form.
- The formMessage reference points to the hidden live confirmation.
- Scroll to the end of script.js.
- Add the submit listener by pasting this code:
orderForm.addEventListener('submit', (event) => {
event.preventDefault()
orderForm.reset()
formMessage.hidden = false
})
What does the submit listener do?
- The listener runs after the form passes native validation.
- The preventDefault() call keeps the static page from navigating away.
- The reset() call clears the demonstration values.
- Setting hidden to false reveals the live confirmation.
- Save script.js.
- Reload index.html in Safari.
- Select each link in the responsive navigation.
You should move through the Home, Shop, Bees, and Order sections. The page now supports a complete path from product discovery to enquiry.
Before you test the valid path, what change do you expect after Safari accepts all three required fields?
- Enter Maya Chen in the Name field.
- Enter maya@example.com in the Email field.
- Enter Please prepare a demo honey enquiry. in the Enquiry field.
- Select Prepare enquiry.
You should remain on the same page. The fields should clear and a green confirmation should state that the demo enquiry is ready.
Confirmation stays hidden?
Confirm that the HTML uses id="order-form" and id="form-message". Check that both selector declarations appear before the submit listener.
Ask for help debugging the valid form submission.
✔️ Awesome, I've got everything!
Excellent. Your basket, bee story, and enquiry journey now work together on one responsive page.
ⓧ I'd like to double check the full code
const addButtons = document.querySelectorAll('.add-to-basket')
const basketCount = document.querySelector('#basket-count')
const basketItems = document.querySelector('#basket-items')
const basketStatus = document.querySelector('#basket-status')
const orderForm = document.querySelector('#order-form')
const formMessage = document.querySelector('#form-message')
const basket = []
function renderBasket() {
basketCount.textContent = basket.length
if (basket.length === 0) {
basketItems.innerHTML = '<li>No honey selected yet.</li>'
return
}
const quantities = basket.reduce((summary, product) => {
summary[product] = (summary[product] || 0) + 1
return summary
}, {})
basketItems.replaceChildren()
Object.entries(quantities).forEach(([product, quantity]) => {
const item = document.createElement('li')
item.textContent = `${product} x ${quantity}`
basketItems.append(item)
})
}
addButtons.forEach((button) => {
button.addEventListener('click', () => {
const product = button.dataset.product
basket.push(product)
renderBasket()
basketStatus.textContent = `${product} added to the basket.`
})
})
orderForm.addEventListener('submit', (event) => {
event.preventDefault()
orderForm.reset()
formMessage.hidden = false
})
You made it. Honeybee Harvest now tells a complete story through products, pollinator education, a responsive layout, and a clearly simulated enquiry journey.
Secret mission
Add Quantity Controls and a Live Total
The grouped basket shows what shoppers selected, but it leaves every quantity locked behind repeated Add clicks. Upgrade it with direct quantity controls and a live two-decimal demo total.
Clean Up Your Resources
Clean Up Your Resources
Your Honeybee Harvest page runs entirely from local files in Safari, so keeping it on your Mac creates no ongoing cost. Choose whether to keep the folder, close the preview for now, or delete the local files.
Resources you used:
- Your local honeybee-harvest folder.
Keep everything running
No action is needed. Choose this option if you want to continue testing the storefront or improving its design.
- Keep the honeybee-harvest folder on your Desktop.
- Leave the Honeybee Harvest page open in Safari.
Pause - I'll come back to this later
Close the local preview to clear your screen while keeping every project file ready for later.
- Close the Honeybee Harvest tab in Safari.
- Keep the honeybee-harvest folder on your Desktop.
Delete - I don't want to use this again
Deleting this project is permanent for the local files. Safari stays installed because the command targets only the honeybee-harvest folder.
- Close the Honeybee Harvest tab in Safari.
- Delete the honeybee-harvest folder from your Desktop by running this command:
rm -rf ~/Desktop/honeybee-harvest
What does this command remove?
This permanently removes the local honeybee-harvest folder plus every file inside it. The page has no cloud resources, so this action completes the teardown.
- Check your Desktop to confirm that the honeybee-harvest folder is gone.
Still See the Project Folder?
- Confirm that the folder is on your Desktop.
- Check that its name is exactly honeybee-harvest.
- Ask for help with safely removing the local project folder.
Nice Work!
Nice Work!
You did it! Honeybee Harvest now gives visitors a responsive journey from browsing honey through a live basket to preparing a validated demo enquiry.
You've learned how to:
- Built a responsive one-page storefront with semantic HTML. Composed its layout with Bootstrap 5.3.8. Gave it a distinct identity through custom CSS. Drew its bee-shop artwork with inline SVG.
- Turned inert product buttons into a live JavaScript basket. Rendered grouped quantities beside an updated navigation count. Announced each addition through an accessible status message.
- Added USDA-backed bee education with practical pollinator-support actions. Built an enquiry form with native browser validation. Returned a simulated confirmation while keeping the static demo clear about its sales boundary.
- Secret Mission: Refactored the basket into product records stored in a Map. Added quantity controls powered by event delegation. Calculated a live demo total from every product's price and quantity.
Ready to quiz yourself?