diff --git a/package.json b/package.json index 78deb0d..be615d7 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "react": "^16.13.1", "react-dom": "^16.13.1", + "react-pdf": "^5.7.2", "react-router-dom": "^5.1.2", "react-scripts": "^3.4.3", "react-use": "^13.27.0", diff --git a/src/App.css b/src/App.css index 6803d72..5c97f63 100644 --- a/src/App.css +++ b/src/App.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css?family=Montserrat:regular,bold,italic&subset=latin,latin-ext'); + * { padding: 0px; margin: 0px; @@ -19,7 +21,7 @@ a { .text { color: black; - font-family: 'Avenir'; + font-family: 'Montserrat'; font-size: 18px; } @@ -27,6 +29,7 @@ a { display: flex; width: 100vw; flex-direction: row; + scroll-behavior: smooth; } .content { @@ -34,24 +37,37 @@ a { transition: 500ms ease-out; border: none; outline: none; + /* overflow: hidden; */ + height: 100%; } .row { display: flex; flex-direction: row; } + +@keyframes fadeInOpacity { + 0% { opacity: 0 } + 100% { opacity: 1 } +} + .fade-in { opacity: 1; animation-name: fadeInOpacity; animation-iteration-count: 1; animation-timing-function: ease-in; - animation-duration: 1s; + animation-duration: 0.8s; } .disable-selection { + user-select: none; -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* Internet Explorer */ -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ -webkit-user-select: none; /* Chrome, Safari, and Opera */ -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ } + +body { + height: 100%; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index edf3011..478cf1f 100644 --- a/src/App.js +++ b/src/App.js @@ -1,7 +1,7 @@ import React from 'react'; import { BrowserRouter, Route, Switch } from 'react-router-dom'; -import { Home } from 'containers/Home'; +import { NewHome } from 'containers/NewHome'; import { NoMatchPage } from 'containers/NoMatchPage'; import './App.css'; @@ -16,7 +16,7 @@ const App = () => {
- + diff --git a/src/assets/data/projects.jsx b/src/assets/data/projects.jsx index b5e1868..7c4b5e2 100644 --- a/src/assets/data/projects.jsx +++ b/src/assets/data/projects.jsx @@ -139,7 +139,7 @@ const PROJECTS = [ wideSS: true, }, { - name: 'CoverLove', + name: 'coverlove', logo: coverloveLogo, color: '#d35400', title: 'A simple tool to create new playlist cover art.', @@ -168,20 +168,20 @@ const PROJECTS = [ techs: ['React Native, Voximplant API, Google Speech-to-Text API, Twilio API'], contributors: 'Harun Kaneti, Eytan Nahmiyas', }, - { - name: 'ClaimMate', - logo: claimmateLogo, - color: '#2c3e50', - title: 'Global Legal Hackathon 2019 finalist.', - // description: 'Done in 48 hours. I slept like 5 hours.', - awards: ['2nd Place, Global Legal Hackathon 2019'], - platforms: { - github: 'https://github.com/ralfisalhon/CLAIMate', - website: 'https://globallegalhackathon.com/', - }, - video: 'https://www.youtube.com/embed/3uotkVSIglA', - techs: ['React Native'], - }, + // { + // name: 'ClaimMate', + // logo: claimmateLogo, + // color: '#2c3e50', + // title: 'Global Legal Hackathon 2019 finalist.', + // // description: 'Done in 48 hours. I slept like 5 hours.', + // awards: ['2nd Place, Global Legal Hackathon 2019'], + // platforms: { + // github: 'https://github.com/ralfisalhon/CLAIMate', + // website: 'https://globallegalhackathon.com/', + // }, + // video: 'https://www.youtube.com/embed/3uotkVSIglA', + // techs: ['React Native'], + // }, ]; export default PROJECTS; diff --git a/src/assets/images/leftarrow.png b/src/assets/images/leftarrow.png new file mode 100644 index 0000000..30a9681 Binary files /dev/null and b/src/assets/images/leftarrow.png differ diff --git a/src/assets/images/magglass.png b/src/assets/images/magglass.png new file mode 100644 index 0000000..31b19c7 Binary files /dev/null and b/src/assets/images/magglass.png differ diff --git a/src/assets/pdf/RRS_Resume_Feb_2022.pdf b/src/assets/pdf/RRS_Resume_Feb_2022.pdf new file mode 100644 index 0000000..e87a3ed Binary files /dev/null and b/src/assets/pdf/RRS_Resume_Feb_2022.pdf differ diff --git a/src/assets/pdf/RRS_Resume_Sep_2020.pdf b/src/assets/pdf/RRS_Resume_Sep_2020.pdf deleted file mode 100644 index 141d879..0000000 Binary files a/src/assets/pdf/RRS_Resume_Sep_2020.pdf and /dev/null differ diff --git a/src/components/projects/FloatingProjects.js b/src/components/projects/FloatingProjects.js new file mode 100644 index 0000000..07ec244 --- /dev/null +++ b/src/components/projects/FloatingProjects.js @@ -0,0 +1,85 @@ +import React, { useCallback, useState } from 'react'; +import './styles.css'; +import { Base, Item, RoundIcon } from './styles.js'; + +import PROJECTS from 'assets/data/projects'; + +// const getLink = name => { +// const proj = PROJECTS.find(proj => proj.name === name); +// return proj.platforms.appstore || proj.platforms.github; +// }; + +export const FloatingProjects = ({ isPaused, setIsPaused }) => { + const [someAnimationState, setSomeAnimationState] = useState(0); + const [selectedIndex, setSelectedIndex] = useState(100); + const [numClicksGreater, setNumClicksGreater] = useState(0); + const [preventClicks, setPreventClicks] = useState(false); + + const handleOnClickProject = useCallback( + index => { + if (selectedIndex === index) { + setSelectedIndex(-1); + return setIsPaused(false); + } + setPreventClicks(true); + setTimeout(() => { + setPreventClicks(false); + }, 500); + if (selectedIndex < index || index === 0) { + setNumClicksGreater(numClicksGreater + 1); + } + setSomeAnimationState(2 + numClicksGreater - index * 0.125 - 0.125); + setIsPaused(true); + setSelectedIndex(index); + // const el = document.getElementById('base'); + // el.classList.remove('base'); + // void el.offsetHeight; /* trigger reflow */ + // el.classList.add('base'); + + // const el2 = document.getElementById('item'); + // el2.classList.remove('item'); + // void el2.offsetHeight; /* trigger reflow */ + // el2.classList.add('item'); + }, + [numClicksGreater, selectedIndex, setIsPaused] + ); + return ( + <> + {/* */} + + {PROJECTS.map((project, index) => ( + +
!preventClicks && handleOnClickProject(index)} + rel="noopener noreferrer" + target="_blank" + > + +

{project.name}

+
+
+ ))} + + + ); +}; diff --git a/src/components/projects/index.js b/src/components/projects/index.js new file mode 100644 index 0000000..a823895 --- /dev/null +++ b/src/components/projects/index.js @@ -0,0 +1 @@ +export * from './FloatingProjects'; diff --git a/src/components/projects/styles.css b/src/components/projects/styles.css new file mode 100644 index 0000000..3f163a6 --- /dev/null +++ b/src/components/projects/styles.css @@ -0,0 +1,159 @@ + +.base p { + color: var(--light-color); +} + +.base a { + text-decoration-line: none; +} + +.row { + display: flex; + flex-direction: row; + justify-content: space-evenly; +} + +.drop-glow { + filter: drop-shadow(0 0 0.75rem gray); +} + +.round_icon { + width: 100px; + height: 100px; + cursor: pointer; + filter: drop-shadow(0 0 0.75rem #2f3640); +} + +.centertext { + position: absolute; + top: 50%; + left: calc(50% + 5vw); + transform: translate(-50%, -50%); + opacity: 1 !important; +} + +.centertext-mobile { + display: flex; + flex-direction: column; + height: 100vh; + justify-content: center; + overflow: hidden; +} + +:root { + --size: 800px; +} + +.base { + width: var(--size); + height: var(--size); + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + margin-left: -135px; + margin-top: -380px; + position: absolute; + border-radius: 50%; + /* border: 1px solid white; */ + + + animation-delay: -2s; + animation-name: spin; + animation-fill-mode: forwards; + /* animation-iteration-count: infinite; */ + animation-timing-function: linear; + + pointer-events: none; +} + +.item { + position: absolute; + font-family: Arial; + font-size: 85px; + text-align: center; + width: 100px; + height: 100px; + + animation-delay: -2s; + animation-name: spin; + animation-fill-mode: forwards; + animation-iteration-count: infinite; + animation-timing-function: linear; + animation-direction: reverse; + + pointer-events: auto; +} + + + +.base:hover .item { + /* animation-play-state: paused; */ + opacity: 0.5; + +} + +.base:hover { + /* animation-play-state: paused; */ +} + +.item:hover { + opacity: 1 !important; + filter: drop-shadow(0 0 0.75rem gray); +} + +.item-8 { + top: calc(50% - 50px); + left: calc(100% - 100px); +} + +.item-6 { + top: 0px; + left: calc(50% - 50px); +} + +.item-4 { + top: calc(50% - 54px); + left: 0px; +} + +.item-2 { + top: calc(100% - 100px); + left: calc(50% - 54px); +} + +.item-1 { + top: calc(84% - 75px); + left: calc(84% - 79px); +} + +.item-3 { + top: calc(84% - 71px); + left: calc(16% - 33px); +} + +.item-7 { + top: calc(16% - 33px); + left: calc(84% - 67px); +} + +.item-5 { + top: calc(16% - 37px); + left: calc(16% - 33px); +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +.footer-mobile { + display: flex; + flex-direction: row; + min-height: 35px; +} diff --git a/src/components/projects/styles.js b/src/components/projects/styles.js new file mode 100644 index 0000000..ed12afe --- /dev/null +++ b/src/components/projects/styles.js @@ -0,0 +1,25 @@ +import styled from 'styled-components/macro'; + +export const Base = styled.div` + animation-iteration-count: ${({ isPaused, someAnimationState }) => + isPaused ? `${someAnimationState}` : 'infinite'}; + transition: left 1s ease; + left: ${({ isPaused }) => (isPaused ? `-220px` : '0')}; + + animation-duration: ${({ isPaused, someAnimationState }) => + isPaused ? `3s` : '45s'}; + + ${({ isPaused }) => isPaused && `animation-play-state: running`}; +`; + +export const Item = styled.div` + animation-iteration-count: ${({ isPaused, someAnimationState }) => + isPaused ? `${someAnimationState}` : 'infinite'}; + + animation-duration: ${({ isPaused, someAnimationState }) => + isPaused ? `3s` : '45s'}; + ${({ selected, isPaused }) => isPaused && !selected && `opacity: 0.5`}; + + ${({ isPaused }) => isPaused && `animation-play-state: running`}; +`; +export const RoundIcon = styled.img``; diff --git a/src/components/resume/AnnotationLayer.css b/src/components/resume/AnnotationLayer.css new file mode 100644 index 0000000..77b28ed --- /dev/null +++ b/src/components/resume/AnnotationLayer.css @@ -0,0 +1,205 @@ +/* Copyright 2014 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.annotationLayer section { + position: absolute; +} + +.annotationLayer .linkAnnotation > a, +.annotationLayer .buttonWidgetAnnotation.pushButton > a { + position: absolute; + font-size: 1em; + top: 0; + left: 0; + width: 100%; + height: 100%; +} + +.annotationLayer .linkAnnotation > a:hover, +.annotationLayer .buttonWidgetAnnotation.pushButton > a:hover { + opacity: 0.2; + background: #ff0; + box-shadow: 0px 2px 10px #ff0; +} + +.annotationLayer .textAnnotation img { + position: absolute; + cursor: pointer; +} + +.annotationLayer .textWidgetAnnotation input, +.annotationLayer .textWidgetAnnotation textarea, +.annotationLayer .choiceWidgetAnnotation select, +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { + background-color: rgba(0, 54, 255, 0.13); + border: 1px solid transparent; + box-sizing: border-box; + font-size: 9px; + height: 100%; + margin: 0; + padding: 0 3px; + vertical-align: top; + width: 100%; +} + +.annotationLayer .choiceWidgetAnnotation select option { + padding: 0; +} + +.annotationLayer .buttonWidgetAnnotation.radioButton input { + border-radius: 50%; +} + +.annotationLayer .textWidgetAnnotation textarea { + font: message-box; + font-size: 9px; + resize: none; +} + +.annotationLayer .textWidgetAnnotation input[disabled], +.annotationLayer .textWidgetAnnotation textarea[disabled], +.annotationLayer .choiceWidgetAnnotation select[disabled], +.annotationLayer .buttonWidgetAnnotation.checkBox input[disabled], +.annotationLayer .buttonWidgetAnnotation.radioButton input[disabled] { + background: none; + border: 1px solid transparent; + cursor: not-allowed; +} + +.annotationLayer .textWidgetAnnotation input:hover, +.annotationLayer .textWidgetAnnotation textarea:hover, +.annotationLayer .choiceWidgetAnnotation select:hover, +.annotationLayer .buttonWidgetAnnotation.checkBox input:hover, +.annotationLayer .buttonWidgetAnnotation.radioButton input:hover { + border: 1px solid #000; +} + +.annotationLayer .textWidgetAnnotation input:focus, +.annotationLayer .textWidgetAnnotation textarea:focus, +.annotationLayer .choiceWidgetAnnotation select:focus { + background: none; + border: 1px solid transparent; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after, +.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { + background-color: #000; + content: ''; + display: block; + position: absolute; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before, +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { + height: 80%; + left: 45%; + width: 1px; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:before { + transform: rotate(45deg); +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input:checked:after { + transform: rotate(-45deg); +} + +.annotationLayer .buttonWidgetAnnotation.radioButton input:checked:before { + border-radius: 50%; + height: 50%; + left: 30%; + top: 20%; + width: 50%; +} + +.annotationLayer .textWidgetAnnotation input.comb { + font-family: monospace; + padding-left: 2px; + padding-right: 0; +} + +.annotationLayer .textWidgetAnnotation input.comb:focus { + /* + * Letter spacing is placed on the right side of each character. Hence, the + * letter spacing of the last character may be placed outside the visible + * area, causing horizontal scrolling. We avoid this by extending the width + * when the element has focus and revert this when it loses focus. + */ + width: 115%; +} + +.annotationLayer .buttonWidgetAnnotation.checkBox input, +.annotationLayer .buttonWidgetAnnotation.radioButton input { + appearance: none; + padding: 0; +} + +.annotationLayer .popupWrapper { + position: absolute; + width: 20em; +} + +.annotationLayer .popup { + position: absolute; + z-index: 200; + max-width: 20em; + background-color: #ffff99; + box-shadow: 0px 2px 5px #888; + border-radius: 2px; + padding: 6px; + margin-left: 5px; + cursor: pointer; + font: message-box; + font-size: 9px; + word-wrap: break-word; +} + +.annotationLayer .popup > * { + font-size: 9px; +} + +.annotationLayer .popup h1 { + display: inline-block; +} + +.annotationLayer .popup span { + display: inline-block; + margin-left: 5px; +} + +.annotationLayer .popup p { + border-top: 1px solid #333; + margin-top: 2px; + padding-top: 2px; +} + +.annotationLayer .highlightAnnotation, +.annotationLayer .underlineAnnotation, +.annotationLayer .squigglyAnnotation, +.annotationLayer .strikeoutAnnotation, +.annotationLayer .freeTextAnnotation, +.annotationLayer .lineAnnotation svg line, +.annotationLayer .squareAnnotation svg rect, +.annotationLayer .circleAnnotation svg ellipse, +.annotationLayer .polylineAnnotation svg polyline, +.annotationLayer .polygonAnnotation svg polygon, +.annotationLayer .caretAnnotation, +.annotationLayer .inkAnnotation svg polyline, +.annotationLayer .stampAnnotation, +.annotationLayer .fileAttachmentAnnotation { + cursor: pointer; +} diff --git a/src/components/resume/index.jsx b/src/components/resume/index.jsx new file mode 100644 index 0000000..cf3d91a --- /dev/null +++ b/src/components/resume/index.jsx @@ -0,0 +1,35 @@ +import React, { Component } from 'react'; +import { Document, Page, pdfjs } from 'react-pdf'; +import resume from '../../assets/pdf/RRS_Resume_Feb_2022.pdf'; +import './AnnotationLayer.css'; +import './styles.css'; +pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`; + +function removeTextLayerOffset() { + const textLayers = document.querySelectorAll('.react-pdf__Page__textContent'); + textLayers.forEach((layer) => { + const { style } = layer; + style.top = '0'; + style.left = '0'; + style.transform = ''; + }); +} + +class ResumePage extends Component { + render() { + // const resumeHeight = window.innerHeight * 1.25; + const resumeHeight = this.props.height; + return ( +
+
+ + + +
+

Last Updated: February 2022

+
+ ); + } +} + +export default ResumePage; diff --git a/src/components/resume/styles.css b/src/components/resume/styles.css new file mode 100644 index 0000000..220b1fd --- /dev/null +++ b/src/components/resume/styles.css @@ -0,0 +1,18 @@ +.resume-page { + width: 540px; + display: flex; + flex-direction: column; + align-items: center; + padding-bottom: 20px; +} + +.resume-page a { + border: none; + text-decoration-line: none; +} + +.download { + color: white !important; + font-size: 16px; + padding-top: 10px; +} \ No newline at end of file diff --git a/src/containers/Home/home.css b/src/containers/Home/home.css deleted file mode 100644 index 4aa346a..0000000 --- a/src/containers/Home/home.css +++ /dev/null @@ -1,14 +0,0 @@ -.pointer { - cursor: pointer; -} - -.rotate { - -moz-transition: all 250ms linear; - -webkit-transition: all 250ms linear; - transition: all 250ms linear; -} -.rotate.down { - -moz-transform: rotate(150deg); - -webkit-transform: rotate(150deg); - transform: rotate(150deg); -} diff --git a/src/containers/Home/index.js b/src/containers/Home/index.js deleted file mode 100644 index 5cf53a4..0000000 --- a/src/containers/Home/index.js +++ /dev/null @@ -1,267 +0,0 @@ -import React, { useState, useCallback } from 'react'; -import { - Wrapper, - Splash, - ButtonWrapper, - Button, - SocialButtons, - RefreshContainer, - InfoWrapper, - Projects, - Resume, - ProjLinks, - Awards, - ProjScreenshots, - ProjVideo, - Logo, - HeaderRow, - SelectedProj, -} from './styles'; - -import LinkedInLogo from 'assets/svg/linkedin.svg'; -import YoutubeLogo from 'assets/svg/youtube.svg'; -import GithubLogo from 'assets/svg/github.svg'; -import SpotifyLogo from 'assets/svg/spotify.svg'; - -import RefreshLogo from 'assets/svg/refresh.svg'; -import SunLogo from 'assets/svg/sun.svg'; - -import PROJECTS from 'assets/data/projects'; -import RESUME from 'assets/pdf/RRS_Resume_Sep_2020.pdf'; - -import { Fun } from 'components/Fun'; - -import './home.css'; - -const PLATFORM_CONVERSIONS = { - github: 'GitHub', - playstore: 'Play Store', - appstore: 'App Store', - website: 'Website', -}; - -const COLORS = [ - { fg: '#f2f2f2', bg: '#181818', darkBg: true }, - { fg: '#f2f2f2', bg: '#16a085', darkBg: false }, - { fg: '#f2f2f2', bg: '#f39c12', darkBg: false }, - { fg: '#f2f2f2', bg: '#27ae60', darkBg: false }, - { fg: '#f2f2f2', bg: '#d35400', darkBg: false }, - { fg: '#f2f2f2', bg: '#2980b9', darkBg: false }, - { fg: '#f2f2f2', bg: '#c0392b', darkBg: false }, - { fg: '#f2f2f2', bg: '#8e44ad', darkBg: false }, - { fg: '#f2f2f2', bg: '#2c3e50', darkBg: false }, -]; - -const IS_MOBILE = window.innerWidth <= 1200 || window.innerHeight < 600; - -const getRandomNumber = (min, max) => { - let randomNum = Math.random() * (max - min) + min; - return Math.round(randomNum); -}; - -export const Home = () => { - const [color, setColor] = useState(COLORS[0]); - const [lightMode, setLightMode] = useState(true); - const [pressed, setPressed] = useState(false); - const [randNumber, setRandNumber] = useState(getRandomNumber(0, COLORS.length - 1)); - const [selected, setSelected] = useState(); - const [selectedProj, setSelectedProj] = useState(); - - const handleKeyPress = (e) => { - e.persist(); - if (e.key === ' ') { - setLightMode(!lightMode); - } - }; - - const handleRefresh = useCallback(() => { - setPressed(true); - setTimeout(() => setPressed(false), 250); - let newRand = randNumber; - while (newRand === randNumber) newRand = getRandomNumber(0, COLORS.length - 1); - setRandNumber(newRand); - setColor(COLORS[newRand]); - }, [randNumber]); - - const handleSelectedTab = (id) => { - if (selected === id) { - setSelected(null); - } else { - setSelected(id); - } - }; - - if (!color) return null; - return ( -
- - - - Refresh Logo handleRefresh()} - /> - Sun Logo setLightMode(!lightMode)} - /> - -

Ralfi Salhon

- - -

/

- -

/

- -

/

-
handleRefresh()} onClick={() => setLightMode(false)}> - -
-
- {selected && ( - - {selected === 'resume' && ( - -

- - View - - - Download - -

-
- )} - {selected === 'projects' && ( - <> - - {PROJECTS?.map((proj) => ( -

setSelectedProj(proj)} - > - {proj.name} -

- ))} -
- {selectedProj && ( - - - {!IS_MOBILE && ( - - logo - - )} -
- - {selectedProj?.awards?.map((award) => ( - -

- {'🏆'} {award} -

-
- ))} -
- - {Object.keys(selectedProj.platforms).map((platform) => ( - - {PLATFORM_CONVERSIONS[platform]} - - ))} - -
-
- - {selectedProj.screenshots?.map((screenshot) => ( - Project Screenshot - ))} - - - - {selectedProj?.video && ( -