Redesigned work page and added about page

This commit is contained in:
Justin Edmund 2025-05-26 12:56:35 -07:00
parent 49bde18f27
commit c01163cecc
7 changed files with 281 additions and 196 deletions

View file

@ -5,7 +5,7 @@
<header class="site-header">
<div class="header-content">
<a href="/" class="header-link" aria-label="@jedmund">
<a href="/about" class="header-link" aria-label="@jedmund">
<Avatar />
</a>
<SegmentedController />

View file

@ -13,14 +13,15 @@
<style lang="scss">
.page {
background: var(--page-color);
border-radius: 16px;
border-radius: $card-corner-radius; // Match universe posts
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: $unit-4x;
margin: $unit-6x auto $unit-6x;
padding: $unit-5x;
max-width: 784px;
width: 100%;
max-width: 700px;
&:first-child {
margin-top: 0;

View file

@ -6,50 +6,100 @@
export let backgroundColor: string
export let name: string
export let description: string
export let highlightColor: string
export let index: number = 0
$: isEven = index % 2 === 0
// Create highlighted description
$: highlightedDescription = description.replace(
new RegExp(`(${name})`, 'gi'),
`<span style="color: ${highlightColor};">$1</span>`
)
</script>
<div class="project-item">
<SVGHoverEffect
{SVGComponent}
{backgroundColor}
maxMovement={10}
containerHeight="220px"
bounceDamping={0.2}
/>
<h3 class="project-name">{name}</h3>
<p class="project-description">{description}</p>
<div class="project-item {isEven ? 'even' : 'odd'}">
<div class="project-logo">
<SVGHoverEffect
{SVGComponent}
{backgroundColor}
maxMovement={10}
containerHeight="80px"
bounceDamping={0.2}
/>
</div>
<div class="project-content">
<p class="project-description">{@html highlightedDescription}</p>
</div>
</div>
<style lang="scss">
.project-item {
display: flex;
flex-direction: column;
gap: $unit;
flex-direction: row;
align-items: center;
gap: $unit-3x;
padding: $unit-3x;
background: $grey-100;
border-radius: $card-corner-radius;
&.odd {
flex-direction: row-reverse;
}
}
.project-name {
margin: $unit 0 $unit-half;
font-size: 1rem;
font-weight: bold;
color: $red-60;
.project-logo {
flex-shrink: 0;
width: 80px;
height: 80px;
:global(.svg-container) {
width: 80px !important;
height: 80px !important;
border-radius: $unit-2x;
display: flex;
align-items: center;
justify-content: center;
}
:global(svg) {
width: 48px !important;
height: 48px !important;
}
}
.project-content {
flex: 1;
min-width: 0;
}
.project-description {
margin: 0;
font-size: 1rem;
font-size: 1.125rem; // 18px
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
color: $grey-00;
}
@include breakpoint('phone') {
.project-description {
-webkit-line-clamp: none;
overflow: visible;
max-height: fit-content;
.project-item {
flex-direction: column !important;
gap: $unit-2x;
padding: $unit-2x;
}
.project-logo {
width: 60px;
height: 60px;
:global(.svg-container) {
width: 60px !important;
height: 60px !important;
}
:global(svg) {
width: 36px !important;
height: 36px !important;
}
}
}
</style>

View file

@ -12,6 +12,7 @@
backgroundColor: string
name: string
description: string
highlightColor: string
}
const projects: Project[] = [
@ -19,58 +20,95 @@
SVGComponent: MaitsuLogo,
backgroundColor: '#FFF7EA',
name: 'Maitsu',
description: "I'm building a hobby journal that helps people make something new every week."
description: "Maitsu is a hobby journal that helps people make something new every week.",
highlightColor: '#F77754'
},
{
SVGComponent: SlackLogo,
backgroundColor: '#4a154b',
name: 'Slack',
description:
'I led design for Workflows and other consumer-facing automation features at Slack.'
'At Slack, I helped redefine strategy for Workflows and other features in under the automation umbrella.',
highlightColor: '#611F69'
},
{
SVGComponent: FigmaLogo,
backgroundColor: '#2c2c2c',
name: 'Figma',
description: 'I helped lead and set the direction for the early prototyping team at Figma.'
description: 'At Figma, I designed features and led R&D and strategy for the nascent prototyping team.',
highlightColor: '#0ACF83'
},
{
SVGComponent: PinterestLogo,
backgroundColor: '#f7f7f7',
name: 'Pinterest',
description:
'As the first design hire at Pinterest, I helped define product direction and grow the design team.'
'At Pinterest, I was the first product design hired, and touched almost every part of the product.',
highlightColor: '#CB1F27'
}
]
</script>
<section class="projects">
<ul>
{#each projects as project}
<li>
<div class="intro-card">
<p class="intro-text">
<span class="highlighted">@jedmund</span> is a software designer and strategist based out of San Francisco.
</p>
<p class="intro-text">
In his 15 year career, he's focused his design practice on building tools that help people connect with technology—and their own creativity.
</p>
</div>
</li>
{#each projects as project, index}
<li>
<ProjectItem {...project} />
<ProjectItem {...project} {index} />
</li>
{/each}
</ul>
</section>
<style lang="scss">
.projects ul {
display: grid;
grid-template-columns: 1fr 1fr;
list-style: none;
padding: 0;
.projects {
display: flex;
justify-content: center;
width: 100%;
gap: $unit-4x;
margin: 0;
ul {
display: flex;
flex-direction: column;
list-style: none;
padding: 0;
width: 100%;
max-width: 700px;
gap: $unit-3x;
margin: 0;
@include breakpoint('phone') {
grid-template-columns: 1fr;
li {
width: 100%;
}
}
}
.intro-card {
padding: $unit-3x;
background: $grey-100;
border-radius: $card-corner-radius;
}
.intro-text {
margin: 0;
font-size: 1.125rem; // 18px
line-height: 1.3;
color: $grey-00;
&:not(:last-child) {
margin-bottom: $unit-2x;
}
li {
flex: 0 0 calc(50% - #{$unit-2x}); /* 50% width minus gap */
box-sizing: border-box;
.highlighted {
color: #D0290D;
}
}
</style>

View file

@ -1,152 +1,5 @@
<script lang="ts">
import Album from '$components/Album.svelte'
import Game from '$components/Game.svelte'
import MentionList from '$components/MentionList.svelte'
import Page from '$components/Page.svelte'
import ProjectList from '$components/ProjectList.svelte'
import RecentAlbums from '$components/RecentAlbums.svelte'
import type { PageData } from './$types'
export let data: PageData
$: ({ albums, games, error } = data)
</script>
<Page>
<svelte:fragment slot="header">
<h2 class="subheader">@jedmund is a software designer</h2>
</svelte:fragment>
<ProjectList />
</Page>
<Page>
<svelte:fragment slot="header">
<h2>A little about me</h2>
</svelte:fragment>
<section class="bio">
<p>
Hello! My name is <em>Justin Edmund</em>. I'm a software designer and developer living in San
Francisco.
</p>
<p>
Right now, I'm spending my free time building a hobby journaling app called <a
href="https://maitsu.co"
target="_blank">Maitsu</a
>. I've spent time at several companies over the last 11 years, but you might know me from
<a href="https://www.pinterest.com/" target="_blank">Pinterest</a>, where I was the first
design hire.
</p>
<p>
I was born and raised in New York City and spend a lot of time in Tokyo. I graduated from <a
href="http://design.cmu.edu/"
target="_blank">Carnegie Mellon University</a
> in 2011 with a Bachelors of Arts in Communication Design.
</p>
<p>
I occasionally write about design and development on my <a href="/universe">blog</a>.
</p>
</section>
</Page>
<Page>
<svelte:fragment slot="header">
<h2>Notable mentions</h2>
</svelte:fragment>
<MentionList />
</Page>
<Page noHorizontalPadding={true}>
<svelte:fragment slot="header">
<h2>Now playing</h2>
</svelte:fragment>
<RecentAlbums {albums} />
<!-- <section class="latest-games">
{#if games && games.length > 0}
<ul>
{#each games.slice(0, 3) as game}
<Game {game} />
{/each}
</ul>
{:else}
<p>Loading games...</p>
{/if}
</section> -->
</Page>
<footer>
<p>&copy; 2024 Justin Edmund</p>
</footer>
<style lang="scss">
a,
em {
color: $red-60;
font-weight: 500;
font-style: normal;
text-decoration: none;
}
a:hover {
cursor: pointer;
text-decoration: underline;
text-decoration-style: wavy;
}
header {
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: $unit-2x;
}
h1,
h2 {
margin: 0;
}
h2 {
color: $accent-color;
font-size: 1.2rem;
font-weight: 500;
&.subheader {
margin-bottom: $unit-2x;
}
}
.bio {
font-size: 1rem;
line-height: 1.3;
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
}
ul {
list-style: none;
margin: 0;
padding: 0;
}
.latest-games ul {
display: flex;
flex-direction: row;
gap: $unit-4x;
width: 100%;
}
footer {
font-size: 0.85rem;
color: $grey-40;
text-align: center;
}
</style>
<ProjectList />

View file

@ -0,0 +1,115 @@
<script lang="ts">
import Album from '$components/Album.svelte'
import Game from '$components/Game.svelte'
import MentionList from '$components/MentionList.svelte'
import Page from '$components/Page.svelte'
import RecentAlbums from '$components/RecentAlbums.svelte'
import type { PageData } from './$types'
export let data: PageData
$: ({ albums, games, error } = data)
</script>
<Page>
<svelte:fragment slot="header">
<h2>A little about me</h2>
</svelte:fragment>
<section class="bio">
<p>
Hello! My name is <em>Justin Edmund</em>. I'm a software designer and developer living in San
Francisco.
</p>
<p>
Right now, I'm spending my free time building a hobby journaling app called <a
href="https://maitsu.co"
target="_blank">Maitsu</a
>. I've spent time at several companies over the last 11 years, but you might know me from
<a href="https://www.pinterest.com/" target="_blank">Pinterest</a>, where I was the first
design hire.
</p>
<p>
I was born and raised in New York City and spend a lot of time in Tokyo. I graduated from <a
href="http://design.cmu.edu/"
target="_blank">Carnegie Mellon University</a
> in 2011 with a Bachelors of Arts in Communication Design.
</p>
<p>
I occasionally write about design and development on my <a href="/universe">blog</a>.
</p>
</section>
</Page>
<Page>
<svelte:fragment slot="header">
<h2>Notable mentions</h2>
</svelte:fragment>
<MentionList />
</Page>
<Page noHorizontalPadding={true}>
<svelte:fragment slot="header">
<h2>Now playing</h2>
</svelte:fragment>
<RecentAlbums {albums} />
<!-- <section class="latest-games">
{#if games && games.length > 0}
<ul>
{#each games.slice(0, 3) as game}
<Game {game} />
{/each}
</ul>
{:else}
<p>Loading games...</p>
{/if}
</section> -->
</Page>
<footer>
<p>&copy; 2024 Justin Edmund</p>
</footer>
<style lang="scss">
a,
em {
color: $red-60;
font-weight: 500;
font-style: normal;
text-decoration: none;
}
a:hover {
cursor: pointer;
text-decoration: underline;
text-decoration-style: wavy;
}
h2 {
color: $accent-color;
font-size: 1.2rem;
font-weight: 500;
margin: 0;
}
.bio {
font-size: 1rem;
line-height: 1.3;
p:first-child {
margin-top: 0;
}
p:last-child {
margin-bottom: 0;
}
}
footer {
font-size: 0.85rem;
color: $grey-40;
text-align: center;
}
</style>

28
src/routes/about/+page.ts Normal file
View file

@ -0,0 +1,28 @@
import type { PageLoad } from './$types'
import type { Album } from '$lib/types/lastfm'
export const load: PageLoad = async ({ fetch }) => {
try {
const [albums] = await Promise.all([
fetchRecentAlbums(fetch)
])
return {
albums
}
} catch (err) {
console.error('Error fetching data:', err)
return {
albums: [],
games: [],
error: err instanceof Error ? err.message : 'An unknown error occurred'
}
}
}
async function fetchRecentAlbums(fetch: typeof window.fetch): Promise<Album[]> {
const response = await fetch('/api/lastfm')
if (!response.ok) throw new Error(`Failed to fetch albums: ${response.status}`)
const musicData: { albums: Album[] } = await response.json()
return musicData.albums
}