mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(curriculum): adding photography lab (#61270)
Co-authored-by: Dario-DC <105294544+Dario-DC@users.noreply.github.com>
This commit is contained in:
@@ -4097,6 +4097,12 @@
|
||||
"You will practice working with Tailwind CSS utility classes for flexbox layouts, colors, breakpoints and more."
|
||||
]
|
||||
},
|
||||
"lab-photography-exhibit": {
|
||||
"title": "Design a Photography Exhibit",
|
||||
"intro": [
|
||||
"In this lab, you will practice working with Tailwind CSS by designing a photography exhibit webpage."
|
||||
]
|
||||
},
|
||||
"review-css-libraries-and-frameworks": {
|
||||
"title": "CSS Libraries and Frameworks Review",
|
||||
"intro": [
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: Introduction to the Design a Photography Exhibit
|
||||
block: lab-photography-exhibit
|
||||
superBlock: full-stack-developer
|
||||
---
|
||||
|
||||
## Introduction to the Design a Photography Exhibit
|
||||
|
||||
In this lab, you will practice working with Tailwind CSS by designing a photography exhibit webpage.
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "Design a Photography Exhibit",
|
||||
"blockType": "lab",
|
||||
"blockLayout": "link",
|
||||
"isUpcomingChange": true,
|
||||
"dashedName": "lab-photography-exhibit",
|
||||
"superBlock": "full-stack-developer",
|
||||
"helpCategory": "HTML-CSS",
|
||||
"challengeOrder": [{ "id": "686e64be3cd9db25282a956d", "title": "Design a Photography Exhibit" }],
|
||||
"usesMultifileEditor": true
|
||||
}
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
---
|
||||
id: 686e64be3cd9db25282a956d
|
||||
title: Design a Photography Exhibit
|
||||
challengeType: 25
|
||||
dashedName: lab-photography-exhibit
|
||||
demoType: onClick
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
In this lab, you'll create a photography exhibit layout using Tailwind CSS utility classes.
|
||||
|
||||
For the images, you can use the following URLs if you like:
|
||||
|
||||
- https://cdn.freecodecamp.org/curriculum/labs/colosseo.jpg
|
||||
- https://cdn.freecodecamp.org/curriculum/labs/alps.jpg
|
||||
- https://cdn.freecodecamp.org/curriculum/labs/sea.jpg
|
||||
|
||||
Fulfill the user stories below and get all the tests to pass to complete the lab.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
1. You should have an element with a class called `main-container`.
|
||||
2. Your `.main-container` element should also have the following Tailwind CSS utility classes:
|
||||
- the correct utility class for creating a CSS Grid container.
|
||||
- a utility class for defining the number of columns within the grid. You should use the utility class that utilizes the fixed number of columns. Ex. `grid-cols-<number>`.
|
||||
- a utility class for setting the row and column gap for the grid items.
|
||||
3. Inside your `.main-container` element, there should be at least three elements each with a class called `card`.
|
||||
4. Each of your `.card` elements should also have the following Tailwind CSS utility classes:
|
||||
- a utility class for setting a rounded border of your choice.
|
||||
- a utility class for setting padding of your choice. You should use this format `p-<number>`.
|
||||
5. Inside each of your `.card` elements, you should have the following elements:
|
||||
- an image element with `src` and `alt` attributes.
|
||||
- an element with the class called `subheading`.
|
||||
- an element with the class called `description`.
|
||||
6. Each of your image elements should have a utility class for setting a rounded border of your choice.
|
||||
7. Each of your `.subheading` elements should use utility classes to set the font weight and size of your choosing.
|
||||
8. Each of your `.description` elements should use a utility class to set the font size of your choosing.
|
||||
|
||||
# --hints--
|
||||
|
||||
You should have an element with a class called `main-container`.
|
||||
|
||||
```js
|
||||
assert.exists(document.querySelector(".main-container"));
|
||||
```
|
||||
|
||||
Your `.main-container` element should use the correct Tailwind CSS utility class for creating a CSS Grid container.
|
||||
|
||||
```js
|
||||
const mainContainer = document.querySelector(".main-container");
|
||||
assert.isTrue(mainContainer.classList.contains("grid"));
|
||||
```
|
||||
|
||||
Your `.main-container` element should use a utility class for defining the number of columns within the grid.
|
||||
|
||||
```js
|
||||
const mainContainer = document.querySelector(".main-container");
|
||||
// Grid classes can vary, so this check is for the presence of any grid-cols class
|
||||
const hasGridCols = [...mainContainer.classList].some(className => className.startsWith("grid-cols-"));
|
||||
assert.isTrue(hasGridCols);
|
||||
```
|
||||
|
||||
Your `.main-container` element should use a utility class for setting the row and column gap for the grid items.
|
||||
|
||||
```js
|
||||
const mainContainer = document.querySelector(".main-container");
|
||||
// Check for any gap class including gap-x and gap-y
|
||||
|
||||
const hasGap = [...mainContainer.classList].some(c => c.startsWith("gap-")) ||
|
||||
(classList.some(c => c.startsWith("gap-x-")) &&
|
||||
classList.some(c => c.startsWith("gap-y-")));
|
||||
|
||||
assert.isTrue(hasGap);
|
||||
```
|
||||
|
||||
Your `.main-container` element should contain at least three elements each with a class called `card`.
|
||||
|
||||
```js
|
||||
const cards = document.querySelectorAll(".main-container .card");
|
||||
assert.isAtLeast(cards.length, 3);
|
||||
```
|
||||
|
||||
Each of your `.card` elements should have a utility class for setting a rounded border.
|
||||
|
||||
```js
|
||||
const cards = document.querySelectorAll(".main-container .card");
|
||||
assert.isNotEmpty(cards);
|
||||
|
||||
const predefinedRoundedCorners = [
|
||||
"rounded-none",
|
||||
"rounded-sm",
|
||||
"rounded",
|
||||
"rounded-md",
|
||||
"rounded-lg",
|
||||
"rounded-xl",
|
||||
"rounded-2xl",
|
||||
"rounded-3xl",
|
||||
"rounded-full"
|
||||
];
|
||||
|
||||
cards.forEach(card => {
|
||||
const hasRoundedCorners = predefinedRoundedCorners.some(corner => card.classList.contains(corner));
|
||||
assert.isTrue(hasRoundedCorners);
|
||||
})
|
||||
```
|
||||
|
||||
Each of your `.card` elements should have a utility class for setting padding.
|
||||
|
||||
```js
|
||||
const cards = document.querySelectorAll(".main-container .card");
|
||||
assert.isNotEmpty(cards);
|
||||
const hasPadding = [...cards].every(card => {
|
||||
return [...card.classList].some(c => c.startsWith("p-"));
|
||||
});
|
||||
assert.isTrue(hasPadding);
|
||||
```
|
||||
|
||||
Each of your `.card` elements should contain an image element.
|
||||
|
||||
```js
|
||||
const images = document.querySelectorAll(".main-container .card img");
|
||||
assert.isAtLeast(images.length, 3);
|
||||
```
|
||||
|
||||
Each of your `img` elements should have a `src` attribute.
|
||||
|
||||
```js
|
||||
const images = document.querySelectorAll(".main-container .card img");
|
||||
assert.isAtLeast(images.length, 3);
|
||||
images.forEach(img => assert.isTrue(img.hasAttribute("src")));
|
||||
images.forEach(img => assert.isTrue(img.getAttribute("src").length > 0));
|
||||
```
|
||||
|
||||
Each of your `img` elements should have an `alt` attribute.
|
||||
|
||||
```js
|
||||
const images = document.querySelectorAll(".main-container .card img");
|
||||
assert.isAtLeast(images.length, 3);
|
||||
images.forEach(img => assert.isTrue(img.hasAttribute("alt")));
|
||||
images.forEach(img => assert.isTrue(img.getAttribute("alt").length > 0));
|
||||
```
|
||||
|
||||
Each of your `.card` elements should contain an element with the class called `subheading`.
|
||||
|
||||
```js
|
||||
const subheadings = document.querySelectorAll(".main-container .card .subheading");
|
||||
assert.isAtLeast(subheadings.length, 3);
|
||||
```
|
||||
|
||||
Each of your `.subheading` elements should have some text content.
|
||||
|
||||
```js
|
||||
const subheadings = document.querySelectorAll(".main-container .card .subheading");
|
||||
// Ensure we are not dealing with an empty NodeList
|
||||
assert.isNotEmpty(subheadings);
|
||||
|
||||
subheadings.forEach(subheading => {
|
||||
assert.isNotEmpty(subheading.textContent.trim());
|
||||
});
|
||||
```
|
||||
|
||||
Each of your `.card` elements should contain an element with the class called `description`.
|
||||
|
||||
```js
|
||||
const descriptions = document.querySelectorAll(".main-container .card .description");
|
||||
assert.isAtLeast(descriptions.length, 3);
|
||||
```
|
||||
|
||||
Each of your `.description` elements should have some text content.
|
||||
|
||||
```js
|
||||
const descriptions = document.querySelectorAll(".main-container .card .description");
|
||||
// Ensure we are not dealing with an empty NodeList
|
||||
assert.isNotEmpty(descriptions);
|
||||
|
||||
descriptions.forEach(description => {
|
||||
assert.isNotEmpty(description.textContent.trim());
|
||||
});
|
||||
```
|
||||
|
||||
Each of your `img` elements should have a utility class for setting a rounded border.
|
||||
|
||||
```js
|
||||
const images = document.querySelectorAll(".main-container .card img");
|
||||
assert.isNotEmpty(images);
|
||||
const predefinedRoundedCorners = [
|
||||
"rounded-none",
|
||||
"rounded-sm",
|
||||
"rounded",
|
||||
"rounded-md",
|
||||
"rounded-lg",
|
||||
"rounded-xl",
|
||||
"rounded-2xl",
|
||||
"rounded-3xl",
|
||||
"rounded-full"
|
||||
];
|
||||
|
||||
images.forEach(img => {
|
||||
const hasRoundedCorners = predefinedRoundedCorners.some(corner => img.classList.contains(corner));
|
||||
assert.isTrue(hasRoundedCorners);
|
||||
});
|
||||
```
|
||||
|
||||
Each of your `.subheading` elements should use utility classes to set the font weight.
|
||||
|
||||
```js
|
||||
const subheadings = document.querySelectorAll(".main-container .card .subheading");
|
||||
assert.isNotEmpty(subheadings);
|
||||
const predefinedFontWeights = [
|
||||
"font-thin",
|
||||
"font-extralight",
|
||||
"font-light",
|
||||
"font-normal",
|
||||
"font-medium",
|
||||
"font-semibold",
|
||||
"font-bold",
|
||||
"font-extrabold",
|
||||
"font-black"
|
||||
];
|
||||
|
||||
subheadings.forEach(subheading => {
|
||||
const hasFontWeight = predefinedFontWeights.some(weight => subheading.classList.contains(weight));
|
||||
assert.isTrue(hasFontWeight);
|
||||
});
|
||||
```
|
||||
|
||||
Each of your `.subheading` elements should use utility classes to set the font size.
|
||||
|
||||
```js
|
||||
const subheadings = document.querySelectorAll(".main-container .card .subheading");
|
||||
assert.isNotEmpty(subheadings);
|
||||
const predefinedSizes = [
|
||||
"text-xs",
|
||||
"text-sm",
|
||||
"text-base",
|
||||
"text-lg",
|
||||
"text-xl",
|
||||
"text-2xl",
|
||||
"text-3xl",
|
||||
"text-4xl",
|
||||
"text-5xl",
|
||||
"text-6xl",
|
||||
"text-7xl",
|
||||
"text-8xl",
|
||||
"text-9xl"
|
||||
];
|
||||
|
||||
subheadings.forEach(subheading => {
|
||||
const hasFontSize = predefinedSizes.some(size => subheading.classList.contains(size));
|
||||
assert.isTrue(hasFontSize);
|
||||
});
|
||||
```
|
||||
|
||||
Each of your `.description` elements should use a utility class to set the font size.
|
||||
|
||||
```js
|
||||
const descriptions = document.querySelectorAll(".description");
|
||||
assert.isNotEmpty(descriptions);
|
||||
const predefinedSizes = [
|
||||
"text-xs",
|
||||
"text-sm",
|
||||
"text-base",
|
||||
"text-lg",
|
||||
"text-xl",
|
||||
"text-2xl",
|
||||
"text-3xl",
|
||||
"text-4xl",
|
||||
"text-5xl",
|
||||
"text-6xl",
|
||||
"text-7xl",
|
||||
"text-8xl",
|
||||
"text-9xl"
|
||||
];
|
||||
|
||||
descriptions.forEach(description => {
|
||||
const hasFontSize = predefinedSizes.some(size => description.classList.contains(size));
|
||||
assert.isTrue(hasFontSize);
|
||||
});
|
||||
```
|
||||
|
||||
# --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>Photography Exhibit</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Photography Exhibit</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
<body class="bg-purple-800 text-yellow-400 min-h-screen">
|
||||
<h1 class="text-center text-4xl font-bold my-12">Photography Exhibit</h1>
|
||||
|
||||
<main class="main-container grid grid-cols-1 md:grid-cols-3 gap-8 px-4">
|
||||
<figure
|
||||
class="card bg-purple-900 border border-yellow-400 rounded-lg p-4"
|
||||
>
|
||||
<img
|
||||
src="https://cdn.freecodecamp.org/curriculum/labs/colosseo.jpg"
|
||||
alt="Ancient Colosseum"
|
||||
class="h-48 w-full object-cover rounded-md mb-4"
|
||||
/>
|
||||
<figcaption>
|
||||
<h2 class="subheading text-lg font-semibold mb-2">Colosseum, Rome</h2>
|
||||
<p class="description text-sm mb-4">
|
||||
There is so much history and beauty. The light hits it just right.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
class="bg-purple-800 hover:bg-purple-700 border border-yellow-400 py-1 px-2 rounded-md"
|
||||
>
|
||||
Like
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
class="bg-yellow-400 hover:bg-yellow-300 text-purple-900 font-semibold py-1 px-2 rounded-md text-center"
|
||||
>
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure
|
||||
class="card bg-purple-900 border border-yellow-400 rounded-lg p-4"
|
||||
>
|
||||
<img
|
||||
src="https://cdn.freecodecamp.org/curriculum/labs/alps.jpg"
|
||||
alt="Alpine Mountains"
|
||||
class="h-48 w-full object-cover rounded-md mb-4"
|
||||
/>
|
||||
<figcaption>
|
||||
<h2 class="subheading text-lg font-semibold mb-2">The Alps</h2>
|
||||
<p class="description text-sm mb-4">
|
||||
The mountains go on forever and the view is breathtaking.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
class="bg-purple-800 hover:bg-purple-700 border border-yellow-400 py-1 px-2 rounded-md"
|
||||
>
|
||||
Like
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
class="bg-yellow-400 hover:bg-yellow-300 text-purple-900 font-semibold py-1 px-2 rounded-md text-center"
|
||||
>
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
|
||||
<figure
|
||||
class="card bg-purple-900 border border-yellow-400 rounded-lg p-4"
|
||||
>
|
||||
<img
|
||||
src="https://cdn.freecodecamp.org/curriculum/labs/sea.jpg"
|
||||
alt="Calm Sea"
|
||||
class="h-48 w-full object-cover rounded-md mb-4"
|
||||
/>
|
||||
<figcaption>
|
||||
<h2 class="subheading text-lg font-semibold mb-2">Seascape</h2>
|
||||
<p class="description text-sm mb-4">
|
||||
The water was still, the air was cool and the feeling is pure peace.
|
||||
</p>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<button
|
||||
class="bg-purple-800 hover:bg-purple-700 border border-yellow-400 py-1 px-2 rounded-md"
|
||||
>
|
||||
Like
|
||||
</button>
|
||||
<a
|
||||
href="#"
|
||||
class="bg-yellow-400 hover:bg-yellow-300 text-purple-900 font-semibold py-1 px-2 rounded-md text-center"
|
||||
>
|
||||
View Details
|
||||
</a>
|
||||
</div>
|
||||
</figcaption>
|
||||
</figure>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
@@ -1228,6 +1228,9 @@
|
||||
{
|
||||
"dashedName": "lab-music-shopping-cart-page"
|
||||
},
|
||||
{
|
||||
"dashedName": "lab-photography-exhibit"
|
||||
},
|
||||
{
|
||||
"dashedName": "review-css-libraries-and-frameworks"
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user