---
id: 587d78af367417b2b2512b04
title: Build a Product Landing Page
challengeType: 25
dashedName: build-a-product-landing-page
demoType: onClick
saveSubmissionToDB: true
---

# --description--

**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.

**User Stories:**

1. Your product landing page should have a `header` element with a corresponding `id="header"`.
2. You should have an image within the `header` element with a corresponding `id="header-img"` (A logo would make a good image here).
3. Within the `#header` element, you should have a `nav` element with a corresponding `id="nav-bar"`.
4. You should have at least three clickable elements inside the `nav` element, each with the class `nav-link`.
5. When you click a `.nav-link` button in the `nav` element, you should be taken to the corresponding section of the landing page.
6. You should have an embedded product video with `id="video"`.
7. Your landing page should have a `form` element with a corresponding `id="form"`.
8. Within the form, there should be an `input` field with `id="email"` where you can enter an email address.
9. The `#email` input field should have placeholder text to let users know what the field is for.
10. The `#email` input field should use HTML5 validation to confirm that the entered text is an email address.
11. Within the form, there should be a submit `input` with a corresponding `id="submit"`.
12. When you click the `#submit` element, the email should be submitted to a static page (use this mock URL: `https://www.freecodecamp.org/email-submit`).
13. The navbar should always be at the top of the viewport.
14. Your product landing page should have at least one media query.
15. Your product landing page should utilize CSS flexbox at least once.

**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.

# --hints--

You should have a `header` element with an `id` of `header`.

```js
const el = document.getElementById('header');
assert.exists(el); 
assert.strictEqual(el.tagName, 'HEADER'); 
```

You should have an `img` element with an `id` of `header-img`.

```js
const el = document.getElementById('header-img');
assert.exists(el); 
assert.strictEqual(el.tagName, 'IMG');
```

Your `#header-img` should be a descendant of the `#header`.

```js
const els = document.querySelectorAll('#header #header-img');
assert.isNotEmpty(els);
```

Your `#header-img` should have a `src` attribute.

```js
const el = document.getElementById('header-img');
assert.exists(el);
assert.exists(el.src); 
```

Your `#header-img`'s `src` value should be a valid URL (starts with `http`).

```js
const el = document.getElementById('header-img');
assert.exists(el);
assert.isNotEmpty(el.getAttribute('src')); 
assert.match(el.src, /^http/);
```

You should have a `nav` element with an `id` of `nav-bar`.

```js
const el = document.getElementById('nav-bar');
assert.exists(el);
assert.strictEqual(el.tagName, "NAV");
```

Your `#nav-bar` should be a descendant of the `#header`.

```js
const els = document.querySelectorAll('#header #nav-bar');
assert.isNotEmpty(els);
```

You should have at least 3 `.nav-link` elements within the `#nav-bar`.

```js
const els = document.querySelectorAll('#nav-bar .nav-link');
assert.isAtLeast(els.length, 3); 
```

Each `.nav-link` element should have an `href` attribute.

```js
const els = document.querySelectorAll('.nav-link')
els.forEach(el => {
  assert.exists(el.href);
  assert.isNotEmpty(el.href);
})
assert.isNotEmpty(els); 
```

Each `.nav-link` element should link to a corresponding element on the landing page (has an `href` with a value of another element's id. e.g. `#footer`).

```js
const els = document.querySelectorAll('.nav-link')
els.forEach(el => {
  const linkDestination = el.getAttribute('href').slice(1)
  assert.exists(document.getElementById(linkDestination));
})
assert.isNotEmpty(els); 
```

You should have a `video` or `iframe` element with an `id` of `video`.

```js
const el = document.getElementById('video');
assert.exists(el); 
assert.oneOf(el.tagName, ['VIDEO','IFRAME']);
```

Your `#video` should have a `src` attribute.

```js
let el = document.getElementById('video')
const sourceNode = el.children;
let sourceElement = null;
if (sourceNode.length) {
  sourceElement = [...video.children].filter(el => el.localName === 'source')[0];
}
if (sourceElement) {
  el = sourceElement;
}
assert.isTrue(el.hasAttribute('src'));
```

You should have a `form` element with an `id` of `form`.

```js
const el = document.getElementById('form');
assert.exists(el); 
assert.strictEqual(el.tagName, "FORM"); 
```

You should have an `input` element with an `id` of `email`.

```js
const el = document.getElementById('email');
assert.exists(el); 
assert.strictEqual(el.tagName, "INPUT"); 
```

Your `#email` should be a descendant of the `#form`.

```js
const els = document.querySelectorAll('#form #email')
assert.isNotEmpty(els);
```

Your `#email` should have the `placeholder` attribute with placeholder text.

```js
const el = document.getElementById('email');
assert.exists(el); 
assert.exists(el.placeholder); 
assert.isNotEmpty(el.placeholder); 
```

Your `#email` should use HTML5 validation by setting its `type` to `email`.

```js
const el = document.getElementById('email');
assert.exists(el); 
assert.strictEqual(el.type, "email");
```

You should have an `input` element with an `id` of `submit`.

```js
const el = document.getElementById('submit');
assert.exists(el); 
assert.strictEqual(el.tagName, "INPUT");
```

Your `#submit` should be a descendant of the `#form`.

```js
const els = document.querySelectorAll('#form #submit');
assert.isNotEmpty(els);
```

Your `#submit` should have a `type` of `submit`.

```js
const el = document.getElementById('submit');
assert.exists(el); 
assert.strictEqual(el.type, 'submit');
```

Your `#form` should have an `action` attribute of `https://www.freecodecamp.org/email-submit`.

```js
const el = document.getElementById('form');
assert.exists(el); 
assert.strictEqual(el.action, 'https://www.freecodecamp.org/email-submit');
```

Your `#email` should have a `name` attribute of `email`.

```js
const el = document.getElementById('email');
assert.exists(el); 
assert.strictEqual(el.name, 'email');
```

Your `#nav-bar` should always be at the top of the viewport.

```js
  const timeout = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));

  const header = document.getElementById('header');
  const headerChildren = header.children;
  const navbarCandidates = [header, ...headerChildren];
  
  // Return smallest top position of all navbar candidates
  const getNavbarPosition = (candidates = []) => {
    return candidates.reduce(
      (min, candidate) =>
        Math.min(min, Math.abs(candidate?.getBoundingClientRect().top)),
      Infinity
    );
  };
  assert.approximately(
    getNavbarPosition(navbarCandidates),
    0,
    15,
    '#header or one of its children should be at the top of the viewport '
  );

  window.scroll(0, 500);
  await timeout(1);

  assert.approximately(
    getNavbarPosition(navbarCandidates),
    0,
    15,
    '#header or one of its children should be at the top of the ' +
      'viewport even after scrolling '
  );
    
  window.scroll(0, 0);
```

Your Product Landing Page should use at least one media query.

```js
const htmlSourceAttr = Array.from(document.querySelectorAll('source')).map(el => el.getAttribute('media'))
const cssCheck = new __helpers.CSSHelp(document).getCSSRules('media');
assert.isTrue(cssCheck.length > 0 || htmlSourceAttr.length > 0);
```

Your Product Landing Page should use CSS Flexbox at least once.

```js
const hasFlex = (rule) => ["flex", "inline-flex"].includes(rule.style?.display)
const stylesheet = new __helpers.CSSHelp(document).getStyleSheet()
const cssRules = new __helpers.CSSHelp(document).styleSheetToCssRulesArray(stylesheet)
const mediaRules = new __helpers.CSSHelp(document).getCSSRules('media')
const usesFlex = cssRules.find(rule => hasFlex(rule))
const usesFlexMedia = mediaRules.find(mediaRule => {
  return [...mediaRule.cssRules].find(rule => hasFlex(rule))
})
assert.exists(usesFlex || usesFlexMedia);
```

# --seed--

## --seed-contents--

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Product Landing Page</title>
  </head>

<body>

</body>

</html>
```

```css

```

# --solutions--

```html
<!DOCTYPE html>
<html>

<head>
    <link rel="stylesheet" href="styles.css" />
    <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.8.1/css/all.css"
        integrity="sha384-50oBUHEmvpQ+1lW4y57PTFmhCaXp0ML5d60M1M7uH2+nqUivzIebhndOJK28anvf" crossorigin="anonymous" />
</head>

<body>
    <div id="page-wrapper">
        <header id="header">
            <div class="logo">
                <img id="header-img"
                    src="https://cdn.freecodecamp.org/testable-projects-fcc/images/product-landing-page-logo.png"
                    alt="original trombones logo" />
            </div>

            <nav id="nav-bar">
                <ul>
                    <li><a class="nav-link" href="#features">Features</a></li>
                    <li><a class="nav-link" href="#how-it-works">How It Works</a></li>
                    <li><a class="nav-link" href="#pricing">Pricing</a></li>
                </ul>
            </nav>
        </header>

        <div class="container"></div>

        <section id="hero">
            <h2>Handcrafted, home-made masterpieces</h2>
            <form id="form" action="https://www.freecodecamp.org/email-submit">
                <input name="email" id="email" type="email" placeholder="Enter your email address" required />
                <input id="submit" type="submit" value="Get Started" class="btn" />
            </form>
        </section>

        <div class="container">
            <section id="features">
                <div class="grid">
                    <div class="icon"><i class="fa fa-3x fa-fire"></i></div>
                    <div class="desc">
                        <h2>Premium Materials</h2>
                        <p>
                            Our trombones use the shiniest brass which is sourced locally.
                            This will increase the longevity of your purchase.
                        </p>
                    </div>
                </div>
                <div class="grid">
                    <div class="icon"><i class="fa fa-3x fa-truck"></i></div>
                    <div class="desc">
                        <h2>Fast Shipping</h2>
                        <p>
                            We make sure you receive your trombone as soon as we have
                            finished making it. We also provide free returns if you are not
                            satisfied.
                        </p>
                    </div>
                </div>
                <div class="grid">
                    <div class="icon">
                        <i class="fa fa-3x fa-battery-full" aria-hidden="true"></i>
                    </div>
                    <div class="desc">
                        <h2>Quality Assurance</h2>
                        <p>
                            For every purchase you make, we will ensure there are no damages
                            or faults and we will check and test the pitch of your
                            instrument.
                        </p>
                    </div>
                </div>
            </section>
            <section id="how-it-works">
                <iframe id="video" height="315"
                    src="https://www.youtube-nocookie.com/embed/y8Yv4pnO7qc?rel=0&amp;controls=0&amp;showinfo=0"
                    frameborder="0" allowfullscreen></iframe>
            </section>
            <section id="pricing">
                <div class="product" id="tenor">
                    <div class="level">Tenor Trombone</div>
                    <h2>$600</h2>
                    <ol>
                        <li>Good for beginners</li>
                        <li>Excellent sound quality</li>
                        <li>Great for Jazz Bands</li>
                        <li>Nice and shiny</li>                        
                    </ol>
                    <button class="btn">Select</button>
                </div>
                <div class="product" id="bass">
                    <div class="level">Bass Trombone</div>
                    <h2>$900</h2>
                    <ol>
                        <li>Sound quality is unmatched</li>
                        <li>Best for professionals</li>
                        <li>Absolutely stunning</li>
                        <li>Durable and long lasting</li>
                    </ol>
                    <button class="btn">Select</button>
                </div>
                <div class="product" id="valve">
                    <div class="level">Valve Trombone</div>
                    <h2>$1200</h2>
                    <ol>
                        <li>Plays similar to a Trumpet</li>
                        <li>Great for Jazz Bands</li>
                        <li>Beautiful in sound and appearance</li>
                        <li>Just amazing</li>
                    </ol>
                    <button class="btn">Select</button>
                </div>
            </section>
            <footer>
                <ul>
                    <li><a href="#">Privacy</a></li>
                    <li><a href="#">Terms</a></li>
                    <li><a href="#">Contact</a></li>
                </ul>
                <span>Copyright 2016, Original Trombones</span>
            </footer>
        </div>
    </div>
</body>

</html>
```

```css
/** global element styling **/

@import 'https://fonts.googleapis.com/css?family=Lato:400,700';

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    background-color: #eee;
    font-family: 'Lato', sans-serif;
}

#page-wrapper {
    position: relative;
}

li {
    list-style: none;
}

a {
    color: #000;
    text-decoration: none;
}

/** global classes styling **/

.container {
    max-width: 1000px;
    width: 100%;
    margin: 0 auto;
}

.btn {
    padding: 0 20px;
    height: 40px;
    font-size: 1em;
    font-weight: 900;
    text-transform: uppercase;
    border: 3px black solid;
    border-radius: 2px;
    background: transparent;
    cursor: pointer;
}

.grid {
    display: flex;
}

header {
    position: fixed;
    top: 0;
    min-height: 75px;
    padding: 0px 20px;
    display: flex;
    justify-content: space-around;
    align-items: center;
    background-color: #eee;
}

@media (max-width: 600px) {
    header {
        flex-wrap: wrap;
    }
}

.logo {
    width: 60vw;
}

@media (max-width: 650px) {
    .logo {
        margin-top: 15px;
        width: 100%;
        position: relative;
    }
}

.logo>img {
    width: 100%;
    height: 100%;
    max-width: 300px;
    display: flex;
    justify-content: center;
    align-items: center;
    text-align: center;
    margin-left: 20px;
}

@media (max-width: 650px) {
    .logo>img {
        margin: 0 auto;
    }
}

nav {
    font-weight: 400;
}

@media (max-width: 650px) {
    nav {
        margin-top: 10px;
        width: 100%;
        display: flex;
        flex-direction: column;
        align-items: center;
        text-align: center;
        padding: 0 50px;
    }

    nav li {
        padding-bottom: 5px;
    }
}

nav>ul {
    width: 35vw;
    display: flex;
    flex-direction: row;
    justify-content: space-around;
}

@media (max-width: 650px) {
    nav>ul {
        flex-direction: column;
    }
}

#hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    height: 200px;
    margin-top: 50px;
}

#hero>h2 {
    margin-bottom: 20px;
    word-wrap: break-word;
}

#hero input[type='email'] {
    max-width: 275px;
    width: 100%;
    padding: 5px;
}

#hero input[type='submit'] {
    max-width: 150px;
    width: 100%;
    height: 30px;
    margin: 15px 0;
    border: 0;
    background-color: #f1c40f;
}

#hero input[type='submit']:hover {
    background-color: orange;
    transition: background-color 1s;
}

@media (max-width: 650px) {
    #hero {
        margin-top: 120px;
    }
}

#features {
    margin-top: 30px;
}

#features .icon {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 125px;
    width: 20vw;
    color: darkorange;
}

@media (max-width: 550px) {
    #features .icon {
        display: none;
    }
}

#features .desc {
    display: flex;
    flex-direction: column;
    justify-content: center;
    height: 125px;
    width: 80vw;
    padding: 5px;
}

@media (max-width: 550px) {
    #features .desc {
        width: 100%;
        text-align: center;
        padding: 0;
        height: 150px;
    }
}

@media (max-width: 650px) {
    #features {
        margin-top: 0;
    }
}

#how-it-works {
    margin-top: 50px;
    display: flex;
    justify-content: center;
}

#how-it-works>iframe {
    max-width: 560px;
    width: 100%;
}

#pricing {
    margin-top: 60px;
    display: flex;
    flex-direction: row;
    justify-content: center;
}

.product {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    width: calc(100% / 3);
    margin: 10px;
    border: 1px solid #000;
    border-radius: 3px;
}

.product>.level {
    background-color: #ddd;
    color: black;
    padding: 15px 0;
    width: 100%;
    text-transform: uppercase;
    font-weight: 700;
}

.product>h2 {
    margin-top: 15px;
}

.product>ol {
    margin: 15px 0;
}

.product>ol>li {
    padding: 5px 0;
}

.product>button {
    border: 0;
    margin: 15px 0;
    background-color: #f1c40f;
    font-weight: 400;
}

.product>button:hover {
    background-color: orange;
    transition: background-color 1s;
}

@media (max-width: 800px) {
    #pricing {
        flex-direction: column;
    }

    .product {
        max-width: 300px;
        width: 100%;
        margin: 0 auto;
        margin-bottom: 10px;
    }
}

footer {
    margin-top: 30px;
    background-color: #ddd;
    padding: 20px;
}

footer>ul {
    display: flex;
    justify-content: flex-end;
}

footer>ul>li {
    padding: 0 10px;
}

footer>span {
    margin-top: 5px;
    display: flex;
    justify-content: flex-end;
    font-size: 0.9em;
    color: #444;
}
```
