├── .gitattributes ├── .gitignore ├── README-template.md ├── README.md ├── app ├── js │ └── script.js └── scss │ ├── components │ ├── _index.scss │ ├── card-grid.scss │ ├── card.scss │ ├── header.scss │ └── toggle.scss │ ├── globals │ ├── _index.scss │ ├── boilerplate.scss │ ├── colors.scss │ ├── fonts.scss │ ├── layout.scss │ └── typography.scss │ ├── style.scss │ └── util │ ├── _index.scss │ ├── breakpoints.scss │ └── functions.scss ├── gulpfile.js ├── images ├── favicon-32x32.png ├── icon-down.svg ├── icon-facebook.svg ├── icon-instagram.svg ├── icon-twitter.svg ├── icon-up.svg └── icon-youtube.svg ├── index-accessibility.html ├── index.html ├── notes.md ├── package-lock.json ├── package.json └── style-guide.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Avoid accidental Sketch file upload 2 | ############################################### 3 | ## Please do not remove line 5 - thanks! 🙂 ## 4 | ############################################### 5 | *.sketch 6 | 7 | # Avoid accidental XD or Figma upload if you convert the design file 8 | ####################################################### 9 | ## Please do not remove lines 11 and 12 - thanks! 🙂 ## 10 | ####################################################### 11 | *.xd 12 | *.fig 13 | 14 | # Avoid your project being littered with annoying .DS_Store files! 15 | .DS_Store 16 | 17 | node_modules/ 18 | dist/ 19 | design/ -------------------------------------------------------------------------------- /README-template.md: -------------------------------------------------------------------------------- 1 | # Frontend Mentor - Social media dashboard with theme switcher solution 2 | 3 | This is a solution to the [Social media dashboard with theme switcher challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/social-media-dashboard-with-theme-switcher-6oY8ozp_H). Frontend Mentor challenges help you improve your coding skills by building realistic projects. 4 | 5 | ## Table of contents 6 | 7 | - [Overview](#overview) 8 | - [The challenge](#the-challenge) 9 | - [Screenshot](#screenshot) 10 | - [Links](#links) 11 | - [My process](#my-process) 12 | - [Built with](#built-with) 13 | - [What I learned](#what-i-learned) 14 | - [Continued development](#continued-development) 15 | - [Useful resources](#useful-resources) 16 | - [Author](#author) 17 | - [Acknowledgments](#acknowledgments) 18 | 19 | **Note: Delete this note and update the table of contents based on what sections you keep.** 20 | 21 | ## Overview 22 | 23 | ### The challenge 24 | 25 | Users should be able to: 26 | 27 | - View the optimal layout for the site depending on their device's screen size 28 | - See hover states for all interactive elements on the page 29 | - Toggle color theme to their preference 30 | 31 | ### Screenshot 32 | 33 | ![](./screenshot.jpg) 34 | 35 | Add a screenshot of your solution. The easiest way to do this is to use Firefox to view your project, right-click the page and select "Take a Screenshot". You can choose either a full-height screenshot or a cropped one based on how long the page is. If it's very long, it might be best to crop it. 36 | 37 | Alternatively, you can use a tool like [FireShot](https://getfireshot.com/) to take the screenshot. FireShot has a free option, so you don't need to purchase it. 38 | 39 | Then crop/optimize/edit your image however you like, add it to your project, and update the file path in the image above. 40 | 41 | **Note: Delete this note and the paragraphs above when you add your screenshot. If you prefer not to add a screenshot, feel free to remove this entire section.** 42 | 43 | ### Links 44 | 45 | - Solution URL: [Add solution URL here](https://your-solution-url.com) 46 | - Live Site URL: [Add live site URL here](https://your-live-site-url.com) 47 | 48 | ## My process 49 | 50 | ### Built with 51 | 52 | - Semantic HTML5 markup 53 | - CSS custom properties 54 | - Flexbox 55 | - CSS Grid 56 | - Mobile-first workflow 57 | - [React](https://reactjs.org/) - JS library 58 | - [Next.js](https://nextjs.org/) - React framework 59 | - [Styled Components](https://styled-components.com/) - For styles 60 | 61 | **Note: These are just examples. Delete this note and replace the list above with your own choices** 62 | 63 | ### What I learned 64 | 65 | Use this section to recap over some of your major learnings while working through this project. Writing these out and providing code samples of areas you want to highlight is a great way to reinforce your own knowledge. 66 | 67 | To see how you can add code snippets, see below: 68 | 69 | ```html 70 |

Some HTML code I'm proud of

71 | ``` 72 | ```css 73 | .proud-of-this-css { 74 | color: papayawhip; 75 | } 76 | ``` 77 | ```js 78 | const proudOfThisFunc = () => { 79 | console.log('🎉') 80 | } 81 | ``` 82 | 83 | If you want more help with writing markdown, we'd recommend checking out [The Markdown Guide](https://www.markdownguide.org/) to learn more. 84 | 85 | **Note: Delete this note and the content within this section and replace with your own learnings.** 86 | 87 | ### Continued development 88 | 89 | Use this section to outline areas that you want to continue focusing on in future projects. These could be concepts you're still not completely comfortable with or techniques you found useful that you want to refine and perfect. 90 | 91 | **Note: Delete this note and the content within this section and replace with your own plans for continued development.** 92 | 93 | ### Useful resources 94 | 95 | - [Example resource 1](https://www.example.com) - This helped me for XYZ reason. I really liked this pattern and will use it going forward. 96 | - [Example resource 2](https://www.example.com) - This is an amazing article which helped me finally understand XYZ. I'd recommend it to anyone still learning this concept. 97 | 98 | **Note: Delete this note and replace the list above with resources that helped you during the challenge. These could come in handy for anyone viewing your solution or for yourself when you look back on this project in the future.** 99 | 100 | ## Author 101 | 102 | - Website - [Add your name here](https://www.your-site.com) 103 | - Frontend Mentor - [@yourusername](https://www.frontendmentor.io/profile/yourusername) 104 | - Twitter - [@yourusername](https://www.twitter.com/yourusername) 105 | 106 | **Note: Delete this note and add/remove/edit lines above based on what links you'd like to share.** 107 | 108 | ## Acknowledgments 109 | 110 | This is where you can give a hat tip to anyone who helped you out on this project. Perhaps you worked in a team or got some inspiration from someone else's solution. This is the perfect place to give them some credit. 111 | 112 | **Note: Delete this note and edit this section's content as necessary. If you completed this challenge by yourself, feel free to delete this section entirely.** 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Frontend Mentor - Social media dashboard with theme switcher 2 | 3 | This code is from my [YouTube video series](https://www.youtube.com/watch?v=iL4irerdGdU&list=PLUWqFDiirlsu5az5EIyxe8ZddyNO_kDuP) building a social media dashboard with dark/light toggle. 4 | 5 | [Frontend Mentor challenge](https://www.frontendmentor.io/challenges/social-media-dashboard-with-theme-switcher-6oY8ozp_H) 6 | 7 | Check out the live website [here](https://codercoder-darklight-toggle.pages.dev/)! 8 | -------------------------------------------------------------------------------- /app/js/script.js: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | The first time the page is loaded, the color mode set on the preference 4 | is used and set as 'default' in the local storage. 5 | Changing the default preferences works the same way as changing the 6 | color mode using the buttons, if the page is loaded. 7 | When the page is reloaded, whatever is the value set on the local storage 8 | has precedence over the values in the preference. If the preference 9 | changed after the page was visited - and the page is not loaded - 10 | the last value saved on the local storage is loaded. 11 | */ 12 | 13 | const darkButton = document.getElementById('dark'); 14 | const lightButton = document.getElementById('light'); 15 | 16 | const setDarkMode = () => { 17 | document.querySelector('body').classList = 'dark'; 18 | localStorage.setItem('colorMode', 'dark'); 19 | }; 20 | 21 | const setLightMode = () => { 22 | document.querySelector('body').classList = 'light'; 23 | localStorage.setItem('colorMode', 'light'); 24 | }; 25 | 26 | const colorModeFromLocalStorage = () => { 27 | return localStorage.getItem('colorMode'); 28 | }; 29 | 30 | const colorModeFromPreferences = () => { 31 | return window.matchMedia('(prefers-color-scheme: dark)').matches 32 | ? 'dark' 33 | : 'light' // If preference is set or does not match anything (light is default) 34 | }; 35 | 36 | const loadAndUpdateColor = () => { 37 | // local storage has precendence over the prefers-color-scheme 38 | const color = colorModeFromLocalStorage() || colorModeFromPreferences(); 39 | color == 'dark' ? darkButton.click() : lightButton.click(); 40 | }; 41 | 42 | // when the inputs are clicked, check which radio button is checked and change the color 43 | const radioButtons = document.querySelectorAll('.toggle__wrapper input'); 44 | radioButtons.forEach(button => { 45 | button.addEventListener('click', (event) => { 46 | darkButton.checked ? setDarkMode() : setLightMode(); 47 | }); 48 | }); 49 | 50 | // when the prefers-color-scheme changes, this event will be emitted 51 | // event reflects the media query, if it matches, the new color is dark, else it is light 52 | window.matchMedia('(prefers-color-scheme: dark)') 53 | .addEventListener('change', (event) => { 54 | event.matches ? darkButton.click() : lightButton.click(); 55 | }); 56 | 57 | // Load the right color on startup - localStorage has precedence 58 | loadAndUpdateColor(); -------------------------------------------------------------------------------- /app/scss/components/_index.scss: -------------------------------------------------------------------------------- 1 | @forward 'header'; 2 | @forward 'toggle'; 3 | @forward 'card'; 4 | @forward 'card-grid'; 5 | -------------------------------------------------------------------------------- /app/scss/components/card-grid.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | 3 | .card-grid { 4 | display: grid; 5 | grid-template-columns: repeat(2, 1fr); 6 | grid-template-rows: repeat(2, auto); 7 | justify-items: start; 8 | gap: rem(23); 9 | 10 | .card__subtitle { 11 | } 12 | 13 | .card__count { 14 | margin-bottom: 0; 15 | } 16 | 17 | .card__count, 18 | .card__change { 19 | align-self: end; 20 | } 21 | 22 | img, 23 | .card__change { 24 | justify-self: end; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/scss/components/card.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | 3 | .cards { 4 | display: grid; 5 | grid-template-columns: 1fr; 6 | gap: rem(30); 7 | 8 | @include breakpoint(medium) { 9 | grid-template-columns: repeat(2, 1fr); 10 | } 11 | 12 | @include breakpoint(large) { 13 | grid-template-columns: repeat(4, 1fr); 14 | } 15 | } 16 | 17 | .card { 18 | position: relative; 19 | overflow: hidden; 20 | background: var(--card-bg); 21 | color: var(--dark-text1); 22 | padding: rem(25); 23 | border-radius: rem(5); 24 | text-align: center; 25 | transition: background 150ms ease-in-out; 26 | cursor: pointer; 27 | 28 | &:hover { 29 | background: var(--card-hover); 30 | } 31 | 32 | &--facebook { 33 | border-top: rem(5) solid var(--facebook); 34 | } 35 | 36 | &--twitter { 37 | border-top: rem(5) solid var(--twitter); 38 | } 39 | 40 | &--instagram { 41 | padding-top: rem(30); 42 | 43 | &::before { 44 | content: ''; 45 | position: absolute; 46 | display: block; 47 | left: 0; 48 | top: 0; 49 | width: 100%; 50 | height: rem(5); 51 | background: linear-gradient( 52 | 225deg, 53 | var(--instagram-end), 54 | var(--instagram-middle) 50.91%, 55 | var(--instagram-start) 100% 56 | ); // var(--instagram-start); 57 | } 58 | //border-top: rem(5) solid linear-gradient(225deg, hsl(329, 70%, 58%) 0%, hsl(5, 77%, 71%) 50.91%, #fdc366 100%); 59 | } 60 | 61 | &--youtube { 62 | border-top: rem(5) solid var(--youtube); 63 | } 64 | 65 | &__platform { 66 | display: flex; 67 | justify-content: center; 68 | align-items: center; 69 | height: rem(20); 70 | margin-top: rem(5); 71 | margin-bottom: rem(28); 72 | } 73 | 74 | &__subtitle { 75 | font-size: rem(14); 76 | font-weight: 700; 77 | color: var(--text-color2); 78 | } 79 | 80 | &__icon { 81 | margin-right: rem(8); 82 | 83 | &--facebook { 84 | } 85 | 86 | &--twitter { 87 | } 88 | 89 | &--instagram { 90 | } 91 | 92 | &--youtube { 93 | } 94 | } 95 | 96 | &__username { 97 | font-size: rem(12); 98 | font-weight: 700; 99 | color: var(--text-color2); 100 | } 101 | 102 | &__followers { 103 | margin-bottom: rem(25); 104 | } 105 | 106 | &__count { 107 | color: var(--text-color); 108 | font-weight: 700; 109 | letter-spacing: rem(-2); 110 | line-height: 1; 111 | margin-bottom: rem(4); 112 | 113 | &--big { 114 | font-size: rem(56); 115 | } 116 | 117 | &--small { 118 | font-size: rem(32); 119 | } 120 | } 121 | 122 | &__label { 123 | font-size: rem(12); 124 | letter-spacing: rem(5); 125 | font-weight: 400; 126 | color: var(--text-color2); 127 | text-transform: uppercase; 128 | } 129 | 130 | &__change { 131 | display: flex; 132 | align-items: center; 133 | justify-content: center; 134 | font-size: rem(12); 135 | font-weight: 700; 136 | 137 | &--up { 138 | color: var(--limegreen); 139 | } 140 | 141 | &--down { 142 | color: var(--brightred); 143 | } 144 | 145 | img { 146 | margin-right: rem(4); 147 | } 148 | } 149 | 150 | &__number { 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /app/scss/components/header.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | 3 | .header { 4 | display: flex; 5 | flex-wrap: wrap; 6 | margin-top: rem(36); 7 | 8 | @include breakpoint(medium) { 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | &__title { 14 | width: 100%; 15 | 16 | @include breakpoint(medium) { 17 | width: auto; 18 | } 19 | } 20 | 21 | &__subtitle { 22 | font-size: rem(14); 23 | font-weight: 700; 24 | color: var(--text-color2); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/scss/components/toggle.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | // https://codepen.io/SaraSoueidan/pen/jpBbrq/?editors=1100 3 | 4 | .toggle { 5 | display: grid; 6 | grid-template-columns: 1fr 3rem; 7 | border: none; 8 | margin: 0; 9 | 10 | label { 11 | font-size: rem(14); 12 | font-weight: 700; 13 | color: var(--toggle); 14 | 15 | &[for='dark'] { 16 | line-height: rem(24); 17 | margin-right: rem(13); 18 | } 19 | } 20 | 21 | &__wrapper { 22 | position: relative; 23 | height: rem(24); 24 | } 25 | 26 | input[type='radio'] { 27 | margin: 0 rem(-2) 0 rem(-2); 28 | opacity: 0; 29 | width: rem(24); 30 | height: rem(24); 31 | 32 | &:focus ~ .toggle__button { 33 | border: 2px solid white; 34 | } 35 | } 36 | 37 | &__background { 38 | display: block; 39 | height: 100%; 40 | position: absolute; 41 | width: 100%; 42 | top: 0; 43 | border-radius: rem(12); 44 | background: var(--toggle-bg); 45 | pointer-events: none; 46 | } 47 | 48 | &__button { 49 | position: absolute; 50 | left: rem(3); 51 | top: rem(3); 52 | right: 100%; 53 | width: rem(18); 54 | height: rem(18); 55 | border-radius: 50%; 56 | background-color: var(--toggle-button); 57 | transition: all 150ms ease-in-out; 58 | } 59 | 60 | #light:checked ~ .toggle__button { 61 | left: calc(100% - 21px); 62 | right: 3px; 63 | } 64 | 65 | #system:checked ~ .toggle__button { 66 | left: 50%; 67 | right: auto; 68 | transform: translate(-50%); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/scss/globals/_index.scss: -------------------------------------------------------------------------------- 1 | @forward 'fonts'; 2 | @forward 'colors'; 3 | @forward 'boilerplate'; 4 | @forward 'typography'; 5 | @forward 'layout'; 6 | -------------------------------------------------------------------------------- /app/scss/globals/boilerplate.scss: -------------------------------------------------------------------------------- 1 | html { 2 | font-size: 100%; 3 | box-sizing: border-box; 4 | } 5 | 6 | *, 7 | *::before, 8 | *::after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | padding: 0; 15 | font-family: var(--font-inter); 16 | background: var(--background); 17 | color: var(--text-color); 18 | } 19 | 20 | .visually-hidden { 21 | position: absolute; 22 | left: -10000px; 23 | top: auto; 24 | width: 1px; 25 | height: 1px; 26 | overflow: hidden; 27 | } 28 | -------------------------------------------------------------------------------- /app/scss/globals/colors.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --limegreen: hsl(163, 72%, 41%); 3 | --brightred: hsl(356, 69%, 56%); 4 | --facebook: hsl(208, 92%, 53%); 5 | --twitter: hsl(203, 89%, 53%); 6 | --instagram-start: hsl(37, 97%, 70%); 7 | --instagram-middle: hsl(5, 77%, 71%); 8 | --instagram-end: hsl(329, 70%, 58%); 9 | --youtube: hsl(348, 97%, 39%); 10 | --toggle-bg-light: hsl(230, 22%, 74%); 11 | --toggle-bg-start: hsl(210, 78%, 56%); 12 | --toggle-bg-end: hsl(146, 68%, 55%); 13 | --toggle-light: hsl(230, 19%, 60%); 14 | --toggle-button-light: hsl(228, 46%, 96%); 15 | --dark-bg: hsl(230, 17%, 14%); 16 | --dark-top-bg: hsl(232, 19%, 15%); 17 | --dark-card: hsl(228, 28%, 20%); 18 | --dark-card-hover: hsl(228, 25%, 27%); 19 | --dark-text1: hsl(228, 34%, 66%); 20 | --dark-text2: hsl(0, 0%, 100%); 21 | --light-bg: hsl(0, 0%, 100%); 22 | --light-top-bg: hsl(225, 100%, 98%); 23 | --light-card: hsl(227, 47%, 96%); 24 | --light-card-hover: hsl(228, 33%, 91%); 25 | --light-text1: hsl(230, 12%, 44%); 26 | --light-text2: hsl(230, 17%, 14%); 27 | --background: var(--light-bg); 28 | --text-color: var(--light-text2); 29 | --text-color2: var(--light-text1); 30 | --card-bg: var(--light-card); 31 | --card-hover: var(--light-card-hover); 32 | --toggle: var(--toggle-light); 33 | --toggle-bg: var(--toggle-bg-light); 34 | --toggle-button: var(--toggle-button-light); 35 | } 36 | 37 | @media (prefers-color-scheme: dark) { 38 | :root { 39 | --background: var(--dark-bg); 40 | --text-color: var(--dark-text2); 41 | --text-color2: var(--dark-text1); 42 | --card-bg: var(--dark-card); 43 | --card-hover: var(--dark-card-hover); 44 | --toggle: var(--light-bg); 45 | --toggle-bg: linear-gradient( 46 | 225deg, 47 | var(--toggle-bg-end) 0%, 48 | var(--toggle-bg-start) 98.02% 49 | ); 50 | --toggle-button: var(--dark-bg); 51 | } 52 | } 53 | 54 | body.light { 55 | --background: var(--light-bg); 56 | --text-color: var(--light-text2); 57 | --text-color2: var(--light-text1); 58 | --card-bg: var(--light-card); 59 | --card-hover: var(--light-card-hover); 60 | --toggle: var(--toggle-light); 61 | --toggle-bg: var(--toggle-bg-light); 62 | --toggle-button: var(--toggle-button-light); 63 | } 64 | 65 | body.dark { 66 | --background: var(--dark-bg); 67 | --text-color: var(--dark-text2); 68 | --text-color2: var(--dark-text1); 69 | --card-bg: var(--dark-card); 70 | --card-hover: var(--dark-card-hover); 71 | --toggle: var(--light-bg); 72 | --toggle-bg: linear-gradient( 73 | 225deg, 74 | var(--toggle-bg-end) 0%, 75 | var(--toggle-bg-start) 98.02% 76 | ); 77 | --toggle-button: var(--dark-bg); 78 | } 79 | -------------------------------------------------------------------------------- /app/scss/globals/fonts.scss: -------------------------------------------------------------------------------- 1 | :root { 2 | --font-inter: 'Inter', sans-serif; 3 | } 4 | -------------------------------------------------------------------------------- /app/scss/globals/layout.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | 3 | .container { 4 | padding: 0 rem(25); 5 | max-width: rem(1110); 6 | margin: 0 auto rem(46); 7 | 8 | @include breakpoint(large) { 9 | padding: 0; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/scss/globals/typography.scss: -------------------------------------------------------------------------------- 1 | @use '../util' as *; 2 | 3 | h1, 4 | h2, 5 | h3 { 6 | margin-top: 0; 7 | line-height: 1.1; 8 | } 9 | 10 | h1 { 11 | font-size: rem(24); 12 | margin-bottom: rem(3); 13 | @include breakpoint(large) { 14 | font-size: rem(28); 15 | } 16 | } 17 | 18 | h2 { 19 | font-size: rem(24); 20 | margin-bottom: rem(24); 21 | color: var(--text-color2); 22 | } 23 | 24 | a, 25 | a:visited, 26 | a:active { 27 | text-decoration: none; 28 | } 29 | -------------------------------------------------------------------------------- /app/scss/style.scss: -------------------------------------------------------------------------------- 1 | @use 'globals'; 2 | @use 'components'; 3 | -------------------------------------------------------------------------------- /app/scss/util/_index.scss: -------------------------------------------------------------------------------- 1 | @forward 'breakpoints'; 2 | @forward 'functions'; 3 | -------------------------------------------------------------------------------- /app/scss/util/breakpoints.scss: -------------------------------------------------------------------------------- 1 | // 640px, 1150px, 1400px 2 | $breakpoints-up: ( 3 | 'medium': '40em', 4 | 'large': '71.875em', 5 | 'xlarge': '87.5em', 6 | ); 7 | 8 | // 639px, 1149px, 1399px 9 | $breakpoints-down: ( 10 | 'small': '39.9375em', 11 | 'medium': '71.8125em', 12 | 'large': '87.4375em', 13 | ); 14 | 15 | @mixin breakpoint($size) { 16 | @media (min-width: map-get($breakpoints-up, $size)) { 17 | @content; 18 | } 19 | } 20 | 21 | @mixin breakpoint-down($size) { 22 | @media (max-width: map-get($breakpoints-down, $size)) { 23 | @content; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/scss/util/functions.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | 3 | // Source: https://css-tricks.com/snippets/sass/px-to-em-functions/ 4 | @function rem($pixels, $context: 16) { 5 | @return (math.div($pixels, $context)) * 1rem; 6 | } 7 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | // Initialize modules 2 | const { src, dest, watch, series } = require('gulp'); 3 | const sass = require('gulp-sass')(require('sass')); 4 | const postcss = require('gulp-postcss'); 5 | const autoprefixer = require('autoprefixer'); 6 | const cssnano = require('cssnano'); 7 | const babel = require('gulp-babel'); 8 | const terser = require('gulp-terser'); 9 | const browsersync = require('browser-sync').create(); 10 | 11 | // Use dart-sass for @use 12 | //sass.compiler = require('dart-sass'); 13 | 14 | // Sass Task 15 | function scssTask() { 16 | return src('app/scss/style.scss', { sourcemaps: true }) 17 | .pipe(sass()) 18 | .pipe(postcss([autoprefixer(), cssnano()])) 19 | .pipe(dest('dist', { sourcemaps: '.' })); 20 | } 21 | 22 | // JavaScript Task 23 | function jsTask() { 24 | return src('app/js/script.js', { sourcemaps: true }) 25 | .pipe(babel({ presets: ['@babel/preset-env'] })) 26 | .pipe(terser()) 27 | .pipe(dest('dist', { sourcemaps: '.' })); 28 | } 29 | 30 | // Browsersync 31 | function browserSyncServe(cb) { 32 | browsersync.init({ 33 | server: { 34 | baseDir: '.', 35 | }, 36 | notify: { 37 | styles: { 38 | top: 'auto', 39 | bottom: '0', 40 | }, 41 | }, 42 | }); 43 | cb(); 44 | } 45 | function browserSyncReload(cb) { 46 | browsersync.reload(); 47 | cb(); 48 | } 49 | 50 | // Watch Task 51 | function watchTask() { 52 | watch('*.html', browserSyncReload); 53 | watch( 54 | ['app/scss/**/*.scss', 'app/**/*.js'], 55 | series(scssTask, jsTask, browserSyncReload) 56 | ); 57 | } 58 | 59 | // Default Gulp Task 60 | exports.default = series(scssTask, jsTask, browserSyncServe, watchTask); 61 | 62 | // Build Gulp Task 63 | exports.build = series(scssTask, jsTask); 64 | -------------------------------------------------------------------------------- /images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thecodercoder/fem-dklt-toggle/b918012b3b329f7b7135aba4ab83210ba6a8633b/images/favicon-32x32.png -------------------------------------------------------------------------------- /images/icon-down.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/icon-facebook.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/icon-instagram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/icon-twitter.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/icon-up.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /images/icon-youtube.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index-accessibility.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | 16 | Frontend Mentor | Social Media Dashboard with Dark/Light Toggle 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 31 | 40 | 41 | 42 |
43 |
44 |

Social Media Dashboard

45 | Total Followers: 23,004 46 |
47 | 48 |
53 |
54 | 55 | 56 | 57 | 58 | 59 |
60 | 61 | 62 | 63 |
64 |
65 | 66 |
67 |
68 | 87 | 88 | 106 | 107 |
108 |
109 | Instagram 114 |
@nathanf
115 |
116 |
117 |
11k
118 |
Followers
119 |
120 |
121 | up arrow 122 |
1099 Today
123 |
124 |
125 | 126 |
127 |
128 | YouTube 133 |
@nathanf
134 |
135 |
136 |
8239
137 |
Followers
138 |
139 |
140 | down arrow 141 |
144 Today
142 |
143 |
144 |
145 | 146 |
147 |

Overview - Today

148 |
149 |
150 |

151 | Page Views for Facebook 152 |

153 | Facebook 154 |
87
155 |
156 | up arrow 157 |
3%
158 |
159 |
160 |
161 |

Likes

162 | Facebook 163 |
52
164 |
165 | down arrow 166 |
2%
167 |
168 |
169 |
170 |
Likes
171 | Instagram 172 |
5462
173 |
174 | up arrow 175 |
2257%
176 |
177 |
178 |
179 |
Profile Views
180 | Instagram 181 |
52k
182 |
183 | up arrow 184 |
1375%
185 |
186 |
187 |
188 |
Retweets
189 | Twitter 190 |
117
191 |
192 | up arrow 193 |
303%
194 |
195 |
196 |
197 |
Likes
198 | Twitter 199 |
507
200 |
201 | up arrow 202 |
553%
203 |
204 |
205 |
206 |
Likes
207 | YouTube 208 |
107
209 |
210 | down arrow 211 |
19%
212 |
213 |
214 |
215 |
Total Views
216 | YouTube 217 |
1407
218 |
219 | down arrow 220 |
12%
221 |
222 |
223 |
224 |
225 |
226 | 227 | 233 | 234 | 235 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15 | Frontend Mentor | [Challenge Name Here] 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 28 | 29 | 38 | 39 | 40 |
41 |
42 |

Social Media Dashboard 🔥🔥🔥🔥🔥

43 | Total Followers: 23,004 44 |
45 | 46 |
51 | 54 |
55 | 56 | 57 | 58 | 59 |
60 | 61 |
62 |
63 | 64 |
65 |
66 | 84 | 85 | 103 | 104 |
105 |
106 | Instagram 111 |
@nathanf
112 |
113 |
114 |
11k
115 |
Followers
116 |
117 |
118 | up arrow 119 |
1099 Today
120 |
121 |
122 | 123 |
124 |
125 | YouTube 130 |
@nathanf
131 |
132 |
133 |
8239
134 |
Followers
135 |
136 |
137 | down arrow 138 |
144 Today
139 |
140 |
141 |
142 | 143 |
144 |

Overview - Today

145 |
146 |
147 |
Page Views
148 | Facebook 149 |
87
150 |
151 | up arrow 152 |
3%
153 |
154 |
155 |
156 |
Likes
157 | Facebook 158 |
52
159 |
160 | down arrow 161 |
2%
162 |
163 |
164 |
165 |
Likes
166 | Instagram 167 |
5462
168 |
169 | up arrow 170 |
2257%
171 |
172 |
173 |
174 |
Profile Views
175 | Instagram 176 |
52k
177 |
178 | up arrow 179 |
1375%
180 |
181 |
182 |
183 |
Retweets
184 | Twitter 185 |
117
186 |
187 | up arrow 188 |
303%
189 |
190 |
191 |
192 |
Likes
193 | Twitter 194 |
507
195 |
196 | up arrow 197 |
553%
198 |
199 |
200 |
201 |
Likes
202 | YouTube 203 |
107
204 |
205 | down arrow 206 |
19%
207 |
208 |
209 |
210 |
Total Views
211 | YouTube 212 |
1407
213 |
214 | down arrow 215 |
12%
216 |
217 |
218 |
219 |
220 |
221 | 222 |
223 | Challenge by 224 | Frontend Mentor. Coded by Your Name Here. 227 |
228 | 229 | 230 | -------------------------------------------------------------------------------- /notes.md: -------------------------------------------------------------------------------- 1 | # Functional Requirements and Notes 2 | 3 | Light/Dark Mode toggle -- takes system pref by default, but can override with toggle 4 | 5 | What HTML markup (accessible) -- https://scottaohara.github.io/a11y_styled_form_controls/src/radio-button--switch/ 6 | 7 | Use fieldset, legend, radio inputs 8 | 9 | Switching between light/dark modes via JS and Prefers-color-scheme media query -- https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme 10 | 11 | https://piccalil.li/tutorial/create-a-user-controlled-dark-or-light-mode/ 12 | 13 | Three option toggle: light/dark/system pref -- https://codepen.io/renddrew/pen/bRomab?editors=1100 14 | 15 | CSS Variables (custom properties) -- https://css-tricks.com/updating-a-css-variable-with-javascript/ 16 | 17 | Accessibility 18 | 19 | - Use correct heading tags 20 | - Screenreader-only text for card titles/username -- https://www.accessibility-developer-guide.com/examples/hiding-elements/visually/ 21 | 22 | NVDA hotkeys: 23 | https://webaim.org/resources/shortcuts/nvda 24 | 25 | - Ctrl will stop speech immediately 26 | - Caps Lock is NVDA key 27 | - NVDA + Q will quit NVDA 28 | - NVDA + B will read entire page from top to bottom 29 | - Press H to navigate through headline tags (other elements have hotkeys) 30 | - D will navigate through region/landmark 31 | - Landmarks/region are recognized by
or
tags, role="rolename" or aria-label 32 | - When NVDA recognizes a landmark/region, if no role or aria-label, it will read the first content 33 | - Press NVDA + down arrow to narrate rest of page from current position 34 | - Pressing Shift + H will navigate backwards 35 | - Go to Top? 36 | - Skip to content? 37 | 38 | Outline for video: 39 | 40 | - Accessibility overview 41 | - Install NVDA 42 | - basic controls, Ctrl, navigating headlines and regions and down 43 | - SaraSoueidan.com website 44 | - Try to read FEM site 45 | - Add: 46 | - title, fill in "yout name here" in footer 47 | - role="contentinfo" to footer 48 | - aria-label="" for platform overview and platform details 49 | - Add screenreader-only h3 tags for card titles 50 | 51 | -- 52 | 53 | Semantic HTML and accessibility -- https://www.youtube.com/watch?v=qSNUi7pRmWg 54 | Inclusive cards -- https://inclusive-components.design/cards/ 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fem-dklt-toggle", 3 | "version": "1.0.0", 4 | "description": "![Design preview for the Social media dashboard with theme switcher coding challenge](./design/desktop-preview.jpg)", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@babel/core": "^7.14.6", 14 | "@babel/preset-env": "^7.14.7", 15 | "autoprefixer": "^10.2.6", 16 | "browser-sync": "^2.27.4", 17 | "cssnano": "^5.0.6", 18 | "gulp": "^4.0.2", 19 | "gulp-babel": "^8.0.0", 20 | "gulp-postcss": "^9.0.0", 21 | "gulp-sass": "^5.0.0", 22 | "gulp-terser": "^2.0.1", 23 | "postcss": "^8.3.5", 24 | "sass": "^1.35.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /style-guide.md: -------------------------------------------------------------------------------- 1 | # Front-end Style Guide 2 | 3 | ## Layout 4 | 5 | The designs were created to the following widths: 6 | 7 | - Mobile: 375px 8 | - Desktop: 1440px 9 | 10 | ## Colors 11 | 12 | ### Primary 13 | 14 | - Lime Green: hsl(163, 72%, 41%) 15 | - Bright Red: hsl(356, 69%, 56%) 16 | 17 | - Facebook: hsl(208, 92%, 53%) 18 | - Twitter: hsl(203, 89%, 53%) 19 | - Instagram: linear gradient hsl(37, 97%, 70%) to hsl(329, 70%, 58%) 20 | - YouTube: hsl(348, 97%, 39%) 21 | 22 | #### Dark Theme 23 | 24 | - Toggle: linear gradient hsl(210, 78%, 56%) to hsl(146, 68%, 55%) 25 | 26 | #### Light Theme 27 | 28 | - Toggle: hsl(230, 22%, 74%) 29 | 30 | ### Neutral 31 | 32 | #### Dark Theme 33 | 34 | - Very Dark Blue (BG): hsl(230, 17%, 14%) 35 | - Very Dark Blue (Top BG Pattern): hsl(232, 19%, 15%) 36 | - Dark Desaturated Blue (Card BG): hsl(228, 28%, 20%) 37 | - Desaturated Blue (Text): hsl(228, 34%, 66%) 38 | - White (Text): hsl(0, 0%, 100%) 39 | 40 | #### Light Theme 41 | 42 | - White (BG): hsl(0, 0%, 100%) 43 | - Very Pale Blue (Top BG Pattern): hsl(225, 100%, 98%) 44 | - Light Grayish Blue (Card BG): hsl(227, 47%, 96%) 45 | - Dark Grayish Blue (Text): hsl(228, 12%, 44%) 46 | - Very Dark Blue (Text): hsl(230, 17%, 14%) 47 | 48 | ## Typography 49 | 50 | ### Body Copy 51 | 52 | - Font size (Overview Card Headings): 14px 53 | 54 | ### Font 55 | 56 | - Family: [Inter](https://fonts.google.com/specimen/Inter) 57 | - Weights: 400, 700 58 | --------------------------------------------------------------------------------