diff --git a/gatsby-config.js b/gatsby-config.js index 0f57958..5f2f53d 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -3,15 +3,9 @@ module.exports = { title: 'Adam Graham', siteUrl: 'https://adamgraham.github.io', description: - 'Adam Graham is a professional software engineer and game developer inspired by a passion for art, design, and engineering.', + 'Adam is a software engineer and game developer inspired by the blending of art, design, and engineering to create best in class user experiences.', }, plugins: [ - { - resolve: 'gatsby-plugin-google-analytics', - options: { - trackingId: 'UA-61761892-1', - }, - }, { resolve: 'gatsby-plugin-manifest', options: { @@ -24,6 +18,7 @@ module.exports = { icon: 'static/logo512.png', }, }, + 'gatsby-plugin-use-query-params', 'gatsby-plugin-react-helmet', 'gatsby-plugin-sitemap', 'gatsby-plugin-portal', diff --git a/gatsby-ssr.js b/gatsby-ssr.js index 15bfe2f..20d4f20 100644 --- a/gatsby-ssr.js +++ b/gatsby-ssr.js @@ -7,7 +7,7 @@ const HtmlAttributes = { const HeadComponents = [ , // prettier-ignore , // prettier-ignore - , // prettier-ignore + , // prettier-ignore , // prettier-ignore ]; diff --git a/package.json b/package.json index 2412a5a..c4b8197 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "gatsby-plugin-react-helmet": "^5.17.0", "gatsby-plugin-sharp": "^4.17.0", "gatsby-plugin-sitemap": "^5.17.0", + "gatsby-plugin-use-query-params": "^1.0.1", "gatsby-source-filesystem": "^4.17.0", "gatsby-transformer-json": "^4.17.0", "gatsby-transformer-sharp": "^4.17.0", @@ -23,7 +24,8 @@ "prop-types": "^15.8.1", "react": "^18.1.0", "react-dom": "^18.1.0", - "react-helmet": "^6.1.0" + "react-helmet": "^6.1.0", + "use-query-params": "^2.2.1" }, "devDependencies": { "eslint": "^8.18.0", diff --git a/src/components/Gallery.js b/src/components/Gallery.js index ca37613..69bc048 100644 --- a/src/components/Gallery.js +++ b/src/components/Gallery.js @@ -1,45 +1,53 @@ +import '../styles/gallery.css'; import { useMediaQuery } from '@zigurous/react-components'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import React, { useContext } from 'react'; import GalleryContext from './GalleryContext'; import Slide from './Slide'; -import icons from '../icons'; -import '../styles/gallery.css'; const Gallery = ({ className }) => { const context = useContext(GalleryContext); - const verticalLayout = useMediaQuery('(max-width: 1365px)'); + const vertical = useMediaQuery('(max-width: 1365px)'); return ( -
{context.currentSlide && }
-
+ ); }; diff --git a/src/components/GalleryContext.js b/src/components/GalleryContext.js index 2bc1db0..a6d85ea 100644 --- a/src/components/GalleryContext.js +++ b/src/components/GalleryContext.js @@ -1,28 +1,25 @@ import { navigate } from 'gatsby'; import { createContext, useMemo } from 'react'; -import { setSessionIndex } from '../utils/session'; -export const useContext = (category, gallery, slideIndex, setSlideIndex) => { - return useMemo( - () => ({ +export const useContextState = (category, slides = [], slideIndex) => { + return useMemo(() => { + return { category, - gallery, + slides, slideIndex, - currentSlide: gallery[slideIndex], + currentSlide: slides[slideIndex], setSlideIndex: (index) => { - index = Math.min(Math.max(index, 0), gallery.length - 1); - setSessionIndex(category, index); - setSlideIndex(index); - navigate(`/${category}`); + if (index >= slides.length) index = 0; + if (index < 0) index = slides.length - 1; + navigate(`/${category}?index=${index}`); }, - }), - [category, gallery, slideIndex] - ); + }; + }, [category, slides, slideIndex]); }; const GalleryContext = createContext({ category: '', - gallery: [], + slides: [], slideIndex: 0, currentSlide: null, setSlideIndex: () => {}, diff --git a/src/components/GalleryPage.js b/src/components/GalleryPage.js deleted file mode 100644 index a942e0f..0000000 --- a/src/components/GalleryPage.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useState } from 'react'; -import Gallery from './Gallery'; -import GalleryContext, { useContext } from './GalleryContext'; -import Page from './Page'; -import { SlideProps } from './Slide'; -import { titleCase } from '../utils/formatting'; -import { getSessionIndex } from '../utils/session'; - -const GalleryPage = ({ category, gallery, location }) => { - const [slideIndex, setSlideIndex] = useState(getSessionIndex(category)); - const context = useContext(category, gallery, slideIndex, setSlideIndex); - return ( - - - - - - ); -}; - -GalleryPage.propTypes = { - category: PropTypes.string, - gallery: PropTypes.arrayOf(SlideProps), - location: PropTypes.object, -}; - -export default GalleryPage; diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..915bfb6 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,123 @@ +import '../styles/header.css'; +import { Link, NavBar, SocialNavLinks } from '@zigurous/react-components'; +import { Link as GatsbyLink } from 'gatsby'; +import PropTypes from 'prop-types'; +import React, { useContext } from 'react'; +import GalleryContext from './GalleryContext'; +import { navLinks, socialLinks } from '../links'; +import { titleCase } from '../utils/formatting'; + +const Header = ({ location, isMenuOpen, toggleMenu }) => { + const gallery = useContext(GalleryContext); + return ( +
+
+
+ + + + + + + + {/* {gallery.slideIndex === undefined && !fullscreen && ( + + {titleCase(category)} + + )} + {gallery.slideIndex !== undefined && ( +
+ + +
+ )} */} +
+
+ + +
+
+
+ ); +}; + +Header.propType = { + location: PropTypes.object, + isMenuOpen: PropTypes.bool, + toggleMenu: PropTypes.func, +}; + +export default Header; diff --git a/src/components/Menu.js b/src/components/Menu.js index ea103cb..bd353a7 100644 --- a/src/components/Menu.js +++ b/src/components/Menu.js @@ -1,41 +1,174 @@ -import { useModalOverlay } from '@zigurous/react-components'; +import '../styles/menu.css'; +import { Link, useModalOverlay } from '@zigurous/react-components'; import classNames from 'classnames'; +import { Link as GatsbyLink, graphql, navigate, useStaticQuery } from 'gatsby'; import PropTypes from 'prop-types'; -import React, { useCallback, useState } from 'react'; -import MenuBar from './MenuBar'; -import MenuGallery from './MenuGallery'; -import MenuList from './MenuList'; -import { MENU_TYPE_GALLERY, MENU_TYPE_LIST, MENU_TYPE_NONE } from '../types/menu'; // prettier-ignore -import '../styles/menu.css'; - -const Menu = ({ location }) => { - const [menuType, setMenuType] = useState(MENU_TYPE_NONE); - const fullscreen = menuType !== MENU_TYPE_NONE; - const listOpen = menuType === MENU_TYPE_LIST; - const galleryOpen = menuType === MENU_TYPE_GALLERY; - - const closeMenu = useCallback(() => { - setMenuType(MENU_TYPE_NONE); - }, []); +import React, { useState } from 'react'; +import Header from './Header'; +import { navLinks } from '../links'; - useModalOverlay(fullscreen, true); +const query = graphql` + query Menu { + games: allGamesJson { + nodes { + id: jsonId + category + title + image { + sharp: childImageSharp { + original { + src + width + height + } + } + } + imageAltText + imageBorder + } + } + art: allArtJson { + nodes { + id: jsonId + category + title + image { + sharp: childImageSharp { + original { + src + width + height + } + } + } + imageAltText + imageBorder + } + } + websites: allWebsitesJson { + nodes { + id: jsonId + category + title + image { + sharp: childImageSharp { + original { + src + width + height + } + } + } + imageAltText + imageBorder + } + } + tech: allTechJson { + nodes { + id: jsonId + category + title + image { + sharp: childImageSharp { + original { + src + width + height + } + } + } + imageAltText + imageBorder + } + } + presentations: allPresentationsJson { + nodes { + id: jsonId + category + title + image { + sharp: childImageSharp { + original { + src + width + height + } + } + } + imageAltText + imageBorder + } + } + } +`; +const Menu = ({ location }) => { + const [isOpen, setIsOpen] = useState(false); + const data = useStaticQuery(query); + useModalOverlay(isOpen, true); return ( -
-
- -
- - + +
+
+
    + {navLinks.map((link) => { + const gallery = data[link.key] || { nodes: [] }; + return ( +
  • + setIsOpen(false)} + tabIndex={isOpen ? 0 : -1} + to={link.to} + unstyled + > + {link.name} + +
    + {gallery.nodes.map((node, index) => ( + + ))} +
    +
  • + ); + })} +
-
+
setIsOpen(!isOpen)} + /> + ); }; diff --git a/src/components/MenuBar.js b/src/components/MenuBar.js deleted file mode 100644 index 4da6a17..0000000 --- a/src/components/MenuBar.js +++ /dev/null @@ -1,99 +0,0 @@ -import { Link, NavBar, SocialNavLinks } from '@zigurous/react-components'; -import { Link as GatsbyLink } from 'gatsby'; -import PropTypes from 'prop-types'; -import React, { useContext } from 'react'; -import GalleryContext from './GalleryContext'; -import icons from '../icons'; -import { navLinks, socialLinks } from '../links'; -import { MENU_TYPE_GALLERY, MENU_TYPE_LIST, MENU_TYPE_NONE } from '../types/menu'; // prettier-ignore -import { titleCase } from '../utils/formatting'; - -const MenuBar = ({ location, menuType, setMenuType }) => { - const context = useContext(GalleryContext); - const showMenu = menuType === MENU_TYPE_LIST; - const showGallery = menuType === MENU_TYPE_GALLERY; - return ( -
-
- - Adam Graham - - {context.slideIndex === undefined && !fullscreen && ( - - {titleCase(category)} - - )} - {context.slideIndex !== undefined && ( -
- - -
- )} - -
-
- - - -
-
- ); -}; - -MenuBar.propTypes = { - location: PropTypes.object, - menuType: PropTypes.oneOf([ - MENU_TYPE_NONE, - MENU_TYPE_LIST, - MENU_TYPE_GALLERY, - ]), - setMenuType: PropTypes.func, -}; - -export default MenuBar; diff --git a/src/components/MenuGallery.js b/src/components/MenuGallery.js deleted file mode 100644 index c8d9d78..0000000 --- a/src/components/MenuGallery.js +++ /dev/null @@ -1,50 +0,0 @@ -import classNames from 'classnames'; -import PropTypes from 'prop-types'; -import React, { useContext } from 'react'; -import GalleryContext from './GalleryContext'; - -const MenuGallery = ({ onSelect, open }) => { - const context = useContext(GalleryContext); - return ( -
-
- {context.gallery.map((slide, index) => ( - - ))} -
-
- ); -}; - -MenuGallery.propTypes = { - onSelect: PropTypes.func, - open: PropTypes.bool, -}; - -export default MenuGallery; diff --git a/src/components/MenuList.js b/src/components/MenuList.js deleted file mode 100644 index bb9719c..0000000 --- a/src/components/MenuList.js +++ /dev/null @@ -1,43 +0,0 @@ -import { Link } from '@zigurous/react-components'; -import classNames from 'classnames'; -import { Link as GatsbyLink } from 'gatsby'; -import PropTypes from 'prop-types'; -import React from 'react'; -import { navLinks } from '../links'; - -const MenuList = ({ onLink, open }) => { - return ( -
-
    - {navLinks.map((link) => ( -
  • - - {link.name} - -
  • - ))} -
-
- ); -}; - -MenuList.propTypes = { - onLink: PropTypes.func, - open: PropTypes.bool, -}; - -export default MenuList; diff --git a/src/components/Page.js b/src/components/Page.js index 6ddb40f..0ec6002 100644 --- a/src/components/Page.js +++ b/src/components/Page.js @@ -1,25 +1,54 @@ import classNames from 'classnames'; import PropTypes from 'prop-types'; -import React from 'react'; +import React, { useEffect } from 'react'; +import GalleryContext, { useContextState } from './GalleryContext'; import Menu from './Menu'; import Metadata from './Metadata'; +import { SlideProps } from './Slide'; +import { getSessionIndex, setSessionIndex } from '../utils/session'; -const Page = ({ children, className, id, location, metadata }) => ( -
- -
- {children} -
- -
-); +const Page = ({ + category, + children, + className, + hideNavigation = false, + id, + location, + metadata, + slides, +}) => { + const urlParams = new URLSearchParams(location?.search); + const slideIndex = urlParams.has('index') + ? Number.parseInt(urlParams.get('index')) || 0 + : getSessionIndex(category); + const context = useContextState(category, slides, slideIndex); + + useEffect(() => { + setSessionIndex(category, slideIndex); + }, [category, slideIndex]); + + return ( + +
+ + {!hideNavigation && } +
+ {children} +
+
+
+ ); +}; Page.propTypes = { + category: PropTypes.string, children: PropTypes.node, className: PropTypes.string, + hideNavigation: PropTypes.bool, id: PropTypes.string, location: PropTypes.object, metadata: PropTypes.object, + slides: PropTypes.arrayOf(SlideProps), }; export default Page; diff --git a/src/components/Project.js b/src/components/Project.js index f65d1e5..bff0098 100644 --- a/src/components/Project.js +++ b/src/components/Project.js @@ -1,56 +1,64 @@ +import '../styles/project.css'; import { Button, ButtonGroup, EmbeddedYouTube, ImageGallery, Link, ProgressiveImage } from '@zigurous/react-components'; // prettier-ignore import classNames from 'classnames'; import PropTypes from 'prop-types'; import React from 'react'; import { ImageProps } from '../types/image'; -import '../styles/project.css'; const Project = ({ className, project }) => (
-

{project.title}

-
-
- {project.description &&

{project.description}

} +

{project.title}

+ {project.description && ( +

{project.description}

+ )} {project.description_long && project.description_long.map((description) => ( -

{description}

+

+ {description} +

))} -
- {project.details && ( -
-

- {project.details.map((detail, index) => ( - - {index !== 0 &&
} - {detail.key} {detail.value} -
- ))} -

-
- )} - {project.buttons && ( -
+ {project.buttons && ( {project.buttons.map((button) => ( - + ))} + )} +
+ {/* {project.details && ( +
+

+ {project.details.map((detail, index) => ( + + {index !== 0 &&
} + {detail.key} {detail.value} +
+ ))} +

- )} + )} */} {project.sections && project.sections.map((section, index) => (
{section.title && ( -

+

{section.link ? ( {section.title} @@ -58,7 +66,7 @@ const Project = ({ className, project }) => ( ) : ( {section.title} )} -

+

)} {section.mainImage && ( ( height: image.sharp.original.height, src: image.sharp.original.src, }))} - minWidth={128} + minWidth={132} /> )} {section.videos && diff --git a/src/components/ProjectPage.js b/src/components/ProjectPage.js deleted file mode 100644 index 9d66f8e..0000000 --- a/src/components/ProjectPage.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { useState } from 'react'; -import GalleryContext, { useContext } from './GalleryContext'; -import Page from './Page'; -import Project, { ProjectProps } from './Project'; -import { SlideProps } from './Slide'; -import { getSessionIndex } from '../utils/session'; - -const ProjectPage = ({ category, gallery, location, project }) => { - const [slideIndex, setSlideIndex] = useState(getSessionIndex(category)); - const context = useContext(category, gallery, slideIndex, setSlideIndex); - return ( - - - - - - ); -}; - -ProjectPage.propTypes = { - category: PropTypes.string, - gallery: PropTypes.arrayOf(SlideProps), - location: PropTypes.object, - project: ProjectProps, -}; - -export default ProjectPage; diff --git a/src/components/Slide.js b/src/components/Slide.js index 10ec7d5..71e7c1c 100644 --- a/src/components/Slide.js +++ b/src/components/Slide.js @@ -1,10 +1,10 @@ +import '../styles/slide.css'; import { Button, Link, ProgressiveImage } from '@zigurous/react-components'; import classNames from 'classnames'; import { Link as GatsbyLink } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; import { ImageProps } from '../types/image'; -import '../styles/slide.css'; const Slide = ({ className, slide }) => { const offline = typeof navigator !== 'undefined' && !navigator.onLine; @@ -12,7 +12,12 @@ const Slide = ({ className, slide }) => { return (
- + {

{slide.date}

-

{slide.title}

-

{slide.description_short || slide.description}

- - +

{slide.title}

+

+ {slide.description_short || slide.description} +

+ +
diff --git a/src/components/index.js b/src/components/index.js index b2bd286..5f7aa19 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,8 +1,6 @@ export { default as Gallery } from './Gallery'; export { default as GalleryContext } from './GalleryContext'; -export { default as GalleryPage } from './GalleryPage'; export { default as Menu } from './Menu'; export { default as Page } from './Page'; export { default as Project, ProjectProps } from './Project'; -export { default as ProjectPage } from './ProjectPage'; export { default as Slide, SlideProps } from './Slide'; diff --git a/src/data/art.json b/src/data/art.json index fe6b712..f7b702b 100644 --- a/src/data/art.json +++ b/src/data/art.json @@ -26,7 +26,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/open?id=1oLkn4-Zg5VK-ZHi1LO3SvlIHqf6Tzh_n" + "link": "https://drive.google.com/file/d/1WG-eI8QvhrFCTdRJlGesNV7vs7nddq6L/view?usp=drive_link" }, { "name": "Source Code", @@ -72,7 +72,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/open?id=1qB9JmZHepVmPDG6hM04gqMZAEV7e5miZ" + "link": "https://drive.google.com/file/d/14FD_603deu6C40w49c3u3vpvZOD9sDkA/view?usp=drive_link" }, { "name": "Source Code", @@ -119,7 +119,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/open?id=1TJlHyaSILvaviyt8g0tODV9eBN7iC0HJ" + "link": "https://drive.google.com/file/d/1Sr5wxTQrm2jqemSOTLa0xlzYCEKysixm/view?usp=drive_link" }, { "name": "Source Code", @@ -164,7 +164,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/drive/folders/1VrMFBTO4-DQbiazHRnyDYpN_LQCaGETv?usp=sharing" + "link": "https://drive.google.com/drive/folders/1uJo0g6eBtnyHLSCCmUatIQl80VX0UY_9?usp=drive_link" }, { "name": "Source Code", diff --git a/src/data/games.json b/src/data/games.json index d848105..caa5130 100644 --- a/src/data/games.json +++ b/src/data/games.json @@ -25,7 +25,7 @@ "sections": [ { "title": "Beta", - "link": "https://drive.google.com/open?id=0BzUWUmwjB6l2ZW00Z3poTXE3UVU", + "link": "https://drive.google.com/file/d/1A28Dm-oLWkOgr3L49f46jjcyovBnfp-G/view?usp=drive_link", "mainVideo": "JtCg7oB67wY", "gallery": [ "../images/screenshots/the-wandering-dark-screenshot-01.jpg", @@ -39,7 +39,7 @@ }, { "title": "Alpha", - "link": "https://drive.google.com/open?id=0BzUWUmwjB6l2QUd2RTNOYnI3TWs", + "link": "https://drive.google.com/file/d/1smEETzdTMVHylPhxZzS2nHsCTbrBYddo/view?usp=drive_link", "mainVideo": "EF8ywk3Qq48", "gallery": [ "../images/screenshots/project-dream-screenshot-01.jpg", @@ -55,7 +55,7 @@ "category": "games", "date": "November 2014", "title": "Boss Rush", - "description": "Boss Rush is a top-down, twin-stick shooter consisting of nothing but boss fights. Developed as a university project, the objective was to create a vertical slice of a complete game, thus it consists of one action-packed boss fight – the Spider Tank.", + "description": "Boss Rush is a top-down twin-stick shooter consisting of nothing but boss fights. Developed as a university project, the objective was to create a vertical slice of a complete game, thus it consists of one action-packed boss fight – the Spider Tank.", "image": "../images/banners/boss-rush-painting.jpg", "imageAltText": "Boss Rush Painting", "details": [ @@ -120,7 +120,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/open?id=0BzUWUmwjB6l2b2M2b00zR25xc28" + "link": "https://drive.google.com/file/d/1wfnwkmW56U3aYZ4cnrMFPZOIpwjYz-52/view?usp=drive_link" } ], "sections": [ @@ -241,7 +241,7 @@ "buttons": [ { "name": "Download", - "link": "https://drive.google.com/open?id=1RHmvqyU1B5gXo0hb18n7ahSxnKQeSTra" + "link": "https://drive.google.com/file/d/1VOryTrKiH_2HlJKeO_CmzNbVjCrU7wRr/view?usp=drive_link" }, { "name": "Source Code", diff --git a/src/data/presentations.json b/src/data/presentations.json index 2fcc736..510346e 100644 --- a/src/data/presentations.json +++ b/src/data/presentations.json @@ -21,7 +21,7 @@ "buttons": [ { "name": "View Deck", - "link": "https://drive.google.com/open?id=1j_bFqhJ2f6z17afxccK0hWlX6mMVkFGj" + "link": "https://drive.google.com/file/d/1mHUDzD7EzfrRR03Z_1WNsduszsIUj4ny/view?usp=drive_link" } ], "sections": [ @@ -106,7 +106,7 @@ "buttons": [ { "name": "View Deck", - "link": "https://drive.google.com/open?id=0BzUWUmwjB6l2X0RCeERwRm9hejA" + "link": "https://drive.google.com/file/d/1nw_EMTZB47svcDjgCPaSAAPE84gNYqfK/view?usp=drive_link" } ], "sections": [ @@ -135,7 +135,7 @@ { "id": "3d-application-development-overview", "category": "presentations", - "title": "3D Application Development Overview", + "title": "3D Application Development", "date": "April 2016", "description_short": "A presentation to teach introductory concepts for 3D application development covering topics across art, design, and tech. The presentation serves as a starting point from which creators can continue to grow their knowledge.", "description_long": [ @@ -143,7 +143,7 @@ "The deck is grounded in the Unity engine to provide real, applicable examples of the concepts. Adam presented this deck to around 30 software professionals who were interested in learning more." ], "image": "../images/banners/3d-application-development-overview-painting.jpg", - "imageAltText": "3D Application Development Overview Painting", + "imageAltText": "3D Application Development Painting", "details": [ { "key": "ROLE —", @@ -161,7 +161,7 @@ "buttons": [ { "name": "View Deck", - "link": "https://drive.google.com/open?id=0BzUWUmwjB6l2MnYzWk00VzZWY1k" + "link": "https://drive.google.com/file/d/1r2DCuz7PckbaD0CH4NWlKIK1X5-po9MI/view?usp=drive_link" } ], "sections": [ diff --git a/src/data/tech.json b/src/data/tech.json index 2c25eb4..f763ffa 100644 --- a/src/data/tech.json +++ b/src/data/tech.json @@ -47,9 +47,9 @@ "category": "tech", "date": "September 2016", "title": "RockstAR", - "description_short": "RockstAR is an interactive AR experience demoed at Solstice FWD. By coupling the power of Microsoft's HoloLens, Philips Hue Lightbulbs, and a series of stage technologies, users are given the ability to customize a live rockband experience.", + "description_short": "RockstAR is an interactive AR experience demoed at Solstice FWD. By coupling the power of Microsoft's HoloLens, Philips Hue Lightbulbs, and a series of stage technologies, users are given the ability to customize a live rock band experience.", "description_long": [ - "Augmented reality is making it possible to merge virtual objects into the physical world around us. To explore the possibilities, Adam and fellow Solstice engineers built RockstAR, an interactive AR experience demoed at Solstice FWD. By coupling the power of Microsoft's HoloLens, Philips Hue Lightbulbs, and a series of stage technologies, users are given the ability to customize a live rockband experience.", + "Augmented reality is making it possible to merge virtual objects into the physical world around us. To explore the possibilities, Adam and fellow Solstice engineers built RockstAR, an interactive AR experience demoed at Solstice FWD. By coupling the power of Microsoft's HoloLens, Philips Hue Lightbulbs, and a series of stage technologies, users are given the ability to customize a live rock band experience.", "You can choose your song and color theme preferences using digital menus. Once you're ready to go, the band plays your song as you set the stage for your audience and let the lights groove to the beat. You might look a little odd pointing to things no one else can see, but the joke's on them - you're the star of the show!" ], "image": "../images/banners/rockstar-painting.jpg", diff --git a/src/data/websites.json b/src/data/websites.json index a6e1be3..8b5d89a 100644 --- a/src/data/websites.json +++ b/src/data/websites.json @@ -151,8 +151,8 @@ "category": "websites", "date": "June 2016", "title": "Ashantis Jones", - "description": "Ashantis is a native Clevelander that entertains and creates memorable events through detail oriented design and calculated execution. Adam designed Ashantis's portfolio to assist in her job hunt as a graduate of the BFA Theatre Management at DePaul University looking for opportunities in the event management space.", - "description_short": "Ashantis is a native Clevelander that entertains and creates memorable events through detail oriented design and calculated execution.", + "description": "Ashantis is an event planner from Cleaveland that entertains and creates memorable events through detail oriented design and calculated execution. Adam designed Ashantis's portfolio to assist in her job hunt as a graduate of the BFA Theatre Management at DePaul University looking for opportunities in the event management space.", + "description_short": "Ashantis is an event planner from Cleaveland that entertains and creates memorable events through detail oriented design and calculated execution.", "image": "../images/banners/ashantis-jones-painting.jpg", "imageAltText": "Ashantis Jones Painting", "details": [ diff --git a/src/hooks/useElementSize.js b/src/hooks/useElementSize.js new file mode 100644 index 0000000..e722059 --- /dev/null +++ b/src/hooks/useElementSize.js @@ -0,0 +1,34 @@ +import { useEffect, useLayoutEffect, useState } from 'react'; +import { clamp } from '../utils/clamp'; + +export function useElementSize(targetRef) { + const getSize = () => { + return { + width: targetRef.current?.offsetWidth ?? 0, + height: targetRef.current?.offsetHeight ?? 0, + }; + }; + + const [size, setSize] = useState(getSize); + const [scale, setScale] = useState(1); + + const handleResize = () => { + const size = getSize(); + setSize(getSize()); + if (typeof window !== 'undefined') { + setScale(clamp((window.innerWidth * 0.8) / size.width, 1, 1.309)); + } + }; + + useEffect(() => { + if (typeof window === 'undefined') return; + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + useLayoutEffect(() => { + handleResize(); + }, []); + + return [size, scale]; +} diff --git a/src/icons.js b/src/icons.js deleted file mode 100644 index 1899f9c..0000000 --- a/src/icons.js +++ /dev/null @@ -1,17 +0,0 @@ -import chevronLeftIcon from './images/icons/chevron-left.svg'; -import chevronRightIcon from './images/icons/chevron-right.svg'; -import galleryIcon from './images/icons/fullscreen.svg'; -import galleryOpenIcon from './images/icons/fullscreen-exit.svg'; -import menuIcon from './images/icons/menu.svg'; -import menuOpenIcon from './images/icons/menu-open.svg'; - -const icons = { - chevronLeft: chevronLeftIcon, - chevronRight: chevronRightIcon, - gallery: galleryIcon, - galleryOpen: galleryOpenIcon, - menu: menuIcon, - menuOpen: menuOpenIcon, -}; - -export default icons; diff --git a/src/images/banners/blackhole-painting.jpg b/src/images/banners/blackhole-painting.jpg index e630af0..3d2eb73 100644 Binary files a/src/images/banners/blackhole-painting.jpg and b/src/images/banners/blackhole-painting.jpg differ diff --git a/src/images/banners/canvas-painting.png b/src/images/banners/canvas-painting.png index 2323b00..4934ff1 100644 Binary files a/src/images/banners/canvas-painting.png and b/src/images/banners/canvas-painting.png differ diff --git a/src/images/icons/chevron-left.svg b/src/images/icons/chevron-left.svg index f595736..7c24ee2 100644 --- a/src/images/icons/chevron-left.svg +++ b/src/images/icons/chevron-left.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/chevron-right.svg b/src/images/icons/chevron-right.svg index 406cc67..fe99ee0 100644 --- a/src/images/icons/chevron-right.svg +++ b/src/images/icons/chevron-right.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/fullscreen-exit.svg b/src/images/icons/fullscreen-exit.svg index b375d15..435b859 100644 --- a/src/images/icons/fullscreen-exit.svg +++ b/src/images/icons/fullscreen-exit.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/fullscreen.svg b/src/images/icons/fullscreen.svg index 048d113..93232c7 100644 --- a/src/images/icons/fullscreen.svg +++ b/src/images/icons/fullscreen.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/image.svg b/src/images/icons/image.svg index 4922e3f..d7b6f6f 100644 --- a/src/images/icons/image.svg +++ b/src/images/icons/image.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/info.svg b/src/images/icons/info.svg index c5c1098..5874f78 100644 --- a/src/images/icons/info.svg +++ b/src/images/icons/info.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/menu-open.svg b/src/images/icons/menu-open.svg index d500435..7558f79 100644 --- a/src/images/icons/menu-open.svg +++ b/src/images/icons/menu-open.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/images/icons/menu.svg b/src/images/icons/menu.svg index 7d9c95c..59dd94b 100644 --- a/src/images/icons/menu.svg +++ b/src/images/icons/menu.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/links.js b/src/links.js index dfc6a84..2a82ec1 100644 --- a/src/links.js +++ b/src/links.js @@ -1,57 +1,70 @@ +export const baseUri = 'https://adamgraham.github.io'; +export const email = 'mailto:adam@zigurous.com'; +export const resume = 'https://docs.google.com/document/d/1qLHBV7Ry11O-pd0XC3HvALBIKJ8YDBmSafP0Z5c4dww/edit?usp=drive_link'; // prettier-ignore +export const github = 'https://github.com/adamgraham'; +export const linkedIn = 'https://www.linkedin.com/in/adamzigurous'; +export const instagram = 'http://instagram.com/adam.zigurous'; +export const twitter = 'https://twitter.com/zigurous'; + export const navLinks = [ { + key: 'games', name: 'Games', to: '/games', }, { - name: 'Visual Art', - to: '/art', + key: 'websites', + name: 'Websites', + to: '/websites', }, { - name: 'Web Design', - to: '/websites', + key: 'art', + name: 'Visual Art', + to: '/art', }, { + key: 'tech', name: 'Emerging Tech', to: '/tech', }, { + key: 'presentations', name: 'Presentations', to: '/presentations', }, - { - name: 'Software', - to: 'https://github.com/adamgraham', - rightIcon: 'open_in_new', - ElementType: 'a', - external: true, - }, + // { + // name: 'Software', + // to: github, + // rightIcon: 'open_in_new', + // ElementType: 'a', + // external: true, + // }, ]; export const socialLinks = [ { key: 'github', name: 'GitHub', - url: 'https://github.com/adamgraham', + url: github, }, { key: 'linkedIn', name: 'LinkedIn', - url: 'https://www.linkedin.com/in/adamzigurous', + url: linkedIn, }, { key: 'instagram', name: 'Instagram', - url: 'http://instagram.com/adam.zigurous', - }, - { - key: 'twitter', - name: 'Twitter', - url: 'https://twitter.com/Zigurous', + url: instagram, }, + // { + // key: 'twitter', + // name: 'Twitter', + // url: twitter, + // }, { key: 'email', name: 'Email', - url: 'mailto:adam@zigurous.com', + url: email, }, ]; diff --git a/src/pages/art.js b/src/pages/art.js index e674fce..c0ac13a 100644 --- a/src/pages/art.js +++ b/src/pages/art.js @@ -1,12 +1,13 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { GalleryPage, SlideProps } from '../components'; +import { Gallery, Page, SlideProps } from '../components'; +import { baseUri } from '../links'; export const query = graphql` query Art { json: allArtJson { - gallery: nodes { + slides: nodes { id: jsonId category date @@ -30,14 +31,26 @@ export const query = graphql` `; const Art = ({ data, location }) => { - const { gallery } = data.json; - return ; + const { slides } = data.json; + return ( + + + + ); }; Art.propTypes = { data: PropTypes.shape({ json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/art/{ArtJson.jsonId}.js b/src/pages/art/{ArtJson.jsonId}.js index b623a7c..39d79b8 100644 --- a/src/pages/art/{ArtJson.jsonId}.js +++ b/src/pages/art/{ArtJson.jsonId}.js @@ -1,7 +1,8 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { ProjectPage, ProjectProps, SlideProps } from '../../components'; +import { Page, Project, ProjectProps, SlideProps } from '../../components'; +import { baseUri } from '../../links'; export const query = graphql` query ($id: String!) { @@ -58,7 +59,7 @@ export const query = graphql` } } json: allArtJson { - gallery: nodes { + slides: nodes { id category title @@ -82,14 +83,21 @@ export const query = graphql` const Art = ({ data, location }) => { const { project } = data; - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/${project.category}/${project.id}`, + title: `Adam Graham • ${project.title}`, + description: project.description_short || project.description, + image: project.image && project.image.sharp.original.src, + }} + > + + ); }; @@ -97,7 +105,7 @@ Art.propTypes = { data: PropTypes.shape({ project: ProjectProps, json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/games.js b/src/pages/games.js index e3713bc..c15db1a 100644 --- a/src/pages/games.js +++ b/src/pages/games.js @@ -1,12 +1,13 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { GalleryPage, SlideProps } from '../components'; +import { Gallery, Page, SlideProps } from '../components'; +import { baseUri } from '../links'; export const query = graphql` query Games { json: allGamesJson { - gallery: nodes { + slides: nodes { id: jsonId category date @@ -30,14 +31,26 @@ export const query = graphql` `; const Games = ({ data, location }) => { - const { gallery } = data.json; - return ; + const { slides } = data.json; + return ( + + + + ); }; Games.propTypes = { data: PropTypes.shape({ json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/games/{GamesJson.jsonId}.js b/src/pages/games/{GamesJson.jsonId}.js index 11c7e51..4d6ef94 100644 --- a/src/pages/games/{GamesJson.jsonId}.js +++ b/src/pages/games/{GamesJson.jsonId}.js @@ -1,7 +1,8 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { ProjectPage, ProjectProps, SlideProps } from '../../components'; +import { Page, Project, ProjectProps, SlideProps } from '../../components'; +import { baseUri } from '../../links'; export const query = graphql` query ($id: String!) { @@ -58,7 +59,7 @@ export const query = graphql` } } json: allGamesJson { - gallery: nodes { + slides: nodes { id category title @@ -82,14 +83,21 @@ export const query = graphql` const Game = ({ data, location }) => { const { project } = data; - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/${project.category}/${project.id}`, + title: `Adam Graham • ${project.title}`, + description: project.description_short || project.description, + image: project.image && project.image.sharp.original.src, + }} + > + + ); }; @@ -97,7 +105,7 @@ Game.propTypes = { data: PropTypes.shape({ project: ProjectProps, json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/index.js b/src/pages/index.js index 9c32734..dcf4e9d 100644 --- a/src/pages/index.js +++ b/src/pages/index.js @@ -1,28 +1,81 @@ -import { Button, Link } from '@zigurous/react-components'; +import { Button, ButtonGroup, Link } from '@zigurous/react-components'; import { Link as GatsbyLink } from 'gatsby'; -import React from 'react'; +import React, { useRef } from 'react'; +import { Page } from '../components'; +import { useElementSize } from '../hooks/useElementSize'; +import { baseUri, resume } from '../links'; const Home = () => { + const ref = useRef(); + const [_, scale] = useElementSize(ref); return ( -
-
- Hello, my name is -

Adam Graham

-

- Adam is a professional software engineer and game developer with 10+ - years of experience. He founded the indie game studio{' '} - - Zigurous - {' '} - in 2021 and is currently working on his first major release alongside - other enterprise projects. Adam's work is inspired by a passion for - art, design, and engineering. -

- - - -
-
+ +
+
+ Hello! 👋 My name is +

Adam Graham

+

+ I'm a software engineer and game developer inspired by the blending + of art, design, and engineering to create best in class user + experiences. I love working on design centric projects across web + and mobile having been a tech lead and UX engineer for many Fortune + 500 clients over the past 10 years. +

+ + + + + {/* + + */} + + + + +
+
+
); }; diff --git a/src/pages/presentations.js b/src/pages/presentations.js index 13fbac0..ed7fd57 100644 --- a/src/pages/presentations.js +++ b/src/pages/presentations.js @@ -1,12 +1,13 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { GalleryPage, SlideProps } from '../components'; +import { Gallery, Page, SlideProps } from '../components'; +import { baseUri } from '../links'; export const query = graphql` query Presentations { json: allPresentationsJson { - gallery: nodes { + slides: nodes { id: jsonId category date @@ -30,20 +31,26 @@ export const query = graphql` `; const Presentations = ({ data, location }) => { - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/presentations`, + title: 'Adam Graham • Presentations', + }} + > + + ); }; Presentations.propTypes = { data: PropTypes.shape({ json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/presentations/{PresentationsJson.jsonId}.js b/src/pages/presentations/{PresentationsJson.jsonId}.js index 0456e0e..68ab693 100644 --- a/src/pages/presentations/{PresentationsJson.jsonId}.js +++ b/src/pages/presentations/{PresentationsJson.jsonId}.js @@ -1,7 +1,8 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { ProjectPage, ProjectProps, SlideProps } from '../../components'; +import { Page, Project, ProjectProps, SlideProps } from '../../components'; +import { baseUri } from '../../links'; export const query = graphql` query ($id: String!) { @@ -58,7 +59,7 @@ export const query = graphql` } } json: allPresentationsJson { - gallery: nodes { + slides: nodes { id category title @@ -82,14 +83,21 @@ export const query = graphql` const Presentation = ({ data, location }) => { const { project } = data; - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/${project.category}/${project.id}`, + title: `Adam Graham • ${project.title}`, + description: project.description_short || project.description, + image: project.image && project.image.sharp.original.src, + }} + > + + ); }; @@ -97,7 +105,7 @@ Presentation.propTypes = { data: PropTypes.shape({ project: ProjectProps, json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/software.js b/src/pages/software.js index a182174..c2e36e0 100644 --- a/src/pages/software.js +++ b/src/pages/software.js @@ -1,9 +1,10 @@ import { useEffect } from 'react'; +import { github } from '../links'; const Software = () => { useEffect(() => { if (typeof window !== 'undefined') { - window.open('https://github.com/adamgraham', '_blank'); + window.open(github, '_blank'); window.history.back(); } }, []); diff --git a/src/pages/tech.js b/src/pages/tech.js index ee7417d..710ff11 100644 --- a/src/pages/tech.js +++ b/src/pages/tech.js @@ -1,12 +1,13 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { GalleryPage, SlideProps } from '../components'; +import { Gallery, Page, SlideProps } from '../components'; +import { baseUri } from '../links'; export const query = graphql` query Tech { json: allTechJson { - gallery: nodes { + slides: nodes { id: jsonId category date @@ -30,14 +31,26 @@ export const query = graphql` `; const Tech = ({ data, location }) => { - const { gallery } = data.json; - return ; + const { slides } = data.json; + return ( + + + + ); }; Tech.propTypes = { data: PropTypes.shape({ json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/tech/{TechJson.jsonId}.js b/src/pages/tech/{TechJson.jsonId}.js index 04e9db4..6e9e29b 100644 --- a/src/pages/tech/{TechJson.jsonId}.js +++ b/src/pages/tech/{TechJson.jsonId}.js @@ -1,7 +1,8 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { ProjectPage, ProjectProps, SlideProps } from '../../components'; +import { Page, Project, ProjectProps, SlideProps } from '../../components'; +import { baseUri } from '../../links'; export const query = graphql` query ($id: String!) { @@ -58,7 +59,7 @@ export const query = graphql` } } json: allTechJson { - gallery: nodes { + slides: nodes { id category title @@ -82,14 +83,21 @@ export const query = graphql` const Tech = ({ data, location }) => { const { project } = data; - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/${project.category}/${project.id}`, + title: `Adam Graham • ${project.title}`, + description: project.description_short || project.description, + image: project.image && project.image.sharp.original.src, + }} + > + + ); }; @@ -97,7 +105,7 @@ Tech.propTypes = { data: PropTypes.shape({ project: ProjectProps, json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/websites.js b/src/pages/websites.js index c833fcb..7601322 100644 --- a/src/pages/websites.js +++ b/src/pages/websites.js @@ -1,12 +1,13 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { GalleryPage, SlideProps } from '../components'; +import { Gallery, Page, SlideProps } from '../components'; +import { baseUri } from '../links'; export const query = graphql` query Websites { json: allWebsitesJson { - gallery: nodes { + slides: nodes { id: jsonId category date @@ -30,16 +31,26 @@ export const query = graphql` `; const Websites = ({ data, location }) => { - const { gallery } = data.json; + const { slides } = data.json; return ( - + + + ); }; Websites.propTypes = { data: PropTypes.shape({ json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/pages/websites/{WebsitesJson.jsonId}.js b/src/pages/websites/{WebsitesJson.jsonId}.js index 1a7294e..d12f5e6 100644 --- a/src/pages/websites/{WebsitesJson.jsonId}.js +++ b/src/pages/websites/{WebsitesJson.jsonId}.js @@ -1,7 +1,8 @@ import { graphql } from 'gatsby'; import PropTypes from 'prop-types'; import React from 'react'; -import { ProjectPage, ProjectProps, SlideProps } from '../../components'; +import { Page, Project, ProjectProps, SlideProps } from '../../components'; +import { baseUri } from '../../links'; export const query = graphql` query ($id: String!) { @@ -58,7 +59,7 @@ export const query = graphql` } } json: allWebsitesJson { - gallery: nodes { + slides: nodes { id category title @@ -82,14 +83,21 @@ export const query = graphql` const Website = ({ data, location }) => { const { project } = data; - const { gallery } = data.json; + const { slides } = data.json; return ( - + metadata={{ + url: `${baseUri}/${project.category}/${project.id}`, + title: `Adam Graham • ${project.title}`, + description: project.description_short || project.description, + image: project.image && project.image.sharp.original.src, + }} + > + + ); }; @@ -97,7 +105,7 @@ Website.propTypes = { data: PropTypes.shape({ project: ProjectProps, json: PropTypes.shape({ - gallery: PropTypes.arrayOf(SlideProps), + slides: PropTypes.arrayOf(SlideProps), }), }), location: PropTypes.object, diff --git a/src/styles/gallery.css b/src/styles/gallery.css index fee8e38..5252f4a 100644 --- a/src/styles/gallery.css +++ b/src/styles/gallery.css @@ -1,25 +1,27 @@ -.page > .gallery { - padding-bottom: 0; -} - .gallery { position: relative; display: flex; justify-content: space-between; align-items: center; - padding-bottom: 0; - width: 100vw; - height: calc(100vh - 80px); - overflow: hidden; + width: 100%; + min-height: calc(100vh - var(--header-height, 80px)); +} + +.gallery__slides { + position: relative; + flex: 1 1 80%; + margin-top: 3rem; + margin-bottom: 6rem; } .gallery__button { display: flex; justify-content: center; align-items: center; - width: 10%; + flex: 0 0 10%; min-width: 48px; height: 100%; + min-height: calc(100vh - var(--header-height, 80px)); font-size: 64px; } @@ -31,84 +33,48 @@ right: 0; } -.gallery__button.info { - position: absolute; - top: 0; - right: 0; - width: 10vw; - height: 10vw; - min-width: 80px; - min-height: 80px; +.gallery__button > svg { + fill: var(--color-foreground); } -.gallery__slides { - position: relative; - display: flex; - justify-content: center; - align-content: center; - flex-grow: 1; - width: 80%; - height: 100%; +.gallery--vertical .slide { + flex-direction: column; + align-items: flex-start; } -@media (max-width: 1365px) { - .gallery__slides { - margin-left: 16px; - margin-right: 16px; - } +.gallery--vertical .slide__text-wrapper { + width: 100%; + max-width: 100%; + margin-left: 0; + margin-top: 2rem; + transition: none; +} + +.gallery--vertical .slide__text-container { + width: 100%; + min-width: 100%; +} + +.gallery--vertical .slide__image > img { + aspect-ratio: 2.35; } @media (max-width: 767px) { .gallery__slides { - margin-left: 8px; - margin-right: 8px; + margin-left: 16px; + margin-right: 16px; } } @media (max-width: 576px) { - .gallery__button.left, - .gallery__button.right { - display: none; - } - .gallery__slides { margin-left: auto; margin-right: auto; padding-left: 16px; padding-right: 16px; } -} - -@media (min-height: 768px) and (min-width: 768px) { - .gallery--hide-info .slide__text-wrapper { - width: 0; - margin-left: 0; - opacity: 0; - pointer-events: none; - } - - .gallery--hide-info.gallery--vertical-layout .slide__image-wrapper { - max-height: 100%; - } - - .gallery--hide-info.gallery--vertical-layout .slide__text-wrapper { - display: none; - } -} - -@media (max-height: 767px) { - .gallery--vertical-layout .slide__image-wrapper, - .gallery--vertical-layout .gallery__button.info { - display: none; - } - .gallery--vertical-layout .slide__text-wrapper { - margin-top: 0; - } -} - -@media (max-width: 767px) { - .gallery--vertical-layout .gallery__button.info { + .gallery__button { display: none; } } diff --git a/src/styles/global.css b/src/styles/global.css index ff3a1f4..6bd803f 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1,15 +1,15 @@ html { - color-scheme: dark; - font-size: 1.125rem; + font-size: 1.25rem; } body { - background-color: #0d1117; overflow-x: hidden; overflow-y: auto; } html, body { + font-family: "Inter", ui-sans-serif, system-ui, sans-serif; + letter-spacing: -0.025em; scroll-behavior: smooth; } @@ -19,13 +19,83 @@ html, body { } } -[data-theme="dark"], -[data-theme="dark"] * { - --color-foreground: #c9d1d9; - --color-foreground-muted: #8b949e; - --color-default: #262a30; - --color-default-emphasis: #31363c; - --color-default-subtle: #2d3137; - --color-on-default: #c9d1d9; - --rgb-default: 38, 42, 48; +.page#cover .btn .icon { + transform: translateY(1px); +} + +.shadow-button { + color: var(--color-default); + background-color: var(--color-background); + box-shadow: rgba(14, 63, 126, 0.06) 0px 0px 0px 1px, rgba(42, 51, 70, 0.03) 0px 1px 1px -0.5px, rgba(42, 51, 70, 0.04) 0px 2px 2px -1px, rgba(42, 51, 70, 0.04) 0px 3px 3px -1.5px, rgba(42, 51, 70, 0.03) 0px 5px 5px -2.5px, rgba(42, 51, 70, 0.03) 0px 10px 10px -5px, rgba(42, 51, 70, 0.03) 0px 24px 24px -8px; + font-size: 16px; + font-weight: 600; +} + +.shadow-button:not(:disabled):hover, +.shadow-button:not(:disabled):active, +.shadow-button:not(:disabled).active { + background-color: var(--color-background); +} + +[data-theme="dark"] .shadow-button, +[data-theme="high-contrast"] .shadow-button { + background-color: var(--color-default-subtle); + box-shadow: none; +} + +[data-theme="dark"] .shadow-button:not(:disabled):hover, +[data-theme="dark"] .shadow-button:not(:disabled):active, +[data-theme="dark"] .shadow-button:not(:disabled).active, +[data-theme="high-contrast"] .shadow-button:not(:disabled):hover, +[data-theme="high-contrast"] .shadow-button:not(:disabled):active, +[data-theme="high-contrast"] .shadow-button:not(:disabled).active { + background-color: var(--color-surface-9); +} + +.ReactModal__Overlay { + z-index: 2000 !important; +} + +.image-gallery__lightbox .ril__outer { + background-color: rgba(255, 255, 255, 0.9); +} + +.image-gallery__lightbox .ril-image-current.ril__image { + border-radius: 1rem; +} + +.image-gallery__lightbox .ril__navButtonPrev { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M14.71 6.71c-.39-.39-1.02-.39-1.41 0L8.71 11.3c-.39.39-.39 1.02 0 1.41l4.59 4.59c.39.39 1.02.39 1.41 0 .39-.39.39-1.02 0-1.41L10.83 12l3.88-3.88c.39-.39.38-1.03 0-1.41z' fill='gray' /%3E%3C/svg%3E"); +} + +.image-gallery__lightbox .ril__navButtonNext { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M9.29 6.71c-.39.39-.39 1.02 0 1.41L13.17 12l-3.88 3.88c-.39.39-.39 1.02 0 1.41.39.39 1.02.39 1.41 0l4.59-4.59c.39-.39.39-1.02 0-1.41L10.7 6.7c-.38-.38-1.02-.38-1.41.01z' fill='gray' /%3E%3C/svg%3E"); +} + +.image-gallery__lightbox .ril__closeButton { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 72 72' %3E%3Cpath d='M 19 15 C 17.977 15 16.951875 15.390875 16.171875 16.171875 C 14.609875 17.733875 14.609875 20.266125 16.171875 21.828125 L 30.34375 36 L 16.171875 50.171875 C 14.609875 51.733875 14.609875 54.266125 16.171875 55.828125 C 16.951875 56.608125 17.977 57 19 57 C 20.023 57 21.048125 56.609125 21.828125 55.828125 L 36 41.65625 L 50.171875 55.828125 C 51.731875 57.390125 54.267125 57.390125 55.828125 55.828125 C 57.391125 54.265125 57.391125 51.734875 55.828125 50.171875 L 41.65625 36 L 55.828125 21.828125 C 57.390125 20.266125 57.390125 17.733875 55.828125 16.171875 C 54.268125 14.610875 51.731875 14.609875 50.171875 16.171875 L 36 30.34375 L 21.828125 16.171875 C 21.048125 15.391875 20.023 15 19 15 z' fill='gray' %3E%3C/path%3E%3C/svg%3E"); +} + +.image-gallery__lightbox .ril__navButtons { + background-size: 48px; +} + +.image-gallery__lightbox .ril__closeButton { + background-size: 32px; + width: 10vw; +} + +.image-gallery__lightbox .ril__navButtons, +.image-gallery__lightbox .ril__closeButton { + opacity: 1; +} + +@media (hover: hover) { + .social-nav-links__item:hover { + transform: none; + } + + .social-nav-links__list:hover .social-nav-links__item { + opacity: var(--opacity-disabled, 0.38); + } } diff --git a/src/styles/header.css b/src/styles/header.css new file mode 100644 index 0000000..855d51e --- /dev/null +++ b/src/styles/header.css @@ -0,0 +1,111 @@ +.header { + display: flex; + justify-content: space-between; + align-items: center; + position: sticky; + top: 0; + width: 100%; + height: var(--header-height, 80px); + background-color: rgba(240, 242, 245, 0.8); + backdrop-filter: blur(12px); + border-bottom: 1px solid var(--color-border); + z-index: var(--z-index-max, 1100); +} + +[data-theme="dark"] .header { + background-color: rgba(25, 29, 35, 0.8); +} + +[data-theme="high-contrast"] .header { + background-color: rgba(22, 24, 28, 0.8); +} + +.header__container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.header__container > div { + display: flex; + align-items: center; + flex: none; +} + +.header .logo { + margin: 0; + font-weight: 700; + white-space: nowrap; + transition: color 200ms; +} + +.header .logo > svg { + background-color: var(--color-foreground); + border-radius: 6px; + padding: 2px; +} + +[data-theme="dark"] .header .logo > svg, +[data-theme="high-contrast"] .header .logo > svg { + background-color: var(--color-background); +} + +.header .logo > svg > polygon { + fill: var(--color-white, white); +} + +.header .navbar { + margin-left: 16px; + margin-right: 16px; +} + +.header .navbar li { + padding: 0; +} + +.header .navbar li a { + color: inherit; + border: none; + font-size: 0.875rem; + font-weight: 600; + opacity: var(--opacity-inactive, 0.62); + transition: color 100ms, opacity 100ms; +} + +.header .navbar li a > .icon { + color: inherit; + transition: color 100ms, opacity 100ms; +} + +.header .navbar li a.active, +.header .navbar li a:hover { + opacity: 1; +} + +.header .navbar li a:hover, +.header .navbar li a.active:hover { + background-color: var(--color-default-subtle); +} + +.header__menu-button svg { + fill: var(--color-foreground); +} + +@media (min-width: 576px) { + .header__container { + padding-left: 32px; + padding-right: 32px; + } +} + +@media (max-width: 424px) { + .header .social-nav-links { + display: none; + } +} + +@media (max-width: 1023px) { + .header .navbar { + display: none; + } +} diff --git a/src/styles/menu.css b/src/styles/menu.css index d08af2a..6c43aa7 100644 --- a/src/styles/menu.css +++ b/src/styles/menu.css @@ -1,203 +1,114 @@ -.app-menu { +.menu { position: fixed; + top: 0; bottom: 0; left: 0; right: 0; - width: 100vw; - max-width: 100vw; - max-height: 100vh; - pointer-events: none; + z-index: var(--z-index-fixed, 1030); + background-color: var(--color-background); + overflow-x: hidden; + overflow-y: auto; } -.app-menu__container { - width: 100%; - height: 100vh; - transform: translateY(calc(100vh - 80px)); - transition: transform 400ms; - background-color: var(--color-surface-2); +.menu.open { pointer-events: all; + visibility: visible; } -.app-menu--fullscreen .app-menu__container { - transform: translateY(0); -} - -.app-menu__header { - position: relative; - display: flex; - justify-content: space-between; - align-items: center; - top: 0; - left: 0; - right: 0; - width: 100%; - height: 80px; - padding: 24px 32px; -} - -.app-menu__header > div { - display: flex; - align-items: center; - flex: none; -} - -.app-menu .logo { - margin: 0; - font-weight: 600; - white-space: nowrap; - transition: color 200ms; -} - -.app-menu .logo:hover { - color: var(--color-white, white); -} - -.app-menu .navbar { - margin-left: 16px; - margin-right: 16px; -} - -.app-menu .navbar li { - padding: 0; -} - -.app-menu .navbar li a { - color: inherit; - border: none; - opacity: 0.54; - transition: color 200ms, opacity 200ms; -} - -.app-menu .navbar li a > .icon { - color: inherit; - transition: color 200ms, opacity 200ms; -} - -.app-menu .navbar li a.active, -.app-menu .navbar li a:hover { - opacity: 1; -} - -.app-menu .navbar li a.active:hover { - background-color: var(--color-default-subtle); -} - -.app-menu__body { - position: relative; - width: 100%; - height: calc(100vh - 80px); - padding: 32px; - overflow-x: hidden; - overflow-y: auto; -} - -.app-menu__menu-view, -.app-menu__gallery-view { - position: absolute; - width: calc(100% - 64px); - height: calc(100% - 64px); +.menu.closed { pointer-events: none; visibility: hidden; } -.app-menu__menu-view.open, -.app-menu__gallery-view.open { - pointer-events: all; - visibility: visible; +.menu__container { + position: relative; + top: var(--header-height, 80px); + padding-top: 2rem; + padding-bottom: 4rem; } -.app-menu__gallery { +.menu__gallery { display: grid; - grid-template-columns: repeat(4, 1fr); - column-gap: 32px; - row-gap: 32px; + grid-template-columns: repeat(2, 1fr); + column-gap: 0.5rem; + row-gap: 0.5rem; } -.app-menu__thumbnail { +.menu__gallery-thumbnail { position: relative; display: flex; align-items: center; height: max-content; + border-radius: 0.25rem; + overflow: hidden; } -.app-menu__nav-list { +.menu ul { + position: relative; margin-block-start: 0; margin-block-end: 0; margin-inline-start: 0; margin-inline-end: 0; padding-inline-start: 0; font-weight: 600; - font-size: 2rem; + font-size: 1.75rem; line-height: 1.25; text-align: left; list-style: none; white-space: nowrap; } -.app-menu__nav-list a { - color: inherit; - border-bottom: none !important; - transition: color 200ms; +.menu ul > li { + margin-bottom: 2rem; } -.app-menu__nav-list a:hover { - color: var(--color-white, white); +.menu ul > li > a { + display: block; + width: 100%; + color: inherit; + font-weight: 700; + margin-bottom: 1rem; } -@media (max-width: 576px) { - .app-menu__header, - .app-menu__body { - padding: 24px; - } - - .app-menu:not(.app-menu--fullscreen) .logo { - display: none; - } - - .app-menu:not(.app-menu--fullscreen) .app-menu__page-title, - .app-menu:not(.app-menu--fullscreen) .app-menu__slide-buttons { - display: block; +@media (min-width: 576px) { + .menu__container { + padding-left: 32px; + padding-right: 32px; } -} -@media (max-width: 767px) { - .app-menu__gallery-button { - display: none; + .menu ul { + font-size: 2rem; } -} -@media (max-width: 1279px) { - .app-menu .navbar { - display: none; + .menu__gallery { + grid-template-columns: repeat(3, 1fr); + column-gap: 0.75rem; + row-gap: 0.75rem; } -} -@media (max-width: 1439px) { - .app-menu .social-nav-links { - display: none; + .menu__gallery-thumbnail { + border-radius: 0.4rem; } } -@media (min-width: 576px) { - .app-menu__nav-list { +@media (min-width: 1024px) { + .menu ul { font-size: 2.5rem; } -} -@media (min-width: 768px) { - .app-menu__nav-list { - font-size: 3rem; + .menu__gallery { + grid-template-columns: repeat(4, 1fr); } } -@media (min-width: 1280px) { - .app-menu__menu-button { - display: none; +@media (min-width: 1366px) { + .menu__gallery { + grid-template-columns: repeat(5, 1fr); } } -@media (min-width: 1366px) { - .app-menu__gallery { +@media (min-width: 1440px) { + .menu__gallery { grid-template-columns: repeat(6, 1fr); } } diff --git a/src/styles/project.css b/src/styles/project.css index 59c7e1c..b8aaee4 100644 --- a/src/styles/project.css +++ b/src/styles/project.css @@ -1,6 +1,10 @@ .page > .project { padding-top: 32px; - padding-bottom: 208px; +} + +.project h1.title { + font-weight: 700; + margin-bottom: .75rem; } .project section { @@ -8,6 +12,16 @@ margin-bottom: 2rem; } +.project section > p { + margin-bottom: .5rem; +} + +.project section > .embedded-video, +.project section > .progressive-image > img { + border-radius: .5rem; + box-shadow: rgba(42, 51, 70, 0.2) 0px 0px 40px; +} + .project em { font-style: normal; letter-spacing: -0.5px; @@ -23,9 +37,19 @@ display: block; } -.project .progressive-image, -.project .embedded-video { - box-shadow: 0 0 2rem rgb(0, 0, 0, 5%); +.project .image-gallery { + margin-top: 12px; + margin-bottom: 12px; +} + +.project .image-gallery__thumbnails { + column-gap: 12px; + row-gap: 12px; +} + +.project .image-gallery__thumbnails img { + border-radius: .25rem; + box-shadow: rgba(14, 63, 126, 0.06) 0px 0px 0px 1px, rgba(42, 51, 70, 0.03) 0px 1px 1px -0.5px, rgba(42, 51, 70, 0.04) 0px 2px 2px -1px, rgba(42, 51, 70, 0.04) 0px 3px 3px -1.5px, rgba(42, 51, 70, 0.03) 0px 5px 5px -2.5px, rgba(42, 51, 70, 0.03) 0px 10px 10px -5px, rgba(42, 51, 70, 0.03) 0px 24px 24px -8px; } @media (max-width: 767px) { diff --git a/src/styles/slide.css b/src/styles/slide.css index 4218e6b..334eea0 100644 --- a/src/styles/slide.css +++ b/src/styles/slide.css @@ -11,26 +11,23 @@ position: relative; display: flex; justify-content: center; - align-items: center; - max-width: 100%; - max-height: 100%; -} - -.slide__image-wrapper > a { - width: 100%; - height: 100%; } .slide__image { display: inline-flex; - max-height: 100%; - border: 12px solid var(--color-surface-2); - border-radius: 0.25rem; - box-shadow: 0 0 1rem rgba(0, 0, 0, 10%); - background-color: var(--color-background); + justify-content: center; + align-items: center; + border-radius: 1rem; + box-shadow: rgba(42, 51, 70, 0.2) 0px 20px 40px, rgba(42, 51, 70, 0.2) 0px 30px 20px -10px; + background-color: var(--color-surface-1); overflow: hidden; } +[data-theme="dark"] .slide__image, +[data-theme="high-contrast"] .slide__image { + box-shadow: none; +} + .slide__image--border-none { border: none; box-shadow: none !important; @@ -43,39 +40,24 @@ .slide__text-wrapper { position: relative; - width: 420px; - margin-left: 6rem; + max-width: 40%; + margin-left: 3rem; transition: opacity 0.3s, width 0.3s, margin-left 0.3s; } .slide__text-container { - width: 420px; - min-width: 420px; + width: 100%; } .slide__text-container > .eyebrow { font-size: 0.675rem; + margin-left: 2px; + margin-bottom: 0; } -.gallery--vertical-layout .slide { - flex-direction: column; - align-items: flex-start; -} - -.gallery--vertical-layout .slide__image-wrapper { - max-height: 50%; -} - -.gallery--vertical-layout .slide__text-wrapper { - width: 100%; - margin-left: 0; - margin-top: 2rem; - transition: none; -} - -.gallery--vertical-layout .slide__text-container { - width: 100%; - min-width: 100%; +.slide__text-container > .title { + font-weight: 700; + margin-bottom: .5rem; } @media (max-width: 1365px) { diff --git a/src/types/menu.js b/src/types/menu.js deleted file mode 100644 index 25ae59e..0000000 --- a/src/types/menu.js +++ /dev/null @@ -1,3 +0,0 @@ -export const MENU_TYPE_NONE = 'none'; -export const MENU_TYPE_LIST = 'list'; -export const MENU_TYPE_GALLERY = 'gallery'; diff --git a/src/utils/clamp.js b/src/utils/clamp.js new file mode 100644 index 0000000..542f772 --- /dev/null +++ b/src/utils/clamp.js @@ -0,0 +1,3 @@ +export function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); +} diff --git a/yarn.lock b/yarn.lock index 24ee376..03add51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5850,6 +5850,11 @@ gatsby-plugin-typescript@^4.25.0: "@babel/runtime" "^7.15.4" babel-plugin-remove-graphql-queries "^4.25.0" +gatsby-plugin-use-query-params@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gatsby-plugin-use-query-params/-/gatsby-plugin-use-query-params-1.0.1.tgz#5e4982580d2e9bad15d243186cff379afef8a207" + integrity sha512-k3xaKuf8VhLq6/arocYRZqiQMTQ84ZRY0JklsO4tuKsRqi64b94zGf6B8SZn6yo0fvtJ/zw684DpH77y/iKdbA== + gatsby-plugin-utils@^3.19.0: version "3.19.0" resolved "https://registry.yarnpkg.com/gatsby-plugin-utils/-/gatsby-plugin-utils-3.19.0.tgz#f464b02cc51dcdc0c0e094b7352ee4bf660126ea" @@ -9445,6 +9450,11 @@ serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" +serialize-query-params@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/serialize-query-params/-/serialize-query-params-2.0.2.tgz#598a3fb9e13f4ea1c1992fbd20231aa16b31db81" + integrity sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q== + serve-static@1.16.2: version "1.16.2" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" @@ -10465,6 +10475,13 @@ url-parse-lax@^3.0.0: dependencies: prepend-http "^2.0.0" +use-query-params@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/use-query-params/-/use-query-params-2.2.1.tgz#c558ab70706f319112fbccabf6867b9f904e947d" + integrity sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q== + dependencies: + serialize-query-params "^2.0.2" + util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"