Skip to content

Commit 95a3881

Browse files
committed
Adding "projects" tab
1 parent 7da6fe1 commit 95a3881

6 files changed

Lines changed: 174 additions & 17 deletions

File tree

src/App.svelte

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import Timeline from "./components/Timeline.svelte";
33
import Projects from "./components/Projects.svelte";
44
import { onMount } from "svelte";
5-
import linkedInImg from "./assets/images/linkedIn.png";
6-
import githubImg from "./assets/images/GitHub.png";
5+
import linkedInImg from "./assets/images/icons/LinkedIn.png";
6+
import githubImg from "./assets/images/icons/GitHub.png";
77
88
let route = "timeline";
99
let animate = false;
@@ -67,7 +67,7 @@
6767
<h3>I'm a Software Enginner based out of NYC<br>Scroll down to see my journey</h3>
6868
<Timeline />
6969
{:else if route === 'projects'}
70-
<Projects title="Projects" />
70+
<Projects />
7171
{/if}
7272
</main>
7373

206 KB
Loading

src/components/ProjectCard.svelte

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
<script>
2+
import { onMount, onDestroy } from "svelte";
3+
export let title = "";
4+
export let image = ""; // imported image path
5+
export let description = "";
6+
export let href = "#";
7+
8+
let isTouch = false;
9+
let revealed = false;
10+
let el; // root anchor element
11+
12+
onMount(() => {
13+
// Detect primary touch devices (no hover, coarse pointer)
14+
if (typeof window !== "undefined" && typeof window.matchMedia === "function") {
15+
const mq = window.matchMedia("(hover: none) and (pointer: coarse)");
16+
isTouch = mq.matches;
17+
// keep in sync if device orientation/profile changes
18+
const handler = (e) => (isTouch = e.matches);
19+
mq.addEventListener?.("change", handler);
20+
21+
// Close overlay when tapping outside on touch devices
22+
const handleDocPointerDown = (e) => {
23+
if (!isTouch) return;
24+
if (!revealed) return;
25+
if (!el) return;
26+
if (!el.contains(e.target)) {
27+
revealed = false;
28+
}
29+
};
30+
document.addEventListener("pointerdown", handleDocPointerDown, true);
31+
32+
onDestroy(() => {
33+
mq.removeEventListener?.("change", handler);
34+
document.removeEventListener("pointerdown", handleDocPointerDown, true);
35+
});
36+
}
37+
});
38+
39+
function handleClick(event) {
40+
// On touch devices: first tap reveals overlay, second tap follows link
41+
if (isTouch && !revealed) {
42+
revealed = true;
43+
event.preventDefault();
44+
}
45+
}
46+
</script>
47+
48+
<a bind:this={el} class="card" class:revealed={revealed} href={href} target="_blank" rel="noopener noreferrer" aria-label={`Open project ${title} in a new tab`} on:click={handleClick}>
49+
<div class="media">
50+
<img src={image} alt={title} loading="lazy" />
51+
<div class="overlay">
52+
<h3>{title}</h3>
53+
<p>{description}</p>
54+
</div>
55+
</div>
56+
</a>
57+
58+
<style>
59+
.card {
60+
display: block;
61+
color: inherit;
62+
text-decoration: none;
63+
border-radius: 12px;
64+
overflow: hidden;
65+
background: #fff;
66+
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
67+
transition: transform 0.15s ease, box-shadow 0.15s ease;
68+
}
69+
.card:focus-visible {
70+
outline: 3px solid #111;
71+
outline-offset: 2px;
72+
}
73+
.card:hover {
74+
transform: translateY(-2px);
75+
box-shadow: 0 6px 20px rgba(0,0,0,0.12);
76+
}
77+
78+
.media {
79+
position: relative;
80+
aspect-ratio: 16 / 9;
81+
background: #f0f0f0;
82+
}
83+
.media img {
84+
width: 100%;
85+
height: 100%;
86+
object-fit: cover;
87+
display: block;
88+
transition: filter 0.2s ease;
89+
}
90+
91+
.overlay {
92+
position: absolute;
93+
inset: 0;
94+
display: flex;
95+
flex-direction: column;
96+
justify-content: flex-end;
97+
gap: 0.25rem;
98+
padding: 1rem;
99+
background: linear-gradient(to top, rgba(0,0,0,0.7), rgba(0,0,0,0.25) 55%, rgba(0,0,0,0));
100+
color: #fff;
101+
opacity: 0;
102+
transition: opacity 0.2s ease;
103+
}
104+
.card:hover .overlay {
105+
opacity: 1;
106+
}
107+
.card.revealed .overlay {
108+
opacity: 1;
109+
}
110+
.card:hover .media img,
111+
.card.revealed .media img {
112+
filter: brightness(0.7) saturate(0.95);
113+
}
114+
.overlay h3 {
115+
margin: 0;
116+
font-size: 1.1rem;
117+
line-height: 1.2;
118+
}
119+
.overlay p {
120+
margin: 0;
121+
font-size: 0.9rem;
122+
color: #eaeaea;
123+
}
124+
</style>

src/components/Projects.svelte

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,61 @@
11
<script>
2-
export let title = "Projects";
2+
import ProjectCard from "./ProjectCard.svelte";
3+
import musicMateImg from "../assets/images/projects/MusicMate.png";
4+
export let title = "These are some projects that I've worked on";
5+
6+
const projects = [
7+
{
8+
title: "MusicMate",
9+
image: musicMateImg,
10+
description: "A Svelte app to explore keys and build/play simple chord progressions with guitar and ukulele diagrams",
11+
href: "https://danrose499.github.io/Music-Mate/"
12+
},
13+
];
314
</script>
415

5-
<div class="projects-container">
16+
<section class="projects">
617
<h1>{title}</h1>
7-
<p>Under Construction — Check back soon!</p>
8-
</div>
18+
<div class="grid">
19+
{#each projects as p}
20+
<ProjectCard title={p.title} image={p.image} description={p.description} href={p.href} />
21+
{/each}
22+
</div>
23+
<div class="note" aria-hidden={true}>
24+
Click on a project to open it in a new tab.
25+
</div>
26+
27+
</section>
928

1029
<style>
11-
.projects-container {
30+
.projects {
1231
min-height: calc(100vh - 64px);
13-
display: flex;
14-
flex-direction: column;
15-
align-items: center;
16-
justify-content: center;
1732
background: #f5f5f5;
18-
text-align: center;
19-
padding: 2rem;
33+
padding: 2rem 1rem 3rem;
34+
max-width: 1100px;
35+
margin: 0 auto;
2036
}
2137
h1 {
22-
margin-bottom: 0.5rem;
38+
text-align: center;
39+
margin: 0 0 1.25rem 0;
2340
}
24-
p {
25-
font-size: 1.25rem;
41+
.grid {
42+
display: grid;
43+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
44+
gap: 1rem;
45+
align-items: stretch;
46+
}
47+
.note {
48+
text-align: center;
2649
color: #666;
50+
font-size: 0.95rem;
51+
margin-top: 1rem;
52+
}
53+
@media (min-width: 640px) {
54+
.projects {
55+
padding: 2.5rem 1.5rem 3.5rem;
56+
}
57+
.grid {
58+
gap: 1.25rem;
59+
}
2760
}
2861
</style>

0 commit comments

Comments
 (0)