diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index abe3ffe..af77578 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -2,6 +2,6 @@ \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 609f356..9426a51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,22 +8,21 @@ "name": "portfolio", "version": "0.1.1", "dependencies": { - "@types/react-syntax-highlighter": "^15.5.13", - "date-fns": "^3.3.1", + "date-fns": "^3.6.0", "next": "^14.2.3", "querystring": "^0.2.1", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", "react-syntax-highlighter": "^15.5.0", - "sharp": "^0.33.2", - "ws": "^8.17.0" + "sharp": "^0.33.2" }, "devDependencies": { "@babel/plugin-transform-class-static-block": "^7.23.4", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-syntax-highlighter": "^15.5.13", "@types/ws": "^8.5.10", "babel-plugin-jsx-control-statements": "^4.1.2", "eslint": "^8", @@ -1547,12 +1546,14 @@ "node_modules/@types/prop-types": { "version": "15.7.11", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", - "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==" + "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.60", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.60.tgz", "integrity": "sha512-dfiPj9+k20jJrLGOu9Nf6eqxm2EyJRrq2NvwOFsfbb7sFExZ9WELPs67UImHj3Ayxg8ruTtKtNnbjaF8olPq0A==", + "dev": true, "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -1572,6 +1573,7 @@ "version": "15.5.13", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", "integrity": "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==", + "dev": true, "dependencies": { "@types/react": "*" } @@ -1579,7 +1581,8 @@ "node_modules/@types/scheduler": { "version": "0.16.8", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", + "dev": true }, "node_modules/@types/unist": { "version": "2.0.10", @@ -2255,7 +2258,8 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2264,9 +2268,9 @@ "dev": true }, "node_modules/date-fns": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.3.1.tgz", - "integrity": "sha512-y8e109LYGgoQDveiEBD3DYXKba1jWf5BA8YU1FL5Tvm0BTdEfy54WLCwnuYWZNnzzvALy/QQ4Hov+Q9RVRv+Zw==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz", + "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", "funding": { "type": "github", "url": "https://github.com/sponsors/kossnocorp" @@ -5762,26 +5766,6 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/ws": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz", - "integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/package.json b/package.json index ef80f72..55b806c 100644 --- a/package.json +++ b/package.json @@ -9,22 +9,21 @@ "lint": "next lint" }, "dependencies": { - "@types/react-syntax-highlighter": "^15.5.13", - "date-fns": "^3.3.1", + "date-fns": "^3.6.0", "next": "^14.2.3", "querystring": "^0.2.1", "react": "^18", "react-dom": "^18", "react-icons": "^5.0.1", "react-syntax-highlighter": "^15.5.0", - "sharp": "^0.33.2", - "ws": "^8.17.0" + "sharp": "^0.33.2" }, "devDependencies": { "@babel/plugin-transform-class-static-block": "^7.23.4", "@types/node": "^20", "@types/react": "^18", "@types/react-dom": "^18", + "@types/react-syntax-highlighter": "^15.5.13", "@types/ws": "^8.5.10", "babel-plugin-jsx-control-statements": "^4.1.2", "eslint": "^8", diff --git a/src/app/page.css b/src/app/page.css index 88f4340..94ba3eb 100644 --- a/src/app/page.css +++ b/src/app/page.css @@ -106,11 +106,13 @@ body { position: relative; height: 60vh; + min-height: 700px; margin: 10vh 0; @media (orientation: portrait) { height: 100vh; + min-height: 850px; } } @@ -129,7 +131,7 @@ body { gap: 20px; } -.presentation-container { +.presentation-container-text { display: flex; flex-direction: column; @@ -154,7 +156,7 @@ body { } } -.presentation-container > p { +.presentation-container-text > p { margin-right: 20px; font-size: 1.2em; @@ -170,6 +172,52 @@ body { gap: 15px; flex-wrap: wrap; + + margin-bottom: 15px; +} + +.presentation-interactable { + display: flex; + + justify-content: space-between; + align-items: center; + + padding: 0 30px; +} + +.presentation-experience { + color: white; + + display: flex; + + flex-direction: column; + + gap: 8px; + + margin-bottom: 15px; +} + +.presentation-experience > p:nth-child(2) { + color: #565656; + + margin-left: 30px; +} + +.presentation-experience > p:nth-child(3) { + margin-left: 60px; +} + +.presentation-contact { + color: white; + + display: flex; + + flex-direction: column; + + align-items: flex-start; + justify-content: center; + + gap: 10px; } .presentation-boxes { @@ -188,12 +236,14 @@ body { width: 50%; height: 20vh; + min-height: 170px; + @media (orientation: portrait) { width: 100%; } } - /*projects section*/ +/*projects section*/ .projects { position: relative; diff --git a/src/app/page.tsx b/src/app/page.tsx index 2a27e0e..c630957 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -4,7 +4,7 @@ import {ReactElement, useEffect, useState} from "react"; import Image from "next/image"; import {formatDistanceToNowStrict} from "date-fns"; -import {FaInstagram, FaLinkedin} from "react-icons/fa6"; +import {FaDiscord, FaInstagram, FaLinkedin} from "react-icons/fa6"; import Social from "@/components/social"; import Box from "@/components/box"; @@ -15,16 +15,33 @@ import FooterContact from "@/components/footer-contact"; import Gist from "@/components/gist"; import Loader from "@/components/content-loader"; import SpotifyStatus from "@/components/spotify-status"; +import GithubRating from "@/components/github-rating"; import logo from "../../public/logo.png"; import "./page.css"; -import GithubRating from "@/components/github-rating"; export default function Home(): ReactElement { const [repos, setRepos]: StateTuple = useState(); const [page, setPage]: StateTuple = useState(0); const [gists, setGists]: StateTuple = useState(); + const [phrase, setPhrase]: StateTuple = useState(); + + const waitingPhrases: ReactElement[] = [ + <>... It's not the time YET, + <>... It hasn't arrived YET, + <>... It's not here, + <>... The moment is not NOW, + <>... The answer is not HERE, + <>... We're still WAITING, + <>... The time hasn't come, + <>... It's still being AWAITED, + <>... The reveal is PENDING, + <>... We are ANTICIPATING, + <>... It remains UNSEEN, + <>... The moment is COMING, + <>... The wait is not OVER + ]; useEffect((): void => { fetch("/github/repos") @@ -42,6 +59,8 @@ export default function Home(): ReactElement { ? (await r.json()) : null )); + + setPhrase(waitingPhrases[Math.floor(Math.random() * waitingPhrases.length)]); }, []); function setCurrentPage(page: number): void { @@ -51,9 +70,9 @@ export default function Home(): ReactElement { function setPresentationFace(smiling: boolean): (() => void) { return (): void => { const element: HTMLHeadingElement - = document.querySelector(".presentation-container > h1 > span") as HTMLHeadingElement; + = document.querySelector(".presentation-container-text > h1 > span") as HTMLHeadingElement; - element.innerText = smiling ? ":D" : ";)"; + element.innerText = smiling ? " :D" : " ;)"; }; } @@ -67,26 +86,54 @@ export default function Home(): ReactElement {
- <> -

Hello ;)

+
+

Hello + +   ;) + +

{` I'm Esteve, a ${formatDistanceToNowStrict(new Date("2005-11-06"))} old developer on a mission to bring ideas to life and craft innovative solutions. Embarking on this journey - ${formatDistanceToNowStrict(new Date("2021-12-26"))} ago, I've delved into realms of C#, C++, JavaScript, React, Vue, Laravel, + ${formatDistanceToNowStrict(new Date("2021-12-26"))} ago, I've delved into realms of C#, C++, + JavaScript, React, Vue, Laravel, and beyond. Eager to embrace fresh challenges and perpetually pursuing knowledge, I stand ready to collaborate and create wonders together. Let's build something extraordinary! - `} + `}

-
- } name="Instagram"/> - } name="LinkedIn"/> +
+
+
+

Take a look at

+

... Forget it ...

+

{phrase ?? "..."}

- +
+

Get in touch:

+
+ } + name="Discord" + /> + } + name="Instagram" + /> + } + name="LinkedIn" + /> +
+
+
- - + +
diff --git a/src/app/types.d.ts b/src/app/types.d.ts index 691f3b8..26a53f7 100644 --- a/src/app/types.d.ts +++ b/src/app/types.d.ts @@ -7,4 +7,10 @@ type BaseProps = React.DetailedHTMLProps = [S, React.Dispatch>]; -declare module "*.pdf"; \ No newline at end of file +declare module "*.pdf"; + +interface LocalRefObject { + current: T; +} + +type WaitingStates = T | undefined | null; \ No newline at end of file diff --git a/src/components/social/index.css b/src/components/social/index.css index 699075b..bfb46d7 100644 --- a/src/components/social/index.css +++ b/src/components/social/index.css @@ -3,6 +3,10 @@ height: 40px; width: 100px; + @media (orientation: portrait) { + width: 30px; + } + display: flex; justify-content: center; @@ -21,6 +25,12 @@ cursor: pointer; } +@media (orientation: portrait) { + .social > span { + display: none; + } +} + .social:hover { box-shadow: 0 0 15px; } diff --git a/src/components/social/index.tsx b/src/components/social/index.tsx index f5535a2..a1dfa46 100644 --- a/src/components/social/index.tsx +++ b/src/components/social/index.tsx @@ -38,12 +38,10 @@ export default function Social({ className={`${className} social`} {...props} > - <> - {hovering - ? - : cloneElement(icon, {className: "logo-in"}) - } - {name} - + {hovering + ? + : cloneElement(icon, {className: "logo-in"}) + } + {name} } \ No newline at end of file diff --git a/src/components/spotify-status/index.css b/src/components/spotify-status/index.css index 724ea69..687f159 100644 --- a/src/components/spotify-status/index.css +++ b/src/components/spotify-status/index.css @@ -120,6 +120,10 @@ .spotify-box-controls > *:first-child { width: 69%; + + @media (max-width: 1800px) { + width: 55%; + } } .logo-spotify { @@ -132,7 +136,7 @@ font-size: 1.5em; - @media (orientation: portrait) { - left: 91%; + @media (max-width: 1670px) { + display: none; } } \ No newline at end of file diff --git a/src/components/spotify-status/index.tsx b/src/components/spotify-status/index.tsx index e99f382..362893c 100644 --- a/src/components/spotify-status/index.tsx +++ b/src/components/spotify-status/index.tsx @@ -26,6 +26,8 @@ https://community.spotify.com/t5/Spotify-for-Developers/Access-to-websockets/td- This uses prediction to avoid making a request every second, it syncs actions within a max window of 5 seconds. +source: https://github.com/stifskere/Portfolio/blob/main/src/components/spotify-status/index.tsx + `); export default function SpotifyStatus(): ReactElement { @@ -34,11 +36,11 @@ export default function SpotifyStatus(): ReactElement { const hovering: MutableRefObject = useRef(false); useEffect((): (() => void) => { - let lastData: SpotifySong | undefined; + let lastData: LocalRefObject> = { current: undefined }; let msInterval: NodeJS.Timeout | undefined; let lastUpdateTimestamp: number | undefined; - const interval: NodeJS.Timeout = setInterval(async (): Promise => { + async function spotifyFetchCycle(lastData: LocalRefObject>): Promise { const data: SpotifySong = await fetch("/spotify", { headers: { "Cache-Control": "no-cache" @@ -46,12 +48,12 @@ export default function SpotifyStatus(): ReactElement { }).then(res => res.json()); if ( - lastData !== undefined - && lastData !== null + lastData.current !== undefined + && lastData.current !== null && data !== null - && lastData.is_playing === data?.is_playing - && lastData.song_url === data?.song_url - && lastData.time_played === ms! + (lastUpdateTimestamp ? Date.now() - lastUpdateTimestamp : 0) + && lastData.current.is_playing === data?.is_playing + && lastData.current.song_url === data?.song_url + && lastData.current.time_played === ms! + (lastUpdateTimestamp ? Date.now() - lastUpdateTimestamp : 0) ) { return; } @@ -59,21 +61,22 @@ export default function SpotifyStatus(): ReactElement { if (data !== null) setMs(data!.time_played); - setCurrent(lastData = data); + setCurrent(lastData.current = data); lastUpdateTimestamp = Date.now(); - }, 5000); + } - msInterval = setInterval(() => { - if ( - lastData !== null - && lastData !== undefined - && !lastData.is_playing - && lastData.time_played >= lastData.time_total - ) + const interval: NodeJS.Timeout = setInterval(spotifyFetchCycle, 5000, lastData); + spotifyFetchCycle(lastData).then(); + + msInterval = setInterval((lastData: LocalRefObject>): void => { + if (lastData.current === undefined || lastData.current === null) + return; + + if (!lastData.current.is_playing || lastData.current.time_played >= lastData.current.time_total) return; setMs(ms => ms! + 500); - }, 500); + }, 500, lastData); return (): void => { clearInterval(interval);