diff --git a/curriculum/challenges/arabic/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/arabic/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/arabic/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/arabic/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/arabic/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/arabic/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/arabic/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/arabic/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/arabic/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/arabic/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/arabic/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/arabic/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md b/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
index 7827cdcb58..0ce5ccc620 100644
--- a/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
+++ b/curriculum/challenges/chinese-traditional/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
@@ -9,7 +9,7 @@ dashedName: step-64
在 `footer` 中,添加一個 `p` 元素。 然後,在 `p` 中嵌套一個錨(`a`)元素,鏈接到 `https://www.freecodecamp.org`,並有文字 `Visit our website`。
-Make sure that the link opens in a new tab by adding a `target` attribute with the value `_blank`.
+通過添加值爲 `_blank` 的 `target` 屬性,確保鏈接在新選項卡中打開。
# --hints--
diff --git a/curriculum/challenges/chinese-traditional/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/chinese-traditional/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/chinese-traditional/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/chinese-traditional/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/chinese-traditional/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/chinese/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md b/curriculum/challenges/chinese/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
index 548bc0088b..e59c67355f 100644
--- a/curriculum/challenges/chinese/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
+++ b/curriculum/challenges/chinese/14-responsive-web-design-22/learn-basic-css-by-building-a-cafe-menu/5f3ef6e0a81099d9a697b550.md
@@ -9,7 +9,7 @@ dashedName: step-64
在 `footer` 中,添加一个 `p` 元素。 然后,在 `p` 中嵌套一个锚(`a`)元素,链接到 `https://www.freecodecamp.org`,并有文字 `Visit our website`。
-Make sure that the link opens in a new tab by adding a `target` attribute with the value `_blank`.
+通过添加值为 `_blank` 的 `target` 属性,确保链接在新选项卡中打开。
# --hints--
diff --git a/curriculum/challenges/chinese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/chinese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/chinese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/chinese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/chinese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/chinese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/chinese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/chinese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/chinese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/chinese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/chinese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/chinese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/espanol/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/espanol/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/espanol/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/espanol/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/espanol/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/espanol/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/espanol/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/espanol/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/espanol/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/espanol/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/espanol/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/espanol/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5bfe091060f1d6a629dd0.md b/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5bfe091060f1d6a629dd0.md
index abf16a3e18..b6106e9187 100644
--- a/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5bfe091060f1d6a629dd0.md
+++ b/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5bfe091060f1d6a629dd0.md
@@ -11,7 +11,7 @@ Auch ohne die Farbmarkierungen ist dir vielleicht aufgefallen, dass die Farben f
Die Funktion `linear-gradient` berechnet diese Werte automatisch für dich und platziert die Farben standardmäßig gleichmäßig entlang der Verlaufslinie.
-Entferne in der CSS-Regel `.red` die drei Farben aus der Funktion `linear-gradient`, um deinen Code ein wenig zu vereinfachen.
+Entferne in der CSS-Regel `.red` die drei Farbstopps aus der Funktion `linear-gradient`, um deinen Code ein wenig zu vereinfachen.
# --hints--
diff --git a/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5d594b887335228ee6533.md b/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5d594b887335228ee6533.md
index 6fbf3e9bd8..7188223366 100644
--- a/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5d594b887335228ee6533.md
+++ b/curriculum/challenges/german/14-responsive-web-design-22/learn-css-colors-by-building-a-set-of-colored-markers/61a5d594b887335228ee6533.md
@@ -7,7 +7,7 @@ dashedName: step-67
# --description--
-Verwende deine `linear-gradient`-Funktion und gehe in die `hsl`-Funktion über, mit den Werten `186` für Farbton, `76%` für Sättigung, und `16%` für Helligkeit als erstes Farbargument.
+Verwende die `linear-gradient` Funktion und übergib ihr als erstes Farbargument eine `hsl` Funktion, mit den Werten `186` für den Farbton, `76%` für die Sättigung, und `16%` für die Helligkeit.
# --hints--
diff --git a/curriculum/challenges/german/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/german/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/german/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/german/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/german/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/german/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/german/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/german/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/german/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/german/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/german/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/german/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/italian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/italian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/italian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/italian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/italian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/italian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/italian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/italian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/italian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/italian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/italian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/italian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/japanese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/japanese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/japanese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/japanese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/japanese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/japanese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/japanese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/japanese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/japanese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/japanese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/japanese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/japanese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/korean/03-front-end-development-libraries/jquery/change-text-inside-an-element-using-jquery.md b/curriculum/challenges/korean/03-front-end-development-libraries/jquery/change-text-inside-an-element-using-jquery.md
index 826b991133..9cdcf5fdf1 100644
--- a/curriculum/challenges/korean/03-front-end-development-libraries/jquery/change-text-inside-an-element-using-jquery.md
+++ b/curriculum/challenges/korean/03-front-end-development-libraries/jquery/change-text-inside-an-element-using-jquery.md
@@ -1,6 +1,6 @@
---
id: 564944c91be2204b269d51e3
-title: Change Text Inside an Element Using jQuery
+title: jQuery로 요소 안의 글자 변경하기
challengeType: 6
forumTopicId: 16773
dashedName: change-text-inside-an-element-using-jquery
@@ -8,27 +8,27 @@ dashedName: change-text-inside-an-element-using-jquery
# --description--
-Using jQuery, you can change the text between the start and end tags of an element. You can even change HTML markup.
+jQuery를 사용하면 요소의 시작과 끝 태그 사이에 있는 텍스트를 변경할 수 있습니다. 심지어 HTML 마크업도 변경할 수 있습니다.
-jQuery has a function called `.html()` that lets you add HTML tags and text within an element. Any content previously within the element will be completely replaced with the content you provide using this function.
+jQuery는 요소 내에 HTML 태그와 텍스트를 추가할 수 있게 해주는 `.html()`라는 함수를 가지고 있습니다. 이 함수를 사용하여 제공하는 내용은 요소 내 기존의 내용을 완전히 대체합니다.
-Here's how you would rewrite and emphasize the text of our heading:
+다음은 헤딩의 텍스트를 다시 쓰고 강조하는 법입니다.
```js
$("h3").html("jQuery Playground");
```
-jQuery also has a similar function called `.text()` that only alters text without adding tags. In other words, this function will not evaluate any HTML tags passed to it, but will instead treat it as the text you want to replace the existing content with.
+jQuery는 태그를 추가하지 않고 오직 텍스트만 변경하는 `.text()`라는 함수도 있습니다. 다시 말해, 이 함수는 전달된 HTML 태그를 평가하지 않는 대신에 기존 콘텐츠를 대체하고자 하는 텍스트로 취급합니다.
-Change the button with id `target4` by emphasizing its text.
+텍스트를 강조하기 위해 id `target4`를 가진 버튼을 변경합니다.
-View our news article for <em> to learn the difference between `` and `` and their uses.
+<em> 에 대한 기사를 보고 ``와 ``의 차이와 사용법을 배우시오.
-Note that while the `` tag has traditionally been used to emphasize text, it has since been adopted for use as a tag for icons. The `` tag is now widely accepted as the tag for emphasis. Either will work for this challenge.
+`` 태그는 전통적으로 텍스트를 강조하기 위해 사용되었지만, 최근에는 아이콘 태그로 사용되기도 합니다. `` 태그는 이제 강조를 위한 태그로 받아들여 집니다. 어느 방식이든 이 과제에 대해서 동작할 것입니다.
# --hints--
-You should emphasize the text in your `target4` button by adding HTML tags.
+HTML 태그를 추가하여 `target4` 버튼의 텍스트를 강조해야 합니다.
```js
assert.isTrue(
@@ -36,25 +36,25 @@ assert.isTrue(
);
```
-The text should otherwise remain unchanged.
+텍스트는 변경하지 않아야 합니다.
```js
assert($('#target4') && $('#target4').text().trim() === '#target4');
```
-You should not alter any other text.
+다른 텍스트 또한 변경하지 않아야 합니다.
```js
assert.isFalse(/|/gi.test($('h3').html()));
```
-You should be using `.html()` and not `.text()`.
+`.html()`를 사용해야 하며 `.text()`를 사용하지 않아야 합니다.
```js
assert(code.match(/\.html\(/g));
```
-You should select `button id="target4"` with jQuery.
+jQuery로 `button id="target4"`를 선택해야 합니다.
```js
assert(code.match(/\$\(\s*?(\"|\')#target4(\"|\')\s*?\)\.html\(/));
diff --git a/curriculum/challenges/korean/03-front-end-development-libraries/react/access-props-using-this.props.md b/curriculum/challenges/korean/03-front-end-development-libraries/react/access-props-using-this.props.md
index be0c1682a3..81b27733df 100644
--- a/curriculum/challenges/korean/03-front-end-development-libraries/react/access-props-using-this.props.md
+++ b/curriculum/challenges/korean/03-front-end-development-libraries/react/access-props-using-this.props.md
@@ -1,6 +1,6 @@
---
id: 5a24c314108439a4d403616e
-title: Access Props Using this.props
+title: this.props 사용해서 프로퍼티에 접근하기
challengeType: 6
forumTopicId: 301375
dashedName: access-props-using-this-props
@@ -8,17 +8,17 @@ dashedName: access-props-using-this-props
# --description--
-The last several challenges covered the basic ways to pass props to child components. But what if the child component that you're passing a prop to is an ES6 class component, rather than a stateless functional component? The ES6 class component uses a slightly different convention to access props.
+앞의 몇 가지 도전에서 자식 컴포넌트로 프로퍼티(props)를 전달하는 기본적인 방법을 알아봤습니다. 하지만 프로퍼티를 전달받는 자식 컴포넌트가 상태가 없는 함수형 컴포넌트 대신에 ES6 클래스 컴포넌트라면 어떨까요? ES6 클래스 컴포넌트는 props에 접근하기 위해 조금 다른 규칙을 사용합니다.
-Anytime you refer to a class component within itself, you use the `this` keyword. To access props within a class component, you preface the code that you use to access it with `this`. For example, if an ES6 class component has a prop called `data`, you write `{this.props.data}` in JSX.
+클래스 컴포넌트 안에서 뭔가를 참조할 때는 `this` 키워드를 사용합니다. 즉, props에 접근하기 위해서도 `this`를 붙여줍니다. 예를 들면, ES6 클래스 컴포넌트에 `data` 프로퍼티가 있다면 JSX에서 `{this.props.data}`로 작성합니다.
# --instructions--
-Render an instance of the `Welcome` component in the parent component `App`. Here, give `Welcome` a prop of `name` and assign it a value of a string. Within the child, `Welcome`, access the `name` prop within the `strong` tags.
+부모 컴포넌트인 `App`에 `Welcome` 컴포넌트 (인스턴스)를 렌더링 해보세요. `Welcome`의 프로퍼티로 문자열이 담긴 `name`을 할당해보세요. 자식 컴포넌트인 `Welcome`의 `strong` 태그안에서 `name` 프로퍼티에 접근해보세요.
# --hints--
-The `App` component should return a single `div` element.
+`App` 컴포넌트는 하나의 `div` 엘리먼트를 반환해야 합니다.
```js
assert(
@@ -29,7 +29,7 @@ assert(
);
```
-The child of `App` should be the `Welcome` component.
+`App` 컴포넌트의 자식은 `Welcome` 컴포넌트이어야 합니다.
```js
assert(
@@ -42,7 +42,7 @@ assert(
);
```
-The `Welcome` component should have a prop called `name`.
+`Welcome` 컴포넌트는 하나의 프로퍼티인 `name`을 가져야 합니다.
```js
assert(
@@ -53,7 +53,7 @@ assert(
);
```
-The `Welcome` component should display the string you pass as the `name` prop within `strong` tags.
+`Welcome` 컴포넌트는 `name`프로퍼티로 전달한 문자열을 `strong` 태그에서 보여줘야 합니다.
```js
assert(
diff --git a/curriculum/challenges/korean/03-front-end-development-libraries/react/add-comments-in-jsx.md b/curriculum/challenges/korean/03-front-end-development-libraries/react/add-comments-in-jsx.md
index 94a2131b27..82708f7a11 100644
--- a/curriculum/challenges/korean/03-front-end-development-libraries/react/add-comments-in-jsx.md
+++ b/curriculum/challenges/korean/03-front-end-development-libraries/react/add-comments-in-jsx.md
@@ -1,6 +1,6 @@
---
id: 5a24bbe0dba28a8d3cbd4c5e
-title: Add Comments in JSX
+title: JSX에 주석 달기
challengeType: 6
forumTopicId: 301376
dashedName: add-comments-in-jsx
@@ -8,35 +8,35 @@ dashedName: add-comments-in-jsx
# --description--
-JSX is a syntax that gets compiled into valid JavaScript. Sometimes, for readability, you might need to add comments to your code. Like most programming languages, JSX has its own way to do this.
+JSX는 자바스크립트로 컴파일되는 언어입니다. 때때로 가독성을 위해 코드에 주석을 달아주는 것이 필요할 수도 있습니다. 다른 프로그래밍 언어들처럼, JSX도 JSX만의 방법이 있습니다.
-To put comments inside JSX, you use the syntax `{/* */}` to wrap around the comment text.
+JSX에 주석을 다는 방법으로는, `{/* */}`으로 주석 내용을 감싸는 문법을 사용하면 됩니다.
# --instructions--
-The code editor has a JSX element similar to what you created in the last challenge. Add a comment somewhere within the provided `div` element, without modifying the existing `h1` or `p` elements.
+코드 에디터에는 지난 도전에서 만든 것과 비슷한 JSX 엘리먼트가 있습니다. 현재 있는 `h1` 혹은 `p` 엘리먼트를 수정하지 말고, 주어진 `div` 엘리먼트 아무데나 주석을 달아 보세요.
# --hints--
-The constant `JSX` should return a `div` element.
+상수 변수 `JSX`는 하나의 `div` 엘리먼트를 반환해야 합니다.
```js
assert(JSX.type === 'div');
```
-The `div` should contain an `h1` tag as the first element.
+`div`는 하나의 `h1` 태그를 첫 번째 엘리먼트로 가지고 있어야 합니다.
```js
assert(JSX.props.children[0].type === 'h1');
```
-The `div` should contain a `p` tag as the second element.
+`div`는 `p` 태그를 두 번째 엘리먼트로 가지고 있어야 합니다.
```js
assert(JSX.props.children[1].type === 'p');
```
-The existing `h1` and `p` elements should not be modified.
+기존의 `h1`과 `p`는 수정되면 안됩니다.
```js
assert(
@@ -45,7 +45,7 @@ assert(
);
```
-The `JSX` should use valid comment syntax.
+`JSX`는 올바른 문법으로 주석을 작성해야 합니다.
```js
assert(/
[\s\S]*{\s*\/\*[\s\S]*\*\/\s*}[\s\S]*<\/div>/.test(code));
diff --git a/curriculum/challenges/korean/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/korean/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/korean/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/korean/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/korean/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/korean/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/korean/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/korean/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/korean/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/korean/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/korean/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/korean/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/portuguese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/portuguese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/portuguese/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/portuguese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/portuguese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/portuguese/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/portuguese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/portuguese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/portuguese/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/portuguese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/portuguese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/portuguese/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/swahili/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/swahili/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/swahili/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/swahili/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/swahili/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/swahili/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/swahili/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/swahili/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/swahili/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/swahili/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/swahili/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/swahili/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+
+
+
+
+
+```
+
+```css
+html {
+ /* Setting a base font size of 10px give us easier rem calculations
+ Info: 1rem === 10px, 1.5rem === 15px, 2rem === 20px and so forth
+ */
+ font-size: 10px;
+}
+
+body {
+ /* Native font stack https://getbootstrap.com/docs/4.2/content/reboot/#native-font-stack */
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
+ 'Helvetica Neue', Arial, sans-serif;
+ font-size: 1.6rem;
+ line-height: 1.5;
+ text-align: center;
+ color: #333;
+ margin: 0;
+}
+
+h1 {
+ font-size: 4rem;
+ margin-bottom: 0;
+}
+
+@media (max-width: 460px) {
+ h1 {
+ font-size: 3.5rem;
+ line-height: 1.2;
+ }
+}
+
+h2 {
+ font-size: 3.25rem;
+}
+
+a {
+ color: #477ca7;
+}
+
+a:visited {
+ color: #74638f;
+}
+
+#main {
+ margin: 30px 8px;
+ padding: 15px;
+ border-radius: 5px;
+ background: #eee;
+}
+
+@media (max-width: 460px) {
+ #main {
+ margin: 0;
+ }
+}
+
+img {
+ max-width: 100%;
+ display: block;
+ height: auto;
+ margin: 0 auto;
+}
+
+#img-div {
+ background: white;
+ padding: 10px;
+ margin: 0;
+}
+
+#img-caption {
+ margin: 15px 0 5px 0;
+}
+
+@media (max-width: 460px) {
+ #img-caption {
+ font-size: 1.4rem;
+ }
+}
+
+#headline {
+ margin: 50px 0;
+ text-align: center;
+}
+
+ul {
+ max-width: 550px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+ line-height: 1.6;
+}
+
+li {
+ margin: 16px 0;
+}
+
+blockquote {
+ font-style: italic;
+ max-width: 545px;
+ margin: 0 auto 50px auto;
+ text-align: left;
+}
+```
diff --git a/curriculum/challenges/ukrainian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md b/curriculum/challenges/ukrainian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
new file mode 100644
index 0000000000..6d3c82e212
--- /dev/null
+++ b/curriculum/challenges/ukrainian/25-front-end-development/lab-football-team-cards/66e7ee20b79186306fc12da5.md
@@ -0,0 +1,598 @@
+---
+id: 66e7ee20b79186306fc12da5
+title: Build a Set of Football Team Cards
+challengeType: 14
+dashedName: lab-football-team-cards
+demoType: onClick
+---
+
+# --description--
+
+In this lab, you will build a set of football team cards. The user should be able to use the dropdown menu and filter between the different players based on their positions.
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should create a `footballTeam` object with the following properties: `team`, `year`, `headCoach`, `players`.
+1. The `team` property should contain the name of the team as a string.
+1. The `year` property should contain the year as a number.
+1. The `headCoach` property should contain the name of the head coach as a string.
+1. The `players` property should be an array containing at least four elements.
+1. Each element in the `players` array should be an object with properties `name`, `position`, `isCaptain`.
+1. The `name` property should contain the name of the player as a string.
+1. The `position` property should have one of the following values: `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+1. The `isCaptain` property should have value of a boolean. One of the players should have their `isCaptain` property set to `true`.
+1. You should display the `coach`, `team` and `year` values on the page. These values should be displayed in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+1. You should display the players data on the page inside the `#player-cards` element, each player should be displayed in a `div` with class of `player-card`, and nested in it, an `h2` containing the name of the player, and `(Captain)` in case of the player being captain, and a `p` containing `Position:` and the position of the player.
+
+ ```html
+
+
Sergio Batista
+
Position: midfielder
+
+
+
(Captain) Diego Maradona
+
Position: midfielder
+
+ ```
+
+1. When the dropdown menu is used to select one of the positions, only players of that position should be shown. If the `"All Players"` option is selected, then all of the players should display on the page.
+
+# --hints--
+
+You should have a `footballTeam` variable.
+
+```js
+assert.exists(footballTeam);
+```
+
+The `footballTeam` variable should be an object with four properties: `team`, `year`, `headCoach` and `players`.
+
+```js
+assert.isObject(footballTeam);
+assert.containsAllKeys(footballTeam, ['team', 'year', 'headCoach', 'players']);
+```
+
+The `team` property should be a string.
+
+```js
+assert.isString(footballTeam.team);
+```
+
+The `year` property should be a number.
+
+```js
+assert.isNumber(footballTeam.year);
+```
+
+The `headCoach` property should be a string.
+
+```js
+assert.isString(footballTeam.headCoach);
+```
+
+The `players` property should be an array of at least four objects, each object should have the keys `name`, `position`, `isCaptain`.
+
+```js
+assert.isArray(footballTeam.players);
+assert.isAtLeast(footballTeam.players.length, 4);
+footballTeam.players.forEach(player => {
+ assert.isObject(player);
+ assert.containsAllKeys(player, ['name', 'position', 'isCaptain']);
+})
+```
+
+The `name` property should have value of a string.
+
+```js
+footballTeam.players.forEach(({name}) => assert.isString(name));
+```
+
+The `position` property should have one of values `"forward"`, `"midfielder"`, `"defender"`, or `"goalkeeper"`.
+
+```js
+footballTeam.players.forEach(({position}) => {
+ assert.isString(position);
+ assert.oneOf(position, ["forward", "midfielder", "defender", "goalkeeper"]);
+});
+```
+
+The `isCaptain` property should have value of a boolean, and there should be only one captain.
+
+```js
+footballTeam.players.forEach(({isCaptain}) => assert.isBoolean(isCaptain));
+const listOfCaptains = footballTeam.players.filter(({isCaptain}) => isCaptain);
+assert.lengthOf(listOfCaptains, 1);
+```
+
+You should display the `coach`, `team` and `year` values from the `footballTeam` object in the HTML elements with the `id` values of `head-coach`, `team` and `year`.
+
+```js
+const teamElement = document.querySelector('.team-stats #team');
+const yearElement = document.querySelector('.team-stats #year');
+const headCoachElement = document.querySelector('.team-stats #head-coach');
+assert.equal(teamElement?.innerText.trim(), footballTeam.team);
+assert.equal(yearElement?.innerText.trim(), footballTeam.year);
+assert.equal(headCoachElement?.innerText.trim(), footballTeam.headCoach);
+```
+
+When the option `All players` is selected, all players should be shown within `#player-cards`.
+
+```js
+const select = document.querySelector('#players')
+select.value = 'all';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, footballTeam.players);
+```
+
+When the option `Position Forward` is selected, only forward players should be shown within `#player-cards`.
+
+```js
+const forwards = footballTeam.players.filter(({position}) => position === 'forward')
+const select = document.querySelector('#players')
+select.value = 'forward';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, forwards);
+```
+
+When the option `Position Midfielder` is selected, only midfielder players should be shown within `#player-cards`.
+
+```js
+const midfielders = footballTeam.players.filter(({position}) => position === 'midfielder')
+const select = document.querySelector('#players')
+select.value = 'midfielder';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, midfielders);
+```
+
+When the option `Position Defender` is selected, only defender players should be shown within `#player-cards`.
+
+```js
+const defenders = footballTeam.players.filter(({position}) => position === 'defender')
+const select = document.querySelector('#players')
+select.value = 'defender';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, defenders);
+```
+
+When the option `Position Goalkeeper` is selected, only goalkeeper players should be shown.
+
+```js
+const goalkeepers = footballTeam.players.filter(({position}) => position === 'goalkeeper')
+const select = document.querySelector('#players')
+select.value = 'goalkeeper';
+select.dispatchEvent(new Event('change'))
+const playerCards = document.querySelectorAll('.player-card');
+const arrayFromPage = Array.from(playerCards).map(el => ({
+ name: el.querySelector('h2').innerText.replace('(Captain)', '').trim(),
+ position: el.querySelector('p').innerText.replace('Position:', '').trim(),
+ isCaptain: /Captain/.test(el.querySelector('h2').innerText)
+ }))
+
+assert.sameDeepMembers(arrayFromPage, goalkeepers);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+
+
+ Build a Set of Football Team Cards
+
+
+
+
+
+ `
+ )
+ .join("");
+};
+
+playersDropdownList.addEventListener("change", (e) => {
+ playerCards.innerHTML = "";
+
+ switch (e.target.value) {
+ case "forward":
+ setPlayerCards(players.filter((player) => player.position === "forward"));
+ break;
+ case "midfielder":
+ setPlayerCards(
+ players.filter((player) => player.position === "midfielder")
+ );
+ break;
+ case "defender":
+ setPlayerCards(
+ players.filter((player) => player.position === "defender")
+ );
+ break;
+ case "goalkeeper":
+ setPlayerCards(
+ players.filter((player) => player.position === "goalkeeper")
+ );
+ break;
+ default:
+ setPlayerCards();
+ }
+});
+
+setPlayerCards();
+
+```
diff --git a/curriculum/challenges/ukrainian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md b/curriculum/challenges/ukrainian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
new file mode 100644
index 0000000000..32acfbcb45
--- /dev/null
+++ b/curriculum/challenges/ukrainian/25-front-end-development/lab-roman-numeral-converter/657bdc8ba322aae1eac38390.md
@@ -0,0 +1,483 @@
+---
+id: 657bdc8ba322aae1eac38390
+title: Build a Roman Numeral Converter
+challengeType: 14
+dashedName: build-a-roman-numeral-converter
+demoType: onClick
+---
+
+# --description--
+
+Roman numerals are based on seven symbols and can be written using various combinations to represent Arabic numerals. For example:
+
+| Roman numerals | Arabic numerals |
+| -------------- | --------------- |
+| M | 1000 |
+| CM | 900 |
+| D | 500 |
+| CD | 400 |
+| C | 100 |
+| XC | 90 |
+| L | 50 |
+| XL | 40 |
+| X | 10 |
+| IX | 9 |
+| V | 5 |
+| IV | 4 |
+| I | 1 |
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You should have an `input` element with an `id` of `"number"`.
+1. You should have a `button` element with an `id` of `"convert-btn"`.
+1. You should have a `div`, `span` or `p` element with an `id` of `output`.
+1. When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+1. When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+1. When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+1. When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+1. When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+1. When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+1. When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+1. When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+# --hints--
+
+You should have an `input` element with an `id` of `"number"`.
+
+```js
+const el = document.getElementById('number');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'input');
+```
+
+You should have a `button` element with an `id` of `"convert-btn"`.
+
+```js
+const el = document.getElementById('convert-btn');
+assert.strictEqual(el?.nodeName?.toLowerCase(), 'button');
+```
+
+You should have a `div`, `span`, or `p` element with an `id` of `"output"`.
+
+```js
+const el = document.getElementById('output');
+assert(['div', 'span', 'p'].includes(el?.nodeName?.toLowerCase()));
+```
+
+When you click on the `#convert-btn` element without entering a value into the `#number` element, the `#output` element should contain the text `"Please enter a valid number"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '';
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a valid number');
+```
+
+When the `#number` element contains the number `-1` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '-1';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains the number `4000` or greater and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '4000';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+When the `#number` element contains the number `9` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"IX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '9';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'IX');
+```
+
+When the `#number` element contains the number `16` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"XVI"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '16';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'XVI');
+```
+
+When the `#number` element contains the number `649` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"DCXLIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '649';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'DCXLIX');
+```
+
+When the `#number` element contains the number `1023` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MXXIII"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '1023';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MXXIII');
+```
+
+When the `#number` element contains the number `3999` and the `#convert-btn` element is clicked, the `#output` element should contain the text `"MMMCMXCIX"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+numberInputEl.value = '3999';
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim(), 'MMMCMXCIX');
+```
+
+When the `#number` element contains a random negative number and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number greater than or equal to 1"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomNegativeNumber = Math.floor(Math.random() * -4000) - 2;
+
+numberInputEl.value = randomNegativeNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number greater than or equal to 1');
+```
+
+When the `#number` element contains a number greater than 4000 and the `#convert-btn` element is clicked, the `#output` element should contain the text `"Please enter a number less than or equal to 3999"`.
+
+```js
+const numberInputEl = document.getElementById('number');
+const convertBtnEl = document.getElementById('convert-btn');
+const outputEl = document.getElementById('output');
+
+const randomBigNumber = Math.floor(Math.random() * (1000000)) + 4000;
+
+numberInputEl.value = randomBigNumber;
+numberInputEl.dispatchEvent(new Event('change'));
+convertBtnEl.click();
+assert.strictEqual(outputEl.innerText.trim().replace(/[.,?!]+$/g, '').toLowerCase(), 'please enter a number less than or equal to 3999');
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+```js
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Roman Numeral Converter
+
+
+
+
+
Roman Numeral Converter
+
+
+
+
+
+
+```
+
+```css
+:root {
+ --gray-00: #ffffff;
+ --gray-05: #f5f6f7;
+ --gray-15: #d0d0d5;
+ --gray-75: #3b3b4f;
+ --gray-85: #1b1b32;
+ --gray-90: #0a0a23;
+ --blue-50: #198eee;
+ --error: #a94442;
+ --danger-color: #850000;
+ --danger-background: #ffadad;
+}
+
+*,
+::before,
+::after {
+ padding: 0;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+body {
+ min-height: 100vh;
+ padding: 50px 20px;
+ font-family: 'Lato', Helvetica, Arial, sans-serif;
+ font-size: 18px;
+ background-color: var(--gray-85);
+ color: var(--gray-05);
+}
+
+main {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.freecodecamp-logo {
+ height: 30px;
+ margin-bottom: 20px;
+}
+
+h1 {
+ text-align: center;
+ margin: 20px auto;
+ max-width: 350px;
+ font-family: 'Castoro Titling', cursive;
+}
+
+form {
+ color: var(--gray-05);
+ margin: auto 25px;
+ padding: 15px auto;
+ border: 3px solid var(--gray-05);
+ text-align: center;
+ width: 90%;
+ max-width: 500px;
+ background-color: var(--gray-75);
+}
+
+fieldset {
+ border: 0 none;
+ height: 100%;
+ padding: 25px;
+ margin: 10px 20px;
+}
+
+label {
+ display: inline-block;
+ font-size: 1.5rem;
+ margin-bottom: 10px;
+ font-weight: bold;
+}
+
+input:focus-visible,
+button:focus-visible {
+ outline: 3px solid var(--blue-50);
+}
+
+input {
+ display: block;
+ font-size: 2.5rem;
+ width: 100%;
+ height: 60px;
+ padding: 6px 12px;
+ margin: 10px 0;
+ line-height: 1.4;
+ color: white;
+ background-color: var(--gray-90);
+ border: 1px solid var(--gray-05);
+}
+
+button {
+ cursor: pointer;
+ margin-top: 15px;
+ text-decoration: none;
+ background-image: linear-gradient(#fecc4c, #ffac33);
+ border: 3px solid #feac32;
+ padding: 10px 16px;
+ font-size: 23px;
+ width: 100%;
+}
+
+.output {
+ color: white;
+ background-color: var(--gray-75);
+ border: 3px solid var(--gray-05);
+ font-size: 2.5rem;
+ width: 90%;
+ max-width: 500px;
+ min-height: 55px;
+ margin-top: 25px;
+ padding: 15px;
+ overflow-wrap: break-word;
+ text-align: center;
+}
+
+.alert {
+ font-size: 2rem;
+ background-color: var(--danger-background);
+ border: 3px solid var(--danger-color);
+ color: var(--danger-color);
+}
+
+.hidden {
+ display: none;
+}
+```
+
+```js
+const form = document.getElementById("form");
+const convertButton = document.getElementById("convert-btn");
+const output = document.getElementById("output");
+
+const convertToRoman = (num) => {
+ const ref = [
+ ["M", 1000],
+ ["CM", 900],
+ ["D", 500],
+ ["CD", 400],
+ ["C", 100],
+ ["XC", 90],
+ ["L", 50],
+ ["XL", 40],
+ ["X", 10],
+ ["IX", 9],
+ ["V", 5],
+ ["IV", 4],
+ ["I", 1],
+ ];
+ const res = [];
+
+ ref.forEach(function (arr) {
+ while (num >= arr[1]) {
+ res.push(arr[0]);
+ num -= arr[1];
+ }
+ });
+
+ return res.join("");
+};
+
+const isValid = (str, int) => {
+ let errText = "";
+
+ if (!str || str.match(/[e.]/g)) {
+ errText = "Please enter a valid number.";
+ } else if (int < 1) {
+ errText = "Please enter a number greater than or equal to 1.";
+ } else if (int > 3999) {
+ errText = "Please enter a number less than or equal to 3999.";
+ } else {
+ // No errors detected
+ return true;
+ }
+
+ // Handle error text and output styling
+ output.innerText = errText;
+ output.classList.add("alert");
+
+ return false;
+};
+
+const clearOutput = () => {
+ output.innerText = "";
+ output.classList.remove("alert");
+};
+
+form.addEventListener("submit", (e) => {
+ e.preventDefault();
+ updateUI();
+});
+
+convertButton.addEventListener("click", () => {
+ updateUI();
+});
+
+const updateUI = () => {
+ const numStr = document.getElementById("number").value;
+ const int = parseInt(numStr, 10);
+
+ output.classList.remove("hidden");
+
+ clearOutput();
+
+ if (isValid(numStr, int)) {
+ output.innerText = convertToRoman(int);
+ }
+};
+```
diff --git a/curriculum/challenges/ukrainian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md b/curriculum/challenges/ukrainian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
new file mode 100644
index 0000000000..2d2fda2a00
--- /dev/null
+++ b/curriculum/challenges/ukrainian/25-front-end-development/lab-technical-documentation-page/587d78b0367417b2b2512b05.md
@@ -0,0 +1,847 @@
+---
+id: 587d78b0367417b2b2512b05
+title: Build a Technical Documentation Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-technical-documentation-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. You can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
+1. Within the `#main-doc` element, you can see several `section` elements, each with a class of `main-section`. There should be a minimum of five.
+1. The first element within each `.main-section` should be a `header` element, which contains text that describes the topic of that section.
+1. Each `section` element with the class of `main-section` should also have an `id` that corresponds with the text of each `header` contained within it. Any spaces should be replaced with underscores (e.g. The section that contains the header "JavaScript and Java" should have a corresponding `id="JavaScript_and_Java"`).
+1. The `.main-section` elements should contain at least ten `p` elements total (not each).
+1. The `.main-section` elements should contain at least five `code` elements total (not each).
+1. The `.main-section` elements should contain at least five `li` items total (not each).
+1. You can see a `nav` element with a corresponding `id="navbar"`.
+1. The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
+1. Additionally, the navbar should contain link (`a`) elements with the class of `nav-link`. There should be one for every element with the class `main-section`.
+1. The `header` element in the `#navbar` must come before any link (`a`) elements in the navbar.
+1. Each element with the class of `nav-link` should contain text that corresponds to the `header` text within each `section` (e.g. if you have a "Hello world" section/header, your navbar should have an element which contains the text "Hello world").
+1. When you click on a navbar element, the page should navigate to the corresponding section of the `#main-doc` element (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id, and contains the corresponding header).
+1. On regular sized devices (laptops, desktops), the element with `id="navbar"` should be shown on the left side of the screen and should always be visible to the user.
+1. Your technical documentation should use at least one media query.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main-doc`.
+
+```js
+const el = document.getElementById('main-doc')
+assert(!!el)
+```
+
+You should have at least five `section` elements with a class of `main-section`.
+
+```js
+const els = document.querySelectorAll('#main-doc section')
+assert(els.length >= 5)
+```
+
+All of your `.main-section` elements should be `section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (el.tagName !== 'SECTION') assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least five `.main-section` elements that are descendants of `#main-doc`.
+
+```js
+const els = document.querySelectorAll('#main-doc .main-section')
+assert(els.length >= 5)
+```
+
+The first child of each `.main-section` should be a `header` element.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
+})
+assert(els.length > 0)
+```
+
+None of your `header` elements should be empty.
+
+```js
+const els = document.querySelectorAll('header')
+els.forEach(el => {
+ if (el.innerText?.length <= 0) assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.main-section` elements should have an `id`.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ if (!el.id || el.id === '') assert(false)
+})
+assert(els.length > 0)
+```
+
+Each `.main-section` should have an `id` that matches the text of its first child, having any spaces in the child's text replaced with underscores (`_`) for the id's.
+
+```js
+const els = document.querySelectorAll('.main-section')
+els.forEach(el => {
+ const text = el.firstElementChild?.innerText?.replaceAll(' ', '_')
+ if (el.id?.toUpperCase() !== text?.toUpperCase()) assert(false)
+})
+assert(els.length > 0)
+```
+
+You should have at least 10 `p` elements (total) within your `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section p')
+assert(els.length >= 10)
+```
+
+You should have at least five `code` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section code')
+assert(els.length >= 5)
+```
+
+You should have at least five `li` elements that are descendants of `.main-section` elements.
+
+```js
+const els = document.querySelectorAll('.main-section li')
+assert(els.length >= 5)
+```
+
+You should have a `nav` element with an `id` of `navbar`.
+
+```js
+const el = document.getElementById('navbar')
+assert(!!el && el.tagName === 'NAV')
+```
+
+Your `#navbar` should have exactly one `header` element within it.
+
+```js
+const els = document.querySelectorAll('#navbar header')
+assert(els.length === 1)
+```
+
+You should have at least one `a` element with a class of `nav-link`.
+
+```js
+const els = document.querySelectorAll('a.nav-link')
+assert(els.length >= 1)
+```
+
+All of your `.nav-link` elements should be anchor (`a`) elements.
+
+```js
+const els = document.querySelectorAll('.nav-link')
+els.forEach(el => {
+ if (el.tagName !== 'A') assert(false)
+})
+assert(els.length > 0)
+```
+
+All of your `.nav-link` elements should be in the `#navbar`.
+
+```js
+const els1 = document.querySelectorAll('.nav-link')
+const els2 = document.querySelectorAll('#navbar .nav-link')
+assert(els2.length > 0 && els1.length === els2.length)
+```
+
+You should have the same number of `.nav-link` and `.main-section` elements.
+
+```js
+const els1 = document.querySelectorAll('.main-section')
+const els2 = document.querySelectorAll('.nav-link')
+assert(els1.length > 0 && els2.length > 0 && els1.length === els2.length)
+```
+
+The `header` element in the `#navbar` should come before any link (`a`) elements also in the `#navbar`.
+
+```js
+const navLinks = document.querySelectorAll('#navbar a.nav-link');
+const header = document.querySelector('#navbar header');
+navLinks.forEach((navLink) => {
+ if (
+ (
+ header.compareDocumentPosition(navLink) &
+ Node.DOCUMENT_POSITION_PRECEDING
+ )
+ ) assert(false)
+});
+assert(!!header)
+```
+
+Each `.nav-link` should have text that corresponds to the `header` text of its related `section` (e.g. if you have a "Hello world" section/header, your `#navbar` should have a `.nav-link` which has the text "Hello world").
+
+```js
+const headerText = Array.from(document.querySelectorAll('.main-section')).map(el =>
+ el.firstElementChild?.innerText?.trim().toUpperCase()
+)
+const linkText = Array.from(document.querySelectorAll('.nav-link')).map(el =>
+ el.innerText?.trim().toUpperCase()
+)
+const remainder = headerText.filter(str => linkText.indexOf(str) === -1)
+assert(headerText.length > 0 && linkText.length > 0 && remainder.length === 0)
+```
+
+Each `.nav-link` should have an `href` attribute that links to its corresponding `.main-section` (e.g. If you click on a `.nav-link` element that contains the text "Hello world", the page navigates to a `section` element with that id).
+
+```js
+const hrefValues = Array.from(document.querySelectorAll('.nav-link')).map(el => el.getAttribute('href'))
+const mainSectionIDs = Array.from(document.querySelectorAll('.main-section')).map(el => el.id)
+const missingHrefValues = mainSectionIDs.filter(str => hrefValues.indexOf('#' + str) === -1)
+assert(hrefValues.length > 0 && mainSectionIDs.length > 0 && missingHrefValues.length === 0)
+```
+
+Your `#navbar` should always be on the left edge of the window.
+
+```js
+const el = document.getElementById('navbar')
+const left1 = el?.offsetLeft
+const left2 = el?.offsetLeft
+assert(!!el && left1 >= -15 && left1 <= 15 && left2 >= -15 && left2 <= 15)
+```
+
+Your Technical Documentation project 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(cssCheck.length > 0 || htmlSourceAttr.length > 0);
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Technical Documentation Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
+
+ Introduction
+
+
+ JavaScript is a cross-platform, object-oriented scripting language.
+ It is a small and lightweight language. Inside a host environment
+ (for example, a web browser), JavaScript can be connected to the
+ objects of its environment to provide programmatic control over
+ them.
+
+
+
+ JavaScript contains a standard library of objects, such as Array,
+ Date, and Math, and a core set of language elements such as
+ operators, control structures, and statements. Core JavaScript can
+ be extended for a variety of purposes by supplementing it with
+ additional objects; for example:
+
+
+
+ Client-side JavaScript extends the core language by supplying
+ objects to control a browser and its Document Object Model (DOM).
+ For example, client-side extensions allow an application to place
+ elements on an HTML form and respond to user events such as mouse
+ clicks, form input, and page navigation.
+
+
+ Server-side JavaScript extends the core language by supplying
+ objects relevant to running JavaScript on a server. For example,
+ server-side extensions allow an application to communicate with a
+ database, provide continuity of information from one invocation to
+ another of the application, or perform file manipulations on a
+ server.
+
+
+
+
+
+ What you should already know
+
+
This guide assumes you have the following basic background:
+
+
+
+ A general understanding of the Internet and the World Wide Web
+ (WWW).
+
+
Good working knowledge of HyperText Markup Language (HTML).
+
+ Some programming experience. If you are new to programming, try
+ one of the tutorials linked on the main page about JavaScript.
+
+
+
+
+
+ JavaScript and Java
+
+
+ JavaScript and Java are similar in some ways but fundamentally
+ different in some others. The JavaScript language resembles Java but
+ does not have Java's static typing and strong type checking.
+ JavaScript follows most Java expression syntax, naming conventions
+ and basic control-flow constructs which was the reason why it was
+ renamed from LiveScript to JavaScript.
+
+
+
+ In contrast to Java's compile-time system of classes built by
+ declarations, JavaScript supports a runtime system based on a small
+ number of data types representing numeric, Boolean, and string
+ values. JavaScript has a prototype-based object model instead of the
+ more common class-based object model. The prototype-based model
+ provides dynamic inheritance; that is, what is inherited can vary
+ for individual objects. JavaScript also supports functions without
+ any special declarative requirements. Functions can be properties of
+ objects, executing as loosely typed methods.
+
+
+ JavaScript is a very free-form language compared to Java. You do not
+ have to declare all variables, classes, and methods. You do not have
+ to be concerned with whether methods are public, private, or
+ protected, and you do not have to implement interfaces. Variables,
+ parameters, and function return types are not explicitly typed.
+
+
+
+
+ Hello world
+
+ To get started with writing JavaScript, open the Scratchpad and write
+ your first "Hello world" JavaScript code:
+ function greetMe(yourName) { alert("Hello " + yourName); }
+ greetMe("World");
+
+
+ Select the code in the pad and hit Ctrl+R to watch it unfold in your
+ browser!
+
+
+
+ Variables
+
+ You use variables as symbolic names for values in your application.
+ The names of variables, called identifiers, conform to certain rules.
+
+
+ A JavaScript identifier must start with a letter, underscore (_), or
+ dollar sign ($); subsequent characters can also be digits (0-9).
+ Because JavaScript is case sensitive, letters include the characters
+ "A" through "Z" (uppercase) and the characters "a" through "z"
+ (lowercase).
+
+
+ You can use ISO 8859-1 or Unicode letters such as å and ü in
+ identifiers. You can also use the Unicode escape sequences as
+ characters in identifiers. Some examples of legal names are
+ Number_hits, temp99, and _name.
+
+
+
+ Declaring variables
+
+ You can declare a variable in three ways:
+
+ With the keyword var. For example, var x = 42. This
+ syntax can be used to declare both local and global variables.
+
+
+ By simply assigning it a value. For example,
+ x = 42. This always declares a global variable. It
+ generates a strict JavaScript warning. You shouldn't use this
+ variant.
+
+
+ With the keyword let. For example, let y = 13. This
+ syntax can be used to declare a block scope local variable. See
+ Variable scope below.
+
+
+
+
+ Variable scope
+
+
+ When you declare a variable outside of any function, it is called a
+ global variable, because it is available to any other code in the
+ current document. When you declare a variable within a function, it
+ is called a local variable, because it is available only within that
+ function.
+
+
+
+ JavaScript before ECMAScript 2015 does not have block statement
+ scope; rather, a variable declared within a block is local to the
+ function (or global scope) that the block resides within. For
+ example the following code will log 5, because the scope of x is the
+ function (or global context) within which x is declared, not the
+ block, which in this case is an if statement.
+
+ if (true) { var x = 5; } console.log(x); // 5
+
+ This behavior changes, when using the let declaration introduced in
+ ECMAScript 2015.
+
+
+ if (true) { let y = 5; } console.log(y); // ReferenceError: y is
+ not defined
+
+
+
+ Global variables
+
+
+ Global variables are in fact properties of the global object. In web
+ pages the global object is window, so you can set and access global
+ variables using the window.variable syntax.
+
+
+
+ Consequently, you can access global variables declared in one window
+ or frame from another window or frame by specifying the window or
+ frame name. For example, if a variable called phoneNumber is
+ declared in a document, you can refer to this variable from an
+ iframe as parent.phoneNumber.
+
+
+
+
+ Constants
+
+
+ You can create a read-only, named constant with the const keyword.
+ The syntax of a constant identifier is the same as for a variable
+ identifier: it must start with a letter, underscore or dollar sign
+ and can contain alphabetic, numeric, or underscore characters.
+
+
+ const PI = 3.14;
+
+ A constant cannot change value through assignment or be re-declared
+ while the script is running. It has to be initialized to a value.
+
+
+
+ The scope rules for constants are the same as those for let block
+ scope variables. If the const keyword is omitted, the identifier is
+ assumed to represent a variable.
+
+
+
+ You cannot declare a constant with the same name as a function or
+ variable in the same scope. For example:
+
+
+ // THIS WILL CAUSE AN ERROR function f() {}; const f = 5; // THIS
+ WILL CAUSE AN ERROR ALSO function f() { const g = 5; var g;
+ //statements }
+ However, object attributes are not protected, so the following
+ statement is executed without problems.
+ const MY_OBJECT = {"key": "value"}; MY_OBJECT.key =
+ "otherValue";
+
+
+
+ Data types
+
+
The latest ECMAScript standard defines seven data types:
+
+
+
Six data types that are primitives:
+
+
Boolean. true and false.
+
+ null. A special keyword denoting a null value. Because
+ JavaScript is case-sensitive, null is not the same as Null,
+ NULL, or any other variant.
+
+
+ undefined. A top-level property whose value is undefined.
+
+
Number. 42 or 3.14159.
+
String. "Howdy"
+
+ Symbol (new in ECMAScript 2015). A data type whose instances
+ are unique and immutable.
+
+
+
+
+
and Object
+
+ Although these data types are a relatively small amount, they enable
+ you to perform useful functions with your applications. Objects and
+ functions are the other fundamental elements in the language. You can
+ think of objects as named containers for values, and functions as
+ procedures that your application can perform.
+
+
+
+ if...else statement
+
+ Use the if statement to execute a statement if a logical condition is
+ true. Use the optional else clause to execute a statement if the
+ condition is false. An if statement looks as follows:
+
+ if (condition) { statement_1; } else { statement_2; }
+ condition can be any expression that evaluates to true or false. See
+ Boolean for an explanation of what evaluates to true and false. If
+ condition evaluates to true, statement_1 is executed; otherwise,
+ statement_2 is executed. statement_1 and statement_2 can be any
+ statement, including further nested if statements.
+
+ You may also compound the statements using else if to have multiple
+ conditions tested in sequence, as follows:
+
+ if (condition_1) { statement_1; } else if (condition_2) {
+ statement_2; } else if (condition_n) { statement_n; } else {
+ statement_last; }
+
+ In the case of multiple conditions only the first logical condition
+ which evaluates to true will be executed. To execute multiple
+ statements, group them within a block statement ({ ... }) . In
+ general, it's good practice to always use block statements, especially
+ when nesting if statements:
+
+ if (condition) { statement_1_runs_if_condition_is_true;
+ statement_2_runs_if_condition_is_true; } else {
+ statement_3_runs_if_condition_is_false;
+ statement_4_runs_if_condition_is_false; }
+ It is advisable to not use simple assignments in a conditional
+ expression, because the assignment can be confused with equality when
+ glancing over the code. For example, do not use the following code:
+ if (x = y) { /* statements here */ } If you need to use
+ an assignment in a conditional expression, a common practice is to put
+ additional parentheses around the assignment. For example:
+
+ if ((x = y)) { /* statements here */ }
+
+
+
+ while statement
+
+ A while statement executes its statements as long as a specified
+ condition evaluates to true. A while statement looks as follows:
+
+ while (condition) statement If the condition becomes
+ false, statement within the loop stops executing and control passes to
+ the statement following the loop.
+
+
+ The condition test occurs before statement in the loop is executed.
+ If the condition returns true, statement is executed and the
+ condition is tested again. If the condition returns false, execution
+ stops and control is passed to the statement following while.
+
+
+
+ To execute multiple statements, use a block statement ({ ... }) to
+ group those statements.
+
+
+ Example:
+
+
+ The following while loop iterates as long as n is less than three:
+
+
+ var n = 0; var x = 0; while (n < 3) { n++; x += n; }
+
+ With each iteration, the loop increments n and adds that value to x.
+ Therefore, x and n take on the following values:
+
+
+
+
After the first pass: n = 1 and x = 1
+
After the second pass: n = 2 and x = 3
+
After the third pass: n = 3 and x = 6
+
+
+ After completing the third pass, the condition n < 3 is no longer
+ true, so the loop terminates.
+
+
+
+
+ Function declarations
+
+ A function definition (also called a function declaration, or function
+ statement) consists of the function keyword, followed by:
+
+
+
The name of the function.
+
+ A list of arguments to the function, enclosed in parentheses and
+ separated by commas.
+
+
+ The JavaScript statements that define the function, enclosed in
+ curly brackets, { }.
+
+
+
+ For example, the following code defines a simple function named
+ square:
+
+
+ function square(number) { return number * number; }
+
+ The function square takes one argument, called number. The function
+ consists of one statement that says to return the argument of the
+ function (that is, number) multiplied by itself. The return
+ statement specifies the value returned by the function.
+
+ return number * number;
+
+ Primitive parameters (such as a number) are passed to functions by
+ value; the value is passed to the function, but if the function
+ changes the value of the parameter, this change is not reflected
+ globally or in the calling function.
+
+
+
+
+ Reference
+
+
+
+ All the documentation in this page is taken from
+ MDN
+
+
+
+
+
+
+
+```
+
+```css
+html,
+body {
+ min-width: 290px;
+ color: #4d4e53;
+ background-color: #ffffff;
+ font-family: 'Open Sans', Arial, sans-serif;
+ line-height: 1.5;
+}
+
+#navbar {
+ position: fixed;
+ min-width: 290px;
+ top: 0px;
+ left: 0px;
+ width: 300px;
+ height: 100%;
+ border-right: solid;
+ border-color: rgba(0, 22, 22, 0.4);
+}
+
+header {
+ color: black;
+ margin: 10px;
+ text-align: center;
+ font-size: 1.8em;
+ font-weight: thin;
+}
+
+#main-doc header {
+ text-align: left;
+ margin: 0px;
+}
+
+#navbar ul {
+ height: 88%;
+ padding: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+#navbar li {
+ color: #4d4e53;
+ border-top: 1px solid;
+ list-style: none;
+ position: relative;
+ width: 100%;
+}
+
+#navbar a {
+ display: block;
+ padding: 10px 30px;
+ color: #4d4e53;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#main-doc {
+ position: absolute;
+ margin-left: 310px;
+ padding: 20px;
+ margin-bottom: 110px;
+}
+
+section article {
+ color: #4d4e53;
+ margin: 15px;
+ font-size: 0.96em;
+}
+
+section li {
+ margin: 15px 0px 0px 20px;
+}
+
+code {
+ display: block;
+ text-align: left;
+ white-space: pre-line;
+ position: relative;
+ word-break: normal;
+ word-wrap: normal;
+ line-height: 2;
+ background-color: #f7f7f7;
+ padding: 15px;
+ margin: 10px;
+ border-radius: 5px;
+}
+
+@media only screen and (max-width: 815px) {
+ /* For mobile phones: */
+ #navbar ul {
+ border: 1px solid;
+ height: 207px;
+ }
+
+ #navbar {
+ background-color: white;
+ position: absolute;
+ top: 0;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ max-height: 275px;
+ border: none;
+ z-index: 1;
+ border-bottom: 2px solid;
+ }
+
+ #main-doc {
+ position: relative;
+ margin-left: 0px;
+ margin-top: 270px;
+ }
+}
+
+@media only screen and (max-width: 400px) {
+ #main-doc {
+ margin-left: -10px;
+ }
+
+ code {
+ margin-left: -20px;
+ width: 100%;
+ padding: 15px;
+ padding-left: 10px;
+ padding-right: 45px;
+ min-width: 233px;
+ }
+}
+```
diff --git a/curriculum/challenges/ukrainian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md b/curriculum/challenges/ukrainian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
new file mode 100644
index 0000000000..f53a0372ca
--- /dev/null
+++ b/curriculum/challenges/ukrainian/25-front-end-development/lab-tribute-page/bd7158d8c442eddfaeb5bd18.md
@@ -0,0 +1,435 @@
+---
+id: bd7158d8c442eddfaeb5bd18
+title: Build a Tribute Page
+challengeType: 14
+demoType: onClick
+dashedName: build-a-tribute-page
+---
+
+# --description--
+
+**Objective:** Fulfill the user stories below and get all the tests to pass to complete the lab.
+
+**User Stories:**
+
+1. Your tribute page should have a `main` element with a corresponding `id` of `main`, which contains all other elements.
+1. You should see an element with an `id` of `title`, which contains a string (i.e. text), that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
+1. You should see either a `figure` or a `div` element with an `id` of `img-div`.
+1. Within the `#img-div` element, you should see an `img` element with a corresponding `id="image"`.
+1. Within the `#img-div` element, you should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `#img-div`.
+1. You should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
+1. You should see an `a` element with a corresponding `id="tribute-link"`, which links to an outside site, that contains additional information about the subject of the tribute page. HINT: You must give your element an attribute of `target` and set it to `_blank` in order for your link to open in a new tab.
+1. Your `#image` should use `max-width` and `height` properties to resize responsively, relative to the width of its parent element, without exceeding its original size.
+1. Your `img` element should be centered within its parent element.
+
+**Note:** Be sure to link your stylesheet in your HTML and apply your CSS.
+
+# --hints--
+
+You should have a `main` element with an `id` of `main`.
+
+```js
+const el = document.getElementById('main')
+assert(!!el && el.tagName === 'MAIN')
+```
+
+Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`.
+
+```js
+const el1 = document.querySelector('#main #img-div')
+const el2 = document.querySelector('#main #image')
+const el3 = document.querySelector('#main #img-caption')
+const el4 = document.querySelector('#main #tribute-info')
+const el5 = document.querySelector('#main #tribute-link')
+assert(!!el1 & !!el2 && !!el3 && !!el4 && !!el5)
+```
+
+You should have an element with an `id` of `title`.
+
+```js
+const el = document.getElementById('title')
+assert(!!el)
+```
+
+Your `#title` should not be empty.
+
+```js
+const el = document.getElementById('title')
+assert(!!el && el.innerText.length > 0)
+
+```
+
+You should have a `figure` or `div` element with an `id` of `img-div`.
+
+```js
+const el = document.getElementById('img-div')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGURE'))
+```
+
+You should have an `img` element with an `id` of `image`.
+
+```js
+const el = document.getElementById('image')
+assert(!!el && el.tagName === 'IMG')
+```
+
+Your `#image` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #image')
+assert(!!el)
+```
+
+You should have a `figcaption` or `div` element with an `id` of `img-caption`.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && (el.tagName === 'DIV' || el.tagName === 'FIGCAPTION'))
+```
+
+Your `#img-caption` should be a descendant of `#img-div`.
+
+```js
+const el = document.querySelector('#img-div #img-caption')
+assert(!!el)
+```
+
+Your `#img-caption` should not be empty.
+
+```js
+const el = document.getElementById('img-caption')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an element with an `id` of `tribute-info`.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el)
+```
+
+Your `#tribute-info` should not be empty.
+
+```js
+const el = document.getElementById('tribute-info')
+assert(!!el && el.innerText.length > 0)
+```
+
+You should have an `a` element with an `id` of `tribute-link`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.tagName === 'A')
+```
+
+Your `#tribute-link` should have an `href` attribute and value.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && !!el.href && el.href.length > 0)
+```
+
+Your `#tribute-link` should have a `target` attribute set to `_blank`.
+
+```js
+const el = document.getElementById('tribute-link')
+assert(!!el && el.target === '_blank')
+```
+
+Your `img` element should have a `display` of `block`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('display')
+assert(style === 'block')
+```
+
+Your `#image` should have a `max-width` of `100%`.
+
+```js
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const style = imgStyle?.getPropertyValue('max-width')
+assert(style === '100%')
+```
+
+Your `#image` should have a `height` of `auto`.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image');
+const imgStyle = window.getComputedStyle(img);
+const oldDisplayValue = imgStyle.getPropertyValue('display');
+const oldDisplayPriority = imgStyle.getPropertyPriority('display');
+img?.style.setProperty('display', 'none', 'important');
+const heightValue = imgStyle?.getPropertyValue('height')
+img?.style.setProperty('display', oldDisplayValue, oldDisplayPriority);
+assert(heightValue === 'auto')
+```
+
+Your `#image` should be centered within its parent.
+
+```js
+// taken from the testable-projects repo
+const img = document.getElementById('image'),
+ imgParent = img?.parentElement,
+ imgLeft = img?.getBoundingClientRect().left,
+ imgRight = img?.getBoundingClientRect().right,
+ parentLeft = imgParent?.getBoundingClientRect().left,
+ parentRight = imgParent?.getBoundingClientRect().right,
+ leftMargin = imgLeft - parentLeft,
+ rightMargin = parentRight - imgRight;
+assert(leftMargin - rightMargin < 6 && rightMargin - leftMargin < 6)
+```
+
+# --seed--
+
+## --seed-contents--
+
+```html
+
+
+
+
+
+ Tribute Page
+
+
+
+
+
+
+
+```
+
+```css
+
+```
+
+# --solutions--
+
+```html
+
+
+
+
+
+
+
+
Dr. Norman Borlaug
+
The man who saved a billion lives
+
+
+
Here's a time line of Dr. Borlaug's life:
+
+
1914 - Born in Cresco, Iowa
+
+ 1933 - Leaves his family's farm to attend the
+ University of Minnesota, thanks to a Depression era program known as
+ the "National Youth Administration"
+
+
+ 1935 - Has to stop school and save up more money.
+ Works in the Civilian Conservation Corps, helping starving
+ Americans. "I saw how food changed them", he said. "All of this left
+ scars on me."
+
+
+ 1937 - Finishes university and takes a job in the
+ US Forestry Service
+
+
+ 1938 - Marries wife of 69 years Margret Gibson.
+ Gets laid off due to budget cuts. Inspired by Elvin Charles Stakman,
+ he returns to school study under Stakman, who teaches him about
+ breeding pest-resistent plants.
+
+
+ 1941 - Tries to enroll in the military after the
+ Pearl Harbor attack, but is rejected. Instead, the military asked
+ his lab to work on waterproof glue, DDT to control malaria,
+ disinfectants, and other applied science.
+
+
+ 1942 - Receives a Ph.D. in Genetics and Plant
+ Pathology
+
+
+ 1944 - Rejects a 100% salary increase from Dupont,
+ leaves behind his pregnant wife, and flies to Mexico to head a new
+ plant pathology program. Over the next 16 years, his team breeds
+ 6,000 different strains of disease resistent wheat - including
+ different varieties for each major climate on Earth.
+
+
+ 1945 - Discovers a way to grown wheat twice each
+ season, doubling wheat yields
+
+
+ 1953 - crosses a short, sturdy dwarf breed of wheat
+ with a high-yeidling American breed, creating a strain that responds
+ well to fertilizer. It goes on to provide 95% of Mexico's wheat.
+
+
+ 1962 - Visits Delhi and brings his high-yielding
+ strains of wheat to the Indian subcontinent in time to help mitigate
+ mass starvation due to a rapidly expanding population
+
+
1970 - receives the Nobel Peace Prize
+
+ 1983 - helps seven African countries dramatically
+ increase their maize and sorghum yields
+
+
+ 1984 - becomes a distinguished professor at Texas
+ A&M University
+
+
+ 2005 - states "we will have to double the world
+ food supply by 2050." Argues that genetically modified crops are the
+ only way we can meet the demand, as we run out of arable land. Says
+ that GM crops are not inherently dangerous because "we've been
+ genetically modifying plants and animals for a long time. Long
+ before we called it science, people were selecting the best breeds."
+
+
2009 - dies at the age of 95.
+
+
+
+ "Borlaug's life and achievement are testimony to the far-reaching
+ contribution that one man's towering intellect, persistence and
+ scientific vision can make to human peace and progress."
+
+ -- Indian Prime Minister Manmohan Singh
+
+
+ If you have time, you should read more about this incredible human
+ being on his
+ Wikipedia entry.
+