diff --git a/.gitignore b/.gitignore index 4d29575..2d6322f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,20 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +# Avoid accidental upload of the Sketch and Figma design files +##################################################### +## Please do not remove lines 5 and 6 - thanks! :) ## +##################################################### +*.sketch +*.fig + +# Avoid accidental XD upload if you convert the design file +############################################### +## Please do not remove line 12 - thanks! :) ## +############################################### +*.xd + +# Avoid your project being littered with annoying .DS_Store files! +.DS_Store +.prettierignore \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..f594353 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "msedge", + "request": "launch", + "name": "Launch Edge against localhost", + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}" + } + ] +} diff --git a/README-template.md b/README-template.md new file mode 100644 index 0000000..ceedd95 --- /dev/null +++ b/README-template.md @@ -0,0 +1,112 @@ +# Frontend Mentor - Launch countdown timer solution + +This is a solution to the [Launch countdown timer challenge on Frontend Mentor](https://www.frontendmentor.io/challenges/launch-countdown-timer-N0XkGfyz-). Frontend Mentor challenges help you improve your coding skills by building realistic projects. + +## Table of contents + +- [Overview](#overview) + - [The challenge](#the-challenge) + - [Screenshot](#screenshot) + - [Links](#links) +- [My process](#my-process) + - [Built with](#built-with) + - [What I learned](#what-i-learned) + - [Continued development](#continued-development) + - [Useful resources](#useful-resources) +- [Author](#author) +- [Acknowledgments](#acknowledgments) + +**Note: Delete this note and update the table of contents based on what sections you keep.** + +## Overview + +### The challenge + +Users should be able to: + +- See hover states for all interactive elements on the page +- See a live countdown timer that ticks down every second (start the count at 14 days) +- **Bonus**: When a number changes, make the card flip from the middle + +### Screenshot + +![](./screenshot.jpg) + +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. + +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. + +Then crop/optimize/edit your image however you like, add it to your project, and update the file path in the image above. + +**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.** + +### Links + +- Solution URL: [Add solution URL here](https://your-solution-url.com) +- Live Site URL: [Add live site URL here](https://your-live-site-url.com) + +## My process + +### Built with + +- Semantic HTML5 markup +- CSS custom properties +- Flexbox +- CSS Grid +- Mobile-first workflow +- [React](https://reactjs.org/) - JS library +- [Next.js](https://nextjs.org/) - React framework +- [Styled Components](https://styled-components.com/) - For styles + +**Note: These are just examples. Delete this note and replace the list above with your own choices** + +### What I learned + +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. + +To see how you can add code snippets, see below: + +```html +

Some HTML code I'm proud of

+``` +```css +.proud-of-this-css { + color: papayawhip; +} +``` +```js +const proudOfThisFunc = () => { + console.log('🎉') +} +``` + +If you want more help with writing markdown, we'd recommend checking out [The Markdown Guide](https://www.markdownguide.org/) to learn more. + +**Note: Delete this note and the content within this section and replace with your own learnings.** + +### Continued development + +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. + +**Note: Delete this note and the content within this section and replace with your own plans for continued development.** + +### Useful resources + +- [Example resource 1](https://www.example.com) - This helped me for XYZ reason. I really liked this pattern and will use it going forward. +- [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. + +**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.** + +## Author + +- Website - [Add your name here](https://www.your-site.com) +- Frontend Mentor - [@yourusername](https://www.frontendmentor.io/profile/yourusername) +- Twitter - [@yourusername](https://www.twitter.com/yourusername) + +**Note: Delete this note and add/remove/edit lines above based on what links you'd like to share.** + +## Acknowledgments + +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. + +**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.** diff --git a/README.md b/README.md index b87cb00..45d59d6 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,95 @@ -# Getting Started with Create React App +# Live demo: https://BadLice.github.io/launch-countdown-timer -This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). +# Frontend Mentor - Launch countdown timer -## Available Scripts +![Design preview for the Launch countdown timer coding challenge](./design/desktop-preview.jpg) -In the project directory, you can run: +## Welcome! 👋 -### `npm start` +Thanks for checking out this front-end coding challenge. -Runs the app in the development mode.\ -Open [http://localhost:3000](http://localhost:3000) to view it in the browser. +[Frontend Mentor](https://www.frontendmentor.io) challenges help you improve your coding skills by building realistic projects. -The page will reload if you make edits.\ -You will also see any lint errors in the console. +**To do this challenge, you need a good understanding of HTML, CSS and JavaScript.** -### `npm test` +## The challenge -Launches the test runner in the interactive watch mode.\ -See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. +Your challenge is to build out this countdown timer and get it looking as close to the design as possible. -### `npm run build` +You can use any tools you like to help you complete the challenge. So if you've got something you'd like to practice, feel free to give it a go. -Builds the app for production to the `build` folder.\ -It correctly bundles React in production mode and optimizes the build for the best performance. +Your users should be able to: -The build is minified and the filenames include the hashes.\ -Your app is ready to be deployed! +- See hover states for all interactive elements on the page +- See a live countdown timer that ticks down every second (start the count at 14 days) +- **Bonus**: When a number changes, make the card flip from the middle -See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. +Want some support on the challenge? [Join our Slack community](https://www.frontendmentor.io/slack) and ask questions in the **#help** channel. -### `npm run eject` +## Where to find everything -**Note: this is a one-way operation. Once you `eject`, you can’t go back!** +Your task is to build out the project to the designs inside the `/design` folder. You will find both a mobile and a desktop version of the design. -If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. +The designs are in JPG static format. Using JPGs will mean that you'll need to use your best judgment for styles such as `font-size`, `padding` and `margin`. -Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. +If you would like the design files (we provide Sketch & Figma versions) to inspect the design in more detail, you can [subscribe as a PRO member](https://www.frontendmentor.io/pro). -You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. +You will find all the required assets in the `/images` folder. The assets are already optimized. -## Learn More +There is also a `style-guide.md` file containing the information you'll need, such as color palette and fonts. -You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). +## Building your project -To learn React, check out the [React documentation](https://reactjs.org/). +Feel free to use any workflow that you feel comfortable with. Below is a suggested process, but do not feel like you need to follow these steps: + +1. Initialize your project as a public repository on [GitHub](https://github.com/). Creating a repo will make it easier to share your code with the community if you need help. If you're not sure how to do this, [have a read-through of this Try Git resource](https://try.github.io/). +2. Configure your repository to publish your code to a web address. This will also be useful if you need some help during a challenge as you can share the URL for your project with your repo URL. There are a number of ways to do this, and we provide some recommendations below. +3. Look through the designs to start planning out how you'll tackle the project. This step is crucial to help you think ahead for CSS classes to create reusable styles. +4. Before adding any styles, structure your content with HTML. Writing your HTML first can help focus your attention on creating well-structured content. +5. Write out the base styles for your project, including general content styles, such as `font-family` and `font-size`. +6. Start adding styles to the top of the page and work down. Only move on to the next section once you're happy you've completed the area you're working on. + +## Deploying your project + +As mentioned above, there are many ways to host your project for free. Our recommend hosts are: + +- [GitHub Pages](https://pages.github.com/) +- [Vercel](https://vercel.com/) +- [Netlify](https://www.netlify.com/) + +You can host your site using one of these solutions or any of our other trusted providers. [Read more about our recommended and trusted hosts](https://medium.com/frontend-mentor/frontend-mentor-trusted-hosting-providers-bf000dfebe). + +## Create a custom `README.md` + +We strongly recommend overwriting this `README.md` with a custom one. We've provided a template inside the [`README-template.md`](./README-template.md) file in this starter code. + +The template provides a guide for what to add. A custom `README` will help you explain your project and reflect on your learnings. Please feel free to edit our template as much as you like. + +Once you've added your information to the template, delete this file and rename the `README-template.md` file to `README.md`. That will make it show up as your repository's README file. + +## Submitting your solution + +Submit your solution on the platform for the rest of the community to see. Follow our ["Complete guide to submitting solutions"](https://medium.com/frontend-mentor/a-complete-guide-to-submitting-solutions-on-frontend-mentor-ac6384162248) for tips on how to do this. + +Remember, if you're looking for feedback on your solution, be sure to ask questions when submitting it. The more specific and detailed you are with your questions, the higher the chance you'll get valuable feedback from the community. + +## Sharing your solution + +There are multiple places you can share your solution: + +1. Share your solution page in the **#finished-projects** channel of the [Slack community](https://www.frontendmentor.io/slack). +2. Tweet [@frontendmentor](https://twitter.com/frontendmentor) and mention **@frontendmentor**, including the repo and live URLs in the tweet. We'd love to take a look at what you've built and help share it around. +3. Share your solution on other social channels like LinkedIn. +4. Blog about your experience building your project. Writing about your workflow, technical choices, and talking through your code is a brilliant way to reinforce what you've learned. Great platforms to write on are [dev.to](https://dev.to/), [Hashnode](https://hashnode.com/), and [CodeNewbie](https://community.codenewbie.org/). + +We provide templates to help you share your solution once you've submitted it on the platform. Please do edit them and include specific questions when you're looking for feedback. + +The more specific you are with your questions the more likely it is that another member of the community will give you feedback. + +## Got feedback for us? + +We love receiving feedback! We're always looking to improve our challenges and our platform. So if you have anything you'd like to mention, please email hi[at]frontendmentor[dot]io. + +This challenge is completely free. Please share it with anyone who will find it useful for practice. + +**Have fun building!** 🚀 diff --git a/design/active-states.jpg b/design/active-states.jpg new file mode 100644 index 0000000..18d53f4 Binary files /dev/null and b/design/active-states.jpg differ diff --git a/design/desktop-design.jpg b/design/desktop-design.jpg new file mode 100644 index 0000000..cfef59b Binary files /dev/null and b/design/desktop-design.jpg differ diff --git a/design/desktop-preview.jpg b/design/desktop-preview.jpg new file mode 100644 index 0000000..ad47278 Binary files /dev/null and b/design/desktop-preview.jpg differ diff --git a/design/mobile-design.jpg b/design/mobile-design.jpg new file mode 100644 index 0000000..76b6f50 Binary files /dev/null and b/design/mobile-design.jpg differ diff --git a/package-lock.json b/package-lock.json index df89c5f..1c6149f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,11 +15,16 @@ "@types/node": "^16.11.65", "@types/react": "^18.0.21", "@types/react-dom": "^18.0.6", + "@types/styled-components": "^5.1.26", "react": "^18.2.0", "react-dom": "^18.2.0", "react-scripts": "5.0.1", + "styled-components": "^5.3.6", "typescript": "^4.8.4", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "gh-pages": "^4.0.0" } }, "node_modules/@adobe/css-tools": { @@ -2140,6 +2145,29 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "dependencies": { + "@emotion/memoize": "^0.8.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -3748,6 +3776,15 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -3907,6 +3944,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "dependencies": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", @@ -4592,6 +4639,15 @@ "node": ">=8" } }, + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", @@ -4940,6 +4996,26 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, + "node_modules/babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -5253,6 +5329,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5675,6 +5759,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -5856,6 +5948,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -6419,6 +6521,12 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "node_modules/email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, "node_modules/emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -7578,6 +7686,32 @@ "node": ">=10" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -8025,6 +8159,103 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "dev": true, + "dependencies": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/gh-pages/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/gh-pages/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/gh-pages/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8227,6 +8458,19 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -12028,6 +12272,27 @@ "node": ">=0.10.0" } }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -14528,6 +14793,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -14887,6 +15157,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -14902,6 +15184,36 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "hasInstallScript": true, + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -15311,6 +15623,18 @@ "node": ">=8" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", @@ -17917,6 +18241,29 @@ "integrity": "sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==", "requires": {} }, + "@emotion/is-prop-valid": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.0.tgz", + "integrity": "sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==", + "requires": { + "@emotion/memoize": "^0.8.0" + } + }, + "@emotion/memoize": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.0.tgz", + "integrity": "sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", @@ -19071,6 +19418,15 @@ "@types/node": "*" } }, + "@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "requires": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -19230,6 +19586,16 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/styled-components": { + "version": "5.1.26", + "resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.26.tgz", + "integrity": "sha512-KuKJ9Z6xb93uJiIyxo/+ksS7yLjS1KzG6iv5i78dhVg/X3u5t1H7juRWqVmodIdz6wGVaIApo1u01kmFRdJHVw==", + "requires": { + "@types/hoist-non-react-statics": "*", + "@types/react": "*", + "csstype": "^3.0.2" + } + }, "@types/testing-library__jest-dom": { "version": "5.14.5", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.5.tgz", @@ -19730,6 +20096,12 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "dev": true + }, "array.prototype.flat": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.0.tgz", @@ -19976,6 +20348,23 @@ "@babel/helper-define-polyfill-provider": "^0.3.3" } }, + "babel-plugin-styled-components": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.7.tgz", + "integrity": "sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.16.0", + "@babel/helper-module-imports": "^7.16.0", + "babel-plugin-syntax-jsx": "^6.18.0", + "lodash": "^4.17.11", + "picomatch": "^2.3.0" + } + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -20220,6 +20609,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -20540,6 +20934,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.3.1.tgz", @@ -20647,6 +21046,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.0.0.tgz", + "integrity": "sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -21065,6 +21474,12 @@ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.282.tgz", "integrity": "sha512-Dki0WhHNh/br/Xi1vAkueU5mtIc9XLHcMKB6tNfQKk+kPG0TEUjRh5QEMAUbRp30/rYNMFD1zKKvbVzwq/4wmg==" }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -21926,6 +22341,23 @@ } } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "filesize": { "version": "8.0.7", "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", @@ -22225,6 +22657,86 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-4.0.0.tgz", + "integrity": "sha512-p8S0T3aGJc68MtwOcZusul5qPSNZCalap3NWbhRUZYu1YOdp+EjZ+4kPmRM8h3NNRdqw00yuevRjlkuSzCn7iQ==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + } + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -22369,6 +22881,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -25117,6 +25644,21 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==" }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -26743,6 +27285,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -27022,12 +27569,38 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "requires": {} }, + "styled-components": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz", + "integrity": "sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + } + }, "stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -27332,6 +27905,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tryer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz", diff --git a/package.json b/package.json index 239b232..c834833 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,51 @@ { - "name": "launch-countdown-timer", - "version": "0.1.0", - "private": true, - "dependencies": { - "@testing-library/jest-dom": "^5.16.5", - "@testing-library/react": "^13.4.0", - "@testing-library/user-event": "^13.5.0", - "@types/jest": "^27.5.2", - "@types/node": "^16.11.65", - "@types/react": "^18.0.21", - "@types/react-dom": "^18.0.6", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-scripts": "5.0.1", - "typescript": "^4.8.4", - "web-vitals": "^2.1.4" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } + "homepage": "https://BadLice.github.io/launch-countdown-timer", + "name": "launch-countdown-timer", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.16.5", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "@types/jest": "^27.5.2", + "@types/node": "^16.11.65", + "@types/react": "^18.0.21", + "@types/react-dom": "^18.0.6", + "@types/styled-components": "^5.1.26", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "styled-components": "^5.3.6", + "typescript": "^4.8.4", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject", + "predeploy": "npm run build", + "deploy": "gh-pages -d build" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "devDependencies": { + "gh-pages": "^4.0.0" + } } diff --git a/public/images/bg-stars.svg b/public/images/bg-stars.svg new file mode 100644 index 0000000..72940e9 --- /dev/null +++ b/public/images/bg-stars.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/favicon-32x32.png b/public/images/favicon-32x32.png new file mode 100644 index 0000000..1e2df7f Binary files /dev/null and b/public/images/favicon-32x32.png differ diff --git a/public/images/icon-facebook.svg b/public/images/icon-facebook.svg new file mode 100644 index 0000000..3c12d36 --- /dev/null +++ b/public/images/icon-facebook.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/icon-instagram.svg b/public/images/icon-instagram.svg new file mode 100644 index 0000000..8cc0ea0 --- /dev/null +++ b/public/images/icon-instagram.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/icon-pinterest.svg b/public/images/icon-pinterest.svg new file mode 100644 index 0000000..459d5f1 --- /dev/null +++ b/public/images/icon-pinterest.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/images/pattern-hills.svg b/public/images/pattern-hills.svg new file mode 100644 index 0000000..8b0ed66 --- /dev/null +++ b/public/images/pattern-hills.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/index.html b/public/index.html index aa069f2..64bd94b 100644 --- a/public/index.html +++ b/public/index.html @@ -1,21 +1,24 @@ - - - - - - - - - - - React App - - - -
- - + --> diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.tsx b/src/App.tsx index a53698a..d28a8e8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,26 +1,49 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import Social from 'social'; +import styled, { createGlobalStyle } from 'styled-components'; +import Timer from 'timer'; +import Title from 'title'; + +const GlobalStyles = createGlobalStyle` + * { + box-sizing: border-box; + font-family: 'Red Hat Text', sans-serif; + } + + html, body { + height: 100%; + width: 100%; + margin: 0px; + background-color: hsl(234, 17%, 12%); + } + + body { + background: url('images/bg-stars.svg'),url('images/pattern-hills.svg') bottom no-repeat; + background-size: 100%; + + } +`; + +const ContainerColumn = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + justify-items: center; + align-items: center; + gap: 3%; + height: 100vh; +`; function App() { - return ( -
-
- logo -

- Edit src/App.tsx and save to reload. -

- - Learn React - -
-
- ); + return ( + <> + + + + <Timer /> + <Social /> + </ContainerColumn> + </> + ); } export default App; diff --git a/src/flip.counter.tsx b/src/flip.counter.tsx new file mode 100644 index 0000000..cfd7989 --- /dev/null +++ b/src/flip.counter.tsx @@ -0,0 +1,218 @@ +import { useEffect, useState } from 'react'; +import styled, { css, keyframes } from 'styled-components'; + +type WrapperProps = { + $animate?: boolean; + $animationDurationMs: number; +}; + +type FlipTimerProps = { + size: number; + className?: string; + value?: number; + clockValue?: number; + label?: string; +}; + +const flip = keyframes` + from { + transform: rotateX(0deg); + } + to { + transform: rotateX(-180deg); + } +`; + +export const Wrapper = styled.div<{ $size: number }>` + position: relative; + width: ${({ $size }) => $size * 2}px; + height: ${({ $size }) => $size}px; + perspective: 1000px; + user-select: none; + + @media only screen and (max-width: 576px) { + width: ${({ $size }) => $size * 0.6 * 2}px; + height: ${({ $size }) => $size * 0.6}px; + } + + @media only screen and (max-width: 334px) { + width: ${({ $size }) => $size * 0.4 * 2}px; + height: ${({ $size }) => $size * 0.4}px; + } +`; + +export const Inner = styled.div<WrapperProps>` + width: 100%; + height: 100%; + text-align: center; + transform-style: preserve-3d; + transform-origin: bottom; + bottom: 0px; + left: 0px; + animation: ${({ $animate, $animationDurationMs }) => + $animate + ? css` + ${flip} ${$animationDurationMs}ms ease-in + ` + : 'none'}; + user-select: none; + border-radius: 10px; + + @media only screen and (max-width: 576px) { + border-radius: 5px; + } +`; + +const TextContainer = styled.div<{ $size: number }>` + font-size: ${({ $size }) => $size * 2 - (($size * 2) / 100) * 45}px; + line-height: ${({ $size }) => $size * 2}px; + border-radius: 10px; + + @media only screen and (max-width: 576px) { + font-size: ${({ $size }) => $size * 0.6 * 2 - (($size * 0.6 * 2) / 100) * 45}px; + line-height: ${({ $size }) => $size * 0.6 * 2}px; + border-radius: 5px; + } + + @media only screen and (max-width: 334px) { + font-size: ${({ $size }) => $size * 0.4 * 2 - (($size * 0.4 * 2) / 100) * 45}px; + line-height: ${({ $size }) => $size * 0.4 * 2}px; + border-radius: 5px; + } +`; + +const Face = styled(TextContainer)` + position: absolute; + width: 100%; + height: 100%; + backface-visibility: hidden; + border-radius: 10px; + + @media only screen and (max-width: 576px) { + border-radius: 5px; + } +`; + +export const Front = styled(Face)` + overflow: hidden; +`; + +export const Back = styled(Face)` + transform: rotateX(180deg); + line-height: 0px; + overflow: hidden; +`; + +export const BottomPlaceholder = styled(TextContainer)` + width: ${({ $size }) => $size * 2}px; + height: ${({ $size }) => $size}px; + line-height: 0px; + overflow: hidden; + text-align: center; + box-shadow: 0 8px 0px 0 rgba(15, 15, 15, 0.3); + + ::before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.15; + z-index: 5; + border-radius: 10px; + + @media only screen and (max-width: 576px) { + border-radius: 5px; + } + } + + @media only screen and (max-width: 576px) { + width: ${({ $size }) => $size * 0.6 * 2}px; + height: ${({ $size }) => $size * 0.6}px; + } + + @media only screen and (max-width: 334px) { + width: ${({ $size }) => $size * 0.4 * 2}px; + height: ${({ $size }) => $size * 0.4}px; + } +`; + +export const UpperPlaceholder = styled(TextContainer)` + width: ${({ $size }) => $size * 2}px; + height: ${({ $size }) => $size}px; + position: absolute; + bottom: 0px; + left: 0px; + overflow: hidden; + text-align: center; + + @media only screen and (max-width: 576px) { + width: ${({ $size }) => $size * 0.6 * 2}px; + height: ${({ $size }) => $size * 0.6}px; + } + + @media only screen and (max-width: 334px) { + width: ${({ $size }) => $size * 0.4 * 2}px; + height: ${({ $size }) => $size * 0.4}px; + } +`; + +export const Label = styled.p` + text-align: center; + background-color: transparent; + margin-top: 15%; + text-transform: uppercase; + letter-spacing: 7px; + text-indent: 7px; + color: hsl(237, 18%, 59%); + font-size: 11px; + + @media only screen and (max-width: 576px) { + font-size: 7px; + letter-spacing: 0px; + text-indent: 0px; + } +`; + +const FlipCounter = ({ className, size, value, label }: FlipTimerProps) => { + const animationDurationMs = 500; + const [displayValue, setDisplayValue] = useState<number>(0); + const [isAnimating, setIsAnimating] = useState(false); + + useEffect(() => { + triggerFlip(); + }, [value]); + + const updateDisplayValue = () => setDisplayValue(value!); + + const triggerFlip = () => { + setIsAnimating(true); + }; + + return ( + <Wrapper $size={size} className={className}> + <UpperPlaceholder $size={size}> + {displayValue + (value! - displayValue)} + </UpperPlaceholder> + <Inner + $animationDurationMs={animationDurationMs} + $animate={isAnimating} + onAnimationStart={() => { + setTimeout(() => { + setIsAnimating(false); + updateDisplayValue(); + }, animationDurationMs - 25); + }} + > + <Front $size={size}>{displayValue}</Front> + <Back $size={size}>{displayValue + (value! - displayValue)}</Back> + </Inner> + <BottomPlaceholder $size={size}>{displayValue}</BottomPlaceholder> + {label && <Label>{label}</Label>} + </Wrapper> + ); +}; + +export default FlipCounter; diff --git a/src/hooks/useTime.tsx b/src/hooks/useTime.tsx new file mode 100644 index 0000000..c2a20b3 --- /dev/null +++ b/src/hooks/useTime.tsx @@ -0,0 +1,192 @@ +import { useReducer } from 'react'; + +const getRandomStartTime = () => { + const randomDate = (start: Date, end: Date) => { + return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())); + }; + const today = new Date(); + const future = new Date(); + future.setDate(future.getDate() + 99); + const result = randomDate(today, future); + let delta = Math.abs(result.getTime() - today.getTime()) / 1000; + const days = Math.floor(delta / 86400); + delta -= days * 86400; + const hours = Math.floor(delta / 3600) % 24; + delta -= hours * 3600; + const minutes = Math.floor(delta / 60) % 60; + delta -= minutes * 60; + const seconds = delta % 60; + + return { + days, + hours, + minutes, + seconds, + }; +}; + +type TimeValues = { + days: number; + hours: number; + minutes: number; + seconds: number; +}; + +type TimeDispatchers = { + days: React.Dispatch<DaysAction>; + hours: React.Dispatch<HoursAction>; + minutes: React.Dispatch<MinutesAction>; + seconds: React.Dispatch<SecondsAction>; +}; + +export enum TimeActionKind { + INCREASE = 'INCREASE', + DECREASE = 'DECREASE', +} + +type DaysAction = { + type: TimeActionKind; + payload: {}; +}; + +type DaysState = { + count: number; +}; + +type HoursAction = { + type: TimeActionKind; + payload: { + dispatchDays: React.Dispatch<DaysAction>; + }; +}; + +type HoursState = { + count: number; +}; + +type MinutesAction = { + type: TimeActionKind; + payload: { + dispatchHours: React.Dispatch<HoursAction>; + dispatchDays: React.Dispatch<DaysAction>; + }; +}; + +type MinutesState = { + count: number; +}; + +type SecondsAction = { + type: TimeActionKind; + payload: { + dispatchMinutes: React.Dispatch<MinutesAction>; + dispatchHours: React.Dispatch<HoursAction>; + dispatchDays: React.Dispatch<DaysAction>; + }; +}; + +type SecondsState = { + count: number; +}; + +const clock = (v: number, limit: number) => (v < 0 ? limit : v > limit ? 0 : v); + +const daysReducer = (state: DaysState, action: DaysAction): DaysState => { + switch (action.type) { + case TimeActionKind.DECREASE: + let value = clock(state.count - 1, 23); + return { count: value }; + + default: + throw new Error(); + } +}; + +const hoursReducer = (state: HoursState, action: HoursAction): HoursState => { + switch (action.type) { + case TimeActionKind.DECREASE: + let value = clock(state.count - 1, 23); + if (value === 23) { + action.payload.dispatchDays({ + payload: { + dispatchDays: action.payload.dispatchDays, + }, + type: TimeActionKind.DECREASE, + }); + } + return { count: value }; + + default: + throw new Error(); + } +}; + +const minutesReducer = (state: MinutesState, action: MinutesAction): MinutesState => { + switch (action.type) { + case TimeActionKind.DECREASE: + let value = clock(state.count - 1, 59); + if (value === 59) { + action.payload.dispatchHours({ + payload: { + dispatchDays: action.payload.dispatchDays, + }, + type: TimeActionKind.DECREASE, + }); + } + return { count: value }; + + default: + throw new Error(); + } +}; + +const secondsReducer = (state: SecondsState, action: SecondsAction): SecondsState => { + switch (action.type) { + case TimeActionKind.DECREASE: + let value = clock(state.count - 1, 59); + if (value === 59) { + action.payload.dispatchMinutes({ + payload: { + dispatchHours: action.payload.dispatchHours, + dispatchDays: action.payload.dispatchDays, + }, + type: TimeActionKind.DECREASE, + }); + } + return { count: value }; + + default: + throw new Error(); + } +}; + +const useTime = (): [TimeValues, TimeDispatchers] => { + let { days: d, hours: h, minutes: m, seconds: s } = getRandomStartTime(); + const [days, dispatchDays] = useReducer(daysReducer, { count: Math.round(d) }); + const [hours, dispatchHours] = useReducer(hoursReducer, { + count: Math.round(h), + }); + const [minutes, dispatchMinutes] = useReducer(minutesReducer, { + count: Math.round(m), + }); + const [seconds, dispatchSeconds] = useReducer(secondsReducer, { + count: Math.round(s), + }); + + return [ + { + days: days.count, + hours: hours.count, + minutes: minutes.count, + seconds: seconds.count, + }, + { + days: dispatchDays, + hours: dispatchHours, + minutes: dispatchMinutes, + seconds: dispatchSeconds, + }, + ]; +}; + +export default useTime; diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.tsx b/src/index.tsx index 032464f..6c8fd2a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,16 +1,12 @@ -import React from 'react'; import ReactDOM from 'react-dom/client'; -import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; -const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -); +const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( - <React.StrictMode> - <App /> - </React.StrictMode> + // <React.StrictMode> + <App /> + // </React.StrictMode> ); // If you want to start measuring performance in your app, pass a function diff --git a/src/social.tsx b/src/social.tsx new file mode 100644 index 0000000..a902de4 --- /dev/null +++ b/src/social.tsx @@ -0,0 +1,44 @@ +import styled from 'styled-components'; +import ContainerRow from 'styled/container.row'; + +const Container = styled(ContainerRow)` + align-items: flex-end; + gap: 30px; + width: 100%; +`; + +const IconButton = styled.a` + width: 28px; + height: 28px; + border: none; + background-color: hsl(237, 18%, 59%); + + &:hover { + background-color: hsl(345, 95%, 68%); + cursor: pointer; + } +`; + +const Facebook = styled(IconButton)` + mask: url('./images/icon-facebook.svg') no-repeat; +`; + +const Instagram = styled(IconButton)` + mask: url('./images/icon-instagram.svg') no-repeat; +`; + +const Pinterest = styled(IconButton)` + mask: url('./images/icon-pinterest.svg') no-repeat; +`; + +function Social() { + return ( + <Container> + <Facebook href='https://www.facebook.com/' target='blank' /> + <Instagram href='https://www.instagram.com/' target='blank' /> + <Pinterest href='https://www.pinterest.com/' target='blank' /> + </Container> + ); +} + +export default Social; diff --git a/src/styled/container.row.tsx b/src/styled/container.row.tsx new file mode 100644 index 0000000..446cc69 --- /dev/null +++ b/src/styled/container.row.tsx @@ -0,0 +1,12 @@ +import styled from 'styled-components'; + +const ContainerRow = styled.div` + display: flex; + flex-direction: row; + justify-content: center; + align-items: center; + gap: 5%; + margin-top: -1.9%; + height: 33%; +`; +export default ContainerRow; diff --git a/src/timer.tsx b/src/timer.tsx new file mode 100644 index 0000000..35dc351 --- /dev/null +++ b/src/timer.tsx @@ -0,0 +1,50 @@ +import FlipCounter from 'flip.counter'; +import useTime, { TimeActionKind } from 'hooks/useTime'; +import { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import ContainerRow from 'styled/container.row'; + +const Flipper = styled(FlipCounter)` + & > div, + & > div > div { + background-color: hsl(236, 21%, 26%); + color: hsl(345, 95%, 68%); + } +`; + +function Timer() { + let size = 60; + const [time, dispatch] = useTime(); + const [timerInterval, setTimerInterval] = useState<NodeJS.Timer | null>(null); + + const updateTimer = useCallback(() => { + dispatch.seconds({ + type: TimeActionKind.DECREASE, + payload: { + dispatchMinutes: dispatch.minutes, + dispatchHours: dispatch.hours, + dispatchDays: dispatch.days, + }, + }); + }, [dispatch]); + + useEffect(() => { + if (!timerInterval) { + setTimerInterval(setInterval(() => updateTimer(), 1000)); + return () => { + clearInterval(Number(timerInterval!)); + }; + } + }, [timerInterval, updateTimer]); + + return ( + <ContainerRow> + <Flipper value={time.days} size={size} label='days' /> + <Flipper value={time.hours} size={size} label='hours' /> + <Flipper value={time.minutes} size={size} label='minutes' /> + <Flipper value={time.seconds} size={size} label='seconds' /> + </ContainerRow> + ); +} + +export default Timer; diff --git a/src/title.tsx b/src/title.tsx new file mode 100644 index 0000000..83aa83c --- /dev/null +++ b/src/title.tsx @@ -0,0 +1,21 @@ +import styled from 'styled-components'; +import ContainerRow from 'styled/container.row'; + +const Text = styled.div` + color: white; + letter-spacing: 8px; + text-indent: 8px; + font-size: 18px; + align-self: flex-end; + text-align: center; +`; + +function Title() { + return ( + <ContainerRow> + <Text>WE'RE LAUNCHING SOON</Text> + </ContainerRow> + ); +} + +export default Title; diff --git a/style-guide.md b/style-guide.md new file mode 100644 index 0000000..ac1e779 --- /dev/null +++ b/style-guide.md @@ -0,0 +1,41 @@ +# Front-end Style Guide + +## Layout + +The designs were created to the following widths: + +- Mobile: 375px +- Desktop: 1440px + +## Colors + +### Primary + +- Grayish blue: hsl(237, 18%, 59%) +- Soft red: hsl(345, 95%, 68%) + +### Neutral + +- White: hsl(0, 0%, 100%) +- Dark desaturated blue: hsl(236, 21%, 26%) +- Very dark blue: hsl(235, 16%, 14%) +- Very dark (mostly black) blue: hsl(234, 17%, 12%) + +## Typography + +### Body Copy + +- Font size: 14px + +### Font + +- Family: [Red Hat Text](https://fonts.google.com/specimen/Red+Hat+Text) +- Weights: 700 + +## Icons + +We provide the required social icons. But, if you prefer, you can use a font icon library. Some suggestions can be found below: + +- [Font Awesome](https://fontawesome.com) +- [IcoMoon](https://icomoon.io) +- [Ionicons](https://ionicons.com) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index a273b0c..dad08a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,21 @@ { - "compilerOptions": { - "target": "es5", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": [ - "src" - ] + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "baseUrl": "src", + "allowJs": true, + "skipLibCheck": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"] }