mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-24 11:03:17 -04:00
feat: convert RWD cert projects (#44564)
* feat: convert RWD cert projects * feat: convert original projects * fix: add usesMultiFileEditor to meta * feat: add cypress tests for saving and loading to/from database * fix: broken cypress tests * fix: inconsistent variable naming * fix: missed variable name * fix: more cypress * feat: add solutions for english * fix: ctrl+s to database only if signed in * fix: prioritize code from db * refactor: expand the comments slightly
This commit is contained in:
@@ -91,7 +91,7 @@ const jsCertProjectIds = [
|
||||
'aa2e6f85cab2ab736c9a9b24'
|
||||
];
|
||||
|
||||
const multiFileCertProjectIds = getChallenges()
|
||||
const multifileCertProjectIds = getChallenges()
|
||||
.filter(challenge => challenge.challengeType === 14)
|
||||
.map(challenge => challenge.id);
|
||||
|
||||
@@ -132,7 +132,7 @@ export function buildUserUpdate(
|
||||
let completedChallenge = {};
|
||||
if (
|
||||
jsCertProjectIds.includes(challengeId) ||
|
||||
multiFileCertProjectIds.includes(challengeId)
|
||||
multifileCertProjectIds.includes(challengeId)
|
||||
) {
|
||||
completedChallenge = {
|
||||
..._completedChallenge,
|
||||
@@ -315,7 +315,7 @@ export function modernChallengeCompleted(req, res, next) {
|
||||
// step or normal challenge we can avoid storing in the database.
|
||||
if (
|
||||
jsCertProjectIds.includes(id) ||
|
||||
multiFileCertProjectIds.includes(id)
|
||||
multifileCertProjectIds.includes(id)
|
||||
) {
|
||||
completedChallenge.challengeType = challengeType;
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ export function* saveChallengeSaga() {
|
||||
);
|
||||
}
|
||||
|
||||
// only allow saving of multiFileCertProject's
|
||||
if (challengeType === challengeTypes.multiFileCertProject) {
|
||||
// only allow saving of multifileCertProject's
|
||||
if (challengeType === challengeTypes.multifileCertProject) {
|
||||
const body = standardizeRequestBody({ id, challengeFiles, challengeType });
|
||||
const bodySizeInBytes = getStringSizeInBytes(body);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ import EditorTabs from './editor-tabs';
|
||||
interface ActionRowProps {
|
||||
block: string;
|
||||
hasNotes: boolean;
|
||||
isMultiFileCertProject: boolean;
|
||||
isMultifileCertProject: boolean;
|
||||
showConsole: boolean;
|
||||
showNotes: boolean;
|
||||
showPreview: boolean;
|
||||
@@ -23,7 +23,7 @@ const mapDispatchToProps = {
|
||||
|
||||
const ActionRow = ({
|
||||
hasNotes,
|
||||
isMultiFileCertProject,
|
||||
isMultifileCertProject,
|
||||
togglePane,
|
||||
showNotes,
|
||||
showPreview,
|
||||
@@ -40,7 +40,7 @@ const ActionRow = ({
|
||||
</div>
|
||||
<div className='tabs-row'>
|
||||
<EditorTabs />
|
||||
{!isMultiFileCertProject && (
|
||||
{!isMultifileCertProject && (
|
||||
<button className='restart-step-tab' onClick={resetChallenge}>
|
||||
{t('learn.editor-tabs.restart-step')}
|
||||
</button>
|
||||
|
||||
@@ -86,15 +86,15 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
||||
|
||||
const challengeFile = getChallengeFile();
|
||||
const projectBasedChallenge = hasEditableBoundaries;
|
||||
const isMultiFileCertProject =
|
||||
challengeType === challengeTypes.multiFileCertProject;
|
||||
const isMultifileCertProject =
|
||||
challengeType === challengeTypes.multifileCertProject;
|
||||
const displayPreview =
|
||||
projectBasedChallenge || isMultiFileCertProject
|
||||
projectBasedChallenge || isMultifileCertProject
|
||||
? showPreview && hasPreview
|
||||
: hasPreview;
|
||||
const displayNotes = projectBasedChallenge ? showNotes && hasNotes : false;
|
||||
const displayConsole =
|
||||
projectBasedChallenge || isMultiFileCertProject ? showConsole : true;
|
||||
projectBasedChallenge || isMultifileCertProject ? showConsole : true;
|
||||
const {
|
||||
codePane,
|
||||
editorPane,
|
||||
@@ -106,11 +106,11 @@ const DesktopLayout = (props: DesktopLayoutProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className='desktop-layout'>
|
||||
{(projectBasedChallenge || isMultiFileCertProject) && (
|
||||
{(projectBasedChallenge || isMultifileCertProject) && (
|
||||
<ActionRow
|
||||
block={block}
|
||||
hasNotes={hasNotes}
|
||||
isMultiFileCertProject={isMultiFileCertProject}
|
||||
isMultifileCertProject={isMultifileCertProject}
|
||||
showConsole={showConsole}
|
||||
showNotes={showNotes}
|
||||
showPreview={showPreview}
|
||||
|
||||
@@ -23,7 +23,8 @@ import { Themes } from '../../../components/settings/theme';
|
||||
import {
|
||||
userSelector,
|
||||
saveChallenge,
|
||||
isDonationModalOpenSelector
|
||||
isDonationModalOpenSelector,
|
||||
isSignedInSelector
|
||||
} from '../../../redux';
|
||||
import {
|
||||
ChallengeFiles,
|
||||
@@ -75,6 +76,7 @@ interface EditorProps {
|
||||
initTests: (tests: Test[]) => void;
|
||||
initialTests: Test[];
|
||||
isResetting: boolean;
|
||||
isSignedIn: boolean;
|
||||
output: string[];
|
||||
resizeProps: ResizeProps;
|
||||
saveChallenge: () => void;
|
||||
@@ -118,6 +120,7 @@ const mapStateToProps = createSelector(
|
||||
isDonationModalOpenSelector,
|
||||
isProjectPreviewModalOpenSelector,
|
||||
isResettingSelector,
|
||||
isSignedInSelector,
|
||||
userSelector,
|
||||
challengeTestsSelector,
|
||||
(
|
||||
@@ -127,6 +130,7 @@ const mapStateToProps = createSelector(
|
||||
open,
|
||||
previewOpen: boolean,
|
||||
isResetting: boolean,
|
||||
isSignedIn: boolean,
|
||||
{ theme = Themes.Default }: { theme: Themes },
|
||||
tests: [{ text: string; testString: string }]
|
||||
) => ({
|
||||
@@ -134,6 +138,7 @@ const mapStateToProps = createSelector(
|
||||
challengeType,
|
||||
previewOpen,
|
||||
isResetting,
|
||||
isSignedIn,
|
||||
output,
|
||||
theme,
|
||||
tests
|
||||
@@ -427,7 +432,8 @@ const Editor = (props: EditorProps): JSX.Element => {
|
||||
label: 'Save editor content',
|
||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S],
|
||||
run:
|
||||
props.challengeType === challengeTypes.multiFileCertProject
|
||||
props.challengeType === challengeTypes.multifileCertProject &&
|
||||
props.isSignedIn
|
||||
? // save to database
|
||||
props.saveChallenge
|
||||
: // save to local storage
|
||||
|
||||
@@ -316,7 +316,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
return (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
challengeType === challengeTypes.multiFileCertProject
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
);
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ class ShowClassic extends Component<ShowClassicProps, ShowClassicState> {
|
||||
} = this.getChallenge();
|
||||
|
||||
const showBreadCrumbs =
|
||||
challengeType !== challengeTypes.multiFileCertProject;
|
||||
challengeType !== challengeTypes.multifileCertProject;
|
||||
return (
|
||||
<SidePanel
|
||||
block={block}
|
||||
|
||||
@@ -81,17 +81,18 @@ function ToolPanel({
|
||||
>
|
||||
{isMobile ? t('buttons.run') : t('buttons.run-test')}
|
||||
</Button>
|
||||
{isSignedIn && challengeType === challengeTypes.multiFileCertProject && (
|
||||
{isSignedIn && challengeType === challengeTypes.multifileCertProject && (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
data-cy='save-code-to-database-btn'
|
||||
className='btn-invert'
|
||||
onClick={saveChallenge}
|
||||
>
|
||||
{isMobile ? t('buttons.save') : t('buttons.save-code')}
|
||||
</Button>
|
||||
)}
|
||||
{challengeType !== challengeTypes.multiFileCertProject && (
|
||||
{challengeType !== challengeTypes.multifileCertProject && (
|
||||
<Button
|
||||
block={true}
|
||||
bsStyle='primary'
|
||||
|
||||
@@ -7,6 +7,7 @@ import { setContent, isPoly } from '../../../../../utils/polyvinyl';
|
||||
import { createFlashMessage } from '../../../components/Flash/redux';
|
||||
import { FlashMessages } from '../../../components/Flash/redux/flash-messages';
|
||||
import { actionTypes as appTypes } from '../../../redux/action-types';
|
||||
import { savedChallengesSelector } from '../../../redux';
|
||||
|
||||
import { actionTypes } from './action-types';
|
||||
import {
|
||||
@@ -136,9 +137,20 @@ function loadCodeEpic(action$, state$) {
|
||||
const fileKeys = challengeFiles.map(x => x.fileKey);
|
||||
const invalidForLegacy = fileKeys.length > 1;
|
||||
const { title: legacyKey } = challenge;
|
||||
|
||||
const codeFound = getCode(id);
|
||||
if (codeFound && isFilesAllPoly(codeFound)) {
|
||||
|
||||
// first check if the store (which is syncronized with the db) has saved
|
||||
// code
|
||||
const savedChallenges = savedChallengesSelector(state);
|
||||
const savedChallenge = savedChallenges?.find(saved => {
|
||||
return saved.id === challenge.id;
|
||||
});
|
||||
|
||||
// if the store is already populated with the saved files, we should not
|
||||
// overwrite them with the local storage data
|
||||
if (savedChallenge) {
|
||||
return of(noStoredCodeFound());
|
||||
} else if (codeFound && isFilesAllPoly(codeFound)) {
|
||||
finalFiles = challengeFiles.reduce((challengeFiles, challengeFile) => {
|
||||
let foundChallengeFile = {};
|
||||
// TODO: after sufficient time, say 6 months from this commit, we can
|
||||
|
||||
@@ -85,7 +85,7 @@ function submitModern(type, state) {
|
||||
let body;
|
||||
if (
|
||||
block === 'javascript-algorithms-and-data-structures-projects' ||
|
||||
challengeType === challengeTypes.multiFileCertProject
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
body = standardizeRequestBody({ id, challengeType, challengeFiles });
|
||||
} else {
|
||||
|
||||
@@ -59,8 +59,8 @@ export function* executeCancellableChallengeSaga(payload) {
|
||||
const { challengeType, id } = yield select(challengeMetaSelector);
|
||||
const { challengeFiles } = yield select(challengeDataSelector);
|
||||
|
||||
// if multiFileCertProject, see if body/code size is submittable
|
||||
if (challengeType === challengeTypes.multiFileCertProject) {
|
||||
// if multifileCertProject, see if body/code size is submittable
|
||||
if (challengeType === challengeTypes.multifileCertProject) {
|
||||
const body = standardizeRequestBody({ id, challengeFiles, challengeType });
|
||||
const bodySizeInBytes = getStringSizeInBytes(body);
|
||||
|
||||
|
||||
@@ -189,7 +189,7 @@ export const challengeDataSelector = state => {
|
||||
} else if (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
challengeType === challengeTypes.multiFileCertProject
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
const { required = [], template = '' } = challengeMetaSelector(state);
|
||||
challengeData = {
|
||||
|
||||
@@ -72,7 +72,7 @@ const buildFunctions = {
|
||||
[challengeTypes.backend]: buildBackendChallenge,
|
||||
[challengeTypes.backEndProject]: buildBackendChallenge,
|
||||
[challengeTypes.pythonProject]: buildBackendChallenge,
|
||||
[challengeTypes.multiFileCertProject]: buildDOMChallenge
|
||||
[challengeTypes.multifileCertProject]: buildDOMChallenge
|
||||
};
|
||||
|
||||
export function canBuildChallenge(challengeData) {
|
||||
@@ -94,7 +94,7 @@ const testRunners = {
|
||||
[challengeTypes.html]: getDOMTestRunner,
|
||||
[challengeTypes.backend]: getDOMTestRunner,
|
||||
[challengeTypes.pythonProject]: getDOMTestRunner,
|
||||
[challengeTypes.multiFileCertProject]: getDOMTestRunner
|
||||
[challengeTypes.multifileCertProject]: getDOMTestRunner
|
||||
};
|
||||
export function getTestRunner(buildData, runnerConfig, document) {
|
||||
const { challengeType } = buildData;
|
||||
@@ -149,7 +149,7 @@ export function buildDOMChallenge(
|
||||
.then(challengeFiles => {
|
||||
return {
|
||||
challengeType:
|
||||
challengeTypes.html || challengeTypes.multiFileCertProject,
|
||||
challengeTypes.html || challengeTypes.multifileCertProject,
|
||||
build: concatHtml({
|
||||
required: finalRequires,
|
||||
template,
|
||||
@@ -195,7 +195,7 @@ export function buildBackendChallenge({ url }) {
|
||||
export function updatePreview(buildData, document, proxyLogger) {
|
||||
if (
|
||||
buildData.challengeType === challengeTypes.html ||
|
||||
buildData.challengeType === challengeTypes.multiFileCertProject
|
||||
buildData.challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
createMainPreviewFramer(document, proxyLogger)(buildData);
|
||||
} else {
|
||||
@@ -208,7 +208,7 @@ export function updatePreview(buildData, document, proxyLogger) {
|
||||
export function updateProjectPreview(buildData, document) {
|
||||
if (
|
||||
buildData.challengeType === challengeTypes.html ||
|
||||
buildData.challengeType === challengeTypes.multiFileCertProject
|
||||
buildData.challengeType === challengeTypes.multifileCertProject
|
||||
) {
|
||||
// Give iframe a title attribute for accessibility using the preview
|
||||
// document's <title>.
|
||||
@@ -230,7 +230,7 @@ export function challengeHasPreview({ challengeType }) {
|
||||
return (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
challengeType === challengeTypes.multiFileCertProject
|
||||
challengeType === challengeTypes.multifileCertProject
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { ChallengeFiles } from '../redux/prop-types';
|
||||
|
||||
/*
|
||||
* Express's body-parser has a default size limit of 102400 bytes for a request body.
|
||||
* These helper functions make sure the request body isn't too big when saving or submitting multiFile cert projects
|
||||
* These helper functions make sure the request body isn't too big when saving or submitting multifile cert projects
|
||||
*/
|
||||
|
||||
export const MAX_BODY_SIZE = 102400;
|
||||
|
||||
@@ -9,7 +9,7 @@ export const getSolutionDisplayType = ({
|
||||
challengeType
|
||||
}: CompletedChallenge) => {
|
||||
if (challengeFiles?.length)
|
||||
return challengeType === challengeTypes.multiFileCertProject
|
||||
return challengeType === challengeTypes.multifileCertProject
|
||||
? 'showMultifileProjectSolution'
|
||||
: 'showUserCode';
|
||||
if (!solution) return 'none';
|
||||
|
||||
@@ -13,7 +13,7 @@ const pythonProject = 10;
|
||||
const video = 11;
|
||||
const codeAllyPractice = 12;
|
||||
const codeAllyCert = 13;
|
||||
const multiFileCertProject = 14;
|
||||
const multifileCertProject = 14;
|
||||
|
||||
// individual exports
|
||||
exports.backend = backend;
|
||||
@@ -38,7 +38,7 @@ exports.challengeTypes = {
|
||||
video,
|
||||
codeAllyPractice,
|
||||
codeAllyCert,
|
||||
multiFileCertProject
|
||||
multifileCertProject
|
||||
};
|
||||
|
||||
// (Oliver) I don't think we need this for codeally projects, so they're ignored
|
||||
@@ -74,7 +74,7 @@ exports.viewTypes = {
|
||||
[video]: 'video',
|
||||
[codeAllyPractice]: 'codeAlly',
|
||||
[codeAllyCert]: 'codeAlly',
|
||||
[multiFileCertProject]: 'classic'
|
||||
[multifileCertProject]: 'classic'
|
||||
};
|
||||
|
||||
// determine the type of submit function to use for the challenge on completion
|
||||
@@ -96,7 +96,7 @@ exports.submitTypes = {
|
||||
[modern]: 'tests',
|
||||
[video]: 'tests',
|
||||
[codeAllyCert]: 'project.frontEnd',
|
||||
[multiFileCertProject]: 'tests'
|
||||
[multifileCertProject]: 'tests'
|
||||
};
|
||||
|
||||
// determine which help forum questions should be posted to
|
||||
|
||||
@@ -123,7 +123,7 @@ function getProjectPreviewConfig(challenge, allChallengeEdges) {
|
||||
showProjectPreview:
|
||||
challengeOrder === 0 &&
|
||||
usesMultifileEditor &&
|
||||
challengeType !== challengeTypes.multiFileCertProject,
|
||||
challengeType !== challengeTypes.multifileCertProject,
|
||||
challengeData: {
|
||||
challengeType: lastChallenge.challengeType,
|
||||
challengeFiles: projectPreviewChallengeFiles
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Build a Personal Portfolio Webpage Project",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"order": 19,
|
||||
"time": "30 hours",
|
||||
"template": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Build a Product Landing Page Project",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"order": 16,
|
||||
"time": "30 hours",
|
||||
"template": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Build a Survey Form Project",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"order": 4,
|
||||
"time": "30 hours",
|
||||
"template": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Build a Technical Documentation Page Project",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"order": 13,
|
||||
"time": "30 hours",
|
||||
"template": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Build a Tribute Page Project",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"order": 9,
|
||||
"time": "30 hours",
|
||||
"template": "",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "Responsive Web Design Projects",
|
||||
"isUpcomingChange": false,
|
||||
"usesMultifileEditor": true,
|
||||
"dashedName": "responsive-web-design-projects",
|
||||
"order": 7,
|
||||
"time": "150 hours",
|
||||
|
||||
@@ -1,47 +1,277 @@
|
||||
---
|
||||
id: bd7158d8c242eddfaeb5bd13
|
||||
title: Build a Personal Portfolio Webpage
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301143
|
||||
dashedName: build-a-personal-portfolio-webpage
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/zNBOYG>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/zNBOYG" target="_blank"><https://codepen.io/freeCodeCamp/full/zNBOYG></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your portfolio should have a welcome section with an `id` of `welcome-section`
|
||||
1. The welcome section should have an `h1` element that contains text
|
||||
1. Your portfolio should have a projects section with an `id` of `projects`
|
||||
1. The projects section should contain at least one element with a `class` of `project-tile` to hold a project
|
||||
1. The projects section should contain at least one link to a project
|
||||
1. Your portfolio should have a navbar with an id of `navbar`
|
||||
1. The navbar should contain at least one link that you can click on to navigate to different sections of the page
|
||||
1. Your portfolio should have a link with an id of `profile-link`, which opens your GitHub or freeCodeCamp profile in a new tab
|
||||
1. Your portfolio should have at least one media query
|
||||
1. The height of the welcome section should be equal to the height of the viewport
|
||||
1. The navbar should always be at the top of the viewport
|
||||
|
||||
**User Story #1:** My portfolio should have a welcome section with an id of `welcome-section`.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** The welcome section should have an `h1` element that contains text.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** My portfolio should have a projects section with an id of `projects`.
|
||||
Your portfolio should have a "Welcome" section with an `id` of `welcome-section`.
|
||||
|
||||
**User Story #4:** The projects section should contain at least one element with a class of `project-tile` to hold a project.
|
||||
```js
|
||||
const el = document.getElementById('welcome-section')
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
**User Story #5:** The projects section should contain at least one link to a project.
|
||||
Your `#welcom-section` element should contain an `h1` element.
|
||||
|
||||
**User Story #6:** My portfolio should have a navbar with an id of `navbar`.
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#welcome-section h1').length,
|
||||
0,
|
||||
'Welcome section should contain an h1 element '
|
||||
);
|
||||
```
|
||||
|
||||
**User Story #7:** The navbar should contain at least one link that I can click on to navigate to different sections of the page.
|
||||
You should not have any empty `h1` elements within `#welcome-section` element.
|
||||
|
||||
**User Story #8:** My portfolio should have a link with an id of `profile-link`, which opens my GitHub or FCC profile in a new tab.
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#welcome-section h1')?.[0]?.innerText?.length,
|
||||
0,
|
||||
'h1 element in welcome section should contain your name or camper ' +
|
||||
'name '
|
||||
);
|
||||
```
|
||||
|
||||
**User Story #9:** My portfolio should have at least one media query.
|
||||
You should have a "Projects" section with an `id` of `projects`.
|
||||
|
||||
**User Story #10:** The height of the welcome section should be equal to the height of the viewport.
|
||||
```js
|
||||
const el = document.getElementById('projects')
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
**User Story #11:** The navbar should always be at the top of the viewport.
|
||||
Your portfolio should contain at least one elment with a class of `project-tile`.
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#projects .project-tile').length,
|
||||
0
|
||||
);
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
Your `#projects` element should contain at least one `a` element.
|
||||
|
||||
# --solutions--
|
||||
```js
|
||||
assert.isAbove(document.querySelectorAll('#projects a').length, 0);
|
||||
```
|
||||
|
||||
Your portfolio should have a navbar with an `id` of `navbar`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('navbar');
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
Your `#navbar` element should contain at least one `a` element whose `href` attribute starts with `#`.
|
||||
|
||||
```js
|
||||
const links = [...document.querySelectorAll('#navbar a')].filter(
|
||||
(nav) => (nav?.getAttribute('href') || '').substr(0, 1) === '#'
|
||||
);
|
||||
|
||||
assert.isAbove(
|
||||
links.length,
|
||||
0,
|
||||
'Navbar should contain an anchor link '
|
||||
);
|
||||
```
|
||||
|
||||
Your portfolio should have an `a` element with an `id` of `profile-link`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('profile-link');
|
||||
assert(!!el && el.tagName === 'A')
|
||||
```
|
||||
|
||||
Your `#profile-link` element should have a `target` attribute of `_blank`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('profile-link');
|
||||
assert(!!el && el.target === '_blank')
|
||||
```
|
||||
|
||||
Your portfolio should use at least one media query.
|
||||
|
||||
```js
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
Your `#navbar` element should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
|
||||
const navbar = document.getElementById('navbar');
|
||||
assert.approximately(
|
||||
navbar?.getBoundingClientRect().top,
|
||||
0,
|
||||
15,
|
||||
"Navbar's parent should be body and it should be at the top of " +
|
||||
'the viewport '
|
||||
);
|
||||
|
||||
window.scroll(0, 500);
|
||||
|
||||
await timeout(1);
|
||||
|
||||
assert.approximately(
|
||||
navbar?.getBoundingClientRect().top,
|
||||
0,
|
||||
15,
|
||||
'Navbar should be at the top of the viewport even after ' +
|
||||
'scrolling '
|
||||
);
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<title>Personal Portfolio</title>
|
||||
</head>
|
||||
<body>
|
||||
<link href="https://fonts.googleapis.com/css?family=Pacifico" rel="stylesheet" type="text/css">
|
||||
<!--Font Reference-->
|
||||
<nav id="navbar">
|
||||
<a href="#projects">Projects</a> |
|
||||
<a href="#contact">Contact me</a>
|
||||
</nav>
|
||||
<main>
|
||||
<section id="welcome-section">
|
||||
<br>
|
||||
<h1>It's me!</h1>
|
||||
<img src="https://s.cdpn.io/profiles/user/4369153/512.jpg?1587151780" height=100px>
|
||||
<h2>Naomi Carrigan</h2>
|
||||
<p>Welcome to my portfolio page!</p>
|
||||
</section><hr>
|
||||
<section id="projects">
|
||||
<h1>Projects</h1>
|
||||
<h2><a href="https://codepen.io/nhcarrigan">Here's what I've worked on!</a></h2>
|
||||
<p class="project-tile">
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Algebraic Concepts" src="https://codepen.io/nhcarrigan/embed/preview/NWGrWBR?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/NWGrWBR'>Algebraic Concepts</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Pokemon Daycare Service" src="https://codepen.io/nhcarrigan/embed/preview/mdeEbeq?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/mdeEbeq'>Pokemon Daycare Service</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Togepi Fan Club" src="https://codepen.io/nhcarrigan/embed/preview/vYNGoBE?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/vYNGoBE'>Togepi Fan Club</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Togepi" src="https://codepen.io/nhcarrigan/embed/preview/yLYOWEN?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/yLYOWEN'>Togepi</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
</p></section><hr>
|
||||
<section id="contact">
|
||||
<h1>Contact me!</h1>
|
||||
<h2>Use the links below to get in touch.</h2>
|
||||
<p><a href="https://www.freecodecamp.org/nhcarrigan" id="profile-link" target="_blank" rel="noopener noreferrer">FreeCodeCamp.org</a> | <a href="https://github.com/nhcarrigan" id="github-link" target="_blank" rel="noopener noreferrer">GitHub</a> | <a href="https://www.facebook.com/nhcarrigan" id="facebook-link" target="_blank" rel="noopener noreferrer">Facebook</a> | <a href="https://www.linkedin.com/in/Naomi-l-carrigan/" id="linkedin-link" target="_blank" rel="noopener noreferrer">LinkedIn</a>
|
||||
</section>
|
||||
<footer><a href="../">Return to Project List</a> | <a href="https://www.nhcarrigan.com">Return to HomePage</a></footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
nav{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-size: 24pt;
|
||||
top: 0%;
|
||||
right: 5px;
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
@media (max-width: 500px){
|
||||
nav{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
a{
|
||||
color: #ffffff;
|
||||
}
|
||||
main{
|
||||
text-align: center;
|
||||
background-color: black;
|
||||
font-family:Pacifico
|
||||
}
|
||||
h1{
|
||||
font-size: 48pt;
|
||||
}
|
||||
h2{
|
||||
font-size: 24pt;
|
||||
}
|
||||
p{
|
||||
font-size: 12pt;
|
||||
}
|
||||
#welcome-section{
|
||||
background-color:#251a4a;
|
||||
color: #FFFFFF;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#projects{
|
||||
background-color: #060a9c;
|
||||
color: #ffffff;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#contact{
|
||||
background-color: #03300b;
|
||||
color: #ffffff;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,55 +1,424 @@
|
||||
---
|
||||
id: 587d78af367417b2b2512b04
|
||||
title: Build a Product Landing Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301144
|
||||
dashedName: build-a-product-landing-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/RKRbwL>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/RKRbwL" target="_blank"><https://codepen.io/freeCodeCamp/full/RKRbwL></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your product landing page should have a `header` element with a corresponding `id="header"`
|
||||
1. You can see an image within the `header` element with a corresponding `id="header-img"` (A logo would make a good image here)
|
||||
1. Within the `#header` element, you can see a `nav` element with a corresponding `id="nav-bar"`
|
||||
1. You can see at least three clickable elements inside the `nav` element, each with the class `nav-link`
|
||||
1. When you click a `.nav-link` button in the `nav` element, you are taken to the corresponding section of the landing page
|
||||
1. You can watch an embedded product video with `id="video"`
|
||||
1. Your landing page has a `form` element with a corresponding `id="form"`
|
||||
1. Within the form, there is an `input` field with `id="email"` where you can enter an email address
|
||||
1. The `#email` input field should have placeholder text to let users know what the field is for
|
||||
1. The `#email` input field uses HTML5 validation to confirm that the entered text is an email address
|
||||
1. Within the form, there is a submit `input` with a corresponding `id="submit"`
|
||||
1. When you click the `#submit` element, the email is submitted to a static page (use this mock URL: `https://www.freecodecamp.com/email-submit`)
|
||||
1. The navbar should always be at the top of the viewport
|
||||
1. Your product landing page should have at least one media query
|
||||
1. Your product landing page should utilize CSS flexbox at least once
|
||||
|
||||
**User Story #1:** My product landing page should have a `header` element with a corresponding `id="header"`.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I can see an image within the `header` element with a corresponding `id="header-img"`. A company logo would make a good image here.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** Within the `#header` element I can see a `nav` element with a corresponding `id="nav-bar"`.
|
||||
You should have a `header` element with an `id` of `header`
|
||||
|
||||
**User Story #4:** I can see at least three clickable elements inside the `nav` element, each with the class `nav-link`.
|
||||
```js
|
||||
const el = document.getElementById('header')
|
||||
assert(!!el && el.tagName === 'HEADER')
|
||||
```
|
||||
|
||||
**User Story #5:** When I click a `.nav-link` button in the `nav` element, I am taken to the corresponding section of the landing page.
|
||||
You should have an `img` element with an `id` of `header-img`
|
||||
|
||||
**User Story #6:** I can watch an embedded product video with `id="video"`.
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && el.tagName === 'IMG')
|
||||
```
|
||||
|
||||
**User Story #7:** My landing page has a `form` element with a corresponding `id="form"`.
|
||||
Your `#header-img` should be a descendant of the `#header`
|
||||
|
||||
**User Story #8:** Within the form, there is an `input` field with `id="email"` where I can enter an email address.
|
||||
```js
|
||||
const els = document.querySelectorAll('#header #header-img')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #9:** The `#email` input field should have placeholder text to let the user know what the field is for.
|
||||
Your `#header-img` should have a `src` attribute
|
||||
|
||||
**User Story #10:** The `#email` input field uses HTML5 validation to confirm that the entered text is an email address.
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && !!el.src)
|
||||
```
|
||||
|
||||
**User Story #11:** Within the form, there is a submit `input` with a corresponding `id="submit"`.
|
||||
Your `#header-img`’s `src` value should be a valid URL (starts with `http`)
|
||||
|
||||
**User Story #12:** When I click the `#submit` element, the email is submitted to a static page (use this mock URL: <https://www.freecodecamp.com/email-submit>).
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && /^http/.test(el.src))
|
||||
```
|
||||
|
||||
**User Story #13:** The navbar should always be at the top of the viewport.
|
||||
You should have a `nav` element with an `id` of `nav-bar`
|
||||
|
||||
**User Story #14:** My product landing page should have at least one media query.
|
||||
```js
|
||||
const el = document.getElementById('nav-bar')
|
||||
assert(!!el && el.tagName === 'NAV')
|
||||
```
|
||||
|
||||
**User Story #15:** My product landing page should utilize CSS flexbox at least once.
|
||||
Your `#nav-bar` should be a descendant of the `#header`
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
const els = document.querySelectorAll('#header #nav-bar')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
You should have at least 3 `.nav-link` elements within the `#nav-bar`
|
||||
|
||||
# --solutions--
|
||||
```js
|
||||
const els = document.querySelectorAll('#nav-bar .nav-link')
|
||||
assert(els.length >= 3)
|
||||
```
|
||||
|
||||
Each `.nav-link` element should have an `href` attribute
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('.nav-link')
|
||||
els.forEach(el => {
|
||||
if (!el.href) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Each `.nav-link` element should link to a corresponding element on the landing page (has an `href` with a value of another element's id. e.g. `#footer`)
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('.nav-link')
|
||||
els.forEach(el => {
|
||||
const linkDestination = el.getAttribute('href').slice(1)
|
||||
if (!document.getElementById(linkDestination)) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
You should have a `video` or `iframe` element with an `id` of `video`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('video')
|
||||
assert(!!el && (el.tagName === 'VIDEO' || el.tagName === 'IFRAME'))
|
||||
```
|
||||
|
||||
Your `#video` should have a `src` attribute
|
||||
|
||||
```js
|
||||
const el = document.getElementById('video')
|
||||
assert(!!el && !!el.src)
|
||||
```
|
||||
|
||||
You should have a `form` element with an `id` of `form`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('form')
|
||||
assert(!!el && el.tagName === 'FORM')
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#email` should be a descendant of the `#form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#form #email')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should have the `placeholder` attribute with placeholder text
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should use HTML5 validation by setting its `type` to `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.type === 'email')
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#submit` should be a descendant of the `#form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#form #submit')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Your `#submit` should have a `type` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.type === 'submit')
|
||||
```
|
||||
|
||||
Your `#form` should have an `action` attribute of `https://www.freecodecamp.com/email-submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('form')
|
||||
assert(!!el && el.action === 'https://www.freecodecamp.com/email-submit')
|
||||
```
|
||||
|
||||
Your `#email` should have a `name` attribute of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.name === 'email')
|
||||
```
|
||||
|
||||
Your `#nav-bar` should always be at the top of the viewport
|
||||
|
||||
```js
|
||||
const el = document.getElementById('nav-bar')
|
||||
const top1 = el?.offsetTop
|
||||
const top2 = el?.offsetTop
|
||||
assert(!!el && top1 >= -15 && top1 <= 15 && top2 >= -15 && top2 <= 15)
|
||||
```
|
||||
|
||||
Your Product Landing Page should use at least one media query
|
||||
|
||||
```js
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
Your Product Landing Page should use CSS Flexbox at least once
|
||||
|
||||
```js
|
||||
const stylesheet = new __helpers.CSSHelp(document).getStyleSheet()
|
||||
const cssRules = new __helpers.CSSHelp(document).styleSheetToCssRulesArray(stylesheet)
|
||||
const usesFlex = cssRules.find(rule => {
|
||||
return rule.style?.display === 'flex' || rule.style?.display === 'inline-flex'
|
||||
})
|
||||
assert(usesFlex)
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Product Landing Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header id="header">
|
||||
<nav id="nav-bar">
|
||||
<img
|
||||
id="header-img"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="50px"
|
||||
/>
|
||||
<a href="#Features" class="nav-link">Features</a> |
|
||||
<a href="#Video" class="nav-link">See our facility!</a> |
|
||||
<a href="#Pricing" class="nav-link">Purchase</a>
|
||||
<hr />
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h1>
|
||||
Pokemon Daycare Service
|
||||
</h1>
|
||||
<section id="Features">
|
||||
<h2>What we offer</h2>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">Guaranteed friendly and loving staff!</div>
|
||||
</div>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">
|
||||
Comfortable environment for Pokemon to explore and play!
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">
|
||||
Multiple membership plans to fit your lifestyle!
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Video">
|
||||
<h2>Check us out!</h2>
|
||||
A sneak peek into our facility:
|
||||
<br />
|
||||
<iframe
|
||||
id="video"
|
||||
width="520"
|
||||
height="347"
|
||||
src="https://www.youtube.com/embed/Nw-ksH2r6AQ"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
alt="A video tour of our facility"
|
||||
>
|
||||
</iframe>
|
||||
</section>
|
||||
<section id="Pricing">
|
||||
<h2>Membership Plans</h2>
|
||||
<div class="flex-mem">
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Basic Membership</font><br />
|
||||
<ul>
|
||||
<li>One Pokemon</li>
|
||||
<li>Food and berries provided</li>
|
||||
</ul>
|
||||
<em>$9.99/month</em>
|
||||
</div>
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Silver Membership</font><br />
|
||||
<ul>
|
||||
<li>Up to Three Pokemon</li>
|
||||
<li>Food and berries provided</li>
|
||||
<li>Grooming and accessories included</li>
|
||||
</ul>
|
||||
<em>$19.99/month</em>
|
||||
</div>
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Gold Membership</font><br />
|
||||
<ul>
|
||||
<li>Up to six Pokemon!</li>
|
||||
<li>Food and berries provided</li>
|
||||
<li>Grooming and accessories included</li>
|
||||
<li>Personal training for each Pokemon</li>
|
||||
<li>Breeding and egg hatching</li>
|
||||
</ul>
|
||||
<em>$29.99/month</em>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<form id="form" action="https://www.freecodecamp.com/email-submit">
|
||||
<p>Sign up for our newsletter!</p>
|
||||
<label for="email"><p>Email:</p><input name="email" id="email" type="email" placeholder="johnsmith@email.com" required></label>
|
||||
<input type="submit" id="submit">
|
||||
</form>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background-color: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
main {
|
||||
max-width: 750px;
|
||||
margin: 50px auto;
|
||||
}
|
||||
input {
|
||||
background-color: #92869c;
|
||||
}
|
||||
a:not(.nav-link) {
|
||||
color: white;
|
||||
}
|
||||
#header-img {
|
||||
max-height: 25px;
|
||||
}
|
||||
#nav-bar {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
top: 0%;
|
||||
background-color: #92869c;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
||||
#bullet {
|
||||
max-height: 25px;
|
||||
}
|
||||
.flex-here {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.flex-left {
|
||||
height: 25px;
|
||||
}
|
||||
.flex-mem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.flex-mem-box {
|
||||
background-color: #92869c;
|
||||
border-color: black;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
color: black;
|
||||
}
|
||||
@media (max-width: 350px) {
|
||||
#video {
|
||||
width: 300;
|
||||
height: 200;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,57 +1,516 @@
|
||||
---
|
||||
id: 587d78af367417b2b2512b03
|
||||
title: Build a Survey Form
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301145
|
||||
dashedName: build-a-survey-form
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/VPaoNP>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/VPaoNP" target="_blank"><https://codepen.io/freeCodeCamp/full/VPaoNP></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. You should have a page title in an `h1` element with an `id` of `title`
|
||||
1. You should have a short explanation in a `p` element with an `id` of `description`
|
||||
1. You should have a `form` element with an `id` of `survey-form`
|
||||
1. Inside the form element, you are **required** to enter your name in an `input` field that has an `id` of `name` and a `type` of `text`
|
||||
1. Inside the form element, you are **required** to enter your email in an `input` field that has an `id` of `email`
|
||||
1. If you enter an email that is not formatted correctly, you will see an HTML5 validation error
|
||||
1. Inside the form, you can enter a number in an `input` field that has an `id` of `number`
|
||||
1. If you enter non-numbers in the number input, you will see an HTML5 validation error
|
||||
1. If you enter numbers outside the range of the number input, which are defined by the `min` and `max` attributes, you will see an HTML5 validation error
|
||||
1. For the name, email, and number input fields, you can see corresponding `label` elements in the form, that describe the purpose of each field with the following ids: `id="name-label"`, `id="email-label"`, and `id="number-label"`
|
||||
1. For the name, email, and number input fields, you can see placeholder text that gives a description or instructions for each field
|
||||
1. Inside the form element, you should have a `select` dropdown element with an `id` of `dropdown` and at least two options to choose from
|
||||
1. Inside the form element, you can select an option from a group of at least two radio buttons that are grouped using the `name` attribute
|
||||
1. Inside the form element, you can select several fields from a series of checkboxes, each of which must have a `value` attribute
|
||||
1. Inside the form element, you are presented with a `textarea` for additional comments
|
||||
1. Inside the form element, you are presented with a button with `id` of `submit` to submit all the inputs
|
||||
|
||||
**User Story #1:** I can see a title with `id="title"` in H1 sized text.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I can see a short explanation with `id="description"` in P sized text.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** I can see a `form` with `id="survey-form"`.
|
||||
You should have an `h1` element with an `id` of `title`
|
||||
|
||||
**User Story #4:** Inside the form element, I am required to enter my name in a field with `id="name"`.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.tagName === 'H1')
|
||||
```
|
||||
|
||||
**User Story #5:** Inside the form element, I am required to enter an email in a field with `id="email"`.
|
||||
Your `#title` should not be empty
|
||||
|
||||
**User Story #6:** If I enter an email that is not formatted correctly, I will see an HTML5 validation error.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
**User Story #7:** Inside the form, I can enter a number in a field with `id="number"`.
|
||||
You should have a `p` element with an `id` of `description`
|
||||
|
||||
**User Story #8:** If I enter non-numbers in the number input, I will see an HTML5 validation error.
|
||||
```js
|
||||
const el = document.getElementById('description')
|
||||
assert(!!el && el.tagName === 'P')
|
||||
```
|
||||
|
||||
**User Story #9:** If I enter numbers outside the range of the number input, which are defined by the `min` and `max` attributes, I will see an HTML5 validation error.
|
||||
Your `#description` should not be empty
|
||||
|
||||
**User Story #10:** For the name, email, and number input fields inside the form I can see corresponding labels that describe the purpose of each field with the following ids: `id="name-label"`, `id="email-label"`, and `id="number-label"`.
|
||||
```js
|
||||
const el = document.getElementById('description')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
**User Story #11:** For the name, email, and number input fields, I can see placeholder text that gives me a description or instructions for each field.
|
||||
You should have a `form` element with an `id` of `survey-form`
|
||||
|
||||
**User Story #12:** Inside the form element, I can select an option from a dropdown that has a corresponding `id="dropdown"`.
|
||||
```js
|
||||
const el = document.getElementById('survey-form')
|
||||
assert(!!el && el.tagName === 'FORM')
|
||||
```
|
||||
|
||||
**User Story #13:** Inside the form element, I can select a field from one or more groups of radio buttons. Each group should be grouped using the `name` attribute.
|
||||
You should have an `input` element with an `id` of `name`
|
||||
|
||||
**User Story #14:** Inside the form element, I can select several fields from a series of checkboxes, each of which must have a `value` attribute.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
**User Story #15:** Inside the form element, I am presented with a `textarea` at the end for additional comments.
|
||||
Your `#name` should have a `type` of `text`
|
||||
|
||||
**User Story #16:** Inside the form element, I am presented with a button with `id="submit"` to submit all my inputs.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.type === 'text')
|
||||
```
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
Your `#name` should require input
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.required)
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
Your `#name` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #name')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#email` should have a `type` of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.type === 'email')
|
||||
```
|
||||
|
||||
Your `#email` should require input
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.required)
|
||||
```
|
||||
|
||||
Your `#email` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #email')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `number`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#number` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #number')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#number` should have a `type` of `number`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.type === 'number')
|
||||
```
|
||||
|
||||
Your `#number` should have a `min` attribute with a numeric value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.min && isFinite(el.min))
|
||||
```
|
||||
|
||||
Your `#number` should have a `max` attribute with a numeric value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.max && isFinite(el.max))
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `name-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `email-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `number-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
Your `#name-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#email-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#number-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#name-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #name-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#email-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #email-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#number-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #number-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#name` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#number` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
You should have a `select` field with an `id` of `dropdown`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('dropdown')
|
||||
assert(!!el && el.tagName === 'SELECT')
|
||||
```
|
||||
|
||||
Your `#dropdown` should have at least two selectable (not disabled) `option` elements
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#dropdown option:not([disabled])')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
Your `#dropdown` should be a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #dropdown')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have at least two `input` elements with a `type` of `radio` (radio buttons)
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('input[type="radio"]')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
You should have at least two radio buttons that are descendants of `#survey-form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#survey-form input[type="radio"]')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
All your radio buttons should have a `value` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('input[type="radio"]')
|
||||
const els2 = document.querySelectorAll('input[type="radio"][value=""], input[type="radio"]:not([value])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
All your radio buttons should have a `name` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('input[type="radio"]')
|
||||
const els2 = document.querySelectorAll('input[type="radio"][name=""], input[type="radio"]:not([name])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
Every radio button group should have at least 2 radio buttons
|
||||
|
||||
```js
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
const groups = {}
|
||||
|
||||
if (radioButtons) {
|
||||
radioButtons.forEach(el => {
|
||||
if (!groups[el.name]) groups[el.name] = []
|
||||
groups[el.name].push(el)
|
||||
})
|
||||
}
|
||||
|
||||
const groupKeys = Object.keys(groups)
|
||||
|
||||
groupKeys.forEach(key => {
|
||||
if (groups[key].length < 2) assert(false)
|
||||
})
|
||||
|
||||
assert(groupKeys.length > 0)
|
||||
```
|
||||
|
||||
You should have at least two `input` elements with a `type` of `checkbox` (checkboxes) that are descendants of `#survey-form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#survey-form input[type="checkbox"]');
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
All your checkboxes inside `#survey-form` should have a `value` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('#survey-form input[type="checkbox"]')
|
||||
const els2 = document.querySelectorAll('#survey-form input[type="checkbox"][value=""], #survey-form input[type="checkbox"]:not([value])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
You should have at least one `textarea` element that is a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form textarea')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` or `button` element with an `id` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && (el.tagName === 'INPUT' || el.tagName === 'BUTTON'))
|
||||
```
|
||||
|
||||
Your `#submit` should have a `type` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.type === 'submit')
|
||||
```
|
||||
|
||||
Your `#submit` should be a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #submit')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Survey Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Survey Form</h1>
|
||||
<p>The card below was built as a sample survey form for freeCodeCamp.</p>
|
||||
<main id="main">
|
||||
<h1 id="title">Join the Togepi Fan Club!</h1>
|
||||
<p id="description">
|
||||
Enter your information here to receive updates about club activities,
|
||||
our monthly newsletter, and other email communications.
|
||||
</p>
|
||||
<form id="survey-form" action="#">
|
||||
<label for="name" id="name-label"
|
||||
><p>Name:</p>
|
||||
<input type="text" id="name" placeholder="e.g. John Smith" required />
|
||||
</label>
|
||||
<label for="email" id="email-label"
|
||||
><p>Email:</p>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="e.g. john.smith@email.com"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label for="age" id="number-label"
|
||||
><p>Age<em>(optional)</em>:</p>
|
||||
<input
|
||||
type="number"
|
||||
id="number"
|
||||
placeholder="e.g. 19"
|
||||
min="18"
|
||||
max="99"
|
||||
/>
|
||||
</label>
|
||||
<label for="interest" id="interest-label"
|
||||
><p>What are you most interested in?</p>
|
||||
<select id="dropdown">
|
||||
<option selected disabled hidden></option>
|
||||
<option id="battles">Battling</option>
|
||||
<option id="breeding">Breeding</option>
|
||||
<option id="catching">Completing my Pokedex</option>
|
||||
<option id="exploring">Exploring new regions</option>
|
||||
</select>
|
||||
</label>
|
||||
<p>Who is your favourite Pokemon?</p>
|
||||
<label for="togepi">
|
||||
<input
|
||||
id="togepi"
|
||||
type="radio"
|
||||
name="favorite"
|
||||
value="togepi"
|
||||
/>Togepi!
|
||||
</label>
|
||||
<label for="pikachu">
|
||||
<input
|
||||
id="pikachu"
|
||||
type="radio"
|
||||
name="favorite"
|
||||
value="pikachu"
|
||||
/>Pikachu
|
||||
</label>
|
||||
<label for="other">
|
||||
<input id="other" type="radio" name="favorite" value="other" />Other
|
||||
</label>
|
||||
<p>Which communications do you want to receive?</p>
|
||||
<label for="newsletter">
|
||||
<input
|
||||
id="newsletter"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="newsletter"
|
||||
/>Newsletter
|
||||
</label>
|
||||
<label for="events">
|
||||
<input
|
||||
id="events"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="events"
|
||||
/>Event updates
|
||||
</label>
|
||||
<label for="updates">
|
||||
<input
|
||||
id="updates"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="updates"
|
||||
/>Club updates
|
||||
</label>
|
||||
<p>Any other information you would like to share?</p>
|
||||
<textarea id="additional-information" rows="4" cols="50">
|
||||
You can provide comments, suggestions, or feedback here.</textarea
|
||||
>
|
||||
<p>
|
||||
<em
|
||||
>Please note: This form is a proof of concept. Submitting the form
|
||||
will not actually transmit your data.</em
|
||||
>
|
||||
</p>
|
||||
<button type="Submit" id="submit">Submit</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
main {
|
||||
text-align: center;
|
||||
background-color: #92869c;
|
||||
background-blend-mode: lighten;
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
border-radius: 50px;
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
|
||||
color: black;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
background: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
input, textarea, select, button {
|
||||
background: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,55 +1,525 @@
|
||||
---
|
||||
id: 587d78b0367417b2b2512b05
|
||||
title: Build a Technical Documentation Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301146
|
||||
dashedName: build-a-technical-documentation-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/NdrKKL>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/NdrKKL" target="_blank"><https://codepen.io/freeCodeCamp/full/NdrKKL></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
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
|
||||
|
||||
**User Story #1:** I can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** Within the `#main-doc` element, I can see several `section` elements, each with a class of `main-section`. There should be a minimum of 5.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** The first element within each `.main-section` should be a `header` element which contains text that describes the topic of that section.
|
||||
You should have a `main` element with an `id` of `main-doc`
|
||||
|
||||
**User Story #4:** 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"`).
|
||||
```js
|
||||
const el = document.getElementById('main-doc')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
**User Story #5:** The `.main-section` elements should contain at least 10 `p` elements total (not each).
|
||||
You should have at least five `section` elements with a class of `main-section`
|
||||
|
||||
**User Story #6:** The `.main-section` elements should contain at least 5 `code` elements total (not each).
|
||||
```js
|
||||
const els = document.querySelectorAll('#main-doc section')
|
||||
assert(els.length >= 5)
|
||||
```
|
||||
|
||||
**User Story #7:** The `.main-section` elements should contain at least 5 `li` items total (not each).
|
||||
All of your `.main-section` elements should be `section` elements
|
||||
|
||||
**User Story #8:** I can see a `nav` element with a corresponding `id="navbar"`.
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if (el.tagName !== 'SECTION') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #9:** The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
|
||||
You should have at least five `.main-section` elements that are descendants of `#main-doc`
|
||||
|
||||
**User Story #10:** 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`.
|
||||
```js
|
||||
const els = document.querySelectorAll('#main-doc .main-section')
|
||||
assert(els.length >= 5)
|
||||
```
|
||||
|
||||
**User Story #11:** The `header` element in the navbar must come before any link (`a`) elements in the navbar.
|
||||
The first child of each `.main-section` should be a `header` element
|
||||
|
||||
**User Story #12:** 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").
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #13:** When I click on a navbar element, the page should navigate to the corresponding section of the `main-doc` element (e.g. If I click on a `nav-link` element that contains the text "Hello world", the page navigates to a `section` element that has that id and contains the corresponding `header`.
|
||||
None of your `header` elements should be empty
|
||||
|
||||
**User Story #14:** 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.
|
||||
```js
|
||||
const els = document.querySelectorAll('header')
|
||||
els.forEach(el => {
|
||||
if (el.innerText?.length <= 0) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #15:** My Technical Documentation page should use at least one media query.
|
||||
All of your `.main-section` elements should have an `id`
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if (!el.id || el.id === '') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
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
|
||||
|
||||
# --solutions--
|
||||
```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[class="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 && headerText.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
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Technical Documentation Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navbar">
|
||||
<header><br />Algebraic Concepts</header>
|
||||
<hr />
|
||||
<a href="#introduction" class="nav-link">Introduction</a><br />
|
||||
<hr />
|
||||
<a href="#definitions" class="nav-link">Definitions</a><br />
|
||||
<hr />
|
||||
<a href="#examples" class="nav-link">Examples</a><br />
|
||||
<hr />
|
||||
<a href="#solving_equations" class="nav-link">Solving Equations</a><br />
|
||||
<hr />
|
||||
<a href="#solving_equations_ii" class="nav-link">Solving Equations II</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#solving_equations_iii" class="nav-link">Solving Equations III</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#system_of_equations" class="nav-link">System of Equations</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#try_it_yourself!" class="nav-link">Try it Yourself!</a><br />
|
||||
<hr />
|
||||
<a href="#more_information" class="nav-link">More Information</a><br />
|
||||
</nav>
|
||||
<main id="main-doc">
|
||||
<section class="main-section" id="introduction">
|
||||
<header>Introduction</header>
|
||||
<p>
|
||||
Welcome to a basic introduction of algebra. In this tutorial, we will
|
||||
review some of the more common algebraic concepts.
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="definitions">
|
||||
<header>Definitions</header>
|
||||
<p>
|
||||
To start with, let's define some of the more common terms used in
|
||||
algebra:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variable:</b> A variable is an unknown value, usually represented
|
||||
by a letter.
|
||||
</li>
|
||||
<li>
|
||||
<b>Expression:</b> Essentially a mathematical object. For the
|
||||
purpose of this tutorial, an expression is one part of an equation.
|
||||
</li>
|
||||
<li>
|
||||
<b>Equation:</b> An equation is a mathematical argument in which two
|
||||
expressions result in the same value.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="main-section" id="examples">
|
||||
<header>Examples</header>
|
||||
<p>
|
||||
Sometimes it is easier to understand the definitions when you have a
|
||||
physical example to look at. Here is an example of the above terms.<br /><br />
|
||||
<code>x + 5 = 12 </code><br /><br />
|
||||
In this above example, we have:
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>Variable:</b> The variable in the example is "x".</li>
|
||||
<li>
|
||||
<b>Expression:</b> There are two expressions in this example. They
|
||||
are "x+5" and "12".
|
||||
</li>
|
||||
<li>
|
||||
<b>Equation:</b> The entire example, "x+5=12", is an equation.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations">
|
||||
<header>Solving Equations</header>
|
||||
<p>
|
||||
The primary use for algebra is to determine an unknown value, the
|
||||
"variable", with the information provided. Continuing to use our
|
||||
example from above, we can find the value of the variable "x".<br /><br />
|
||||
<code>x + 5 = 12 </code><br /><br />
|
||||
In an equation, both sides result in the same value. So you can
|
||||
manipulate the two expressions however you need, as long as you
|
||||
perform the same operation (or change) to each side. You do this
|
||||
because the goal when solving an equation is to
|
||||
<b
|
||||
>get the variable into its own expression, or by itself on one side
|
||||
of the = sign.</b
|
||||
><br />For this example, we want to remove the "+5" so the "x" is
|
||||
alone. To do this, we can <em>subtract 5</em>, because subtraction is
|
||||
the opposite operation to addition. But remember, we have to perform
|
||||
the same operation to both sides of the equation. Now our equation
|
||||
looks like this.<br /><br />
|
||||
<code>x + 5 - 5 = 12 - 5</code><br /><br />
|
||||
The equation looks like a mess right now, because we haven't completed
|
||||
the operations. We can <b>simplify</b> this equation to make it easier
|
||||
to read by performing the operations "5-5" and "12-5". The result
|
||||
is:<br /><br />
|
||||
<code>x = 7</code><br /><br />
|
||||
We now have our solution to this equation!
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations_ii">
|
||||
<header>Solving Equations II</header>
|
||||
<p>
|
||||
Let us look at a slightly more challenging equation.<br /><br />
|
||||
<code>3x + 4 = 13</code><br /><br />
|
||||
Again we can start with subtraction. In this case, we want to subtract
|
||||
4 from each side of the equation. We will also go ahead and simplify
|
||||
with each step. So now we have:<br /><br />
|
||||
<code>3x = 9</code><br /><br />
|
||||
"3x" translates to "3*x", where the "*" symbol indicates
|
||||
multiplication. We use the "*" to avoid confusion, as the "x" is now a
|
||||
variable instead of a multiplication symbol. The opposite operation
|
||||
for multiplication is division, so we need to
|
||||
<b>divide each expression by 3</b>.<br /><br />
|
||||
<code>x = 3</code><br /><br />
|
||||
And now we have our solution!
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations_iii">
|
||||
<header>Solving Equations III</header>
|
||||
<p>
|
||||
Now we are getting in to more complex operations. Here is another
|
||||
equation for us to look at:<br /><br />
|
||||
<code>x^2 - 8 = 8</code><br /><br />
|
||||
Our very first step will be to <b>add</b> 8 to each side. This is
|
||||
different from our previous examples, where we had to subtract. But
|
||||
remember, our goal is to get the variable alone by performing opposite
|
||||
operations.<br /><br />
|
||||
<code>x^2 = 16</code><br /><br />
|
||||
But what does the "^2" mean? The "^" symbol is used to denote
|
||||
exponents in situations where superscript is not available. When
|
||||
superscript <b>is</b> available, you would see it as x<sup>2</sup>.
|
||||
For the sake of this project, however, we will use the "^" symbol.<br />
|
||||
An exponent tells you how many times the base (in our case, "x") is
|
||||
multiplied by itself. So, "x^2" would be the same as "x*x". Now the
|
||||
opposite function of multiplication is division, but we would have to
|
||||
<b>divide both sides by "x"</b>. We do not want to do this, as that
|
||||
would put an "x" on the other side of the equation. So instead, we
|
||||
need to use the root operation! For an exponent of "2", we call this
|
||||
the "square root" and denote it with "√". Our equation is now:
|
||||
<br /><br />
|
||||
<code>x = √9</code><br /><br />
|
||||
Performing a root operation by hand can be a tedious process, so we
|
||||
recommend using a calculator when necessary. However, we are lucky in
|
||||
that "9" is a
|
||||
<a href="https://en.wikipedia.org/wiki/Square_number"
|
||||
>perfect square</a
|
||||
>, so we do not need to calculate anything. Instead, we find our
|
||||
answer to be:<br /><br />
|
||||
<code>x = 3</code>
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="system_of_equations">
|
||||
<header>System of Equations</header>
|
||||
<p>
|
||||
As you explore your algebra studies further, you may start to run
|
||||
across equations with more than one variable. The first such equations
|
||||
will likely look like:<br /><br />
|
||||
<code>y = 3x</code><br /><br />
|
||||
An equation like this does <b>not have one single solution</b>.
|
||||
Rather, there are a series of values for which the equation is true.
|
||||
For example, if "x=3" and "y=9", the equation is true. These equations
|
||||
are usually used to plot a graph. <br />
|
||||
Getting more complicated, though, you may be given a <b>pair</b> of
|
||||
equations. This is called a "system of equations", and CAN be solved.
|
||||
Let's look at how we do this! Consider the following system of
|
||||
equations:<br /><br />
|
||||
<code>y = 3x | y - 6 = x</code>
|
||||
A system of equations IS solvable, but it is a multi-step process. To
|
||||
get started, we need to chose a variable we are solving for. Let's
|
||||
solve for "x" first. From the second equation, we know that "x" equals
|
||||
"y - 6", but we cannot simplify that further because we do not have a
|
||||
value for "y". Except, thanks to the system of equations, we DO have a
|
||||
value for "y". We know that "y" equals "3x". So, looking at our second
|
||||
equation, we can replace "y" with "3x" because they have the same
|
||||
value. We then get:<br /><br />
|
||||
<code>3x - 6 = x</code><br /><br />
|
||||
Now we can solve for "x"! We start by adding 6 to each side.<br /><br />
|
||||
<code>3x = x + 6</code><br /><br />
|
||||
We still need to get "x" by itself, so we subtract "x" from both sides
|
||||
and get:<br /><br />
|
||||
<code>2x = 6</code><br /><br />
|
||||
If this confuses you, remember that "3x" is the same as "x+x+x".
|
||||
Subtract an "x" from that and you get "x+x", or "2x". Now we divide
|
||||
both sides by 2 and have our value for x!<br /><br />
|
||||
<code>x = 3</code><br /><br />
|
||||
However, our work is not done yet. We still need to find the value for
|
||||
"y". Let's go back to our first equation:<br /><br />
|
||||
<code>y = 3x</code><br /><br />
|
||||
We have a value for "x" now, so let's see what happens if we put that
|
||||
value in.<br /><br />
|
||||
<code>y = 3*3</code><br /><br />
|
||||
We perform the multiplication and discover that "y=9"! Our solution to
|
||||
this system of equations then is:<br /><br />
|
||||
<code>x = 3 and y = 9</code><br /><br />
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="try_it_yourself!">
|
||||
<header>Try it Yourself!</header>
|
||||
<p>Coming Soon!</p>
|
||||
<p>Keep an eye out for new additions!</p>
|
||||
</section>
|
||||
<section class="main-section" id="more_information">
|
||||
<header>More Information</header>
|
||||
<p>Check out the following links for more information!</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.wolframalpha.com/examples/mathematics/algebra/"
|
||||
>Wolfram Alpha</a
|
||||
>
|
||||
is a great source for multiple mathematic fields.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://en.wikipedia.org/wiki/Algebra"
|
||||
>Wikipedia's Algebra page</a
|
||||
>
|
||||
for more general information.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
* {
|
||||
background-color: #3a3240;
|
||||
}
|
||||
a {
|
||||
color: #92869c;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #92869c;
|
||||
color: #3a3240;
|
||||
}
|
||||
#navbar {
|
||||
border-style: solid;
|
||||
border-width: 5px;
|
||||
border-color: #92869c;
|
||||
height: 100%;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
color: #92869c
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
#navbar {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
main {
|
||||
margin-left: 220px;
|
||||
color: #92869c
|
||||
}
|
||||
header {
|
||||
font-size: 20pt;
|
||||
}
|
||||
code {
|
||||
background-color: #92869c;
|
||||
border-style: dashed;
|
||||
border-width: 2px;
|
||||
border-color: #92869c;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,43 +1,327 @@
|
||||
---
|
||||
id: bd7158d8c442eddfaeb5bd18
|
||||
title: Build a Tribute Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301147
|
||||
dashedName: build-a-tribute-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/zNqgVx>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/zNqgVx" target="_blank"><https://codepen.io/freeCodeCamp/full/zNqgVx></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your tribute page should have an element with a corresponding `id="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
|
||||
|
||||
**User Story #1:** My tribute page should have an element with a corresponding `id="main"`, which contains all other elements.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I should see an element with a corresponding `id="title"`, which contains a string (i.e. text) that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** I should see either a `figure` or a `div` element with a corresponding `id="img-div"`.
|
||||
You should have a `main` element with an `id` of `main`
|
||||
|
||||
**User Story #4:** Within the `img-div` element, I should see an `img` element with a corresponding `id="image"`.
|
||||
```js
|
||||
const el = document.getElementById('main')
|
||||
assert(!!el && el.tagName === 'MAIN')
|
||||
```
|
||||
|
||||
**User Story #5:** Within the `img-div` element, I should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `img-div`.
|
||||
Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`
|
||||
|
||||
**User Story #6:** I should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
|
||||
```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)
|
||||
```
|
||||
|
||||
**User Story #7:** I 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 (i.e. `target="_blank"`).
|
||||
You should have an element with an `id` of `title`
|
||||
|
||||
**User Story #8:** The `img` element should responsively resize, relative to the width of its parent element, without exceeding its original size.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
**User Story #9:** The `img` element should be centered within its parent element.
|
||||
Your `#title` should not be empty
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
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')
|
||||
```
|
||||
|
||||
You should use an `#image` selector in your CSS to style the `#image` and pass the next three tests
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')
|
||||
assert(!!style)
|
||||
```
|
||||
|
||||
Your `#image` should have a `display` of `block`
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('display')
|
||||
assert(style === 'block')
|
||||
```
|
||||
|
||||
Your `#image` should have a `max-width` of `100%`
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')?.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 maxWidthValue = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('max-width')
|
||||
const displayValue = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('display')
|
||||
const oldDisplayValue = img?.style.getPropertyValue('display');
|
||||
const oldDisplayPriority = img?.style.getPropertyPriority('display');
|
||||
img?.style.setProperty('display', 'none', 'important');
|
||||
const heightValue = new __helpers.CSSHelp(document).getStyle('#image')?.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
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Pacifico"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Lobster"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
<title>Tribute Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tribute Page</h1>
|
||||
<p>The below card was designed as a tribute page for freeCodeCamp.</p>
|
||||
<main id="main">
|
||||
<div id="img-div">
|
||||
<img
|
||||
id="image"
|
||||
class="border"
|
||||
src="https://upload.wikimedia.org/wikipedia/en/5/53/Pok%C3%A9mon_Togepi_art.png"
|
||||
alt="An image of Togepi"
|
||||
/>
|
||||
<figcaption id="img-caption">Togepi, happy as always.</figcaption>
|
||||
</div>
|
||||
<h2 id="title">Togepi</h2>
|
||||
<hr />
|
||||
<div id="tribute-info">
|
||||
<p>
|
||||
Togepi was first discovered in the Johto region, when Ash Ketchum
|
||||
discovered a mysterious egg. However, when the egg hatched, Togepi saw
|
||||
Ash's friend Misty first and imprinted on her. Like many other
|
||||
creatures, this imprinting process created a bond and Togepi views
|
||||
Misty as his mother.
|
||||
</p>
|
||||
<p>
|
||||
Togepi is a very childlike Pokemon, and is very emotionally
|
||||
expressive. He demonstrates extreme levels of joy and sadness.
|
||||
</p>
|
||||
<hr />
|
||||
<p><u>Battle Information</u></p>
|
||||
<ul style="list-style-type: none">
|
||||
<li>Type: Fairy</li>
|
||||
<li>Evolutions: Togepi -> Togetic -> Togekiss</li>
|
||||
<li>Moves: Growl, Pound, Sweet Kiss, Charm</li>
|
||||
<li>Weaknesses: Poison, Steel</li>
|
||||
<li>Resistances: Dragon</li>
|
||||
</ul>
|
||||
<p>
|
||||
Check out this
|
||||
<a
|
||||
id="tribute-link"
|
||||
href="https://bulbapedia.bulbagarden.net/wiki/Togepi_(Pok%C3%A9mon)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Bulbapedia article on Togepi</a
|
||||
>
|
||||
for more information on this great Pokemon.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background-color: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
main {
|
||||
background-color: #92869c;
|
||||
font-family: Lobster;
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
color: black;
|
||||
border-radius: 50px;
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 20pt;
|
||||
font-family: Pacifico;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.border {
|
||||
border-color: black;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
}
|
||||
#image {
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#img-caption {
|
||||
font-size: 10pt;
|
||||
}
|
||||
a:not(#tribute-link) {
|
||||
color: white;
|
||||
}
|
||||
hr {
|
||||
border-color: black;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,47 +1,277 @@
|
||||
---
|
||||
id: bd7158d8c242eddfaeb5bd13
|
||||
title: Build a Personal Portfolio Webpage
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301143
|
||||
dashedName: build-a-personal-portfolio-webpage
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/zNBOYG>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/zNBOYG" target="_blank"><https://codepen.io/freeCodeCamp/full/zNBOYG></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your portfolio should have a welcome section with an `id` of `welcome-section`
|
||||
1. The welcome section should have an `h1` element that contains text
|
||||
1. Your portfolio should have a projects section with an `id` of `projects`
|
||||
1. The projects section should contain at least one element with a `class` of `project-tile` to hold a project
|
||||
1. The projects section should contain at least one link to a project
|
||||
1. Your portfolio should have a navbar with an id of `navbar`
|
||||
1. The navbar should contain at least one link that you can click on to navigate to different sections of the page
|
||||
1. Your portfolio should have a link with an id of `profile-link`, which opens your GitHub or freeCodeCamp profile in a new tab
|
||||
1. Your portfolio should have at least one media query
|
||||
1. The height of the welcome section should be equal to the height of the viewport
|
||||
1. The navbar should always be at the top of the viewport
|
||||
|
||||
**User Story #1:** My portfolio should have a welcome section with an id of `welcome-section`.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** The welcome section should have an `h1` element that contains text.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** My portfolio should have a projects section with an id of `projects`.
|
||||
Your portfolio should have a "Welcome" section with an `id` of `welcome-section`.
|
||||
|
||||
**User Story #4:** The projects section should contain at least one element with a class of `project-tile` to hold a project.
|
||||
```js
|
||||
const el = document.getElementById('welcome-section')
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
**User Story #5:** The projects section should contain at least one link to a project.
|
||||
Your `#welcom-section` element should contain an `h1` element.
|
||||
|
||||
**User Story #6:** My portfolio should have a navbar with an id of `navbar`.
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#welcome-section h1').length,
|
||||
0,
|
||||
'Welcome section should contain an h1 element '
|
||||
);
|
||||
```
|
||||
|
||||
**User Story #7:** The navbar should contain at least one link that I can click on to navigate to different sections of the page.
|
||||
You should not have any empty `h1` elements within `#welcome-section` element.
|
||||
|
||||
**User Story #8:** My portfolio should have a link with an id of `profile-link`, which opens my GitHub or FCC profile in a new tab.
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#welcome-section h1')?.[0]?.innerText?.length,
|
||||
0,
|
||||
'h1 element in welcome section should contain your name or camper ' +
|
||||
'name '
|
||||
);
|
||||
```
|
||||
|
||||
**User Story #9:** My portfolio should have at least one media query.
|
||||
You should have a "Projects" section with an `id` of `projects`.
|
||||
|
||||
**User Story #10:** The height of the welcome section should be equal to the height of the viewport.
|
||||
```js
|
||||
const el = document.getElementById('projects')
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
**User Story #11:** The navbar should always be at the top of the viewport.
|
||||
Your portfolio should contain at least one elment with a class of `project-tile`.
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
assert.isAbove(
|
||||
document.querySelectorAll('#projects .project-tile').length,
|
||||
0
|
||||
);
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
Your `#projects` element should contain at least one `a` element.
|
||||
|
||||
# --solutions--
|
||||
```js
|
||||
assert.isAbove(document.querySelectorAll('#projects a').length, 0);
|
||||
```
|
||||
|
||||
Your portfolio should have a navbar with an `id` of `navbar`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('navbar');
|
||||
assert(!!el);
|
||||
```
|
||||
|
||||
Your `#navbar` element should contain at least one `a` element whose `href` attribute starts with `#`.
|
||||
|
||||
```js
|
||||
const links = [...document.querySelectorAll('#navbar a')].filter(
|
||||
(nav) => (nav?.getAttribute('href') || '').substr(0, 1) === '#'
|
||||
);
|
||||
|
||||
assert.isAbove(
|
||||
links.length,
|
||||
0,
|
||||
'Navbar should contain an anchor link '
|
||||
);
|
||||
```
|
||||
|
||||
Your portfolio should have an `a` element with an `id` of `profile-link`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('profile-link');
|
||||
assert(!!el && el.tagName === 'A')
|
||||
```
|
||||
|
||||
Your `#profile-link` element should have a `target` attribute of `_blank`.
|
||||
|
||||
```js
|
||||
const el = document.getElementById('profile-link');
|
||||
assert(!!el && el.target === '_blank')
|
||||
```
|
||||
|
||||
Your portfolio should use at least one media query.
|
||||
|
||||
```js
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
Your `#navbar` element should always be at the top of the viewport.
|
||||
|
||||
```js
|
||||
(async () => {
|
||||
const timeout = (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds));
|
||||
|
||||
const navbar = document.getElementById('navbar');
|
||||
assert.approximately(
|
||||
navbar?.getBoundingClientRect().top,
|
||||
0,
|
||||
15,
|
||||
"Navbar's parent should be body and it should be at the top of " +
|
||||
'the viewport '
|
||||
);
|
||||
|
||||
window.scroll(0, 500);
|
||||
|
||||
await timeout(1);
|
||||
|
||||
assert.approximately(
|
||||
navbar?.getBoundingClientRect().top,
|
||||
0,
|
||||
15,
|
||||
'Navbar should be at the top of the viewport even after ' +
|
||||
'scrolling '
|
||||
);
|
||||
window.scroll(0, 0);
|
||||
})();
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="stylesheet" type="text/css" href="styles.css">
|
||||
<title>Personal Portfolio</title>
|
||||
</head>
|
||||
<body>
|
||||
<link href="https://fonts.googleapis.com/css?family=Pacifico" rel="stylesheet" type="text/css">
|
||||
<!--Font Reference-->
|
||||
<nav id="navbar">
|
||||
<a href="#projects">Projects</a> |
|
||||
<a href="#contact">Contact me</a>
|
||||
</nav>
|
||||
<main>
|
||||
<section id="welcome-section">
|
||||
<br>
|
||||
<h1>It's me!</h1>
|
||||
<img src="https://s.cdpn.io/profiles/user/4369153/512.jpg?1587151780" height=100px>
|
||||
<h2>Naomi Carrigan</h2>
|
||||
<p>Welcome to my portfolio page!</p>
|
||||
</section><hr>
|
||||
<section id="projects">
|
||||
<h1>Projects</h1>
|
||||
<h2><a href="https://codepen.io/nhcarrigan">Here's what I've worked on!</a></h2>
|
||||
<p class="project-tile">
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Algebraic Concepts" src="https://codepen.io/nhcarrigan/embed/preview/NWGrWBR?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/NWGrWBR'>Algebraic Concepts</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Pokemon Daycare Service" src="https://codepen.io/nhcarrigan/embed/preview/mdeEbeq?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/mdeEbeq'>Pokemon Daycare Service</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Togepi Fan Club" src="https://codepen.io/nhcarrigan/embed/preview/vYNGoBE?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/vYNGoBE'>Togepi Fan Club</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
<iframe height="265" style="width: 25;" scrolling="no" title="Togepi" src="https://codepen.io/nhcarrigan/embed/preview/yLYOWEN?height=265&theme-id=dark&default-tab=result" frameborder="no" allowtransparency="true" allowfullscreen="true" loading="lazy">
|
||||
See the Pen <a href='https://codepen.io/nhcarrigan/pen/yLYOWEN'>Togepi</a> by Naomi Carrigan
|
||||
(<a href='https://codepen.io/nhcarrigan'>@nhcarrigan</a>) on <a href='https://codepen.io'>CodePen</a>.
|
||||
</iframe>
|
||||
</p></section><hr>
|
||||
<section id="contact">
|
||||
<h1>Contact me!</h1>
|
||||
<h2>Use the links below to get in touch.</h2>
|
||||
<p><a href="https://www.freecodecamp.org/nhcarrigan" id="profile-link" target="_blank" rel="noopener noreferrer">FreeCodeCamp.org</a> | <a href="https://github.com/nhcarrigan" id="github-link" target="_blank" rel="noopener noreferrer">GitHub</a> | <a href="https://www.facebook.com/nhcarrigan" id="facebook-link" target="_blank" rel="noopener noreferrer">Facebook</a> | <a href="https://www.linkedin.com/in/Naomi-l-carrigan/" id="linkedin-link" target="_blank" rel="noopener noreferrer">LinkedIn</a>
|
||||
</section>
|
||||
<footer><a href="../">Return to Project List</a> | <a href="https://www.nhcarrigan.com">Return to HomePage</a></footer>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
nav{
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
text-align: right;
|
||||
font-size: 24pt;
|
||||
top: 0%;
|
||||
right: 5px;
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
@media (max-width: 500px){
|
||||
nav{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
a{
|
||||
color: #ffffff;
|
||||
}
|
||||
main{
|
||||
text-align: center;
|
||||
background-color: black;
|
||||
font-family:Pacifico
|
||||
}
|
||||
h1{
|
||||
font-size: 48pt;
|
||||
}
|
||||
h2{
|
||||
font-size: 24pt;
|
||||
}
|
||||
p{
|
||||
font-size: 12pt;
|
||||
}
|
||||
#welcome-section{
|
||||
background-color:#251a4a;
|
||||
color: #FFFFFF;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#projects{
|
||||
background-color: #060a9c;
|
||||
color: #ffffff;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
#contact{
|
||||
background-color: #03300b;
|
||||
color: #ffffff;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,55 +1,424 @@
|
||||
---
|
||||
id: 587d78af367417b2b2512b04
|
||||
title: Build a Product Landing Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301144
|
||||
dashedName: build-a-product-landing-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/RKRbwL>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/RKRbwL" target="_blank"><https://codepen.io/freeCodeCamp/full/RKRbwL></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your product landing page should have a `header` element with a corresponding `id="header"`
|
||||
1. You can see an image within the `header` element with a corresponding `id="header-img"` (A logo would make a good image here)
|
||||
1. Within the `#header` element, you can see a `nav` element with a corresponding `id="nav-bar"`
|
||||
1. You can see at least three clickable elements inside the `nav` element, each with the class `nav-link`
|
||||
1. When you click a `.nav-link` button in the `nav` element, you are taken to the corresponding section of the landing page
|
||||
1. You can watch an embedded product video with `id="video"`
|
||||
1. Your landing page has a `form` element with a corresponding `id="form"`
|
||||
1. Within the form, there is an `input` field with `id="email"` where you can enter an email address
|
||||
1. The `#email` input field should have placeholder text to let users know what the field is for
|
||||
1. The `#email` input field uses HTML5 validation to confirm that the entered text is an email address
|
||||
1. Within the form, there is a submit `input` with a corresponding `id="submit"`
|
||||
1. When you click the `#submit` element, the email is submitted to a static page (use this mock URL: `https://www.freecodecamp.com/email-submit`)
|
||||
1. The navbar should always be at the top of the viewport
|
||||
1. Your product landing page should have at least one media query
|
||||
1. Your product landing page should utilize CSS flexbox at least once
|
||||
|
||||
**User Story #1:** My product landing page should have a `header` element with a corresponding `id="header"`.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I can see an image within the `header` element with a corresponding `id="header-img"`. A company logo would make a good image here.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** Within the `#header` element I can see a `nav` element with a corresponding `id="nav-bar"`.
|
||||
You should have a `header` element with an `id` of `header`
|
||||
|
||||
**User Story #4:** I can see at least three clickable elements inside the `nav` element, each with the class `nav-link`.
|
||||
```js
|
||||
const el = document.getElementById('header')
|
||||
assert(!!el && el.tagName === 'HEADER')
|
||||
```
|
||||
|
||||
**User Story #5:** When I click a `.nav-link` button in the `nav` element, I am taken to the corresponding section of the landing page.
|
||||
You should have an `img` element with an `id` of `header-img`
|
||||
|
||||
**User Story #6:** I can watch an embedded product video with `id="video"`.
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && el.tagName === 'IMG')
|
||||
```
|
||||
|
||||
**User Story #7:** My landing page has a `form` element with a corresponding `id="form"`.
|
||||
Your `#header-img` should be a descendant of the `#header`
|
||||
|
||||
**User Story #8:** Within the form, there is an `input` field with `id="email"` where I can enter an email address.
|
||||
```js
|
||||
const els = document.querySelectorAll('#header #header-img')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #9:** The `#email` input field should have placeholder text to let the user know what the field is for.
|
||||
Your `#header-img` should have a `src` attribute
|
||||
|
||||
**User Story #10:** The `#email` input field uses HTML5 validation to confirm that the entered text is an email address.
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && !!el.src)
|
||||
```
|
||||
|
||||
**User Story #11:** Within the form, there is a submit `input` with a corresponding `id="submit"`.
|
||||
Your `#header-img`’s `src` value should be a valid URL (starts with `http`)
|
||||
|
||||
**User Story #12:** When I click the `#submit` element, the email is submitted to a static page (use this mock URL: <https://www.freecodecamp.com/email-submit>).
|
||||
```js
|
||||
const el = document.getElementById('header-img')
|
||||
assert(!!el && /^http/.test(el.src))
|
||||
```
|
||||
|
||||
**User Story #13:** The navbar should always be at the top of the viewport.
|
||||
You should have a `nav` element with an `id` of `nav-bar`
|
||||
|
||||
**User Story #14:** My product landing page should have at least one media query.
|
||||
```js
|
||||
const el = document.getElementById('nav-bar')
|
||||
assert(!!el && el.tagName === 'NAV')
|
||||
```
|
||||
|
||||
**User Story #15:** My product landing page should utilize CSS flexbox at least once.
|
||||
Your `#nav-bar` should be a descendant of the `#header`
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
const els = document.querySelectorAll('#header #nav-bar')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
You should have at least 3 `.nav-link` elements within the `#nav-bar`
|
||||
|
||||
# --solutions--
|
||||
```js
|
||||
const els = document.querySelectorAll('#nav-bar .nav-link')
|
||||
assert(els.length >= 3)
|
||||
```
|
||||
|
||||
Each `.nav-link` element should have an `href` attribute
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('.nav-link')
|
||||
els.forEach(el => {
|
||||
if (!el.href) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Each `.nav-link` element should link to a corresponding element on the landing page (has an `href` with a value of another element's id. e.g. `#footer`)
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('.nav-link')
|
||||
els.forEach(el => {
|
||||
const linkDestination = el.getAttribute('href').slice(1)
|
||||
if (!document.getElementById(linkDestination)) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
You should have a `video` or `iframe` element with an `id` of `video`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('video')
|
||||
assert(!!el && (el.tagName === 'VIDEO' || el.tagName === 'IFRAME'))
|
||||
```
|
||||
|
||||
Your `#video` should have a `src` attribute
|
||||
|
||||
```js
|
||||
const el = document.getElementById('video')
|
||||
assert(!!el && !!el.src)
|
||||
```
|
||||
|
||||
You should have a `form` element with an `id` of `form`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('form')
|
||||
assert(!!el && el.tagName === 'FORM')
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#email` should be a descendant of the `#form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#form #email')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should have the `placeholder` attribute with placeholder text
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should use HTML5 validation by setting its `type` to `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.type === 'email')
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#submit` should be a descendant of the `#form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#form #submit')
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Your `#submit` should have a `type` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.type === 'submit')
|
||||
```
|
||||
|
||||
Your `#form` should have an `action` attribute of `https://www.freecodecamp.com/email-submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('form')
|
||||
assert(!!el && el.action === 'https://www.freecodecamp.com/email-submit')
|
||||
```
|
||||
|
||||
Your `#email` should have a `name` attribute of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.name === 'email')
|
||||
```
|
||||
|
||||
Your `#nav-bar` should always be at the top of the viewport
|
||||
|
||||
```js
|
||||
const el = document.getElementById('nav-bar')
|
||||
const top1 = el?.offsetTop
|
||||
const top2 = el?.offsetTop
|
||||
assert(!!el && top1 >= -15 && top1 <= 15 && top2 >= -15 && top2 <= 15)
|
||||
```
|
||||
|
||||
Your Product Landing Page should use at least one media query
|
||||
|
||||
```js
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
Your Product Landing Page should use CSS Flexbox at least once
|
||||
|
||||
```js
|
||||
const stylesheet = new __helpers.CSSHelp(document).getStyleSheet()
|
||||
const cssRules = new __helpers.CSSHelp(document).styleSheetToCssRulesArray(stylesheet)
|
||||
const usesFlex = cssRules.find(rule => {
|
||||
return rule.style?.display === 'flex' || rule.style?.display === 'inline-flex'
|
||||
})
|
||||
assert(usesFlex)
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Product Landing Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<header id="header">
|
||||
<nav id="nav-bar">
|
||||
<img
|
||||
id="header-img"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="50px"
|
||||
/>
|
||||
<a href="#Features" class="nav-link">Features</a> |
|
||||
<a href="#Video" class="nav-link">See our facility!</a> |
|
||||
<a href="#Pricing" class="nav-link">Purchase</a>
|
||||
<hr />
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<h1>
|
||||
Pokemon Daycare Service
|
||||
</h1>
|
||||
<section id="Features">
|
||||
<h2>What we offer</h2>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">Guaranteed friendly and loving staff!</div>
|
||||
</div>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">
|
||||
Comfortable environment for Pokemon to explore and play!
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-here">
|
||||
<div class="flex-left">
|
||||
<img
|
||||
id="bullet"
|
||||
src="https://upload.wikimedia.org/wikipedia/commons/3/39/Pokeball.PNG"
|
||||
max-height="25px"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-right">
|
||||
Multiple membership plans to fit your lifestyle!
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="Video">
|
||||
<h2>Check us out!</h2>
|
||||
A sneak peek into our facility:
|
||||
<br />
|
||||
<iframe
|
||||
id="video"
|
||||
width="520"
|
||||
height="347"
|
||||
src="https://www.youtube.com/embed/Nw-ksH2r6AQ"
|
||||
frameborder="0"
|
||||
allowfullscreen
|
||||
alt="A video tour of our facility"
|
||||
>
|
||||
</iframe>
|
||||
</section>
|
||||
<section id="Pricing">
|
||||
<h2>Membership Plans</h2>
|
||||
<div class="flex-mem">
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Basic Membership</font><br />
|
||||
<ul>
|
||||
<li>One Pokemon</li>
|
||||
<li>Food and berries provided</li>
|
||||
</ul>
|
||||
<em>$9.99/month</em>
|
||||
</div>
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Silver Membership</font><br />
|
||||
<ul>
|
||||
<li>Up to Three Pokemon</li>
|
||||
<li>Food and berries provided</li>
|
||||
<li>Grooming and accessories included</li>
|
||||
</ul>
|
||||
<em>$19.99/month</em>
|
||||
</div>
|
||||
<div class="flex-mem-box">
|
||||
<font size="+2">Gold Membership</font><br />
|
||||
<ul>
|
||||
<li>Up to six Pokemon!</li>
|
||||
<li>Food and berries provided</li>
|
||||
<li>Grooming and accessories included</li>
|
||||
<li>Personal training for each Pokemon</li>
|
||||
<li>Breeding and egg hatching</li>
|
||||
</ul>
|
||||
<em>$29.99/month</em>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<form id="form" action="https://www.freecodecamp.com/email-submit">
|
||||
<p>Sign up for our newsletter!</p>
|
||||
<label for="email"><p>Email:</p><input name="email" id="email" type="email" placeholder="johnsmith@email.com" required></label>
|
||||
<input type="submit" id="submit">
|
||||
</form>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background-color: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
main {
|
||||
max-width: 750px;
|
||||
margin: 50px auto;
|
||||
}
|
||||
input {
|
||||
background-color: #92869c;
|
||||
}
|
||||
a:not(.nav-link) {
|
||||
color: white;
|
||||
}
|
||||
#header-img {
|
||||
max-height: 25px;
|
||||
}
|
||||
#nav-bar {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
top: 0%;
|
||||
background-color: #92869c;
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
||||
#bullet {
|
||||
max-height: 25px;
|
||||
}
|
||||
.flex-here {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.flex-left {
|
||||
height: 25px;
|
||||
}
|
||||
.flex-mem {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.flex-mem-box {
|
||||
background-color: #92869c;
|
||||
border-color: black;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
margin: 10px;
|
||||
padding: 10px;
|
||||
color: black;
|
||||
}
|
||||
@media (max-width: 350px) {
|
||||
#video {
|
||||
width: 300;
|
||||
height: 200;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,57 +1,516 @@
|
||||
---
|
||||
id: 587d78af367417b2b2512b03
|
||||
title: Build a Survey Form
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301145
|
||||
dashedName: build-a-survey-form
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/VPaoNP>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/VPaoNP" target="_blank"><https://codepen.io/freeCodeCamp/full/VPaoNP></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. You should have a page title in an `h1` element with an `id` of `title`
|
||||
1. You should have a short explanation in a `p` element with an `id` of `description`
|
||||
1. You should have a `form` element with an `id` of `survey-form`
|
||||
1. Inside the form element, you are **required** to enter your name in an `input` field that has an `id` of `name` and a `type` of `text`
|
||||
1. Inside the form element, you are **required** to enter your email in an `input` field that has an `id` of `email`
|
||||
1. If you enter an email that is not formatted correctly, you will see an HTML5 validation error
|
||||
1. Inside the form, you can enter a number in an `input` field that has an `id` of `number`
|
||||
1. If you enter non-numbers in the number input, you will see an HTML5 validation error
|
||||
1. If you enter numbers outside the range of the number input, which are defined by the `min` and `max` attributes, you will see an HTML5 validation error
|
||||
1. For the name, email, and number input fields, you can see corresponding `label` elements in the form, that describe the purpose of each field with the following ids: `id="name-label"`, `id="email-label"`, and `id="number-label"`
|
||||
1. For the name, email, and number input fields, you can see placeholder text that gives a description or instructions for each field
|
||||
1. Inside the form element, you should have a `select` dropdown element with an `id` of `dropdown` and at least two options to choose from
|
||||
1. Inside the form element, you can select an option from a group of at least two radio buttons that are grouped using the `name` attribute
|
||||
1. Inside the form element, you can select several fields from a series of checkboxes, each of which must have a `value` attribute
|
||||
1. Inside the form element, you are presented with a `textarea` for additional comments
|
||||
1. Inside the form element, you are presented with a button with `id` of `submit` to submit all the inputs
|
||||
|
||||
**User Story #1:** I can see a title with `id="title"` in H1 sized text.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I can see a short explanation with `id="description"` in P sized text.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** I can see a `form` with `id="survey-form"`.
|
||||
You should have an `h1` element with an `id` of `title`
|
||||
|
||||
**User Story #4:** Inside the form element, I am required to enter my name in a field with `id="name"`.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.tagName === 'H1')
|
||||
```
|
||||
|
||||
**User Story #5:** Inside the form element, I am required to enter an email in a field with `id="email"`.
|
||||
Your `#title` should not be empty
|
||||
|
||||
**User Story #6:** If I enter an email that is not formatted correctly, I will see an HTML5 validation error.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
**User Story #7:** Inside the form, I can enter a number in a field with `id="number"`.
|
||||
You should have a `p` element with an `id` of `description`
|
||||
|
||||
**User Story #8:** If I enter non-numbers in the number input, I will see an HTML5 validation error.
|
||||
```js
|
||||
const el = document.getElementById('description')
|
||||
assert(!!el && el.tagName === 'P')
|
||||
```
|
||||
|
||||
**User Story #9:** If I enter numbers outside the range of the number input, which are defined by the `min` and `max` attributes, I will see an HTML5 validation error.
|
||||
Your `#description` should not be empty
|
||||
|
||||
**User Story #10:** For the name, email, and number input fields inside the form I can see corresponding labels that describe the purpose of each field with the following ids: `id="name-label"`, `id="email-label"`, and `id="number-label"`.
|
||||
```js
|
||||
const el = document.getElementById('description')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
**User Story #11:** For the name, email, and number input fields, I can see placeholder text that gives me a description or instructions for each field.
|
||||
You should have a `form` element with an `id` of `survey-form`
|
||||
|
||||
**User Story #12:** Inside the form element, I can select an option from a dropdown that has a corresponding `id="dropdown"`.
|
||||
```js
|
||||
const el = document.getElementById('survey-form')
|
||||
assert(!!el && el.tagName === 'FORM')
|
||||
```
|
||||
|
||||
**User Story #13:** Inside the form element, I can select a field from one or more groups of radio buttons. Each group should be grouped using the `name` attribute.
|
||||
You should have an `input` element with an `id` of `name`
|
||||
|
||||
**User Story #14:** Inside the form element, I can select several fields from a series of checkboxes, each of which must have a `value` attribute.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
**User Story #15:** Inside the form element, I am presented with a `textarea` at the end for additional comments.
|
||||
Your `#name` should have a `type` of `text`
|
||||
|
||||
**User Story #16:** Inside the form element, I am presented with a button with `id="submit"` to submit all my inputs.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.type === 'text')
|
||||
```
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
Your `#name` should require input
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && el.required)
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
Your `#name` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #name')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#email` should have a `type` of `email`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.type === 'email')
|
||||
```
|
||||
|
||||
Your `#email` should require input
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && el.required)
|
||||
```
|
||||
|
||||
Your `#email` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #email')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` element with an `id` of `number`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.tagName === 'INPUT')
|
||||
```
|
||||
|
||||
Your `#number` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #number')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#number` should have a `type` of `number`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.type === 'number')
|
||||
```
|
||||
|
||||
Your `#number` should have a `min` attribute with a numeric value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.min && isFinite(el.min))
|
||||
```
|
||||
|
||||
Your `#number` should have a `max` attribute with a numeric value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && el.max && isFinite(el.max))
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `name-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `email-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
You should have a `label` element with an `id` of `number-label`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number-label')
|
||||
assert(!!el && el.tagName === 'LABEL')
|
||||
```
|
||||
|
||||
Your `#name-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#email-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#number-label` should not be empty
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number-label')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
```
|
||||
|
||||
Your `#name-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #name-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#email-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #email-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#number-label` should be a descedant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #number-label')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
Your `#name` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('name')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#email` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('email')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
Your `#number` should have a `placeholder` attribute and value
|
||||
|
||||
```js
|
||||
const el = document.getElementById('number')
|
||||
assert(!!el && !!el.placeholder && el.placeholder.length > 0)
|
||||
```
|
||||
|
||||
You should have a `select` field with an `id` of `dropdown`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('dropdown')
|
||||
assert(!!el && el.tagName === 'SELECT')
|
||||
```
|
||||
|
||||
Your `#dropdown` should have at least two selectable (not disabled) `option` elements
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#dropdown option:not([disabled])')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
Your `#dropdown` should be a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #dropdown')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have at least two `input` elements with a `type` of `radio` (radio buttons)
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('input[type="radio"]')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
You should have at least two radio buttons that are descendants of `#survey-form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#survey-form input[type="radio"]')
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
All your radio buttons should have a `value` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('input[type="radio"]')
|
||||
const els2 = document.querySelectorAll('input[type="radio"][value=""], input[type="radio"]:not([value])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
All your radio buttons should have a `name` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('input[type="radio"]')
|
||||
const els2 = document.querySelectorAll('input[type="radio"][name=""], input[type="radio"]:not([name])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
Every radio button group should have at least 2 radio buttons
|
||||
|
||||
```js
|
||||
const radioButtons = document.querySelectorAll('input[type="radio"]');
|
||||
const groups = {}
|
||||
|
||||
if (radioButtons) {
|
||||
radioButtons.forEach(el => {
|
||||
if (!groups[el.name]) groups[el.name] = []
|
||||
groups[el.name].push(el)
|
||||
})
|
||||
}
|
||||
|
||||
const groupKeys = Object.keys(groups)
|
||||
|
||||
groupKeys.forEach(key => {
|
||||
if (groups[key].length < 2) assert(false)
|
||||
})
|
||||
|
||||
assert(groupKeys.length > 0)
|
||||
```
|
||||
|
||||
You should have at least two `input` elements with a `type` of `checkbox` (checkboxes) that are descendants of `#survey-form`
|
||||
|
||||
```js
|
||||
const els = document.querySelectorAll('#survey-form input[type="checkbox"]');
|
||||
assert(els.length >= 2)
|
||||
```
|
||||
|
||||
All your checkboxes inside `#survey-form` should have a `value` attribute and value
|
||||
|
||||
```js
|
||||
const els1 = document.querySelectorAll('#survey-form input[type="checkbox"]')
|
||||
const els2 = document.querySelectorAll('#survey-form input[type="checkbox"][value=""], #survey-form input[type="checkbox"]:not([value])')
|
||||
assert(els1.length > 0 && els2.length === 0)
|
||||
```
|
||||
|
||||
You should have at least one `textarea` element that is a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form textarea')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
You should have an `input` or `button` element with an `id` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && (el.tagName === 'INPUT' || el.tagName === 'BUTTON'))
|
||||
```
|
||||
|
||||
Your `#submit` should have a `type` of `submit`
|
||||
|
||||
```js
|
||||
const el = document.getElementById('submit')
|
||||
assert(!!el && el.type === 'submit')
|
||||
```
|
||||
|
||||
Your `#submit` should be a descendant of `#survey-form`
|
||||
|
||||
```js
|
||||
const el = document.querySelector('#survey-form #submit')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Survey Form</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Survey Form</h1>
|
||||
<p>The card below was built as a sample survey form for freeCodeCamp.</p>
|
||||
<main id="main">
|
||||
<h1 id="title">Join the Togepi Fan Club!</h1>
|
||||
<p id="description">
|
||||
Enter your information here to receive updates about club activities,
|
||||
our monthly newsletter, and other email communications.
|
||||
</p>
|
||||
<form id="survey-form" action="#">
|
||||
<label for="name" id="name-label"
|
||||
><p>Name:</p>
|
||||
<input type="text" id="name" placeholder="e.g. John Smith" required />
|
||||
</label>
|
||||
<label for="email" id="email-label"
|
||||
><p>Email:</p>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
placeholder="e.g. john.smith@email.com"
|
||||
required
|
||||
/>
|
||||
</label>
|
||||
<label for="age" id="number-label"
|
||||
><p>Age<em>(optional)</em>:</p>
|
||||
<input
|
||||
type="number"
|
||||
id="number"
|
||||
placeholder="e.g. 19"
|
||||
min="18"
|
||||
max="99"
|
||||
/>
|
||||
</label>
|
||||
<label for="interest" id="interest-label"
|
||||
><p>What are you most interested in?</p>
|
||||
<select id="dropdown">
|
||||
<option selected disabled hidden></option>
|
||||
<option id="battles">Battling</option>
|
||||
<option id="breeding">Breeding</option>
|
||||
<option id="catching">Completing my Pokedex</option>
|
||||
<option id="exploring">Exploring new regions</option>
|
||||
</select>
|
||||
</label>
|
||||
<p>Who is your favourite Pokemon?</p>
|
||||
<label for="togepi">
|
||||
<input
|
||||
id="togepi"
|
||||
type="radio"
|
||||
name="favorite"
|
||||
value="togepi"
|
||||
/>Togepi!
|
||||
</label>
|
||||
<label for="pikachu">
|
||||
<input
|
||||
id="pikachu"
|
||||
type="radio"
|
||||
name="favorite"
|
||||
value="pikachu"
|
||||
/>Pikachu
|
||||
</label>
|
||||
<label for="other">
|
||||
<input id="other" type="radio" name="favorite" value="other" />Other
|
||||
</label>
|
||||
<p>Which communications do you want to receive?</p>
|
||||
<label for="newsletter">
|
||||
<input
|
||||
id="newsletter"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="newsletter"
|
||||
/>Newsletter
|
||||
</label>
|
||||
<label for="events">
|
||||
<input
|
||||
id="events"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="events"
|
||||
/>Event updates
|
||||
</label>
|
||||
<label for="updates">
|
||||
<input
|
||||
id="updates"
|
||||
type="checkbox"
|
||||
name="communications"
|
||||
value="updates"
|
||||
/>Club updates
|
||||
</label>
|
||||
<p>Any other information you would like to share?</p>
|
||||
<textarea id="additional-information" rows="4" cols="50">
|
||||
You can provide comments, suggestions, or feedback here.</textarea
|
||||
>
|
||||
<p>
|
||||
<em
|
||||
>Please note: This form is a proof of concept. Submitting the form
|
||||
will not actually transmit your data.</em
|
||||
>
|
||||
</p>
|
||||
<button type="Submit" id="submit">Submit</button>
|
||||
</form>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
main {
|
||||
text-align: center;
|
||||
background-color: #92869c;
|
||||
background-blend-mode: lighten;
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
border-radius: 50px;
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
|
||||
color: black;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
background: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
input, textarea, select, button {
|
||||
background: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
a {
|
||||
color: white;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,55 +1,525 @@
|
||||
---
|
||||
id: 587d78b0367417b2b2512b05
|
||||
title: Build a Technical Documentation Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301146
|
||||
dashedName: build-a-technical-documentation-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/NdrKKL>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/NdrKKL" target="_blank"><https://codepen.io/freeCodeCamp/full/NdrKKL></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
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
|
||||
|
||||
**User Story #1:** I can see a `main` element with a corresponding `id="main-doc"`, which contains the page's main content (technical documentation).
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** Within the `#main-doc` element, I can see several `section` elements, each with a class of `main-section`. There should be a minimum of 5.
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** The first element within each `.main-section` should be a `header` element which contains text that describes the topic of that section.
|
||||
You should have a `main` element with an `id` of `main-doc`
|
||||
|
||||
**User Story #4:** 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"`).
|
||||
```js
|
||||
const el = document.getElementById('main-doc')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
**User Story #5:** The `.main-section` elements should contain at least 10 `p` elements total (not each).
|
||||
You should have at least five `section` elements with a class of `main-section`
|
||||
|
||||
**User Story #6:** The `.main-section` elements should contain at least 5 `code` elements total (not each).
|
||||
```js
|
||||
const els = document.querySelectorAll('#main-doc section')
|
||||
assert(els.length >= 5)
|
||||
```
|
||||
|
||||
**User Story #7:** The `.main-section` elements should contain at least 5 `li` items total (not each).
|
||||
All of your `.main-section` elements should be `section` elements
|
||||
|
||||
**User Story #8:** I can see a `nav` element with a corresponding `id="navbar"`.
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if (el.tagName !== 'SECTION') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #9:** The navbar element should contain one `header` element which contains text that describes the topic of the technical documentation.
|
||||
You should have at least five `.main-section` elements that are descendants of `#main-doc`
|
||||
|
||||
**User Story #10:** 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`.
|
||||
```js
|
||||
const els = document.querySelectorAll('#main-doc .main-section')
|
||||
assert(els.length >= 5)
|
||||
```
|
||||
|
||||
**User Story #11:** The `header` element in the navbar must come before any link (`a`) elements in the navbar.
|
||||
The first child of each `.main-section` should be a `header` element
|
||||
|
||||
**User Story #12:** 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").
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if(el.firstElementChild?.tagName !== 'HEADER') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #13:** When I click on a navbar element, the page should navigate to the corresponding section of the `main-doc` element (e.g. If I click on a `nav-link` element that contains the text "Hello world", the page navigates to a `section` element that has that id and contains the corresponding `header`.
|
||||
None of your `header` elements should be empty
|
||||
|
||||
**User Story #14:** 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.
|
||||
```js
|
||||
const els = document.querySelectorAll('header')
|
||||
els.forEach(el => {
|
||||
if (el.innerText?.length <= 0) assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
**User Story #15:** My Technical Documentation page should use at least one media query.
|
||||
All of your `.main-section` elements should have an `id`
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`
|
||||
```js
|
||||
const els = document.querySelectorAll('.main-section')
|
||||
els.forEach(el => {
|
||||
if (!el.id || el.id === '') assert(false)
|
||||
})
|
||||
assert(els.length > 0)
|
||||
```
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
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
|
||||
|
||||
# --solutions--
|
||||
```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[class="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 && headerText.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
|
||||
assert.isAtLeast(new __helpers.CSSHelp(document).getCSSRules('media')?.length, 1);
|
||||
```
|
||||
|
||||
# --seed--
|
||||
|
||||
## --seed-contents--
|
||||
|
||||
```html
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="stylesheet" type="text/css" href="styles.css" />
|
||||
<title>Technical Documentation Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<nav id="navbar">
|
||||
<header><br />Algebraic Concepts</header>
|
||||
<hr />
|
||||
<a href="#introduction" class="nav-link">Introduction</a><br />
|
||||
<hr />
|
||||
<a href="#definitions" class="nav-link">Definitions</a><br />
|
||||
<hr />
|
||||
<a href="#examples" class="nav-link">Examples</a><br />
|
||||
<hr />
|
||||
<a href="#solving_equations" class="nav-link">Solving Equations</a><br />
|
||||
<hr />
|
||||
<a href="#solving_equations_ii" class="nav-link">Solving Equations II</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#solving_equations_iii" class="nav-link">Solving Equations III</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#system_of_equations" class="nav-link">System of Equations</a
|
||||
><br />
|
||||
<hr />
|
||||
<a href="#try_it_yourself!" class="nav-link">Try it Yourself!</a><br />
|
||||
<hr />
|
||||
<a href="#more_information" class="nav-link">More Information</a><br />
|
||||
</nav>
|
||||
<main id="main-doc">
|
||||
<section class="main-section" id="introduction">
|
||||
<header>Introduction</header>
|
||||
<p>
|
||||
Welcome to a basic introduction of algebra. In this tutorial, we will
|
||||
review some of the more common algebraic concepts.
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="definitions">
|
||||
<header>Definitions</header>
|
||||
<p>
|
||||
To start with, let's define some of the more common terms used in
|
||||
algebra:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<b>Variable:</b> A variable is an unknown value, usually represented
|
||||
by a letter.
|
||||
</li>
|
||||
<li>
|
||||
<b>Expression:</b> Essentially a mathematical object. For the
|
||||
purpose of this tutorial, an expression is one part of an equation.
|
||||
</li>
|
||||
<li>
|
||||
<b>Equation:</b> An equation is a mathematical argument in which two
|
||||
expressions result in the same value.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="main-section" id="examples">
|
||||
<header>Examples</header>
|
||||
<p>
|
||||
Sometimes it is easier to understand the definitions when you have a
|
||||
physical example to look at. Here is an example of the above terms.<br /><br />
|
||||
<code>x + 5 = 12 </code><br /><br />
|
||||
In this above example, we have:
|
||||
</p>
|
||||
<ul>
|
||||
<li><b>Variable:</b> The variable in the example is "x".</li>
|
||||
<li>
|
||||
<b>Expression:</b> There are two expressions in this example. They
|
||||
are "x+5" and "12".
|
||||
</li>
|
||||
<li>
|
||||
<b>Equation:</b> The entire example, "x+5=12", is an equation.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations">
|
||||
<header>Solving Equations</header>
|
||||
<p>
|
||||
The primary use for algebra is to determine an unknown value, the
|
||||
"variable", with the information provided. Continuing to use our
|
||||
example from above, we can find the value of the variable "x".<br /><br />
|
||||
<code>x + 5 = 12 </code><br /><br />
|
||||
In an equation, both sides result in the same value. So you can
|
||||
manipulate the two expressions however you need, as long as you
|
||||
perform the same operation (or change) to each side. You do this
|
||||
because the goal when solving an equation is to
|
||||
<b
|
||||
>get the variable into its own expression, or by itself on one side
|
||||
of the = sign.</b
|
||||
><br />For this example, we want to remove the "+5" so the "x" is
|
||||
alone. To do this, we can <em>subtract 5</em>, because subtraction is
|
||||
the opposite operation to addition. But remember, we have to perform
|
||||
the same operation to both sides of the equation. Now our equation
|
||||
looks like this.<br /><br />
|
||||
<code>x + 5 - 5 = 12 - 5</code><br /><br />
|
||||
The equation looks like a mess right now, because we haven't completed
|
||||
the operations. We can <b>simplify</b> this equation to make it easier
|
||||
to read by performing the operations "5-5" and "12-5". The result
|
||||
is:<br /><br />
|
||||
<code>x = 7</code><br /><br />
|
||||
We now have our solution to this equation!
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations_ii">
|
||||
<header>Solving Equations II</header>
|
||||
<p>
|
||||
Let us look at a slightly more challenging equation.<br /><br />
|
||||
<code>3x + 4 = 13</code><br /><br />
|
||||
Again we can start with subtraction. In this case, we want to subtract
|
||||
4 from each side of the equation. We will also go ahead and simplify
|
||||
with each step. So now we have:<br /><br />
|
||||
<code>3x = 9</code><br /><br />
|
||||
"3x" translates to "3*x", where the "*" symbol indicates
|
||||
multiplication. We use the "*" to avoid confusion, as the "x" is now a
|
||||
variable instead of a multiplication symbol. The opposite operation
|
||||
for multiplication is division, so we need to
|
||||
<b>divide each expression by 3</b>.<br /><br />
|
||||
<code>x = 3</code><br /><br />
|
||||
And now we have our solution!
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="solving_equations_iii">
|
||||
<header>Solving Equations III</header>
|
||||
<p>
|
||||
Now we are getting in to more complex operations. Here is another
|
||||
equation for us to look at:<br /><br />
|
||||
<code>x^2 - 8 = 8</code><br /><br />
|
||||
Our very first step will be to <b>add</b> 8 to each side. This is
|
||||
different from our previous examples, where we had to subtract. But
|
||||
remember, our goal is to get the variable alone by performing opposite
|
||||
operations.<br /><br />
|
||||
<code>x^2 = 16</code><br /><br />
|
||||
But what does the "^2" mean? The "^" symbol is used to denote
|
||||
exponents in situations where superscript is not available. When
|
||||
superscript <b>is</b> available, you would see it as x<sup>2</sup>.
|
||||
For the sake of this project, however, we will use the "^" symbol.<br />
|
||||
An exponent tells you how many times the base (in our case, "x") is
|
||||
multiplied by itself. So, "x^2" would be the same as "x*x". Now the
|
||||
opposite function of multiplication is division, but we would have to
|
||||
<b>divide both sides by "x"</b>. We do not want to do this, as that
|
||||
would put an "x" on the other side of the equation. So instead, we
|
||||
need to use the root operation! For an exponent of "2", we call this
|
||||
the "square root" and denote it with "√". Our equation is now:
|
||||
<br /><br />
|
||||
<code>x = √9</code><br /><br />
|
||||
Performing a root operation by hand can be a tedious process, so we
|
||||
recommend using a calculator when necessary. However, we are lucky in
|
||||
that "9" is a
|
||||
<a href="https://en.wikipedia.org/wiki/Square_number"
|
||||
>perfect square</a
|
||||
>, so we do not need to calculate anything. Instead, we find our
|
||||
answer to be:<br /><br />
|
||||
<code>x = 3</code>
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="system_of_equations">
|
||||
<header>System of Equations</header>
|
||||
<p>
|
||||
As you explore your algebra studies further, you may start to run
|
||||
across equations with more than one variable. The first such equations
|
||||
will likely look like:<br /><br />
|
||||
<code>y = 3x</code><br /><br />
|
||||
An equation like this does <b>not have one single solution</b>.
|
||||
Rather, there are a series of values for which the equation is true.
|
||||
For example, if "x=3" and "y=9", the equation is true. These equations
|
||||
are usually used to plot a graph. <br />
|
||||
Getting more complicated, though, you may be given a <b>pair</b> of
|
||||
equations. This is called a "system of equations", and CAN be solved.
|
||||
Let's look at how we do this! Consider the following system of
|
||||
equations:<br /><br />
|
||||
<code>y = 3x | y - 6 = x</code>
|
||||
A system of equations IS solvable, but it is a multi-step process. To
|
||||
get started, we need to chose a variable we are solving for. Let's
|
||||
solve for "x" first. From the second equation, we know that "x" equals
|
||||
"y - 6", but we cannot simplify that further because we do not have a
|
||||
value for "y". Except, thanks to the system of equations, we DO have a
|
||||
value for "y". We know that "y" equals "3x". So, looking at our second
|
||||
equation, we can replace "y" with "3x" because they have the same
|
||||
value. We then get:<br /><br />
|
||||
<code>3x - 6 = x</code><br /><br />
|
||||
Now we can solve for "x"! We start by adding 6 to each side.<br /><br />
|
||||
<code>3x = x + 6</code><br /><br />
|
||||
We still need to get "x" by itself, so we subtract "x" from both sides
|
||||
and get:<br /><br />
|
||||
<code>2x = 6</code><br /><br />
|
||||
If this confuses you, remember that "3x" is the same as "x+x+x".
|
||||
Subtract an "x" from that and you get "x+x", or "2x". Now we divide
|
||||
both sides by 2 and have our value for x!<br /><br />
|
||||
<code>x = 3</code><br /><br />
|
||||
However, our work is not done yet. We still need to find the value for
|
||||
"y". Let's go back to our first equation:<br /><br />
|
||||
<code>y = 3x</code><br /><br />
|
||||
We have a value for "x" now, so let's see what happens if we put that
|
||||
value in.<br /><br />
|
||||
<code>y = 3*3</code><br /><br />
|
||||
We perform the multiplication and discover that "y=9"! Our solution to
|
||||
this system of equations then is:<br /><br />
|
||||
<code>x = 3 and y = 9</code><br /><br />
|
||||
</p>
|
||||
</section>
|
||||
<section class="main-section" id="try_it_yourself!">
|
||||
<header>Try it Yourself!</header>
|
||||
<p>Coming Soon!</p>
|
||||
<p>Keep an eye out for new additions!</p>
|
||||
</section>
|
||||
<section class="main-section" id="more_information">
|
||||
<header>More Information</header>
|
||||
<p>Check out the following links for more information!</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="https://www.wolframalpha.com/examples/mathematics/algebra/"
|
||||
>Wolfram Alpha</a
|
||||
>
|
||||
is a great source for multiple mathematic fields.
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://en.wikipedia.org/wiki/Algebra"
|
||||
>Wikipedia's Algebra page</a
|
||||
>
|
||||
for more general information.
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
* {
|
||||
background-color: #3a3240;
|
||||
}
|
||||
a {
|
||||
color: #92869c;
|
||||
}
|
||||
a:hover {
|
||||
background-color: #92869c;
|
||||
color: #3a3240;
|
||||
}
|
||||
#navbar {
|
||||
border-style: solid;
|
||||
border-width: 5px;
|
||||
border-color: #92869c;
|
||||
height: 100%;
|
||||
top: -5px;
|
||||
left: -5px;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
color: #92869c
|
||||
}
|
||||
@media (min-width: 480px) {
|
||||
#navbar {
|
||||
position: fixed;
|
||||
}
|
||||
}
|
||||
main {
|
||||
margin-left: 220px;
|
||||
color: #92869c
|
||||
}
|
||||
header {
|
||||
font-size: 20pt;
|
||||
}
|
||||
code {
|
||||
background-color: #92869c;
|
||||
border-style: dashed;
|
||||
border-width: 2px;
|
||||
border-color: #92869c;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,43 +1,327 @@
|
||||
---
|
||||
id: bd7158d8c442eddfaeb5bd18
|
||||
title: Build a Tribute Page
|
||||
challengeType: 3
|
||||
challengeType: 14
|
||||
forumTopicId: 301147
|
||||
dashedName: build-a-tribute-page
|
||||
---
|
||||
|
||||
# --description--
|
||||
|
||||
**Objective:** Build a [CodePen.io](https://codepen.io) app that is functionally similar to this: <https://codepen.io/freeCodeCamp/full/zNqgVx>.
|
||||
**Objective:** Build an app that is functionally similar to <a href="https://codepen.io/freeCodeCamp/full/zNqgVx" target="_blank"><https://codepen.io/freeCodeCamp/full/zNqgVx></a>
|
||||
|
||||
Fulfill the below [user stories](https://en.wikipedia.org/wiki/User_story) and get all of the tests to pass. Give it your own personal style.
|
||||
**User Stories:**
|
||||
|
||||
You can use HTML, JavaScript, and CSS to complete this project. Plain CSS is recommended because that is what the lessons have covered so far and you should get some practice with plain CSS. You can use Bootstrap or SASS if you choose. Additional technologies (just for example jQuery, React, Angular, or Vue) are not recommended for this project, and using them is at your own risk. Other projects will give you a chance to work with different technology stacks like React. We will accept and try to fix all issue reports that use the suggested technology stack for this project. Happy coding!
|
||||
1. Your tribute page should have an element with a corresponding `id="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
|
||||
|
||||
**User Story #1:** My tribute page should have an element with a corresponding `id="main"`, which contains all other elements.
|
||||
Fulfill the user stories and pass all the tests below to complete this project. Give it your own personal style. Happy Coding!
|
||||
|
||||
**User Story #2:** I should see an element with a corresponding `id="title"`, which contains a string (i.e. text) that describes the subject of the tribute page (e.g. "Dr. Norman Borlaug").
|
||||
# --hints--
|
||||
|
||||
**User Story #3:** I should see either a `figure` or a `div` element with a corresponding `id="img-div"`.
|
||||
You should have a `main` element with an `id` of `main`
|
||||
|
||||
**User Story #4:** Within the `img-div` element, I should see an `img` element with a corresponding `id="image"`.
|
||||
```js
|
||||
const el = document.getElementById('main')
|
||||
assert(!!el && el.tagName === 'MAIN')
|
||||
```
|
||||
|
||||
**User Story #5:** Within the `img-div` element, I should see an element with a corresponding `id="img-caption"` that contains textual content describing the image shown in `img-div`.
|
||||
Your `#img-div`, `#image`, `#img-caption`, `#tribute-info`, and `#tribute-link` should all be descendants of `#main`
|
||||
|
||||
**User Story #6:** I should see an element with a corresponding `id="tribute-info"`, which contains textual content describing the subject of the tribute page.
|
||||
```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)
|
||||
```
|
||||
|
||||
**User Story #7:** I 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 (i.e. `target="_blank"`).
|
||||
You should have an element with an `id` of `title`
|
||||
|
||||
**User Story #8:** The `img` element should responsively resize, relative to the width of its parent element, without exceeding its original size.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el)
|
||||
```
|
||||
|
||||
**User Story #9:** The `img` element should be centered within its parent element.
|
||||
Your `#title` should not be empty
|
||||
|
||||
You can build your project by <a href='https://codepen.io/pen?template=MJjpwO' target='_blank' rel='nofollow'>using this CodePen template</a> and clicking `Save` to create your own pen. Or you can use this CDN link to run the tests in any environment you like: `https://cdn.freecodecamp.org/testable-projects-fcc/v1/bundle.js`.
|
||||
```js
|
||||
const el = document.getElementById('title')
|
||||
assert(!!el && el.innerText.length > 0)
|
||||
|
||||
Once you're done, submit the URL to your working project with all its tests passing.
|
||||
```
|
||||
|
||||
# --solutions--
|
||||
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')
|
||||
```
|
||||
|
||||
You should use an `#image` selector in your CSS to style the `#image` and pass the next three tests
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')
|
||||
assert(!!style)
|
||||
```
|
||||
|
||||
Your `#image` should have a `display` of `block`
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('display')
|
||||
assert(style === 'block')
|
||||
```
|
||||
|
||||
Your `#image` should have a `max-width` of `100%`
|
||||
|
||||
```js
|
||||
const style = new __helpers.CSSHelp(document).getStyle('#image')?.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 maxWidthValue = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('max-width')
|
||||
const displayValue = new __helpers.CSSHelp(document).getStyle('#image')?.getPropertyValue('display')
|
||||
const oldDisplayValue = img?.style.getPropertyValue('display');
|
||||
const oldDisplayPriority = img?.style.getPropertyPriority('display');
|
||||
img?.style.setProperty('display', 'none', 'important');
|
||||
const heightValue = new __helpers.CSSHelp(document).getStyle('#image')?.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
|
||||
// solution required
|
||||
|
||||
```
|
||||
|
||||
```css
|
||||
|
||||
```
|
||||
|
||||
## --solutions--
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Pacifico"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Lobster"
|
||||
rel="stylesheet"
|
||||
type="text/css"
|
||||
/>
|
||||
<link href="styles.css" rel="stylesheet" type="text/css" />
|
||||
<title>Tribute Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Tribute Page</h1>
|
||||
<p>The below card was designed as a tribute page for freeCodeCamp.</p>
|
||||
<main id="main">
|
||||
<div id="img-div">
|
||||
<img
|
||||
id="image"
|
||||
class="border"
|
||||
src="https://upload.wikimedia.org/wikipedia/en/5/53/Pok%C3%A9mon_Togepi_art.png"
|
||||
alt="An image of Togepi"
|
||||
/>
|
||||
<figcaption id="img-caption">Togepi, happy as always.</figcaption>
|
||||
</div>
|
||||
<h2 id="title">Togepi</h2>
|
||||
<hr />
|
||||
<div id="tribute-info">
|
||||
<p>
|
||||
Togepi was first discovered in the Johto region, when Ash Ketchum
|
||||
discovered a mysterious egg. However, when the egg hatched, Togepi saw
|
||||
Ash's friend Misty first and imprinted on her. Like many other
|
||||
creatures, this imprinting process created a bond and Togepi views
|
||||
Misty as his mother.
|
||||
</p>
|
||||
<p>
|
||||
Togepi is a very childlike Pokemon, and is very emotionally
|
||||
expressive. He demonstrates extreme levels of joy and sadness.
|
||||
</p>
|
||||
<hr />
|
||||
<p><u>Battle Information</u></p>
|
||||
<ul style="list-style-type: none">
|
||||
<li>Type: Fairy</li>
|
||||
<li>Evolutions: Togepi -> Togetic -> Togekiss</li>
|
||||
<li>Moves: Growl, Pound, Sweet Kiss, Charm</li>
|
||||
<li>Weaknesses: Poison, Steel</li>
|
||||
<li>Resistances: Dragon</li>
|
||||
</ul>
|
||||
<p>
|
||||
Check out this
|
||||
<a
|
||||
id="tribute-link"
|
||||
href="https://bulbapedia.bulbagarden.net/wiki/Togepi_(Pok%C3%A9mon)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>Bulbapedia article on Togepi</a
|
||||
>
|
||||
for more information on this great Pokemon.
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
<footer>
|
||||
<a href="../">Return to Project List</a> |
|
||||
<a href="https://www.nhcarrigan.com">Return to HomePage</a>
|
||||
</footer>
|
||||
</html>
|
||||
```
|
||||
|
||||
```css
|
||||
body {
|
||||
background-color: #3a3240;
|
||||
color: white;
|
||||
}
|
||||
main {
|
||||
background-color: #92869c;
|
||||
font-family: Lobster;
|
||||
max-width: 500px;
|
||||
margin: 20px auto;
|
||||
color: black;
|
||||
border-radius: 50px;
|
||||
box-shadow: 10px 10px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-size: 20pt;
|
||||
font-family: Pacifico;
|
||||
}
|
||||
body {
|
||||
text-align: center;
|
||||
font-size: 12pt;
|
||||
}
|
||||
footer {
|
||||
text-align: center;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.border {
|
||||
border-color: black;
|
||||
border-width: 5px;
|
||||
border-style: solid;
|
||||
}
|
||||
#image {
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: auto;
|
||||
max-width: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
#img-caption {
|
||||
font-size: 10pt;
|
||||
}
|
||||
a:not(#tribute-link) {
|
||||
color: white;
|
||||
}
|
||||
hr {
|
||||
border-color: black;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -11,9 +11,9 @@ const links = {
|
||||
classic2:
|
||||
'/learn/responsive-web-design/basic-html-and-html5/headline-with-the-h2-element',
|
||||
frontEnd1:
|
||||
'/learn/responsive-web-design/responsive-web-design-projects/build-a-tribute-page',
|
||||
'/learn/front-end-development-libraries/front-end-development-libraries-projects/build-a-random-quote-machine',
|
||||
frontEnd2:
|
||||
'/learn/responsive-web-design/responsive-web-design-projects/build-a-survey-form',
|
||||
'/learn/front-end-development-libraries/front-end-development-libraries-projects/build-a-markdown-previewer',
|
||||
backEnd1:
|
||||
'/learn/back-end-development-and-apis/back-end-development-and-apis-projects/timestamp-microservice',
|
||||
backEnd2:
|
||||
@@ -28,7 +28,7 @@ const links = {
|
||||
// we wait for these titles to be present:
|
||||
const titles = {
|
||||
classic2: 'Headline with the h2 Element',
|
||||
frontEnd2: 'Build a Survey Form',
|
||||
frontEnd2: 'Build a Markdown Previewer',
|
||||
backEnd2: 'Request Header Parser Microservice',
|
||||
video2: 'Introduction: Hardware Architecture'
|
||||
};
|
||||
|
||||
56
cypress/integration/learn/challenges/multifileCertProject.js
Normal file
56
cypress/integration/learn/challenges/multifileCertProject.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const save1text = 'save 1';
|
||||
const save2text = 'save 2';
|
||||
|
||||
const selectors = {
|
||||
editor: '.react-monaco-editor-container',
|
||||
saveCodeBtn: '[data-cy="save-code-to-database-btn"]'
|
||||
};
|
||||
|
||||
describe('multifileCertProjects', function () {
|
||||
before(() => {
|
||||
cy.exec('npm run seed');
|
||||
cy.login();
|
||||
cy.visit(
|
||||
'learn/responsive-web-design/responsive-web-design-projects/build-a-tribute-page'
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
cy.preserveSession();
|
||||
});
|
||||
|
||||
it('should show a save code button', function () {
|
||||
cy.get(selectors.saveCodeBtn);
|
||||
});
|
||||
|
||||
it('should save to database (savedChallenges) when clicking save code button', function () {
|
||||
cy.get(selectors.editor).click().focused().clear().type(save1text);
|
||||
cy.get(selectors.saveCodeBtn).click();
|
||||
cy.contains('Your code was saved to the database.');
|
||||
});
|
||||
|
||||
it('should not allow you to save twice in a row too fast', function () {
|
||||
cy.get(selectors.saveCodeBtn).click().click();
|
||||
cy.contains('Your code was not saved.');
|
||||
});
|
||||
|
||||
it('should load saved code on a hard refresh', function () {
|
||||
cy.reload();
|
||||
cy.contains(save1text);
|
||||
});
|
||||
|
||||
it('should save to database (savedChallenges) when using ctrl+s hotkey', function () {
|
||||
cy.get(selectors.editor)
|
||||
.click()
|
||||
.focused()
|
||||
.clear()
|
||||
.type(`${save2text}{ctrl+s}`);
|
||||
cy.contains('Your code was saved to the database.');
|
||||
});
|
||||
|
||||
it('should load saved code when navigating site (no hard refresh)', function () {
|
||||
cy.contains('Responsive Web Design Projects').click();
|
||||
cy.contains('Build a Tribute Page').click();
|
||||
cy.contains(save2text);
|
||||
});
|
||||
});
|
||||
@@ -10,17 +10,17 @@ describe('Donate page', () => {
|
||||
});
|
||||
|
||||
const projects = [
|
||||
'tribute-page',
|
||||
'survey-form',
|
||||
'product-landing-page',
|
||||
'technical-documentation-page',
|
||||
'personal-portfolio-webpage'
|
||||
'random-quote-machine',
|
||||
'markdown-previewer',
|
||||
'drum-machine',
|
||||
'javascript-calculator',
|
||||
'25--5-clock'
|
||||
];
|
||||
|
||||
it('Should be able to submit projects', () => {
|
||||
const submitProject = str => {
|
||||
cy.visit(
|
||||
`/learn/responsive-web-design/responsive-web-design-projects/build-a-${str}`
|
||||
`/learn/front-end-development-libraries/front-end-development-libraries-projects/build-a-${str}`
|
||||
);
|
||||
cy.get('#dynamic-front-end-form')
|
||||
.get('#solution')
|
||||
@@ -37,7 +37,7 @@ describe('Donate page', () => {
|
||||
|
||||
it('Should have a pop up modal', () => {
|
||||
cy.contains(
|
||||
'Nicely done. You just completed Responsive Web Design Projects.'
|
||||
'Nicely done. You just completed Front End Development Libraries Projects.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
import { SuperBlocks } from '../../../../config/certification-settings';
|
||||
|
||||
const projects = {
|
||||
superBlock: SuperBlocks.RespWebDesign,
|
||||
block: 'responsive-web-design-projects',
|
||||
superBlock: SuperBlocks.FrontEndDevLibs,
|
||||
block: 'front-end-development-libraries-projects',
|
||||
challenges: [
|
||||
{
|
||||
slug: 'build-a-tribute-page',
|
||||
slug: 'build-a-random-quote-machine',
|
||||
solution: 'https://codepen.io/moT01/pen/ZpJpKp'
|
||||
},
|
||||
{
|
||||
slug: 'build-a-survey-form',
|
||||
slug: 'build-a-markdown-previewer',
|
||||
solution: 'https://codepen.io/moT01/pen/LrrjGz?editors=1010'
|
||||
},
|
||||
{
|
||||
slug: 'build-a-product-landing-page',
|
||||
slug: 'build-a-drum-machine',
|
||||
solution: 'https://codepen.io/moT01/full/qKyKYL/'
|
||||
},
|
||||
{
|
||||
slug: 'build-a-technical-documentation-page',
|
||||
slug: 'build-a-javascript-calculator',
|
||||
solution: 'https://codepen.io/moT01/full/JBvzNL/'
|
||||
},
|
||||
{
|
||||
slug: 'build-a-personal-portfolio-webpage',
|
||||
slug: 'build-a-25--5-clock',
|
||||
solution: 'https://codepen.io/moT01/pen/vgOaoJ'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
describe('Responsive Web Design Superblock', () => {
|
||||
describe('Front End Development Libraries Superblock', () => {
|
||||
before(() => {
|
||||
cy.exec('npm run seed');
|
||||
cy.login();
|
||||
cy.visit('/learn/responsive-web-design');
|
||||
cy.visit('/learn/front-end-development-libraries');
|
||||
});
|
||||
describe('Before submitting projects', () => {
|
||||
it('should navigate to "/settings#certification-settings" when clicking the "Go to settings to claim your certification" anchor', () => {
|
||||
@@ -53,7 +53,7 @@ describe('Responsive Web Design Superblock', () => {
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(`/settings`);
|
||||
});
|
||||
cy.get('[data-cy=btn-for-responsive-web-design]').click();
|
||||
cy.get('[data-cy=btn-for-front-end-development-libraries]').click();
|
||||
cy.contains('Show Certification').click();
|
||||
cy.location().should(loc => {
|
||||
expect(loc.pathname).to.eq(
|
||||
|
||||
@@ -37,6 +37,15 @@ Cypress.Commands.add('login', () => {
|
||||
cy.contains('Welcome back');
|
||||
});
|
||||
|
||||
Cypress.Commands.add('preserveSession', () => {
|
||||
Cypress.Cookies.preserveOnce(
|
||||
'jwt_access_token',
|
||||
'csrf_token',
|
||||
'_csrf',
|
||||
'connect.sid'
|
||||
);
|
||||
});
|
||||
|
||||
Cypress.Commands.add('toggleAll', () => {
|
||||
cy.visit('/settings');
|
||||
// cy.get('input[name="isLocked"]').click();
|
||||
|
||||
Reference in New Issue
Block a user