Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

# testing
/coverage
/screenshots/

# next.js
/.next/
Expand Down
49 changes: 29 additions & 20 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import React, { useState } from 'react';
import styles from '../styles/Footer.module.scss';

export default function Footer(): JSX.Element {

const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const onSubmit = async (e) => {

const onSubmit = async (e: React.FormEvent<HTMLFormElement>): Promise<void> => {
e.preventDefault();
if(name && email && message) {
if (name && email && message) {
const res = await window.fetch('/api/contact', {
method: 'POST',
headers: {
Expand All @@ -27,31 +27,40 @@ export default function Footer(): JSX.Element {
};

return (
<footer id={styles.footer}>
<h4>WORK WITH ME</h4>
<p>
Got an idea? If you have a project in mind,
please reach out below or send me an email.
I’ll respond within 24 hours.
</p>
<footer id="contact" className={styles.footer}>
<div className={styles.footerIntro}>
<p>WORK WITH ME</p>
<h2>Have a product, prototype, or polished interface in mind?</h2>
<span>
Tell me what you are building and I’ll respond with questions, next steps, or a good coffee recommendation.
</span>
</div>

<form className={styles.contact} onSubmit={onSubmit} >
<div style={{display: 'flex', columnGap: '20px' }} >
<form className={styles.contact} onSubmit={onSubmit}>
<div className={styles.row}>
<div className={styles.group}>
<label htmlFor='name'>Name</label>
<input type='text' value={name} placeholder='Name' onChange={(e) => setName(e.target.value)}/>
<label htmlFor="name">Name</label>
<input id="name" type="text" value={name} placeholder="Your name" onChange={(e) => setName(e.target.value)} />
</div>
<div className={styles.group}>
<label htmlFor='email'>Email</label>
<input type='email' value={email} placeholder='Email' onChange={(e) => setEmail(e.target.value)} />
<label htmlFor="email">Email</label>
<input id="email" type="email" value={email} placeholder="Email address" onChange={(e) => setEmail(e.target.value)} />
</div>
</div>
<div className={styles.group}>
<label htmlFor='message'>Message</label>
<textarea placeholder='Blah blah blah' value={message} onChange={(e) => setMessage(e.target.value)} />
<label htmlFor="message">Message</label>
<textarea id="message" placeholder="What should we make?" value={message} onChange={(e) => setMessage(e.target.value)} />
</div>
<input type="submit" value="SUBMIT" className={styles.submit} />
</form>

<div className={styles.footerMeta}>
<strong>BRYAN PAN</strong>
<div>
<a href="https://www.linkedin.com/in/blpan/" target="_blank" rel="noreferrer">LinkedIn</a>
<a href="https://github.com/BryanPan342" target="_blank" rel="noreferrer">GitHub</a>
</div>
<input type='submit' value='SUBMIT' id={styles.submit}/>
</form >
</div>
</footer>
);
}
17 changes: 10 additions & 7 deletions components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@ import React from 'react';
import styles from '../styles/Navbar.module.scss';
import Link from './Link';


export default function Navbar(): JSX.Element {
return (
<header id={styles.navbar}>
<Link href="/" className={styles.icon}>
<Image src="/icon.png" layout="responsive" width="53.53px" height="40px" />
<Image src="/icon.png" layout="responsive" width="53.53px" height="40px" alt="Bryan Pan monogram" />
</Link>
<div id={styles.links}>
<nav id={styles.links} aria-label="Primary navigation">
<Link href="/#work">WORK</Link>
<Link href="/about">ABOUT</Link>
<button>
CONTACT
</button>
</div>
<a href="https://www.linkedin.com/in/blpan/" target="_blank" rel="noreferrer">
LINKEDIN
</a>
<a className="button-link" href="#contact">
LET&apos;S WORK TOGETHER
</a>
</nav>
</header>
);
}
29 changes: 21 additions & 8 deletions components/Project.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
import React from 'react';
import styles from '../styles/Project.module.scss';
import Link from './Link';

export interface ProjectProps {
readonly body: string;
readonly title: string;
readonly color: string;
readonly cta?: string;
readonly ctaLabel?: string;
readonly eyebrow?: string;
readonly tags?: string[];
}

export default function Project({body, color, cta, title}: ProjectProps): JSX.Element {

export default function Project({body, color, cta, ctaLabel, eyebrow, tags, title}: ProjectProps): JSX.Element {
return (
<div className={styles['project-card']}>
<div className={styles['project-text']}>
<article className={styles.projectCard}>
<div className={styles.projectText}>
{eyebrow && <span>{eyebrow}</span>}
<h4>{title}</h4>
<p>{body}</p>
{cta && <Link href={cta}>LEARN MORE</Link>}
{tags && (
<ul>
{tags.map((tag: string) => <li key={tag}>{tag}</li>)}
</ul>
)}
{cta && (
<a href={cta} target={cta.startsWith('http') ? '_blank' : undefined} rel="noreferrer">
{ctaLabel ?? 'LEARN MORE'}
</a>
)}
</div>

<div className={styles['project-art']} style={{backgroundColor: color}}>
<div className={styles.projectArt} style={{backgroundColor: color}}>
<div />
<div />
</div>
</div>
</article>
);
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"stylelint": "npx stylelint '**/*.css' '**/*.scss'",
"lint": "yarn run eslint && yarn run stylelint",
"lint:fix": "yarn run eslint --fix && yarn run stylelint --fix",
"no-lock": "scripts/noPackageLock.sh"
"no-lock": "scripts/noPackageLock.sh",
"screenshot": "node scripts/screenshot.js"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare Playwright for the new screenshot script

In a clean checkout, yarn run screenshot invokes this new script, but scripts/screenshot.js calls require('playwright') and playwright is not listed in dependencies or devDependencies. That means the visual-regression aid exits immediately unless a developer has manually installed an undeclared package, so the new package script is not reproducible from the committed lockfile.

Useful? React with 👍 / 👎.

},
"dependencies": {
"dotenv": "^16.0.0",
Expand Down
62 changes: 57 additions & 5 deletions pages/about.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,65 @@ import React from 'react';
import Layout from '../components/Layout';
import styles from '../styles/About.module.scss';

export default function Home(): JSX.Element {
const skills = [
'Frontend architecture',
'Product engineering',
'Design systems',
'Rapid prototyping',
'Cross-functional collaboration',
'User-centered polish',
];

const principles = [
{
title: 'Make it useful',
body: 'Start with the human problem, reduce ambiguity, and build the smallest thing that proves value.',
},
{
title: 'Make it beautiful',
body: 'Use typography, hierarchy, and motion restraint to make software feel calm and intentional.',
},
{
title: 'Make it shippable',
body: 'Balance craft with pragmatic engineering so ideas become reliable products people can use.',
},
];

export default function About(): JSX.Element {
return (
<Layout id={styles.container}>
<div id={styles.splash}>
<h3>ABOUT</h3>
<Layout
id={styles.container}
title="About Bryan Pan"
description="Learn more about Bryan Pan, a Seattle-based software engineer focused on thoughtful software, polished interfaces, and creative technical systems."
>
<section id={styles.splash}>
<p className={styles.eyebrow}>ABOUT</p>
<h1>Hi, I’m Bryan [潘博瑞]</h1>
</div>
<p className={styles.bio}>
I’m a Seattle-based software engineer with experience in product environments like Meta Reality Labs
and a UCLA foundation. I love building software for others, meeting new people, and drinking coffee.
</p>
</section>

<section className={styles.story}>
<div>
<p className={styles.eyebrow}>SKILLS</p>
<h2>My magical abilities.</h2>
</div>
<ul>
{skills.map((skill: string) => <li key={skill}>{skill}</li>)}
</ul>
</section>

<section className={styles.principles} aria-label="Working principles">
{principles.map(({body, title}) => (
<article key={title}>
<span />
<h3>{title}</h3>
<p>{body}</p>
</article>
))}
</section>
</Layout>
);
}
117 changes: 89 additions & 28 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,109 @@ export interface HomeProps {
readonly projects: ProjectProps[];
}

const capabilities = [
'Product-minded engineering',
'Design systems & polished interfaces',
'Prototyping, shipping, and iteration',
];

export default function Home({projects}: HomeProps): JSX.Element {
return (
<Layout id={styles.container}>
<div id={styles.splash}>
<div id={styles['splash-text']}>
<h3>BRYAN PAN</h3>
<h1>Building cool shit with magic.</h1>
</div>
<div id={styles.developer} className={styles['background-text']}>
DEVELOPER
</div>
<div id={styles.designer} className={styles['background-text']}>
DESIGNER
</div>
<div id={styles.magic}>
<Image src="/magic.png" layout="responsive" width="293.96px" height="230.81px" />
<Layout
id={styles.container}
title="Bryan Pan — Software engineer and creative builder"
description="Bryan Pan is a Seattle-based software engineer who builds thoughtful products, polished interfaces, and creative technical systems."
>
<section id={styles.splash}>
<div id={styles.splashText}>
<p className={styles.eyebrow}>BRYAN PAN · SEATTLE, WA</p>
<h1>I build thoughtful software with a little magic.</h1>
<p className={styles.intro}>
I’m a software engineer who loves building for other people, meeting new collaborators,
and turning ambiguous ideas into calm, useful product experiences.
</p>
<div className={styles.actions}>
<a className="button-link" href="#work">VIEW WORK</a>
<a href="https://www.linkedin.com/in/blpan/" target="_blank" rel="noreferrer">
CONNECT ON LINKEDIN
</a>
</div>
</div>
</div>
<div id={styles['work-container']}>
<div id={styles['work-header']}>
<h3>WORK</h3>
<h1>Selected feats of magic</h1>

<aside id={styles.heroCard} aria-label="Bryan Pan profile summary">
<div id={styles.magic}>
<Image src="/magic.png" layout="responsive" width="293.96px" height="230.81px" alt="Portrait of Bryan Pan" priority />
</div>
<div className={styles.cardCopy}>
<span>Currently</span>
<strong>Building human-centered software across product, web, and immersive experiences.</strong>
</div>
</aside>

<div id={styles.developer} className={styles.backgroundText}>DEVELOPER</div>
<div id={styles.designer} className={styles.backgroundText}>DESIGNER</div>
</section>

<section className={styles.capabilities} aria-label="Core capabilities">
{capabilities.map((capability: string) => (
<div key={capability}>
<span />
<p>{capability}</p>
</div>
))}
</section>

<section id="work" className={styles.workContainer}>
<div id={styles.workHeader}>
<p className={styles.eyebrow}>WORK</p>
<h2>Sorcery performed in real life.</h2>
<p>
A first pass at reframing the portfolio around professional outcomes: systems that feel polished,
products that ship, and interfaces with a point of view.
</p>
</div>
<div id={styles.projects}>
{projects.map((props: ProjectProps) => <Project key={props.title} {...props} />)}
</div>
</div>
</section>
</Layout>
);
}

const dummyProject: ProjectProps = {
title: 'PROJECT NAME',
body: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi ex tortor, commodo in dolor aliquet, tempus fringilla ipsum.',
color: '#C4C4C4',
cta: 'bryanpan.co',
};

export const getStaticProps: GetStaticProps = () => {
const projects: ProjectProps[] = [
{
title: 'Reality Labs product engineering',
eyebrow: 'Experience',
body: 'Contributed software craft in a research-heavy product environment where interaction quality, reliability, and cross-functional execution matter.',
color: '#8b5a89',
tags: ['Meta', 'Product', 'Systems'],
cta: 'https://www.linkedin.com/in/blpan/',
ctaLabel: 'View profile',
},
{
title: 'Portfolio design system',
eyebrow: 'Brand & web',
body: 'Translated a high-fidelity visual direction into reusable typography, color, layout, and component patterns for a more mature personal brand.',
color: '#f6ca56',
tags: ['Next.js', 'Design system', 'Frontend'],
cta: '/about',
ctaLabel: 'About Bryan',
},
{
title: 'Creative technical prototypes',
eyebrow: 'Practice',
body: 'Explorations that blend engineering discipline with playful interfaces—the kind of work that makes complex tools feel approachable.',
color: '#438fd4',
tags: ['Prototyping', 'UI polish', 'Iteration'],
cta: 'https://github.com/BryanPan342',
ctaLabel: 'Open GitHub',
},
];

return {
props: {
projects: Array(5).fill(dummyProject),
projects,
},
};
};
8 changes: 8 additions & 0 deletions screenshots/about-desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading