added
components/canvas.module.css
@@ -0,0 +1,4 @@.canvas { width: 100%; height: 100%;}
modified
components/constellations.js
@@ -1,5 +1,5 @@import React, { useEffect, useRef } from "react";import styled from "styled-components";import styles from "./canvas.module.css";import PropTypes from "prop-types";const Constellations = ({ options }) => {
@@ -121,7 +121,7 @@ const Constellations = ({ options }) => { }; }, [isActive]); return <Canvas ref={canvas} />; return <canvas ref={canvas} className={styles.canvas} />;};Constellations.propTypes = {
@@ -130,7 +130,4 @@ Constellations.propTypes = {export default Constellations;const Canvas = styled.canvas` width: 100%; height: 100%;`;// migrated to CSS Modules
modified
components/grid.js
@@ -1,19 +1,25 @@import React from "react";import styled from "styled-components";import styles from "./grid.module.css";import PropTypes from "prop-types";const Grid = (props) => { return ( <> <GridLines> <div className={styles.gridLines}> {Array(6) .fill() .map((_, i) => { const column = i + 1; return <GridLine key={column} column={column} />; return ( <div key={column} className={styles.gridLine} style={{ gridColumn: column }} /> ); })} </GridLines> <GridArea>{props.children}</GridArea> </div> <div className={styles.gridArea}>{props.children}</div> </> );};
@@ -27,31 +33,4 @@ Grid.propTypes = {export default Grid;const GridArea = styled.div` display: grid; grid-template-columns: 60px 15% 1fr 1fr 15% 60px; grid-template-rows: 1fr auto 1fr; grid-template-areas: ". . . . . ." ". . main main . ." ". . . . . ."; min-height: 100vh; width: 100%; @media (${(props) => props.theme.breakpoints.tablet}) { grid-template-columns: 10px 5% 1fr 1fr 5% 10px; }`;const GridLines = styled(GridArea)` position: fixed; pointer-events: none; @media (${(props) => props.theme.breakpoints.tablet}) { display: none; }`;const GridLine = styled.div` border-right: 1px solid rgba(125, 125, 125, 0.2); grid-row: 1 / -1; grid-column: ${(props) => props.column};`;// migrated to CSS Modules
added
components/grid.module.css
@@ -0,0 +1,42 @@.gridArea { display: grid; grid-template-columns: 60px 15% 1fr 1fr 15% 60px; grid-template-rows: 1fr auto 1fr; grid-template-areas: ". . . . . ." ". . main main . ." ". . . . . ."; min-height: 100vh; width: 100%;}@media (max-width: 1023.98px) { .gridArea { grid-template-columns: 10px 5% 1fr 1fr 5% 10px; }}.gridLines { position: fixed; pointer-events: none; display: grid; grid-template-columns: 60px 15% 1fr 1fr 15% 60px; grid-template-rows: 1fr auto 1fr; grid-template-areas: ". . . . . ." ". . main main . ." ". . . . . ."; min-height: 100vh; width: 100%;}@media (max-width: 1023.98px) { .gridLines { display: none; }}.gridLine { border-right: 1px solid rgba(125, 125, 125, 0.2); grid-row: 1 / -1;}
modified
components/loader.js
@@ -1,66 +1,32 @@import React from "react";import styled, { keyframes } from "styled-components";import styles from "./loader.module.css";const Loader = () => { return ( <> <Grid> <div className={styles.grid}> {Array(6) .fill() .map((_, i) => { const column = i + 1; return <GridColumn key={column} column={column} />; return ( <div key={column} className={styles.gridColumn} style={{ gridColumn: column, // match animation-delay: column * 100ms for both pseudo-elements // We'll set CSS variable that both ::before and ::after can read ["--delay"]: `${column * 100}ms`, }} /> ); })} </Grid> </div> </> );};export default Loader;const FoldUp = keyframes` 0% { height: 100vh; } 100% { height: 0; }`;const Grid = styled.div` display: grid; grid-template-columns: 60px 15% 1fr 1fr 15% 60px; grid-template-rows: 1fr; min-height: 100vh; width: 100%; position: fixed; z-index: 9999; pointer-events: none;`;const GridColumn = styled.div` grid-column: ${(props) => props.column}; &::before { content: ""; display: block; width: 100%; height: 100vh; background-color: ${(props) => props.theme.colors.primary}; border-right: 1px solid ${(props) => props.theme.colors.primary}; animation: ${FoldUp} 1000ms normal forwards; animation-delay: ${(props) => props.column * 100}ms; } &::after { content: ""; display: block; width: 100%; height: 100vh; background-color: white; border-right: 1px solid white; animation: ${FoldUp} 1000ms normal forwards; animation-delay: ${(props) => props.column * 100}ms; }`;// migrated to CSS Modules
added
components/loader.module.css
@@ -0,0 +1,39 @@@keyframes foldUp { 0% { height: 100vh; } 100% { height: 0; }}.grid { display: grid; grid-template-columns: 60px 15% 1fr 1fr 15% 60px; grid-template-rows: 1fr; min-height: 100vh; width: 100%; position: fixed; z-index: 9999; pointer-events: none;}.gridColumn::before,.gridColumn::after { content: ""; display: block; width: 100%; height: 100vh; animation: foldUp 1000ms normal forwards; animation-delay: var(--delay, 0ms);}.gridColumn::before { background-color: var(--color-primary); border-right: 1px solid var(--color-primary);}.gridColumn::after { background-color: white; border-right: 1px solid white;}
modified
components/menu.js
@@ -3,7 +3,7 @@ import React, { useState } from "react";import { CSSTransition } from "react-transition-group";import Image from "next/image";import Link from "next/link";import styled from "styled-components";import styles from "./menu.module.css";const Menu = () => { const [open, setOpen] = useState(false);
@@ -27,314 +27,103 @@ const Menu = () => { return ( <> <Hamburger onClick={toggleMenu} aria-label="Hamburger"> <Patty style={{ width: "15px" }} /> <Patty /> <Patty style={{ width: "20px" }} /> </Hamburger> <CSSTransition in={open} timeout={500} classNames="transition"> <Overlay> <OverlayGrid> <CSSTransition in={open} timeout={500} classNames="transition" appear > <OverlayGridLeft> <TopBar> <button className={styles.hamburger} onClick={toggleMenu} aria-label="Hamburger" > <div className={styles.patty} style={{ width: "15px" }} /> <div className={styles.patty} /> <div className={styles.patty} style={{ width: "20px" }} /> </button> <CSSTransition in={open} timeout={500} classNames="menu" appear onEnter={() => { if (typeof document !== "undefined") { document.body.style.overflowY = "hidden"; } }} onExited={() => { if (typeof document !== "undefined") { document.body.style.overflowY = "scroll"; } }} > <div className={styles.overlay}> <div className={styles.overlayGrid}> <CSSTransition in={open} timeout={500} classNames="menu" appear> <div className={styles.overlayGridLeft}> <div className={styles.topBar}> <Link href="https://blog.bythewood.me/" passHref> <TopLink target="_blank">Blog</TopLink> <a className={styles.topLink} target="_blank"> Blog </a> </Link> <Link href="https://status.bythewood.me/properties/87097ef2-5643-4999-917e-72b172dd9e19/" passHref > <TopLink target="_blank">Status</TopLink> <a className={styles.topLink} target="_blank"> Status </a> </Link> <Link href="https://analytics.bythewood.me/properties/30e69c06-9beb-4283-8919-8c7a686ab013/" passHref > <TopLink target="_blank">Analytics</TopLink> <a className={styles.topLink} target="_blank"> Analytics </a> </Link> <Link href="https://github.com/overshard" passHref> <TopLink target="_blank">GitHub</TopLink> <a className={styles.topLink} target="_blank"> GitHub </a> </Link> <Bar /> </TopBar> <div className={styles.bar} /> </div> {pages.map((page) => { return ( <Link key={page.href} href={page.href} passHref> <OverlayLink data-text={page.title} onClick={toggleMenu}> <a className={styles.overlayLink} data-text={page.title} onClick={toggleMenu} > {page.title} </OverlayLink> </a> </Link> ); })} <BottomBar> <div className={styles.bottomBar}> <Image src="/static/images/avatar.webp" alt="Isaac Bythewood" width={50} height={50} /> <Bar /> </BottomBar> </OverlayGridLeft> <div className={styles.bar} /> </div> </div> </CSSTransition> <CSSTransition in={open} timeout={500} classNames="transition" appear > <OverlayGridRight> <CSSTransition in={open} timeout={500} classNames="menu" appear> <div className={styles.overlayGridRight}> <Image src="/static/images/art/acrylic-pours/006.webp" alt="#006 Molten Copper" loading="lazy" layout="fill" /> </OverlayGridRight> </div> </CSSTransition> </OverlayGrid> </Overlay> </div> </div> </CSSTransition> </> );};export default Menu;const Hamburger = styled.button` position: fixed; top: 30px; right: 0; padding: 20px 15px; background: white; z-index: 5001; border: none; &:hover * { width: 30px !important; } @media (${(props) => props.theme.breakpoints.tablet}) { top: 0; padding-bottom: 21px; }`;const Patty = styled.div` background: black; width: 30px; height: 3px; margin-bottom: 5px; margin-left: auto; transition-duration: 250ms; transition-property: width; &:last-child { margin-bottom: 0; }`;const Overlay = styled.div` position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: -5000; &.transition-enter { z-index: 5000; } &.transition-enter-active { z-index: 5000; } &.transition-enter-done { z-index: 5000; } &.transition-exit { z-index: 5000; } &.transition-exit-active { z-index: 5000; } &.transition-exit-done { z-index: -5000; }`;const OverlayGrid = styled.div` display: grid; grid-template-columns: 60% 40%; @media (${(props) => props.theme.breakpoints.mobile}) { grid-template-columns: 100%; }`;const OverlayGridLeft = styled.div` grid-area: 1/1; display: flex; flex-direction: column; justify-content: center; align-items: center; background: white; transition-duration: 500ms; transition-property: transform; transform: translateX(-100%); &.transition-enter { transform: translateX(-100%); } &.transition-enter-active { transform: translateX(0); } &.transition-enter-done { transform: translateX(0); } &.transition-exit { transform: translateX(0); } &.transition-exit-active { transform: translateX(-100%); } &.transition-exit-done { transform: translateX(-100%); }`;const OverlayGridRight = styled.div` grid-area: 1/2; height: 100vh; overflow: hidden; transition-duration: 500ms; transition-property: transform; transform: translateX(100%); &.transition-enter { transform: translateX(100%); } &.transition-enter-active { transform: translateX(0); } &.transition-enter-done { transform: translateX(0); } &.transition-exit { transform: translateX(0); } &.transition-exit-active { transform: translateX(100%); } &.transition-exit-done { transform: translateX(100%); } img { object-fit: cover; height: 100vh; }`;const OverlayLink = styled.a` font-weight: 900; text-decoration: none; margin-bottom: 10px; font-size: 3em; color: black; position: relative; &:last-child { margin-bottom: 0; } &::before { content: attr(data-text); color: white; position: absolute; width: 0; left: 0; bottom: 3px; height: 27px; background-color: black; transition-property: width; transition-duration: 200ms; line-height: 0; overflow: hidden; } &:hover { &::before { width: 100%; } }`;const TopBar = styled.div` display: flex; width: 100%; position: absolute; left: 20px; top: 20px; align-items: center; justify-content: start;`;const TopLink = styled.a` text-decoration: none; color: white; background-color: rgba(0, 0, 0, 1); padding: 0.25rem 0.5rem; margin-left: 1rem; transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace; &:hover { background-color: rgba(0, 0, 0, 0.1); color: black; } @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 0.8em; margin-left: 0.5rem; }`;const BottomBar = styled.div` display: flex; width: 100%; position: absolute; left: 20px; bottom: 20px; align-items: center;`;const Bar = styled.div` width: 100%; height: 1px; background: rgba(0, 0, 0, 0.3); margin-left: 40px; margin-right: 60px;`;
added
components/menu.module.css
@@ -0,0 +1,196 @@.hamburger { position: fixed; top: 30px; right: 0; padding: 20px 15px; background: white; z-index: 5001; border: none;}.hamburger:hover * { width: 30px !important;}@media (max-width: 1023.98px) { .hamburger { top: 0; padding-bottom: 21px; }}.patty { background: black; width: 30px; height: 3px; margin-bottom: 5px; margin-left: auto; transition: width 250ms;}.patty:last-child { margin-bottom: 0;}.overlay { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: -5000; background: transparent;}.overlay:global(.menu-enter),.overlay:global(.menu-enter-active),.overlay:global(.menu-enter-done),.overlay:global(.menu-exit),.overlay:global(.menu-exit-active) { z-index: 5000;}.overlay:global(.menu-exit-done) { z-index: -5000;}.overlayGrid { display: grid; grid-template-columns: 60% 40%;}@media (max-width: 767.98px) { .overlayGrid { grid-template-columns: 100%; }}.overlayGridLeft { grid-area: 1/1; display: flex; flex-direction: column; justify-content: center; align-items: center; background: white; transition: transform 500ms; transform: translateX(-100%);}.overlayGridLeft:global(.menu-enter),.overlayGridLeft:global(.menu-enter-active),.overlayGridLeft:global(.menu-enter-done) { transform: translateX(0);}.overlayGridLeft:global(.menu-exit),.overlayGridLeft:global(.menu-exit-active),.overlayGridLeft:global(.menu-exit-done) { transform: translateX(-100%);}.overlayGridRight { grid-area: 1/2; height: 100vh; overflow: hidden; transition: transform 500ms; transform: translateX(100%);}.overlayGridRight:global(.menu-enter),.overlayGridRight:global(.menu-enter-active),.overlayGridRight:global(.menu-enter-done) { transform: translateX(0);}.overlayGridRight:global(.menu-exit),.overlayGridRight:global(.menu-exit-active),.overlayGridRight:global(.menu-exit-done) { transform: translateX(100%);}.overlayGridRight img { object-fit: cover; height: 100vh;}.overlayLink { font-weight: 900; text-decoration: none; margin-bottom: 10px; font-size: 3em; color: black; position: relative;}.overlayLink:last-child { margin-bottom: 0;}.overlayLink::before { content: attr(data-text); color: white; position: absolute; width: 0; left: 0; bottom: 3px; height: 27px; background-color: black; transition: width 200ms; line-height: 0; overflow: hidden;}.overlayLink:hover::before { width: 100%;}.topBar { display: flex; width: 100%; position: absolute; left: 20px; top: 20px; align-items: center; justify-content: flex-start;}.topLink { text-decoration: none; color: white; background-color: rgba(0, 0, 0, 1); padding: 0.25rem 0.5rem; margin-left: 1rem; transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; font-family: ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", monospace;}.topLink:hover { background-color: rgba(0, 0, 0, 0.1); color: black;}@media (max-width: 767.98px) { .topLink { font-size: 0.8em; margin-left: 0.5rem; }}.bottomBar { display: flex; width: 100%; position: absolute; left: 20px; bottom: 20px; align-items: center;}.bar { width: 100%; height: 1px; background: rgba(0, 0, 0, 0.3); margin-left: 40px; margin-right: 60px;}
modified
components/mouse.js
@@ -1,5 +1,5 @@import React, { useEffect, useRef } from "react";import styled from "styled-components";import styles from "./mouse.module.css";const Mouse = () => { const cursorRef = useRef(null);
@@ -19,9 +19,12 @@ const Mouse = () => { isInteractiveRef.current = target.tagName === "BUTTON" || target.tagName === "A" || target.classList?.contains("mouse-activate"); (target.classList && target.classList.contains("mouse-activate")); if (circleRef.current) { circleRef.current.classList.toggle("activated", isInteractiveRef.current); circleRef.current.classList.toggle( "activated", isInteractiveRef.current ); } };
@@ -33,8 +36,10 @@ const Mouse = () => { const animate = () => { const { x, y } = mousePos.current; if (cursorRef.current) cursorRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`; if (circleRef.current) circleRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`; if (cursorRef.current) cursorRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`; if (circleRef.current) circleRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`; animationFrameId = requestAnimationFrame(animate); };
@@ -50,61 +55,23 @@ const Mouse = () => { return ( <> <PageCursor ref={cursorRef}> <svg width="16" height="16" viewBox="0 0 16 16" fill="#ff2d2d" fillOpacity="0.9" xmlns="http://www.w3.org/2000/svg"> <div ref={cursorRef} className={styles.pageCursor}> <svg width="16" height="16" viewBox="0 0 16 16" fill="#ff2d2d" fillOpacity="0.9" xmlns="http://www.w3.org/2000/svg" > <circle cx="8" cy="8" r="8" /> </svg> </PageCursor> <PageCursorCircle ref={circleRef} /> </div> <div ref={circleRef} className={styles.pageCursorCircle} /> </> );};export default Mouse;const PageCursor = styled.div` position: fixed; z-index: 100000; top: 0; left: 0; width: 16px; height: 16px; margin: -8px 0 0 -8px; pointer-events: none; will-change: transform; backface-visibility: hidden; @media (${(props) => props.theme.breakpoints.tablet}) { display: none; }`;const PageCursorCircle = styled.div` position: fixed; z-index: 100000; top: 0; left: 0; pointer-events: none; margin: -25px 0 0 -26px; width: 50px; height: 50px; border: 1px solid #ff2d2d; opacity: 0.7; border-radius: 50%; transform-origin: center center; transition: width 0.4s ease, height 0.4s ease, transform 0.4s ease, opacity 0.4s ease; will-change: transform, width, height, opacity; backface-visibility: hidden; &.activated { margin: -35px 0 0 -36px; width: 70px; height: 70px; background-color: #ff2d2d; opacity: 0.3; } @media (${(props) => props.theme.breakpoints.tablet}) { display: none; }`;// migrated to CSS Modules
added
components/mouse.module.css
@@ -0,0 +1,52 @@.pageCursor { position: fixed; z-index: 100000; top: 0; left: 0; width: 16px; height: 16px; margin: -8px 0 0 -8px; pointer-events: none; will-change: transform; backface-visibility: hidden;}@media (max-width: 1023.98px) { .pageCursor { display: none; }}.pageCursorCircle { position: fixed; z-index: 100000; top: 0; left: 0; pointer-events: none; margin: -25px 0 0 -26px; width: 50px; height: 50px; border: 1px solid #ff2d2d; opacity: 0.7; border-radius: 50%; transform-origin: center center; transition: width 0.4s ease, height 0.4s ease, transform 0.4s ease, opacity 0.4s ease; will-change: transform, width, height, opacity; backface-visibility: hidden;}.pageCursorCircle.activated,:global(.activated).pageCursorCircle,.pageCursorCircle:global(.activated) { margin: -35px 0 0 -36px; width: 70px; height: 70px; background-color: #ff2d2d; opacity: 0.3;}@media (max-width: 1023.98px) { .pageCursorCircle { display: none; }}
modified
components/page.js
@@ -1,7 +1,7 @@import React from "react";import Head from "next/head";import PropTypes from "prop-types";import styled from "styled-components";import styles from "./page.module.css";import { strings } from "../site.config";
@@ -20,9 +20,12 @@ const Page = (props) => { content={props.description ? props.description : defaultDescription} /> </Head> <Main gridArea={props.gridArea ? props.gridArea : "main"}> <main style={{ gridArea: props.gridArea ? props.gridArea : "main" }} className={styles.main} > {props.children} </Main> </main> </> );};
@@ -39,6 +42,4 @@ Page.propTypes = {export default Page;const Main = styled.main` ${(props) => props.gridArea && `grid-area: ${props.gridArea};`}`;// migrated to CSS Module
added
components/page.module.css
@@ -0,0 +1,10 @@.main { /* dynamic grid-area added inline via style prop */ display: flex; flex-direction: column; align-items: flex-start; justify-content: center; min-height: 100vh; width: 100%; text-align: left;}
modified
components/particleflow.js
@@ -1,5 +1,5 @@import React, { useEffect, useRef } from "react";import styled from "styled-components";import styles from "./canvas.module.css";import PropTypes from "prop-types";const ParticleFlow = ({ options }) => {
@@ -181,7 +181,7 @@ const ParticleFlow = ({ options }) => { }; }, [isActive]); return <Canvas ref={canvas} />; return <canvas ref={canvas} className={styles.canvas} />;};ParticleFlow.propTypes = {
@@ -190,7 +190,4 @@ ParticleFlow.propTypes = {export default ParticleFlow;const Canvas = styled.canvas` width: 100%; height: 100%;`;// migrated to CSS Modules
modified
components/retrostars.js
@@ -1,5 +1,5 @@import React, { useEffect, useRef, useState } from "react";import styled from "styled-components";import styles from "./canvas.module.css";import PropTypes from "prop-types";const RetroStars = ({ options }) => {
@@ -244,7 +244,7 @@ const RetroStars = ({ options }) => { }; }, [isActive]); return <Canvas ref={canvas} />; return <canvas ref={canvas} className={styles.canvas} />;};RetroStars.propTypes = {
@@ -253,7 +253,4 @@ RetroStars.propTypes = {export default RetroStars;const Canvas = styled.canvas` width: 100%; height: 100%;`;// migrated to CSS Modules
modified
components/sidebar.js
@@ -1,6 +1,6 @@import React, { useState, useEffect } from "react";import PropTypes from "prop-types";import styled from "styled-components";import styles from "./sidebar.module.css";import Link from "next/link";import { withRouter } from "next/router";
@@ -35,15 +35,15 @@ const Sidebar = ({ router }) => { }, []); return ( <Nav> <nav className={styles.nav}> <Link href="/" passHref> <NavLogo aria-label="Back to home" /> <a className={styles.navLogo} aria-label="Back to home" /> </Link> <Link href="/contact" passHref> <NavContact>Get in touch</NavContact> <a className={styles.navContact}>Get in touch</a> </Link> <NavCurrent>{current}</NavCurrent> </Nav> <div className={styles.navCurrent}>{current}</div> </nav> );};
@@ -52,107 +52,3 @@ Sidebar.propTypes = {};export default withRouter(Sidebar);const Nav = styled.nav` background-color: white; color: black; display: flex; flex-direction: column; justify-content: space-between; width: 60px; top: 0; left: 0; bottom: 0; position: fixed; z-index: 1; border-right: 1px solid rgba(125, 125, 125, 0.2); z-index: 100; @media (${(props) => props.theme.breakpoints.tablet}) { bottom: auto; left: 0; flex-direction: row; width: 100%; height: 60px; border-right: none; }`;const NavLogo = styled.a` content: ""; display: block; background-image: linear-gradient( to right, ${(props) => props.theme.colors.blue} 0, ${(props) => props.theme.colors.purple} 100% ); width: 40px; height: 40px; z-index: 3; margin-left: 10px; margin-top: 10px; transition: transform 0.2s ease-in-out; &:hover { transform: rotate(15deg) scale(1.1); } @media (${(props) => props.theme.breakpoints.mobile}) { left: 35px; }`;const NavContact = styled.a` font-weight: 700; writing-mode: vertical-rl; transform: rotate(180deg); text-decoration: none; font-size: 1.2em; line-height: 1.5em; margin: 15px; color: black; position: relative; overflow: hidden; &::before { content: ""; display: block; position: absolute; z-index: -1; right: 100%; width: 100%; height: 100%; background: rgba(14, 64, 244, 0.3); transition: right 250ms; } &:hover { &::before { right: 60%; } } @media (${(props) => props.theme.breakpoints.tablet}) { writing-mode: horizontal-tb; transform: none; } @media (${(props) => props.theme.breakpoints.mobile}) { writing-mode: horizontal-tb; transform: none; display: none; }`;const NavCurrent = styled.div` font-family: monospace; background-color: black; text-align: center; padding: 5px; color: white; @media (${(props) => props.theme.breakpoints.tablet}) { writing-mode: vertical-rl; transform: rotate(180deg); }`;
added
components/sidebar.module.css
@@ -0,0 +1,109 @@.nav { background-color: white; color: black; display: flex; flex-direction: column; justify-content: space-between; width: 60px; top: 0; left: 0; bottom: 0; position: fixed; z-index: 100; border-right: 1px solid rgba(125, 125, 125, 0.2);}@media (max-width: 1023.98px) { .nav { bottom: auto; left: 0; flex-direction: row; width: 100%; height: 60px; border-right: none; }}.navLogo { display: block; background-image: linear-gradient( to right, var(--color-blue) 0, var(--color-purple) 100% ); width: 40px; height: 40px; z-index: 3; margin-left: 10px; margin-top: 10px; transition: transform 0.2s ease-in-out;}.navLogo:hover { transform: rotate(15deg) scale(1.1);}@media (max-width: 767.98px) { .navLogo { left: 35px; }}.navContact { font-weight: 700; writing-mode: vertical-rl; transform: rotate(180deg); text-decoration: none; font-size: 1.2em; line-height: 1.5em; margin: 15px; color: black; position: relative; overflow: hidden;}.navContact::before { content: ""; display: block; position: absolute; z-index: -1; right: 100%; width: 100%; height: 100%; background: rgba(14, 64, 244, 0.3); transition: right 250ms;}.navContact:hover::before { right: 60%;}@media (max-width: 1023.98px) { .navContact { writing-mode: horizontal-tb; transform: none; }}@media (max-width: 767.98px) { .navContact { writing-mode: horizontal-tb; transform: none; display: none; }}.navCurrent { font-family: monospace; background-color: black; text-align: center; padding: 5px; color: white;}@media (max-width: 1023.98px) { .navCurrent { writing-mode: vertical-rl; transform: rotate(180deg); }}
modified
components/synthwave.js
@@ -1,5 +1,5 @@import React, { useEffect, useRef } from "react";import styled from "styled-components";import styles from "./canvas.module.css";const Synthwave = () => { const canvas = useRef(null);
@@ -146,12 +146,9 @@ const Synthwave = () => { }; }, []); return <Canvas ref={canvas} />; return <canvas ref={canvas} className={styles.canvas} />;};export default Synthwave;const Canvas = styled.canvas` width: 100%; height: 100%;`;// migrated to CSS Modules
@@ -15,8 +15,7 @@ "react-transition-group": "^4.4.1", "sequelize": "^6.21.1", "sharp": "^0.30.7", "sqlite3": "^5.0.8", "styled-components": "^5.2.3" "sqlite3": "^5.0.8" }, "devDependencies": { "@next/eslint-plugin-next": "^12.1.6",
@@ -1,9 +1,8 @@import React from "react";import App from "next/app";import styled, { ThemeProvider, createGlobalStyle } from "styled-components";import "../styles/globals.css";import { TransitionGroup, CSSTransition } from "react-transition-group";import { theme } from "../site.config";import Grid from "../components/grid";import Sidebar from "../components/sidebar";import Menu from "../components/menu";
@@ -15,83 +14,29 @@ class MyApp extends App { const { Component, pageProps } = this.props; return ( <ThemeProvider theme={theme}> <> <GlobalStyle /> <Mouse /> <Loader /> <Sidebar /> <Menu /> <TransitionGroup component={null}> <CSSTransition key={this.props.router.route} appear timeout={250} classNames="transition" > <Transition> <Grid> <Component {...pageProps} /> </Grid> </Transition> </CSSTransition> </TransitionGroup> </> </ThemeProvider> <> <Mouse /> <Loader /> <Sidebar /> <Menu /> <TransitionGroup component={null}> <CSSTransition key={this.props.router.route} appear timeout={250} classNames="transition" > <div className="transition"> <Grid> <Component {...pageProps} /> </Grid> </div> </CSSTransition> </TransitionGroup> </> ); }}export default MyApp;const GlobalStyle = createGlobalStyle` body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; color: white; background-color: ${(props) => props.theme.colors.primary}; min-height: 100vh; width: 100%; padding: 0; margin: 0; overflow-x: hidden; text-shadow: rgba(0, 0, 0, .01) 0 0 1px; text-rendering: optimizeLegibility; user-select: none; cursor: none; } a { cursor: none; } button { cursor: none; }`;const Transition = styled.div` position: absolute; top: 0; left: 0; right: 0; transition-duration: 250ms; transition-property: opacity; &.transition-enter { opacity: 0; } &.transition-enter-active { opacity: 1; } &.transition-enter-done { opacity: 1; } &.transition-exit { opacity: 1; } &.transition-exit-active { opacity: 0; }`;// Transition classes now provided by globals.css
modified
pages/_document.js
@@ -1,37 +1,9 @@import React from "react";import Document, { Html, Head, Main, NextScript } from "next/document";import { ServerStyleSheet } from "styled-components";import { theme } from "../site.config";class MyDocument extends Document { static async getInitialProps(ctx) { // Setup styled-components for rendering server side const sheet = new ServerStyleSheet(); const originalRenderPage = ctx.renderPage; try { ctx.renderPage = () => originalRenderPage({ enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />), }); const initialProps = await Document.getInitialProps(ctx); return { ...initialProps, styles: ( <> {initialProps.styles} {sheet.getStyleElement()} </> ), }; } finally { sheet.seal(); } } render() { return ( <Html lang="en">
@@ -1,8 +1,8 @@import React, { useState, useEffect, useRef } from "react";import styled, { keyframes } from "styled-components";import { TransitionGroup, CSSTransition } from "react-transition-group";import Page from "../components/page";import styles from "./about.module.css";const About = () => { const [words, setWords] = useState([
@@ -53,8 +53,8 @@ const About = () => { return ( <Page title="About" description="A brief professional history of myself."> <Background /> <Words> <div className={styles.background} /> <div className={styles.words}> <TransitionGroup component={null}> {words.map((word) => { return (
@@ -62,26 +62,39 @@ const About = () => { key={word} timeout={1000} appear classNames="transition" classNames={{ appearActive: styles.wordAppearActive, appearDone: styles.wordAppearDone, enterActive: styles.wordEnterActive, enterDone: styles.wordEnterDone, exitActive: styles.wordExitActive, }} > <Word> <WordText>{word}</WordText> </Word> <h2 className={styles.word}> <span className={styles.wordText}>{word}</span> </h2> </CSSTransition> ); })} </TransitionGroup> </Words> <Paragraph> I am a <Strong>senior solutions architect</Strong>. I enjoy working on everything from linux kernel modules to website front-ends. My first job was <Strong>modifying kernel modules</Strong> for Digium Telephony Cards to work on CentOS. I am currently doing a bit of everything — chiefly creating <Strong>custom software</Strong> on a variety of platforms, mostly the web. I have done consulting for many companies on SEO, Online Advertising, Social Media, Cloud Services, Security, HIPAA & PCI Compliance, and myriad other topics. <Resume href="/static/pdfs/resume-isaac-bythewood.pdf" target="_blank"> </div> <p className={styles.paragraph}> I am a <span className={styles.strong}>senior solutions architect</span> . I enjoy working on everything from linux kernel modules to website front-ends. My first job was{" "} <span className={styles.strong}>modifying kernel modules</span> for Digium Telephony Cards to work on CentOS. I am currently doing a bit of everything — chiefly creating{" "} <span className={styles.strong}>custom software</span> on a variety of platforms, mostly the web. I have done consulting for many companies on SEO, Online Advertising, Social Media, Cloud Services, Security, HIPAA & PCI Compliance, and myriad other topics. <a className={styles.resume} href="/static/pdfs/resume-isaac-bythewood.pdf" target="_blank" rel="noopener noreferrer" > <svg xmlns="http://www.w3.org/2000/svg" width="20"
@@ -95,218 +108,10 @@ const About = () => { /> </svg> My Resume </Resume> </Paragraph> </a> </p> </Page> );};export default About;const SlideLeft = keyframes` 0% { left: -100%; } 40% { left: 0; } 60% { left: 0; } 100% { left: 100%; }`;const QuickFadeIn = keyframes` 0% { color: rgba(0, 0, 0, 0); } 49% { color: rgba(0, 0, 0, 0); } 50% { color: rgba(0, 0, 0, 1); } 100% { color: rgba(0, 0, 0, 1); }`;const FadeIn = keyframes` 0% { color: rgba(0, 0, 0, 0); } 100% { color: rgba(0, 0, 0, 1); }`;const Background = styled.div` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;`;const Words = styled.div` position: absolute; z-index: -1; top: 0; bottom: 0; left: 60px; opacity: 0.05; font-size: 5vh; line-height: 11vh; color: black; text-transform: uppercase; height: 100vh; overflow: hidden; @media (${(props) => props.theme.breakpoints.mobile}) { top: 50px; font-size: 4vh; line-height: 10vh; left: calc(10px + 5%); }`;const Word = styled.h2` margin: 0; &.transition-appear-active, &.transition-enter-active { & > span { animation-name: ${QuickFadeIn}; animation-duration: 1000ms; &::before { animation-name: ${SlideLeft}; animation-duration: 1000ms; } } } &.transition-exit-active { & > span { animation-name: ${QuickFadeIn}; animation-duration: 1000ms; animation-direction: reverse; &::before { animation-name: ${SlideLeft}; animation-duration: 1000ms; animation-direction: reverse; } } } &.transition-appear-done, &.transition-enter-done { & > span { color: rgba(0, 0, 0, 1); } }`;const WordText = styled.span` position: relative; white-space: nowrap; overflow: hidden; color: rgba(0, 0, 0, 0); display: inline-block; vertical-align: top; &::before { content: ""; position: absolute; width: 100%; height: 100%; background-color: black; left: -100%; z-index: 1; }`;const Paragraph = styled.p` font-size: 1.8em; font-weight: 300; color: black; position: relative; color: rgba(0, 0, 0, 0); animation-name: ${FadeIn}; animation-delay: 1500ms; animation-duration: 1000ms; animation-fill-mode: forwards; &::before { content: ""; display: block; width: 75px; height: 5px; margin-bottom: 20px; background-color: ${(props) => props.theme.colors.blue}; } @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 1.2em; }`;const Strong = styled.strong` font-weight: 700; position: relative; white-space: nowrap; overflow: hidden; animation: ${QuickFadeIn} 1250ms normal forwards; color: rgba(0, 0, 0, 0); display: inline-block; vertical-align: top; &::before { content: ""; position: absolute; width: 100%; height: 100%; background-color: black; left: -100%; z-index: 1; animation: ${SlideLeft} 1250ms normal forwards; }`;const Resume = styled.a` display: block; text-decoration: none; margin-top: 20px; font-weight: 700; color: black; position: relative; overflow: hidden; &::before { content: ""; display: block; position: absolute; z-index: -1; top: 100%; width: 100%; height: 100%; background: rgba(14, 64, 244, 0.3); transition: top 250ms; } &:hover { &::before { top: 60%; } } svg { margin-right: 10px; }`;
added
pages/about.module.css
@@ -0,0 +1,206 @@/* About page styles migrated from styled-components */@keyframes slideLeft { 0% { left: -100%; } 40% { left: 0; } 60% { left: 0; } 100% { left: 100%; }}@keyframes quickFadeIn { 0% { color: rgba(0, 0, 0, 0); } 49% { color: rgba(0, 0, 0, 0); } 50% { color: rgba(0, 0, 0, 1); } 100% { color: rgba(0, 0, 0, 1); }}@keyframes fadeIn { 0% { color: rgba(0, 0, 0, 0); } 100% { color: rgba(0, 0, 0, 1); }}.background { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;}.words { position: absolute; z-index: -1; top: 0; bottom: 0; left: 60px; opacity: 0.05; font-size: 5vh; line-height: 11vh; color: black; text-transform: uppercase; height: 100vh; overflow: hidden;}@media (max-width: 767.98px) { .words { top: 50px; font-size: 4vh; line-height: 10vh; left: calc(10px + 5%); }}.word { margin: 0;}.wordText { position: relative; white-space: nowrap; overflow: hidden; color: rgba(0, 0, 0, 0); display: inline-block; vertical-align: top;}.wordText::before { content: ""; position: absolute; width: 100%; height: 100%; background-color: black; left: -100%; z-index: 1;}/* Transition mappings for word animations */.wordAppearActive > .wordText,.wordEnterActive > .wordText { animation-name: quickFadeIn; animation-duration: 1000ms;}.wordAppearActive > .wordText::before,.wordEnterActive > .wordText::before { animation-name: slideLeft; animation-duration: 1000ms;}.wordExitActive > .wordText { animation-name: quickFadeIn; animation-duration: 1000ms; animation-direction: reverse;}.wordExitActive > .wordText::before { animation-name: slideLeft; animation-duration: 1000ms; animation-direction: reverse;}.wordAppearDone > .wordText,.wordEnterDone > .wordText { color: rgba(0, 0, 0, 1);}.paragraph { font-size: 1.8em; font-weight: 300; color: black; position: relative; color: rgba(0, 0, 0, 0); animation-name: fadeIn; animation-delay: 1500ms; animation-duration: 1000ms; animation-fill-mode: forwards;}.paragraph::before { content: ""; display: block; width: 75px; height: 5px; margin-bottom: 20px; background-color: var(--color-blue);}@media (max-width: 767.98px) { .paragraph { font-size: 1.2em; }}.strong { font-weight: 700; position: relative; white-space: nowrap; overflow: hidden; animation: quickFadeIn 1250ms normal forwards; color: rgba(0, 0, 0, 0); display: inline-block; vertical-align: top;}.strong::before { content: ""; position: absolute; width: 100%; height: 100%; background-color: black; left: -100%; z-index: 1; animation: slideLeft 1250ms normal forwards;}.resume { display: block; text-decoration: none; margin-top: 20px; font-weight: 700; color: black; position: relative; overflow: hidden;}.resume::before { content: ""; display: block; position: absolute; z-index: -1; top: 100%; width: 100%; height: 100%; background: rgba(14, 64, 244, 0.3); transition: top 250ms;}.resume:hover::before { top: 60%;}.resume svg { margin-right: 10px;}
@@ -1,5 +1,5 @@import React, { useState } from "react";import styled from "styled-components";import styles from "./art.module.css";import Image from "next/image";import Page from "../components/page";
@@ -30,19 +30,20 @@ const Art = () => { return ( <Page title="Art" description="Some of my art... what even is art..."> <Background /> <Heading>Acrylic Pours</Heading> <Paragraph> <div className={styles.background} /> <h1 className={styles.heading}>Acrylic Pours</h1> <p className={styles.paragraph}> A bit more traditional than my usual art, acrylics mixed with water, glue, and silicone on canvas and hit with a heat gun. </Paragraph> <Cards> <Card </p> <div className={styles.artGrid}> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/006.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/006.webp" alt="Molten Copper"
@@ -51,17 +52,18 @@ const Art = () => { className="mouse-activate" priority={true} /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Molten Copper <span>006</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/005.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/005.webp" alt="Nebulas in Triangulum"
@@ -70,17 +72,18 @@ const Art = () => { className="mouse-activate" priority={true} /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Nebulas in Triangulum <span>005</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/004.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/004.webp" alt="Metal on Mars"
@@ -89,17 +92,18 @@ const Art = () => { className="mouse-activate" priority={true} /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Metal on Mars <span>004</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/003.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/003.webp" alt="Water on Jupiter"
@@ -108,17 +112,18 @@ const Art = () => { className="mouse-activate" priority={true} /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Water on Jupiter <span>003</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/002.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/002.webp" alt="Cracks in Clay"
@@ -126,17 +131,18 @@ const Art = () => { height={360} className="mouse-activate" /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Cracks in Clay <span>002</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/001.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/001.webp" alt="Reef Drop-off"
@@ -144,17 +150,18 @@ const Art = () => { height={360} className="mouse-activate" /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Reef Drop-off <span>001</span> </CardHeading> </Card> <Card </h2> </div> <div className={styles.artItem} onClick={() => openLightbox("/static/images/art/acrylic-pours/000.webp") } > <CardImage> <span className={styles.cardImage}> <Image src="/static/images/art/acrylic-pours/000.webp" alt="Blood in Waves"
@@ -162,337 +169,126 @@ const Art = () => { height={360} className="mouse-activate" /> </CardImage> <CardHeading> </span> <h2 className={styles.artItemHeader}> Blood in Waves <span>000</span> </CardHeading> </Card> </Cards> <Heading>Emergent Generative Art</Heading> <Paragraph> </h2> </div> </div> <h1 className={styles.heading}>Emergent Generative Art</h1> <p className={styles.paragraph}> Autonomously generated entities that are observed to have qualities in a group that they do not have on their own. </Paragraph> <Subheading> </p> <h2 className={styles.subheading}> <span>000</span> Constellations </Subheading> <Paragraph> </h2> <p className={styles.paragraph}> Moving stars, circles, on a canvas that attach to nearby stars, with a line, to generate constellations. Line opacity is based on star distance. </Paragraph> <ArtContainer> </p> <div className={styles.artContainer}> <Constellations options={{ numStars: 50, isActive: activeArt === "constellations" }} /> <PlayButton <button className={styles.playButton} onClick={() => handleArtToggle("constellations")} active={activeArt === "constellations"} data-active={activeArt === "constellations"} > {activeArt === "constellations" ? "⏸" : "▶"} </PlayButton> </ArtContainer> <Link </button> </div> <a className={styles.artItemButton} href="https://github.com/overshard/isaacbythewood.com/blob/master/components/constellations.js" rel="noopener noreferrer" target="_blank" > See the Code </Link> <Subheading> </a> <h2 className={styles.subheading}> <span>001</span> Retro Stars </Subheading> <Paragraph> </h2> <p className={styles.paragraph}> Multiple parallax planes of stars that shift based on cursor position. Inspired by the retro art style of Celeste. </Paragraph> <ArtContainer> </p> <div className={styles.artContainer}> <RetroStars options={{ numStars: 50, isActive: activeArt === "retrostars" }} /> <PlayButton <button className={styles.playButton} onClick={() => handleArtToggle("retrostars")} active={activeArt === "retrostars"} data-active={activeArt === "retrostars"} > {activeArt === "retrostars" ? "⏸" : "▶"} </PlayButton> </ArtContainer> <Link </button> </div> <a className={styles.artItemButton} href="https://github.com/overshard/isaacbythewood.com/blob/master/components/retrostars.js" rel="noopener noreferrer" target="_blank" > See the Code </Link> <Subheading> </a> <h2 className={styles.subheading}> <span>002</span> Particle Flow </Subheading> <Paragraph> </h2> <p className={styles.paragraph}> Colorful particles flowing through an invisible force field, creating organic, flowing patterns with trailing effects. Each particle follows the field while leaving a colorful trail that slowly fades. </Paragraph> <ArtContainer> </p> <div className={styles.artContainer}> <ParticleFlow options={{ numParticles: 80, isActive: activeArt === "particleflow" }} /> <PlayButton <button className={styles.playButton} onClick={() => handleArtToggle("particleflow")} active={activeArt === "particleflow"} data-active={activeArt === "particleflow"} > {activeArt === "particleflow" ? "⏸" : "▶"} </PlayButton> </ArtContainer> <Link </button> </div> <a className={styles.artItemButton} href="https://github.com/overshard/isaacbythewood.com/blob/master/components/particleflow.js" rel="noopener noreferrer" target="_blank" > See the Code </Link> </a> {lightboxImage !== null && ( <Lightbox onClick={() => closeLightbox()}> <LightboxLoading className={lightboxLoaded && "hide"}> <div className={styles.lightboxOverlay} onClick={() => closeLightbox()}> <div className={ styles.lightboxLoading + (lightboxLoaded ? " " + styles.hide : "") } > Loading... </LightboxLoading> <LightboxImage src={lightboxImage} className={lightboxLoaded && "show"} onLoad={() => setLightboxLoaded(true)} /> </Lightbox> </div> <span className={styles.lightboxImageWrapper}> <Image className={ styles.lightboxImage + (lightboxLoaded ? " " + styles.show : "") } src={lightboxImage} alt="Lightbox" layout="fill" sizes="90vw" objectFit="contain" onLoad={() => setLightboxLoaded(true)} /> </span> </div> )} </Page> );};export default Art;const Background = styled.div` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;`;const Heading = styled.h1` font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2.5em; color: black; &::before { content: ""; display: block; width: 50px; height: 5px; margin-bottom: 20px; background-color: ${(props) => props.theme.colors.blue}; }`;const Subheading = styled.h2` font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2em; color: black; position: relative; & > span { font-family: monospace; font-size: 0.8em; padding: 3px 9px; background-color: black; color: white; position: absolute; right: 100%; margin-right: 10px; top: 20%; @media (${(props) => props.theme.breakpoints.tablet}) { display: none; } }`;const Paragraph = styled.p` font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black;`;const ArtContainer = styled.div` width: 100%; height: 500px; max-height: 100vh; background: black; margin-bottom: 20px; position: relative; overflow: hidden;`;const Link = styled.a` padding: 10px 15px; font-size: 1.2em; text-decoration: none; background-color: black; margin-bottom: 20px; font-weight: 700; text-transform: uppercase; display: inline-block; letter-spacing: 2px; color: white; background-image: linear-gradient( to right, ${(props) => props.theme.colors.blue} 0, ${(props) => props.theme.colors.purple} 100% ); transform: scale(1); transition-duration: 250ms; transition-property: transform; &:hover { transform: scale(1.2); }`;const Cards = styled.div` display: flex; width: 100%; flex-wrap: wrap; gap: 20px;`;const Card = styled.div` background-color: black; color: white; box-shadow: 0 5px 0 rgba(0, 0, 0, 0); transition-duration: 200ms; transition-property: box-shadow; transition-timing-function: ease-in; width: calc(50% - 20px); display: flex; flex-direction: column; &:hover { box-shadow: 0 5px 25px rgba(0, 0, 0, 0.4); } @media (${(props) => props.theme.breakpoints.mobile}) { width: 100%; }`;const CardHeading = styled.h2` margin: 0; margin-top: -5px; padding: 10px; display: flex; justify-content: space-between; align-items: center; font-weight: 900; & > span { color: black; font-family: monospace; font-weight: 100; font-size: 0.9em; background-color: white; padding: 3px 7px; }`;const CardImage = styled.span` & img { object-fit: cover; object-position: center; width: 100%; height: 360px; }`;const Lightbox = styled.div` width: 100vw; height: 100vh; position: fixed; top: 0; bottom: 0; left: 0; right: 0; z-index: 10; background-color: rgba(0, 0, 0, 0.7); display: flex; justify-content: center; align-items: center;`;const LightboxImage = styled.img` position: absolute; max-width: 100%; max-height: 100%; padding: 5vw; box-sizing: border-box; visibility: hidden; opacity: 0; transition-property: opacity; transition-duration: 250ms; &.show { opacity: 1; visibility: visible; }`;const LightboxLoading = styled.div` position: absolute; max-width: 100%; max-height: 100%; box-sizing: border-box; color: white; font-family: monospace; font-size: 3em; visibility: visible; opacity: 1; transition-property: opacity; transition-duration: 250ms; &.hide { opacity: 0; visibility: hidden; }`;const PlayButton = styled.button` position: absolute; top: 20px; right: 20px; width: 60px; height: 60px; border: none; background: transparent; color: white; font-size: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 5; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8); &:hover { transform: scale(1.1); text-shadow: 0 4px 8px rgba(0, 0, 0, 0.9); } &:active { transform: scale(0.95); }`;
added
pages/art.module.css
@@ -0,0 +1,318 @@/* Art page styles migrated from styled-components */@keyframes transformRight { from { transform: scaleX(0); } to { transform: scaleX(1); }}@keyframes transformLeft { from { transform: scaleX(1); } to { transform: scaleX(0); }}.background { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;}.heading { font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2.5em; color: black;}.heading::before { content: ""; display: block; width: 50px; height: 5px; margin-bottom: 20px; background-color: var(--color-blue);}.paragraph { font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black;}.subheading { font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2em; color: black; position: relative;}.subheading > span { font-family: monospace; font-size: 0.8em; padding: 3px 9px; background-color: black; color: white; position: absolute; right: 100%; margin-right: 10px; top: 20%;}@media (max-width: 1023.98px) { .subheading > span { display: none; }}.paragraph a { color: black; text-decoration: none; position: relative; white-space: nowrap;}.paragraph a::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 1); transform-origin: left; animation: transformLeft 300ms normal forwards;}.paragraph a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 0.2);}.paragraph a:hover::before { animation: transformRight 300ms normal forwards;}.artGrid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;}@media (max-width: 1023.98px) { .artGrid { grid-template-columns: repeat(2, 1fr); }}@media (max-width: 767.98px) { .artGrid { grid-template-columns: 1fr; }}.artItem { background-color: black; color: white; box-shadow: 0 5px 0 rgba(0, 0, 0, 0); transition-duration: 200ms; transition-property: box-shadow; transition-timing-function: ease-in;}.artItem:hover { box-shadow: 0 5px 25px rgba(0, 0, 0, 0.4);}.artItemHeader { margin: 0; margin-top: -5px; padding: 10px; display: flex; justify-content: space-between; align-items: center; font-weight: 900;}.artItemHeader > span { color: black; font-family: monospace; font-weight: 100; font-size: 0.9em; background-color: white; padding: 3px 7px;}.artItemTitle { font-weight: 700;}.artItemButton { padding: 10px 15px; font-size: 1.2em; text-decoration: none; background-color: black; margin-bottom: 20px; font-weight: 700; text-transform: uppercase; display: inline-block; letter-spacing: 2px; color: white; background-image: linear-gradient( to right, var(--color-blue) 0, var(--color-purple) 100% ); transform: scale(1); transition-duration: var(--transition-250); transition-property: transform;}.artItemButton:hover { transform: scale(1.2);}.artItemImageWrapper { position: relative; width: 100%; padding-top: 66.6667%;}.artItemImage { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover;}/* Card image wrapper used in JSX as styles.cardImage */.cardImage img { object-fit: cover; object-position: center; width: 100%; height: 360px;}.artContainer { width: 100%; height: 500px; max-height: 100vh; background: black; margin-bottom: 20px; position: relative; overflow: hidden;}.playButton { position: absolute; top: 20px; right: 20px; width: 60px; height: 60px; border: none; background: transparent; color: white; font-size: 32px; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease; z-index: 5; text-shadow: 0 2px 4px rgba(0, 0, 0, 0.8);}.playButton:hover { transform: scale(1.1); text-shadow: 0 4px 8px rgba(0, 0, 0, 0.9);}.playButton:active { transform: scale(0.95);}.lightboxOverlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.85); display: flex; align-items: center; justify-content: center; z-index: 1000; opacity: 1; pointer-events: auto;}.lightboxImage { visibility: hidden; opacity: 0; transition-property: opacity; transition-duration: var(--transition-250);}.lightboxLoading { position: absolute; max-width: 100%; max-height: 100%; box-sizing: border-box; color: white; font-family: monospace; font-size: 3em; visibility: visible; opacity: 1; transition-property: opacity; transition-duration: var(--transition-250);}.show { opacity: 1; visibility: visible;}.hide { opacity: 0; visibility: hidden;}/* Lightbox image wrapper ensures centering and sizing for Next/Image with layout=fill */.lightboxImageWrapper { position: relative; width: 90vw; height: 90vh;}.closeButton { position: absolute; top: 20px; right: 20px; background: transparent; border: none; color: white; font-size: 2rem; cursor: pointer;}
@@ -1,6 +1,6 @@import React from "react";import styled, { keyframes } from "styled-components";import PropTypes from "prop-types";import styles from "./code.module.css";import Page from "../components/page";
@@ -21,9 +21,9 @@ const GitHubIcon = () => {const Code = ({ commits }) => { return ( <Page title="Code" description="Some of my most recent coding projects."> <Background /> <Heading>Code</Heading> <Paragraph> <div className={styles.background} /> <h1 className={styles.heading}>Code</h1> <p className={styles.paragraph}> A probably not entirely up-to-date list of my current side projects... There is plenty more to see on{" "} <a
@@ -34,187 +34,197 @@ const Code = ({ commits }) => { my GitHub account </a>{" "} and generally around the internet if you are interested. </Paragraph> <Projects> <Project> <ProjectHeading>Analytics</ProjectHeading> <ProjectParagraph> </p> <div className={styles.projects}> <div className={styles.project}> <h1 className={styles.projectHeading}>Analytics</h1> <p className={styles.projectParagraph}> A self-hostable analytics service with a straightforward API to track events from any source. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.analytics.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/analytics" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>Status</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>Status</h1> <p className={styles.projectParagraph}> A self-hosted status monitoring service. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.status.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/status" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>Blog</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>Blog</h1> <p className={styles.projectParagraph}> A self-hostable blog built on Wagtail targeted towards developers with code blocks, syntax highlighting, live search, great SEO, and a clean customizable UI. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.blog.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/blog" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>Timelite</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>Timelite</h1> <p className={styles.projectParagraph}> A simple time tracking progressive web app. Uses local storage and service workers to remain accessible offline. Sometimes you just need the essentials when you are busy. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.timelite.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/timelite" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>Timestrap</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>Timestrap</h1> <p className={styles.projectParagraph}> A full feature time tracking web app. Supports multiple users and exporting reports in multiple formats. Makes use of websockets to maintain state across clients. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.timestrap.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/timestrap" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>isaacbythewood.com</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>isaacbythewood.com</h1> <p className={styles.projectParagraph}> The personal website of Isaac Bythewood. So this site... </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.isaacbythewood.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/isaacbythewood.com" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>New Tab</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>New Tab</h1> <p className={styles.projectParagraph}> A clean new tab page extension for Chrome. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.newtab.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/newtab" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>dockerfiles</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>dockerfiles</h1> <p className={styles.projectParagraph}> All the Dockerfiles I use for various purposes. More detailed usage instructions are at the top of each Dockerfile. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.dockerfiles.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/dockerfiles" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>alpinefiles</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>alpinefiles</h1> <p className={styles.projectParagraph}> Some of the files that I use on my Alpine Linux servers. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.alpinefiles.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/alpinefiles" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> <Project> <ProjectHeading>dotfiles</ProjectHeading> <ProjectParagraph> </a> </div> <div className={styles.project}> <h1 className={styles.projectHeading}>dotfiles</h1> <p className={styles.projectParagraph}> A variety of config files for setting up new systems. </ProjectParagraph> <ProjectCommit> </p> <pre className={styles.projectCommit}> {JSON.stringify(commits.dotfiles.data, null, 2)} </ProjectCommit> <ProjectButton </pre> <a className={styles.projectButton} href="https://www.github.com/overshard/dotfiles" rel="noopener noreferrer" target="_blank" > <GitHubIcon /> GitHub </ProjectButton> </Project> </Projects> </a> </div> </div> </Page> );};
@@ -355,170 +365,3 @@ Code.propTypes = {};export default Code;const TransformRight = keyframes` from { transform: scaleX(0); } to { transform: scaleX(1); }`;const TransformLeft = keyframes` from { transform: scaleX(1); } to { transform: scaleX(0); }`;const Background = styled.div` position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;`;const Heading = styled.h1` font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2.5em; color: black; &::before { content: ""; display: block; width: 50px; height: 5px; margin-bottom: 20px; background-color: ${(props) => props.theme.colors.blue}; }`;const Paragraph = styled.p` font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black; a { color: black; text-decoration: none; position: relative; white-space: nowrap; &::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 1); transform-origin: left; animation: ${TransformLeft} 300ms normal forwards; } &::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 0.2); } &:hover { &::before { animation: ${TransformRight} 300ms normal forwards; } } }`;const Projects = styled.div` display: flex; width: 100%; flex-wrap: wrap; gap: 20px;`;const Project = styled.div` width: calc(50% - 20px); display: flex; flex-direction: column; @media (${(props) => props.theme.breakpoints.mobile}) { width: 100%; }`;const ProjectHeading = styled.h1` font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2em; color: black;`;const ProjectParagraph = styled.p` font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black; flex-grow: 1;`;const ProjectButton = styled.a` padding: 10px 15px; font-size: 1.2em; text-decoration: none; background-color: black; margin-bottom: 20px; font-weight: 700; text-transform: uppercase; display: inline-block; letter-spacing: 2px; color: white; background-image: linear-gradient( to right, ${(props) => props.theme.colors.blue} 0, ${(props) => props.theme.colors.purple} 100% ); transform: scale(1); transition-duration: 250ms; transition-property: transform; width: 125px; text-align: center; display: flex; align-items: center; & svg { margin-right: 10px; } &:hover { transform: scale(1.2); }`;const ProjectCommit = styled.pre` font-family: monospace; background: black; color: #00ff00; padding: 20px; overflow-x: hidden; max-width: 100%; text-overflow: ellipsis;`;
added
pages/code.module.css
@@ -0,0 +1,166 @@/* Code page styles migrated from styled-components */@keyframes transformRight { from { transform: scaleX(0); } to { transform: scaleX(1); }}@keyframes transformLeft { from { transform: scaleX(1); } to { transform: scaleX(0); }}.background { position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;}.heading { font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2.5em; color: black;}.heading::before { content: ""; display: block; width: 50px; height: 5px; margin-bottom: 20px; background-color: var(--color-blue);}.paragraph { font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black;}.paragraph a { color: black; text-decoration: none; position: relative; white-space: nowrap;}.paragraph a::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 1); transform-origin: left; animation: transformLeft 300ms normal forwards;}.paragraph a::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(0, 0, 0, 0.2);}.paragraph a:hover::before { animation: transformRight 300ms normal forwards;}.projects { display: flex; width: 100%; flex-wrap: wrap; gap: 20px;}.project { width: calc(50% - 20px); display: flex; flex-direction: column;}@media (max-width: 767.98px) { .project { width: 100%; }}.projectHeading { font-weight: 700; margin-top: 60px; margin-bottom: 20px; font-size: 2em; color: black;}.projectParagraph { font-size: 1.5em; margin-top: 0; margin-bottom: 20px; font-weight: 300; color: black; flex-grow: 1;}.projectButton { padding: 10px 15px; font-size: 1.2em; text-decoration: none; background-color: black; margin-bottom: 20px; font-weight: 700; text-transform: uppercase; display: inline-flex; align-items: center; justify-content: center; letter-spacing: 2px; color: white; background-image: linear-gradient( to right, var(--color-blue) 0, var(--color-purple) 100% ); transform: scale(1); transition-duration: 250ms; transition-property: transform; width: 125px; text-align: center;}.projectButton svg { margin-right: 10px;}.projectButton:hover { transform: scale(1.2);}.projectCommit { font-family: monospace; background: black; color: #00ff00; padding: 20px; overflow-x: hidden; max-width: 100%; text-overflow: ellipsis;}
modified
pages/contact.js
@@ -1,6 +1,7 @@import React from "react";import styled, { keyframes } from "styled-components";import { TransitionGroup, CSSTransition } from "react-transition-group";import styles from "./contact.module.css";import Image from "next/image";import Page from "../components/page";
@@ -19,55 +20,61 @@ const Contact = () => { description="How to get in contact with me." gridArea="1 / 1 / 4 / 7" > <Background /> <Grid> <GridLeft> <ContactWrapper> <Heading>Contact Me</Heading> <ShoutOut>I love chatting</ShoutOut> <ShoutOut>with other developers!</ShoutOut> <ContactList> <ContactKey>Email</ContactKey> <ContactValue> <ContactLink href="mailto:isaac@bythewood.me"> <div className={styles.background} /> <div className={styles.grid}> <div className={styles.gridLeft}> <div className={styles.contactWrapper}> <h1 className={styles.heading}>Contact Me</h1> <h2 className={styles.shoutOut}>I love chatting</h2> <h2 className={styles.shoutOut}>with other developers!</h2> <dl className={styles.contactList}> <dt className={styles.contactKey}>Email</dt> <dd className={styles.contactValue}> <a className={styles.contactLink} href="mailto:isaac@bythewood.me" > isaac@bythewood.com </ContactLink> </ContactValue> <ContactKey>LinkedIn</ContactKey> <ContactValue> <ContactLink </a> </dd> <dt className={styles.contactKey}>LinkedIn</dt> <dd className={styles.contactValue}> <a className={styles.contactLink} href="https://www.linkedin.com/in/isaac-bythewood/" rel="noopener noreferrer" target="_blank" > Isaac Bythewood </ContactLink> </ContactValue> <ContactKey>GitHub</ContactKey> <ContactValue> <ContactLink </a> </dd> <dt className={styles.contactKey}>GitHub</dt> <dd className={styles.contactValue}> <a className={styles.contactLink} href="https://github.com/overshard" rel="noopener noreferrer" target="_blank" > /overshard </ContactLink> </ContactValue> <ContactKey>Discord</ContactKey> <ContactValue> <ContactLink </a> </dd> <dt className={styles.contactKey}>Discord</dt> <dd className={styles.contactValue}> <a className={styles.contactLink} href="https://discordapp.com/" rel="noopener noreferrer" target="_blank" > Overshard#4907 </ContactLink> </ContactValue> </ContactList> </ContactWrapper> </GridLeft> <GridRight> <Chat> </a> </dd> </dl> </div> </div> <div className={styles.gridRight}> <div className={styles.chat}> <TransitionGroup component={null}> {chatMessages.map((message, index) => { const transitionTimeout = (index + 2) * 2000;
@@ -79,277 +86,32 @@ const Contact = () => { timeout={{ appear: transitionTimeout }} classNames="fade" > <ChatLine <div className={styles.chatLine} style={{ transitionDelay: `${transitionDelay}ms` }} > <ChatAvatar src="/static/images/avatar.webp" alt="Chat Avatar" /> <ChatMessage> <span className={styles.chatAvatar}> <Image src="/static/images/avatar.webp" alt="Chat Avatar" width={50} height={50} /> </span> <div className={styles.chatBubble}> {message} <span>Isaac</span> </ChatMessage> </ChatLine> </div> </div> </CSSTransition> ); })} </TransitionGroup> </Chat> </GridRight> </Grid> </div> </div> </div> </Page> );};export default Contact;const SlideUp = keyframes` from { opacity: 0; transform: translateX(-100px); } to { opacity: 1; transform: translateY(0); }`;const TransformRight = keyframes` from { transform: scaleX(0); } to { transform: scaleX(1); }`;const TransformLeft = keyframes` from { transform: scaleX(1); } to { transform: scaleX(0); }`;const Background = styled.div` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;`;const Grid = styled.div` display: grid; grid-template-columns: 50vw 1fr; grid-template-rows: auto; grid-template-areas: "left right"; margin-left: 60px; transform: translateX(-100vw); animation: ${SlideUp} 750ms 500ms forwards; @media (${(props) => props.theme.breakpoints.tablet}) { margin-left: 0; grid-template-columns: 1fr; grid-template-rows: auto auto; margin-top: 60px; }`;const GridColumn = styled.div` min-height: 100vh; display: flex; align-items: center; @media (${(props) => props.theme.breakpoints.tablet}) { min-height: auto; }`;const GridLeft = styled(GridColumn)` grid-area: left; background-color: rgba(0, 0, 0, 0.9); @media (${(props) => props.theme.breakpoints.tablet}) { grid-column: 1; grid-row: 1; }`;const GridRight = styled(GridColumn)` grid-area: right; @media (${(props) => props.theme.breakpoints.tablet}) { grid-column: 1; grid-row: 2; }`;const ContactWrapper = styled.div` padding-left: 60px; @media (${(props) => props.theme.breakpoints.tablet}) { min-height: auto; padding-top: 30px; padding-bottom: 30px; }`;const Heading = styled.h1` font-size: 3em; margin-top: 10px; margin-bottom: 20px; &::before { content: ""; display: block; width: 75px; height: 5px; margin-bottom: 10px; background-color: ${(props) => props.theme.colors.blue}; } @media (${(props) => props.theme.breakpoints.mobile}) { margin-bottom: 10px; }`;const ShoutOut = styled.h2` text-transform: uppercase; padding: 5px; margin: 0 0 2px 0; float: left; clear: left; font-size: 1.4em; background-image: linear-gradient( to right, ${(props) => props.theme.colors.blue} 0, ${(props) => props.theme.colors.purple} 100% ); @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 1.1em; }`;const ContactList = styled.dl` padding-top: 30px; clear: both;`;const ContactKey = styled.dt` text-transform: uppercase; font-size: 0.8em; font-weight: 700; opacity: 0.7;`;const ContactValue = styled.dd` font-size: 1.5em; margin-left: 0; margin-bottom: 30px;`;const ContactLink = styled.a` color: white; text-decoration: none; position: relative; &::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(255, 255, 255, 1); transform-origin: left; animation: ${TransformLeft} 300ms normal forwards; } &::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(255, 255, 255, 0.2); } &:hover { &::before { animation: ${TransformRight} 300ms normal forwards; } }`;const Chat = styled.div` margin: 30px 0; display: flex; flex-direction: column;`;const ChatLine = styled.div` display: flex; margin-bottom: 20px; justify-content: center; opacity: 0; &.fade-appear { opacity: 0; transform: translateX(-100px); } &.fade-appear-active { opacity: 1; transform: translateX(0); transition-duration: 250ms; transition-property: opacity, transform; } &.fade-appear-done { opacity: 1; }`;const ChatMessage = styled.div` font-size: 1.2em; padding: 10px 20px; background-color: rgba(0, 0, 0, 0.7); width: 65%; @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 1em; width: 70%; } & span { display: block; font-size: 0.5em; text-transform: uppercase; margin-top: 5px; color: rgba(255, 255, 255, 0.7); letter-spacing: 1px; font-weight: 800; }`;const ChatAvatar = styled.img` margin-right: 20px; width: 50px; height: 50px; @media (${(props) => props.theme.breakpoints.mobile}) { margin-right: 10px; width: 40px; height: 40px; }`;
added
pages/contact.module.css
@@ -0,0 +1,258 @@/* Contact page styles migrated from styled-components */@keyframes transformRight { from { transform: scaleX(0); } to { transform: scaleX(1); }}@keyframes transformLeft { from { transform: scaleX(1); } to { transform: scaleX(0); }}@keyframes slideUp { from { opacity: 0; transform: translateX(-100vw); } to { opacity: 1; transform: translateX(0); }}.background { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: white; z-index: -2;}.grid { display: grid; grid-template-columns: 50vw 1fr; grid-template-rows: auto; grid-template-areas: "left right"; margin-right: 60px; transform: translateX(-100vw); animation: slideUp 750ms 500ms forwards;}@media (max-width: 1023.98px) { .grid { margin-right: 0; grid-template-columns: 1fr; grid-template-rows: auto auto; margin-top: 60px; }}/* Former GridColumn shared styles applied directly to left/right columns */.gridLeft { grid-area: left; background-color: rgba(0, 0, 0, 0.9); min-height: 100vh; display: flex; align-items: center; padding-left: 60px;}.gridRight { grid-area: right; min-height: 100vh; display: flex; align-items: center;}@media (max-width: 1023.98px) { .gridLeft, .gridRight { min-height: auto; }}.contactWrapper { padding-left: 60px; /* match legacy inner offset so content doesn't hug the sidebar */}@media (max-width: 1023.98px) { .contactWrapper { min-height: auto; padding-top: 30px; padding-bottom: 30px; }}.heading { font-size: 3em; margin-top: 10px; margin-bottom: 20px;}.heading::before { content: ""; display: block; width: 75px; height: 5px; margin-bottom: 10px; background-color: var(--color-blue);}@media (max-width: 767.98px) { .heading { margin-bottom: 10px; }}.shoutOut { text-transform: uppercase; padding: 5px; margin: 0 0 2px 0; float: left; clear: left; font-size: 1.4em; background-image: linear-gradient( to right, var(--color-blue) 0, var(--color-purple) 100% );}@media (max-width: 767.98px) { .shoutOut { font-size: 1.1em; }}.contactList { padding-top: 30px; clear: both;}.contactKey { text-transform: uppercase; font-size: 0.8em; font-weight: 700; opacity: 0.7;}.contactValue { font-size: 1.5em; margin-left: 0; margin-bottom: 30px;}.contactLink { color: white; text-decoration: none; position: relative;}.contactLink::before { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(255, 255, 255, 1); transform-origin: left; animation: transformLeft 300ms normal forwards;}.contactLink::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; background: rgba(255, 255, 255, 0.2);}.contactLink:hover::before { animation: transformRight 300ms normal forwards;}.chat { margin: 30px 0; display: flex; flex-direction: column;}.chatLine { display: flex; margin-bottom: 20px; justify-content: center; opacity: 0;}/* Fade-in transitions for chat lines (react-transition-group classNames="fade") */.chatLine:global(.fade-appear) { opacity: 0; transform: translateX(-100px);}.chatLine:global(.fade-appear-active) { opacity: 1; transform: translateX(0); transition-property: opacity, transform; transition-duration: 250ms;}.chatLine:global(.fade-appear-done) { opacity: 1;}.chatBubble { font-size: 1.2em; padding: 10px 20px; background-color: rgba(0, 0, 0, 0.7); color: white; width: 65%;}@media (max-width: 767.98px) { .chatBubble { font-size: 1em; width: 70%; }}.chatBubble span { display: block; font-size: 0.5em; text-transform: uppercase; margin-top: 5px; color: rgba(255, 255, 255, 0.7); letter-spacing: 1px; font-weight: 800;}/* removed conflicting duplicate chat styles *//* Restore avatar spacing/sizing per legacy */.chatAvatar { margin-right: 20px; width: 50px; height: 50px; display: inline-flex;}@media (max-width: 767.98px) { .chatAvatar { margin-right: 10px; width: 40px; height: 40px; }}
@@ -1,9 +1,9 @@import React, { useEffect, useState, useRef } from "react";import styled, { keyframes } from "styled-components";import { TransitionGroup, CSSTransition } from "react-transition-group";import Image from "next/image";import Page from "../components/page";import styles from "./index.module.css";const Index = () => { const words = ["Developer", "SysAdmin", "DevOps", "Consultant"];
@@ -26,153 +26,49 @@ const Index = () => { return ( <Page title="Senior Solutions Architect located in Elkin, NC"> <ImageWrapper> <div className={styles.imageWrapper}> <Image src="/static/images/art/acrylic-pours/005.webp" alt="#005 Nebulas in Triangulum" loading="eager" layout="fill" priority={true} objectFit="cover" objectPosition="center" /> </ImageWrapper> <TransitionGroup component={Words}> </div> <TransitionGroup component="div" className={styles.words}> {currentWords.map((word) => { return ( <CSSTransition key={word} timeout={1000} classNames="transition"> <Word>{word}</Word> <CSSTransition key={word} timeout={1000} classNames={{ appear: styles.wordAppear, appearActive: styles.wordAppearActive, appearDone: styles.wordAppearDone, enter: styles.wordEnter, enterActive: styles.wordEnterActive, enterDone: styles.wordEnterDone, exit: styles.wordExit, exitActive: styles.wordExitActive, }} appear > <h3 className={styles.word}>{word}</h3> </CSSTransition> ); })} </TransitionGroup> <Description>Senior Solutions Architect located in Elkin, NC</Description> <Name>Isaac</Name> <Name style={{ animationDelay: "100ms" }}>Bythewood</Name> <h1 className={styles.description}> Senior Solutions Architect located in Elkin, NC </h1> <h2 className={styles.name}>Isaac</h2> <h2 className={styles.name} style={{ animationDelay: "100ms" }}> Bythewood </h2> </Page> );};export default Index;const FadeStart = keyframes` from { opacity: 0; } to { opacity: 1; }`;const SlideStart = keyframes` from { transform: translateX(-100vw); } to { transform: translateX(0); }`;const ImageWrapper = styled.div` img { object-fit: cover; object-position: center; }`;const Words = styled.div` position: absolute; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100vh; display: flex; align-items: center; justify-content: center; z-index: 10; mix-blend-mode: darken; background: rgba(0, 0, 0, 0.8);`;const Word = styled.h3` opacity: 0.2; font-size: 14vw; text-transform: uppercase; margin: 0; position: absolute; &.transition-appear, &.transition-enter { opacity: 0; transform: translateY(-200px); } &.transition-appear-active, &.transition-enter-active { opacity: 0.2; transform: translateY(0); transition-duration: 1000ms; transition-property: opacity, transform; } &.transition-appear-done, &.transition-enter-done { opacity: 0.2; } &.transition-exit { transform: translateY(0); opacity: 0.2; } &.transition-exit-active { opacity: 0; transform: translateY(200px); transition-duration: 1000ms; transition-property: opacity, transform; }`;const Description = styled.h1` font-size: 3.5em; font-weight: bolder; opacity: 0; animation-name: ${FadeStart}; animation-duration: 1500ms; animation-fill-mode: forwards; position: relative; z-index: 20; &::before { content: ""; display: block; width: 75px; height: 5px; margin-bottom: 20px; background-color: ${(props) => props.theme.colors.blue}; } @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 2em; }`;const Name = styled.h2` font-size: 2.5em; text-transform: uppercase; padding: 5px; margin: 0 0 2px 0; float: left; clear: left; background-image: linear-gradient( to right, ${(props) => props.theme.colors.blue} 0, ${(props) => props.theme.colors.purple} 100% ); transform: translateX(-100vw); animation-name: ${SlideStart}; animation-duration: 750ms; animation-fill-mode: forwards; position: relative; z-index: 20; @media (${(props) => props.theme.breakpoints.mobile}) { font-size: 1.5em; }`;
added
pages/index.module.css
@@ -0,0 +1,139 @@/* Landing page styles migrated from styled-components */@keyframes fadeStart { from { opacity: 0; } to { opacity: 1; }}@keyframes slideStart { from { transform: translateX(-100vw); } to { transform: translateX(0); }}.imageWrapper { position: fixed; top: 0; left: 0; right: 0; bottom: 0; width: 100%; height: 100vh; z-index: 0; pointer-events: none;}.words { position: fixed; top: 0; bottom: 0; left: 0; right: 0; width: 100%; height: 100vh; display: flex; align-items: center; justify-content: center; z-index: 10; mix-blend-mode: darken; background: rgba(0, 0, 0, 0.8);}.word { opacity: 0.2; font-size: 14vw; text-transform: uppercase; margin: 0; position: absolute;}/* CSSTransition mappings for the word animation */.wordAppear,.wordEnter { opacity: 0; transform: translateY(-200px);}.wordAppearActive,.wordEnterActive { opacity: 0.2; transform: translateY(0); transition-duration: 1000ms; transition-property: opacity, transform;}.wordAppearDone,.wordEnterDone { opacity: 0.2;}.wordExit { transform: translateY(0); opacity: 0.2;}.wordExitActive { opacity: 0; transform: translateY(200px); transition-duration: 1000ms; transition-property: opacity, transform;}.description { font-size: 3.5em; font-weight: bolder; opacity: 0; animation-name: fadeStart; animation-duration: 1500ms; animation-fill-mode: forwards; position: relative; z-index: 20;}.description::before { content: ""; display: block; width: 75px; height: 5px; margin: 0 0 20px 0; background-color: var(--color-blue);}@media (max-width: 767.98px) { .description { font-size: 2em; }}.name { font-size: 2.5em; text-transform: uppercase; padding: 5px; margin: 0 0 2px 0; /* centered via parent flex container */ align-self: flex-start; background-image: linear-gradient( to right, var(--color-blue) 0, var(--color-purple) 100% ); transform: translateX(-100vw); animation-name: slideStart; animation-duration: 750ms; animation-fill-mode: forwards; position: relative; z-index: 20;}@media (max-width: 767.98px) { .name { font-size: 1.5em; }}
@@ -0,0 +1,54 @@:root { /* Design tokens (used by CSS Modules as var(--token)) */ --color-primary: #20232e; --color-blue: #0e3ff4; --color-purple: #842bff; --radius-2: 3px; --transition-250: 250ms; --animation-1500: 1500ms;}/* Base global styles (migrated from styled-components GlobalStyle) */body { font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; color: white; background-color: var(--color-primary); min-height: 100vh; width: 100%; padding: 0; margin: 0; overflow-x: hidden; text-shadow: rgba(0, 0, 0, 0.01) 0 0 1px; text-rendering: optimizeLegibility; user-select: none; cursor: none;}a,button { cursor: none;}/* React Transition Group classes (global) */.transition { position: absolute; top: 0; left: 0; right: 0; transition: opacity var(--transition-250);}.transition-enter { opacity: 0;}.transition-enter-active,.transition-enter-done { opacity: 1;}.transition-exit { opacity: 1;}.transition-exit-active { opacity: 0;}