Skip to content

Commit 0dabcdb

Browse files
committed
Optimize mobile layout and hero image
1 parent 81c179a commit 0dabcdb

5 files changed

Lines changed: 141 additions & 30 deletions

File tree

123 KB
Loading
352 KB
Loading
57.2 KB
Loading

src/pages/Home.jsx

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { useEffect, useRef, useState } from 'react';
2-
import profilePicture from '../assets/profile_picture.jpeg';
2+
import profilePicture800 from '../assets/profile_picture_800.jpeg';
3+
import profilePicture1200 from '../assets/profile_picture_1200.jpeg';
4+
import profilePicture2000 from '../assets/profile_picture_2000.jpeg';
35

46
export default function Home() {
57
const [typedCount, setTypedCount] = useState(0);
68
const [hidePrompt, setHidePrompt] = useState(false);
9+
const [reduceMotion, setReduceMotion] = useState(false);
710
const promptRef = useRef(null);
811
const [promptWidth, setPromptWidth] = useState(0);
912

@@ -22,7 +25,22 @@ export default function Home() {
2225
}, []);
2326

2427
useEffect(() => {
25-
let hideTimer;
28+
const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
29+
const handleMotionChange = () => setReduceMotion(motionQuery.matches);
30+
31+
handleMotionChange();
32+
motionQuery.addEventListener('change', handleMotionChange);
33+
34+
return () => motionQuery.removeEventListener('change', handleMotionChange);
35+
}, []);
36+
37+
useEffect(() => {
38+
if (reduceMotion) {
39+
setTypedCount(totalLength);
40+
setHidePrompt(true);
41+
return undefined;
42+
}
43+
2644
const timer = setInterval(() => {
2745
setTypedCount((current) => {
2846
if (current >= totalLength) {
@@ -34,19 +52,20 @@ export default function Home() {
3452

3553
return () => {
3654
clearInterval(timer);
37-
if (hideTimer) {
38-
clearTimeout(hideTimer);
39-
}
4055
};
41-
}, [totalLength]);
56+
}, [reduceMotion, totalLength]);
4257

4358
useEffect(() => {
59+
if (reduceMotion) {
60+
return undefined;
61+
}
62+
4463
if (typedCount === totalLength) {
4564
const timer = setTimeout(() => setHidePrompt(true), 500);
4665
return () => clearTimeout(timer);
4766
}
4867
return undefined;
49-
}, [typedCount, totalLength]);
68+
}, [reduceMotion, typedCount, totalLength]);
5069

5170
useEffect(() => {
5271
if (promptText.length === fullPrompt.length && promptRef.current) {
@@ -56,10 +75,16 @@ export default function Home() {
5675

5776
return (
5877
<div className="home">
59-
<section
60-
className="hero"
61-
style={{ backgroundImage: `url(${profilePicture})` }}
62-
>
78+
<section className="hero">
79+
<img
80+
className="hero-image"
81+
src={profilePicture1200}
82+
srcSet={`${profilePicture800} 722w, ${profilePicture1200} 1083w, ${profilePicture2000} 1806w`}
83+
sizes="(max-width: 900px) calc(100vw - 2rem), 852px"
84+
alt="Portrait of David Bingmann"
85+
decoding="async"
86+
fetchPriority="high"
87+
/>
6388
<div className="hero-overlay" />
6489
<div className="hero-content">
6590
<div className="hero-command">

src/styles.css

Lines changed: 105 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
:root {
22
color-scheme: light;
3+
--page-gutter: 1.5rem;
34
--bg: #f5f3ee;
45
--bg-top: #ffffff;
56
--surface: #ffffff;
@@ -31,6 +32,7 @@
3132
body {
3233
margin: 0;
3334
min-height: 100vh;
35+
min-height: 100svh;
3436
font-family: 'Space Mono', monospace;
3537
color: var(--text);
3638
background: radial-gradient(circle at top, var(--bg-top) 0%, var(--bg) 55%);
@@ -62,14 +64,18 @@ p {
6264

6365
.shell {
6466
min-height: 100vh;
67+
min-height: 100svh;
6568
display: flex;
6669
flex-direction: column;
6770
align-items: center;
6871
}
6972

7073
.header {
7174
width: 100%;
72-
padding: 2.5rem 1.5rem 0;
75+
padding-top: 2.5rem;
76+
padding-bottom: 0;
77+
padding-left: calc(var(--page-gutter) + env(safe-area-inset-left, 0px));
78+
padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px));
7379
display: flex;
7480
flex-direction: column;
7581
align-items: center;
@@ -87,12 +93,11 @@ p {
8793

8894
.nav-link {
8995
color: var(--muted);
90-
padding-bottom: 0.5rem;
96+
padding: 0.65rem 0.35rem 0.5rem;
9197
border-bottom: 2px solid transparent;
9298
transition: color 0.2s ease, border-color 0.2s ease;
9399
}
94100

95-
.nav-link:hover,
96101
.nav-link--active {
97102
color: var(--text);
98103
border-color: var(--accent);
@@ -107,17 +112,20 @@ p {
107112

108113
.main {
109114
width: min(900px, 100%);
110-
padding: 0 1.5rem 4rem;
115+
padding-top: 0;
116+
padding-bottom: 4rem;
117+
padding-left: calc(var(--page-gutter) + env(safe-area-inset-left, 0px));
118+
padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px));
111119
}
112120

113121
.hero {
114122
width: 100%;
115123
max-width: 900px;
116124
margin: 0 auto;
117125
height: 70vh;
126+
height: 70svh;
118127
min-height: 420px;
119-
background-size: cover;
120-
background-position: center;
128+
background: #0b0b0b;
121129
position: relative;
122130
display: flex;
123131
align-items: flex-end;
@@ -127,15 +135,26 @@ p {
127135
overflow: hidden;
128136
}
129137

138+
.hero-image {
139+
position: absolute;
140+
inset: 0;
141+
width: 100%;
142+
height: 100%;
143+
object-fit: cover;
144+
object-position: 50% 30%;
145+
pointer-events: none;
146+
}
147+
130148
.hero-overlay {
131149
position: absolute;
132150
inset: 0;
133151
background: linear-gradient(180deg, rgba(0, 0, 0, 0.15) 0%, rgba(0, 0, 0, 0.55) 70%);
152+
z-index: 1;
134153
}
135154

136155
.hero-content {
137156
position: relative;
138-
z-index: 1;
157+
z-index: 2;
139158
text-align: center;
140159
padding: 0 1.5rem 2.5rem;
141160
animation: fadeUp 0.8s ease forwards;
@@ -229,10 +248,6 @@ p {
229248
transition: color 0.2s ease;
230249
}
231250

232-
.social-links a:hover {
233-
color: var(--text);
234-
}
235-
236251
.empty-note {
237252
padding: 2rem;
238253
border: 1px dashed var(--line);
@@ -283,7 +298,10 @@ p {
283298

284299
.footer {
285300
width: min(900px, 100%);
286-
padding: 3rem 1.5rem 2.5rem;
301+
padding-top: 3rem;
302+
padding-bottom: 2.5rem;
303+
padding-left: calc(var(--page-gutter) + env(safe-area-inset-left, 0px));
304+
padding-right: calc(var(--page-gutter) + env(safe-area-inset-right, 0px));
287305
display: grid;
288306
justify-items: center;
289307
gap: 1.25rem;
@@ -308,7 +326,6 @@ p {
308326
transition: color 0.2s ease, border-color 0.2s ease;
309327
}
310328

311-
.footer-impressum:hover,
312329
.footer-impressum:focus-visible {
313330
color: var(--text);
314331
border-color: var(--text);
@@ -325,11 +342,6 @@ p {
325342
transition: color 0.2s ease, background-color 0.2s ease;
326343
}
327344

328-
.footer-links a:hover {
329-
color: var(--text);
330-
background-color: var(--hover);
331-
}
332-
333345
.footer-icon {
334346
width: 18px;
335347
height: 18px;
@@ -370,10 +382,57 @@ p {
370382
}
371383
}
372384

385+
@media (hover: hover) {
386+
.nav-link:hover {
387+
color: var(--text);
388+
border-color: var(--accent);
389+
}
390+
391+
.social-links a:hover {
392+
color: var(--text);
393+
}
394+
395+
.footer-impressum:hover {
396+
color: var(--text);
397+
border-color: var(--text);
398+
}
399+
400+
.footer-links a:hover {
401+
color: var(--text);
402+
background-color: var(--hover);
403+
}
404+
}
405+
373406
@media (max-width: 720px) {
407+
:root {
408+
--page-gutter: 1.1rem;
409+
}
410+
411+
.header {
412+
padding-top: 1.75rem;
413+
}
414+
415+
.nav-tabs {
416+
gap: 1.25rem 1.75rem;
417+
}
418+
374419
.hero {
375-
height: 60vh;
420+
height: 58vh;
421+
height: 58svh;
376422
min-height: 320px;
423+
border-radius: 14px;
424+
}
425+
426+
.hero-image {
427+
object-position: 50% 24%;
428+
}
429+
430+
.hero-command {
431+
font-size: clamp(2rem, 9vw, 3rem);
432+
}
433+
434+
.hero-content {
435+
padding: 0 1.25rem 2rem;
377436
}
378437

379438
.section-body {
@@ -383,6 +442,33 @@ p {
383442
.timeline {
384443
padding-left: 1rem;
385444
}
445+
446+
.timeline-item {
447+
animation-delay: 0ms !important;
448+
}
449+
}
450+
451+
@media (max-width: 420px) {
452+
:root {
453+
--page-gutter: 0.95rem;
454+
}
455+
456+
.hero-command {
457+
font-size: clamp(1.85rem, 10vw, 2.6rem);
458+
}
459+
460+
.hero-command .path {
461+
white-space: normal;
462+
overflow-wrap: anywhere;
463+
}
464+
465+
.section {
466+
padding-top: 2.25rem;
467+
}
468+
469+
.section-body {
470+
padding: 1.25rem;
471+
}
386472
}
387473

388474
@media (prefers-reduced-motion: reduce) {

0 commit comments

Comments
 (0)