<file_sep>import React, { useEffect } from 'react'
import styled, { keyframes, css } from 'styled-components'
import Portal from './Portal'
import { MdClose } from 'react-icons/md'

const fadeIn = keyframes`
  0% {
    opacity: 0
  }
  100% {
    opacity: 1
  }
`

const fadeOut = keyframes`
  0% {
    opacity: 1
  }
  100% {
    opacity: 0
  }
`

const ModalWrapper = styled.div`
  box-sizing: border-box;
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 1000;
  overflow: auto;
  outline: 0;
  visibility: ${(props) => (props.visible ? 'visible' : 'hidden')};
  transition: visibility 1s linear;
  ${(props) => props.visible && css`animation: ${fadeIn} 1s`};
  ${(props) => !props.visible && css`animation: ${fadeOut} 1s`};
  display: flex;
  flex-flow: column nowrap;
  & button#close {
    margin: 0px;
    padding: 20px;
    align-self: flex-end;
    transform: scale(1.2); 
    color: #fff;
    transition: all .2s ease-out;
    -webkit-appearance: none;
    appearance: none;
    background-color: transparent;
    border: 0;
    cursor: pointer;
    outline: none;
    position: absolute;
    z-index: 4;
    filter: drop-shadow(0 2px 2px #1a1a1a);
    &:hover {
      cursor:pointer;
      color: #337ab7;
      transform: scale(1.3); 
    }
    & svg {
      width: 36px;
      height: 36px;
    }
  }
  @media (max-width: 768px) {
    button#close {
      padding: 15px;
      &:hover {
        color: #fff;
        transform: scale(1.2); 
      }
      & svg {
        width: 24px;
        height: 24px;
    }
  }
  @media (max-width: 480px) {
    button#close {
    padding: 10px;
    & svg {
      width: 16px;
      height: 16px;
      }
    }
  }
}
`

const ModalOverlay = styled.div`
  box-sizing: border-box;
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.9);
  z-index: 999;
  visibility: ${(props) => (props.visible ? 'visible' : 'hidden')};
  transition: visibility 1s linear;
  ${(props) => props.visible && css`animation: ${fadeIn} 1s`};
  ${(props) => !props.visible && css`animation: ${fadeOut} 1s`};
`

function Modal (props) {
  const handleClose = () => {
    props.setFocus(false)
    props.allowScroll()
  }
  
  useEffect(() => {
    return () => {
      handleClose()
    }
  })

  return (
    <Portal container='modal-root'>
      <ModalOverlay visible={props.visible} />
      <ModalWrapper visible={props.visible}>
        <button id='close' onClick={handleClose}><MdClose /></button>
        {props.children}
      </ModalWrapper>
    </Portal>
  )
}

export default Modal
<file_sep>import React from 'react'
import styled from 'styled-components'

const StyledFooter = styled.header`
  background-color: #dbdada;
  color: #857e7a;
  font-family: 'Roboto', sans-serif;
  font-size: .6rem;
  margin-top: auto;
  padding: .8vh;
  text-align: center;
`

function NavBar () {
  return (
    <StyledFooter>
      Copyright 2021. Lee, <NAME>. All rights reserved.
    </StyledFooter>
  )
}

export default NavBar
<file_sep>import React, { useState, useEffect } from 'react'
import styled from 'styled-components'
import Fade from 'react-reveal/Fade'

const StyledContainer = styled.div`
  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: center;
  height: 100vh;
  width: 100%;
	position: relative;
	overflow: hidden;
  background-image: url('../../assets/landing_0.jpg');  
  background-size: cover;

  .bird {
    background-image: url('../../assets/bird-cells-new.svg');
    background-size: auto 100%;
    width: 88px;
    height: 125px;
    will-change: background-position;
    
    animation-name: fly-cycle;
    animation-timing-function: steps(10);
    animation-iteration-count: infinite;
  
    &--one {
      animation-duration: 1s;
      animation-delay: -0.5s;		
    }
    
    &--two {
      animation-duration: 0.9s;
      animation-delay: -0.75s;
    }
    
    &--three {
      animation-duration: 1.25s;
      animation-delay: -0.25s;
    }
    
    &--four {
      animation-duration: 1.1s;
      animation-delay: -0.5s;
    }
  
  }
  
  .bird-container {
    position: absolute;
    top: 20%;
    left: -10%;
    transform: scale(1.5) translateX(-10vw);
    will-change: transform;

    @media (max-width: 600px) {
      left: -20%;
    }
    
    animation-name: fly-right-one;
    animation-timing-function: linear;
    animation-iteration-count: infinite;
    
    &--one {
      animation-duration: 15s;
      animation-delay: 0;
    }
    
    &--two {
      animation-duration: 16s;
      animation-delay: 1s;
    }
    
    &--three {
      animation-duration: 14.6s;
      animation-delay: 9.5s;
    }
    
    &--four {
      animation-duration: 16s;
      animation-delay: 10.25s;
    }
    
  }
  
  @keyframes fly-cycle {
    100% {
      background-position: -900px 0;
    }
  }
  
  @keyframes fly-right-one {
    0% {
      transform: scale(0.6) translateX(-10vw);
    }
    10% {
      transform: translateY(2vh) translateX(10vw) scale(0.6);
    }
    20% {
      transform: translateY(0vh) translateX(30vw) scale(0.6);
    }
    30% {
      transform: translateY(4vh) translateX(50vw) scale(0.6);
    }
    40% {
      transform: translateY(2vh) translateX(70vw) scale(0.5);
    }
    50% {
      transform: translateY(0vh) translateX(90vw) scale(0.5);
    }
    60% {
      transform: translateY(0vh) translateX(110vw) scale(0.4);
    }
    100% {
      transform: translateY(0vh) translateX(110vw) scale(0.4);
    }
  }

  @media (max-width: 600px) {
    @keyframes fly-right-one {
      0% {
        transform: scale(0.3) translateX(-10vw);
      }
      10% {
        transform: translateY(2vh) translateX(10vw) scale(0.3);
      }
      20% {
        transform: translateY(0vh) translateX(30vw) scale(0.3);
      }
      30% {
        transform: translateY(4vh) translateX(50vw) scale(0.3);
      }
      40% {
        transform: translateY(2vh) translateX(70vw) scale(0.2);
      }
      50% {
        transform: translateY(0vh) translateX(90vw) scale(0.2);
      }
      60% {
        transform: translateY(0vh) translateX(110vw) scale(0.1);
      }
      100% {
        transform: translateY(0vh) translateX(110vw) scale(0.1);
      }
    }
  }
`

const StyledLogoWrapper = styled.div`
  transform: translateY(${(props) => -props.offset/5}%);
  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: center;
`

const StyledLogo = styled.div`
  margin-top: 3vh;
  color: #443c36;
  font-family: '<NAME>', cursive;
  font-size: 5vh;
  @media (max-width: 600px) {
    font-size: 4vh;
  }
`

const StyledLogoSub = styled.div`
  color: #443c36;
  font-family: 'WandohopeB';
  font-size: 4vh;
  @media (max-width: 600px) {
    font-size: 3vh;
  }
`

function Landing(props) {
  const [offset, setOffset] = useState(0);

  useEffect(() => {
    window.onscroll = () => {
      setOffset(window.pageYOffset)
    }
  }, []);

  return (
    <StyledContainer offset={offset}>
      <div className="bird-container bird-container--one">
        <div className="bird bird--one"></div>
      </div>
      <div className="bird-container bird-container--two">
        <div className="bird bird--two"></div>
      </div>
      <div className="bird-container bird-container--three">
        <div className="bird bird--three"></div>
      </div>
      <div className="bird-container bird-container--four">
        <div className="bird bird--four"></div>
      </div>
      <StyledLogoWrapper offset={offset}>
        <Fade In duration={5000} cascade>
        <StyledLogo>EunGyeol</StyledLogo>
        <StyledLogoSub>은결 이미선 한국화 갤러리</StyledLogoSub>
        </Fade>
      </StyledLogoWrapper>
    </StyledContainer>
  )
}

export default Landing
<file_sep># EunGyeol

## Link
http://eungyeol.com

## Description
70th birthday present for my mother's surprise :)

Could be an example of artist(painter) portfolio web page built with react.js.

## Blending two awesome libraries quite well
react-image-gallery(https://github.com/xiaolin/react-image-gallery)

react-photo-gallery(https://github.com/neptunian/react-photo-gallery)

Please check Arts component(Arts.js and Arts.css) about this.<file_sep>import React, { useState } from 'react'
import { BrowserRouter, Switch, Route, Redirect } from 'react-router-dom'
import styled from 'styled-components'
import NavBar from './components/NavBar'
import Footer from './components/Footer'
import About from './pages/About'
import Arts from './pages/Arts'
import Home from './pages/Home'
import Landing from './pages/Landing'

const StyledContainer = styled.div`
  display: flex;
  flex-flow: column nowrap;
  justify-content: center;
  align-items: stretch;
  min-height: inherit;
  & main {
    padding-top: 2vh;
    padding-bottom: 2vh;
  }
`

function App() {
  const [lang, setLang] = useState('Korean')

  return (
    <BrowserRouter>
    <Landing lang={lang} setLang={setLang} />
    <StyledContainer>
      <NavBar lang={lang} setLang={setLang} />
      <main>
        <Switch>
          <Redirect from='/' to='/home' exact />
          <Route path='/home'>
            <Home lang={lang} setLang={setLang} />
          </Route>
          <Route path='/arts'>
            <Arts />
          </Route>
          <Route path='/about'>
            <About lang={lang} setLang={setLang} />
          </Route>
        </Switch>
      </main>
      <Footer />
      </StyledContainer>
    </BrowserRouter>
  );
}

export default App;
<file_sep>import React from 'react'
import styled from 'styled-components'
import Fade from 'react-reveal/Fade'

const StyledAbout = styled.div`
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  align-items: center;
  font-family: 'Roboto', sans-serif;
  overflow-x: hidden;
`

const StyledPhoto = styled.div`
  margin: 0vh 10vh 0vh 10vh;
  & img {
    width: 300px;
    height: 300px;
    object-fit: cover;
    border-radius: 50%;
    @media (max-width: 900px) {
      width: 200px;
      height: 200px;
    }
  }
`

const StyledBio = styled.div`
  color: #857e7a;
  font-family: 'Cafe24Oneprettynight';
  font-size: 1.2rem;
  text-align: justify;
  max-width: 600px;
  min-height: 400px;
  margin-top: 1vh;
  @media (max-width: 600px) {
    max-width: 300px;
    font-size: 1rem;
  }
`

function About(props) {
  return (
    <StyledAbout>
      <StyledPhoto>
      <Fade left>
        <img src='/assets/portrait.jpg' alt='Portrait of EunGyeol'></img>
        </Fade>
        </StyledPhoto>
      { props.lang === 'Korean' && <StyledBio>
      <Fade right cascade>
        <div>
        <p>
          은결은 한국의 화가이다. 
        </p>
        <p>
          1952년 3월 23일 대한민국 경상북도 안동에서 출생하였으며 독실한 기독교 가정에서 자랐다.
          어린 시절부터 자연 속 삶에 매료되어 13세 때에는 실제로 이를 직접 실행에 옮기기도 하였다.
        </p>
        <p>
          경북대학교 사범대학을 졸업하여 화학 교사를 역임하였다.
          이후 해외 주재원 생활을 하게 된 현재의 배우자와 결혼하여, 그를 따라 프랑스 벨포로 이주 후 그 곳에서 수년간의 해외 생활을 하였다.
          그 시절 그녀는 유럽의 많은 나라들을 여행할 기회를 갖게 되었으며, 그 곳의 다양한 회화, 예술, 문화들은 그녀의 작품세계에도 적지 않은 영향을 끼치게 되었다. 
        </p>
        <p>
          그녀는 그녀의 전 생애에 걸쳐 그림을 그렸다.
          현재 사랑받는 아내이며, 두 아들의 어머니이자 네 아이들의 할머니이기도 하다.
          그리고 아직도 하나님의 사랑과 계획하심 안에 시골 어딘가에서 자유로운 삶을 살게되길 꿈꾸고 있다.
        </p>
        <p>
          2021년은 그녀의 탄생 70주년이 되는 해이다.
        </p>
        </div>
      </Fade>
      </StyledBio> }
      { props.lang === 'English' && <StyledBio>
      <Fade right cascade>
        <div>
        <p>
          EunGyeol is contemporary Korean painting artist. 
        </p>
        <p>
          She was born in Andong, a small city placed in southern east area of South Korea, March 23, 1952.
          She was raised in devout Christian family.
          Since childhood, she was attracted by living in nature, and she actually tried it when she was thirteen.
        </p>
        <p>
          She graduated Kyungpook National University as a chemistry teacher. 
          And married with present husband and moved to Belfort, France following him due to he was assigned as a expatriate.
          At that time she had many opportunities to travel various European countries and there she influenced by paintings, arts and culture there.
        </p>
        <p>
          She is painting through her whole life time.
          Now she is beloved wife, mother of two son and grandmother of four kids.
          And Still dreaming the life somewhere in countryside with the believe in the love of god and his plan.
        </p>
        <p>
          2021 is the year of her 70th birth anniversary.
        </p>
        </div>
      </Fade>
      </StyledBio> }
    </StyledAbout>
  )
}

export default About
<file_sep>import { useMemo } from 'react'
import { createPortal } from 'react-dom'

function Portal (props) {
  const container = useMemo(() => document.getElementById(props.container), [props.container])

  return createPortal(props.children, container)
}

export default Portal<file_sep>import React from 'react';
import ReactDOM from 'react-dom';
import { createGlobalStyle } from "styled-components";
import App from './App';
import reportWebVitals from './reportWebVitals';

const GlobalStyle = createGlobalStyle`
  @font-face {
      font-family: 'WandohopeR';
      src: local('WandohopeR'), url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-10@1.0/WandohopeR.woff') format('woff');
      font-weight: normal;
      font-style: normal;
  }
  @font-face {
      font-family: 'WandohopeB';
      src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_20-10@1.0/WandohopeB.woff') format('woff');
      font-weight: normal;
      font-style: normal;
  }
  @font-face {
      font-family: 'Cafe24Oneprettynight';
      src: url('https://cdn.jsdelivr.net/gh/projectnoonnu/noonfonts_twelve@1.1/Cafe24Oneprettynight.woff') format('woff');
      font-weight: normal;
      font-style: normal;
  }

  html, body, #root {
    margin: 0px;
    padding: 0px;
    min-height: 100vh;

    // /* Hide scrollbar for Chrome, Safari and Opera */
    // ::-webkit-scrollbar {
    //   display: none;
    // }
    // -ms-overflow-style: none;  /* IE and Edge */
    // scrollbar-width: none;  /* Firefox */
  }
`

ReactDOM.render(
  <>
    <App />
    <GlobalStyle />
  </>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
