Skip to content

Commit 9d208f5

Browse files
authored
chore: responsive styles, biggo clean up (#76)
1 parent 9a3a368 commit 9d208f5

File tree

20 files changed

+495
-446
lines changed

20 files changed

+495
-446
lines changed

.eslintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
],
1010
"plugins": ["prettier", "@typescript-eslint"],
1111
"rules": {
12-
"@typescript-eslint/explicit-module-boundary-types": "off"
12+
"@typescript-eslint/explicit-module-boundary-types": "off",
13+
"@typescript-eslint/no-explicit-any": "off"
1314
}
1415
}

src/components/Bio.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ const Bio: FC = () => {
4545
<Text>
4646
Wanna chat about <span className="dynamic">{talkingPoint}</span>? Lets
4747
talk. You can reach me at{' '}
48-
<Link href="mailto:hi@alexxie.com">hi@alexxie.com</Link>.
48+
<Link href="mailto:alex@xie.codes">alex@xie.codes</Link>.
4949
</Text>
5050
</p>
5151
</Container>
Lines changed: 67 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,50 @@
11
import { memo, FC, useEffect, useState, useCallback } from 'react';
2-
import { prominent } from 'color.js';
3-
4-
import { rgbToHsl, useVisibilityChange } from 'services/utils';
5-
import { useSiteContext } from 'services/site/context';
6-
import { TNowPlayingData } from 'services/now-playing';
7-
import { fetchNowPlaying } from 'services/now-playing/fetch';
82
import TextLoop from 'react-text-loop';
3+
import { styled } from 'goober';
94

10-
const getBestTextColor = async (coverArt: string) => {
11-
const colors = (await prominent(coverArt, {
12-
amount: 3,
13-
group: 20,
14-
format: 'array',
15-
sample: 10,
16-
})) as number[][];
17-
18-
let [bestH, bestS, bestL] = rgbToHsl(colors[0]);
19-
for (const rgb of colors) {
20-
const [h, s, l] = rgbToHsl(rgb);
21-
if (s > 40) {
22-
[bestH, bestS, bestL] = [h, s, l];
23-
break;
24-
}
5+
import { useVisibilityChange } from 'services/utils';
6+
import { useSiteContext } from 'services/site/context';
7+
import {
8+
TNowPlayingData,
9+
isNowPlayingData,
10+
getNowPlaying,
11+
} from 'services/now-playing';
12+
13+
const CoverArtLink = styled('a')`
14+
position: relative;
15+
display: inline-block;
16+
17+
transition: transform 250ms;
18+
&:hover {
19+
transform: scale(1.1);
2520
}
2621
27-
// upper bound lightness value at 40 to make it readable
28-
return `hsl(${bestH}, ${bestS}%, ${Math.min(bestL, 40)}%)`;
29-
};
30-
31-
const CoverArtLink = ({ href, children }) => {
32-
const [hover, setHover] = useState(false);
33-
34-
return (
35-
<a
36-
href={href}
37-
target="_blank"
38-
rel="noreferrer noopener"
39-
onMouseEnter={() => setHover(true)}
40-
onMouseLeave={() => setHover(false)}
41-
style={{
42-
position: 'relative',
43-
display: 'inline-block',
44-
transform: `scale(${hover ? 1.1 : 1})`,
45-
transition: 'transform 250ms',
46-
}}
47-
>
48-
{children}
49-
</a>
50-
);
51-
};
22+
& img {
23+
width: 18px;
24+
height: 18px;
25+
border-radius: 3px;
26+
transform: translateY(1px);
27+
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
28+
}
29+
`;
5230

5331
const nowPlayingMarkup = ({
5432
name,
55-
artist,
33+
artistName,
34+
podcastName,
5635
link,
5736
coverArtSrc,
5837
coverArtColor,
5938
}: TNowPlayingData) => {
60-
const isTrack = !!artist;
61-
const action = isTrack ? "jammin' out to" : 'listening to';
62-
const label = `${name}${isTrack ? ` by ${artist}` : ''}`;
39+
const isPodcast = !!podcastName;
40+
const hasArtist = !!artistName;
41+
const action = isPodcast ? 'listening to an episode of' : "jammin' out to";
42+
const label = `${isPodcast ? podcastName : name}${
43+
hasArtist ? ` by ${artistName}` : ''
44+
}`;
6345

6446
return [
65-
...action.split(' ').map((a) => <span style={{ color: '#000' }}>{a}</span>),
47+
...action.split(' ').map((a) => <span>{a}</span>),
6648
...label.split(' ').map((a) => (
6749
<span
6850
className="dynamic"
@@ -71,85 +53,71 @@ const nowPlayingMarkup = ({
7153
{a}
7254
</span>
7355
)),
74-
<CoverArtLink href={link}>
75-
<img
76-
src={coverArtSrc}
77-
style={{
78-
height: '18px',
79-
borderRadius: '3px',
80-
transform: 'translateY(1px)',
81-
}}
82-
/>
56+
<CoverArtLink href={link} target="_blank" rel="noreferrer noopener">
57+
<img src={coverArtSrc} />
8358
</CoverArtLink>,
8459
];
8560
};
8661

8762
const DynamicCurrentStatus: FC = memo(() => {
88-
const { nowPlaying, activity, spotifyToken } = useSiteContext();
89-
const [np, setNp] = useState<(TNowPlayingData | string)[]>([
90-
nowPlaying ?? `probably ${activity}`,
63+
const { nowPlayingData, activity, spotifyToken } = useSiteContext();
64+
const [statuses, setStatuses] = useState<(TNowPlayingData | string)[]>([
65+
nowPlayingData ?? `probably ${activity}`,
9166
]);
92-
const [shouldFetchNew, setShouldFetchNew] = useState(true);
9367

9468
const refetchNp = useCallback(async () => {
95-
const lastNp = np[np.length - 1];
96-
const lastNowPlayingData = typeof lastNp === 'string' ? null : lastNp;
97-
const updatedNowPlayingData = await fetchNowPlaying(spotifyToken);
98-
console.debug(updatedNowPlayingData, lastNowPlayingData);
69+
const updatedNowPlayingData = await getNowPlaying(spotifyToken);
9970

100-
if (
101-
updatedNowPlayingData &&
102-
updatedNowPlayingData.uri !== lastNowPlayingData?.uri
103-
) {
71+
const lastStatus = statuses[statuses.length - 1];
72+
const lastNowPlayingData = isNowPlayingData(lastStatus) ? lastStatus : null;
73+
const hasNewNowPlayingData =
74+
!!updatedNowPlayingData &&
75+
updatedNowPlayingData.uri !== lastNowPlayingData?.uri;
76+
77+
if (hasNewNowPlayingData) {
10478
console.debug('New now playing data found...', updatedNowPlayingData);
105-
const color = await getBestTextColor(updatedNowPlayingData.coverArtSrc);
106-
setNp((prev) => [
107-
...prev,
108-
{
109-
...updatedNowPlayingData,
110-
coverArtColor: color,
111-
},
112-
]);
79+
setStatuses((prev) => [...prev, updatedNowPlayingData]);
11380
}
114-
}, [np, spotifyToken]);
81+
}, [statuses, spotifyToken]);
11582

83+
/**
84+
*Refetch what's currently playing on Spotify when tab receives focus, and on mount.
85+
*/
11686
useVisibilityChange((isHidden) => {
11787
if (!isHidden) {
11888
console.debug('Received focus, refreshing now playing...');
119-
setShouldFetchNew(true);
89+
refetchNp();
12090
}
12191
});
12292

12393
useEffect(() => {
124-
(async () => {
125-
if (shouldFetchNew) {
126-
await refetchNp();
127-
setShouldFetchNew(false);
128-
}
129-
})();
130-
}, [refetchNp, shouldFetchNew]);
94+
refetchNp();
95+
// eslint-disable-next-line react-hooks/exhaustive-deps
96+
}, []);
13197

132-
const npMarkup = np.map((e) => {
133-
return typeof e === 'string' ? e.split(' ') : nowPlayingMarkup(e);
134-
});
98+
const statusesMarkup = statuses.map((status) =>
99+
isNowPlayingData(status) ? nowPlayingMarkup(status) : status.split(' ')
100+
);
135101

136102
return (
137103
<span>
138-
{[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17].map(
139-
(i) => {
104+
{new Array(Math.max(...statusesMarkup.map((s) => s.length)))
105+
.fill('')
106+
.map((_, wordIdx) => {
140107
return (
141108
<>
142109
<TextLoop
143-
interval={np.map((_, i) => (i === np.length - 1 ? -1 : 2000))} // don't transition from the last data back to the initial data
144-
children={npMarkup.map((e) => e[i] ?? ' ')}
110+
// transition to next status, but don't transition from last back to first
111+
interval={statuses.map((_, i) =>
112+
i === statuses.length - 1 ? -1 : 1000
113+
)}
114+
children={statusesMarkup.map((m) => m[wordIdx] ?? '')}
145115
/>{' '}
146116
</>
147117
);
148-
}
149-
)}
118+
})}
150119
</span>
151120
);
152121
});
153-
// TODO: remove react-motion
154122

155123
export default DynamicCurrentStatus;

src/components/DynamicFavicon.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { useState, FC } from 'react';
2+
import Head from 'next/head';
3+
4+
import { useVisibilityChange } from 'services/utils';
5+
6+
const DynamicFavicon: FC = () => {
7+
const [isAway, setAway] = useState(false);
8+
useVisibilityChange(setAway);
9+
10+
return (
11+
<Head>
12+
<link
13+
rel="shortcut icon"
14+
type="image/png"
15+
href={isAway ? '/favicon-away.png' : '/favicon.png'}
16+
/>
17+
</Head>
18+
);
19+
};
20+
21+
export default DynamicFavicon;

src/components/DynamicTime.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,6 @@ type TextGradientInfo = [
99
gradientTo: string
1010
];
1111

12-
const GradientContainer = styled<{ gradient: TextGradientInfo }>('span')`
13-
color: ${({ gradient }) => gradient[1]};
14-
background: ${({ gradient }) =>
15-
`linear-gradient(${gradient[0]}, ${gradient[1]}, ${gradient[2]})`};
16-
background-clip: text;
17-
-webkit-background-clip: text;
18-
-webkit-text-fill-color: transparent;
19-
`;
20-
2112
const timeHourMarkup = (hour: number) => {
2213
const twelveHourTime = hour % 12 || 12; // 0 or 24 becomes 12am
2314
const timeOfDay = hour < 12 || hour === 24 ? 'AM' : 'PM';
@@ -50,7 +41,7 @@ const timeToColor = (hour: number, time: string): TextGradientInfo => {
5041
case '2PM':
5142
case '3PM':
5243
case '4PM':
53-
return [`${140 + hour * 3}deg`, '#FFCE32', '#5995B7'];
44+
return [`${140 + hour * 3}deg`, '#FFCE32', '#45B6F7'];
5445

5546
case '5PM': // sunset
5647
return ['120deg', '#5995B7', '#FF8C18'];
@@ -74,6 +65,15 @@ const timeToColor = (hour: number, time: string): TextGradientInfo => {
7465
}
7566
};
7667

68+
const GradientContainer = styled<{ gradient: TextGradientInfo }>('span')`
69+
color: ${({ gradient }) => gradient[1]};
70+
background: ${({ gradient }) =>
71+
`linear-gradient(${gradient[0]}, ${gradient[1]}, ${gradient[2]})`};
72+
background-clip: text;
73+
-webkit-background-clip: text;
74+
-webkit-text-fill-color: transparent;
75+
`;
76+
7777
const DynamicTime: FC = memo(() => {
7878
const { currentDate } = useSiteContext();
7979
const timeMarkup = timeHourMarkup(currentDate.getHours());

src/components/Heading.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const H1 = styled('h1')`
88
font-size: 48px;
99
text-align: center;
1010
margin-bottom: 32px;
11+
12+
@media only screen and (max-width: 600px) {
13+
font-size: 32px;
14+
}
1115
`;
1216

1317
const Heading: FC = memo(() => {

src/components/Links.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const Container = styled('footer')`
1010
align-items: center;
1111
justify-content: center;
1212
13-
margin: 1em 0 2em 0;
13+
margin: 0 0 2em 0;
1414
1515
& > a {
1616
margin: 0 6px;

src/components/MeIllustration.tsx

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
import { FC, memo, useState } from 'react';
2+
import { styled } from 'goober';
23

34
import { useSiteContext } from 'services/site/context';
45

6+
const Container = styled('svg')`
7+
height: 280px;
8+
9+
@media only screen and (max-width: 600px) {
10+
height: 220px;
11+
}
12+
`;
13+
514
const WEIRD = (
615
<g id="Face">
716
<g fill="none" fillRule="evenodd" stroke="none" strokeWidth="1">
@@ -17,6 +26,7 @@ const WEIRD = (
1726
</g>
1827
</g>
1928
);
29+
2030
const SURPRISED = (
2131
<g
2232
id="Face/Surprise"
@@ -65,14 +75,13 @@ const MeIllustration: FC = memo(() => {
6575

6676
const onIllustrationClick = () =>
6777
setNumClicks((prev) => {
68-
if (prev + 1 === 3)
78+
if ((prev + 1) % 3 === 0)
6979
window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ', '_blank');
7080
return prev + 1;
7181
});
7282

7383
return (
74-
<svg
75-
height="280"
84+
<Container
7685
overflow="visible"
7786
viewBox="233.511 78 682.97 695.5"
7887
onMouseEnter={() => setHovering(true)}
@@ -266,7 +275,7 @@ const MeIllustration: FC = memo(() => {
266275
</g>
267276
</g>
268277
</g>
269-
</svg>
278+
</Container>
270279
);
271280
});
272281

src/pages/_document.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,10 @@ import Document, {
77
} from 'next/document';
88
import { extractCss } from 'goober';
99

10-
export default class CustomDocument extends Document<{ css: any }> {
10+
export default class CustomDocument extends Document<{ css: string }> {
1111
static async getInitialProps({ renderPage }: DocumentContext) {
12+
// inline critical css for page render
1213
const page = renderPage();
13-
14-
// extract the css for each page render
1514
const css = extractCss();
1615
return { ...page, css };
1716
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { StorageClient } from 'services/_server_';
2+
3+
export default function handler(_, res) {
4+
const client = new StorageClient();
5+
client.getSpotifyCredentials();
6+
res.statusCode = 200;
7+
res.setHeader('Content-Type', 'application/json');
8+
res.end(JSON.stringify({ success: true }));
9+
}

0 commit comments

Comments
 (0)