mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(curriculum): add blog post card lab (#56165)
Co-authored-by: Jessica Wilkins <67210629+jdwilkin4@users.noreply.github.com> Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
9d4f63920b
commit
245b486f63
@@ -2026,7 +2026,12 @@
|
||||
"In these lecture videos, you will learn about working with backgrounds and borders."
|
||||
]
|
||||
},
|
||||
"pahx": { "title": "45", "intro": [] },
|
||||
"lab-blog-post-card": {
|
||||
"title": "Design a Blog Post Card",
|
||||
"intro": [
|
||||
"In this lab, you'll design a blog post card using HTML and CSS"
|
||||
]
|
||||
},
|
||||
"review-css-backgrounds-and-borders": {
|
||||
"title": "CSS Backgrounds and Borders Review",
|
||||
"intro": [
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Design a Blog Post Card
|
||||
block: lab-blog-post-card
|
||||
superBlock: full-stack-developer
|
||||
---
|
||||
|
||||
## Introduction to the Design a Blog Post Card
|
||||
|
||||
In this lab, you'll design a blog post card using HTML and CSS
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Design a Blog Post Card",
|
||||
"isUpcomingChange": true,
|
||||
"usesMultifileEditor": true,
|
||||
"dashedName": "lab-blog-post-card",
|
||||
"superBlock": "full-stack-developer",
|
||||
"challengeOrder": [{ "id": "66eaddd04a9e533fba689001", "title": "Design a Blog Post Card" }],
|
||||
"helpCategory": "HTML-CSS",
|
||||
"blockType": "lab",
|
||||
"blockLayout": "link"
|
||||
}
|
||||
+390
@@ -0,0 +1,390 @@
|
||||
---
|
||||
id: 66eaddd04a9e533fba689001
|
||||
title: Design a Blog Post Card
|
||||
challengeType: 14
|
||||
dashedName: lab-blog-post-card
|
||||
demoType: onClick
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this lab, you'll practice how to style backgrounds and borders by creating a blog post card.
|
||||
|
||||
**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
1. You should have a `div` element with a class of `blog-post-card` to hold all of your card elements.
|
||||
2. Within the `.blog-post-card` `div` element, you should have an image element with a valid `alt` attribute and text, and a class of `post-img`. You can use `https://cdn.freecodecamp.org/curriculum/labs/cover-photo.jpg` for the `src` attribute of the image.
|
||||
3. You should have a `div` with a class of `post-content` within the `.blog-post-card` `div` element with the following:
|
||||
- An `h2` element with a class of `post-title` and text for the title of your blog post.
|
||||
- A `p` element with a class of `post-excerpt` and text to summarize your blog post.
|
||||
- An `a` element with a class of `read-more` with the text `Read More`.
|
||||
4. You should apply the following styles to the `.blog-post-card` element:
|
||||
- A white background.
|
||||
- Rounded corners.
|
||||
- A width of your choice.
|
||||
- The text alignment of your choice.
|
||||
5. The `.post-img` element should be styled so that the image fills the card's entire width and has a bottom border.
|
||||
6. The `.post-content` element should be styled so that there is padding inside the card.
|
||||
7. The `.post-title` and `.post-excerpt` elements should have a text color other than the default and margins on all sides.
|
||||
8. The `.read-more` element should be styled like a button and have:
|
||||
- A text color other than the default.
|
||||
- A background color.
|
||||
- Margins on all sides.
|
||||
- Padding on all sides.
|
||||
- Rounded corners.
|
||||
- A `display` property set to `inline-block`.
|
||||
- A hover effect that changes its background color.
|
||||
|
||||
**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have a `div` with a class of `blog-post-card`.
|
||||
|
||||
```js
|
||||
assert.exists(document.querySelector("div.blog-post-card"));
|
||||
```
|
||||
|
||||
You should have an `img` element with a `class` of `post-img`. Make sure your image has an `alt` attribute with text and a `src` attribute with a value.
|
||||
|
||||
```js
|
||||
const card = document.querySelector("div.blog-post-card");
|
||||
assert.exists(card);
|
||||
|
||||
const img = card.querySelector("img.post-img");
|
||||
assert.exists(img);
|
||||
assert.isString(img.alt);
|
||||
assert.isNotEmpty(img.alt);
|
||||
assert.isTrue(img.hasAttribute("src"));
|
||||
assert.isNotEmpty(img.getAttribute("src"));
|
||||
```
|
||||
|
||||
You should have a `div` with class of `post-content`.
|
||||
|
||||
```js
|
||||
const card = document.querySelector("div.blog-post-card");
|
||||
assert.exists(card);
|
||||
const content = card.querySelector("div.post-content");
|
||||
assert.exists(content);
|
||||
```
|
||||
|
||||
You should have an `h2` element with a class of `post-title`. Make sure it has some text content for the title of your blog post.
|
||||
|
||||
```js
|
||||
const card = document.querySelector("div.blog-post-card");
|
||||
assert.exists(card);
|
||||
|
||||
const content = card.querySelector("div.post-content");
|
||||
assert.exists(content);
|
||||
|
||||
const postTitle = content.querySelector("h2.post-title");
|
||||
assert.exists(postTitle);
|
||||
assert.isString(postTitle.textContent);
|
||||
assert.isNotEmpty(postTitle.textContent);
|
||||
```
|
||||
|
||||
You should have a `p` with a class of `post-excerpt`. Make sure it has some text content to summarize your blog post.
|
||||
|
||||
```js
|
||||
const card = document.querySelector("div.blog-post-card");
|
||||
assert.exists(card);
|
||||
|
||||
const content = card.querySelector("div.post-content");
|
||||
assert.exists(content);
|
||||
|
||||
const postExcerpt = content.querySelector("p.post-excerpt");
|
||||
assert.exists(postExcerpt);
|
||||
```
|
||||
|
||||
You should have an `a` element with a `class` of `read-more`.
|
||||
|
||||
```js
|
||||
const card = document.querySelector("div.blog-post-card");
|
||||
assert.exists(card);
|
||||
|
||||
const content = card.querySelector("div.post-content");
|
||||
assert.exists(content);
|
||||
|
||||
const readMore = content.querySelector("a.read-more");
|
||||
assert.exists(readMore);
|
||||
```
|
||||
|
||||
Your `.read-more` element should have the text `Read More`.
|
||||
|
||||
```js
|
||||
const el = document.querySelector("div.post-content a.read-more");
|
||||
assert.exists(el);
|
||||
assert.strictEqual(el.textContent, "Read More");
|
||||
```
|
||||
|
||||
Your `.blog-post-card` element should have a `border-radius` property with a value (should not be `0` or a negative value).
|
||||
|
||||
```js
|
||||
const card = document.querySelector('.blog-post-card');
|
||||
assert.exists(card);
|
||||
|
||||
const borderRadius = getComputedStyle(card).borderRadius;
|
||||
assert.isAbove(parseInt(borderRadius), 0);
|
||||
```
|
||||
|
||||
Your `.blog-post-card` element should have a white background.
|
||||
|
||||
```js
|
||||
const card = document.querySelector('.blog-post-card');
|
||||
assert.exists(card);
|
||||
|
||||
const backgroundColor = getComputedStyle(card).backgroundColor;
|
||||
const whiteColors = ['rgb(255, 255, 255)', 'rgba(255, 255, 255, 1)', '#ffffff', '#fff', 'white'];
|
||||
|
||||
assert.oneOf(backgroundColor.toLowerCase(), whiteColors);
|
||||
```
|
||||
|
||||
You should target `.blog-post-card` and set its `width` property.
|
||||
|
||||
```js
|
||||
assert.isNotEmpty(new __helpers.CSSHelp(document).getStyle(".blog-post-card")?.getPropertyValue("width"));
|
||||
```
|
||||
|
||||
You should target `.blog-post-card` and set its `text-align` property.
|
||||
|
||||
```js
|
||||
assert.isNotEmpty(new __helpers.CSSHelp(document).getStyle(".blog-post-card")?.getPropertyValue("text-align"));
|
||||
```
|
||||
|
||||
You should target `.post-content` and set its `padding` property.
|
||||
|
||||
```js
|
||||
assert.isNotEmpty(new __helpers.CSSHelp(document).getStyle(".post-content")?.getPropertyValue("padding"));
|
||||
```
|
||||
|
||||
Your `.read-more` element should have a hover effect.
|
||||
|
||||
```js
|
||||
const link = document.querySelector('.read-more');
|
||||
assert.exists(link);
|
||||
|
||||
const stylesheet = [...document.styleSheets].find(sheet => [...sheet.rules].some(rule => rule.selectorText === '.read-more:hover'));
|
||||
assert.exists(stylesheet);
|
||||
|
||||
const hoverRule = [...stylesheet.rules].find(rule => rule.selectorText === '.read-more:hover');
|
||||
const hoverBackgroundColor = hoverRule.style.backgroundColor;
|
||||
assert.isNotEmpty(hoverBackgroundColor);
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `color` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMoreTextColor = getComputedStyle(readMore).color;
|
||||
assert.notStrictEqual(readMoreTextColor, 'rgba(0, 0, 0, 0)');
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `background-color` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMoreBackgroundColor = getComputedStyle(readMore).backgroundColor;
|
||||
assert.notStrictEqual(readMoreBackgroundColor, 'rgba(0, 0, 0, 0)');
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `margin` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMoreMargin = getComputedStyle(readMore).marginTop;
|
||||
assert.notStrictEqual(readMoreMargin, '0px');
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `display` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMoreDisplay = getComputedStyle(readMore).display;
|
||||
assert.strictEqual(readMoreDisplay, 'inline-block');
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `border-radius` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMoreBorderRadius = getComputedStyle(readMore).borderRadius;
|
||||
assert.notStrictEqual(readMoreBorderRadius, '0px');
|
||||
```
|
||||
|
||||
You should target `.read-more` and set its `padding` property.
|
||||
|
||||
```js
|
||||
const readMore = document.querySelector('.read-more');
|
||||
assert.exists(readMore);
|
||||
|
||||
const readMorePadding = getComputedStyle(readMore).padding;
|
||||
assert.notStrictEqual(readMorePadding, '0px');
|
||||
```
|
||||
|
||||
Your `.post-img` element should fill the card's `width` and have a `border-bottom` value.
|
||||
|
||||
```js
|
||||
const postImg = document.querySelector('.post-img');
|
||||
assert.exists(postImg);
|
||||
|
||||
const imgWidth = getComputedStyle(postImg).width;
|
||||
const cardWidth = getComputedStyle(postImg.parentElement).width;
|
||||
assert.strictEqual(imgWidth, cardWidth);
|
||||
|
||||
const imgBorderBottom = getComputedStyle(postImg).borderBottomWidth;
|
||||
assert.notStrictEqual(imgBorderBottom, '0px');
|
||||
```
|
||||
|
||||
Your `.post-title` and `.post-excerpt` elements should have margins and non-default text colors.
|
||||
|
||||
```js
|
||||
const title = document.querySelector('.post-title');
|
||||
const excerpt = document.querySelector('.post-excerpt');
|
||||
|
||||
function isColorApplied(element) {
|
||||
const color = getComputedStyle(element).getPropertyValue('color');
|
||||
return color && color !== 'rgba(0, 0, 0, 0)' && color !== 'transparent';
|
||||
}
|
||||
|
||||
function isMarginApplied(element) {
|
||||
const margin = getComputedStyle(element).getPropertyValue('margin');
|
||||
return margin && margin !== '0px';
|
||||
}
|
||||
|
||||
assert.isTrue(isColorApplied(title));
|
||||
assert.isTrue(isMarginApplied(title));
|
||||
|
||||
assert.isTrue(isColorApplied(excerpt));
|
||||
assert.isTrue(isMarginApplied(excerpt));
|
||||
|
||||
const styleSheets = [...document.styleSheets];
|
||||
|
||||
function hasSelectorWithColor(selector) {
|
||||
return styleSheets.some(sheet => {
|
||||
try {
|
||||
return [...sheet.cssRules].some(rule => {
|
||||
return rule.selectorText?.split(',').map(s => s.trim()).includes(selector) && rule.style.color;
|
||||
});
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
assert.isTrue(hasSelectorWithColor('.post-title'));
|
||||
assert.isTrue(hasSelectorWithColor('.post-excerpt'));
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blog Post Card</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Blog Post Card</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="blog-post-card">
|
||||
<img src="https://cdn.freecodecamp.org/curriculum/labs/cover-photo.jpg" alt="Blog Post Image" class="post-img">
|
||||
<div class="post-content">
|
||||
<h2 class="post-title">Learn Web Development in 2024</h2>
|
||||
<p class="post-excerpt">Stay ahead of the curve with the latest trends in web development. Discover what's new and exciting in 2024</p>
|
||||
<a href="#" class="read-more">Read More</a>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f0f0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.blog-post-card {
|
||||
background-color: white;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
width: 350px;
|
||||
margin: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.post-img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-bottom: 5px solid #333;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
color: #333;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.post-excerpt {
|
||||
color: #667;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.read-more {
|
||||
display: inline-block;
|
||||
margin-top: 10px;
|
||||
padding: 10px 15px;
|
||||
background-color: #333;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.read-more:hover {
|
||||
background-color: #555;
|
||||
}
|
||||
```
|
||||
@@ -98,6 +98,7 @@
|
||||
{ "dashedName": "lecture-styling-lists-and-links" },
|
||||
{ "dashedName": "lab-stylized-to-do-list" },
|
||||
{ "dashedName": "lecture-working-with-backgrounds-and-borders" },
|
||||
{ "dashedName": "lab-blog-post-card" },
|
||||
{ "dashedName": "review-css-backgrounds-and-borders" },
|
||||
{ "dashedName": "quiz-css-backgrounds-and-borders" }
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user