diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a11777cc471a4344702741ab1c8a588998b1311a Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000000000000000000000000000000000000..c909a563fa58bd8c79a0670f2a7686ad6e691de9 --- /dev/null +++ b/public/index.html @@ -0,0 +1,15 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> + <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"> + <title>Final Project test website</title> +</head> + + <body> + <div id="app"></div> + </body> + +</html> diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..fc44b0a3796c0e0a64c3d858ca038bd4570465d9 Binary files /dev/null and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..a4e47a6545bc15971f8f63fba70e4013df88a664 Binary files /dev/null and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..7eac1c58148ecbac83320b62fd56d6f5a0d7ef34 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,14 @@ +{ + + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": "./index.html", + "display": "standalone", + "theme_color": "#181d23", + "background_color": "#fafafa" +} diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000000000000000000000000000000000000..e9e57dc4d41b9b46e05112e9f45b7ea6ac0ba15e --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000000000000000000000000000000000000..74b5e053450a48a6bdb4d71aad648e7af821975c --- /dev/null +++ b/src/App.css @@ -0,0 +1,38 @@ +.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.js b/src/App.js new file mode 100644 index 0000000000000000000000000000000000000000..f1819896067b86319c4a9f67db3fa85fb4efba0d --- /dev/null +++ b/src/App.js @@ -0,0 +1,88 @@ +import React, { Component } from 'react'; +import Menu from './Components/Menu/Menu'; +import Nav from './Components/Nav/Nav'; +import Header from './Components/Header/Header'; +import About from './Components/About/About'; +import Projects from './Components/Projects/Projects'; +import Ending from './Components/Ending/Ending'; + +class App extends Component { + state = { + menuState: false + }; + + toggleMenu = () => { + this.setState(state => ({ + menuState: !state.menuState + ? 'active' + : state.menuState === 'deactive' + ? 'active' + : 'deactive' + })); + }; + +//This is the render method for the component. It returns a React Fragment with the Menu, Nav, Header, About, Projects, and Ending components inside. +//It also passes the toggleMenu method and the menuState value as props to the Menu and Nav components. + + render() { + return ( + <React.Fragment> + <Menu toggleMenu={this.toggleMenu} showMenu={this.state.menuState} /> + <Nav toggleMenu={this.toggleMenu} showMenu={this.state.menuState} /> + <Header /> + <About /> + <Projects /> + <Ending /> + </React.Fragment> + ); + } + + // Parallax effect. + +componentDidMount() { + // This is a lifecycle method that is called after the component has rendered. + + const navbar = document.querySelector('#navbar'); + const header = document.querySelector('#welcome-section'); + const forest = document.querySelector('.forest'); + let forestInitPos = -300; + // These lines select the elements with the specified IDs or class names and store them in variables. + //The forestInitPos variable is used to store the initial position of the forest element. + + window.onscroll = () => { + let scrollPos = document.documentElement.scrollTop || document.body.scrollTop; + // This line gets the current scroll position of the page. + + if (scrollPos <= window.innerHeight) { + forest.style.bottom = `${parseInt(forestInitPos + scrollPos / 6)}px`; + } + // If the scroll position is within the viewport height, this code updates the bottom position of the forest element with a parallax effect. + + if (scrollPos - 100 <= window.innerHeight) + header.style.visibility = header.style.visibility === 'hidden' && 'visible'; + else header.style.visibility = 'hidden'; + // If the scroll position is within 100 pixels of the viewport height, this code shows the header element. Otherwise, it hides the header element. + + if (scrollPos + 100 >= window.innerHeight) navbar.classList.add('bg-active'); + else navbar.classList.remove('bg-active'); + // If the scroll position is within 100 pixels of the viewport height, this code adds the "bg-active" class to the navbar element. Otherwise, it removes the "bg-active" class from the navbar element. + }; + + (function navSmoothScrolling() { + const internalLinks = document.querySelectorAll('a[href^="#"]'); + for (let i in internalLinks) { + if (internalLinks.hasOwnProperty(i)) { + internalLinks[i].addEventListener('click', e => { + e.preventDefault(); + document.querySelector(internalLinks[i].hash).scrollIntoView({ + block: 'start', + behavior: 'smooth' + }); + }); + } + } + })(); + } +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js new file mode 100644 index 0000000000000000000000000000000000000000..a754b201bf9c6caf5271293588189fb4210f99d1 --- /dev/null +++ b/src/App.test.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import App from './App'; + +it('renders without crashing', () => { + const div = document.createElement('div'); + ReactDOM.render(<App />, div); + ReactDOM.unmountComponentAtNode(div); +}); diff --git a/src/Components/About/About.css b/src/Components/About/About.css new file mode 100644 index 0000000000000000000000000000000000000000..c935693d34624e768e5c77bc90978671ae655796 --- /dev/null +++ b/src/Components/About/About.css @@ -0,0 +1,96 @@ +#about { + height: 100%; + min-height: 100vh; + font-size: 1.4rem; + position: relative; + background: #fafafa; + clip-path: polygon(0 0, 20% 5%, 100% 0, 100% 100%, 80% 95%, 0 100%); + z-index: 5; + background: #fafafa; + background-attachment: fixed; +} + +#about .wrapper { + padding: 15rem 10rem 12rem; + height: 100%; + min-height: 100vh; + max-width: 1400px; + margin: 0 auto; +} + +#about article { + display: grid; + grid-template-columns: repeat(4, 1fr); + grid-gap: 1rem; + padding: 3rem 0; +} + +#about .title { + grid-column-end: span 4; + display: flex; + flex-direction: column; + align-items: center; +} + +#about .title h3 { + font-size: 2.4rem; +} + +#about .separator { + background: #f300b4; + width: 150px; + height: 2px; + margin: 1rem 0; + padding: 0; +} + +#about .subtitle { + font-size: 1.6rem; + text-align: center; + color: inherit; + padding-bottom: 1.5rem; +} + +#about p { + padding-bottom: 1.5rem; + color: #555; + line-height: 1.9rem; +} + +#about .desc.full { + grid-column-end: span 4; + margin-bottom: 2rem; +} + +#about .desc { + grid-column-end: span 2; + background: #ffffffaa; + padding: 2rem; + text-align: justify; +} + +@media only screen and (max-width: 1149px) { + #about article { + grid-template-columns: 1fr; + grid-gap: 0; + } + #about .desc.full { + grid-column-end: -1; + } + + #about .desc { + grid-column-end: -1; + } +} +@media only screen and (max-width: 949px) { + #about { + clip-path: polygon(0 0, 20% 2%, 100% 0, 100% 100%, 80% 98%, 0 100%); + background-position: top left; + background-size: cover; + } +} +@media only screen and (max-width: 649px) { + #about .wrapper { + padding: 10rem 2rem 8rem; + } +} diff --git a/src/Components/About/About.jsx b/src/Components/About/About.jsx new file mode 100644 index 0000000000000000000000000000000000000000..6b86e404bbe6b000682a20e96415cfceb257934d --- /dev/null +++ b/src/Components/About/About.jsx @@ -0,0 +1,31 @@ +import React from 'react'; +import './About.css'; + +const About = props => { + return ( + <section id="about"> + <div className="wrapper"> + <article> + <div className="title"> + <h3>Project name goes here</h3> + <p className="separator" /> + </div> + <div className="desc"> + <h4 className="subtitle">About my project here </h4> + <p> + Some info about project here + </p> + </div> + <div className="desc"> + <h4 className="subtitle">Another subheading here</h4> + <p> + And some more info here + </p> + </div> + </article> + </div> + </section> + ); +}; + +export default About; diff --git a/src/Components/Ending/Ending.css b/src/Components/Ending/Ending.css new file mode 100644 index 0000000000000000000000000000000000000000..ecf1d06660f7672439d0b3640a7b0bfd0924d86a --- /dev/null +++ b/src/Components/Ending/Ending.css @@ -0,0 +1,51 @@ +#ending { + background: #181d23; + clip-path: polygon(0 0, 20% 100px, 100% 0, 100% 100%, 0 100%); + color: #fafafa; + min-height: 100vh; + width: 100%; + padding: 5rem 3rem; + display: flex; + justify-content: center; + align-items: center; + position: relative; + margin: -100px 0 140px; + z-index: 1; +} + +#ending .container { + width: 70%; + max-width: 1200px; + padding: 25vh 0; +} + +#ending .container .heading-wrapper { + display: flex; + justify-content: space-between; +} + + +.heading-wrapper .heading .title { + font-size: 3rem; + line-height: 2.4rem; +} + +.heading-wrapper .heading .separator { + background: #f300b4; + width: 150px; + height: 2px; + margin: 1rem 0; +} + +@media only screen and (max-width: 1149px) { + #ending .social a { + display: block; + } +} + +@media only screen and (max-width: 649px) { + #ending { + clip-path: polygon(0 0, 20% 5%, 100% 0, 100% 100%, 0 100%); + padding: 0; + } +} \ No newline at end of file diff --git a/src/Components/Ending/Ending.jsx b/src/Components/Ending/Ending.jsx new file mode 100644 index 0000000000000000000000000000000000000000..9cf1b7b215d117028a1f66e9c0a27491a5096b05 --- /dev/null +++ b/src/Components/Ending/Ending.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import './Ending.css'; + +const Ending = props => { + return ( + <section id="ending"> + <div className="container"> + <div className="heading-wrapper"> + <div className="heading"> + <p className="title"> + Extra text space here + </p> + </div> + </div> + </div> + </section> + ); +}; + +export default Ending; diff --git a/src/Components/Header/Header.css b/src/Components/Header/Header.css new file mode 100644 index 0000000000000000000000000000000000000000..225cc714fbfd414ac69aec9e3b666812c9821267 --- /dev/null +++ b/src/Components/Header/Header.css @@ -0,0 +1,191 @@ +/* Header */ +#welcome-section { + background: #202736; + background: linear-gradient(to bottom, #181d23 0%, #202736 80%); + background-attachment: fixed; + background-size: cover; + position: relative; + min-height: 100vh; + margin: 0 auto; + z-index: 1; +} + +#welcome-section::before { + content: ''; + position: fixed; + background: url(../../Images/Stars.png); + background-attachment: fixed; + width: 100%; + min-height: 100vh; + z-index: -1; + opacity: 0; + animation: stars-move-in 1000ms 300ms forwards; +} + +@keyframes stars-move-in { + from { + background-position-y: -100px; + } + to { + opacity: 1; + background-position-y: 0; + } +} + +.forest { + position: absolute; + bottom: -300px; + left: 0; + background: url(../../Images/Trees.png) bottom left repeat-x; + /* background-position-x: 100px; */ + background-size: contain; + width: 100%; + height: 80%; + opacity: 0; + animation: forest-move-in 1000ms 500ms forwards; + border-bottom: 300px solid #181d23; +} + +@keyframes forest-move-in { + from { + background-position-y: 150%; + } + to { + opacity: 1; + background-position-y: 100%; + } +} + +.moon { + position: absolute; + top: 0; + right: 0; + position: fixed; + background: url(../../Images/Moon.png) right 150% no-repeat; + background-size: 24% 37%; + background-attachment: fixed; + width: 100%; + height: 100%; + z-index: -1; + opacity: 0; + animation: moon-move-in 1.2s 1s forwards; +} + +@keyframes moon-move-in { + from { + opacity: 0; + background-position: right 150%; + } + to { + opacity: 1; + background-position: top right; + } +} + +/* Copy and CTA */ +#welcome-section .container { + width: fit-content; + position: absolute; + right: 0; + top: 50%; + right: 25%; + opacity: 0; + transform: translate(0, -50%); + animation: text-fade-in 1000ms 800ms forwards; +} + +@keyframes text-fade-in { + from { + right: 0; + } + to { + opacity: 1; + right: 25%; + } +} + +#welcome-section .container h1 { + font-size: 4rem; + font-weight: normal; + font-style: italic; + color: #fafafa; + line-height: 3rem; +} + +#welcome-section .container h1 .line:first-child { + margin-left: 1rem; +} + +#welcome-section .container h1 .line:last-child { + margin-left: 2rem; +} + +#welcome-section .container .buttons { + display: flex; + margin-top: 1rem; +} + +#welcome-section .container .buttons a, +#welcome-section .container .buttons a:visited { + width: 100%; + padding: 1rem; + border: 1px solid #fafafa; + color: #fafafa; + text-align: center; + text-transform: uppercase; + font-size: 1rem; +} + +#welcome-section .container .buttons a:hover, +#welcome-section .container .buttons a:active { + border: 1px solid #f300b4; + transform: translateY(-2px); + box-shadow: 0 10px 100px -20px #f300b4; +} + +#welcome-section .container .buttons a.cta, +#welcome-section .container .buttons a.cta:visited { + background: #f300b4; + border: 1px solid transparent; + color: #fafafa; + font-weight: bold; +} + +#welcome-section .container .buttons a.cta:hover, +#welcome-section .container .buttons a.cta:active { + background: transparent; + border: 1px solid #f300b4; +} + +#welcome-section .container .buttons a:first-child { + margin-right: 1rem; +} + +.line { + display: block; +} + +.color { + color: #f300b4; + font-style: italic; +} + +@media only screen and (max-width: 649px) { + #welcome-section .container { + right: 50%; + top: 10%; + width: 80%; + transform: translate(50%, 0); + animation: text-fade-in 1000ms 800ms forwards; + } + + @keyframes text-fade-in { + from { + right: 0; + } + to { + opacity: 1; + right: 50%; + } + } +} \ No newline at end of file diff --git a/src/Components/Header/Header.jsx b/src/Components/Header/Header.jsx new file mode 100644 index 0000000000000000000000000000000000000000..708cfa59f51ec2fa89807426e519696ea67d659b --- /dev/null +++ b/src/Components/Header/Header.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import './Header.css'; + +const Header = props => { + return ( + <header id="welcome-section"> + <div className="forest" /> + + <div className="moon" /> + <div className="container"> + <h1> + This is my prototype + </h1> + <div className="buttons"> + <a href="#projects">Go down 2 sections</a> + <a href="#ending" className="cta"> + bottom of page + </a> + </div> + </div> + </header> + ); +}; + +export default Header; diff --git a/src/Components/Menu/Menu.css b/src/Components/Menu/Menu.css new file mode 100644 index 0000000000000000000000000000000000000000..0f7c099309b3c3904fa66438d1d35c15b4291294 --- /dev/null +++ b/src/Components/Menu/Menu.css @@ -0,0 +1,224 @@ +/***** Overlay Layer *****/ +.menu-container > .overlay, +.menu-container.active > .overlay { + position: absolute; + right: 0; + height: calc( 100vh - 120px ); + width: calc( 100vw - 120px ); + background: #fafafa; +} + +.menu-container.active > .overlay { + animation: overlay-slide-in 300ms forwards 300ms; +} + +@keyframes overlay-slide-in { + from { + width: calc( 100vw - 120px ); + } + to { + width: 0; + } +} + +.menu-container > .overlay { + animation: overlay-slide-out 300ms forwards; +} + +@keyframes overlay-slide-out { + from { + left: 0; + width: 0; + } + to { + left: 0; + width: calc( 100vw - 120px ); + } +} + +/***** Menu Layer *****/ +.menu-container { + position: fixed; + height: 100vh; + width: 100vw; + background: #202934; + border: 60px solid #181d23; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + overflow: hidden; +} + +.menu-container::before, +.menu-container::after { + content: ''; + position: absolute; + width: 100%; + min-height: 100vh; + z-index: -1; +} + +.menu-container::before { + background: url(../../Images/Stars.png); +} + +.menu-container::after { + background: url(../../Images/Trees.png) bottom repeat-x; +} + +.menu-container.deactive { + animation: fade-out 600ms forwards; +} + +@keyframes fade-out { + 0% { + opacity: 1; + z-index: 999; + } + 50% { + opacity: 1; + z-index: 999; + } + 100% { + opacity: 0; + z-index: -1; + } +} + +.menu-container.active { + animation: fade-in 300ms forwards; +} + +@keyframes fade-in { + from { + opacity: 0; + z-index: -1; + } + to { + opacity: 1; + z-index: 999; + } +} + +/***** Menu Items: Animation *****/ +.menu-container ul, +.menu-container .social { + margin-left: -80px; + opacity: 0; + animation: slide-out 200ms forwards; +} + +.menu-container ul { + list-style-type: none !important; + font-size: 3rem; +} + +@keyframes slide-out { + from { + opacity: 1; + margin-left: 0px; + } + to { + opacity: 0; + margin-left: -80px; + } +} + +.menu-container.active ul, +.menu-container.active .social { + animation: slide-in 300ms forwards 600ms; +} + +@keyframes slide-in { + from { + opacity: 0; + margin-left: -80px; + } + to { + opacity: 1; + margin-left: 0; + } +} + +/***** Menu Items: Hover Animation *****/ +.menu-container ul li { + border-left: .2rem solid transparent; + transition: border-left 200ms; +} + +.menu-container ul li a { + font-size: 3rem; + padding-left: .5rem; +} + +.menu-container ul li a::after { + content: ' »'; + font-size: 2.5rem; + color: transparent; + transition: color 200ms; +} + +.menu-container ul li a:hover::after { + content: ' »'; + color: #f300b4; +} + +.social { + padding: 1rem 0 0 .5rem; +} + +.social a { + font-size: 1.5rem; + padding: .2rem; +} + +.menu-container a, +.menu-container a:visited { + color: #fafafa; +} + +.menu-container a:hover, +.menu-container a:active { + color: #f300b4; +} + +@media only screen and (max-width: 649px) { + .menu-container { + border: none; + } + + .menu-container > .overlay, + .menu-container.active > .overlay { + height: 100vh; + width: 100vw; + } + + .menu-container.active > .overlay { + animation: overlay-slide-in 300ms forwards 300ms; + } + + @keyframes overlay-slide-in { + from { + width: 100vw; + } + to { + width: 0; + } + } + + .menu-container > .overlay { + animation: overlay-slide-out 300ms forwards; + } + + @keyframes overlay-slide-out { + from { + left: 0; + width: 0; + } + to { + left: 0; + width: 100vw; + } + } +} \ No newline at end of file diff --git a/src/Components/Menu/Menu.jsx b/src/Components/Menu/Menu.jsx new file mode 100644 index 0000000000000000000000000000000000000000..f6ff567fe8cc105b513ee985e3aa01e598563cf5 --- /dev/null +++ b/src/Components/Menu/Menu.jsx @@ -0,0 +1,36 @@ +import React from 'react'; +import './Menu.css'; + +const Menu = props => { + return ( + <div className={`menu-container ${props.showMenu}`}> + <div className="overlay" /> + <div className="menu-items"> + <ul> + <li> + <a href="#welcome-section" onClick={props.toggleMenu}> + HOME + </a> + </li> + <li> + <a href="#about" onClick={props.toggleMenu}> + ABOUT + </a> + </li> + <li> + <a href="#projects" onClick={props.toggleMenu}> + PROJECT + </a> + </li> + <li> + <a href="#ending" onClick={props.toggleMenu}> + ENDING + </a> + </li> + </ul> + </div> + </div> + ); +}; + +export default Menu; diff --git a/src/Components/Nav/Nav.css b/src/Components/Nav/Nav.css new file mode 100644 index 0000000000000000000000000000000000000000..9eba480d78fa0c2883242bb6db398af85e1fe39b --- /dev/null +++ b/src/Components/Nav/Nav.css @@ -0,0 +1,87 @@ +#navbar { + position: fixed; + z-index: 9999; + width: 100%; + padding: 1rem; + display: flex; + justify-content: center; +} + +#navbar.bg-active { + background: #181d23; +} + +#navbar .nav-wrapper { + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + max-width: 1400px; + padding: 0 2rem; +} + +#navbar .brand { + font-size: 1.6rem; + color: #fafafa; + cursor: default; +} + +/***** Menu Button *****/ +.menu-button { + position: relative; + height: 22px; + width: 30px; + outline: none; +} + +.menu-button span, +.menu-button span::before, +.menu-button span::after { + position: absolute; + content: ''; + width: 30px; + height: 3px; + background: #fafafa; + transition: 500ms cubic-bezier(0.77, 0, 0.175, 1); +} + +.menu-button span { + position: relative; + display: block; + top: 50%; + transform: translate(0,-50%); +} + +.menu-button span::before { + top: -8px; +} + +.menu-button span::after { + top: 8px; +} + +.menu-button:hover > span, +.menu-button:hover > span::before, +.menu-button:hover > span::after { + background: #f300b4; +} + +.menu-button.active > span { + background: transparent; +} + +.menu-button.active > span::before { + transform: rotate(-225deg); + top: 0px; +} + +.menu-button.active > span::after { + transform: rotate(225deg); + top: 0px; +} + +@media only screen and (max-width: 849px) { + #navbar { + background: #181d23aa; + } +} \ No newline at end of file diff --git a/src/Components/Nav/Nav.jsx b/src/Components/Nav/Nav.jsx new file mode 100644 index 0000000000000000000000000000000000000000..641fc3ba3196ffe21fd24e79e333bb0d7dc8ec05 --- /dev/null +++ b/src/Components/Nav/Nav.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import './Nav.css'; + +const Nav = props => { + return ( + <React.Fragment> + <nav id="navbar"> + <div className="nav-wrapper"> + <p className="brand"> + Asgher + <strong> Merchant</strong> + </p> + <a + onClick={props.toggleMenu} + className={props.showMenu === 'active' ? 'menu-button active' : 'menu-button'} + > + <span /> + </a> + </div> + </nav> + </React.Fragment> + ); +}; + +export default Nav; diff --git a/src/Components/Projects/Images/WaterLily.jpg b/src/Components/Projects/Images/WaterLily.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cc6e25216cff4e38c01dfa968dbf74ca1bc5aec4 Binary files /dev/null and b/src/Components/Projects/Images/WaterLily.jpg differ diff --git a/src/Components/Projects/Project.jsx b/src/Components/Projects/Project.jsx new file mode 100644 index 0000000000000000000000000000000000000000..e4092597b9b87fa6de3c37fd9be06da5ce1b01da --- /dev/null +++ b/src/Components/Projects/Project.jsx @@ -0,0 +1,29 @@ +import React from 'react'; + +const Project = props => { + + + const link = props.link || 'http://'; + + return ( + <div className="project"> + <a className="project-link" href={link} target="_blank" rel="noopener noreferrer"> + <img className="project-image" src={props.img} /> + </a> + <div className="project-details"> + <div className="project-tile"> + + {props.title}{' '} + </div> + {props.children} + <div className="buttons"> + <a > + Take me to theme + </a> + </div> + </div> + </div> + ); +}; + +export default Project; diff --git a/src/Components/Projects/Projects.css b/src/Components/Projects/Projects.css new file mode 100644 index 0000000000000000000000000000000000000000..69e8d1a72dbd1d99057ccf6d5cf6a7e950d299f4 --- /dev/null +++ b/src/Components/Projects/Projects.css @@ -0,0 +1,171 @@ +#projects { + min-height: 100vh; + font-size: 1.4rem; + position: relative; + background: #f0f0f0; + background: linear-gradient(215deg, #f0f0f0 0%,#fafafa 100%); + margin-top: -10rem; + z-index: 1; +} + +#projects a, +#projects a:visited { + color: #f300b4; +} + +#projects a:hover, +#projects a:active { + color: #252934; +} + +/* Container */ +#projects .projects-container { + max-width: 1400px; + margin: 0 auto; + width: 100%; + padding: 12rem 5rem 8rem; +} + +/* Heading */ +#projects .heading .title { + text-align: center; + font-size: 2.4rem; + line-height: 2.4rem; +} + +#projects .heading .separator { + background: #f300b4; + width: 150px; + height: 2px; + margin: 1rem auto; +} + +#projects .heading .subtitle { + font-size: 1.4rem; + text-align: center; + width: 70%; + margin: 0 auto; + text-align: justify; +} + +/* Single Project */ +#projects .project { + margin: 1rem auto; + width: 70%; + padding: 2rem; + display: grid; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + grid-gap: 2rem; +} + +/* Project Image */ +#projects .project .project-link { + display: block; + margin: auto 0; + color: #252934; + overflow: hidden; + text-align: center; + border-radius: 50%; + border: 1px solid #fafafa; + box-shadow: 0 20px 10px -10px #25293450; + transition: 300ms; +} + +#projects .project .project-link:hover { + box-shadow: 0 50px 15px -30px #25293450; +} + +#projects .project .project-link:hover > img { + filter: saturate(1); + transform: scale(1.05); +} + +#projects .project .project-image { + width: 100%; + transform: scale(1.2); + filter: saturate(0); + transition: all 300ms; +} + +/* Project Details */ +#projects .project .project-details { + margin: auto 0; +} + +#projects .project-details .project-tile { + font-size: 2rem; + text-transform: uppercase; + font-weight: bold; + margin-bottom: 0; + color: #f300b4; +} + + + +#projects .project-details .icons i { + margin-right: .4rem; + font-weight: normal; + font-size: 1.4rem; +} + +/* Text */ +#projects .project-details small { + font-style: italic; +} + +#projects .project-details p { + margin: 1rem 0; +} + +/* Buttons */ +#projects .project-details .buttons { + display: flex; + justify-content: space-between; +} + +#projects .project-details .buttons a { + width: 49%; + padding: .5rem; + border: none; + border-bottom: 1px solid #f300b4; + color: #252934; + background: #fafafa; + font-size: 1.2rem; + text-align: center; +} + +#projects .project-details .buttons a:hover { + background: #f300b4; + color: #fafafa; +} +#projects .project-details .buttons i { + font-size: .8rem; + vertical-align: middle; + margin-left: .5rem;; +} + + +@media only screen and (max-width: 1149px) { + #projects .project { + grid-template-columns: 1fr 2fr; + } +} + +@media only screen and (max-width: 949px) { + #projects .project { + grid-template-columns: 1fr; + } +} + +@media only screen and (max-width: 649px) { + #projects { + background: #f0f0f0; + } + #projects .projects-container { + padding: 12rem 0 8rem; + } + #projects .project { + padding: 2rem 0; + } +} \ No newline at end of file diff --git a/src/Components/Projects/Projects.jsx b/src/Components/Projects/Projects.jsx new file mode 100644 index 0000000000000000000000000000000000000000..37e57421860428c14ddd471511b569cb5cf9de56 --- /dev/null +++ b/src/Components/Projects/Projects.jsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Project from './Project'; +import './Projects.css'; +import WaterLilyImg from './Images/WaterLily.jpg'; + + +const Projects = props => { + return ( + <section id="projects"> + <div className="projects-container"> + <div className="heading"> + <h3 className="title">Pick project theme here</h3> + <p className="separator" /> + <p className="subtitle"> + Last bit of project info here + </p> + </div> + <div className="projects-wrapper"> + <Project + title="Theme 1" + img={WaterLilyImg} + tech="" + link="" + > + </Project> + <Project + title="Theme 2" + img={WaterLilyImg} + tech="" + link="" + > + </Project> + <Project + title="Theme 3" + img={WaterLilyImg} + tech="" + link="" + > + </Project> + </div> + </div> + </section> + ); +}; + +export default Projects; diff --git a/src/Images/Moon.png b/src/Images/Moon.png new file mode 100644 index 0000000000000000000000000000000000000000..49975a4e9dd9ed1e1e6081b4d8e8370bb179d333 Binary files /dev/null and b/src/Images/Moon.png differ diff --git a/src/Images/Stars.png b/src/Images/Stars.png new file mode 100644 index 0000000000000000000000000000000000000000..95e83ec354cb8ac4915b835425facf5e1615827b Binary files /dev/null and b/src/Images/Stars.png differ diff --git a/src/Images/Trees.png b/src/Images/Trees.png new file mode 100644 index 0000000000000000000000000000000000000000..e7fe09353c5c6d2b289d018dc4250bbb1a5652c8 Binary files /dev/null and b/src/Images/Trees.png differ diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000000000000000000000000000000000000..8052c1bca34f69107c587e2477db4f1ae71e1c6e --- /dev/null +++ b/src/index.css @@ -0,0 +1,35 @@ +*, +*::before, +*::after, +:root { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +@import 'https://fonts.googleapis.com/css?family=Overlock:400,400i,700|Oleo+Script'; + +html, body { + height: 100%; +} + +body { + color: #252934; + background: #fafafa; + font-size: 62.5%; + font-family: 'Overlock', Arial, Helvetica, sans-serif; + overflow-x: hidden; +} + +a, +a:visited { + color: #252934; + font-size: 1.4rem; + text-decoration: none; + transition: 200ms; +} + +a:hover, +a:active { + color: #f300b4; +} \ No newline at end of file diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000000000000000000000000000000000000..3236fd058a2e4cfab482b5e0c5570a2a916c1bd2 --- /dev/null +++ b/src/index.js @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import './index.css'; +import App from './App'; +import * as serviceWorker from './serviceWorker'; + +ReactDOM.render(<App />, document.getElementById('app')); + +serviceWorker.unregister(); diff --git a/src/serviceWorker.js b/src/serviceWorker.js new file mode 100644 index 0000000000000000000000000000000000000000..012c322dd2c6e070733638263c45c256fa8eb341 --- /dev/null +++ b/src/serviceWorker.js @@ -0,0 +1,131 @@ +// This optional code is used to register a service worker. +// register() is not called by default. + +// This lets the app load faster on subsequent visits in production, and gives +// it offline capabilities. However, it also means that developers (and users) +// will only see deployed updates on subsequent visits to a page, after all the +// existing tabs open on the page have been closed, since previously cached +// resources are updated in the background. + +// To learn more about the benefits of this model and instructions on how to +// opt-in, read http://bit.ly/CRA-PWA. + +const isLocalhost = Boolean( + window.location.hostname === 'localhost' || + // [::1] is the IPv6 localhost address. + window.location.hostname === '[::1]' || + // 127.0.0.1/8 is considered localhost for IPv4. + window.location.hostname.match( + /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ + ) +); + +export function register(config) { + if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { + // The URL constructor is available in all browsers that support SW. + const publicUrl = new URL(process.env.PUBLIC_URL, window.location); + if (publicUrl.origin !== window.location.origin) { + // Our service worker won't work if PUBLIC_URL is on a different origin + // from what our page is served on. This might happen if a CDN is used to + // serve assets; see https://github.com/facebook/create-react-app/issues/2374 + return; + } + + window.addEventListener('load', () => { + const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; + + if (isLocalhost) { + // This is running on localhost. Let's check if a service worker still exists or not. + checkValidServiceWorker(swUrl, config); + + // Add some additional logging to localhost, pointing developers to the + // service worker/PWA documentation. + navigator.serviceWorker.ready.then(() => { + console.log( + 'This web app is being served cache-first by a service ' + + 'worker. To learn more, visit http://bit.ly/CRA-PWA' + ); + }); + } else { + // Is not localhost. Just register service worker + registerValidSW(swUrl, config); + } + }); + } +} + +function registerValidSW(swUrl, config) { + navigator.serviceWorker + .register(swUrl) + .then(registration => { + registration.onupdatefound = () => { + const installingWorker = registration.installing; + installingWorker.onstatechange = () => { + if (installingWorker.state === 'installed') { + if (navigator.serviceWorker.controller) { + // At this point, the updated precached content has been fetched, + // but the previous service worker will still serve the older + // content until all client tabs are closed. + console.log( + 'New content is available and will be used when all ' + + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' + ); + + // Execute callback + if (config && config.onUpdate) { + config.onUpdate(registration); + } + } else { + // At this point, everything has been precached. + // It's the perfect time to display a + // "Content is cached for offline use." message. + console.log('Content is cached for offline use.'); + + // Execute callback + if (config && config.onSuccess) { + config.onSuccess(registration); + } + } + } + }; + }; + }) + .catch(error => { + console.error('Error during service worker registration:', error); + }); +} + +function checkValidServiceWorker(swUrl, config) { + // Check if the service worker can be found. If it can't reload the page. + fetch(swUrl) + .then(response => { + // Ensure service worker exists, and that we really are getting a JS file. + if ( + response.status === 404 || + response.headers.get('content-type').indexOf('javascript') === -1 + ) { + // No service worker found. Probably a different app. Reload the page. + navigator.serviceWorker.ready.then(registration => { + registration.unregister().then(() => { + window.location.reload(); + }); + }); + } else { + // Service worker found. Proceed as normal. + registerValidSW(swUrl, config); + } + }) + .catch(() => { + console.log( + 'No internet connection found. App is running in offline mode.' + ); + }); +} + +export function unregister() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); + } +}