18 KiB
id, title, challengeType, dashedName, demoType
| id | title | challengeType | dashedName | demoType |
|---|---|---|---|---|
| 587d78af367417b2b2512b04 | Build a Product Landing Page | 14 | build-a-product-landing-page | onClick |
--description--
Fulfill the user stories below and get all the tests to pass to complete the lab.
User Stories:
- Your product landing page should have a
headerelement with a correspondingid="header". - You should have an image within the
headerelement with a correspondingid="header-img"(A logo would make a good image here). - Within the
#headerelement, you should have anavelement with a correspondingid="nav-bar". - You should have at least three clickable elements inside the
navelement, each with the classnav-link. - When you click a
.nav-linkbutton in thenavelement, you should be taken to the corresponding section of the landing page. - You should have an embedded product video with
id="video". - Your landing page should have a
formelement with a correspondingid="form". - Within the form, there should be an
inputfield withid="email"where you can enter an email address. - The
#emailinput field should have placeholder text to let users know what the field is for. - The
#emailinput field should use HTML5 validation to confirm that the entered text is an email address. - Within the form, there should be a submit
inputwith a correspondingid="submit". - When you click the
#submitelement, the email should be submitted to a static page (use this mock URL:https://www.freecodecamp.com/email-submit). - The navbar should always be at the top of the viewport.
- Your product landing page should have at least one media query.
- 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.
const el = document.getElementById('header')
assert(!!el && el.tagName === 'HEADER')
You should have an img element with an id of header-img.
const el = document.getElementById('header-img')
assert(!!el && el.tagName === 'IMG')
Your #header-img should be a descendant of the #header.
const els = document.querySelectorAll('#header #header-img')
assert(els.length > 0)
Your #header-img should have a src attribute.
const el = document.getElementById('header-img')
assert(!!el && !!el.src)
Your #header-img’s src value should be a valid URL (starts with http).
const el = document.getElementById('header-img');
assert(!!el && el.getAttribute('src') !== "" && /^http/.test(el.src));
You should have a nav element with an id of nav-bar.
const el = document.getElementById('nav-bar')
assert(!!el && el.tagName === 'NAV')
Your #nav-bar should be a descendant of the #header.
const els = document.querySelectorAll('#header #nav-bar')
assert(els.length > 0)
You should have at least 3 .nav-link elements within the #nav-bar.
const els = document.querySelectorAll('#nav-bar .nav-link')
assert(els.length >= 3)
Each .nav-link element should have an href attribute.
const els = document.querySelectorAll('.nav-link')
els.forEach(el => {
if (!el.href) assert(false)
})
assert(els.length > 0)
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).
const els = document.querySelectorAll('.nav-link')
els.forEach(el => {
const linkDestination = el.getAttribute('href').slice(1)
if (!document.getElementById(linkDestination)) assert(false)
})
assert(els.length > 0)
You should have a video or iframe element with an id of video.
const el = document.getElementById('video')
assert(!!el && (el.tagName === 'VIDEO' || el.tagName === 'IFRAME'))
Your #video should have a src attribute.
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(el.hasAttribute('src'));
You should have a form element with an id of form.
const el = document.getElementById('form')
assert(!!el && el.tagName === 'FORM')
You should have an input element with an id of email.
const el = document.getElementById('email')
assert(!!el && el.tagName === 'INPUT')
Your #email should be a descendant of the #form.
const els = document.querySelectorAll('#form #email')
assert(els.length > 0)
Your #email should have the placeholder attribute with placeholder text.
const el = document.getElementById('email')
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
Your #email should use HTML5 validation by setting its type to email.
const el = document.getElementById('email')
assert(!!el && el.type === 'email')
You should have an input element with an id of submit.
const el = document.getElementById('submit')
assert(!!el && el.tagName === 'INPUT')
Your #submit should be a descendant of the #form.
const els = document.querySelectorAll('#form #submit')
assert(els.length > 0)
Your #submit should have a type of submit.
const el = document.getElementById('submit')
assert(!!el && el.type === 'submit')
Your #form should have an action attribute of https://www.freecodecamp.com/email-submit.
const el = document.getElementById('form')
assert(!!el && el.action === 'https://www.freecodecamp.com/email-submit')
Your #email should have a name attribute of email.
const el = document.getElementById('email')
assert(!!el && el.name === 'email')
Your #nav-bar should always be at the top of the viewport.
(async () => {
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.
const htmlSourceAttr = Array.from(document.querySelectorAll('source')).map(el => el.getAttribute('media'))
const cssCheck = new __helpers.CSSHelp(document).getCSSRules('media')
assert(cssCheck.length > 0 || htmlSourceAttr.length > 0);
Your Product Landing Page should use CSS Flexbox at least once.
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(usesFlex || usesFlexMedia)
--seed--
--seed-contents--
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Product Landing Page</title>
</head>
<body>
</body>
</html>
--solutions--
<!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.com/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&controls=0&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>
/** 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;
}