Updates to design

* Disabled games for now
* Added failsafe for when last.fm can't find an album
* Updated to use rems
* Extracted RecentAlbums into a component
* Fixes for mobile
This commit is contained in:
Justin Edmund 2024-11-18 02:10:15 -08:00
parent cbb711d2c4
commit 3d58dd5b46
14 changed files with 220 additions and 63 deletions

View file

@ -12,6 +12,14 @@
$tablet-width: 1024px; $tablet-width: 1024px;
$tablet-height: 1024px; $tablet-height: 1024px;
@if $breakpoint == desktop {
// prettier-ignore
@media only screen
and (min-width: $tablet-width) {
@content;
}
}
@if $breakpoint == tablet { @if $breakpoint == tablet {
// prettier-ignore // prettier-ignore
@media only screen @media only screen

View file

@ -54,15 +54,17 @@
<style lang="scss"> <style lang="scss">
.album { .album {
flex-basis: 100%; width: 100%;
height: 100%;
a { a {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit * 1.5; gap: $unit * 1.5;
text-decoration: none; text-decoration: none;
transition: gap 0.125s ease-in-out; transition: gap 0.125s ease-in-out;
width: 100%;
height: 100%;
img { img {
border: 1px solid rgba(0, 0, 0, 0.1); border: 1px solid rgba(0, 0, 0, 0.1);
@ -70,6 +72,7 @@
box-shadow: 0 0 8px rgba(0, 0, 0, 0.1); box-shadow: 0 0 8px rgba(0, 0, 0, 0.1);
width: 100%; width: 100%;
height: auto; height: auto;
object-fit: cover;
} }
.info { .info {
@ -86,6 +89,11 @@
font-size: $font-size; font-size: $font-size;
font-weight: $font-weight-med; font-weight: $font-weight-med;
color: $accent-color; color: $accent-color;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
} }
.artist-name { .artist-name {

View file

@ -1,4 +1,6 @@
<script> <script>
// What if we have a headphones avatar that is head bopping if the last scrobble was < 5 mins ago
// We can do a thought bubble-y thing with the album art that takes you to the album section of the page
import { onMount } from 'svelte' import { onMount } from 'svelte'
import { spring } from 'svelte/motion' import { spring } from 'svelte/motion'

View file

@ -51,7 +51,7 @@
h4 { h4 {
display: block; display: block;
font-weight: 400; font-weight: 400;
font-size: $font-size; font-size: 1rem;
flex-grow: 2; flex-grow: 2;
margin: 0; margin: 0;
@ -64,12 +64,12 @@
color: $grey-50; color: $grey-50;
flex-grow: 0; flex-grow: 0;
font-weight: $font-weight-med; font-weight: $font-weight-med;
font-size: 0.8em; font-size: 0.9rem;
} }
.source, .source,
.source-type { .source-type {
margin-right: 0.5em; margin-right: $unit;
} }
} }
</style> </style>

View file

@ -61,5 +61,11 @@
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
@include breakpoint('phone') {
ul {
grid-template-columns: 1fr;
}
}
} }
</style> </style>

View file

@ -1,7 +1,8 @@
<script lang="ts"> <script lang="ts">
export let noHorizontalPadding = false
</script> </script>
<section class="page"> <section class="page" class:no-horizontal-padding={noHorizontalPadding}>
<header> <header>
<slot name="header" /> <slot name="header" />
</header> </header>
@ -19,17 +20,32 @@
gap: $unit-4x; gap: $unit-4x;
margin: $unit-6x auto $unit-6x; margin: $unit-6x auto $unit-6x;
padding: $unit-5x; padding: $unit-5x;
width: 100%;
max-width: 784px; max-width: 784px;
&.no-horizontal-padding {
padding-left: 0;
padding-right: 0;
}
@include breakpoint('phone') { @include breakpoint('phone') {
margin-top: $unit-2x; margin-top: $unit-2x;
margin-bottom: $unit-3x; margin-bottom: $unit-3x;
padding: $unit-3x; padding: $unit-3x;
&.no-horizontal-padding {
padding-left: 0;
padding-right: 0;
}
} }
@include breakpoint('small-phone') { @include breakpoint('small-phone') {
gap: $unit-2x;
padding: $unit-2x; padding: $unit-2x;
&.no-horizontal-padding {
padding-left: 0;
padding-right: 0;
}
} }
header { header {

View file

@ -13,9 +13,7 @@
{SVGComponent} {SVGComponent}
{backgroundColor} {backgroundColor}
maxMovement={10} maxMovement={10}
smoothness={0.1}
containerHeight="220px" containerHeight="220px"
bounceStiffness={0.1}
bounceDamping={0.2} bounceDamping={0.2}
/> />
<h3 class="project-name">{name}</h3> <h3 class="project-name">{name}</h3>
@ -31,20 +29,27 @@
.project-name { .project-name {
margin: $unit 0 $unit-half; margin: $unit 0 $unit-half;
font-size: 18px; font-size: 1.2rem;
font-weight: bold; font-weight: bold;
color: $red-60; color: $red-60;
} }
.project-description { .project-description {
margin: 0; margin: 0;
font-size: 14px; font-size: 1rem;
line-height: 1.4; line-height: 1.3;
max-height: 2.8em;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
} }
@include breakpoint('phone') {
.project-description {
-webkit-line-clamp: none;
overflow: visible;
max-height: fit-content;
}
}
</style> </style>

View file

@ -0,0 +1,85 @@
<script lang="ts">
import Album from '$components/Album.svelte'
import type { Album as AlbumType } from '$lib/types/lastfm'
export let albums: AlbumType[] = []
</script>
<section class="recent-albums">
{#if albums.length > 0}
<ul>
{#each albums.slice(0, 4) as album}
<li>
<Album {album} />
</li>
{/each}
</ul>
{:else}
<p>Loading albums...</p>
{/if}
</section>
<style lang="scss">
.recent-albums {
ul {
list-style: none;
margin: 0;
padding: 0;
}
p {
text-align: center;
}
// Desktop styles
@include breakpoint('desktop') {
ul {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
li {
flex: 1 1 calc(20% - 10px); // Adjust to fit exactly 5 albums
margin: 5px;
&:first-child {
margin-left: $unit-5x;
}
&:last-child {
margin-right: $unit-5x;
}
}
}
}
@include breakpoint('phone') {
ul {
display: flex;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scroll-snap-type: x mandatory;
gap: $unit-2x;
scrollbar-width: none; /* Firefox */
}
ul::-webkit-scrollbar {
display: none;
}
li {
flex: 0 0 auto;
scroll-snap-align: start;
width: 100%;
padding: 0 $unit-3x;
box-sizing: border-box;
}
}
@include breakpoint('small-phone') {
li {
padding: 0 $unit-2x;
}
}
}
</style>

View file

@ -39,7 +39,7 @@
} }
$: { $: {
text // add this as a dependency text
updateTextWidth() updateTextWidth()
} }
</script> </script>
@ -65,7 +65,7 @@
<style lang="scss"> <style lang="scss">
.squiggly-header { .squiggly-header {
font-size: $font-size; font-size: 1.2rem;
font-weight: 500; font-weight: 500;
margin-bottom: $unit-fourth; margin-bottom: $unit-fourth;
} }

View file

@ -7,7 +7,11 @@
name="description" name="description"
content="Justin Edmund is a software designer based in San Francisco, California." content="Justin Edmund is a software designer based in San Francisco, California."
/> />
<!-- <link href="/assets/styles/reset.css" rel="stylesheet" /> --> <meta
name="viewport"
content="width=device-width, initial-scale=1.0,
user-scalable=no"
/>
</svelte:head> </svelte:head>
<main> <main>
@ -15,9 +19,20 @@
</main> </main>
<style lang="scss"> <style lang="scss">
@import '../assets/styles/reset.css';
@import '../assets/styles/globals.scss';
:global(html) { :global(html) {
background: var(--bg-color); background: var(--bg-color);
color: var(--text-color); color: var(--text-color);
font-family: 'cstd', 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'cstd', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 16px;
width: 100%;
}
@include breakpoint('phone') {
:global(html) {
font-size: 18px;
}
} }
</style> </style>

View file

@ -5,6 +5,7 @@
import MentionList from '$components/MentionList.svelte' import MentionList from '$components/MentionList.svelte'
import Page from '$components/Page.svelte' import Page from '$components/Page.svelte'
import ProjectList from '$components/ProjectList.svelte' import ProjectList from '$components/ProjectList.svelte'
import RecentAlbums from '$components/RecentAlbums.svelte'
import Squiggly from '$components/Squiggly.svelte' import Squiggly from '$components/Squiggly.svelte'
import type { PageData } from './$types' import type { PageData } from './$types'
@ -19,9 +20,7 @@
<h1 aria-label="@jedmund"> <h1 aria-label="@jedmund">
<Avatar /> <Avatar />
</h1> </h1>
<Squiggly <Squiggly text="@jedmund is a software designer" />
text="@jedmund is a software designer that helps you explore what your product can be, unburdened by what it has been"
/>
</svelte:fragment> </svelte:fragment>
<ProjectList /> <ProjectList />
@ -40,9 +39,9 @@
<p> <p>
Right now, I'm spending my free time building a hobby journaling app called <a Right now, I'm spending my free time building a hobby journaling app called <a
href="https://maitsu.co">Maitsu</a href="https://maitsu.co">Maitsu</a
>. I've spent time at several companies over the last 11 years, but you might know me as the >. I've spent time at several companies over the last 11 years, but you might know me from
first designer hired at <a href="https://www.pinterest.com/" target="_blank">Pinterest</a>, where I was the first
<a href="https://www.pinterest.com/" target="_blank">Pinterest</a>. design hire.
</p> </p>
<p> <p>
I was born and raised in New York City and spend a lot of time in Tokyo. I graduated from <a I was born and raised in New York City and spend a lot of time in Tokyo. I graduated from <a
@ -51,27 +50,22 @@
> in 2011 with a Bachelors of Arts in Communication Design. > in 2011 with a Bachelors of Arts in Communication Design.
</p> </p>
</section> </section>
</Page>
<Page>
<svelte:fragment slot="header">
<Squiggly text="Notable mentions" />
</svelte:fragment>
<MentionList /> <MentionList />
</Page> </Page>
<Page> <Page noHorizontalPadding={true}>
<svelte:fragment slot="header"> <svelte:fragment slot="header">
<Squiggly text="Now playing" /> <Squiggly text="Now playing" />
</svelte:fragment> </svelte:fragment>
<section class="weekly-albums"> <RecentAlbums {albums} />
{#if albums.length > 0}
<ul>
{#each albums.slice(0, 5) as album}
<Album {album} />
{/each}
</ul>
{:else}
<p>Loading albums...</p>
{/if}
</section>
<section class="latest-games"> <!-- <section class="latest-games">
{#if games && games.length > 0} {#if games && games.length > 0}
<ul> <ul>
{#each games.slice(0, 3) as game} {#each games.slice(0, 3) as game}
@ -81,7 +75,7 @@
{:else} {:else}
<p>Loading games...</p> <p>Loading games...</p>
{/if} {/if}
</section> </section> -->
</Page> </Page>
<footer> <footer>
@ -110,19 +104,21 @@
margin-bottom: $unit-2x; margin-bottom: $unit-2x;
} }
.bio {
font-size: 1rem;
line-height: 1.3;
p:last-child {
margin-bottom: 0;
}
}
ul { ul {
list-style: none; list-style: none;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
.weekly-albums ul {
display: flex;
flex-direction: row;
gap: $unit-4x;
width: 100%;
}
.latest-games ul { .latest-games ul {
display: flex; display: flex;
flex-direction: row; flex-direction: row;

View file

@ -3,26 +3,27 @@ import type { Album } from '$lib/types/lastfm'
export const load: PageLoad = async ({ fetch }) => { export const load: PageLoad = async ({ fetch }) => {
try { try {
const [albums, steamGames, psnGames] = await Promise.all([ // const [albums, steamGames, psnGames] = await Promise.all([
fetchRecentAlbums(fetch), const [albums] = await Promise.all([
fetchRecentSteamGames(fetch), fetchRecentAlbums(fetch)
fetchRecentPSNGames(fetch) // fetchRecentSteamGames(fetch),
// fetchRecentPSNGames(fetch)
]) ])
const response = await fetch('/api/giantbomb', { // const response = await fetch('/api/giantbomb', {
method: 'POST', // method: 'POST',
body: JSON.stringify({ games: psnGames }), // body: JSON.stringify({ games: psnGames }),
headers: { // headers: {
'Content-Type': 'application/json' // 'Content-Type': 'application/json'
} // }
}) // })
const games = await response.json() // const games = await response.json()
return { return {
albums, albums
games: games, // games: games,
steamGames: steamGames, // steamGames: steamGames,
psnGames: psnGames // psnGames: psnGames
} }
} catch (err) { } catch (err) {
console.error('Error fetching data:', err) console.error('Error fetching data:', err)

View file

@ -12,7 +12,7 @@ import type { LastfmImage } from '@musicorum/lastfm/dist/types/packages/common'
const LASTFM_API_KEY = process.env.LASTFM_API_KEY const LASTFM_API_KEY = process.env.LASTFM_API_KEY
const USERNAME = 'jedmund' const USERNAME = 'jedmund'
const ALBUM_LIMIT = 3 const ALBUM_LIMIT = 10
export const GET: RequestHandler = async ({ url }) => { export const GET: RequestHandler = async ({ url }) => {
const client = new LastClient(LASTFM_API_KEY || '') const client = new LastClient(LASTFM_API_KEY || '')
@ -21,10 +21,23 @@ export const GET: RequestHandler = async ({ url }) => {
// const albums = await getWeeklyAlbumChart(client, USERNAME) // const albums = await getWeeklyAlbumChart(client, USERNAME)
const albums = await getRecentAlbums(client, USERNAME, ALBUM_LIMIT) const albums = await getRecentAlbums(client, USERNAME, ALBUM_LIMIT)
console.log(albums)
const enrichedAlbums = await Promise.all( const enrichedAlbums = await Promise.all(
albums.slice(0, ALBUM_LIMIT).map((album) => enrichAlbumWithInfo(client, album)) albums.slice(0, ALBUM_LIMIT).map(async (album) => {
try {
return await enrichAlbumWithInfo(client, album)
} catch (error) {
if (error instanceof Error && error.message.includes('Album not found')) {
console.warn(`Skipping album: ${album.name} (Album not found)`)
return null // Skip the album
}
throw error // Re-throw if it's a different error
}
})
) )
const albumsWithItunesArt = await addItunesArtToAlbums(enrichedAlbums)
const validAlbums = enrichedAlbums.filter((album) => album !== null)
const albumsWithItunesArt = await addItunesArtToAlbums(validAlbums)
return new Response(JSON.stringify({ albums: albumsWithItunesArt }), { return new Response(JSON.stringify({ albums: albumsWithItunesArt }), {
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }

View file

@ -1,6 +1,8 @@
import 'dotenv/config' import 'dotenv/config'
import { Redis } from 'ioredis' import { Redis } from 'ioredis'
console.log('Redis client')
console.log(process.env.REDIS_URL)
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379') const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379')
export default redis export default redis